C++11新特性

本文详细介绍了C++11引入的一些重要特性,包括auto类型推导、decltype获取类型、变量初始化、增强for循环、静态断言、noexcept异常声明、nullptr的使用、强类型枚举、常量表达式、自定义字面量、原生字符串字面值、继承构造函数、委托构造函数、final和override的使用、default和delete的修饰作用、模板实例化、using别名、左值引用和右值引用、返回值优化技术、移动构造函数、转移赋值函数、标准库函数std::move、完美转发std::forward以及智能指针(unique_ptr、shared_ptr、weak_ptr)的使用。此外,还讲解了闭包和lambda表达式的概念和用法。
摘要由CSDN通过智能技术生成

一 auto

用法

C++11之前:表示自动在栈区分配内存,可隐藏

// 二者等价
int a = 1;
auto int a = 1;

C++11之后:表示自动推导类型

auto a = 1;
注意点
  1. auto变量必须初始化,否则无法推导类型
  2. visual studio不支持函数参数是auto类型,clion也不支持函数参数为auto类型,qt creator支持
  3. 数组不能使用auto
  4. 模板类型不能是auto

二 decltype

用法

获取变量类型,定义变量

int main(){
    int i;
    // decltype(i)相当于int,可以直接用于变量定义
    decltype(i) j = 0;
}

配合auto可以自动获取返回类型

auto func(int a, couble b) -> decltype(a + b){
    return a + b;
}

配合模板,可以智能的获取返回类型

template<class T1, class T2>
auto mul(T1 &t1, T2 &t2) -> decltype(t1 * t2){
    return t1 * t2;
}
int main(){
    int a = 10;
    double b = 11.1;
    auto c = mul(a, b);
    cou << "c = " << c << endl;
}

三 变量初始化

可以通过构造函数参数列表初始化类变量

class A{
public:
    int a;
    A(int i) : a(i){ 
    }
}

可以通过{}进行类变量初始化

class B{
public:
    int a = {1};
    int b = 2;
    A temp = {3};
    string str = {"mike"};
}

可以通过{}列表初始化

int a = 1;
int b = {2};
int c{3};
int arr[] = {1, 2, 3};
int arr2[]{1, 2, 3};

在vs和clion中通过列表初始化可以避免类型收窄。类型收窄就是因为自动类型转换导致的精度丢失。Java是默认防止类型收窄的。qt可以不会防止类型收窄。

四 增强for循环

纯指针无法使用增强for循环,没有定义大小的数组也无法使用增强for循环。

int* arr;
/*
 * 错误
for(auto i : arr){
    
}
*/

五 静态断言

在编译时就检查条件,静态断言使用的必须是常量,编译阶段就可以确定值

int main(){
    bool flag = false;
    // 非静态断言
    assert(flag == true);
    // 静态断言, 编译阶段就报错
    static_assert(sizeof(flag) == 2, "断言不通过");
    cout << "断言通过" << endl;
    return 0;
}

六 noexcept

表示不能抛出任何异常

c++ 11 之前可以这么写

// 不能抛出任何异常
void func() throw(){
    
}

c++ 11 之后可以这么写

void func() noexcept{
    
}

七 nullptr

消除NULL带来的二义性

传参的时候NULL会被当做0来处理

int main() {
    // 成功
    int p = NULL;
    // 失败
    int q = nullptr;
    return 0;
}

八 强类型枚举

int main(){
    // 编译报错,main::ok重定义,以前的定义是枚举数
    enum status { ok, error };
    enum status2 {ok, error};
}

int main(){
    enum class status { ok, error };
    enum class status2 { ok, error };

    // 必须要加作用域
    status flag = status::ok;
    cout << sizeof(flag) << endl;
    
    // 可以指定里面数据的具体类型
    enum class status3:char { ok, error };
    enum class status4:long long { ok, error };
}

九 常量表达式

发生在编译阶段

constexpr int getNum(){
    return 3;
}
int getNum2(){
    return 3;
}

int main(){
    // getNum()可以当做常量,在编译阶段就确定
    enum {e1 = getNum(), e2};
    // 错误,初始化类型必须是整型常量
    enum {e1 = getNum2(), e2};
}

constexpr的局限性

int main(){
    constexpr int func();
    // 错误,在使用时必须有定义
    int a = func();
}

constexpr int func(){
    // constexpr int a = 1 可以作为return语句,与return 1相同
    constexpr int a = 1;
}

int a = 1;
constexpr int func2(){
    // 错误,不可以返回全局变量
    return a;
}

constexpr int func3() {
    // 错误,返回值必须是常量或常量表达式,不能是运行时数据
    return rand();
}

类中成员函数的表达式

class Date{
public:
    // 常量表达式构造函数的函数体必须是空的
    constexpr Date(int year):year(year){
    }
    
    constexpr int getYear(){
        return year;
    }

private:
    int year;
};

int main(){
    constexpr Date d(3);
    // 常量表达式可以配合静态断言
    static_assert(d.getYear() == 3, "年不为2");
    
    Date d2(4);
    // 错误! d2是变量,无法使用静态断言
    static_assert(d2.getYear() == 4, "年不为2");
    cout << d.getYear() << endl;
}

十 自定义字面量

自定义字面量,名字要求 operate_“” xxx*(T1, size_t)

T1只能是如下七种类型之一;size_t是可选的,当T1是字符串类型时,size_t存在,但传参时无需赋值,编译器自动推导出T1的长度传给size_t。

char const *				// 传参时,不是传的字符串常量
unsigned long long
long double
char const *, size_t
wchar_t const *, size_t
char16_t const *, size_t
char32_t const *, size_t

十一 原生字符串字面值

可以规避字符串中的一些转义字符,保留换行后的Tab

十二 继承构造函数

在子类中,使用using 父类::父类构造函数可以直接声明子类构造函数沿用父类的构造函数。

但是继承构造函数不能使用默认构造函数

继承构造函数只能初始化父类中的成员变量

一旦使用继承构造函数,编译器就不会提供默认构造函数

class A{
public:
    A(int a, int b){
        this->a = a;
        this->b = b;
    }

protected:
    int a;
    int b;
};

class B : public A{
public:
    using A::A;
    void display(){
        cout << "a = " << a << "b = " << b <<  endl;
    }
};

十三 委托构造函数

一个构造函数调用其他构造函数

class Test{
public:
    Test():Test(1, 'a'){
    }
    Test(int x): Test(x, 'b'){
    }
    Test(int x, int b): a(x), b(y){
    }
private:
    int a;
    int b;
}

十四 final

作用基本上和Java中的一致,只是写的位置不一样。final修饰函数时,只能修饰虚函数

class A final{
private:
    int a;
}
// 报错!A类不可被继承
class B : public A{ 
}
class A{
public:
    virtual void func() final{
        cout << "这是虚函数" << endl;
    }
}
class B{
public:
    // 报错!final虚函数不可被重写
    virtual void func(){
	
    }
}

十五 override

用于显式的标记某个方法是重写父类的同名方法

在C++中,重写与规则与Java不同。如果子类重写了父类的某个方法,则会覆盖掉所用同名的重载方法。Java则不会覆盖。

public:
    virtual void func(){
        cout << "这是虚函数" << endl;
    }

    virtual void func(int a){
        cout << "这是带参数的虚函数" << endl;
    }

};
class B : public A{
public:
    // 报错!final虚函数不可被重写
    virtual void func(int a) override{
    }
};

int main(){
    B b;
    // 报错!A中的无参func()已经被覆盖
    b.func();
}

十六 default

default只能修饰类中默认提供的成员函数:无参构造、拷贝构造、赋值运算符重载、析构函数。表示使用系统提供的相关函数,效率比程序员自己提供要高。

class X{
public:
    x() = default;
    X(int i){
        a - i;
    }
    
    int a;
}

十七 delete

显式的声明某个函数被禁用,无法调用。可以用于自定的函数。

class Person{
public:
    Person(){}
    Person (const Person & p){
        this->name = p.name;
    } // 拷贝构造
    Person& operator=(const Person& p){
        this->name = p.name;
        return *this;
    }
    void* operator new(size_t) = delete;	// 禁用new操作符,无法new对象
private:
    int name;
};

禁用拷贝构造和等号赋值

class Person{
public:
    Person(){}
    Person (const Person & p) = delete; // 禁用拷贝构造
    Person& operator=(const Person& p) = delete; // 禁用等号赋值  
private:
    int name;
};

十八 模板实例化

在使用嵌套模板的时候,会存在X<<Z>>的情况,在C++11之前,>>被强制解析为右移,因此在嵌套模板中,必须加空格,写成X<Y<Z> >,比较麻烦。在C++11之后,改进了这一点,不需要在加空格,编译器会自动解析是模板实例化还是右移操作符。

十九 用using起别名

C++11之前,通过typedef来给一个类型起别名

typedef int int_32;

在C++11之后,可以使用using来定义别名

using int_32 = int;

二十 支持函数模板的默认参数

类模板默认参数是在C++11之前就支持,但函数模板的默认参数在C++11之后才支持。

类模板默认参数必须从右到左定义。函数模板的默认参数则没有这个限制。

二十一 左值引用和右值引用

左值:有明确定义的变量,引用返回类型的函数等

右值:字面量,非引用返回类型的函数

右值引用:使用双&&

int main(){
    int i = 1;
    int& ref1 = i;		// 正确
    int& ref2 = 10;		// 错误,左值引用无法引用右值
    int&& ref3 = 10;	// 正确
    int&& ref4 = i;		// 错误,右值引用无法引用左值
}
void func(int&& i){
    cout << i << endl;
}
void func(int& i){
    cout << i << endl;
}

int main(){
    func(1);		// 输出1
    int a = 2;
    func(a);		// 输出2
}

二十二 返回值优化技术

在vs、qt creator和clion中,会对值返回类型的函数做不同程度的优化。以如下代码举例

class MyString {
public:
    MyString(char* tmp) {
        len = (int)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 << "调用拷贝构造 " << "str = " << str << 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 << "调用赋值运算符" << endl;

        return *this;
    }
    ~MyString() {
        if (str != NULL) {
            cout << "调用析构函数 " << "str = " << str << endl;
            delete[] str;
            str = NULL;
            len = 0;
        }

    }

private:
    int len;
    char* str;
};

MyString getString() {
    char ch[] = "abcd";
    char* str = ch;
    MyString myString(str);
    return myString;
}

int main() {
    MyString temp = getString();
    return 0;
}

在vs 2013中,如果返回的是值,会生成临时对象返回,再将这个临时对象过度给接收的变量,此时过度不会调用拷贝构造,因为vs 2013进行了优化。

在vs 2022、qt creator和clion中,省略临时变量,将返回对象赋值给接收的变量。

在这里插入图片描述

如果使用-std=c++11 -fno-elide-constructors禁用返回值优化技术,就会多出许多两次拷贝构造函数和析构函数。

在这里插入图片描述

c++11新特性对于临时变量的拷贝,给出了新的优化–使用移动构造函数

二十三 移动构造函数

移动构造函数是针对拷贝构造函数进行的优化。使用右值引用来定义

class MyString{
public:
    ...
    MyString(MyString && tmp){
        str = tmp.str;
        len = tmp.len;
        t.str = NULL; 		//非常重要!否则会发生重复释放内存问题导致程序崩溃。
    }
}

二十四 转移赋值函数

是针对赋值运算符重载函数的优化。同样是使用右值引用来进行定义的。

二十五 标准库函数 std::move

作用:将一个左值转化为右值

左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;
右值当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器。
一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。
左值:指表达式结束后依然存在的持久对象,可以取地址,具名变量或对象 。
右值:表达式结束后就不再存在的临时对象,不可以取地址,没有名字。

std::move是为了性能而生。通过它可以尽可能的释放不再需要使用的左值,仅使用右值。

二十六 完美转发 std::forward

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

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

以下面的代码为例:

template <class T> void func(const T& t){

}

template <class T> void func(T& t){

}

template <class T> void forward_val(const T& tmp){
    func(tmp);
}

template <class T> void forward_val(T& tmp){
    func(tmp);
}

对于forward_val函数来说,有多少种不同的参数就要重载不同的次数。而作为中间层的forward_val函数来说,重载这么多次是没有实际效果的,因为最终都是调用的func的方法。因此能不能只进行一次forward_val函数的定义?可以通过完美转发来实现。

C++11是如何解决完美转发的问题的?实际上,C++11是通过引入一条所谓“折叠引用”(reference collapsing)的新语言规则,并结合新的模板推导规则来完美转发。

折叠引用:将复杂的未知表达式折叠为已知的简单表达式。

例如

typedef const int T;
typedef T& TR;			// 实际上 TR 就是 const int &
TR & v = 1;				// ??? v 不成了 const int && 的类型?事实上C++编译器会进行折叠引用

C++11中的引用折叠规则:

TR类型声明的变量v的类型v的实际类型
T&TRT&
T&TR&T&
T&TR&&T&
T&&TRT&&
T&&TR&T&
T&&TR&&T&&

总结:只要出现左值引用(即只有一个&),那折叠的结果就是左值引用,出现右值引用(即有两个&&)而没有左值引用,则结果为右值引用。

配合std::forward,就可以将参数的实际类型传递给目标函数。事实上,折叠引用更像是一个mask。

template <class T> 
void forward_val(T&& tmp){
    // 保持tmp的左值、右值、const、non-const属性
    func(std::forward<>(tmp));
}

二十七 智能指针 unique_ptr

在指针离开其作用域时,可以自动释放指针所指向的空间。程序员只需要关心申请堆内存,而不需要关心是否要释放堆内存。

unique_ptr内存禁用了拷贝构造函数。因为是unique的。

unique_ptr<int> up1(new int(11));
// 错误,禁止使用拷贝构造函数
unique_ptr<int> up2 = up1;

但是可以使用std::move()

unique_ptr<int> up1(new int(11));
// 可以,把up1的使用权转移给up2,up1将不能再操作堆空间
unique_ptr<int> up2 = std::move(up1);

使用reset()函数可以显式释放堆区内容

使用带参的reset(unique_ptr),会先释放原来堆区内容,重新给up1绑定一个新的堆区内容

int main(){
    unique_ptr<int> up1(new int(111));
    // 释放控制权,但不释放堆内存空间,让给指针p
    int* p = up1.release();
    
    unique_ptr<int> up2(new int(111));
    // 释放指针,释放堆内存
    up2.reset();
}

二十八 智能指针 shared_ptr

多个指针指向同一个堆内存对象。每个shared_ptr都会用一个计数器字段use_count记录当前指向的对象总共有多少个shared_ptr。每次reset一个shared_ptr都会将计数器减1。若计数器为1,再释放当前shared_ptr,此时计数器将变为0,就会释放堆内存。

二十九 智能指针 weak_ptr

weak_ptr不直接绑定堆内存,而是间接的通过shared_ptr指向堆内存。直接用shared_ptr赋值给weak_ptr不会增加use_count()的大小。只有通过weak_ptr的lock()方法获取shared_ptr之后,use_count才会增加。

三十 闭包

什么是闭包?一种说法是有状态的函数。什么叫有状态的函数?就是这个函数有了自己的局部变量,在运行的时候,这个函数会发生值的改变,即状态发生了改变。

闭包的实现

  • 仿函数(函数对象):不是C++11新特性
  • std::bind
  • lambda

std::bind绑定器:类似于函数指针,可间接调用某个函数

// std::bind(func, 值或占位符)(占位符值)
void func(int x, int y){
    cout << x << " " << y << endl;
}
int main(){
    bind(func, 11, 22);
    // 使用占位符
    bind(func, std::placeholders::_1, std::placeholders::_2)(11, 22, 33);
    // 使用命名空间 std::placeholders
    using namespace std::placeholders;
    // 报错!占位符绑定第二个参数,而参数只有1个
    bind(func, 11, _2)(11);
    bind(func, 11, _2)(11, 22);
}

三十一 lambda表达式

lambda表达式是一个匿名函数,本质上是一个函数

语法:[](){}

[]:外部变量捕获列表,捕获的变量可以在lambda内部使用

():方法参数列表,没有参数的话可以省略

{}:方法体

int main(){
    int a = 1;
    int b = 2;
    int c = 3;

    auto f1 = [](){};
    auto f2 = [a, b]{cout << a << " " << b << endl;};
    auto f3 = [a, b](int x, int y){
        cout << a << " " << b << endl;
        cout << "x = " << x << ", b = " << b << endl;
    };
    // 调用f3
    f3(10, 20);

    auto f4 = [=]{
        cout << a << " " << b << " " << c << endl;
    };
    f4();
    // 捕获全部外部变量,以引用传递的方式
    auto f5 = [&]{
        cout << a << " " << b << " " << c << endl;
    };
    // 捕获全部外部变量,a以值方式捕获,其余以引用方式捕获
    auto f6 = [&, a]{
        cout << a << " " << b << " " << c << endl;
    };

    auto f7 = [&, a]{
        // 报错!无法改变外部变量的值
        a++;
    };
    // 默认lambda表达式是const修饰的
    // 若想修改外部变量的值,要添加mutable关键字,且()不能省略
    auto f7 = [&, a]() mutable{
        a++;
    };
}

在这里插入图片描述

int i = 0;
class Demo{

public:
    void func(){
        auto f1 = [](){
            // 默认可以捕获全局变量
            cout << i << endl;
            // 报错,没有捕获变量a
            cout << a << endl;
        };
        // 捕获this即可捕获成员变量
        auto f2 = [this](){
            cout << i << endl;
            cout << a << endl;
        };
    }
private:
    int a = 1;
};

值传递和引用传递的区别

int main(){
    int a = 1;
    auto f1 = [=]() mutable{
        a = 10;
        // 输出10
        cout << "a = " << a << endl;
    };
    f1();
    // 依然输出1
    cout << a << endl;
    a = 2;
    // 依然输出10
    f1();
    auto f2 = [&]() mutable{
        a = 10;
    };
    f2();
    // 输出10
    cout << a << endl;
}
  • 值传递:lambda内部的变量a和外部的变量a不是同一个变量,内部a由外部a拷贝赋值
  • 引用传递:内外部a是同一个变量

事实上,lambda表达式的底层实现就是仿函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值