C++11新特性整理

简单整理了 C++11 的新特性

long long 类型

算数类型中引入 long long 类型
算数类型指: 整型 (包括字符和布尔类型) 和 浮点型

类型 含义 最小长度
bool 布尔类型 未定义
char 字符 8 位
char16_t Unicode 字符 16 位
char32_t Unicode 字符 32 位
double 双精度浮点数 10 位有效数字
float 单精度浮点数 6 位有效数字
int 整型 16 位
long 长整型 32 位
long double 扩展精度浮点数 10 位有效数字
long long 长整型 64 位
short 短整型 16 位
wchar_t 宽字符 16 位

nullptr 常量

空指针(null pointer) 值为 0 的指针,空指针合法但是不指向任何对象, nullptr 是一种特殊类型的字面值,它可以被转成任意其他的指针类型

int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;     // NULL 这个变量在头文件 cstdlib 中定义,它的值就是0

关于 nullptr 相对于 NULL 的优势,可以看一下这篇文章 为什么建议你用nullptr而不是NULL

初始化

初始化分为2种: 拷贝初始化和直接初始化

一位大佬很详细的解释

C++11提供一种统一的语法初始化任意的对象,它扩展了初始化列表语法

类内初始化

C++11新标准规定,可以为数据成员提供一个类内初始值(在结构体或类的数据成员声明的同时提供初始值,必须置于等号右侧或花括号内),创建对象时,类内初始值将用于初始化数据恒源,没有初始值的成员将被默认初始化。

struct example {
    int width = 100;
    int height{100};
};

列表初始化

使用花括号来初始化变量在C++11中得到全面应用

注意,如果使用列表初始化且初始值存在丢失信息的风险,则编译器会报错

long double ld = 3.141592654;
int a{Id} , b = {ld};          // 转换失败,存在信息丢失的风险 
int c(ld) , d = id;            // 转换成功,且确实丢失了部分值

列表初始化返回值

函数可以返回花括号包围的值的列表,此处的列表也用来对表示函数返回的临时量进行初始化,返回的值由函数的返回类型决定

vector<string> process(int condition)
{
    if (condition == 0)
        return {};
    else
        return {"test", "function"};
}

容器(顺序)的列表初始化

形式 内容
C c 默认构造函数
C c1{c2}
C c1=c2
c1初始化为c2的拷贝
C c{a,b,c…}
C c = {a,b,c…}
c 初始化为初始化列表中的元素拷贝
C c(b,e) c 初始化为迭代器 b 和 e 指定范围的元素拷贝

关联容器的列表初始化

set<string> exclude = { "the", "but", "and"};

map<string,string> authors = { {"Joyce", "janmes"},
                               {"Austen", "Jane"},
                               {"Dickens", "Charles"} };

列表初始化 pair 的返回类型

pair<string, int> process (vector<string> &v)
{
    if (!v.empty())
        return {v.back(), v.back().size()};
    else
        return pair<string,int>();

}

pair 的列表初始化

pair<string,int> p{"test". 1};

动态分配对象的列表初始化

vector<int> *pv = new vector<int>{0,1,2,3,4}

动态分配数组的列表初始化

int *p = new int[3]{1,2,3}

lambda 函数

一个 lambda 表达式表示一个可调用的代码单元,我们可以将其理解为一个未命名的内联函数,匿名函数

https://zh.wikipedia.org/wiki/%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0#C++_11

既然作为函数,需要返回类型,函数名称,0个或多个形参组成的列表以及函数体

[capture] (parameters) mutable exception attribute -> return_type { body }

如果lambda函数没有形参且没有 mutableexceptionattribute 声明,那么参数的空圆括号可以省略。但如果需要给出 mutableexceptionattribute 声明,那么参数即使为空,圆括号也不能省略。

如果函数体只有一个return语句,或者返回值类型为void,那么返回值类型声明可以被省略:

[capture](parameters){body}

根据上面的定义可以用一个简单的例子展示一下

[](int x, int y) -> int { return x + y; }
[](int x, int y) { return x + y; } 
[](int& x) { ++x; } 
[]() { ++global_x; } 
[]{ ++global_x; } 

C++ 同样支持闭包(简单点说,就是允许函数捕获一些变量,即使该函数在其作用域外被调用), 闭包在 lamdba 表达式声明的 [] 中定义

[]         // 未定义变量。尝试在lambda中使用任何外部变量是一个错误。
[x, &y]    // x 按值捕获,y 引用捕获
[&]        // 任何外部变量被引用隐式捕获 (如果使用)
[=]        // 任何外部变量被值隐式捕获 (如果使用)
[&, x]     // x 通过值显式捕获。其他变量将通过引用
[=, &z]    // z 通过引用显式捕获。其他变量将按值捕获

注意:

  • 默认的捕获写在前,特殊捕获写在后, 例如 [&x, =] 这种写法就是错的
  • 按值捕获的变量是个只读常量,默认是不允许在函数体中修改捕获的变量,而用 mutable 修饰 lamdba 函数可以打破这种限制

使用引用捕获

std::vector<int> some_list{ 1, 2, 3, 4, 5 };
int total = 0;
std::for_each(begin(some_list), end(some_list),
              [&total](int x) { total += x; });

total 值为 75

使用按值捕获

std::vector<int> some_list{ 1, 2, 3, 4, 5 };
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list),
              [&total, value](int x) { total += x*value; });

total 值为 75, value 值为 5

现在使用 mutable 修饰 lamdba 函数,改变 value 值

std::vector<int> some_list{ 1, 2, 3, 4, 5 };
int total = 0;
int value = 5;
std::for_each(begin(some_list), end(some_list),
              [&total, value](int x) mutable {value=2; total += x*value; });

total 值为 30, value 值为 5

从这里我们可以看出,我们给 value 做了赋值操作,但是这一操作仅只在函数内部生效而已,实际是给拷贝至函数内部的 value 进行赋值,而外部的 value 的值依旧是 5

this 捕获

[this] 捕获当前类的 this 指针,让 lambda 表达式拥有和当前类成员同样的访问权限,可以修改类的成员变量,使用类的成员函数。

如果已经使用了 & 或者 =,就默认添加此选项。

class Foo
{
public:
    Foo() : x(10) {}

    void bar()
    {
        auto lam = [this](){ return ++x; };
        std::cout << lam() << std::endl;
    }
private:
    int x;
};

Foo foo;
foo.bar(); // Outputs 11
foo.bar(); // Outputs 12

结合 QT

Qt5, 可以将 lambda 用于 connect 中,而不必定义单独的函数。

举个简单的例子, m_lineEdit 上的值发生改变,将值显示在 m_label 上

connect(m_lineEdit, &QLineEdit::textChanged,
         [this](const QString& s) { m_label->setText(s); });

lambda 补充

关于 lambda 表达式的类型, 一般情况下我们使用 auto 关键字自动推导类型,同时我们也可以使用 std::functionstd::bind 来存储和操作 lambda 表达式

std::function<int (int)> f1 = [](int a) {return a;};
std::function<int (int)> f2 = std::bind([](int a){return a;}, std::placeholders::_1);
std::cout << f1(22) << std::endl;
std::cout << f2(22) << std::endl;

对于没有捕获任何变量的lambda,还可以转换成一个普通的函数指针。

int (*f)(int) = [](int a){return a;};
std::cout << f(1) << std::endl;

关键字

constexpr

提到 constexpr, 需要简单解释一下 常量表达式,常量表达式是指代码中类似于 1+1 每次执行都是相同的结果,直接用结果 2 代替不会产生新的问题,而这一部分是编译时期就能优化。而在数组大小的定义和枚举值等地方C++要求必须使用常量表达式

修饰变量

C++11之前,可以在常量表达式中使用的的变量必须被声明为const,用常量表达式来初始化,并且必须是整型或枚举类型。C++11去除了变量必须是整型或枚举类型的限制,只要变量使用了constexpr关键字来定义:

constexpr double earth_gravitational_acceleration = 9.8;
constexpr double moon_gravitational_acceleration = earth_gravitational_acceleration / 6.0;

修饰函数

声明为constexpr的函数也可以像其他函数一样用于常量表达式以外的地方, 但是用constexpr修饰函数将限制函数的行为:

  • 函数返回值不能是void类型
  • 函数体不能声明变量或定义新的类型
  • 函数体只能包含声明、null语句或者一条return语句
  • 在形参实参结合后,return语句中的表达式为常量表达式

    constexpr int GetFive() {return 5;}

    int some_value[GetFive() + 7];

修饰构造

通过前置 constexpr 关键字,就可以声明 constexpr 构造函数,同时:除了声明为 =default 或者 =delete 以外,constexpr 构造函数的函数体一般为空,使用初始化列表或者其他的 constexpr 构造函数初始化所有数据成员,之后这个构造函数就可以被用在其它constexpr里

类型别名 using

使用类型别名可以使复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚的知道使用该类型的真实目的

类型别名有下面2种方法:

  • typedef

    typedef double wages;

  • 别名声明
    关键字 using 作为别名声明的开始,后面紧跟别名和等号,其作用是把等号左侧的名字规定成等号右侧类型的别名

    using pFunc = int (*)(int); pFunc f = [](int a){return a;}; std::cout << f(1) << std::endl;

auto

auto 用它能让编译器替我们去分析表达式所属的类型, auto 让编译器通过初始值来推算变量的类型,也就是说,auto 定义的变量必须有初始值

注意: auto 一般会忽略掉 顶层const , 同时 底层const 会被保存下来

顶层const 可以表示任意的对象是常量
底层const 则与指针和引用等复合类型的基本基础类型部分有关,表示指向的对象是一个常量
简单点说就是用 const 修饰对象的就是 顶层const ,比较特殊的是指针,既可以是 顶层const,也可以使 底层const

例如

int i=0;
int *const p1 = &i;       // 不能修改 p1, 顶层const
const int ci = 10;        // 不能修改 a, 顶层const
const int *p2 = &ci;      // 允许修改 p2. 底层const
const int *const p3 = p2  // 第一个是底层const, 第二个是顶层const
const int &r = ci         // 用于声明引用的const 都是底层const

回头再一下 auto

const int ci = 1;      // 顶层 const 
const int &cr = ci;    // 底层 const

auto b = ci;    // b 是一个整数
auto c = cr;    // c 是一个整数
auto d = &i;    // d 是一个整形指针
auto e = &ci;   // e 是一个指向整数常量的指针

decltype

作用:选择并返回操作数的数据类型

decltype 对 顶层const底层const 处理和 auto 不同,原对象类型是啥,返回类型包括顶层const和引用

int i=42, *p =&i, &r = i;
decltype(r+0) b;    // 未初始化的 int
decltype(*p) c;     // 错误, int& 必须初始化

解引用操作: *p 解引用指针可以得到指针的所指的对象,而且还可以给这个对象赋值,因此 decltype(*p) 的结果类型就是 int& , 而非 int

注意: decltype((variable)) (注意是双括号)的结果永远是引用。

noexcept

使用noexcept表明函数或操作不会发生异常,会给编译器更大的优化空间。

智能指针

更简单的语法和新规定

范围 for 语句

for语句允许简单的范围迭代

for (declaration: expression)
    statemnt
  • expression 必须表示一个序列,这些类型的共同特点是拥有能返回迭代器的 beginend 成员
  • declaration 定义一个变量,序列中的每个元素都得能转换成该变量的类型,最简单直接使用 auto 类型说明符

例如:

int my_array[5] = {1, 2, 3, 4, 5};

for (int &x : my_array) {
    x *= 2;
}

for (auto &x : my_array) {
    x *= 2;
}

定义 vector 对象的 vector

旧版本中,如果vector的元素还是vector 或者其他模板类型时,必须在外层 vector 对象的右尖括号和其元素类型之间的添加一个空格,新版本没有这个限制了

vector<vector<int> >

原因: C++03的分析器一律将”>>“视为右移运算符。但在嵌套模板定义式中,绝大多数的场合其实都代表两个连续右角括号。为了避免分析器误判,撰码时不能把右角括号连着写。

C++11变更了分析器的解读规则;当遇到连续的右角括号时,会在合理的情况下将右尖括号解析为模板引用的结束符号。给使用>,>=,>>的表达式加上圆括号,可以避免其与圆括号外部的左尖括号相匹配:

除法的舍入规则

C++11 新标准则规定商一律向0取整,即直接切除小数部分

iota 函数

iota 函数可将给定区间的值设定为从某值开始的连续值,例如将连续十个整数设定为从 1 开始的连续整数(即 1、2、3、4、5、6、7、8、9、10)。

#include <iostream>
#include <array>
#include <numeric>

std::array<int, 10> ai;
std::iota(ai.begin(), ai.end(), 1);
for(int i: ai){
    std::cout<<i<<" ";    //1 2 3 4 5 6 7 8 9 10 
}

可扩展的随机数功能

C标准库允许使用rand函数来生成伪随机数。不过其算法则取决于各程序库开发者。C++直接从C继承了这部分,但是C++11将会提供产生伪随机数的新方法。

C++11的随机数功能分为两部分:第一,一个随机数生成引擎,其中包含该生成引擎的状态,用来产生随机数。第二,一个分布,这可以用来决定产生随机数的范围,也可以决定以何种分布方式产生随机数。随机数生成对象即是由随机数生成引擎和分布所构成。

不同于C标准库的rand;针对产生随机数的机制,C++11将会提供三种算法,每一种算法都有其强项和弱项:

模板类 整数/浮点数 质量 速度 状态数*
linear_congruential 整数 中等[来源请求] 1
subtract_with_carry Unicode两者皆可 中等 25
mersenne_twister 整数 624

C++11将会提供一些标准分布:

  • uniform_int_distribution(整数均匀分布)
  • bernoulli_distribution(伯努利分布)
  • geometric_distribution(几何分布)
  • poisson_distribution(卜瓦松分布)
  • binomial_distribution(二项分布)
  • uniform_real_distribution(浮点型均匀分布)
  • exponential_distribution(指数分布)
  • normal_distribution(正态分布)
  • gamma_distribution(伽玛分布)

底下描述一个随机数生成对象如何由随机数生成引擎和分布构成:

std::uniform_int_distribution<int> distribution(0, 99); // 以离散型均匀分布方式产生int乱数,范围落在0到99之間
std::mt19937 engine; // 建立乱数生成引擎
auto generator = std::bind(distribution, engine); // 利用bind將乱数生成引擎和分布組合成一個乱数生成物件
int random = generator();  // 产生乱数