C++引用(变量引用、数组引用与数组指针、引用本质-指针常量、常量引用)

文章目录

  • 1 引用的基本用法
    • 1.1 变量的引用
    • 1.2 数组的引用
  • 2 引用注意事项
  • 3 引用作为函数参数
  • 4 引用作为函数返回值
  • 5 引用的本质——指针常量
  • 6 常量引用


1 引用的基本用法

作用:为变量或数组起别名。

1.1 变量的引用

语法数据类型 &别名 = 原名;

示例

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.2 数组的引用

(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]类型的实体。

  1. &arr整个数组取地址;
    arr&arr[0]数组首元素取地址【地址值相同,含义不同】。
  2. 一维数组名不能解释为数组首元素地址的两种特例【其它情况下,数组名表示数组首元素地址】:
    sizeof(数组名):整个数组的大小;
    &数组名:整个数组的地址(地址值与首元素地址相同,但意义不同)
  3. &arr表示整个数组的地址,指向整个数组,&arr + 1会跳过整个数组【加上整个数组的总字节数】,如int *p = (int *)(&arr + 1),指针p指向数组的末尾;
    arr&arr[0]表示数组首元素地址,指向第1个元素,arr + 1&arr[0] + 1会跳过第1个元素【加上1个数组元素的字节数】,指向数组的下1个元素。
  4. &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;
}

2 引用注意事项

(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;
}

3 引用作为函数参数

作用:函数传参时,可通过引用使形参改变实参。
优点:简化指针的用法,使用形参修改实参。

注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;
}

4 引用作为函数返回值

作用:引用可作为函数的返回值。
注意事项
(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;
}

5 引用的本质——指针常量

本质: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;
}

6 常量引用

应用场景:常量引用修饰函数形参(添加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;
}

你可能感兴趣的:(C++面向对象,c++)