C++引用专题总结

1 变量名的回顾

在C和C++中,变量名的实质是什么了?

变量名的实质是一段连续存储空间的别名,是一个标号(名牌号)如下图所示,程序中通过变量来申请并且命名内存空间,通过变量的名字可以使用存储空间。但是这可以提出一个问题。对一串连续的内存空间只能取一个别名吗?这就引出了C++中引用的概念。

image

2 引用的概念

  • 在C++中新增加了引用的概念。
  • 引用可以看做一个已定义变量的别名。
  • 引用的语法: Type& name = var;
void main()
{
	int  a = 10; //c编译器分配4个字节内存 a内存空间的别名
	int &b = a;  //b就是a的别名
	a = 11;      //直接赋值
	{
		int *p = &a;
		*p = 12;
		printf("a %d \n",a);
	}
	b = 14; 
	printf("a:%d b:%d", a, b);
	system("pause");
}
// 对a和b的操作其实是一样

这里必须注意的是,引用是C++编译器对于C的扩展,在C编译的环境中是无法使用的。

3 引用做函数参数

  • 普通引用在声明时必须用其他的变量对其进行初始化。
  • 引用作为函数参数声明时不进行初始化。

对于复杂类型的引用其实也和普通的变量是一致的。举个栗子:

//复杂数据类型的引用
struct Teacher
{
	char name[64];
	int age ;
};

void printfT(Teacher *pT)
{
	cout<<pT->age<<endl;
}

//pT是t1的别名 ,相当于修改了t1
void printfT2(Teacher &pT)
{
	//cout<<pT.age<<endl;
	pT.age = 33;
}

//pT和t1的是两个不同的变量
void printfT3(Teacher pT)
{
	cout<<pT.age<<endl;
	pT.age = 45; //只会修改pT变量 ,不会修改t1变量
}
void main()
{
	Teacher t1;
	t1.age = 35;

	printfT(&t1);

	printfT2(t1); //pT是t1的别名
	printf("t1.age:%d \n", t1.age); //33

	printfT3(t1) ;// pT是形参 ,t1 copy一份数据 给pT     //---> pT = t1
	printf("t1.age:%d \n", t1.age); //35
	
	cout<<"hello..."<<endl;    
	system("pause");
	return ;
}

那么有人就会疑惑,这种操作我在C中就有指针代替,为什么要有引用这种概念了?

4 引用的意义

  • 引用作为其他变量的别名,作为这一点来说,它在一些场合可以代替指针
  • 引用相对于指针而言具有更好的可读性和实用性

我们可以来比较两个代码,看看指针和引用哪一种方式更为直观和便于阅读。

int swap(int &a, int &b)
{
	int temp = a;
	a = b;
	b = temp;
	return 0;
}
int swap(int *a, int *b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
	return 0;
}

想必大家都有自己的答案吧。

5 引用本质的思考

我们说引用是变量的别名,这个是很直接可以观察和测试,但是C++编译器这个bitch背着我们在背后做了什么勾当了?

image

  • 引用在C++内部的实现是一个常指针,Type &name === Type *const name
  • C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。
  • 从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏
  • 当我们使用引用语法的时,我们不去关心编译器引用是怎么做的,当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的,从实际出发。

6 函数的返回值是引用

当函数的返回值是引用时,这个是引用的一个难点问题。

  • 当函数的返回值为栈变量时,不能成为其他引用的初始化值,不能成为左值。
  • 若返回是静态变量或者全局变量,可以成为其他引用的初始值,也可以成为左值使用,也可以作为右值使用。

举个栗子

int getValue1()
{
	int a;
	a = 10;
	return a;
}

//基础类型a返回的时候,也会有一个副本
int& getValue2()
{
	int a;
	a = 10;
	return a;
}

int main()
{	
	int a1 = getValue1();
	int a2 = getValue2();
	int &a3 = getValue2();
	//这三个里面那几个是正常的
	printf("a1: %d, a2: %d, a3: %d",a1, a2, a3);
	return 0;
}

这道我觉的很有参考意义,首先int a1 = getValue1();是很正常的方法,肯定没有什么问题,然后int a2 = getValue2();因为getValue2()的返回值是一个引用,所以你用什么方式来接这个引用编译的做法就不一样。

如果你使用变量来接,如int a2 = getValue2();这个编译就直接把引用的值取出来,作为副本直接赋值给a2,如果你使用引用初始化的方式来接返回值,如 int &a3 = getValue2();这个时候编译就返回引用的本质,引用的本质之前讨论了是一个常指针,所有a3就是一个地址,这个时候getValue2()结束,局部变量解析,但是由于编译不同和编译方式的不同,其局部变量可能被覆盖也可能保持不变。这个时候使用printf函数,C++编译器判断a3是一个引用,就偷偷在a3前加一个*这样就取出a3地址的值就是10.

结果如下:

image-20211022222147523

7 常引用

  • 常引用是变量的引用只有只读属性,不能通过引用去修改变量。

  • 常引用的初始化分为两种情况

    • 第一种是用变量去初始常引用,int x = 10; const int & y = x;

    • 第二种是用字面量初始常量引用

      int &m = 41; //这种方式是错误,字面量是没有地址,引用是内存地址的别名,没有地址没法取的。

      const int&m = 41; // 在前面加入const 变成常引用就可以通过编译,本质还是编译那个bitch会偷偷给字面量分配空间

7.1结论

C语言中的const变量:

  • C语言中的const变量是只读变量,有自己的存储空间。

C++中的const变量:可能分配空间也有可能不分配空间。

  • const为全局变量时,并且在其他的文件中需要使用,会分配内存空间
  • 当使用&操作符,取const常量地址时,也分配内存空间。
  • const int &a = 10; const修饰引用,也会分配存储空间
posted @ 2021-10-22 22:53  Kroner  阅读(96)  评论(0编辑  收藏  举报