c++11特性(1)

4 篇文章 0 订阅

c++11新特性

类型推导

auto 类型推倒

auto从初始化表达式中推断出变量的数据类型,从这个意义上讲并非一种类型声明,而是一个类型声明时的占位符,编译器在编译时期会将auto替换为变量实际的类型
注意:

  1. auto函数参数,有些编译器无法通过编译
  2. auto非静态成员变量,无法通过编译
  3. auto数组,无法通过编译
  4. auto模板参数(实例化时),无法通过编译
decltype,获取auto声明变量的类型

类似于auto的反函数

追踪返回类型
auto func2(int, int) -> int;

易用性的改进

类内部成员初始化

初始化列表
int a[]{1, 3, 5};
int j{3}; 

struct Person  
{  
  std::string name;  
  int age;  
};
int main()  
{  
    Person p = {"Frank", 25};  
    std::cout << p.name << " : " << p.age << std::endl;  
}  

防止类型收窄

类型收窄是指数据内容发生变化或者精度丢失的隐式类型转换,使用列表初始化可以防止类型收窄

int g{2.0f}	//类型收窄,无法通过编译

基于范围的for循环
int a[5] = {1,2,3,4,5};
for(int &e : a)
{
    cout << e << endl;
}

其中,for循环迭代的范围必须是可确定的

静态断言

assert这是一个宏,用于在运行阶段对断言进行检查,如果条件为真,执行程序否则调用abort()
c++11新增了static_assert可用于在编译阶段对断言进行测试
静态断言的好处:
l 更早的报告错误,我们知道构建是早于运行的,更早的错误报告意味着开发成本的降低
l 减少运行时开销,静态断言是编译期检测的,减少了运行时开销

int main()
{
    //该static_assert用来确保编译仅在32位的平台上进行,不支持64位的平台
    static_assert( sizeof(void *)== 4, "64-bit code generation is not supported."); 
    cout << "Hello World!" << endl;
    return 0;
}

noexcept修饰符
void func3() throw(int, char) //只能够抛出 int 和char类型的异常
{//C++11已经弃用这个声明
     throw 0;
}
void BlockThrow() throw() //代表此函数不能抛出异常,如果抛出,就会异常
{
    throw 1;
}
//代表此函数不能抛出异常,如果抛出,就会异常
//C++11 使用noexcept替代throw()
void BlockThrowPro() noexcept
{
    throw 2;
}

nullptr

为了解决NULL的二义性,NULL实际是0

强枚举类型

枚举类或者强类型枚举,在enum后面加入class或者struct关键字
因为传统c++相同代码区域中两个枚举类型具有相同名字的枚举成员会导致命名冲突

enum Old{yes, no};
//enum status{yes, no};	//编译失败,因为重名
enum class new{yes, no};	//强枚举,编译成功
enum class new2{yes, no};	//yes

常量表达式

使用constexpr,你可以创建一个编译时的函数:

constexpr int GetConst()
{
    return 3;
}

int main()
{
    int arr[ GetConst() ] = {0};
    enum { e1 = GetConst(), e2 };
    constexpr int num = GetConst();
    return 0;
}

constexpr函数的限制:
l 函数中只能有一个return语句(有极少特例)
l 函数必须返回值(不能是void函数)
l 在使用前必须已有定义
l return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式
//一个constexpr函数,只允许包含一行可执行代码
//但允许包含typedef、 using 指令、静态断言等。

用户自定义面量
long double operator"" _mm(long double x) { return x / 1000; }
long double operator"" _m(long double x)  { return x; }
long double operator"" _km(long double x) { return x * 1000; }
 
int main()
{
    cout << 1.0_mm << endl; //0.001
    cout << 1.0_m  << endl; //1
    cout << 1.0_km << endl; //1000
 
    return 0;
}

原生字符串字面值

使用户书写的字符串“所见即所得”

int main()
{
    cout << R"(hello,\n
         world)" << endl;
 
    return 0;
}

类的改进

继承构造

c++11允许派生类继承基类的构造函数,(默认构造函数、复制构造函数、移动构造函数除外)

class A
{
public:
    A(int i) { cout << "i = " << i << endl; }
    A(double d, int i) {}
    A(float f, int i, const char* c) {}
    // ...
};
 
class B : public A
{
public:
    using A::A; // 继承构造函数
    // ...
    virtual void ExtraInterface(){}
};

注意:
l 继承的构造函数只能初始化基类中的成员变量,不能初始化派生类的成员变量
l 如果基类的构造函数被声明为私有,或者派生类是从基类中虚继承,那么不能继承构造函数
l 一旦使用继承构造函数,编译器不会再为派生类生成默认构造函数

委托构造

如果一个类包含多个构造函数,,c++11允许在一个构造函数定义中使用另外一个构造函数,但这必须通过初始化列表进行操作

class Info
{
public:
    Info() : Info(1) { }    // 委托构造函数
    Info(int i) : Info(i, 'a') { } // 既是目标构造函数,也是委托构造函数
    Info(char e): Info(1, e) { }
 
private:
    Info(int i, char e): type(i), name(e) { /* 其它初始化 */ } // 目标构造函数
    int  type;
    char name;
    // ...
};

继承控制

final和override
l final阻止类的进一步派生和虚函数的进一步重写
l override确保在派生类中声明的函数跟基类的虚函数有相同的签名

class B1 final {}; // 此类不能被继承

类默认函数的控制

"=default"函数
C++ 的类有四类特殊成员函数,它们分别是:默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。这些类的特殊成员函数负责创建、初始化、销毁,或者拷贝类的对象。如果程序员没有显式地为一个类定义某个特殊成员函数,而又需要用到该特殊成员函数时,则编译器会隐式的为这个类生成一个默认的特殊成员函数。

class X
{
public:
    x() = default;	//该函数比用户自己定义的默认构造函数获得更高的代码效率
    x(int i)
    {
        a = i;
    }
private:
    int a;
};

X obj;

C++11 标准引入了一个新特性:"=default"函数。程序员只需在函数声明后加上“=default;”,就可将该函数声明为 "=default"函数,编译器将为显式声明的 "=default"函数自动生成函数体。

为了能够让程序员显式的禁用某个函数,C++11 标准引入了一个新特性:“=delete"函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。
”=delete"函数特性还可以用来禁用某些用户自定义的类的 new 操作符,从而避免在自由存储区创建类的对象:

class X
{
public:
    X();
    X(const X&) = delete;	//声明拷贝构造函数为 deleted函数
    X& operator = (const X&) =delete;	//声明拷贝赋值操作符为deleted函数
};
int main()
{
    X obj;
    X obj2 = obj;	//错误,拷贝构造函数被禁用
    
    X obj3;
    obj3 = obj;	//错误,拷贝赋值操作符被禁用
    
    return 0;
}

最经典的应用即独占指针中,禁用了赋值和和拷贝构造,打开了移动拷贝构造函数

模板的改进

右尖括号>改进

在C++98/03的泛型编程中,模板实例化有一个很繁琐的地方,就是连续两个右尖括号(>>)会被编译解释成右移操作符,而不是模板参数表的形式,需要一个空格进行分割,以避免发生编译时的错误。

模板的别名
using uint = unsigned int;
typedef unsigned int UINT;
using sint = int;

函数模板的默认模板参数
//函数模板的默认参数, c++98编译失败,c++11编译通过
template <typename T = int> void DefTempParm(){}

//类模板的默认模板参数必须从右往左定义,数模板的默认模板参数则没这个限定
template<class T1, class T2 = int> class DefClass1;
template<class T1 = int, class T2> class DefClass2;//无法通过编译

可变参数模板
template<class ... T> void func(T ... args)//T叫模板参数包,args叫函数参数包

省略号作用有两个:

  1. 声明一个参数包,这个参数包中可以包含0到任意个模板参数
  2. 在模板定义的右边,可以将参数包展开成一个一个独立的参数

右值引用

左值右值引用

在C语言中,我们常常会提起左值(lvalue)、右值(rvalue)这样的称呼。一个最为典型的判别方法就是,在赋值表达式中,出现在等号左边的就是“左值”,而在等号右边的,则称为“右值”。如:
不过C++中还有一个被广泛认同的说法,那就是可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值。那么这个加法赋值表达式中,&a是允许的操作,但&(b + c)这样的操作则不会通过编译。因此a是一个左值,(b + c)是一个右值。

左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
右值引用使用&&表示

int && r1 = 22;

通常右值引用不能绑定左值的

const 类型 & 为万能的引用类型,他可以接收非常量左值,常亮左值,右值对其进行初始化

移动语义

右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。

转移构造函数
class MyString
{
public:
    MyString(const char *tmp = "abc")
    {//普通构造函数
        len = strlen(tmp);  //长度
        str = new char[len+1]; //堆区申请空间
        strcpy(str, tmp); //拷贝内容
 
        cout << "普通构造函数 str = " << str << endl;
    }
 
    MyString(const MyString &tmp)
    {//拷贝构造函数
        len = tmp.len;
        str = new char[len + 1];
        strcpy(str, tmp.str);
 
        cout << "拷贝构造函数 tmp.str = " << tmp.str << endl;
    }
 
    //移动构造函数
    //参数是非const的右值引用
    MyString(MyString && t)
    {
        str = t.str; //拷贝地址,没有重新申请内存
        len = t.len;
 
        //原来指针置空
        t.str = NULL;
        cout << "移动构造函数" << endl;
    }
 
    MyString &operator= (const MyString &tmp)
    {//赋值运算符重载函数
        if(&tmp == this)
        {
            return *this;
        }
 
        //先释放原来的内存
        len = 0;
        delete []str;
 
        //重新申请内容
        len = tmp.len;
        str = new char[len + 1];
        strcpy(str, tmp.str);
 
         cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;
 
        return *this;
 
    }
 
    ~MyString()
    {//析构函数
        cout << "析构函数: ";
        if(str != NULL)
        {
            cout << "已操作delete, str =  " << str;
            delete []str;
            str = NULL;
            len = 0;
 
        }
        cout << endl;
    }
 
private:
    char *str = NULL;
    int len = 0;
};
 
MyString func() //返回普通对象,不是引用
{
    MyString obj("mike");
 
    return obj;
}
 
int main()
{
    MyString &&tmp = func(); //右值引用接收
 
    return 0;
}

和拷贝构造函数类似,有几点需要注意:

  1. 参数右值的符号必须是右值引用符号,即 &&
  2. 参数右值不可以是常量,因此我们需要修改右值
  3. 参数右值的资源连接和标记必须修改,否则右值的析构函数就会释放资源,转移到新对象的资源也就无效了

有了右值引用和转移语义,我们再设计和实现类时,对于需要动态申请大量资源的类,应该设计转移构造函数和转移赋值函数,以提高应用程序的效率

转移赋值函数
class MyString
{
public:
    MyString(const char *tmp = "abc")
    {//普通构造函数
        len = strlen(tmp);  //长度
        str = new char[len+1]; //堆区申请空间
        strcpy(str, tmp); //拷贝内容
 
        cout << "普通构造函数 str = " << str << endl;
    }
 
    MyString(const MyString &tmp)
    {//拷贝构造函数
        len = tmp.len;
        str = new char[len + 1];
        strcpy(str, tmp.str);
 
        cout << "拷贝构造函数 tmp.str = " << tmp.str << endl;
    }
 
    //移动构造函数
    //参数是非const的右值引用
    MyString(MyString && t)
    {
        str = t.str; //拷贝地址,没有重新申请内存
        len = t.len;
 
        //原来指针置空
        t.str = NULL;
        cout << "移动构造函数" << endl;
    }
 
    MyString &operator= (const MyString &tmp)
    {//赋值运算符重载函数
        if(&tmp == this)
        {
            return *this;
        }
 
        //先释放原来的内存
        len = 0;
        delete []str;
 
        //重新申请内容
        len = tmp.len;
        str = new char[len + 1];
        strcpy(str, tmp.str);
 
         cout << "赋值运算符重载函数 tmp.str = " << tmp.str << endl;
 
        return *this;
 
    }
 
    //移动赋值函数
    //参数为非const的右值引用
    MyString &operator=(MyString &&tmp)
    {
        if(&tmp == this)
        {
            return *this;
        }
 
        //先释放原来的内存
        len = 0;
        delete []str;
 
        //无需重新申请堆区空间
        len = tmp.len;
        str = tmp.str; //地址赋值
        tmp.str = NULL;
 
        cout << "移动赋值函数\n";
 
        return *this;
    }
 
    ~MyString()
    {//析构函数
        cout << "析构函数: ";
        if(str != NULL)
        {
            cout << "已操作delete, str =  " << str;
            delete []str;
            str = NULL;
            len = 0;
 
        }
        cout << endl;
    }
 
private:
    char *str = NULL;
    int len = 0;
};
 
MyString func() //返回普通对象,不是引用
{
    MyString obj("mike");
 
    return obj;
}
 
int main()
{
    MyString tmp("abc"); //实例化一个对象
    tmp = func();
 
    return 0;
}

标准库函数std::move

既然编译器只对右值引用才能调用转移构造函数和转移赋值函数,而所有命名对象都只能是左值引用,如果已知一个命名对象不再被使用而想对他调用转移构造函数和转移赋值函数,也就是吧一个左值引用当做右值引用来使用,该怎么做呢,标准库提供了std::move,这个函数以非常简单的方式将左值引用转换为右值引用

int a;
int &&r1 = a;	//编译失败
int &&r2 = std::move(a);	//编译成功

完美转发

std::forward
完美转发使用于这样的场景:需要将一组参数原封不动的传递给另一个函数

原封不动 不仅仅是参数的值不变,在c++中,除了参数值之外,还有一下两组属性:左值、右值和const、non-const。完美转发就是在参数传递过程中,所有这些属性和参数值都不能改变,同时,而不产生额外的开销,就像转发者不存在一样,在泛型函数中,这样的需求非常普遍

对于一个参数就要重载两次,也就是函数重载的次数和参数的个数是一个正比的关系。这个函数的定义次数对于程序员来说,是非常低效的。
那c++11是如何解决完美转发问题的呢?实际上c++11是通过引入了一条所谓 引用折叠 的新语言规则,并结合新的模板推导规则来完成完美转发

typedef const int T;
typrdef T & TR;
TR &v = 1;	//在c++11中,一旦出现这样的表达式就会发生引用折叠,即 将复杂未知表达式折叠为已知简单的表达式

c++11的引用折叠规则

TR的类型定义声明v的类型v的实际类型
T &TRT &
T &TR &T &
T &TR &&T &
T &&TRT &&
T &&TR &T &
T &&TR &&T &&

一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用

//完美转发


#include <iostream>
using namespace std;

template<typename T> void process_value(T & val)
{
    cout << "T &" << endl;
}

template<typename T> void process_value(const T & val)
{
    cout << "const T &" << endl;
}

//函数forward_value是一个泛型函数,他将一个参数传递给另一个函数 process_value
template<typename T> void forward_value(const T & val)
{
    process_value(val);
}

template<typename T> void forward_value(T & val)
{
    process_value(val);
}

void test()
{
    int a =0;
    const int &b = 1;

    forward_value(a);   //T&
    forward_value(b);   //const T&
    forward_value(2);   //const T&
}


int main()
{
    test();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值