C++11——变量和基础类型

列表初始化(List Initialization)

当初始化列表与内置类型的变量一起使用时,这种初始化形式具有一个重要属性:如果初始化器可能导致信息丢失,编译器将不允许我们列出内置类型的初始化变量。

Code:
    long double ld = 3.1415926536;
    int a{ld}, b = {ld};    // error:narrowing conversion required
    int c(ld), d = ld;      // ok: but value will be truncated

编译器拒绝初始化ab,因为使用long double初始化int可能会丢失数据。至少ld的小数部分将被截断,另外ld中的整数部分可能太大而不适合int
这一新特性似乎与C++98区别不大,毕竟我们不太可能直接使用long double初始化int。然而在“模版编程和范型编程”中,可能无意中发生这样的初始化!

空指针(Null Pointers)

初始化指针最直接的方法是使用新标准引入的字面量nullptr初始化指针。 nullptr是一个具有特殊类型的字面量,可以转换为任何其他指针类型。 或者我们可以使用字面量0初始化一个指针。
较老的程序有时使用预处理程序变量NULL初始化指针。该变量在cstdlib头文件中被定义为0。
现代C++程序通常应该避免使用NULL,而应该使用nullptr作为替代。

Code:
    int *p1 = nullptr;    // equivalent to int *p1 = 0;
    int *p2 = 0;          // directly initializes p2 from the literal constant 0
    // #include <cstdlib>
    int *p3 = NULL;       // equivalent to int *p3 = 0;

int型变量赋给指针是非法的,即使该int变量的值恰好为0。

Code:
    int zero = 0;
    int *pi = zero;    // error: cannot assign an int to a pointer

初始化指针的注意事项

未初始化的指针是运行时错误(run-time errors)的常见原因。与任何其他未初始化的变量一样,当我们使用未初始化的指针时会发生什么是未定义的。 使用未初始化的指针几乎总会导致运行时崩溃。然而调试这种崩溃往往非常困难。
我们建议初始化所有变量特别是指针。 如果可能仅在定义了欲指向的对象之后定义指针。如果指针没有要指向的对象,那么就将指针初始化为nullptr或0,这样程序就可以判断这个指针没有指向任何对象。


constexpr变量(constexpr Variables)

常量表达式是一个表达式,其值不能更改,并且可以在编译时进行计算。

Code:
    const int max_files = 20;          // max_files is a constant expression
    const int limit = max_files + 1;   // limit is a constant expression
    const int sz = get_size();         // sz is not a constant expression

虽然sz是一个const int类型的变量,但它不是一个常量表达式,因为它的初始化器的值只有在运行时才能获得。
在新标准下,我们可以通过使用constexpr声明一个变量,从而要求编译器去验证该变量是否为常量表达式。

Code:
    constexpr int mf = 20;          // 20 is a constant expression
    constexpr int limit = mf + 1;   // mf + 1 is a constant expression
    constexpr int sz = size();      // ok only if size is a constexpr function

虽然我们不能使用普通函数作为constexpr变量的初始化函数,但新标准允许我们将某些函数定义为constexpr。 这些函数必须足够简单,以便编译器可以在编译时对它们进行评估。 我们可以在constexpr变量的初始化器中使用constexpr函数。
通常,最好将constexpr用于打算用作常量表达式的变量。

类型别名(Type Aliases)

通常我们使用typedef来定义类型别名,新标准引入了另一种通过别名声明来定义类型别名的方法:

Code:
    using SI = Sales_item;    // SI is a synonym for class Sales_item

别名声明以关键字using开头,后跟别名和=。 别名声明将=左侧的名称定义为右侧显示的类型的别名。类型别名是类型名称,可以出现在类型名称出现的任何位置:

Code:
    SI item;                // same as Sales_item item

auto类型说明符(The auto Type Specifier)

auto类型说明符是C++11全新引入的。
编写程序过程中我们想要将表达式的值存储在变量中并不罕见。 要声明变量,我们必须知道该表达式的类型。 当我们编写程序时,确定表达式的类型可能会非常困难,有时甚至是不可能的。 在新标准下,通过使用auto类型说明符我们可以让编译器为我们找出类型。 与类型说明符(例如double)命名特定类型不同,auto告诉编译器要从初始化程序中推断出类型。 因为编译器对于auto说明符需要推断的依据,所以使用auto作为其类型说明符的变量必须具有初始化器:

Code:
    // the type of item is deduced from the type of the result of adding val1 and val2
    auto item = val1 + val2; // item initialized to the result of val1 + val2

此处编译器将根据val1 + val2返回的类型来推断item的类型。如果val1val2是类Sales_item的对象,那么item将具有类型Sales_item。如果val1val2的类型是double,那么item将是类型double的变量。
与任何其他类型说明符一样,我们可以使用auto定义多个变量。 因为声明只涉及单个基类型,所以声明中所有变量的初始化器必须具有彼此一致的类型:

Code:
    auto i = 0, *p = &i;       // ok: i is int and p is a pointer to int
    auto sz = 0, pi = 3.14;    // error: inconsistent types for sz and pi
符合类型,const和auto

编译器推断auto的类型并不总是与初始化程序的类型完全相同。 相反,编译器会调整类型以符合常规初始化规则。
首先,当我们使用引用时,我们实际上正在使用被引用的对象的一个引用。 特别是当我们使用引用作为初始化器时,初始化器是相应的对象。 编译器将该对象的类型用于auto的类型推导:

Code:
    int i = 0, &r = i;
    auto a = r;  // a is an int (r is an alias for i, which has type int)

其次,auto通常会忽略顶层const。 但初始化时会保持低层const,例如初始化器是指向const的指针时:

Code:
    const int ci = i, &cr = ci;
    auto b = ci;  // b is an int (top-level const in ci is dropped)
    auto c = cr;  // c is an int (cr is an alias for ci whose const is top-level)
    auto d = &i;  // d is an int*(& of an int object is int*)
    auto e = &ci; // e is const int*(& of a const object is low-level const)

如果我们想要推理出保留顶层const的类型,需要进行显示声明:

Code:
    const auto f = ci; // deduced type of ci is int; f has type const int

我们还可以指定对auto推理出的类型的引用。 通常的初始化规则仍然适用:

Code:
    auto &g = ci;         // g is a const int& that is bound to ci (can't ignore the top-level const in ci)
    auto &h = 42;         // error: we can't bind a plain reference to a literal
    const auto &j = 42;   // ok: we can bind a const reference to a literal

当我们要求引用由auto推理出的类型时,初始化器中的顶层const不会被忽略。 通常当我们将引用绑定到初始化器时,const就不是顶层的了。
当我们在同一个语句中定义多个变量时,重要的是要记住引用或指针是特定声明符的一部分,而不是声明的基本类型的一部分。 通常初始化器必须提供一致的自动推导类型:(注:auto用于推导基本类型)

Code:
    auto k = ci, &l = i;      // k is int; l is int&
    auto &m = ci, *p = &ci;   // m is a const int&;p is a pointer to const int
    // error: type deduced from i is int; type deduced from &ci is const int
    auto &n = i, *p2 = &ci;

注:\color{red}{auto容易被滥用!}使用过程中应该时刻注意由auto推理出的类型!

decltype类型说明符(The decltype Type Specifier)

有时我们想要定义一个变量,该变量具有编译器从表达式推导出的类型,但不希望使用该表达式来初始化变量。 对于这种情况,新标准引入了第二个类型说明符decltype,它返回其操作数的类型。 编译器分析表达式以确定其类型,但不评估表达式:

Code:
    decltype(f()) sum = x;   // sum has whatever type f returns

这里,编译器不调用函数f(),但它使用函数f()返回的类型作为sum的类型。
decltype处理顶层const和引用的方式与auto的处理方式略有不同。 当我们对一个变量应用decltype时,decltype返回该变量的类型,包括顶层const和引用:

Code:
    const int ci = 0, &cj = ci;
    decltype(ci) x = 0;     // x has type const int
    decltype(cj) y = x;     // y has type const int& and is bound to x
    decltype(cj) z;         // error: z is a reference and must be initialized

值得注意的是,decltype是唯一一个定义为引用的变量不被视为它所引用的对象的同义词的说明符。

decltype和引用

当我们将decltype应用于不是变量的表达式时,我们得到该表达式返回的类型。某些表达式将导致decltype生成引用类型, 通常decltype为该类表达式返回一个引用类型,这些表达式产生的对象可以作为左值:

Code:
    // decltype of an expression can be a reference type
    int i = 42, *p = &i, &r = i;
    decltype(r + 0) b;    // ok: addition yields an int; b is an (uninitialized) int
    decltype(*p) c;       // error: c is int& and must be initialized

此处r是一个引用,所以decltype(r)是一个引用类型。如果我们想要获得r引用的类型,我们可以将r应用到表达式中(如:r + 0),这个表达式将产生一个不是引用类型的值。
另一方面,解除引用运算符是decltype返回引用的表达式的一个例子。 正如我们所见,当我们解引用指针时,我们得到指针指向的对象。 而且,我们可以对该对象进行赋值。 因此,由decltype(*p)推导出的类型是int&,而不是无格式的int
decltypeauto之间的另一个重要区别是decltype所做的推论取决于其给定表达式的形式。 令人困惑的是将变量名用括号括起来将会影响decltype返回的类型。 当我们将decltype应用于没有任何括号的变量时,我们得到该变量的类型。 如果我们将变量的名称包装在一组或多组括号中,编译器会将变量及括号计算为一个表达式。 变量是一个可以作为左值进行赋值的表达式。结果是decltype作用于这样的表达式上将产生一个引用:

Code:
    // decltype of a parenthesized variable is always a reference
    decltype((i)) d;    // error: d is int& and must be initialized
    decltype(i) e;      // ok: e is an (uninitialized) int

\color{red}{注:}decltype((variable))\color{red}{一直是一个引用类型;但是}decltype(variable)\color{red}{仅当}variable\color{red}{是一个引用类型时才会返回引用!}

参考文献

[1] Lippman S B , Josée Lajoie, Moo B E . C++ Primer (5th Edition)[J]. 2013.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容