1. 复习前面介绍过的C++11功能
1.1 新类型
C++新增了long long和unsigned long long,以支持64位的整型;新增了类型char_16_t和char32_t,以支持16位和32位的字符表示;
1.2 统一的初始化
C++11扩大了用大括号括起来的列表(初始化列表)的适用范围。使用初始化列表时,可添加等号(=),也可不添加。
int x = {
5};
double y {
2.75};
short quar[5] {
1, 2, 3, 4, 5};
int *ar = new int [4] {
2, 4, 6, 7};
Class s1{
3, 15};//创建对象
如果类有将模板std::initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式。另外,初始化列表不允许缩窄操作!可以进行宽操作。
char c1 {
3.3};//不允许
char c2 {
66}//范围内,允许
1.3 声明
1.3.1 auto
auto可用于自动类型推断(初始化时):
auto maton = 112;//int
auto pt = &maton;//指针
for(auto p = il.begin();p != il.end(); p++);
1.3.2 decltype
decltype(x) y;//让y的类型与x相同
1.3.3 返回类型后置
C++新增了一种函数声明语法:在函数名和参数列表后面指定返回类型:
double f1(double, int)//传统语法
auto f2(double, int) -> double//新语法,返回类型为double
这种方式配合decltype来使用
template<typename T, typename U>
auto eff(T t, U u) ->decltype(T*U)//类型未知,但是类型和T*U相同
{
...}
1.3.4 using =
用法和typedef相同:
typedef std::vector<std::string>::iterator itType;
using itType = std::vector<std::string>::iterator;
但是在模板部分具体化时,只能用using=:
template<typename T>
using arr12 = std::arr<T,12>
//下列的实现相同:
std::array<double,12> a1;
arr12<double> a1;
1.3.4 nullptr
C++11新增了nullptr,用于表示空指针,它是指针类型,不能转化为整型类型。但是为了向后兼容,仍可以用于进行判断:
nullptr == 0;//可以判断,但不能转换为0
1.4 智能指针
一共有四个智能指针:auto_ptr(c++11摒弃)、unique_ptr、shared_ptr和weak_ptr。
1.5 作用域内枚举
C++11新增一种枚举,适用于class或struct:
enum Old1 {
yes, no, maybe};
enum class New1 {
never, sometimes, often, always};
enum struct New2 {
never, lever, sever};
新枚举要求进行显式限定,避免名称冲突。New1::never和New2::never等
1.6 对类的修改
1.6.1 显式转换运算符
C++引入了关键字explicit,以禁止单参数构造函数导致的自动转换:
class Plebe
{
Plebe(int);
explicit Plebe(double);
};
Plebe a, b;
a = 5;//隐式转换
b = 0.5;//不允许
b = Plebe(0.5);//显式转换explicit
C++11拓展了explicit的这种用法,使得可对转换函数做类似的处理:
class Plebe
{
//转换函数
operator int()const;
explicit operator double() const;
};
Plebe a, b;
int n = a;//自动转换
double x = b;//不允许
x = double (b);//显式转换explicit
1.6.2 类内成员初始化
class Session
{
int mem1 = 10;//类内初始化
double mem2 {
1966.54};//类内初始化
short mem3;;
public:
Session(int n, double d, short s):mem1(n), mem2(d),mem3(s){
}//覆盖类内初始化
}
如果构造函数提供了初始化列表,则类内初始化会被覆盖。
1.7 模板和STL方面的修改
1.7.1 基于范围的for循环
对于内置数组和包含方法begin()和end()的类(std::string)和STL容器,基于范围的for循环可简化为它们编写循环的工作。
double prices[5] = {
3.4, 5.6, 4.6, 2,3, 5,9};
for(double x : prices)//for(auto x : prices)
std::cout << x <<std::endl;
//如果需要修改数组或容器的每个元素,可使用引用类型:
std::vector<int> vi(6);
for(auto & x : vi)
x = std::rand();
1.7.2 新的STL容器
C++11新增了STL容器forward_list(单向链表)、unordered_multimap、unordered_set和unordered_multiset。unordered的四种新增容器都是使用哈希表实现的。
C++11还新增了模板array。
1.7.3 新的STL方法
C++11新增了STL方法cbegin()和cend()。它们也是返回一个迭代器,指向容器的第一个元素和最后一个元素的后面,这些新方法将元素视为const,与此类似,crbegin()和crend()是cbegin()和rend()的const版本。
除了赋值构造函数和常规赋值运算符外,STL容器还有移动构造函数和移动赋值运算符。将在后面介绍。
1.7.4 valarray升级
新增了两个函数begin()和end(),可以将基于范围的STL算法用于valarray。
1.7.5 摒弃export
C++98新增了export,让程序员能够将模板定义放在接口文件和实现文件中。实践证明这不显示,因此C++11将其摒弃,但保留了,供以后使用。
1.7.6 尖括号
为避免运算符>>混淆,C++要求在声明嵌套模板时使用空格将尖括号分开:
std::vector<std::list<int> >v1;//C++98不能使用>>
C++11不再这么要求:
std::vector<std::list<int>>v1;
1.8 右值引用
int x = 10;
int y = 13;
int && r1 = l3;//关联到13
int && r2 = x + y;//r2关联到(x + y),也就是关联到了13.x,y改变不影响r2
double && r3 = std::sqrt(2.0);
&r2表示r2的地址。右值引用的主要目的之一是实现移动语义。下面将介绍。
2. 移动语义和右值引用
下面将介绍本书未讨论的主题。
复制临时对象时,需要在复制给另一个对象后,需要删除临时对象,但是这样做了无用功。移动语义将更高效,它不会移动原始数据,而只是修改了记录。
使用左值对象初始化对象时,将使用复制构造函数;使用右值对象初始化对象时,将使用移动构造函数。
Useless two = one;//复制构造函数
Useless four (one + three);//+运算符需要重载,移动构造函数
适用于构造函数的移动语义也适用于赋值运算符。
Useless & Useless::operator = (const Useless & f);//copy赋值
Useless & Useless::operator=(Useless && f);//移动语义
移动构造函数和移动赋值运算符使用右值,如果要让它们使用左值,可以使用c
++11提供的函数std::move(),该函数在头文件utility中声明。
four = move(one);//强制移动,std::move(one)是右值
如果没有定义移动赋值运算符,同时,也没有定义复制移动运算符,那么这种操作是不允许的。现在STL类中都有了复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符。
3. 新的类功能
3.1 默认的方法和禁用的方法
如果自己创建了构造函数,而导致没有默认构造函数,那么可以使用关键字default显式地声明这些方法的默认版本:
class Someclass
{
public:
Someclass(Someclass &&);
Someclass() = default;
Someclass(const Someclass &) = default;
Someclass & operator = (const Someclass &) = default;
}
另一方面,关键字delete可用于禁止编译器使用特定方法。例如如果要禁止复制对象,可禁用复制构造函数和复制赋值运算符:
class Someclass
{
public:
Someclass() = default;
Someclass(const Someclass &) = delete;
Someclass& operator=(const Someclass &) = delete;
};
前面也介绍过,如果要禁止复制,可将复制构造函数和赋值运算符放在类定义的private部分,但使用delete也能达到这个目的,且更不容易犯错、更容易理解。
default只能用于6个特殊成员函数,但delete可用于任何成员函数。delete的一种可能用法是禁止特定的转换。
class Someclass
{
public:
void redo(double);
void redo(int) = delete;
};
sc.redo(5)将会报错。这种错误为编译错误。
3.2 委托构造函数
C++11允许在一个构造函数的定义中使用另一个构造函数,这种方法被称为委托,因为构造函数暂时将创建对象的工作委托给另一个构造函数。
class Notes{
int k;
double x;
std::string st;
public:
Notes();
Notes(int);
Notes(int, double);
Notes(int, double, std::string);
};
Notes::Notes(int kk, double xx, std::string stt) : k(kk),x(xx), st(stt){
...}
Notes::Notes(Notes(0, 0.01, "oh"){
...})//委托,下面也是
Notes::Notes(int kk):Notes(kk, 0.01,"Ah"){
...}
Notes::Notes(int kk, double xx):Notes(kk, xx, "Uh"){
...}
3.3 继承构造函数
C++11提供了一种让派生类继承基类构造函数的机制。
class C1
{
public:
int fn(int j){
...}
double fn(double w) {
...}
void fn(const char (s) {
...}
};
class C2:public C1
{
public:
using C1::fn;
double fn(double){
...}
};
C2 c2;
int k = c2.fn(3);//C1::fn(int)
double z = c2.fn(2.4);//C2::fn(double)
C++11这种方法用于构造函数,这让派生类继承基类的所有构造函数,但不会使用与派生类构造函数的特征标匹配的构造函数。
class BS
{
public:
BS();
...
};
class DR :public BS
{
public:
using BS::BS;
}
3.4 管理虚方法:override和final
虚方法对实现多态类层次结构很重要,但虚方法也带来了一些陷阱,例如,假设基类声明了一个虚方法,而您决定在派生类中提供不同的版本,这将覆盖旧版本。如果特征标不匹配,这将隐藏而不是覆盖旧版本:
class Action
{
int a;
public:
Action(int i = 0) : a(i){
}
int val()const{
return a;};
virtual void f(char ch )const {
std::cout<<val()<<ch<<"\n";}
};
class Bingo : public Action
{
public:
Bingo(int i = 0) : Action(i){
}
virtual voidf(char* ch)const{
std::cout<<val()<<ch<<"!\n";}
};
由于Bingo定义的是f(char*ch)而不是f(char ch),将对Bingo对象隐藏f(char ch),则下面的代码不能使用:
Bingo b(10);
b.f('@');//f被隐藏,不能使用
在C++11中,可使用虚说明符override支出要覆盖的一个虚函数(加在函数末尾):如果声明与基类方法不匹配,编译器将视为错误。
//下面的方法将报错,因为与基类不匹配
virtual voidf(char* ch)const override{
std::cout<<val()<<ch<<"!\n";}
说明符final解决了另一个问题,可禁止派生类覆盖特定的虚函数。
//下面的代码进制Action的派生类重新定义函数f()
virtual void f(char ch) const final{
std::cout<<val()<<ch<<"ch";}
override和final并非关键字,而是具有特殊含义的标识符。
4. Lambda函数
4.1 比较函数指针、函数符和Lambda函数
举这样一个例子:生成一个随机整数列表,并判断多少个整数能被3整除。
函数指针的实现代码:
#include<vector>
#include<algorithm>
#include<cmath>
#include<iostream>
bool f3(int x) {
return x%3 == 0;}//判断能否被3整除
int main()
{
std::vector<int> numbers(10);
std::generate(numbers.begin(),numbers.end(),std::rand);//生成随机数
int count3 = std::count_if(numbers.begin(),numbers.end(),f3);//计算个数
std::cout<<count3<<std::endl;
}
函数符的实现代码:
#include<vector>
#include<algorithm>
#include<cmath>
#include<iostream>
class f_mod
{
private:
int dv;
public:
f_mod(int d = 1) : dv(d){
}
bool operator()(int x){
return x%dv == 0;}
};
int main()
{
std::vector<int> numbers(10);
std::generate(numbers.begin(),numbers.end(),std::rand);//生成随机数
int count3 = std::count_if(numbers.begin(), numbers.end(),f_mod(3));
std::cout<<count3<<std::endl;
}
在C++11中,对于接受函数指针或函数符的函数,可使用匿名函数定义(lambda)作为其参数。上面f3对应的lambda如下:
[](int x){
return x%3 == 0};//f3:bool f3(int x) {return x % 3 == 0};
对比传统函数,Lambda函数的特点:用[]替代了函数名,没有声明返回类型(如果没有return,则为void)。
所以Lambda的实现形式为:
cout3 = std::count_if(numbers.begin(), numbers.end(),
[](int x) {
return x % 3 == 0});
注意仅当lambad表达式完全由一条返回语句组成时,自动类型推断才管用;否则需要使用新增的返回类型后置语法:
[](double x) ->double {
int y = x;return x - y;}//返回类型为double 后置->double
4.2 为何使用lambda
原因:距离、简洁、效率和功能。
距离:可以在代码附近定义Lambda,而不能在函数内部定义其他函数。
简介:可以使用同一个Lambda两次,而且可给Lambda制定一个名称:
//使用两次:
cout1 = std::count_if(numbers.begin(), numbers.end(),
[](int x) {
return x % 3 == 0});
cout2 = std::count_if(numbers.begin(), numbers.end(),
[](int x) {
return x % 3 == 0});
//指定名称
auto mod3 = [](int x) {
return x % 3 == 0;}
count1 = std::count_if(n1.begin(), n1,end(), mod3);
bool result = mod3(z);// return z%3 == 0
效率:函数指针不能使用内联,因为编译器不会内联其地址被获取的函数,因为函数地址意味着非内联函数。
功能:Lambda有一些额外的功能。Lambda可访问作用域内的任何动态变量。可分为按值捕获和按引用捕获。
int count3 =0;
int count13 = 0;
std::for_each(numbers.begin(), numbers.end(),
[&](int x){
count3 += x %3 == 0;count13 +=x%13 == 0; });
这里[&]可以让您能够在lamb表达式中使用所有的自动变量,包括count3和count13。
5. 包装器
C==提供了多个包装器(wrapper, 也叫适配器[adapter])。C++11提供了其他的包装器,包括bind、mem_fn和reference_wrapper以及包装器function。
其中模板bind可替代bind1st和bind2nd,但更灵活;模板mem_fn可以将成员函数作为常规函数进行传递;模板reference_wrapper能够创建行为像引用但可被复制的对象;包装器function可以以统一的方式处理多种类似函数的形式。
这一节未更新!!!感兴趣可自行查看。
6. C++11新增的其他功能
6.1 并行编程
C++定义了一个支持线程化执行的内存模型,添加了关键字thread_local(静态存储,持续性与特定线程相关),提供了相关的库支持。
库支持由原子操作(atomic operation)库和线程支持库组成,其中原子操作库提供了头文件atomic,而线程支持库提供了头文件thread、mutex、condition_variable和future。
6.2 新增的库
C++11添加了多个专用库。
- 头文件random:支持的可扩展随机库提供了大量比rand()复杂的随机数据工具。例如,您可以选择随机数生成器和分布状态。
- 头文件chrono:提供了处理时间间隔的途径。
- 头文件tuple:支持模板tuple。tuple对象是广义的pair对象。pair对象可存储两个类型不同的值,而tuple对象可存储任意多个类型不同的值。
- 头文件ratio:支持的编译阶段有理数算数库让您能够准确地表达任何有理数,
- 头文件regex:支持正则表达式。