[GeekBand] 探讨C++新标准之新语法——C++ 11~14
一、 可变参数模板(Variadic Templates)
在C++11中,出现了参数数目可变的模板,这部分在之前C++高级编程的时候就有学习到。
其实,在C中就有类似的设定。最常用的printf(),就是一个采用了一个…类型的可变参数。…类型的参数代表是一个参数组。
- int sumi(int c, ...)
{
va_list ap; - va_start(ap, c);
- int i;
- int sum = c;
- c = va_arg(ap, int);
- while (0 != c)
{ - sum = sum + c;
- c = va_arg(ap, int);
- }
- return sum;
}在C中的…参数可以用va_list,va_start,va_arg等宏定义进行操作,其中va_arg()代表取值并进行指针的偏移;这种方式要求参数的类型必须能够得知。对于上例,即给定了参数就是int型;对于printf,则是通过一个字符串参数确定了有多少个变量。
那么在C++中的…称为Pack(包);正如之前在高级编程时介绍的一样,通常采用递归的方式进行参数调用。
- void print() {}
- template < typename T, typename… Types > void print(const T & firstArg,
- const Types & …args) {
- cout << firstArg << endll;
- print(args…);
- }
在这里和之前版本的Package不同,非常神奇的一点是,我们不再需要使用偏移量。也就是说,我们在使用的时候根本不需要知道Pack里边的参数类型是什么。
在C++11中提出了一个新的数据类型tuple(元祖),就是利用了新的…实现的:
- template < typename Head, typename...Tail > class tuple < Head, Tail... > : private tuple < Tail... > {
- typedef tuple < Tail... > inherited;
- public:
- tuple() {}
- tuple(Head, Tail...vtail): m_head(v), inherited(vtail...) {} //看第二行,inherited(vtail…)实际上是初始化父类对象
- typename Head head() {
- return m_head;
- }
- inherited & tail() {
- return *this;
- } //这个地方充分利用了递归继承的关系,只要指向自己的指针转型为指向自己父类的指针,那么就可以得到尾部(因为尾部就是当前对象的父类对象)
- protected:
- Head m_head;
- };
这个用法又非常的巧妙,Tuple继承自比自己少了Head参数的Tuple;称之为递归继承关系,如下图中的类图:

提出几个原则,适用于…的使用:
1. <T a,Types… b>比<Types… a>更特化;同时存在时,<Types… a>永远不会被调用;
2. 注意递归的使用方法,例如求一组数据的最大值,就可以采用
- int maximum(int n, types… args) {
- return max(x, maximum(args…))
- };
3.可以通过给模板参数加变量成员(即<int X,typename Y>这种类型)如int来进行计数,可以知道处理到了什么位置。
4. 可以使用递归继承和递归组合的方式。
二、 两个新的关键字nullptr & auto
1. nullptr
nullptr对象是一个空的指针,类型是nullptr_t;在C++11之前,一直是使用0(NULL就是0的一种宏定义)来代表空指针。在C++11中引入了nullptr,这一方面提高了代码可读性,另一方面使fun(int)和fun(void*)这种重载成为可能;
2. auto
自动推导返回值类型,编译器本身是有类型检查的功能的。C++11的auto就是在类型检查的时候才决定到底是什么类型,而不是像之前的编译器,检查左右是否一致。
建议是指使用在类型特别长或者特别难写的情况,否则会降低可读性。
还有一种情况是lambda表达式经常使用auto关键字
- auto I = [](int x)-> bool { /*…*/ }
三、 初始化列表
1.通用初始化方法
在之前,初始化的时候如果调用构造函数,则要使用小括号;如果是创建对象数组,则要使用大括号。而在C++11中,我们可以使用大括号进行所有的初始化操作,包括诸如int values[] {1,2,3}及complex<double> c{4,3} [等价于c(4,3)].其内部时进行了一个Initializer List的转化,关于initializer list的信息见下一节。
必须提出的是,作为数组进行初始化时,大括号中的参数是一个一个传给变量进行初始化的,并不能提供多个的初始化。
2.std::initializer_list<>
- #include < iostream >
- #include < stdio.h >
- #include < algorithm >
- #include < functional >
- using namespace std;
- class Print: public binary_function <
- const char * , int, void > {
- public: void operator()(const char * p, int a) const {
- cout << a << ' ';
- }
- };
- class P {
- public: P(int a, int b) {
- cout << "P(int,int),a=" << a << ",b=" << b << endl;
- }
- P(initializer_list < int > initlist) {
- cout << "P(initializer_list<int>),values= ";
- for_each(initlist.begin(), initlist.end(), bind1st(Print(), "%d "));
- cout << endl;
- }
- };
- int main() {
- P p {
- 77, 5
- };
- P q(77, 5);
- }
创建p时会适配到初始化列表为参数;而创建q时则会适配到以两个int为参数。注意即使没有initializer_list版本的构造器,编译一样可以通过,因为发生了initializer_list的自动类型转换。
3.initializer_list源码分析
template<class _E>
// The compiler can call a private constructor.
initializer_list(const _E* __a, size_t __l)
: __array(__a), __len(__l) { }
Initializer内部使用array数据结构的迭代器(指针),但他并不内含一个array,可以视作是一个浅拷贝。在C++11的标准库中,所有的容器都添加了使用initializer_list的版本。
诸如max()之类的函数也添加了接受Initializer_list的版本。以前,max只能进行两个值的比较;而现在,可以采用类似于max({1,2,3,4,5})之类的表达方式进行任意参数的比较。
4. 由于初始化列表带来的explicit关键字用法的变化
在C++11之前,explicit关键字只能用于构造函数具有一个实参,有这样的情况:
- class Complex {
- int real, imag;
- explicit Complex(int re, int im = 0): real(re), imag(im) {}
- Complex operator + (const Complex & x) {
- return Complex((real + x.real), (imag + x.imag));
- }
- };
- int main() {
- Complex a(0, 1);
- Complex b = a + 1; //error,explicit
- }
四、 新的for语法
- for (decl: coll) {
- Statement;
- }
类似于python的for语法,decl指向coll容器的每一个元素,直到容器尾。如果你想对容器中的元素进行修改,可以采用传引用的方法,如下例:
- for (auto & elem: vec) {
- elem += 3;
- }
也可以是向量组中的每一个元素都能作为单参构造的参数,其实就是通过构造函数实现的自动类型转换。
五、保留默认函数的方法
一般情况下,当我们创建了构造函数,拷贝构造函数,赋值操作的情况时候,默认的函数就会自动消失。现在,C++11允许我们使用=default和=delete两个关键字声明来保留或删除原有的,如下例:
- Zoo(const Zoo & ) = delete; //删除已经存在的版本,可能是编译器给的或自己写的;
- Zoo(const Zoo & ) = default; //采用默认定义,对于构造函数,就是保留无参构造。
右值引用(C++11引入)也可以使用这两个关键字,右值引用的知识在之后介绍。
在使用这两个关键字时,其能否通过编译,关键就是看是否有二义性,或是否有先定义了再=delete的情形(在这种情形下,你先已经定义了一个函数,然后又说要删除这个函数,编译器会不知所措)
利用=delete,可以实现不允许拷贝构造的类。在Singleton设计模式,原来是采用了一个私有的拷贝构造函数,现在我们有了新的方法。
六、别名(Alias)(using新用法)
1. 模板别名
在C++11之后,using有了新的用法:
- template < typename T >
- using Vec = std::vector < T, MyAlloc < T >> ;
用宏是达不到这样的效果的 。
在之前提及过的模板模板参数中,也涉及到了using的用法。使用模板模板参数的方法编译器无法调用向量参数的默认值,而每一个容器都有一个默认的第二参数,即内存分配器;具体写法在笔者《C++高级编程》笔记中有涉及到,不再重复讲述。
2. 类型别名(与typedef相似)
以下两条语句等价:
- typedef void( * p)(int, int);
- using func = void( * )(int, int);
第二种方式(新的)提高了可读性。
七、无异常声明noexcept
- void foo() noexcept;
表示foo不再抛出异常。我们知道,C++的异常处理是逐级上报的形式,那么这种情况下也就是说,如果foo()内部发生了异常,异常只在foo内部处理,如果foo处理不了,就直接中断程序(否则则会一直抛到main才退出程序)。这提高了异常处理的效率。
需要注意的是,右值引用的移动语义构造和赋值必须有noexpect声明。
八、关于继承的两个关键字
1.override
- Struct Base {
- virtual void vfunc(float) {}
- };
- Struct Derived1: base {
- virtual void vfunc(int) {}
- };
有时候难免会发生写错的情况,如上例,我们本想复写这一虚函数,不过不小心写错了。
在C++11中添加了一个安全检查关键字 override:
- Struct Derived1: base {
- virtual void vfunc(int) override {}
- };
这样如果你写下来override,则相当于告诉编译器自己要复写,编译器发现没有match会报错。
2.final
用于虚函数中,高速编译器,这个类或是这个虚函数不再允许派生或复写。例如:
struct Base final{…};
[GeekBand] 探讨C++新标准之新语法——C++ 11~14的更多相关文章
- 《新标准C++程序设计》1.1-1.6(C++学习笔记1)
1.cout输出 cout<<待输出项<<待输出项2<<···; 2.cin输入 cin>>变量1>>变量2>>···; 3.C ...
- C++11新标准学习
<深入理解C++11:C++11新特性解析与应用> <华章科技:深入理解C++11:C++11新特性解析与应用>一共8章:第1章从设计思维和应用范畴两个维度对C++11新标准中 ...
- .NET 新标准介绍
本文介绍如何使用 .NET 标准,更容易地实现向 .NET Core 迁移.文中会讨论计划包含的 APIs,跨构架兼容性如何工作以及这对 .NET Core 意味着什么. 如果你对细节感兴趣,这篇文章 ...
- C99标准的新特性
C语言标准的发展 C语言的发展历史大致上分为4个阶段:Old Style C.C89.C99和C11. C89是最早的C语言规范,于1989年提出,1990年先由ANSI(美国国家标准委员会,Amer ...
- 让Delphi的TRichEdit支持新标准
先说明, 不是直接让TRichedit支持, 而是派生出一个类支持 原理就是, IDE自带的richedit使用的是2.0版本(RICHEDIT20A/RICHEDIT20W), 这个版本虽然支持图片 ...
- 正确处理类的复合关系------新标准c++程序设计
假设要编写一个小区养狗管理程序,该程序需要一个“主人”类,还需要一个“狗”类.狗是有主人的,主人也有狗.假定狗只有一个主人,但一个主人可以有最多10条狗.该如何处理“主人”类和“狗”类的关系呢?下面是 ...
- 在成员函数中调用虚函数(关于多态的注意事项)------新标准c++程序设计
类的成员函数之间可以互相调用.在成员函数(静态成员函数.构造函数和析构函数除外)中调用其他虚成员函数的语句是多态的.例如: #include<iostream> using namespa ...
- 多态实现的原理------新标准c++程序设计
“多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定.例子: #include<iostream> using namespac ...
- 多态的作用-游戏编程展示------新标准c++程序设计
游戏软件的开发最能体现面向对象设计方法的优势.游戏中的人物.道具.建筑物.场景等都是很直观的对象,游戏运行的过程就是这些对象相互作用的过程.每个对象都有自己的属性和方法,不同对象也可能有共同的属性和方 ...
随机推荐
- 在 Fedora 里安装自带的 MATE和 cinnamon
参见 http://wiki.mate-desktop.org/download#fedora安装方法: yum groupinstall mate-desktop yum groupinstall ...
- Keeplived 配制图解
http://blog.csdn.net/tantexian/article/details/50056229
- 熟练掌握HDFS的Shell访问
HDFS设计的主要目的是对海量数据进行存储,也就是说在其上能够存储很大量文件 (可以存储TB级的文件).HDFS将这些文件分割之后,存储在不同的DataNode上, HDFS 提供了两种访问接口:Sh ...
- C语言第一节 C语言程序与开发工具
开发工具的选择 可以用来写代码的工具:记事本.UltraEdit.Vim.Xcode等 选择Xcode的原因:苹果官方提供的开发利器.简化开发过程.有高亮显示功能 使用Xcode新建一个C程序的源代码 ...
- 最小K个数之和
描述 输入n个整数,输出其中最小的K个数之和.例如输入4,5,1,1,6,2,7,3,3这9个数字,当k=4,则输出最小的4个数之和为7(1,1,2,3). 输入 测试样例组数不超过10 每个测试案例 ...
- Objective-C基础笔记一
这里开始了我OC旅程 花了8天的时间粗略的学习了新知识Objective-C(简称OC),虽然只是学习了其中的基础部分,但经过这一周的学习也算是入门了.对面向对象的封装.继承.多态以及其中所包含的方法 ...
- Uva120 Stacks of Flapjacks 翻煎饼
水水题.给出煎饼数列, 一次只能让第一个到第i个数列全部反转,要求把数列排序为升序. 算法点破后不值几钱... 只要想办法把最大的煎饼放到最后一个,然后就变成前面那些煎饼的数列的子题目了.递归或循环即 ...
- c编写程序完成m名旅客和n辆汽车的同步程序代写
jurassic公园有一个恐龙博物馆和一个公园,有m名旅客和n辆汽车,每辆汽车仅能允许承载一名旅客.旅客在博物馆参观一阵,然后排队乘坐旅行车.当一辆车可用时,他载入一名旅客,再绕花园行驶任意长的时间. ...
- AngularJs学习经验汇集
>>关于ng-include 有时候你会发现你用这个指令想要加载某个模板总是加载不出来,url明明是对的,页面还是一片空白,这里有一个细节要注意以下: <div ng-include ...
- Samba服务详解
Samba文件服务器 本章结构 服务简介 SMB协议 Server Message Block,服务消息块 CIFS协议 Common Internet File System,通用互联网文件系统 S ...