这是C ++中的一个怪癖/功能。尽管我们不认为引用是类型,但实际上它们是在类型系统中“坐下”的。尽管这看起来很尴尬(考虑到使用引用时,引用语义会自动出现,并且引用“摆脱干扰”),但仍有一些合理的理由说明为什么要在类型系统中对引用进行建模,而不是将引用作为外部的单独属性进行建模类型。
首先,让我们考虑并非声明名称的每个属性都必须在类型系统中。从C语言开始,我们有“存储类”和“链接”。可以将名称引入extern const int ri
,其中extern
指示静态存储类和链接的存在。类型是const int
。
C ++显然接受这样一个概念,即表达式具有类型系统之外的属性。该语言现在具有“值类”的概念,它是一种尝试组织越来越多的表达式可以显示的非类型属性的尝试。
但是引用是类型。为什么?
过去在C ++教程中曾解释说,这样const int &ri
引入的声明ri
具有type const int
,但是引用了语义。该引用语义不是一种类型;它只是一种属性,指示名称与存储位置之间的异常关系。此外,引用不是类型这一事实被用来合理化为什么您不能基于引用构造类型,即使类型构造语法允许这样做也是如此。例如,无法使用数组或指向引用的指针:const int &ari[5]
和const int &*pri
。
但是实际上引用是类型,因此会decltype(ri)
检索某些不合格的引用类型节点。您必须下降经过类型树中的该节点才能使用到达基础类型remove_reference
。
当您使用时ri
,引用将透明地解析,因此ri
“外观和感觉类似i
”,因此可以称为“别名”。但是,在类型系统中,ri
实际上确实具有“引用 const int
”的类型。
为什么是引用类型?
考虑如果引用不是类型,则这些函数将被视为具有相同的类型:
void foo(int);
void foo(int &);
那根本不可能是出于显而易见的原因。如果它们具有相同的类型,则意味着任何一个声明都适用于任何一个定义,因此(int)
必须怀疑每个函数都使用了引用。
同样,如果引用不是类型,则这两个类声明将是等效的:
class foo {
int m;
};
class foo {
int &m;
};
一个翻译单元使用一个声明,而同一程序中的另一个翻译单元使用另一个声明是正确的。
事实是引用意味着实现上的差异,并且不可能将其与类型分开,因为C ++中的类型与实体的实现有关:可以这么说,它的“布局”以位为单位。如果两个函数具有相同的类型,则可以使用相同的二进制调用约定来调用它们:ABI是相同的。如果两个结构或类具有相同的类型,则它们的布局以及访问所有成员的语义都相同。引用的存在改变了类型的这些方面,因此将它们合并到类型系统中是一个直接的设计决定。(但是,请注意此处的一个反参数:struct / class成员可以是static
,它也会更改表示形式;但这不是类型!)
因此,引用在类型系统中为“第二类公民”(与ISO C中的函数和数组不同)。在某些情况下,我们无法使用引用“执行”操作,例如声明指向引用的指针或它们的数组。但这并不意味着它们不是类型。它们只是不是有意义的类型。
并非所有这些第二类限制都是必不可少的。假设存在引用结构,则可能存在引用数组!例如
int x = 0, y = 0;
int &ar[2] = { x, y };
只是没有在C ++中实现,仅此而已。但是,指向引用的指针根本没有任何意义,因为从引用中提起的指针只是指向被引用的对象。没有引用数组的可能原因是C ++人们认为数组是从C继承的一种低级功能,它以许多无法修复的方式被破坏,并且他们不想将数组作为任何新事物的基础。但是,引用数组的存在将清楚地说明引用必须是类型的例子。
非const
-qualifiable类型:在ISO C90发现呢!
一些答案暗示了引用没有const
限定词的事实。那真是个红鲱鱼,因为声const int &ri = i
明甚至都没有尝试进行const
-qualified引用:它是对const限定类型的引用(它本身不是const
)。就像const in *ri
声明一个指向某物的指针一样const
,但是该指针本身不是const
。
也就是说,确实引用不能const
本身带有限定符。
然而,这并不是那么奇怪。即使使用ISO C 90语言,也不是所有类型都可以const
。也就是说,数组不能是。
首先,声明常量数组的语法不存在:int a const [42]
是错误的。
但是,上面的声明试图做的事情可以通过一个中间体来表达typedef
:
typedef int array_t[42];
const array_t a;
但这并没有像看起来那样做。在此声明,这不是a
它得到const
合格的,但元素!也就是说,a[0]
是const int
,但a
只是“整数数组”。因此,这不需要诊断:
int *p = a;
这样做:
a[0] = 1;
再一次强调了这样的想法,即引用在某种意义上是类型系统中的“第二类”,例如数组。
请注意,类比的含义更深,因为数组还具有“不可见的转换行为”,如引用。无需程序员使用任何显式运算符,标识符就a
自动变成一个int *
指针,就像使用了表达式&a[0]
一样。这类似于引用ri
,当我们将其用作主要表达式时,如何神奇地表示i
绑定到的对象。这只是另一个“衰变”,例如“指针衰减数组”。
就像我们一定不要对“指向指针的数组”的迷恋误入为“数组只是C和C ++中的指针”而感到困惑一样,我们同样也不能认为引用只是没有自己类型的别名。
当decltype(ri)
抑制对引用对其引用对象的常规转换时,这与sizeof a
抑制数组到指针的转换以及对数组类型本身进行操作以计算其大小没有什么不同。
boolalpha(cout)
是非常不寻常的。您可以std::cout << boolalpha
代替。