C++语法之引用

本文首发于个人博客

C++ 引用

  • 引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

注意点

  • 引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用)
  • 对引用做计算,就是对引用所指向的变量做计算
  • 在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”
  • 可以利用引用初始化另一个引用,相当于某个变量的多个别名
  • 不存在【引用的引用、指向引用的指针、引用数组】

例如

1
2
3
4
5
6
int main(){
int age = 18;
int &ref = age;
ref = 20;
cout << age << endl; //打印结果为 20
}

上面的代码中修改ref也就相当于修改了age。

引用的本质

  • 引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
    • 一个引用占用一个指针的大小

间接证明

1
2
3
4
5
6
7
struct person {
int age;
};

int main(){
cout << "person size is " << sizeof(person) << endl;
}

在X86位架构下,上面的代码输入为 person size is 4
如果代码改成如下

1
2
3
4
5
6
7
struct person {
int &ref;
};

int main(){
cout << "person size is " << sizeof(person) << endl;
}

在X86架构下,上面的代码输入为 person size is 8
而我们知道在X86架构下,指针类型占用字节为8。int 类型为占用4个字节。所以引用和指针占用字节数一样。

汇编证明

  • 如下使用指针的代码
1
2
3
4
int main(){
int age = 3;
int *p = &age;
}

汇编代码如下

1
2
3
4
5
6
7
8
9
10
11

_main:
push rbp
mov rbp, rsp
xor eax, eax
mov dword [rbp+var_4], 0x0
mov dword [rbp+var_8], 0x3
lea rcx, qword [rbp+var_8]
mov qword [rbp+var_10], rcx
pop rbp
ret
  • 把指针改成引用的代码
1
2
3
4
5

int main(){
int age = 3;
int &ref = age;
}

汇编代码如下

1
2
3
4
5
6
7
8
9
10
 _main:
push rbp
mov rbp, rsp
xor eax, eax
mov dword [rbp+var_4], 0x0
mov dword [rbp+var_8], 0x3
lea rcx, qword [rbp+var_8]
mov qword [rbp+var_10], rcx
pop rbp
ret

可发现,引用和指针的汇编代码完全一致。就说明最终转成机器码也是一样的。所以引用的本质就是指针。

常引用(Const Reference)

  • 引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用
  • const必须写在&符号的左边,才能算是常引用

const引用的特点

  • 可以指向临时数据(常量、表达式、函数返回值等)
  • 可以指向不同类型的数据
  • 作为函数参数时(此规则也适用于const指针)
    • 可以接受const和非const实参(非const引用,只能接受非const实参)
    • 可以跟非const引用构成重载

当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量

常引用指向相同类型的数据

1
2
3
4
int a = 10;
const int &ref = a;
a = 12;
cout << "ref is " << ref << endl;

上面代码输出为12。其对应的汇编如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 push       rbp
mov rbp, rsp
xor eax, eax
mov dword [rbp+var_4], 0x0
//10赋值给[rbp+var_8] 这个内存空间 也就是 变量a的地址
mov dword [rbp+var_8], 0xa

// a的地址赋值给引用ref
lea rcx, qword [rbp+var_8]
mov qword [rbp+var_10], rcx//

//把a的值改为12
mov dword [rbp+var_8], 0xc
pop rbp
ret

常引用指向不同类型的数据

1
2
3
4
int a = 10;
const long &ref = a;
a = 12;
cout << "ref is " << ref << endl;

上面代码输出为10。其对应的汇编如下,改变了a的值,并不能改变ref的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
             _main:
push rbp
mov rbp, rsp
xor eax, eax
mov dword [rbp+var_4], 0x0

//10赋值给[rbp+var_8] 这个内存空间 也就是 变量a的地址
mov dword [rbp+var_8], 0xa

//增加一个中间变量地址为[rbp+var_18] 作为引用ref的地址
movsxd rcx, dword [rbp+var_8]
mov qword [rbp+var_18], rcx
lea rcx, qword [rbp+var_18]
mov qword [rbp+var_10], rcx

//把a的值改为12
mov dword [rbp+var_8], 0xc
pop rbp