C++:引用详解
一、引用简说
1、引用:对地址(变量)取别名,对引用的操作与对原变量的直接操作效果完全一样。注意与指针的区别。
2、声明方法:类别标识符 &引用名 = 目标变量名
3、声明引用时,必须同时对其初始化。
4、引用声明一旦初始化,不能再将该引用名作为别的引用名的别名。
5、对引用名求地址,就是对目标变量求地址,即&r_var与&var相等。
6、不能建立数组的引用。
#include <iostream>
using namespace std;
int main()
{
int var = 10;
int tmp = 20;
int *p_var = &var; // 在栈上开辟一块新的指针内存,用于存放变量var的地址。
// int &r_var; // 错误声明,未初始化。
// 定义引用r_var,它是变量var的引用,即别名。未开辟新内存,r_var直接指向var的地址。
int &r_var = var;
// 赋值操作,将tmp的值拷贝一份到r_var(var)所在内存,此时var与r_var的值都为20.
r_var = tmp;
return 0;
}
二、函数三种传参方式和三种返回值方式
1、值传递:形参是对实参的一份临时拷贝,形参内存中存放的是实参的值,执行交换时,交换的是形参,因此不会交换实参。
#include <iostream>
using namespace std;
void swap(int var01, int var02)
{
int tmp = var01;
var01 = var02;
var02 = tmp;
}
2、址传递:形参是对实参的一份临时拷贝,形参内存中存放的是实参的地址,执行交换操作时,交换的是实参(解引用),因此实现了实参交换。
void swap(int *p_var01, int *p_var02)
{
int tmp = *p_var01;
*p_var01 = *p_var02;
*p_var02 = tmp;
}
3、引用传递:形参执行的是引用操作,形参r_var01与实参指向同一块内存,形参r_var02与实参指向同一块内存,执行交换操作时,交换的是实参。
void swap(int &r_var01, int &r_var02)
{
int tmp = r_var01;
r_var01 = r_var02;
r_var02 = tmp;
}
4、函数返回变量值:与函数传参值传递原理相同。在函数结束返回时,将局部变量值拷贝一份给临时变量,然后将该临时变量返回给调用函数。
#include <iostream>
using namespace std;
int test(int var)
{
int a = var * var;
return a;
}
int main()
{
int a = 10;
int s = test(10);
cout<<"s = "<<s<<endl; // 打印100.
return 0;
}
5、函数返回变量地址:与函数传参址传递原理相同。调用函数结束时,开辟一块int*型临时内存空间(匿名)用于存放局部变量a的地址,释放局部变量a的内存,再将匿名临时内存空间中的数据(局部变量a的地址)拷贝一份到指针变量s的内存地址中。由于函数结束时,函数内的所有局部变量内存都已被回收,因此再利用指针s去读取局部变量a中的数据自然不成功。
#include <iostream>
using namespace std;
int* test(int var)
{
int a = var * var;
return &a;
}
int main()
{
// [Warning] address of local variable 'a' returned [-Wreturn-local-addr]
int* s = test(10);
return 0;
}
6、函数返回变量的引用:调用函数结束时,释放局部变量a的内存,通过引用对局部变量a取别名为s。由于局部变量a的内存已经释放,因此再利用别名去读取已被释放内存空间中的数据自然不能成功。
#include <iostream>
using namespace std;
int& test(int var)
{
int a = var * var;
return a;
}
int main()
{
// [Warning] address of local variable 'a' returned [-Wreturn-local-addr]
int& s = test(10);
return 0;
}
三、常引用
1、声明方法:const 类别标识符 &引用名 = 目标变量名
2、const声明的引用,不能通过引用对目标变量的值进行修改。
int main()
{
int var = 10;
const int &p_var = var;
// p_var = 20; // 错误,p_var 为常引用变量,不能对目标变量进行修改。
var = 20; // 正确。
return 0;
}
3、通过常引用声明的函数形参,可直接调用常量。引用型参数应在能被定义为const的情况下,尽可能定义为const。
#include <iostream>
using namespace std;
void test01(int &var)
{
cout<<var<<endl;
}
void test02(const int &var)
{
cout<<var<<endl;
}
int main()
{
int a = 10;
test01(a); // 正确,打印10.
// [Error] invalid initialization of non-const reference of type 'int&' from an
// rvalue of type 'int'
test01(10);
test02(a); // 正确,打印10.
test02(10); // 正确,打印10.
return 0;
}
四、引用作为返回值
1、引用函数作为返回值,须在函数声明及函数定义返回类型标识符后函数名前加上&。
函数声明:类型标识符 &函数名(形参列表及类型说明);
函数定义:类型标识符 &函数名(形参列表及类型说明){函数体}
2、不能返回临时变量的引用。具体解释参考上面函数返回变量的引用部分。
3、若函数的返回值是引用,则该函数的调用可以作为赋值表达式的左值。
#include <iostream>
using namespace std;
int tmp; // tmp为全局变量。
int test01(int var)
{
tmp = var * var;
return tmp;
}
int &test02(int var)
{
tmp = var * var;
return tmp;
}
int main()
{
int a = 10;
// 系统生成返回值tmp的一份副本(匿名),将匿名副本中的数据拷贝到s内存中。
// s和tmp各有的内存。
int s = test01(a);
// 系统生成返回值tmp的一份临时副本(匿名),通过引用对匿名副本内存空间取别名s。
// 不能对匿名临时副本空间取别名。该行代码非法。
// int &s = test01(a);
// 系统不生成返回值tmp的副本,将全局变量tmp中的数据拷贝到s内存中。s和tmp各有
// 各的内存。
int s = test02(a);
// 系统不生成返回值tmp的副本,通过引用对全局变量tmp取别名s。s和tmp为同一块内
// 存区。
int &s = test02(a);
cout<<s<<endl;
return 0;
}
五、总结
1、引用的本质是一个指针常量(int* const p),因此声明引用时必须初始化,且之后不可修改其指向;
2、引用的目的主要用于在函数参数传递中,解决大块数据或对象传递效率和空间不如意的问题,同时可以避免利用指针实现该功能的繁琐。在定义引用参数时,尽可能加上关键字const以保证传入数据的安全性(保证传入数据不在函数内部被修改)。