【C++ | 进阶】(侯捷)C++11/14新特性

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;

#definetypedef都不能达到化名模板的效果,两者都只能定义固定的类型,而不能添加参数作为模板参数

应用:在模板参数时使用模板参数

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的使用:用于提前声明成员所在作用域

  1. 对于命名空间的using指令using namespace std;获取命名空间,或者对于命名空间成员的using定义using std::cout;获取命名空间中的成员,之后使用则无须前缀
  2. 对于类成员的using定义,用于获取其他类(一般为基类)的成员,使用时则无须前缀
  3. 对类型或者模板起别名,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

元编程是对类型的编程使用

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值