简单整理了 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函数没有形参且没有 mutable
、exception
或 attribute
声明,那么参数的空圆括号可以省略。但如果需要给出 mutable
、exception
或 attribute
声明,那么参数即使为空,圆括号也不能省略。
如果函数体只有一个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::function
和 std::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 必须表示一个序列,这些类型的共同特点是拥有能返回迭代器的
begin
和end
成员 - 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(); // 产生乱数