Table of Contents:

引用入门教程

参数的传递本质上是一次赋值的过程,赋值就是对内存进行拷贝。所谓内存拷贝,是指将一块内存上的数据复制到另一块内存上。
对于像 char、bool、int、float 等基本类型的数据,它们占用的内存往往只有几个字节,对它们进行内存拷贝非常快速。
而数组、结构体、对象是一系列数据的集合,数据的数量没有限制,可能很少,也可能成千上万,对它们进行频繁的内存拷贝可能会消耗很多时间,拖慢程序的执行效率。

引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它数据,这有点类似于常量(const 变量)。
如果读者不希望通过引用来修改原始的数据,那么可以在定义时添加 const 限制,形式为:
const type &name = value;   也可以是:  type const &name = value;
当你不需要"重新指向(reseating)"时,引用一般优先于指针被选用。这通常意味着引用用于类的公有接口时更有用。引用出现的典型场合是对象的表面,而指针用于对象内部

返回一个引用,意味着什么?

意味着该函数调用可以出现在赋值运算符的左边。
最初这种能力看起来有些古怪。例如,没有人会认为表达式 f() = 7 有意义。然而,如果 a 是一个 Array 类,大多数人会认为 a[i] = 7 有意义,即使 a[i] 实际上是一个函数调用的伪装(它调用了 如下的 Array 类的 Array::operator)。

 class Array {
 public:
   int size() const;
   float& operator[] (int index);
   // ...
 };
 int main()
 {
   Array a;
   for (int i = 0; i < a.size(); ++i)
     a[i] = 7// 这行调用了 Array::operator[](int)
 }

object.method1().method2() 是什么意思? 

第一个被执行的是 object.method1()。它返回对象,可能是对象的引用(如,method1()可能以 return *this 结束),或可能是一些其他对象。我们姑且把返回的对象称为objectB。然后objectB成为method2()的this对象。
方法链最常用的地方是iostream库。例如,cout << x << y 可以执行因为 cout << x是一个返回cout.的函数

引用在本质上是什么,它和指针到底有什么区别?

其实引用只是对指针进行了简单的封装,它的底层依然是通过指针实现的,引用在C++中的内部实现是一个常量指针
Type& name 等价于 Type* const name
引用占用的内存和指针占用的内存长度一样,在 32 位环境下是 4 个字节,在 64 位环境下是 8 个字节,之所以不能获取引用的地址,是因为编译器进行了内部转换
从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏

int a = 99;
int &r = a;
r = 18;
cout<<&r<<endl;

编译时会被转换成如下的形式:

int a = 99;
int * const r = &a;
*r = 18;
cout<<r<<endl;

使用&r取地址时,编译器会对代码进行隐式的转换,使得代码输出的是 r 的内容(引用的对象的地址),而不是 r 的地址,这就是为什么获取不到引用变量的地址的原因。也就是说,不是变量 r 不占用内存,而是编译器不让获取它的地址
C++ 的发明人 Bjarne Stroustrup 也说过,他在 C++ 中引入引用的直接目的是为了让代码的书写更加漂亮,尤其是在运算符重载中,不借助引用有时候会使得运算符的使用很麻烦。

引用和指针的其他区别

1. 引用必须在定义时初始化,不存在空引用,并且以后也要从一而终,不能再指向其他数据;而指针没有这个限制,指针在定义时不必赋值,以后也能指向任意数据。
2. 指针和引用的自增(++)自减(--)运算意义不一样。对指针使用 ++ 表示指向下一份数据,对引用使用 ++ 表示它所指代的数据本身加 1;
3. 可以有 const 指针,但是没有 const 引用。也就是说,引用变量不能定义为下面的形式:

int a = 20;
int & const r = a;

注意:由于在调用者的代码处,无法提供清晰的的引用语义,所以传统的 C 程序员有时并不喜欢引用。然而,当有了一些 C++ 经验后,你会很快认识到这是信息隐藏的一种形式,它是有益的而不是有害的。
引用相对于指针来说具有更好的可读性和实用性
C++链式编程中,经常用到引用

引用不能绑定到临时数据

我们平时不太留意的临时数据,例如表达式的结果、函数的返回值等,它们可能会放在内存中,也可能会放在寄存器中。一旦它们被放到了寄存器中,就没法用&获取它们的地址了,也就没法用指针指向它们了。
int、double、bool、char 等基本类型的数据往往不超过 8 个字节,用一两个寄存器就能存储,所以这些类型的临时数据通常会放到寄存器中;而对象、结构体变量是自定义类型的数据,大小不可预测,所以这些类型的临时数据通常会放到内存中

常量表达式的值虽然在内存中,但是没有办法寻址,所以也不能使用&来获取它的地址,更不能用指针指向它

编译器会为const引用创建临时变量

将常引用绑定到临时数据时,编译器采取了一种妥协机制:编译器会为临时数据创建一个新的、无名的临时变量,并将临时数据放入该临时变量中,然后再将引用绑定到该临时变量。注意,临时变量也是变量,所有的变量都会被分配内存

const 和 引用

不同类型的数据占用的内存数量不一样,处理方式也不一样,指针的类型要与它指向的数据的类型严格对应。
引用(Reference)和指针(Pointer)在本质上是一样的,引用仅仅是对指针进行了简单的封装,类型严格一致这条规则同样也适用于引用。
但给引用添加 const 限定后,不但可以将引用绑定到临时数据,还可以将引用绑定到类型相近的数据,这使得引用更加灵活和通用,它们背后的机制都是临时变量

const 和函数形参

const形参好处:
1. 使用 const 可以避免无意中修改数据的编程错误;
2. 清楚的分清参数的输入和输出特性
3. 可以接受const和非const的变量,否则将只能接收非 const 类型的实参
4. 使用 const 引用能够让函数正确生成并使用临时变量,可以将引用绑定到类型相近的数据,而不仅仅是严格匹配

double volume(const double &len, const double &width, const double &hei){
    return len*width*2 + len*hei*2 + width*hei*2;
}
int main(){
    int a = 12, b = 3, c = 20;
    double v1 = volume(a, b, c);
    double v2 = volume(10, 20, 30);
    double v3 = volume(89.4, 32.7, 19);
    double v4 = volume(a+12.5, b+23.4, 16.78);
    double v5 = volume(a+b, a+c, b+c);
}