C++复健4(未整理)
C++11特性
很多以前存在的组件已经放入了STL
比如regex,tuple等
需要注意的比较重要的特性
模板多函数参数
1 |
|
这三个点实际上就是一个关键词
1 | ... |
表示一个pack,在不同的位置有不同的意思和用法
1 | template<typename T, typename... Types> |
很方便的实现递归的操作比如tuple的实现,实现同名不同类型的成员等
接下来第二个例子
了解了新特性后打算重写printf(c的输出函数)
1 | void myprintf(const char* s) { |
第三例子:实现一个Max函数
1 | //如果只是单纯实现,参数类型相同时 |
tuple 元组
一般用于,返回值有多个类型
第四个例子
tuple的使用
make_tuple的使用
递归,分节递归,递归的创建
第五个例子
递归的继承
同样是从tuple源代码来看,简易版
1 | template<typename... Balues>class tuple; |
第六例子
用于复合,tuple
Spaces in Template Expressions
模板嵌套空格省略
nullptr and std::nullptr_t
使用nullptr 代替 函数参数中NULL/0,作用就是更好的区分开空指针与数值(int)0 的区别
定义了一个新的指针类型nullptr_t
Automatic Type Deduction with auto
auto 推导类型,是一种语法糖
一般在定义迭代器的时候,类型将会写很长,很复杂,这里我我们就可以使用auto 来自动推导类型
Uniform Initialization
统一版本的对象初始化
使用{}态构造初始化
{}构建出std::initializer_list
在这个容器中逐一调用构造函数或者原本就可以多个进行初始化
initializer_lists
编译器可以调用改对象的私有构造
需要注意的是
1 | int a = 10 / 3 ; // 3 |
explicit
关键字
在C++11之前
explicit for ctors taking one argument
只会调用一次,主要调用在构造函数上并且构造函数中只有一个参数
C++11后
explicit for ctors taking more than one argument
也只会调用一次,但是构造函数可以不只一个参数
range-based for statement
for循环的一种简单写法
1 | for( decl : coll ) { |
=delete , = default
通常写了一个构造函数之后,编译器就不回给你默认的构造函数,在构造函数之后添加=default 后就可以获得并使用default ctor
=delete 就顾名思义,删除为零
一个类中如果包含指针,基本必须都需要自己主动定义Big-Three (构造拷贝构造析构三大件)
No-Copy and Private-Copy
Private- Copy or No-Copy 的典型应用就是单例模式single
Alias Template
化名模板(别名)
比c的宏有更简单的用法(当然也是有代价的)
关键字:using
1 | template <typename T> |
模板的特化问题,尚未解决,不能对别名做特化,只能原原本本的模板本身进行特化操作
模板模板参数
template template parameter
模板的高深的使用方式。
但是注意的是只能模板模板参数,不能模板模板参数的模板参数*2,编译器只能推导一次
编译器第一次开始编译推导确实是能通过的,但是,需要注意的是,并不知道的模板模板参数的绑定究竟是什么类型,实际在运行的时候如果还是需要编译器进行推导还是会出现问题。(编译器并没有想象的这么聪明
所以常常Alias template 和模板模板参数进行混合使用,从而达到模板模板参数的模板参数的效果
现在我们模拟一个实现场景
已知一个数据组,我需要将其进行测试,满足以下调用效果,作用是检测是否该数据组是否能够放入不同容器并且进行数据搬移move。
1 | Test(vector, mystring); |
起初写了一个测试函数
1 | template<typename Container, typename T> |
然而并不行,在实际调用时编译出现错误,错误非常明显,Container并不是一个模板
这时,就可以想到了,模板模板参数(不过呢,为了实现过程在更曲折一点,假装没想到模板模板参数。
想到了一个下位替代,使用函数模板和迭代器和萃取器(traits)
我提前就将数据放好在容器中作为参数,再进行测试
1 | template<typename Container> |
然而测试函数的调用方式发生了改变,尽管作用一致
1 | Test(vector<mystring>()); |
感觉还是没有一开始设想的调用方式好,假如不想使用迭代器和trait怎么办呢,假如所使用的容器是自己设计的并没有遵循标准库的设计,是不是就无法使用这个测试函数了。
这个时候,终于想起来要使用模板模板参数了(迫真
1 | template<typename T,template <typename U> class Container> |
这样的类测试编译通过了,再进行调用时,却出现了错误
1 | XCls<mystring, vector> c1; |
错误原因是:第二个实参的类型不符合,深排原因,标准库中vector的实现有两个模板参数(需要指定适配器),然而,模板模板参数中需要特别注意的点来了,即使模板中的模板参数中第二个参数有默认值时,编译器并不能进行推导。
这个时候可以使用别名参数进行指定参数到底是什么,来帮助编译器进行推导
1 | template <typename T> |
需要注意的事,该化名并不能在类中或者function body中声明。
最终函数的调用方式将会是
1 | XClS<string, Vec> mystring; |
type alias
类型别名
1 | using func = void(*)(int ,int); |
实际上我们在使用标准库的容器时,使用的string 实际就是using string = std:: basic_string \
noexcept 不异常
宣告这个函数不会出现异常
1 | void func () noexcept { |
异常是个大学问,此处不谈
在使用成长性容器时(vector 或deque(实际上内部是靠着vector实现)),会发生 内存越界 memeory reallocation
在11特性中,noexcept的作用是声明 vector的调用不会抛出异常,而是有一个自己设定的处理方法(move)
总结就是,在设计一个类时,抛异常是非常致命的,设计到进行搬移的时候
override 重写
一般应用在虚函数之上,告诉编译器该函数重写:避免和重载和声明定义新的函数发生混乱
final 最终
两种比较常见用法,不过效果都是表示不可重写,与override对立
1 | struct Basel final{}; |
decltype
使用方式类似 sizeof() and typeof();
类型的表达式的类型,一般用来定义一个type
1 | //得到container_obj的类型 |
有三种应用
第一种:用于声明返回类型
1 | template<typename T1, typename T2> |
需要注意的是这是一种新的返回类型的指定方式,与lanbdas的表达形式相似
第二种:in metaprogranmming
1 | typedef typename decltype(container_obj):: iterator iType; |
在lambda没有使用之前,设定一个仿函数需要定义一个类,按照传统的方式来定义
使用仿函数时作谓词时
1 | std::set<string, functor> myset; |
使用lambda时使用方式则变更为
这就是第三种
传递lambda的类型
1 | auto cmp = [](const Person& p1, const Person& p2){ |
原因是因为我们手上往往只有objec 而没有type,所以需要借助decltpye来取出类型(类),在set的众多的构造函数中,有着一个可以使用lambda的接口,需要将lambda传给构造函数,否则调用的依旧是默认的规则(构造函数),然而lambda并没有构造函数,所以将会编译失败。
lambdas
定义的一个内联函数
definition of inline functionality,similar to like functions
lambda introducer
这个中括号有说法,除了平常常用对外部的变量的值和引用以外还有更多深入的用法
1 | //表示外部传的值可以发生变化,(可读不可写) mutable |
由于lambdas默认是内联函数 效率实际上还是很高的
更多的使用方式需要单独开一章文更加深入了解
右值引用与左值引用
左值其实就是一个变量,这是咱的基本的变量的定义
1 | int a = 1; |
而右值,是不可以出现在左边的,在C++11以前,是没有右值引用的概念的
1 | int foo(){retuen 6;} |
在C++ 11以后,出现了新的语法,来声明,告诉编译器,这是一个右值引用,加入noexpect修饰,保证不会报错
一般,在拷贝时,深拷贝与浅拷贝,实际上都是左值copy,而遇到成长性的容器时,在保证效率需要使用到右值拷贝来进行效率上的优化避免出现不必要的拷贝,只需要把指针拷贝就好,于是就出现了 steal 的构造函数 move ctor,但是这个事情是相当危险的,毕竟是两个指针指向同一个内存块,所以move 函数的搬移 就是将左值转换为右值, 当拷贝完成之后,就需要打断指针指向,所以,被move的值,就不能再进行使用,从而保证安全。
注意,以上的move都是 std::move
1 | Mystring(Mystring && str)noexcept:initalization list{ |
设计一个MyString和一个无move的功能的MyString来实现对照
设计多个构造函数,并对每次调用进行计数,测试搬移构造与拷贝构造的效率
在测试vector和deque等成长性容器可以发现,有move与无move的copy效率差别是巨大的,而对于list与红黑树等影响不是非常大
1 | //传统的左值传入,进行的拷贝 |
再次声明:当拷贝完成之后,就需要打断指针指向,所以,被move的值,就不能再进行使用,从而保证安全。