1. 引用简介
变量的本质是某段内存的名称,因此,可以把引用理解为该段内存的另一个名称。通俗来说,引用为已存在的变量的别名,是 C++ 为了优化指针而提出的新类型。
- 示例:
int var = 0;
int& ref = var;
以上定义后,当改变 ref 的内容时,var 的内容同样被改变,这与指针操作内存相似。
2. 引用规则
引用遵循以下规则:
- 引用本质上没有定义,而是一种关系型声明,声明它和已存在实体的关系。因此,引用类型必须与实体类型一致,且引用不再分配内存,地址与被引用的实体相同。
- 引用声明时必须用实体进行初始化,表示该引用为该实体的别名。且声明后,无法变更引用的实体。
- 可对某实体进行多次引用声明,表示该实体具备多个别名。
- 可用引用对引用进行初始化,前者引用实际为前者引用的实体。
- 引用除声明时需要使用 “&” 外,其余都不需要 “&” 表引用。其余时候的 “&” 作为取址符出现。
- 实验:
int main()
{
int var = 2;
int& ref_A = var;
cout << "var = " << var << endl; // var = 2
cout << "&var = " << &var << endl; // &var = 0x7ffccea0922c
cout << "ref_A = " << ref_A << endl; // ref_A = 2
cout << "&ref_A = " << &ref_A << endl; // &ref_A = 0x7ffccea0922c
int& ref_B = ref_A ;
cout << "ref_B = " << ref_B << endl; // ref_B = 2
cout << "&ref_B = " << &ref_B << endl; // &ref_B = 0x7ffccea0922c
int tmp = 3;
ref_B = tmp; // 仅是值的传递
cout << "var = " << var << endl; // var = 3
cout << "ref_A = " << ref_A << endl; // ref_A = 3
cout << "ref_B = " << ref_B << endl; // ref_B = 3
}
3. 引用与指针
引用与指针存在两种搭配:引用指针与指针引用。这就是中文拗口的地方,比较把两者混淆。其实两者的概念是不一样的:
- 指针引用:指针为修饰词,即实际上为引用,这个引用的实体为指针。
- 引用指针:引用为修饰词,即实际上为指针,指针是特殊数据类型变量,不能作为引用变量使用。因此,不能定义引用指针。注意,指向引用的指针与引用指针是不同的概念。
除了不存在引用指针外,引用的引用也是不存在的,因为引用只能进行一级定义。
- 实验:
int main()
{
int var = 2;
int& ref_A = var;
int* p = &ref_A;
cout << "var = " << var << endl; // var = 2
cout << "&var = " << &var << endl; // &var = 0x7fffa15199ec
cout << "*p = " << *p << endl; // *p = 2
cout << "p = " << p << endl; // p = 0x7fffa15199ec
// 指针引用
int*& p_ref = p;
cout << "*p_ref = " << *p_ref << endl; // *p_ref = 2
cout << "p_ref = " << p_ref << endl; // p_ref = 0x7fffa15199ec
// 引用指针
// int&* ref_p = p_ref; // error:cannot declare pointer to 'int &'
// 引用的引用
// int&& ref_B = ref_A; // error:expected unqualified-id before '&&' token
}
4. 引用与数组
与引用与指针的联系类似,引用与数组也存在两种搭配:引用数组与数组引用。比较把两者混淆,其实两者的概念是不一样的:
- 引用数组:引用为修饰词,即实际上为数组,数组的每个元素为引用。引用数组是不存在的,可以用反证法证明。假设存在引用数组,由于引用在声明时必须初始化,因此在定义数组时必须对每个引用元素进行初始化,但引用声明的变量并不一定在一片连续的内存空间中,即每个引用元素的实际地址未必是连续的,而数组的本质便是一片连续的内存空间,这里明显产生了矛盾。因此,不存在引用数组这一概念。
- 数组引用;数组为修饰词,即实际上为引用,引用的实体为数组。数组引用可用于函数传参为数组的情况,能避免在函数形参中数组被退化为指针,进而避免可能发生非法的内存访问。
- 实验:
void print_arr_siz(int arr[], int (&arr_ref)[10])
{
cout << "sizeof(arr) = " << sizeof(arr) << endl; // sizeof(arr) = 8 (64位环境)
cout << "sizeof(arr_ref) = " << sizeof(arr_ref) << endl; // sizeof(arr_ref) = 40
}
int main()
{
/* 数组引用 */
int arr[10] = {0};
int (&arr_ref)[10] = arr;
cout << "&arr = " << &arr << endl; // &arr = 0x7ffcb99b0580
cout << "&arr_ref = " << &arr_ref << endl; // &arr_ref = 0x7ffcb99b0580
print_arr_siz(arr, arr);
/* 引用数组 */
int var_A = 0, var_B = 1;
// int& ref_arr[2] = {var_A, var_B}; // error:declaration of 'ref_arr' as array of references
}
5. 引用本质
C++ 编译器在编译过程中使用指针常量作为引用的内部实现,引用所占用空间大小与指针相同。因此,引用的本质是一个指针常量,即:type & ref <==> typr * const ref
。
- 实验:
struct Test
{
int& ref_i;
char& ref_c;
};
int main()
{
cout << "sizeof(Test) = " << sizeof(Test) << endl; // sizeof(Test) = 16 (64位机)
}
在业务的程序开发中,还是应该从引用的角度而非指针的角度对待使用引用。
6. const 修饰的引用
当使用 const 关键字修饰引用时,引用的一些特征发生了改变:
- 可以使用字面量对 const 引用进行初始化,此时可以把引用符忽略,实际定义了一个新的只读属性的变量。
- 当用已存在的变量对 const 引用初始化即仅是在普通引用声明前使用 const 修饰时,该引用为只读属性的引用,但不影响原本变量的读写属性。
- 对于 const 引用,可以通过指针的方式进行读写,由于 C++ 为强类型语言,在指针的赋值时应使用强类型转换完成指针的赋值操作。
- 可使用不同的数据类型的变量对 const 引用进行初始化,此时实际定义了一个新的只读属性的不同数据类型的变量,此时可能发生数据截断。
- 实验:
int main()
{
/* const 修饰普通的引用声明 */
int var = 0;
const int& ref = var;
var = 1;
// ref = 2; // error: assignment of read-only reference 'ref'
cout << "var = " << var << endl; // var = 1
cout << "ref = " << ref << endl; // ref = 1
cout << "&var = " << &var << endl; // &var = 0x7fffe9b3ea74
cout << "&ref = " << &ref << endl; // &ref = 0x7fffe9b3ea74
/* 用字面量对 const 引用初始化 */
// int& ref_tmp = 5; // error: invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'
const int& ref_con = 5;
cout << "ref_con = " << ref_con << endl; // ref_con = 5
cout << "&ref_con = " << &ref_con << endl; // &ref_con = 0x7fffe9b3ea78
/* 指针访问 const 引用 */
// int* p = &ref_con; // error: invalid conversion from 'const int *' to 'int *'
int* p = (int*)&ref_con;
*p = 6;
cout << "ref_con = " << ref_con << endl; // ref_con = 6
/* 不同数据类型的 const 引用声明 */
double pi = 3.14;
// int& pi_tmp = pi; // error: invalid initialization of reference of type 'int&' from expression of type 'int'
const int &ref_pi = pi;
cout << "ref_pi = " << ref_pi << endl; // ref_pi = 3
cout << "&pi = " << &pi << endl; // &pi = 0x7fffe9b3ea80
cout << "&ref_pi = " << &ref_pi << endl; // &ref_pi = 0x7fffe9b3ea7c
}