作用:为变量或数组起别名。
语法:数据类型 &别名 = 原名;
示例:
int main() {
int a = 5;
int &b = a; //引用:为变量起别名
cout << "a = " << a << endl; //5
cout << "b = " << b << endl; //5
b = 10;
cout << "a = " << a << endl; //10
cout << "b = " << b << endl; //10
return 0;
}
(1)直接定义数组引用
语法:数据类型 (&别名)[数组长度] = 原数组名;
int arr[5] = {1,2,3,4,5};
//直接定义数组引用
int (&array)[5] = arr;
//直接定义数组指针
int (*arrPtr)[5] = &arr;
注1:数组引用 vs 数组指针:
数组引用定义:数据类型 (&别名)[数组长度] = 原数组名;
,如:int (&array)[5] = arr;
数组引用访问元素:别名[索引]
,如array[i]
数组指针定义:数据类型 (*数组指针名)[数组长度] = &数组名;
,如:int (*arrPtr)[5] = &arr;
数组指针访问元素:(*数组指针名)[索引]
,如(*arrPtr)[i]
或·*((*arrPtr) + i)
【区别】数组引用(赋值符左边是&
,右边是数组名
);数组指针(赋值符左边是*
,右边是数组地址
)
注2:数组指针 vs 指针数组:
数组指针(array pointer):指向数组的指针,用于存储数组的地址。数组指针:使用小括号,如
int (*p)[5] = &arr;
指向长度为5且元素类型为int
类型的一维数组,即指向一个int[5],存储数组地址;
占用内存大小为4字节。
指针数组(pointer array):元素为指针类型的数组,用于存储指针类型的元素。
指针数组:未使用小括号,如
int *p[5] = &arr;
长度为5且元素类型为int *
(指针类型)的一维数组,每个int *
(指针类型)元素存放其对应常量的首地址;
占用内存大小为20字节(int *
指针类型占4字节,共5个元素)。
示例1:直接定义数组引用
#include
using namespace std;
int main() {
int arr[5] = { 1,2,3,4,5 };
/* 1.直接定义数组引用 */
int (&array)[5] = arr;
//使用数组别名遍历数组
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
cout << array[i] << endl;
}
/* 定义数组指针 */
int (*arrPtr)[5] = &arr; //&arr:整个数组的地址
//arr等价于&arr[0],类型为int *类型:数组首元素地址
//int (*arrPtr)[5] = arr; //报错:int *类型的值不能用于初始化int (*)[5]类型的实体
//使用数组指针遍历数组
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
cout << (*arrPtr)[i] << endl;
//cout << *(*arrPtr + i) << endl; //等价写法
}
return 0;
}
注3:定义数组指针时,赋值符右侧应该是对整个数组取地址
&arr
,而不能是对数组首元素取地址arr
或&arr[0]
,否则编译器报错:int *类型的值不能用于初始化int (*)[5]类型的实体。
&arr
对整个数组取地址;
arr
或&arr[0]
对数组首元素取地址【地址值相同,含义不同】。- 一维数组名不能解释为数组首元素地址的两种特例【其它情况下,数组名表示数组首元素地址】:
①sizeof(数组名)
:整个数组的大小;
②&数组名
:整个数组的地址(地址值与首元素地址相同,但意义不同)&arr
表示整个数组的地址,指向整个数组,&arr + 1
会跳过整个数组【加上整个数组的总字节数】,如int *p = (int *)(&arr + 1)
,指针p指向数组的末尾;
arr
或&arr[0]
表示数组首元素地址,指向第1个元素,arr + 1
或&arr[0] + 1
会跳过第1个元素【加上1个数组元素的字节数】,指向数组的下1个元素。&arr
的地址类型为int (*p)[5]
,使用数组指针(指向数组的指针)接收;
arr
的地址类型为int *
,使用int类型的指针(指向数组首元素的指针)接收。
(2)先定义数组的类型,通过数组类型定义数组引用
定义数组类型:typedef 数据类型(数组类型)[数组长度];
定义数组引用:数组类型 &别名 = 原数组名;
int arr[5] = {1,2,3,4,5};
//先定义数组类型
typedef int(ARRAY_TYPE)[5];
//通过数组类型,定义数组引用
ARRAY_TYPE &array = arr;
示例2:先定义数组的类型,再定义数组引用
int main() {
int arr[5] = { 1,2,3,4,5 };
/* 2.先定义数组的类型,通过数组类型定义数组引用 */
typedef int(ARRAY_TYPE)[5];
ARRAY_TYPE &array = arr;
//使用数组别名遍历数组
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
cout << array[i] << endl;
}
return 0;
}
(3)先定义数组引用的类型,再定义数组引用
定义数组引用类型:typedef 数据类型(& 数组引用类型)[数组长度];
定义数组引用:数组引用类型 别名 = 原数组名;
int arr[5] = {1,2,3,4,5};
//先定义数组引用类型
typedef int(&ARRAY_REF_TYPE)[5];
//通过数组引用类型,定义数组引用
ARRAY_REF_TYPE array = arr;
示例3:先定义数组引用的类型,再定义数组引用
int main() {
int arr[5] = { 1,2,3,4,5 };
/* 3.先定义数组引用的类型,再定义数组引用 */
typedef int(&ARRAY_REF_TYPE)[5];
ARRAY_REF_TYPE array = arr;
//使用数组别名遍历数组
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
cout << array[i] << endl;
}
return 0;
}
(1)引用必须初始化,否则编译器报错:引用变量需要初始值设定项
。
int a = 5;
int &b = a; //正确
//int &b; //错误:引用必须初始化
(2)引用在初始化后,不可再改变。
int a = 5;
int b = 10;
int &c = a; //正确
//c = b; //赋值操作,非修改引用
(3)非常量引用初始化时,必须绑定合法的内存空间(栈区、堆区变量)。不能使用const修饰的常量
或字面量(右值)
,否则编译器报错:将int &类型的引用绑定到const int类型的初始值设定项时,限定符被丢弃
或非常量引用的初始值必须是左值
。
常量引用初始化时,可以绑定const修饰的常量
或字面量
。
const int c_var = 5;
//编译器报错:将int &类型的引用绑定到const int类型的初始值设定项时,限定符被丢弃
//int &ref = c_var; //错误:非常量引用绑定常量
const int &ref = c_var; //正确:常量引用
//编译器报错:非常量引用的初始值必须是左值
//int &ref = 10; //错误:非常量引用绑定字面量
const int &ref = 10; //正确:常量引用
示例:
#include
using namespace std;
int main() {
int a = 5;
int b = 10;
/* 引用必须初始化 */
//int &c; //错误:引用变量需要初始值设定项
/* 引用初始化后不可再改变 */
int& c = a;
//c = b; //赋值操作,非修改引用
/* 非常量引用不能绑定const修饰的常量 */
const int c_var = 1;
//编译器报错:将int &类型的引用绑定到const int类型的初始值设定项时,限定符被丢弃
//int &ref = c_var; //错误:非常量引用绑定常量
const int& c_ref = c_var; //正确:常量引用
cout << c_ref << endl; //1
/* 非常量引用不能绑定字面量 */
//编译器报错:非常量引用的初始值必须是左值
//int &ref = 2; //错误:非常量引用绑定字面量
const int& ref = 2; //正确:常量引用
cout << ref << endl; //2
return 0;
}
作用:函数传参时,可通过引用使形参改变实参。
优点:简化指针的用法,使用形参修改实参。
注1:引用作为函数参数,其效果与地址传递相同,但语法更简单清晰。
注2:引用作为函数参数时,函数形参是实参的别名,指向同一块内存空间,当形参(引用 / 别名)发生改变时,实参(原名)随之改变。
示例:
#include
using namespace std;
/* 值传递:形参不改变实参 */
void func_value(int a, int b) {
int temp = a;
a = b;
b = temp;
}
/* 地址传递:形参改变实参 */
void func_address(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
/* 引用传递:形参改变实参 */
void func_reference(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int m1 = 5, n1 = 10;
func_value(m1, n1);
cout << "m1 = " << m1 << endl; //5
cout << "n1 = " << n1 << endl; //10
int m2 = 5, n2 = 10;
func_address(&m2, &n2);
cout << "m2 = " << m2 << endl; //10
cout << "n2 = " << n2 << endl; //5
int m3 = 5, n3 = 10;
func_reference(m3, n3);
cout << "m3 = " << m3 << endl; //10
cout << "n3 = " << n3 << endl; //5
return 0;
}
作用:引用可作为函数的返回值。
注意事项:
(1)不能返回局部变量的引用。同理,不能返回局部变量的地址。
注:栈区的数据在执行完毕后内存由编译器自动释放,不能返回局部变量的引用(即不能使用引用访问和操作该内存)。但编译器会保留一次局部变量的内存地址(可临时访问一次),随后该内存被释放,无法再次访问。
示例:
//错误示例:不能返回局部变量的引用
int& func() {
int a = 5; //局部变量
return a;
}
int main() {
/* 不能返回局部变量的引用 */
int &ref = func();
cout << "ref = " << ref << endl; //5
cout << "ref = " << ref << endl; //2047461904(随机值)//局部变量的内存已被释放
return 0;
}
(2)当函数的返回类型为引用时,该函数调用可作为左值。
注:函数调用的返回值相当于
原名
;【函数调用作为左值】
接收函数返回值的引用相当于别名
。【函数调用作为右值】
原名和别名均指向相同内存地址,通过原名修改数据时,引用(别名)对应数据发生改变。
示例:
//返回静态变量的引用
int& func() {
static int a = 5; //静态变量:存储在全局区,其内存在程序结束后由操作系统释放
return a; //函数调用的返回值,相当于原名
}
int main() {
int &ref = func(); //引用ref相当于别名【函数调用作为右值】
cout << "ref = " << ref << endl; //5 //通过别名(引用)访问数据
cout << "ref = " << ref << endl; //5
/* 当函数的返回类型为引用时,则函数调用可作为左值 */
func() = 10; //func()的返回值相当于原名【函数调用作为左值】 //通过原名修改数据
cout << "ref = " << ref << endl; //10 //通过别名(引用)访问数据
cout << "ref = " << ref << endl; //10
return 0;
}
本质:C++中,引用的内部实现是一个指针常量。全部的指针操作由编译器自动转换。
(1)指针常量的指针指向不可变 → 引用初始化后不可更改
(2)指针常量指向的值可以改变 → 引用对应的数据可重新赋值
指针常量
语法:数据类型 * const 常量名;
示例:int * const p = &var;
特点:指针指向的值可以修改,但指针的指向不可修改。
示例:
#include
using namespace std;
/* 引用作为函数参数 */
void func(int& ref) { //编译器内部自动转换为 int * const ref = &a;
ref = 88;
}
int main() {
int a = 5;
/* 引用的本质是指针常量:指针常量的指针指向不可变,但指向的值可以改变 */
//指针常量的指针指向不可变 → 引用初始化后不可更改
int& ref = a; //编译器内部自动转换为 int * const ref = &a;
//指针常量指向的值可以改变 → 引用对应的数据可重新赋值
ref = 10; //编译器内部自动转换为 *ref = 10;
cout << "a:" << a << endl; //10
cout << "ref:" << ref << endl; //10
//引用作为函数参数传递
func(a); //编译器内部自动转换为 int * const ref = &a;
cout << "a:" << a << endl; //88
cout << "ref:" << ref << endl; //88
return 0;
}
应用场景:常量引用修饰函数形参(添加const关键字
),防止误操作,避免形参改变实参。
注:为避免数据被修改等误操作,可使用const关键字将函数形参的引用限定为常量引用,无法进行重新赋值操作(限定为只读不可写状态),若修改则编译器报错:表达式必须是可修改的左值。
【同《C++结构体》“7 结构体中const的使用场景”】
示例:
#include
using namespace std;
/* 常量引用修饰函数形参(添加const关键字),防止误操作,避免形参改变实参 */
void print(const int& value) { //常量引用作为函数形参时,形参不可改变
//value += 10; //错误:表达式必须是可修改的左值
cout << "value = " << value << endl;
}
int main() {
/* 非常量引用不能绑定字面量 */
//int& ref = 5; //编译器报错:非常量引用的初始值必须是左值
//使用const修饰引用后,编译器内部优化:int temp = 5; const int& ref = temp;
//但编译器内部创建的临时变量temp不可访问,只能使用引用(别名)访问
const int& ref = 5; //正确:常量引用
cout << "ref = " << ref << endl; //5
/* 应用场景:常量引用作为函数形参 */
int a = 10;
print(a);
cout << "a = " << a << endl; //10
return 0;
}