就学习C++语言来说,引用是必然要理解的内容,今天这波文章就给大家肝一波引用的深入理解与使用,帮助大家理解应用,以便更好的学习C++语言
引用的概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它 引用的变量共用同一块内存空间,俗称:取别名
区别C语言,& 符号在C++当中是引用符号,不是取地址符号
一. 先看一个简单的引用程序
int main()
{
int a = 10;
int& b = a;//此时b和a指向同一块空间
int& c = b;//再次为b取别名,此时 a b c 的地址是同一块空间
return 0;
}
二.引用的特征
1. 引用必须在定义的时候初始化
int& d;//此时错误,需要进行初始化
2. 一个变量可以有多个引用
int a = 10; int& b = a; int& c = b;//a可以取别名叫b,b又可以取别名叫c...
3. 引用一旦引用一个实体,再不能引用其他实体
int e = 20; b = e;//b在上面引用了a,就不能在引用e,此时是赋值操作
三. 深入理解常引用
先看一个代码,思考一下为什么会报错呢?
int main()
{
double d = 5.5;
int& f = d;//此时报错
return 0;
}
我们知道,数据类型之间是存在隐式类型转换的,存在截断或提升,隐式类型转换在中间会就会产生临时变量
所以,在上图当中可以当作 d 中的 5 拷贝给了临时变量 然后又给了int,临时变量具有常性,不可更改,即需要加上const使权限一样
所以取别名的原则是,对原引用变量,权限只能缩小(或不变),不能放大
int main()
{
const int x = 20;//加上const,此时x是只读
int& y = x;//int是可读可写,权限放大
const int& y = x;//权限不变
int c = 30;
const int& d = c;//权限缩小,只读
return 0;
}
后面学习类和对象后,我们传参如果写成普通引用,很多参数无法传递
void func(int& x)//void func(const int& x)
{}
int main()
{
int a = 10;
double d = 5.5;
int f = d;
const int& e = d;
func(a);
func(d);//此时往下权限放大都传不了,在上面func函数要使用const
func(e);
return 0;
}
四.引用的使用场景
1.先看一个传值、传引用的效率比较程序
#includestruct A { int a[10000]; }; void TestFunc1(A aa) {} void TestFunc2(A& aa) {} 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; } int main() { TestRefAndValue(); return 0; }
运行结果:
TestFunc1(A)-time:7
TestFunc2(A&)-time:0当参数或者返回值类型非常大时,用值作为参数或者返回值类型,效率是非常低下的
2.引用做输出型参数,减少拷贝,提高效率
void swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
void swap(double& x, double & y)
{
double tmp = x;
x = y;
y = tmp;
}
2.引用做返回值(重点)
先看一下下面这个函数,根据我们所了解的C语言普通传值,函数运行完后会自动销毁栈帧
int count()
{
int n = 0;
n++;
//...
return 0;
}
所以我们后来学习了静态或全局变量,在前面加上static
int count()
{
static int n = 0;
n++;
//...
return 0;
}
但是注意:在项目当中,因为后面涉及到线程安全的问题,不太常用静态或全局变量
最后我们选择引用来做返回值
int& count()
{
static int n = 0;
n++;
return n;//返回的是n的别名,不会再临时产生临时变量
}
int main()
{
cout << count() << endl;
cout << count() << endl;
cout << count() << endl;
}
做返回值和上面隐式类型转换一样,同样会产生临时变量,增加拷贝,使用引用做返回值就会减少拷贝,提高效率,这在后面STL学习当中会经常用到
五. 引用和指针的区别
1. 引用在定义时必须初始化,指针没有要求 2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型 实体 3. 没有NULL引用,但有NULL指针 4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节) 5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小 6. 有多级指针,但是没有多级引用 7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理 8. 引用比指针使用起来相对更安全
从语法角度深入分析:
int main()
{
int a = 10;
//语法角度而言:ra是a的别名,没有额外开空间
int& ra = a;
ra = 20;
//语法角度而言:pa存储a的地址,pa开了4/8字节空间
int* pa = &a;
*pa = 20;
return 0;
}
从底层角度深入分析:
int a = 10; 00007FF71CB01F7D mov dword ptr [a],0Ah //语法角度而言:ra是a的别名,没有额外开空间 int& ra = a; 00007FF71CB01F84 lea rax,[a] 00007FF71CB01F88 mov qword ptr [ra],rax ra = 20; 00007FF71CB01F8C mov rax,qword ptr [ra] 00007FF71CB01F90 mov dword ptr [rax],14h //语法角度而言:pa存储a的地址,pa开了4/8字节空间 int* pa = &a; 00007FF71CB01F96 lea rax,[a] 00007FF71CB01F9A mov qword ptr [pa],rax *pa = 20; 00007FF71CB01F9E mov rax,qword ptr [pa] 00007FF71CB01FA2 mov dword ptr [rax],14h return 0; 00007FF71CB01FA8 xor eax,eax
我们发现在底层实现上实际是有空间的,因为引用是按照指针方式来实现的,初学者可以先把引用理解为指针
希望这篇文章大家有所收获,我们下篇见
暑期编程PK赛 得CSDN机械键盘等精美礼品!


