自学内容网 自学内容网

5 C++ const 常量限定符和处理类型

1 const常量限定符

  const常量限定符可以定义常量。因为const对象一旦创建,其值就不能再改变,因此const对象必须初始化。初始化可以使用任意复杂的表达式。const对象只能执行不改变其内容的操作。在编译过程中,编译器会把用到const对象的地方都替换成对应的值。

  默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。为了在文件之间共享const对象,我们需要在声明和定义时都使用extern关键字,这样只需定义一次,并在其它文件中都声明就可以了。

1.1 const的引用

  可以把引用绑定到const对象上,称之为常量引用。与普通引用不同的是,常量引用的对象不能被修改。

  在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。当一个常量引用被绑定到另外一种类型上时,编译器会创建一个转换数据类型的临时量对象,然后常量引用绑定临时量对象。

  常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其它途径改变它的值:

int i = 42;
int &r1 = i;          // 引用 r1 绑定对象 i
const int &r2 = i;    // r2 也绑定对象 i,但是不允许通过 r2 修改 i 的值
r1 = 0;
r2 = 0;               // 错误:r2 是一个常量引用

r2绑定非常量是合法行为。然而,不允许通过r2修改i的值。而i的值可以通过其它途径修改,既可以直接给i赋值,也可以绑定到其它引用来修改。

1.2 指针和const

  指针也可以指向常量,并且指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。

  允许令一个指向常量的指针指向一个非常量对象。和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其它途径改变。

  指针是对象,而引用不是,因此允许把指针本身定为常量。常量指针必须初始化,而且一旦初始化完成,则它的值就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写隐含着一层意味,即不变的是指针本身的值而不是指向的那个值。

  指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。

1.3 顶层和底层const

  指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是常量就是两个相互独立的问题。用名词顶层const表示指针本身是个常量,而名词底层const表示指针所指的对象是一个常量。

  更一般的,顶层const可以表示任意的对象是常量,对任何数据类型都适用。底层const则与指针和引用等复合类型的基本类型部分有关。

  当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。顶层const则不受什么影响。

1.4 constexpr和常量表达式

  常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。用常量表达式初始化的const对象也是常量表达式。

  一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。

  C++ 允许将对象声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的对象一定是一个常量,而且必须用常量表达式初始化。

  常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见,就把它们称为 “字面值类型”。算术类型、引用、指针都属于字面值类型,自定义类、IO 库、string类型则不属于字面值类型,也就不能被定义成constexpr

  尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr、0 或者是存储于某个固定地址中的对象。

  函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于函数体之外的对象的地址固定不变,能用来初始化constexpr指针。C++ 还允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。因此,constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。

2 类型别名

  类型别名是一个名字,它是某种类型的同义词。使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的目的。

  有两种方法可用于定义类型别名。传统方法是使用关键字typedef。关键字typedef作为声明语句中的基本数据类型的一部分出现。含有typedef的声明语句定义的不再是变量而是类型别名。这里的声明符也可以包含类型修饰,从而也能由基本数据类型构造出复合类型。

  第二种方法是使用别名声明来定义类型的别名。这种方法用关键字using作为别名声明的开始,其后紧跟别名和等号,其作用是把等号左侧名字规定成等号右侧类型的别名。

  类型别名和类型的名字等价,只要是类型的名字能出现的地方,就能使用类型别名。

3 auto类型说明符

  auto类型说明符能让编译器通过初始值替我们去分析表达式所属的类型。因此,auto定义的变量必须有初始值。例如:

auto item = val1 + val2;     // item 初始化为 val1 和 val2 相加的结果,并推断出 item 的类型

  使用auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样。例如:

auto i = 0, *p = &i;       // 正确:i 是整数,p 是整形指针
auto sz = 0, pi = 3.14;    // 错误:sz 和 pi 的类型不一致

  编译器推断出来的auto类型有时和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。
  首先,使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型。例如:

int i = 0, &r = i;
auto a = r;        // a 是一个整型

  其次,auto一般会忽略掉顶层const,同时底层const则会保留下来。例如,当初始值是一个指向常量的指针时:

const int ci = i, &cr = ci;
auto b = ci;         // b 是一个整型(ci 的顶层 const 特性被忽略掉了)
auto c = cr;         // c 是一个整型(cr 是 ci 的别名,ci 本身是一个顶层 const)
auto d = &i;         // d 是一个整型指针(整型的地址就是指向整型的指针)
auto e = &ci;        // e 是一个指向整数常量的指针(对常量对象取地址是一种底层 const)

如果希望推断出的auto类型是一个顶层const,则需要使用const关键字明确指出。例如:

const auto f = ci;        // ci 的推演类型是 int,f 是 const int

还可以将引用的类型设为auto,此时原来的初始化规则仍然适用。例如:

auto &g = ci;          // g 是一个整型常量引用,绑定到 ci
auto &h = 42;          // 错误:不能为非常量引用绑定字面值
const auto &j = 42;    // 正确:可以为引用常量绑定字面值

设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。

4 decltype类型指示符

  有时会遇到这种情况:希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。decltype类型指示符的作用就是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

  decltype处理顶层const和引用的方法与auto有些许不同。如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。

  需要指出的是,引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外。

  如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。有些表达式将向decltype返回一个引用类型。一般来说,当这种情况发生时,意味着该表达式的结果对象能作为一条赋值语句的左值。

  另一方面,如果表达式的内容是解引用操作,则decltype将得到引用类型。解引用指针可以得到指针所指的对象,而且还能给这个对象赋值。

  decltypeauto的另一处重要区别是,decltype的结果类型与表达式形式密切相关。有一种情况需要特别注意:对于decltype所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。


原文地址:https://blog.csdn.net/zhaobenjian/article/details/143756264

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!