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++深度解析教程)