文章目录
- C++11/14新特性
- 〇、概述
- 一、语言
- Variadic Templates
- Spaces in Template Expressions
- `nullptr` and `std::nullptr_t`
- Automatic Type Deduction with `auto`
- Uniform Initialization
- Initializer Lists
- `Explicit` for ctors taking more than one argument
- range-based for statement
- `=default`,`=delete`
- Alias Template(template typedef)
- Type Alias(similar to `typedef`)
- noexcept
- override
- final
- decltype
- lambda
- Variadic Templates
- 二、标准库
C++11/14新特性
〇、概述
C++Standard演化:C++98(1.0)、C++03(TR1)、C++11(2.0)、C++14(对11一些地方的加强)
C++2.0新特性包括语言和标准库层
Header files
区别:
- C++标准库的header files不带副档名(.h),例如
#incldue<vector>
- 新式C header files不带副名称(.h),例如
#inlcude<cstdlio>
- 旧式C header files带有副名称(.h),例如
#include<stdio.h>
,在新版中仍然可用
早期一些std::tr1
命名空间内的特性,在新版中全部包括在std
命名空间中
一、语言
Variadic Templates
含义:表示数量不定的模板参数,包括0个
语法:...
可以加在类型、对象、变量等的后面
可以用于递归,将函数参数分为第一个和其他参数,然后在函数体中调用自身,参数为剩下的其他参数。实现了函数的递归,每一次执行处理一个参数
例:print模板函数
//当args...为0个参数时使用该重载
void print()
{
}
template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args)
{
cout<<firstArg<<endl;
print(args...);
}
可以使用sizeof...(args)
查看args...
中参数的数量
...
称为包pack,用于模板参数称为模板参数包;用于函数参数类型称为函数参数类型包;用于函数参数称为函数参数包
可以用于重载函数之间的调用
//第一个重载
template<typename T, typename... Types>
inline void hash_val(size_t& seed, const T& val, const Types&... args)
{
hash_combine(seed,val);
hash_val(seed,args);
}
//第二个重载
template<typename... Types>
inline size_t hash_val(const Types&... args)
{
size_t seed = 0;
hash_val(seed,args);
return seed;
}
//第三个重载
template<typename T>
inline void hash_val(size_t& seed, const T& val)
{
hash_combine(seed,val);
}
int main()
{
hash_val(fname, lname, no);
//①因为fname不是size_t类型,所以先调用第二个重载函数
//②seed为size_t类型调用的是第一个重载函数(符合第一个和第二个重载函数,但是第一个是特化,第二个是泛化,所以调用第一个重载)
//③递归调用第一个重载函数,知道args...中只剩下一个参数
//④调用第三个重载函数
}
函数的递归继承
template<typename... Values>class tuple;
template<> class tuple<> {};//包为空时的情况,递归的终止条件
template<typename Head, typename... Tail>
class tuple<Head,Tail...> : private tuple<Tail>//递归继承
{
//类体
};
tuple就是允许你放任意个任意类型东西的对象,详见tuple部分
Spaces in Template Expressions
含义:模板表达式中的空格
对比:
vector<list<int> >
旧版中在两个嵌套的模板表达式中,末尾的两个>
必须要用空格分开,否则会被编译器认为是操作符vector<list<int>>
新版中不需要空格,所有标准库都是如此
nullptr
and std::nullptr_t
nullptr
是一个对象(也常被当做一个关键字),std::nullptr_t
是一个标准库下的类型
nullptr
的类型为std::nullptr_t
,定义的头文件在#include<cstddef>
中typedef decltype(nullptr) nullptr_t
C++11允许使用nullptr
代替0和NULL
void f(int);//参数为int整数
void f(void*);//参数为指针
f(0);//调用f(int)
f(NULL);//如果NULL定义的是0调用f(int),否则产生二义性(其他不为0的定义同理)
f(nullptr);//调用f(void*)
Automatic Type Deduction with auto
含义:可以用此关键字声明一个没有明确指明数据类型的变量,编译器根据变量的值对类型进行自动推导
auto一般用在类型特别长或者特别复杂的情况,尽量不使用auto
Uniform Initialization
含义:一致性的初始化
旧版中对变量的初始化有多种方法,例如小括号法int a(1)
、大括号法int a{1}
、赋值法int a = 1
新版中所有变量可以统一使用大括号进行初始化int a{1}
在变量后加大括号
编译器一看到变量利用大括号进行初始化,就构造一个initializer_list<T>
,并关联一个array<T,n>
,其中的T会自动与数组元素的类型对应。调用函数(一般为构造函数)时,array中的元素会被逐一分解传给函数。但是当函数需要的参数为initializer_list<T>
时,传入参数不会逐一分解,而是直接传入initializer_list<T>
,且这里的initializer_list<T>
需要自己创建,而不是编译器自动打包
Initializer Lists
含义:初始化列表,接受任意个同类型的元素
使用:①利用大括号{}
创建initializer_list<T>
,编译器自动利用大括号内的元素创建initializer_list<T>
,一般用于函数传参;②自定义initializer_list<T>
,自己声明并进行元素赋值
Initializer list底层关联着一个容器array,data层包含一个迭代器array和一个长度len。array指向数组的头部,len表示数组的长度。Initializer list只是关联着一个
Initializer list底层构造函数包含一个私有属性的构造函数,用户无法调用,但是编译器会在需要时自动调用
private:
constexpr initializer_list(const_iterator __a, size_type __l): _M_array(__a), _M_len(__l){ }
Explicit
for ctors taking more than one argument
含义:针对接受一个以上实参的构造函数的关键字explicit
explicit只用在构造函数前,作用是阻止编译器自动调用该构造函数进行隐式转换,只能通过主动调用该构造函数来使用
C++2.0之前的explicit是针对**接受一个实参(可以包含其他带有默认值的参数)**的构造函数的关键字
C++2.0之后可以使用在接受多个实参的构造函数前
class P
{
public:
explicit P(int a, int b, int c)
{
cout<<"ctor more than one agurment"<<endl;
}
};
range-based for statement
含义:基于range的for循环语法,for循环的一种特殊写法
语法:
for (decl : coll)//decl元素 : coll容器
{
statement
}
在底层的实现中,是利用一个迭代器_pos来遍历容器中的每一个元素,将每一个元素赋值给decl,然后执行一遍循环体statement
=default
,=delete
含义:=default
获取默认函数;=delete
禁止获取默认函数
如果自定义了一个ctor,那么编译器不会再提供default ctor
如果强制加上=default
,就可以重新获得default ctor
Big-Three:默认构造函数、拷贝构造函数、拷贝赋值函数、默认析构函数。编译器为会这四种函数加上默认函数,除非已经存在用户的自定义函数。
Big-Five:C++2.0在Big-Three的基础上,加上了移动构造函数和移动赋值函数
如果在Big-Five的函数后面加上=delete
,表示不需要获取默认函数(如果有默认值的话)
对比纯虚函数中的=0
:=delete
可以用于任何函数上,=0
只能用于virtual
函数上
当一个类中有指针成员时,就一定需要自己写Big-Three,不能用默认。主要是因为在拷贝构造和拷贝赋值中的浅拷贝问题
当声明一个空类时,仍然可用调用它的Big-Three(在C++2.0中是Big-Five),一般用于子类调用父类的构造函数
class Empty { };
int main()
{
Empty e1;//call ctor
Empty e2(e1);//call copy
e2=e1;//call assign
//call dtor
}
=delete
可用于模板函数的特化,过滤特定形参类型的重载版本
template<class T>func(T t){ cout<<typeid(t).name()<<endl;//打印变量t的类型}func(int t) = delete;//禁止使用参数类型为int的重载版本
=delete
不能用于dctor(析构函数),否则无法析构对象,报错
禁止拷贝的方法:
- 在拷贝函数后加上
=delete
,从而任何对象都无法调用拷贝函数来进行copy - 将拷贝函数放在private域中限制拷贝,从而只有友元和成员对象才能访问,其他对象无法进行copy
Alias Template(template typedef)
含义:化名模板,给模板名称起一个别名,便于简化使用
使用:使用using关键字实现
//定义template<typename T>using Vec = std::vector<T, MyAlloc<T>>;//使用Vec<int> coll;//等价于std::vector<int,MyAlloc<int>> coll;
#define
和typedef
都不能达到化名模板的效果,两者都只能定义固定的类型,而不能添加参数作为模板参数
应用:在模板参数时使用模板参数
template<typename T, template<class Z> class Container>//在模板参数中typename和class的作用完全相同,都是声明一种类型。但是第二个class表示Container是一个模板类,与前面的class意义class XCls{private: Container<T> c;public: XCls(){}};//使用//XCls<String vector> c1;报错,因为vector需要两个模板参数,虽然第二个模板参数有默认值,但是由于是在模板参数中的模板参数,导致编译器不知道它是有默认值的,无法正常推导template<typename T>using Vec = vector<T, allocator<T>>;//使用化名模板是实现和模板参数接口的统一,使Vec只需要一个模板参数,符合XCls的第二个模板参数格式XCls<String, Vec> c2;//语法正常
Type Alias(similar to typedef
)
含义:类型化名
使用:
using func = void(*)(int,int);//用哪个using定义一种(函数指针)类型//等价于typedef void (*func)(int, int);用typedef定义一个函数指针funcvoid example(int, int){}func fn = example//创建一个func类型的对象,其值为example(函数名就是函数指针)
template<typename T>struct Container{ using value_type = T; //等价于typedef T value_type;}
using的使用:用于提前声明成员所在作用域
- 对于命名空间的using指令
using namespace std;
获取命名空间,或者对于命名空间成员的using定义using std::cout;
获取命名空间中的成员,之后使用则无须前缀 - 对于类成员的using定义,用于获取其他类(一般为基类)的成员,使用时则无须前缀
- 对类型或者模板起别名,
using value_type = T;
、template<typename T> Vec = std::vector<T, alloctor<T>>;
noexcept
含义:指定函数是否抛出异常。当满足括号内的条件时,则函数不抛出异常
void func() noexcept//等价于void func() noexcept(true);//等价于void func() throw();
当满足条件时,func不能抛出异常。如果func抛出了异常(没有被函数内部捕捉并且处理),则noexpect抛出异常,程序终止,通过调用std::terminate()
函数,在该函数内部调用std::abort()
来终止程序
应用:vector的移动构造函数。移动构造函数在vector空间成长的时候被调用,如果移动构造函数没有加noexcept,vector不会调用移动构造函数,因为不能保证移动构造函数不会抛出异常,如果抛出了异常vector是无法处理的
override
含义:覆写、改写
在想要覆写函数时在函数后加上override告诉编译器,便于编译器比较和纠错。编译器会检查基类是否存在被覆写的函数,如果没有则报错
应用:子类覆写父类的虚函数
struct Base{ virtual void vfunc(float) = 0;};struct Derived1 : Base{ //virtual void vfunc(int) { }编译器不会报错,会把该函数当做子类定义的一个新的虚函数 virtual void vfunc(int) override { }//编译器报错,基类不存在被覆写的函数};
final
含义:表示某个类或函数是当前继承体系中最后的继承者,在它之后不存在来其他类或函数来继承或重写它
应用:用于不想被继承的子类或子函数
struct Base1 final { };struct Derived : Base1 { };//报错,Base1不能被继承struct Base2 { virtual void func() final;}struct Derived : Base2{ void func();//报错,func不能被重写}
decltype
含义:相当于typeof作用(C++标准库中没有typrof函数),返回一个表达式的类型
应用:
//1.声明返回值类型template<typename T1, typename T2>//decltype(x+y) add(T1 x, T2 y);在使用decltype(x+y)时编译器还没有读到x和y,所以编译会报错auto add(T1 x, T2 y) -> decltype(x+y);//2.获取一个变量的类型template<typename T>void func(T obj){ //当此处有大量代码时,容易忘记obj的类型 typedef typename decltype(obj)::iterator iType;//这里的typename必须加上,因为当使用嵌套从属类型时,必须加上typename告诉编译器这是一种类型,否则编译器报错}//3.得到lambda表达式的类型auto cmp = [](const Person& p1, const Person& p2){return p1.age() < p2.age(); };//c应该是一个函数对象std::set<Person, decltype(cmp)> coll(cmp);
lambda
含义:lambda表达式,用于定义一些匿名函数或者函数对象
lambda表达式在底层编译器的处理是完全按照函数对象的方式来的,即定义一个lambda表达式,编译器会自动生成一个类,该类中重载了运算符()
,调用时直接在lambda表达式后加上()
即可,表示一个函数对象,值即为函数返回值
格式:[...](...)mutable throwSpec -> retType {...}
其中[]
是导入符号,可以捕获外部变量(全局变量可以直接使用);()
包含函数参数,没有参数时可以不写;mutabale
表示[]
中的内容可以被改写,throwSpec
表示可以抛出异常、retType
表示函数的返回类型,一般可由编译器自己推导,三者都是可选的,如果有任意一个存在,则不可以省略参数列表()
,即使参数为空也不可以省略
auto lam = []{std::cout<<"hello lambda"<<std::endl;}lam();
捕获变量的方式:
- 传值:
[=]
,在lambda内部拷贝一份,不改变外部变量的值
int id = 0;auto f = [id]()mutable { std::cout<<"id:"<<id<<std::endl; ++id;}//lambda表达式等价于class Functor{private: int id;//值传递,复制外面id的值0public: void operator()(){ std::cout<<"id:"<<id<<std::endl; ++id;//只对内部复制的id进行操作,不影响外部变量id的值 }};Functor f;
如果不加mutable,则不能进行++id
,因为不能改变传入的变量的值
[=,&y]
表示除了变量y是引用传递以外,其他变量都是值传递
-
传引用:
[&]
,在lambda表达式内部改变引用传递的变量会导致外部变量的值改变[&, y]
表示除了变量y是值传递以外,其他变量都是引用传递
lambda底层没有默认构造函数和赋值操作(设置为deleted),但有拷贝构造函数。所以lambda只能调用带参数;lambda表达值不能被赋值,但是可以用来拷贝构造新的lambda表达式
Variadic Templates
含义:可变模板
参数类型、参数个数可变的模板。通过参数个数递减的方式实现递归调用
template<typename T, typename... Types>void func(const T& firstArg, const Types&... args){ //sizeof...(args)可以得到args包中的个数 func(args...);}template<typename... Types>void func(const Types&... args) { }//这个函数可以与上面并存,但永远不会被调用,因为上面的模板函数比下面的更特化,优先调用特化的模板
实例:
-
通过递归调用实现打印数量不定、类型不定的参数
void printX() { }//args为空时调用template<typename T, typename... Types>void printX(const T& firstArg, const Types&... args){ cout<<firstArg<<endl; printX(args...);}//调用printX(7.2, "hello", bitset<16>(377), 42);
-
使用可变模板重写printf
void printf(const char *s)//args为空时调用{ whlie(*s) { if(*s == '%' && *(++s) != '%') { throw std::runtime_error("invalid format string: missing arguments"); } std::cout<<*s++; }}template<typename T, typename... Args>void printf(const char *s, T value, Args... args){ while(*s) { if(*s == '%' && *(++s) != '%') { std::cout<<value; printf(++s, args...); return; } std::cout<<*s++;//用于打印空格 } throw std::logic_error("extra arguments provided to printf");}//调用printf("%s %d %f %d", "hello", 10, 1.1, 2);
-
max函数。参数类型一样时可以使用
initializer_list<T>
,如max({1,2,3,4,5});
-
可变参数的max函数
int maximum(int n)//args为空时调用{ return n;}template<typename... Args>int maximum(int n, Args... args){ return max(n, maximum(args...));}//调用maximum(1,2,3,4,5);
-
利用可变参数打印tuple中的所有元素
template<typename... Args>ostream& operator<<(ostream& os, const tuple<Args...>& t){ os<<"["; PRINT_TUPLE<0, sizeof...(Args), Args...>::print(os, t); return os<<"]"};}template<int IDX, int MAX, typename... Args>struct PRINT_TUPLE{ static void print(ostream& os, const tuple<Args...>& t) { os<<get<IDX>(t)<<(IDX+1 == MAX ? "" : ","); PRINT_TUPLE<IDX+1, MAX, Args...>::print(os, t); }}template<int MAX, typename... Args>struct PRINT_TUPLE<MAX, MAX, Args...>{ static void print(std::ostream& os, const tuple<Args...>& t) { }}//调用cout<<make_tuple(7.5, string("hello"), bitset<16>(337));
-
利用可变参数进行递归继承
template<typename... Values>class tuple;template<> class tuple { };template<typename Head, typename... Tail>class tuple<Head, Tail...> : private tuple<Tail...>{ typedef tuple<Tail...> inherited;public: tuple() { } tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail...) { } Head head() {return m_head;} inherited& tail() {return *this;}protected: Head m_head;}//调用tuple<int, float, string> t(10, 2.1, "nice");t.head();//10;t.tail();//tuple<float, string>(2.1, "nice")t.tail().head();//2.1
-
利用可变参数进行递归复合
template<typename... Values> class tup;template<> class tup<> { };template<typename Head, typename... Tail>class tup<Head, Tail...>{ typedef tup<Tail...> composited;protected: composited m_tail; Head m_head;public: tup() { } tup(Head v, Tail... vtail) : m_tail(vtail), m_head(v) { } Head head() {return m_head;} composited& tail() {return m_tail;}}//调用tup<int, float, string> t(10, 1.2, "nice");t.head();//10;t.tail();//tup<float, string>(1.2, "nice")t.tail().head();//1.2
二、标准库
Rvalue references
含义:右值引用。是一种引用类型,在赋值(包括拷贝)一个右值时,可以以类似于浅拷贝的形式move实现,而不是开辟新的内存来拷贝右值(即深拷贝)。简述为,赋予临时对象正式身份——指针
Lvalue:左值,可以放在赋值符号左边的值
Rvalue:右值,只能放在赋值符号右边的值。如对于整数a和b,a+b
不能赋值,只能放在右边,为右值
临时对象(匿名对象)就是右值,但是像string() = "hello"
是属于自定义类型,不是C++的基本数据类型,没有严格遵循右值的概念
右值不能取地址(引用)
当右值被浅拷贝move后,原来的右值就不能使用了,需要将指向右值的临时指针释放,避免右值自动销毁(如临时对象在当前行结束后自动调用析构函数)
左值如果想要实现move就需要调用move函数,move(左值)
返回值即可作为右值使用
想要实现右值的move,需要存在移动构造函数或者移动赋值函数
使用:写一个可被move的class
class MyString{ private: char* _data; size_t _len; void _init_data(const char *s) { _data = new char[_len+1]; memcpy(_data, s, _len); _data[_len] = '' }public: //move constructor MyString(MyString&& str) noexcept : _data(str._data), _len(str._len) { str._len = 0; str._data = NULL;//必要 } //move assignment MyString& operator=(MtString&& str) noexcept { if(this != &str) { if(_data) { delete _data; } _len = str._len; _data = str._data; str._len = 0; str._data = NULL; } return *this; }}
如果容器是以非连续形式如节点存储数据的,那么copy和move的效率差别不大
array
含义:数组容器,内部其实就是数组,添加了容器都有的迭代器供使用
没有构造函数和析构函数,因为本质就是数组
使用:array<类型, 大小> 变量名;
其中大小如果为0,则容器大小设为1
Hashtable
含义:哈希表,本质是一个装满指针的可以成长的vector
每一个指针都是一个篮子bucket,本质是一个链表,所以vector的size就是篮子的数量。每一个元素通过对篮子的数量取余数的方式挂在对应的篮子下
当元素个数大于等于篮子数量时,哈希表以近两倍的方式成长,篮子数量也增加,然后重新计算每个元素应该存入的篮子
元素的hash code需要用一个函数来计算,尽可能的使每个元素的hash code都不一样,避免发生碰撞(即两个不同的元素的hash code相同)
自定义一个万能Hash Function:
class Customer{ //自定义的需要计算Hash Code类public: string fname; string lname; int no;};class CustomerHash{public: std::size_t operator()(const Customer& c) const { return hash_val(c.fname, c.lnanme, c.no); }};//万能的hash函数template<typename T>inline void hash_combine(size_t& seed, const T& val){ seed ^= std::hash<T>()(val) + 0x9e37779b9 + (seed<<6) + (seed>>2);}template<typename T>inline void hash_val(size_t& seed, const T& val){ hash_combine(seed, val);}template<typename T, typename... Types>inline void hash_val(size_t& seed, const T& val, const Types&... args){ hash_combine(seed, val); hash_val(seed, args...);}template<typename... Types>inline size_t hash_val(const Types&... args)//一般调用这个{ size_t seed = 0; hash_val(seed, args...); return seed;}
tuple
含义:可以同时包含若干个不同类型的元素的集合
获取tuple中的元素,使用get<int i>(t);
得到tuple中的第i个元素
使用make_tuple(args...)
将一堆元素打包成元组tuple
可以使用cout直接打印tuple
函数tie(args...)
绑定一个tuple元组t,即将t中的各个元素分别赋值给args
元编程是对类型的编程使用