引用

image

引用

引用是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 &lt;&lt;&quot;a的地址&quot; &lt;&lt; &amp;a &lt;&lt;&quot;a = &quot; &lt;&lt; a &lt;&lt;std::endl;
std::cout &lt;&lt;&quot;b的地址&quot; &lt;&lt; &amp;b &lt;&lt;&quot;b = &quot; &lt;&lt; b &lt;&lt;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;
}
posted @ 2023-07-16 17:34  清光照归途  阅读(38)  评论(0编辑  收藏  举报