引用
引用
引用是C++新增的复合类型,引用是以定义变量的别名,用与函数的形参和返回值
声明引用的语法
数据类型 &引用名 = 原变量名
⚠
- 引用的数据类型应与原变量名的数据类型一致
- 引用名和原变量名可以互换(内存地址相同)
- 必须在声明时初始化,初始化后不可更改
- C语言中&只表示取地址,C++中&还可表示引用
引用的本质
引用是指针常量的伪装
数据类型 *const 变量名 = 原变量名指代地址
指向的变量(对象)不可改变,定义的同时必须初始化;可通过解引用的方法修改内存中的值
int &ra = a
可替换为
int *const ra = &a
引用本质上与指针一样
程序员拥有引用,但编译器仅有指针(地址)
⚠引用的底层逻辑实际上和指针一样,不要相信有别名,不要认为引用可以节省一个指针的空间,因为编译器还会把引用解释为指针
引用用于函数的参数
把函数的形参声明为引用,调用函数时形参将成为实参的别名(操作的内存区域一样)
引用的本质是指针,传递的是变量的地址,在函数中修改形参会影响实参。
❗指针常用于函数的形参和动态分配
❗引用为常量指针,只能用于函数的形参
使用引用的优点
- 传引用的代码更简洁
- 传引用不必使用二级指针
void func(int **p) //传地址,实参为指针的地址,形参为二级指针 *p = new int (1); //*p为地址,**p为值
void func(int *&p) //传引用,实参为指针,形参为指针的别名
p = new int (1);
- 引用的属性和特别之处
引用的形参与const
const :普通变量、指针、函数形参加上const修饰表不可改变
这里看一个例子
void func1(string name,int age){ //传值
}
void func2(string *name,int *age){ //传地址}
void func3(string &name,int &age){ //传引用
}
int main(){
string name = "zhangsan";
int age = 20;
func1(name,age);
func2(name,age);
func3(name,age);
}
上面的代码调用函数时,传入的是变量(有地址),运行没有问题,但是当调用时传入的是常量(无地址),传地址和传引用就会运行出错
func1("zhangsan",20); //编译成功
func2("zhangsan",20); //编译失败,原因int无法转换为*int,类型不匹配
func3("zhangsan",20); //编译失败,原因int无法转换为&int,类型不匹配
⚠如果引用(指针不可以)的数据类型不匹配,当引用为const修饰时,C++将创建临时变量,让引用指向临时变量
int &a = 8; //编译失败,原因8为常量,没有地址
const int &a = 8; //编译成功
//被const 修饰时,C++作特别的处理
int temp = 8;
int &a = temp;
const int &a = 8;
等价于int temp = 8 ; int &a = temp;
❓什么时候会创建临时变量
-
引用被const修饰时
-
数据类型是正确的,但不是左值
const int &a = 1 ✅
const int &a = "zhangsan" ❎
-
数据类型不正确,但是可以转换为正确的数据类型
例如:
字符常量转换为int型('X' ——> 88 )
C风格字符串转换为string
结论
如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确的数据类型的匿名变量,将实参的值传给匿名变量,并让形参来引用该变量
左值:可以被引用的数据类型,可以通过地址访问它们,例如变量、数组元素、结构体成员、引用和解引用的指针
非左值:包括字面常量(用双引号包含的字符串除外(字符串常量存在地址))和包含多项的表达式
将引用形参声明为const的理由
- 使用const可以避免无意中修改数据的编程错误
- 使用const使函数能够处理const和非const实参,否则将只能接受非const实参
func(int &a){ std::cout << a<< std::endl; }
int b = 2;
func(b); //编译正确const int b = 2;
func(b); //编译失败
func(const int &a){
std::cout << a<< std::endl;
}
int b = 2;
func(b); //编译正确
const int b = 2;
func(b); //编译正确
- 通过const函数能正确生成并使用临时变量
引用用于函数的返回值
函数的返回值被拷贝到一个临时变量(寄存器或栈),然后调用者程序在使用这个值
double m = sqrt(36);
sqrt()函数用于返回值的平方根
sqrt(36)的返回值6被拷贝到临时位置,然后赋值给变量m
cout << sqrt(36)
sqrt(36)的返回值6被拷贝到临时位置,然后传递给cout
如果返回值为结构体,则将整个结构体拷贝到临时位置,这会造成性能损耗
解决思路:
如果返回引用不会拷贝内存
语法
返回值的数据类型 &函数名(形参列表){ …… }
注意事项
- 如果返回值局部变量的引用,其本质为野指针
int &func(void){
int ii = 2;
return ii;
}
int main(){
int &b = &func(); //b为函数func返回值ii的别名
cout << b << std::endl; //上一行代码执行完后,ii占用的内存被释放(超出了作用域),&b就为野指针
}
- 返回函数的 引用形参、类的成员、全局变量、静态变量(static)
int &func(int &ra){ ra++; std::cout <<"ra的地址" << &ra <<"ra = " << ra <<std::endl; return ra; //函数返回引用形参 } int main(){ int a = 3; int &b = func(a);
std::cout <<"a的地址" << &a <<"a = " << a <<std::endl; std::cout <<"b的地址" << &b <<"b = " << b <<std::endl; return 0;
}
- 返回引用的函数是被引用变量的别名;如果不希望返回的引用被别人利用,可以将const用于引用的返回类型
//以下代码仅帮助理解,实际开发中不建议使用
int &func(int &ra){
ra++;
std::cout <<"ra的地址" << &ra <<"ra = " << ra <<std::endl;
return ra; //函数返回引用形参
}
int main(){
int a = 3;
func(a) = 10; //返回引用的函数是被引用变量的别名
//把10赋值给a
std::cout <<"a的地址" << &a <<"a = " << a <<std::endl;
return 0;
}
//以下代码仅帮助理解,实际开发中不建议使用
const int &func(int &ra){
ra++;
std::cout <<"ra的地址" << &ra <<"ra = " << ra <<std::endl;
return ra; //函数返回引用形参
}
int main(){
int a = 3;
//编译失败,func(a)必须是可以修改的左值
func(a) = 10; //返回引用的函数是被引用变量的别名
//把10赋值给a
std::cout <<"a的地址" << &a <<"a = " << a <<std::endl;
return 0;
}