一、引用概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。相当于给某人取外号,王五是他,老王也是他,并没有产生一个新的人,两个名字指的是同一个人。
void TestRef()
{
int a = 10;
int& ra = a;
printf("%d\n", a);
printf("%d\n", ra);
printf("%p\n", &a);
printf("%p\n", &ra);
}
注意: 引用类型必须和引用实体是同种类型的
二、引用的特性
1. 引用在定义时必须初始化
引用时必须指明是谁的引用,不能空有引用
2. 一个变量可以有多个引用
一个变量可以有多个引用,就像一个人有多个外号一样
3. 引用一旦引用一个实体,再不能引用其他实体
引用应用一个实体,就不能再应用其他实体,老王只能指定一个人(在一个区域里面),其他人只能叫小王或者其他,不然一个老王 会有很多人答应。
思考一下下面这题:
int main()
{
int x = 1, y = 0;
int*p1 = &x;
int*p2 = &y;
int*&p3 = p1;
*p3 = 10;
p3 = p2;
return 0;
}
运行完p1,p2,p3的值分别是多少?
仔细思考,就能想明白引用的本质。(不给答案,自己运行试试)
三、常引用(重要)
C语言学过const修饰,具有常变量的属性,这里修饰引用,可以改变引用的权限,使其不能用作改变,只用读取。
void TestConstRef()
{
const int a = 10;
const int& ra = a;
const int& b = 10;
double d = 12.34;
const int& rd = d;
}
double在赋值给int时会发生一个隐式类型转换,会产生一个临时变量,而这个临时变量具有常属性,不可更改,用const修饰后,int也具有常属性,就可以接收临时变量的值,类似与char与int比较时会发生类型提升。
- 常引用的好处:
1 .不变或者缩小你的读写权限,保护数据; 2 .传参数时,使用const修饰,可以保护形参; 3 .传引用可以减少传值传参的拷贝; 总结:函数传参如果想减少拷贝用了引用传参,如果函数中不改变这个参数最好用const 引用传参
四、引用的使用场景(非常重要,仔细理解,仔细思考!!!)
1,做参数
void Swap(int& left, int& right) {
int temp = left;
left = right;
right = temp;
}
使用场景:
- 输出型参数。void swap(int& a, int& b)
- 当参数变量比较大,相对于传值,引用做参数可以较少拷贝void StackPrint(const Stack& st)
ps:如果函数中不改变形参的话,建议用const Type & 好处:1.在函数中保护形参,避免被误改。2.可传普通对象,还可以传const对象
2,做返回值(难点)
看看下面的代码:
#include<iostream>
using namespace std;
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "ret " << "= " << ret << endl;
return 0;
}
结果应该是多少? 是不是和你想的不一样;
这 里 ret 是 取 c 的 引 用,c在Add函数返回时创建了临时变量,由于返回值是int&,所以临时变量的类型是int&,再取临时变量的别名ret,所以ret的值,始终是临时变量的值,再次调用Add函数时,由于我的编译器并未清理Add栈帧的空间(不同编译器的结果不一样,这里测试环境是Vs2013),所以临时变量的位置没有改变,但是值发生了变化,最终导致结果发生了变化!所以,此处正确的结果是取决于平台销毁栈帧时是否会清理栈帧的空间!!!
3、正确使用引用做返回值
- 出了函数的作用域,ret变量会销毁,就不能用引用返回;
- 出了函数的作用域,ret变量不会销毁,就可以用引用返回(静态修饰);
返回值引用的意义:
- 引用返回的价值的是减少了拷贝;
- 方便实现类似operator [ ];
五、 传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
#include <time.h>
struct A{
int a[10000];
};
void TestFunc1(A a){}
void TestFunc2(A& a){}
void TestRefAndValue()
{
A a;
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; }
六、值和引用的作为返回值类型的性能比较
#include <time.h>
struct A{ int a[10000]; };
A a;
A TestFunc1() { return a;}
A& TestFunc2(){ return a;}
void TestReturnByRefOrValue()
{
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl; }
-这里暂时也放出代码,可以自己比较~
七、引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
int main()
{
int a = 10;
int*pa = &a;
int& ra = a;
cout << "&a = " << &a << endl;
cout << "*pa = " << pa << endl;
cout << "&ra = " << &ra << endl;
return 0;
}
- 引用与地址相同,语法上是和实体公用一块空间,底层实现上是有空间,因为引用是按照指针方式实现的
- 下面是汇编代码
可以看出,引用时与取地址的步骤相同
八、引用和指针的不同点
- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
- 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
- C++学习ing,持续更新,欢迎各位指点改正~ (点个赞吧)
|