认识引用
Declares a named variable as a reference, that is, an alias to an already-existing object or function. 来自: C++参考手册
先从一个简单的例子开始: 交换函数 swap() 。
// 代码片段01
void swap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 代码片段02
void swap02(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(int argc, const char * argv[]) {
int a= 10, b = 20;
swap01(a, b); // 不会交换
swap02(&a, &b); // 会交换
return 0;
}
上述代码似乎不用解释都知道它所要说明的问题。似乎与今天的主题无关,但是不着急,看下 代码片段02 。内部充斥着 *a
、*b
这样的操作,代码似乎不够简洁。有没有什么方式可以使其更简洁一些呢?有! 引用!!
。
有了引用以后,代码是这样的:
void swap03(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main(int argc, const char * argv[]) {
int a= 10, b = 20;
swap03(a, b); // 会交换
return 0;
}
代码看起来和上面没有什么大的差别。&a
、&b
似乎是!在取地址。实际上不是的。它表示的 a
是某个 int
类型变量的引用(或者说某个 int
类型变量的别名)。
那么如何创建一个 引用
呢?
int main(int argc, const char * argv[]) {
int num = 10;
int & ref = num; // 创建一个引用。
int & refref = ref; // 创建一个引用
return 0;
}
如上,创建的是 num
的引用,名字叫 ret
。既然是引用,就是说,使用 num
和 ret
没有任何区别。
没有任何区别是什么意思?意思就是说:num
和 ret
都是同一个内存地址的名称。举个小例子,如图:
num: 周樟寿
ref: 周树人
refref: 鲁迅
只是名字变了,但它都是同一个内存!!
说到这里,有没有发现其实引用是什么?像不像下面这样:
int &ref = num;
// 等价于
int *ref = #
另外有一个需要注意的地方是: 引用一旦创建,便不能更改(只读)!!既然不能修改,那就说明必须在创建时就要给引用初始化!!!。
int one = 10;
int two = 20;
// 错误01(ref创建时就要被初始化)
int &ref;
ref = one;
// 正确写法:int &ref = one;
// 异常 02(引用一旦创建,便不能修改)
int &ref = one;
ref = two; // 这句话不会报错,但是它表达的意思不是让ref变成two的引用。而是将 two 的值,赋值给 ref。即,ref = 20;
至此,对引用有了一个基本的认识。总结一下:
- 引用是某个变量的别名。
- 引用一旦创建,便不可以修改。
- 引用创建时,必须要初始化。
基于以上的观点。我们可以对引用有一个本质上的认识,即指针常量:
int & ref = num;
// 等价于
int * const ref = #
引用的作用
引用难道只是为了简化 *a
这样的代码,使代码更简洁么? 不是的。引用来源于指针,但不同于指针。
引用最主要的用途是:用作函数的参数(尤其是结构体或对象参数)!!
-
作为函数参数
struct Student { std::string id; std::string name; int age; }; void func01(Student student) { // do something } void func02(Student &student) { // do something }
- func01 中会执行创建临时的Student对象,并执行Student的数据拷贝(拷贝构造函数)。这是耗时和耗空间的。
- func02 中使用的是Student的引用,因此操作的是同一块内存区域,不会创建临时对象,不耗时,也不占用空间。
-
【注意】 使用引用作为参数时,常加上
const
修饰。理由如下:-
避免无意中修改被引用的数据,如:
int i = 5; int cube(int &a) { a *= a * a; return a; } int sum(int &a) { a = a + 5; return a; } int tmp1 = cube(i); // 输入时i= 5,输出时i = 125 int tmp2 = sum(i); // 输入时i = 125,输出时i = 130
-
能够处理
const
和非const
实参,否则只能接受非const
数据,如:const int NUM = 100; int sum01(int &a) { // do something return 0; } int sum02(const int &a) { // do something return 0; } sum01(NUM);// ERROR!!!! sum02(NUM);// OK
- 使函数能够正确生成并使用临时变量。(这种在早期C++较宽松的规则下才会出现)
-
-
作为函数返回值
返回值是引用的函数,实际上返回的是被引用变量的“别名”
有一个需要注意的地方是:不要返回临时变量。如下:
Student &clone(const Student &s) { Student student; // 局部变量,函数执行完成时会被销毁。 student.id = s.id; student.name = s.name; student.age = s.age; return student; }
如上,会返回 student。但是 student 是局部的临时变量,在 clone 函数执行完成后,将被销毁,导致程序出现异常。避免这种问题有一些做法:
-
返回一个作为参数传递给函数的引用。如:
Student &clone(const Student &s) { return s; }
-
用
new
来分配新的内存空间, 如:Student &clone(const Student &s) { Student *student = new Student(); student->id = s.id; student->name = s.name; student->age = s.age; return *student; }
-
引用的使用建议
使用引用参数的主要原因有两个:
- 程序员能够修改调用函数中的数据对象。
- (最主要)通过传递引用而不是整个数据对象(如:结构体和类对象这种数据对象较大的对象),可以提高程序的运行速度。
正如前面所说,引用来源于指针,但不同于指针。甚至引用像是指针的 "语法糖" 。那么问题来了,什么时候使用引用,什么时候使用指针,什么时候使用按值传递呢???
对于使用传递的值而不作修改的函数
- 如果数据对象比较小(如:基本数据类型或者小型的结构),选择按值传递
- 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向
const
的指针- 如果数据对象是较大的结构,则使用指针或者引用,以提高程序的效率。这样可以节省复制结构所需的时间和空间
- 如果数据对象是类对象,则使用
const
修饰的引用。类设计的语义常常要求使用语义,这是C++新增这项特性的主要原因。因此传递类对象参数的标准方式是按引用传递。对于修改调用函数中数据的函数
- 如果数据对象是基本数据类型,则使用指针。
- 如果数据对象是数组,则只能使用指针。
- 如果数据对象是结构,则使用指针或者引用。
- 如果数据对象是类对象,则使用引用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。