◼ 在C语言中,使用指针(Pointer)可以间接获取、修改某个变量的值
◼ 在C++中,使用引用(Reference)可以起到跟指针类似的功能
void funRef(int &ref){
ref++;
}
int main(){
//定义int类型变量
int var = 0x41;
//int指针变量,初始化为变量var的地址
int *pnVar = &var;
//取出指针pcVar指向的地址内容并显示
char *pcVar = (char*)&var;
printf("%s",pcVar);
//引用作为参数,即把var的地址作为参数
funRef(var);
int age = 10;
// *p就是age的别名
int *p = &age;
*p = 30;
// ref就是age的别名
int &ref = age;
ref = 30;
return 0;
}
◼ 注意点
引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用)
对引用做计算,就是对引用所指向的变量做计算
int main() {
//cout << sizeof(Student) << endl;
int age = 10;
// *p就是age的别名
int *p = &age;
*p = 30;
// ref就是age的别名
int &ref = age;
ref += 30;
cout<< age << endl;
}
60
在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”
可以利用引用初始化另一个引用,相当于某个变量的多个别名
void test() {
int age = 10;
int height = 20;
// 定义了一个age的引用,ref相当于是age的别名
int &ref = age;
int &ref1 = ref;
int &ref2 = ref1;
ref += 10;
ref1 += 10;
ref2 += 10;
cout << age << endl;
}
40
◼ 引用存在的价值之一:比指针更安全、函数返回值可以被赋值
引用在开发中的使用Demo
//void swap(int *v1, int *v2) {
// int tmp = *v1;
// *v1 = *v2;
// *v2 = tmp;
//}
/*
后面还可以再用别的值调换吗?不是说引用“从一而终”吗
*/
void swap(int &v1, int &v2) {
int tmp = v1;
v1 = v2;
v2 = tmp;
}
void test2() {
int a = 10;
int b = 20;
// swap(&a, &b);
swap(a, b);
cout << "a = " << a << ", b = " << b << endl;
int c = 2;
int d = 3;
swap(c, d);
cout << "c = " << c << ", d = " << d << endl;
}
a = 20, b = 10
c = 3, d = 2
double &refD = age;
引用的本质
◼ 引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
int age = 10;
// *p就是age的别名
int *p = &age;
*p = 30;
// ref就是age的别名
int &ref = age;
ref += 30;
cout << sizeof(p) << endl;
cout << sizeof(ref) << endl;
8 指针的sizeof 大小和运行环境有关系,我现在用的macos 64位编译,所以一个指针大小为 8
4 这里为什么输出来为4 其实输出的是sizeof(int) 的大小 int在C++中,4位大小
◼ 一个引用占用一个指针的大小
struct Student {
int age;
};
cout << sizeof(Student) << endl;
4
struct Student {
int *age;
};
cout << sizeof(Student) << endl;
8
struct Student {
int &age;
};
cout << sizeof(Student) << endl;
8
侧面证明了一个引用占用一个指针的大小
使用汇编来窥探引用的本质
_p$ = -12 ; size = 4
_ref$ = -8 ; size = 4
_age$ = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
mov DWORD PTR _age$[ebp], 10 ; 0000000aH
lea eax, DWORD PTR _age$[ebp]
mov DWORD PTR _p$[ebp], eax
mov ecx, DWORD PTR _p$[ebp]
mov DWORD PTR [ecx], 30 ; 0000001eH
lea edx, DWORD PTR _age$[ebp]
mov DWORD PTR _ref$[ebp], edx
mov eax, DWORD PTR _ref$[ebp]
mov ecx, DWORD PTR [eax]
add ecx, 30 ; 0000001eH
mov edx, DWORD PTR _ref$[ebp]
mov DWORD PTR [edx], ecx
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
从汇编语言来看,引用和指针生成的汇编代码一致,所以说 引用的本质就是指针
看一下指针地址赋值的过程
◼ lea dest, [ 地址值 ]
- 将地址值赋值给dest,类似于dest = 地址值
int main(){
int age = 3;
// *p就是age的别名
int *p = &age;
*p = 5;
}
// eax == ebp-0Ch,存放着age的地址值
008519C9 lea eax,[ebp-0Ch]
// ebp-18h是指针变量p的地址值
// 将age的地址值存放到指针变量p所在的存储空间
// int *p = &age;
008519CC mov dword ptr [ebp-18h],eax
lea 是没有单位的,move设计到把地址的值取出来,所以是需要单位的 dword 表示4个字节
所以看指针汇编的标志性代码是 lea
再看把指针修改为引用 ,汇编代码完全一样, 所以说引用的本质就是指针
引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用)
- 结构体的引用
//结构体也可以是引用类型
struct Date {
int year;
int month;
int day;
};
Date d = {2011, 1, 5};
Date &ref = d;
ref.day = 2014;
- 指针的引用
int age = 10;
int *p = &age;
int *&ref = p;
*ref = 30;
- 数组的引用
int array[] = {1, 2, 3};
int (&ref)[3] = array;
int *p;
// 指针数组,数组里面可以存放3个int *
int *arr1[3] = {p, p, p};
// 用于指向数组的指针
int (*arr2)[3];//数组指针
int (&ref)[3] = array;
int (*arr2)[3];
不存在【引用的引用、指向引用的指针、引用数组】
常引用(Const Reference)
◼ 引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用
- const必须写在&符号的左边,才能算是常引用
- 常量指针是指指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变, 从而指向另一个常量。
- 指针常量是指指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。它指向的地址将伴其一生,直到生命周期结束。有一点需要注意的是,指针常量在定义时必须同时赋初值。
int height = 20;
int age = 10;
// p2可以修改指向,不可以利用p2间接修改所指向的变量
int const *p2 = &age;
p2 = &height;
// *p2 = 30;
// ref1不能修改指向,但是可以通过ref1间接修改所指向的变量
const int &ref1 = age;
const int *p = &age
//ref1 = 30;
//*p = 30;
- 'const' qualifier may not be applied to a reference
int & const ref3 = age;
加深记忆记住三句话:
指针和 const 谁在前先读谁 ;
*象征着地址,const象征着内容;
谁在前面谁就不允许改变。例如 int const p1 = &b 常量指针 const在前,所以内容不能修改,即 (p1 = 30)错误
int const p2 = &c 指针常量 (指针)在前,所以指针**不能修改 (p2=&d)错误
◼ const引用的特点
- const引用可以指向临时数据(常量、表达式、函数返回值等)
Non-const lvalue reference to type 'int' cannot bind to a temporary of type 'int'
const int &refError = 30;
int func() {
return 8;
}
const int &ref = func();//函数返回值
- const引用可以指向不同类型的数据
Non-const lvalue reference to type 'double' cannot bind to a value of unrelated type 'int'
int age = 10;
const double &ref = age;
- const引用作为函数参数时(此规则也适用于const指针)
✓ 可以接受const和非const实参(非const引用,只能接受非const实参)
✓ 可以跟非const引用构成重载
int sum(int &v1, int &v2) {
cout << "sum(int &v1, int &v2)" << endl;
return v1 + v2;
}
int sum(const int &v1, const int &v2) {
cout << "sum(const int &v1, const int &v2)" << endl;
return v1 + v2;
}
void test2() {
// 非const实参
int a = 10;
int b = 20;
sum(a, b);
// const实参
const int c = 10;
const int d = 20;
sum(c, d);
sum(10, 20);
}
sum(int &v1, int &v2)
sum(const int &v1, const int &v2)
sum(const int &v1, const int &v2)
◼ 当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
int age = 10;
const int &rage = age;
age = 30;
cout << "age is "<< age << endl;
cout << "rage is "<< rage << endl;
age is 30
rage is 30
int age = 10;
const long &rage = age;
age = 30;
cout << "age is "<< age << endl;
cout << "rage is "<< rage << endl;
age is 30
rage is 10
_rage$ = -12 ; size = 4
_$S1$ = -8 ; size = 4
_age$ = -4 ; size = 4
_main PROC
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
mov DWORD PTR _age$[ebp], 10 ; 0000000aH
mov eax, DWORD PTR _age$[ebp]
mov DWORD PTR _$S1$[ebp], eax
lea ecx, DWORD PTR _$S1$[ebp]
mov DWORD PTR _rage$[ebp], ecx
mov DWORD PTR _age$[ebp], 30 ; 0000001eH
xor eax, eax
mov esp, ebp
pop ebp
ret 0
_main ENDP
产生了临时变量 _$S1$
mov eax, DWORD PTR _age$[ebp]
把age的值放入寄存器 eax
mov DWORD PTR _$S1$[ebp], eax
eax的值放入_$S1所在的内存地址
lea ecx, DWORD PTR _$S1$[ebp]
把$S1的内存地址给ecx
mov DWORD PTR _rage$[ebp], ecx
把ecx存的值放入_rage地址中
从这段汇编代码可以看出 常引用指向了不同类型的数据时,会产生临时变量
数组的引用
◼ 常见的2种写法
// 数组名arr其实是数组的地址,也是数组首元素的地址
// 数组名arr可以看做是指向数组首元素的指针(int *)
int arr[] = {1, 2, 3};
int (&ref)[3] = arr;
int * const &ref2 = arr;
- 数组名arr可以看做是指向数组首元素的指针(int *)