c++11深入学习

委托构造函数

在引入C++ 11之前,如果某个类有多个重载的构造函数,且这些构造函数中有一些共同的初始化逻辑,通常都需要再编写一个带参数的初始化函数,然后在这些构造函数中调用这个初始化函数。在C++ 11中,再也不用这么麻烦了。我们可以实现一个最基础的构造函数,其他构造函数都调用这个构造函数。示例代码如下:

1 class CPerson

2 {

3 public:

4  CPerson() : CPerson(0, "") { NULL; }

5  CPerson(int nAge) : CPerson(nAge, "") { NULL; }

6  CPerson(int nAge, const string &strName)

7  {

8   stringstream ss;

9   ss << strName << "is " << nAge << "years old.";

10   m_strInfo = ss.str();

11  }

12

13 private:

14  string m_strInfo;

15 };

统一的初始化语法

在引入C++ 11之前,有各种不同的初始化语法。在C++ 11中,仍可以使用这些初始化语法,但也可以选择使用新引入的统一的初始化语法。统一的初始化语法用一对大括号{}表示,使用{}初始化语法还可有效地避免窄转换。示例代码如下:

1  int a{5};

2  char c{‘X‘};

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

4  vector<int> vctTemp{1, 2, 3};

5  CPerson person{10, "Mike"};

6   int b = 5.3;                     // b赋值成5,发生了窄转换

7   int d{5.3};                      // 会提示编译错误,避免了窄转换

语法甜点4:nullptr

nullptr是C++ 11中新加的一个关键字,用于标识空指针。引入nullptr后,可以解决某些函数重载时的二义性问题。示例代码如下:

1 void F(int a)

2 {

3  cout << a << endl;

4 }

5

6 void F(char *p)

7 {

8  assert(p != NULL);

9

10  cout << p << endl;

11 }

12

13 int main(int argc, _TCHAR* argv[])

14 {

15  int *p = nullptr;

16  int *q = NULL;

17  bool bEqual = (p == q);  // 两个指针值是相等的,bEqual为true

18  int a = nullptr;   // 编译失败,nullptr不是转换为int

19

20  F(0);          // 在C++ 98中编译失败,有二义性;在C++ 11中调用F(int)

21  F(nullptr);    // 调用F(char *)

22

23  getchar();

24  return 0;

25 }

成员变量初始化

与Java和C#中的用法一样,可以对成员变量进行就地初始化。示例代码如下:

1 class CPerson

2 {

3 private:

4  int m_nAge = 10;

5  string m_strName = "Mike";

6 };

默认或禁用函数

当我们定义了自己的带参数的构造函数时,编译器将不再生成默认的构造函数,如果此时想使用默认的构造函数,则必须显式地声明并定义不带参数的构造函数。在C++ 11中,我们可以使用default关键字来表明我们希望使用默认的构造函数。类似的,当我们不想外部使用编译器自动生成的构造函数或赋值函数时,我们一般需要将其声明成protected或private的。在C++ 11中,我们可以使用delete关键字来表明我们不希望编译器生成默认的构造函数或赋值函数。示例代码如下:

1 class CPerson

2 {

3 public:

4  CPerson() = default;

5  CPerson(const CPerson &person) = delete;

6 };

static_assert

静态断言static_assert由一个常量表达式和一个字符串构成。在编译期间,将计算常量表达式的值,如果为false,字符串将作为错误信息输出。示例代码如下:

1  char a = 10;

2  static_assert(sizeof(a)==4, "a is not an integer.");

模板右边双括号

在C++ 98中,vector<vector<int>> vctTemp是一个非法的表达式,编译器会认为右边的>>是一个移位操作符,因此必须修改为vector<vector<int> > vctTemp,即在右边的两个>中间添加一个空格。在C++ 11中,这将不再是一个问题,编译器将能够识别出右边的双括号是两个模板参数列表的结

-

继承的构造函数

当一个派生类的某个函数隐藏了基类中的某个同名函数时,如果我们想在派生类中导出基类中的这个同名函数,可以通过using Base::Func的方式将基类中的这个同名函数引入到派生类的作用域内。当该方法只对普通成员函数有效,不能用于构造函数。在C++ 11中,如果派生类认为基类的构造函数已经足够,则也可以使用using Base::Base的方式将基类的构造函数引入到派生类的作用域内。但需要注意的是,此时派生类中的成员变量并没有进行初始化,所以应当对这些成员变量进行就地初始化。示例代码如下:

1 class CBase

2 {

3 };

4

5 class CDerived : public CBase

6 {

7 public:

8  using CBase::CBase;

9  CDerived(int nData) : m_nData(nData) { NULL; }

10

11 private:

12  int m_nData = 10;

13 };

初始化列表

在引入C++ 11之前,只有数组能使用初始化列表。在C++ 11中,vector、list等各种容器以及string都可以使用初始化列表了。初始化列表对应的类为initializer_list,vector、list等各种容器以及string之所以可以使用初始化列表,是因为它们重载了参数类型为initializer_list的构造函数(称为初始化列表构造函数)和赋值函数(称为初始化列表赋值函数)。下面是一些使用初始化列表的例子。

1 void Print(const initializer_list<int> &ilData)

2 {

3  for (auto a : ilData)

4  {

5   cout << a << endl;

6  }

7 }

8

9 int main(int argc, _TCHAR* argv[])

10 {

11  vector<int> vctNum = {1, 2, 3, 4, 5};

12  map<string, string> mapID2Name = {{"92001", "Jack"}, {"92002", "Mike"}};

13  string strText{"hello world"};

14  Print({});

15  Print({1, 2});

16  Print({1, 2, 3, 4, 5});

17

18  getchar();

19  return 0;

20 }

非成员的begin和end

在C++ 03中,标准容器都提供了begin和end成员函数,但对于普通数组,则只能使用不同的写法。比如:

1 vector<int> v;

2 int a[100];

3 sort(v.begin(), v.end());

4 sort(a, a+sizeof(a)/sizeof(a[0]));

为了统一语法,C++ 11提供了非成员的begin和end函数。用法如下:

1 sort(begin(v), end(v));

2 sort(begin(a), end(a));

显式虚函数重载

在引入C++ 11之前,基类和派生类中的虚函数很容易产生错误使用的情况。比如:

a、基类添加了一个虚函数,但该虚函数与派生类中的某个已有普通函数相同。

b、派生类添加了一个普通函数,但该函数与基类中的某个已有虚函数相同。

为了避免这些情况,在C++ 11中可以使用override来显式地表明需要进行虚函数重载。比如:

1 class Base

2 {

3     virtual void some_func(float);

4 };

5

6 class Derived : public Base

7  {

8     virtual void some_func(int) override;        // 将产生编译错误

9    virtual void some_func(float) override;    // 正确

10 };

C++ 11中还引入了final指示符,用于防止类或接口被继承。比如:

1 class  Base1 final { };

2 class Derived1 : public Base1 { };            // 将产生编译错误

3 class Base2

4 {

5     virtual void f() final;

6 };

7 class Derived2 : public Base2

8 {

9     void f();                                             // 将产生编译错误

10 };

C++ 03中,可以使用typedef给模板类指定一个新的类型名称,但却不能给类模板指定别名。比如:

1 template< typename first, typename second, int third>

2 class SomeType;   template< typename second>

3 typedef SomeType<OtherType, second, 5> TypedefName;  // 在C++ 03中是不合法的

无限制的union

在C++ 03中,并非任意的数据类型都能做为union的成员。比方说,带有non-trivial构造函数的类型就不能是 union 的成员。在C++ 11中,移除了所有对union的使用限制,除了其成员仍然不能是引用类型这种情况。

1 struct point

2 {

3      point() {}

4      point(int x, int y): m_x(x), m_y(y) {}

5      int m_x, m_y;

6 };

7 union

8 {

9      int z;

10      double w;

11      point p;                     // 在C++ 03中不合法;在C++ 11中合法

12 };

在C++ 11中,允许sizeof运算符作用在类型的数据成员上,而无须明确的对象。在C++ 03中,这是不允许的,会导致编译错误。比如:

1 struct SomeType { OtherType member; };

2 sizeof(SomeType::member);        // 在C++ 03中不合法;在C++ 11中合法

新的算法

C++ 11中新增了一些比较实用的算法。比如all_of、any_of、none_of、copy_n、copy_if和iota等。参考代码如下:

1 int a[5] = {-2, -1, 0, 1, 2};

2 auto funIsPositive = [](int v){return v>0;};

3 bool bRet = all_of(a, a+5, funIsPositive);             // false

4 bRet = any_of(a, a+5, funIsPositive);                  // true

5 bRet = none_of(a, a+5, funIsPositive);                // false

6 int b[5] = {0};

7 copy_n(a, 5, b);                                                // 将a开始的5个元素拷贝到b中

8 copy_if(a, a+5, b, funIsPositive);                        // 将1, 2两个数拷贝到b中

9 iota(a, a+5, 10);                                               // a中的每个元素加10

泛化的常数表达式

C++ 03中本来就已经具有常数表示式的概念,比如:3+5,6*7等。常数表示式对编译器来说是优化的机会,编译器常在编译期运行它们并且将值存入程序中。同样地,在许多场合下,C++规范要求使用常数表示式。比如数组大小、枚举值等。

然而,常数表示式总是在遇到了函数调用时就终结。比如:

1 int GetFive() { return 5; }

2 int some_value[GetFive() + 5];         // 不合法

C++ 11引进关键字constexpr允许用户保证函数是编译期常数。比如:

1 constexpr int GetFive() { return 5; }

2 int some_value[GetFive() + 5];

C++ 11中引入的一个非常重要也是比较难于理解的新特性就是完美转发(Perfect Forwarding)。完美转发中有两个关键词:“转发”和“完美”。

我们先来看第一个关键词“转发”,那么在C++中,“转发”表示什么含义呢?转发通常用在模板编程中,假设有一个函数F(a1, a2, ..., an),如果存在另一个函数G(a1, a2, ..., an),调用G相当于调用了F,则我们说函数G将a1, a2, ..., an等参数正确地转发给了函数F。再来看第二个关键词“完美”,“完美”转发是相对于其他转发方案而言的。在目前已提出的7种转发方案中,只有完美转发能够正确地实现转发的语义,其他方案都存在这样或那样的问题。下面一一进行介绍。

转发方案一:使用非常量左值引用。考虑下面的代码。

1 void F(int a)

2 {

3  cout << a << endl;

4 }

5

6 template<class A>

7 void G(A &a)

8 {

9  F(a);

10 }

使用非常量左值引用时,我们可以调用F(10),但无法调用G(10),即我们无法接收非常量右值的参数。

转发方案二:使用常量左值引用。考虑下面的代码。

1 void F(int &a)

2 {

3  cout << a << endl;

4 }

5

6 template<class A>

7 void G(const A &a)

8 {

9  F(a);

10 }

使用常量左值引用时,函数G可以接收任意类型的值作为参数,包括非常量左值、常量左值、非常量右值和常量右值。但当F的参数类型为非常量左值引用时,我们无法将一个常量左值引用转发给一个非常量左值引用。

转发方案三:使用非常量左值引用 + 常量左值引用。考虑下面的代码。

1 template<class A>

2 void G(A &a)

3 {

4  F(a);

5 }

6

7 template<class A>

8 void G(const A &a)

9 {

10  F(a);

11 }

综合前面两种方案的分析结果,可以得出这种方案相当于对函数G进行了重载,此时可以接收任意类型的值作为参数,也可以顺利地实现转发。但由于使用了常量和非常量两种形式的重载,当参数的个数N较大时,需要重载的函数会呈指数级增长(2的N次方),因此这种方案实际上是不可取的。

转发方案四:使用常量左值引用 + const_cast。

1 template<class A>

2 void G(const A &a)

3 {

4  F(const_cast<A &>(a));

5 }

这种方案克服了方案二的缺点,现在可以将常量左值引用转发给非常量左值引用了。但这又带来了新的问题,假如F的参数是一个非常量左值引用,则调用G后,我们可以通过F来修改传入的常量左值和常量右值了,而这是非常危险的。

转发方案五:非常量左值引用 + 修改的参数推导规则。

这种方案与方案一类似,但需要修改现有的参数推导规则,即传递一个非常量右值给模板类型时,将它推导成常量右值,这样就解决了方案一中无法接收非常量右值的参数的问题。但由于修改了现有的参数推导规则,因此会导致已有代码的语义发生改变。考虑下面的代码。

1 template<class A>

2 void F(A &a)

3 {

4  cout << "void F(A& a)" << endl;

5 }

6

7 void F(const long &a)

8 {

9  cout << "void F(const long &a)" << endl;

10 }

在未修改参数推导规则前,调用F(10)会选择第二个重载函数,但修改后,却会调用第一个重载函数,这就给C++带来了兼容性的问题。

转发方案六:右值引用。考虑下面的代码。

1 template<class A>

2 void G(A &&a)

3 {

4  F(a);

5 }

在这种方案中,G将无法接收左值,因为不能将一个左值传递给一个右值引用。另外,当传递非常量右值时也会存在问题,因为此时a本身是一个左值,这样当F的参数是一个非常量左值引用时,我们就可以来修改传入的非常量右值了。

转发方案七:右值引用 + 修改的参数推导规则。

要理解修改后的参数推导规则,我们先来看一下引用叠加规则:

1、T& + & = T&

2、T& + && = T&

3、T&& + & = T&

4、T或T&& + && = T&&

修改后的针对右值引用的参数推导规则为:若函数模板的模板参数为A,模板函数的形参为A&&,则可分为两种情况讨论:

1、若实参为T&,则模板参数A应被推导为引用类型T&。(由引用叠加规则第2点T& + && = T&和A&&=T&,可得出A=T&)

2、若实参为T&&,则模板参数A应被推导为非引用类型T。(由引用叠加规则第4点T或T&& + && = T&&和A&&=T&&,可得出A=T或T&&,强制规定A=T)

应用了新的参数推导规则后,考虑下面的代码。

1 template<class A>

2 void G(A &&a)

3 {

4  F(static_cast<A &&>(a));

5 }

当传给G一个左值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为左值引用类型T&,根据推导规则1,模板参数A被推导为T&。这样,在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T& &&>(a),根据引用叠加规则第2点,即为static_cast<T&>(a),这样转发给F的还是一个左值。

当传给G一个右值(类型为T)时,由于模板是一个引用类型,因此它被隐式装换为右值引用类型T&&,根据推导规则2,模板参数A被推导为T。这样,在G内部调用F(static_cast<A &&>(a))时,static_cast<A &&>(a)等同于static_cast<T&&>(a),这样转发给F的还是一个右值(不具名右值引用是右值)。

可见,使用该方案后,左值和右值都能正确地进行转发,并且不会带来其他问题。另外,C++ 11为了方便转发的实现,提供了一个函数模板forward,用于参数的完美转发。使用forward后的代码可简化为:

1 template<class A>

2 void G(A &&a)

3 {

4  F(forward<A>(a));

5 }

为了便于进行各种转发方案的比较,下面以表格的形式列出了各自的特性。

转发方案非常量左值常量左值非常量右值常量右值修改语言已知问题

一、非常量左值引用非常量左值常量左值无法转发常量左值否无法接收非常量右值的参数

二、常量左值引用常量左值常量左值常量左值常量左值否无法将常量左值引用转发给非常量左值引用

三、非常量左值引用 + 常量左值引用非常量左值常量左值常量左值常量左值否重载函数过多,实际编码不可行

四、常量左值引用 + const_cast非常量左值非常量左值非常量左值非常量左值否可修改常量左值和常量右值,不安全

五、非常量左值引用 + 修改的参数推导规则非常量左值常量左值常量左值常量左值是会导致兼容性问题,且不支持移动语义

六、右值引用无法转发无法转发非常量左值常量左值是可修改非常量右值,不安全

七、右值引用 + 修改的参数推导规则非常量左值常量左值非常量右值常量右值是暂无,故简称为完美转发

wode 下载 :

时间: 2024-10-08 10:00:31

c++11深入学习的相关文章

20145239 《信息安全系统设计基础》第11周学习总结

20145239 <信息安全系统设计基础>第11周学习总结 教材学习内容总结 8.1 异常 从处理器运行开始到结束,程序计数器假设一个序列的值a0a1......an-1,这个控制转义序列叫做处理器的控制流. 异常,就是控制流中的突变,用来响应处理器状态中的某些变化. 状态的变化称为事件,在任何情况下,当处理器检测到有事件发生时,会通过一张叫做异常表的跳转表,进行一个间接过程调用到专门处理程序--异常处理程序.当异常处理程序完成之后,根据引起引起异常的事件类型,会发生以下三种情况之一: 处理程

2016/11/23 学习总结

今天学了JDK中的常用类 Date和Calendar以前就用过,但今天再看还是跟第一次用一样.果然这些工具就要常用才行呢-- 老师让用MarkdownPad来作笔记,支持html,很溜啊,我是不是应该再过一遍HTML啊,全忘了... 摘抄了一些笔记-- 11/23学习笔记: JDK常用类 1. System类 7. DateFormate抽象类与SimpleDateFormate类 2. Runtime类 8. Calendar抽象类与GregorianCalenlar类 3. String类

2015.11.06 学习Ubuntu下常用命令

2015.11.06 学习Ubuntu下常用命令 1.关闭防火墙:ufw disable 2.开启防火墙:ufw enable 3.防火墙状态:ufw status 4.查看占用的端口:#lsof -i 5.查看某一个端口:#lsof -i:8080  或者是: #netstat -apn|grep 8080————接着:#ps -aux|grep 进程号 6.结束占用端口的进程:#killall 进程名 7.自己写一遍,记得牢!

201521123045 &lt;java程序设计&gt;第11周学习总结

201521123045 <java程序设计>第11周学习总结 1. 本周学习总结 2. 书面作业 2. 书面作业 Q1.1.互斥访问与同步访问完成题集4-4(互斥访问)与4-5(同步访问) import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; class Account{ private int balance; private Lock lock = new

20145326蔡馨熠《信息安全系统设计基础》第11周学习总结

20145326蔡馨熠<信息安全系统设计基础>第11周学习总结 教材内容总结 异常控制流(ECF)发生在计算机系统的各个层次,是计算机系统中提供并发的基本机制.在硬件层,异常是由处理器中的事件触发的控制流中的突变.控制流传递给一个软件处理程序,该处理程序进行一些处理,然后返回控制给被中断的控制流. 有四种不同类型的异常:中断.故障.终止和陷阱.当一个外部旧设备,例如定时器芯片或者一个磁盘控制器,设置了处理器芯片上的中断引脚时(对于任意指令)中断会异步地发生控制返回到故障指令后面的那条指令. 一

20145317《信息安全系统设计基础》第11周学习总结1

20145317<信息安全系统设计基础>第11周学习总结1 8.2进程 一个独立的逻辑控制流:他提供一个假象,好像我们的额程序独占的使用处理器.一个私有的地址空间:他提供一个假象,好像我们独占的使用存储器系统.多个流一起执行被称为并发.一个进程和其他进程轮流进行的概念被称为多任务.一个进程执行执行他的控制流的一部分的每一段时间叫做时间片. 并发流:一个逻辑流的执行在时间上与另一个流重叠.并发:多个流并发地执行的一般现象.多任务:一个进程和其他进程轮流运行的概念.时间片:一个进程执行它的控制流的

2015.11.04 学习 Ubuntu下安装gocode

2015.11.04 学习 Ubuntu下安装gocode 1.go get github.com/nsf/gocode,如果不成功 2.网页http://github.com/nsf/gocode,下载包,然后解压,解压后的文件名为gocode-master,修改为gocode. 3.复制gocode文件夹到go的安装目录~/go/github.com/nsf/gocode,(没有的话自行创建) 4.然后命令行执行go install github.com/nsf/gocode.确保安装目录的

2015.11.02 学习 Ubuntu下安装gosublime插件及其配置

2015.11.02 学习 Ubuntu下安装gosublime插件及其配置 1.Ctrl+shift+P 打开package control 输入pcip 回车 2.出现的框中输入gosublime,回车 3.preferences---package setting--gosublime--setting default 打开后在env{}中添加GOROOT和GOPATH 4.保存后全部复制到preferences---package setting--gosublime--setting

《信息安全系统设计基础》第11周学习总结

20145336张子扬 <信息安全系统设计基础>第11周学习总结 学习目标 了解异常及其种类 理解进程和并发的概念 掌握进程创建和控制的系统调用及函数使用:fork,exec,wait,waitpid,exit,getpid,getppid,sleep,pause,setenv,unsetenv 理解数组指针.指针数组.函数指针.指针函数的区别 理解信号机制:kill,alarm,signal,sigaction 掌握管道和I/O重定向:pipe, dup, dup2 教材学习内容总结 异常控

20145306《信息安全系统设计基础》第11周学习总结

20145306<信息安全系统设计基础>第11周学习总结 教材学习内容总结 异常:是异常控制流的的一种形式,一部分由硬件实现,一部分由操作系统实现,异常控制流就是控制流的突变,用于想以你个处理器状态中的某些变化. 异常的种类:被零除,缺页,存储器访问违例,断点,算术溢出:系统调用,来着外部I/O设备的信号. 进程是一个正在执行的进程实例,系统中的每个程序都是运行在某个进程的上下文中的.上下文是由程序正确运行所需的状态组成的. 进程创建和控制的系统调用及函数使用 创建进程: 父进程通过调用for