C++修炼篇:02 关于引用不可不知的问题

文章向导

引用的出场
引用与三目运算符
特殊的引用(const引用)
引用的本质是什么?
强制类型转换中的const引用


一、引用的出场
  
  众所周知,变量是一段实际连续存储空间的别名,编程者可通过变量的名字来访问这片存储空间。而引用则是为变量(对象)所起的一个别名,操作这个别名就等同于操作这个变量(对象)。
  定义一个引用的语法为:Type& name = var; 但需注意的是定义时必须用同类型的变量进行初始化,即如下所示:

int a = 0;
int &b = a; //b为a的别名
a = 2; //操作b就是操作a

一旦引用初始化完成,引用将和它的初始值对象一直绑定在一起。因此无法令引用重新绑定到另外一个对象上去。从这里也就可以知道:引用本身不是一个对象,它需要绑定一个已存在的对象实体,故不能定义引用的引用。
  
  下面是一些错误例子,想必可以帮助大家更好的理解引用的使用。

int &refval = 10; //error,引用类型的初始值必须是个对象
double dval = 3.14;
int &refval1 = dval; //error,引用类型的初始化值不是int类型
int &refval2; //error,引用类型初始化时必须绑定已存在的对象实体

二、引用与三目运算符

int a = 1;
int b = 2;

(a < b ? a : b) = 3; //OK, 返回a或b的引用,可作为左值使用
(a < b ? 1 : b) = 3; //ERROR, 返回1或b的值,不能作为左值

以上展示了两种三目运算符的使用情况,但其中一条是错的,另一条则是对的。具体来说可归结为如下结论:

  • 当三目运算符的可能返回值都是变量时,返回的是变量的引用。
  • 当三目运算符的可能返回值有常量时,返回的是值,故不能作为引用。

三、特殊的引用(const引用)

在上一篇博文中,笔者提到过const常量的概念(因为const对象一经创建后就不能再改变,故创建时必须进行初始化)。那么可否把引用绑定到const对象上呢?答案是肯定的,且我们把此称之为对常量的引用。
  但与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。下面来看两个具体的例子:

[例1]
int a = 4;
const int &b = a;
int *p = (int*)&b;

b = 5; //ERROR, 对常量的引用不能去修改其所绑定的对象
*p = 5; //OK, 修改变量a的值
[例2]
const int &b = 1; //使用字面值常量对const引用进行初始化,编译器会为常量值分配空间,并将引用名作为这段空间的别名。
int *p = (int*)&b;

b = 5; //ERROR, 对常量的引用不能去修改其所绑定的对象
*p = 5; //OK, 修改变量a的值

说道这儿,我们已经接触到const常量特性、const引用特性、使用字面常量值对const引用进行初始化三种情况。三者其实有着类似之处,但也有着比较重要的差异,下面用一综合实例来进行分析:

#include <stdio.h>

/*C++中的 const 常量特性:可能为 a 分配内存空间,但不一定使用其值*/
void Demo_one()
{
	printf("C++的 const 常量特性:\n");
	
	const int a = 0;
	int* p1 = (int*)&a;
	
	printf("a = %d, *p1 = %d\n", a, *p1); //0 0
	*p1 = 2;
	printf("a = %d, *p1 = %d\n", a, *p1); //0 2
}

/*C++中的 const 引用特性:b 虽然是 c 的别名,但 b 这个别名的操作是只读的*/
void Demo_two()
{
	printf("C++的 const 引用特性:\n");
	
	int c = 2;
	const int& b = c; //b 为 c 的 const 引用
	int* p2 = (int*)&b;
	
	printf("c = %d, b = %d, *p2 = %d\n", c, b, *p2); //2 2 2
	b = 5; //error, 对常量的引用不能去修改其所绑定的对象
	*p2 = 5;
	printf("c = %d, b = %d, *p2 = %d\n", c, b, *p2); //5 5 5
	c = 6;
	printf("c = %d, b = %d, *p2 = %d\n", c, b, *p2); //6 6 6
}

/*使用字面常量值对 const 引用初始化*/
void Demo_three()
{
	printf("使用常量对 const 引用初始化:\n");
	
	const int& c = 1;
	int* p = (int*)&c;
	
	printf("c = %d, *p = %d\n", c, *p); //1 1
	//c = 5; //error
	*p = 5;
	printf("c = %d, *p = %d\n", c, *p); //5 5
}

int main(int argc, char *argv[])
{
	Demo_one();
	printf("\n");
	Demo_two();
	printf("\n");
	Demo_three();
	return 0;
}

四、引用的本质是什么?

引用在C++中的内部实现是一个常量指针(指向不变,但指向的内容可变),这点要与**指针常量,即指向常量的指针(指向可变,但指向的内容是常量不可修改)**进行区分。
  理解了这一点后,我们就可以得出如下一种等价形式:

Type &name; <=> Type * const name;
      
  因此C++编译器在编译过程中用指针常量来作为引用的内部实现,这也就意味着引用所占用的空间大小与指针相同。但从我们使用者的角度来观察,引用只是一个别名,C++帮我们很好的隐藏了引用的存储空间这一细节。

下面这个例子则很好的说明了引用的本质特性,读者可用心体会。

#include <stdio.h>

struct TRef
{
	char& r;
};

int main(int argc, char *argv[])
{
	char c = 'c';
	char& rc = c;
	TRef ref = { c }; //==> 初始化: char& r = c;
	
	printf("sizeof(char&) = %d\n", sizeof(char&)); //1
	printf("sizeof(rc) = %d\n", sizeof(rc)); // <==> sizeof(c) = 1
	printf("sizeof(TRef) = %d\n", sizeof(TRef)); //8
	printf("sizeof(ref.r) = %d\n", sizeof(ref.r)); // <==> sizeof(c) = 1
	
	return 0;
}

应注意到sizeof(char&)与sizeof(TRef)的大小差别!!!


五、强制类型转换中的const引用

此部分所要谈及的是C++新式类型转换中的const_cast强制类型转换。该转换用于去除变量的只读属性,同时强制转换的目标必须是指针或引用。

#include<stdio.h>

int main()
{
	const int& j = 1; //使用字面常量值对const引用进行初始化
	int& k = const_cast<int&>(j);
	const int x = 2; //真正意义上的常量
	int& y = const_cast<int&>(x); //为x分配空间并将y作为其空间别名
	//int z = const_cast<int>(x); //Error,必须指是指针或引用
	
	k = 5;
	printf("k = %d\n", k); //5
	printf("j = %d\n", j); //5
	
	y = 8;
	printf("x = %d\n", x); //2, 取的是符号表中的值
	printf("y = %d\n", y); //8,取的是栈空间中的值
	printf("&x = %p\n", &x);
	printf("&y = %p\n", &y);

	return 0;
}

【测试结果】
这里写图片描述

参阅资料
primer c++ (第五版)
狄泰软件学院(C++深度解析教程)

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值