C++引用详解---完全零基础入门

                                                           引用(reference)

先给大家讲个笑话:

-------有一个顽皮的男生给同班的一位叫满美丽女生取外号叫胖猪,该女生哭着告到老师那里,老师答应对该男生进行批评教育。
------第二天上课,老师在班上讲话:我们班里有位同学太没有礼貌了,随便给别的同学起外号,总不能人家像啥就叫啥吧。
------在这个笑话中,针对这个女生,有两个称号,姓名叫满美丽,外号叫胖猪。一般来讲,只要喊着两个称号中的一个,这个女生都会做出回应。

这个笑话跟引用有什么关系呢?对的,没错,引用就是起外号。起别名

一、引用的定义

1.1 对单变量进行引用

语法格式:类型名 &外号 = 变量名;

C++引用详解---完全零基础入门_第1张图片

int main()
{
	//引用的定义
	int a = 5;//我们首先定义一个int型变量,并赋值为5
	int &xiaoming = a;//然后,我们给变量a起一个外号,哦不,用一个引用,叫xiaoming。
	//下面,我们分别看一下这两个变量的地址和对应的值
	cout <<"我们首先看一下两个变量是不是一样的值"<<endl;
	cout << "xiaoming = " << xiaoming << endl;
	cout << "       a = " << a << endl;
	//这是毋容置疑的,他们肯定都是5
	cout << "我们再看一下他们的地址" << endl;
	int *ptr_a = &a;//这边的这个&是取址符的意思
	int *ptr_xm = &xiaoming;
	cout << "xiaoming = " << ptr_xm << endl;
	cout << "       a = " << ptr_a << endl;
	cout << "我们再看一下他们的地址的偏移量" << endl;
	cout << "xiaoming+1 = " << ptr_xm +1<< endl;
	cout << "       a+1 = " << ptr_a +1<< endl;
}

C++引用详解---完全零基础入门_第2张图片
------从这边大家就可以看出来,这两个变量不管是变量对应的值,还是地址,还是地址的偏移量,都是一样的。这充分说明了,他们就是同一个东西,就是胖猪和满美丽之间的关系。

为了充分证明我们所说的理论的正确性,再举一个小栗子,我们给xiaoming赋值。
C++引用详解---完全零基础入门_第3张图片
代码如下:

int main()
{
	//引用的赋值
	int a = 5;
	int &xiaoming = a;
	//现在,我们先修改xiaoming的值,看看a的值会不会发生变化
	cout << "修改之前" << endl;
	cout << "xiaoming = " << xiaoming << endl;
	cout << "       a = " << a << endl;
	xiaoming = 20;
	cout << "修改之后" << endl;
	cout << "xiaoming = " << xiaoming << endl;
	cout << "       a = " << a << endl;
}

这充分证明了,引用,其实就是起外号。
但是需要注意的是:

  1. 外号的类型名和被引用变量的类型名是一样的。下面这种情况时错误的
    int a = 10;
    Long &xiaoming = a;
    这就是错误的了。

  2. 此外,引用不是初始化,引用初始化之后就不能修改了。

int main()
{
	int a = 5;
	int &xiaoming = a;
}

------一旦初始化之后,就不能修改了。因为满美丽这个人虽然有一定的忍耐性,但是脾气还是很大的。你可以给我起一个外号,但是你不能给我起好多,不然,会生气,会报错。

1.2 对数组进行引用

直接看代码:

  • 第一种方法

int main()
{
	//给数组
	int a[10];
	//先给数组赋值。
	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		a[i] = i;
	}
	//使用引用给数组起外号
	int(&xiaoming)[10] = a;
	cout << "使用外号访问数组元素" << endl;
	for (int i = 0; i < sizeof(xiaoming) / sizeof(int); i++)
	{
		cout << xiaoming[i] << " " ;
	}
	cout << endl;
	//这边我们复习一下用指针访问数组
	cout << "使用指针访问数组元素" << endl;
	//使用指针访问数组元素,首先需要
	int *ptr_xm = xiaoming;//此处是定义一个指针,并将其指向xiaoming
	//如果想获取地址,用*ptr_xm =& xiaoming;
	for (int i = 0; i < sizeof(xiaoming) / sizeof(int); i++)
	{
		cout << *ptr_xm+i<<" ";
	}
	cout << endl;
}

  • 第二种方法:

------这种方法其实不强求大家掌握,但是需要知道,以后在看代码的时候万一遇到了,就不至于手忙脚乱了。

int main()
{
	//给数组
	int a[10];
	//先给数组赋值。
	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		a[i] = i;
	}

	//使用引用给数组起外号
	int(&xiaoming)[10] = a;
	cout << "使用外号访问数组元素" << endl;
	for (int i = 0; i < sizeof(xiaoming) / sizeof(int); i++)
	{
		cout << xiaoming[i] << " " ;
	}
	cout << endl;
	//这边我们复习一下用指针访问数组
	cout << "使用指针访问数组元素" << endl;
	//使用指针访问数组元素,首先需要
	int *ptr_xm = xiaoming;//此处是定义一个指针,并将其指向xiaoming
	//如果想获取地址,用*ptr_xm =& xiaoming;
	for (int i = 0; i < sizeof(xiaoming) / sizeof(int); i++)
	{
		cout << *ptr_xm+i<<" ";
	}
	cout << endl;
}
}
//第二种定义外号的方法
	typedef int(arrxiaoming)[10];
	arrxiaoming & xiaoming2 = a;
	cout << "使用第二种其外号访问数组元素" << endl;
	for (int i = 0; i < sizeof(xiaoming2) / sizeof(int); i++)
	{
		cout << xiaoming2[i] << " ";
	}
	cout << endl;

1.3 引用的作用

------关于值传递地址传递引用传递。一个非常常见的例子,交换两个数的值。
------首先,对于值传递,我们只希望在调用这些变量的(或者这些变量所在的函数),而不修改变量值,那么这时候我们只进行值传递就行了。举个例子:
------开发游戏的时候,武器是有固有属性的,我们可以把武器写成一个函数,而武器的一些伤害,攻击力加成等数据在使用的时候都是不想改变的(当然耐久先不提)。这个时候,我们只需要进行值传递即可。

C++引用详解---完全零基础入门_第4张图片
------我们分别就值传递,地址传递,引用传递构造三个test函数,然后再main函数中调用即可。

void test_zhi(int a,int b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
	cout << "值传递函数内部传递" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

//创建地址传递函数
void tset_dizhi(int * a, int *b)//因为这个函数要接收的是地址,所以里面应该用*定义,表示定义该变量是地址。
{
	//地址传递
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
	cout << "地址传递函数内部传递" << endl;
	cout << "a = " << *a << endl;
	cout << "b = " << *b << endl;
}
void test_yiny(int &a1,int &b1)//因为要接收的是引用,因此要使用&符号定义变量。引用就是起外号,因此呢,这边我们区分一下。
{//引用传递。
	//既然是起外号,那其实就跟操作
	int temp;
	temp = a1;
	a1 = b1;
	b1 = temp;
	cout << "引用传递函数内部传递" << endl;
	cout << "a = " << a1 << endl;
	cout << "b = " << b1 << endl;
}

int main()
{
	int a = 10;
	int b = 20;
	test_zhi(a, b);
	cout << "调用值传递函数结果" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	
	tset_dizhi(&a, &b);
	cout << "调用地址传递函数结果" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

	test_yiny(a, b);
	cout << "调用引用传递函数结果" << endl;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

C++引用详解---完全零基础入门_第5张图片
------从上面的三个函数,以及在主函数中的调用我们可以看出,值传递无法改变变量的值,只能在函数体内修改变量。
------而指针传递引用传递都是可以修改变量值的。

  • 这边大家要分清逻辑转换关系呀。这边我说的没错,代码也没错。指针传递和引用传递确实改变了传递的值。至于大家后面看引用传递调用后的还是10
    20 。那是因为之前指针传递已经修改成了20 10 。因此引用传递进行了再次修改。

二、引用的一些注意事项

2.1. 引用必须应用一块合法的内存空间。

那么什么是合法的内存空间呢?这也是为什么我们前面说的,为什么引用必须初始化。

如果为我们定义了一个变量a 并把a赋值,那么这个a就是合法的,且初始化了。

int a = 10;
int &a1 = a;
这个时候,上面这串代码是不会报错的。因为a已经开辟出了一块内存空间,因此是合法的。
但是,如果使用下面这种情况,就不对了。

Int &a = 10;
这肯定是会报错的,因为10不是合法的内存空间。

2.2. 不能返回局部变量的应用

------这边大家都清楚,应该知道。因为局部变量是有使用寿命的,即该变量只在该函数周期内运行,该函数停止,则这些变量就被收回了。因为引用就是给另一个变量起外号,打比方:某天放学后,你又想去调戏满美丽,于是喊胖猪,不过这时候满美丽已经放学回家了,你在学校里不管怎么喊,你都喊不到她。是吧? 学校就相当于一个函数,满美丽就是这个函数的内部变量,函数结束就是放学了,满美丽回家就是局部变量被收回。
C++引用详解---完全零基础入门_第6张图片

int& dowork()
{
	int a = 10;
	return a;
}
void test1()
{
	int &ret = dowork();
	cout << "ret = " << ret << endl;
}
int main()
{
	//引用的一些需要注意的地方
	//调用test1()
	test1();
}

这个时候,大家运行上面的代码是不会出错的。为什么呢?

在这里插入图片描述
按照道理来讲,执行完dowork()之后,a就应该被回收了,可是为什么还会输出10,而不是乱码呢?这是因为编译器错了优化,可以容许你错一次,但是如果你多次打印,那么肯定会出问题的。

比如你把test1()中的打印多调用几次

void test1()
{
	int &ret = dowork();
	cout << "ret = " << ret << endl;//编译器优化。
cout << "ret = " << ret << endl;
cout << "ret = " << ret << endl;
cout << "ret = " << ret << endl;
cout << "ret = " << ret << endl;
}

这样就会报错了。也不是报错,就是乱码了
C++引用详解---完全零基础入门_第7张图片
这个时候,如果我们之一想要返回引用的局部变量怎么办呢?
方法是:将其变成静态变量。
看代码

int& dowork()
{
	Static int a = 10;
	return a;
}

但是我们不推荐这么做,感觉没意思。为什么呢?
因为如果函数的返回值是引用,我们可以将其作为左值。
什么事左值?a = 10;a就是左值。
惊讶不?那个可是函数呀,为什么可以这么做?别忘了,引用其实就是起外号。

void test2()
{
	int &ret = dowork();
	dowork() = 1000;//这就相当于让a = 1000
}

这只是一个简单的应用,大家需要理解,以后还有更高深的开发。

三、引用的本质

之前有说过,引用有点类似于地址传递。

应用的本质就是一个指针常量。

不多说,结合代码来解释。

void test3(int &ref)
{
	ref = 100;//ref是引用,转化为*ref = 100
}
int main()
{
	//发现引用,转换为int* const ref_a = &a
	int a = 10;
	int &ref_a = a;//这一步,系统自动转换为int* const ref_a = &a;.这个地方的&是取址符。这也说明了引用不许初始化的原因。
	ref_a = 20;//系统内部发现ref_a是引用,自动帮助我们转化为*ref_a = 20;
	cout << "    a = " << a << endl;
	cout << "ref_a = " << ref_a << endl;
	test3(a);
}

四、引用的使用场景

4.1 指针的引用

利用指针引用来开辟空间。

这块以后讲,因为我现在还讲不出清楚,还有好多后续知识

4.2 常量的引用

前面我们说过,不能引用非法空间。即:

Test1()
{
int &ref = 10;//引用不合法的内存,不可以
}

这样写在编译器里肯定是错误的。
但是,如果我们换成下面的格式,就不会出错了

Test2()
{
Const int &ref = 10;//加入const后,编译器将其处理为:int temp = 10;cons tint &ref = temp;
//这个时候,temp的值是无法直接修改的:
//temp = 20;//这样直接报错的,但是我们可以绕过编译器的检查,使用指针进行修改
int * p = (int*)&ref;//这边是取地址

*p = 1000;
cout<<"ref = "<<ref<<endl;

}

常量引用使用场景,用来修饰形参。

你可能感兴趣的