补充:C++与Java中引用的区别
既然引用这么好,那么是不是C++就可以跟Java一样不需要指针了?
答案是:并不是这样的,C++中指针和引用是相辅相成的两种语法,缺一不可
而Java中的确不需要指针
为什么呢?
int main() { int a = 10; int& b = a; int c = 16; //请问:b=c;这行代码是什么意思? //选项1:b不再是a的别名,而是成为了c的别名 //选项2:b和a赋值为c b = c; cout << "&a = " << &a << endl; cout << "&b = " << &b << endl; cout << "&c = " << &c << endl; cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; return 0; }
请大家先结合我们所学过的指针的特性来选择一下
选项2是正确的,也就是说b依然是a的别名,只不过a(也就是b)的值被赋值为16而已
但是如果是指针的话,情况就不一样了
int main() { int a = 10; int* b = &a; int c = 16; b = &c; cout << "&a = " << &a << endl; cout << "b = " << b << endl; cout << "&c = " << &c << endl; cout << "a = " << a << endl; cout << "*b = " << *b << endl; cout << "c = " << c << endl; return 0; }
2.引用做返回值(前置知识:栈帧复用)
引用的第二大价值:引用作为返回值
1.传值返回
下面我们来看一个函数
int Count() { int n=0; n++; return n; }
这里return n;
返回的并不是n,而是n的一个拷贝,这个拷贝是一个临时变量,具有常属性,
是一个右值,而不是左值
因为当Count函数调用完了之后Count函数的栈帧会销毁,所以再返回n的时候要先对返回值n进行拷贝,并把拷贝的临时变量返回给调用方,然后Count函数就可以安心的销毁了
2.传引用返回
int& Count() { int n=0; n++; return n; }
出了作用域,返回对象就销毁了,不能用引用返回,否则结果是不确定的
这个例子是为了说明函数栈帧是否被清理是不确定的
传引用返回并用引用接收
如果我们用引用去接收引用的返回值呢?
这样的话会有很多坑点,有很多程序的运行结果是无法解释的,
因为传引用返回本来就是个非常严重的错误,你还用引用接收,那错误更加严重了
这就像是薛定谔的猫,猫到底是死的还是活的你并不知道
到底是随机值还是原数值你也并不知道
这是给大家举的一些样例:
1.
第二次变成了随机值
2.
但是如果我们在两次cout当中再次调用Count函数的话
根据前面讲过的函数的栈帧复用原则,在第二次调用Count函数时新开辟的Count函数栈帧会复用第一次调用Count函数时开辟的栈帧,第二次调用Count函数时n的地址跟第一次的相等
而ret是通过引用接收的n,所以说ret就是n的别名,显然两次ret的地址也相同
所以两次ret的值也是1(VS编译器下)或者随机值
在这里第二次调用Add函数的时候,复用了第一次调用Add函数时所产生的栈帧,所以c的地址不变,值变为了7或者随机值
3.静态变量传引用返回
那么什么时候可以传引用返回呢?
1.堆上的数据
2.静态变量
反正只要不是局部变量就可以传引用返回(只要除了作用域后并没有销毁即可)
//局部的静态变量只初始化一次 //静态变量只在第一次调用的时候被初始化 int& Add1(int a, int b) { static int c = a + b; return c; } int& Add2(int a, int b) { static int d; d = a + b; return d; } int main014() { int& ret = Add1(1, 2); cout << ret << endl;//3 Add1(3, 4); cout << ret << endl;//3 int& ret2 = Add2(1, 2); cout << ret2 << endl;//3 Add2(3, 4); cout << ret2 << endl;//7 return 0; }
4.引用做返回值真正的价值
1:提高效率
2:后面在类和对象当中会有体现,到时候会详细说明的
这里先以静态的顺序表作为一个例子来看一下引用作为返回值的价值
//这里还没有对数组a进行初始化 //静态顺序表 typedef struct SeqList { int a[100]; int size = 100; }SL; #include <assert.h> void SLModify(SL* ps,int pos,int x) { assert(ps); assert(pos >= 0 && pos < ps->size); ps->a[pos] = x; } //应用于at函数 //这里要用引用返回:可以修改返回对象 //在类和对象当中有很广泛的作用 int& SLat(SL* ps, int pos) { assert(ps); assert(pos >= 0 && pos < ps->size); return ps->a[pos]; } int main() { SL sl; //想要修改顺序表,让每个位置的值++ //这样做的话就很方便,比返回指针方便 //后面还会有一些场景是指针解决不了的,必须要使用引用 for (int i = 0; i < sl.size; i++) { SLat(&sl, i)++; //SLat(&sl,i):想打印打印,想赋值赋值,想修改修改 } return 0; }
4.常引用(前置知识:类型转换时产生的临时变量)
引用跟指针类似,也存在const修饰的引用
权限放大只存在于引用和指针当中
const用于形参 修饰引用/指针
也叫做:预防性编程
int main016() { const int a = 10; int& b = a;//err,这时b不能作为a的别名 //因为a是常变量,不能修改,但是如果b又作为a的别名,但是b又不具有常属性,所以不能这样 //这里的本质是权限的放大 //权限可以不变,可以变小,但是不可以放大 const int& b = a;//yes,权限没有变大 int c = 20; const int& d = c;//yes,权限可以缩小 const int& e = 10;//yes,可以的,因为e具有常属性,跟10都是不可以改变的,也就是说权限并未放大 int& f = 10;//err,权限放大 const int f = 10; int g = f;//可以,g是f的值拷贝,跟权限无关,g跟f不是同一块空间 int i = 1; double j = i; double& k = i;//err,为什么不可以呢? const double& l = i;//这里是可以的,本质还是权限放大缩小的问题 //类型转换会产生临时变量!!!!!!,临时变量具有常性,也就是不能再被修改了 //所以加上const就行了 int& m=i;//这里并没有发生类型转换,无需产生临时变量 return 0; }
5.引用的底层实现
引用底层是用汇编实现的,是用指针实现的,也就是说引用在底层上是跟指针一样都开辟了空间的
但是语法上认为引用并没有开辟空间,认为引用就是取别名,语法上并不管底层是如何实现的
日常学习中我们以语法为主,认为引用没有开辟空间的
1.证明引用的底层跟指针一样
int main() { int a = 10; int& b = a; int* ptr = &a; return 0; }
我们查看汇编代码:
发现引用跟指针的汇编代码极其相似,也就证明了引用在底层上是通过指针实现的
2.证明在语法上引用并没有开辟空间
int main() { //证明在语法上引用并没有开辟空间 //语法上不管底层: char ch = 'x'; char& r = ch; cout << sizeof(r) << endl;//1 //底层上r开了4个或者8个字节,因为底层上引用是用指针实现的 //但是语法上r就是char类型,就是1个字节 return 0; }
6.引用和指针的区别
引用和指针(更多是使用上和概念上的区别)
引用更加安全一些,但是引用不是绝对安全,只是相对指针来说更安全
引用也可能会出现"野引用"的情况,此时并不安全
以上就是C++入门-引用的全部内容,希望能对大家有所帮助,谢谢大家!