1 变量名的回顾
在C和C++中,变量名的实质是什么了?
变量名的实质是一段连续存储空间的别名,是一个标号(名牌号)如下图所示,程序中通过变量来申请并且命名内存空间,通过变量的名字可以使用存储空间。但是这可以提出一个问题。对一串连续的内存空间只能取一个别名吗?这就引出了C++中引用的概念。
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
",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
", t1.age); //33
printfT3(t1) ;// pT是形参 ,t1 copy一份数据 给pT //---> pT = t1
printf("t1.age:%d
", 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背着我们在背后做了什么勾当了?
- 引用在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.
结果如下:
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
修饰引用,也会分配存储空间