引用变量是C++新增的复合类型。
引用是已定义的变量的别名。
引用的主要用途是用作函数的形参和返回值。
声明/创建引用的语法:数据类型 &引用名=原变量名
实际上这个引用就是变量的别名
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main() {
int a = 3;
int& ra = a;
cout << "a的地址是:" << &a << ",a的值是:" << a << endl;
cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl;
ra = 8;
cout << "a的地址是:" << &a << ",a的值是:" << a << endl;
cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl;
return 0;
}
注意:
- 引用的数据类型要与原变量名的数据类型相同。
- 引用名和原变量名可以互换,它们值和内存单元是相同的。
- 必须在声明引用的时候初始化,初始化后不可改变。
- C和C++用
&
符号来指示/取变量的地址,C++给&
符号赋予了另一种含义。
我们看这个代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int main() {
int a = 3;
int& ra = a;
cout << "a的地址是:" << &a << ",a的值是:" << a << endl;
cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl;
int b = 5;
ra = b;//把b的值赋给ra
cout << "a的地址是:" << &a << ",a的值是:" << a << endl;
cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl;
return 0;
}
运行结果是:
注意看这代码的意思是b的值赋给ra和a,而不是ra又变成b的引用。
引用的本质
引用时指针常量的伪装
引用是编译器提供的一个有用且安全的工具,去除了指针的一些缺点,禁止了部分不安全的操作。
变量是什么?变量就是一个在程序执行过程中可以改变的量。
换一个角度,变量是一块内存区域的名字,它代表了这块内存区域,当我们对变量进行修改的时候,会引起内存区域中内容的改变。
引用用于函数的参数
把函数的形参声明为引用,调用函数的时候,形参将成为实参的别名。
这种方法也叫按引用传递或传引用。(传值、传地址、传引用只是说法不同,其实都是传值。)
引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参。
- 传引用的代码更简洁。’
- 传引用不必使用二级指针。’
- 引用的属性和特别之处。‘
我们只需要把形参改为引用就行。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
void func3(int& no, string& str) {//把形参改为引用就行
no = 8;
str = "我有一只小小鸟";
cout << "亲爱的" << no << "号:" << str << endl;
}
int main() {
int bh = 3;
string message = "我是一只傻傻鸟。";
cout << "亲爱的" << bh << "号:" << message << endl;
func3(bh, message);
cout << "亲爱的" << bh << "号:" << message << endl;
return 0;
}
引用的形参和const的用途
如果引用的数据对象类型不匹配,当引用位const时,C++将创建临时变量,让引用指向临时变量。
什么时候将创建临时变量呢?
- 引用是const.
- 数据对象的类型正确,但不是左值
- 数据对象的类型不正确,但是可以转化位正确的类型
例如:int &ra=8
会报错,但是const int& ra=8
则不会报错。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
void func3(const int& no, const string& str) {
cout << "亲爱的" << no << "号:" << str << endl;
}
int main() {
func3(3, "我是一只小小鸟");
func3('X', "我是一只小小鸟");//这样也可以
return 0;
}
结论:如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确类型的匿名变量,将实参的值传递给匿名变量,并让形参来引用该变量。
将引用形参声明为const的理由有三个:
- 使用const可以避免无意中修改数据的编程错误。
- 使用const使函数能够处理const和非const实参,否则将只能接受非const实参。
- 使用const,函数能正确生成并使用临时变量。
左值是可以被引用的数据对象,可以通过地址访问它们,例如:变量、数组元素用和解引用的指针。
引用用于函数的返回值
传统的函数返回机制和值传递类似。
函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用者程序再使用这个值。
double m=sqrt(36);//sqrt()是求平方根函数。
sqrt(36)的返回值6被拷贝到临时的位置,然后赋值给m。
cout << sqrt(25);
sqrt(25)的返回值5被拷贝到临时的位置,然后传递给cout。
如果返回的是一个结构体,将把整个结构体拷贝到临时的位置。
如果返回引用不会拷贝内存。
语法:
返回值的数据类型&函数名(形参列表);
注意:
- 如果返回局部变量的引用,其本质是野指针。
- 可以返回函数的引用参数、类的成员、全局变量、静态变量。
- 返回引用的函数是被引用的变量的别名,将const用于引用的返回类型。
例如:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int func1() {//返回的是值
int i = 2;
return i;
}
int& func2() {//返回的是引用
int i = 3;
return i;
}
int main() {
int a = func1();cout << "a=" << a << endl;
int& b = func2();cout << "b=" << b << endl;
return 0;
}
我们在看一个例子:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int& func2(int& ra) {
ra++;
cout << "ra的地址是:" << &ra << "ra=" << ra << endl;
return ra;
}
int main() {
int a = 2;
int& b = func2(a);
cout << "a的地址是:" << &a << "a=" << a << endl;
cout << "b的地址是:" << &b << "a=" << b << endl;
return 0;
}
这里的地址都是一个,ra,a,b的地址都是一个。
各种形参的使用场景
传值、传地址和传引用的指导原则《C++Primer Plus》
1) 如果不需要在函数中修改实参:
- 如果实参很小,如内置数据类型或小型结构体,则按值传递。
- 如果实参是数组,则使用const 指针,因为这是唯一的选择(没有为数组建立引用的说法)。
- 如果实参是较大的结构,则使用const指针或const 引用。
- 数据实参是类,则使用const引用,传递类的标准方式是按引用传递。
2) 如果需要在函数中修改实参
- 如果实参是内置数据类型,则使用指针。只要看到
func(&x)
的调用,表示函数将修改x
。 - 如果实参是数组,则只能使用指针。
- 如果实参是结构体,则使用指针或引用。
- 如果实参是类,则使用引用。
当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。
例如:对于基本类型,cin使用引用,因此可以使用cin>>a
,而不是cin> >&a
。