C++11新特性

2023-10-31

一 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& TR T&
T& TR& T&
T& TR&& T&
T&& TR T&&
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表达式的底层实现就是仿函数

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11新特性 的相关文章

随机推荐

  • C++卷积神经网络实例:tiny_cnn代码详解(8)——partial_connected_layer层结构类分析(上)

    在之前的博文中我们已经将顶层的网络结构都介绍完毕 包括卷积层 下采样层 全连接层 在这篇博文中主要有两个任务 一是整体贯通一下卷积神经网络在对图像进行卷积处理的整个流程 二是继续我们的类分析 这次需要进行分析的是卷积层和下采样层的公共基类
  • CSDN如何解决复制后代码格式错乱问题?

    不要直接用鼠标选择代码复制粘贴 要用代码块右侧的复制按钮进行复制
  • Cannot prepare internal mirrorlist: No URLs in mirrorlist

    我是在执行 yum install dnf plugins core 这个命令报的错误 问题 在CentOS 8中 使用yum时出现错误 镜像列表中没有url 类似如下 Error Failed to download metadata f
  • canvas 刻度尺

    参考链接 https codepen io luren pen yEagYO 画布
  • 拆书领读

    来源于课程学习笔记 一 为什么拆书 赚钱 自我提升 看书 二 如何拆书 1 平台及类型 听书 用一篇文章的长度 5000 8000字 告诉你一本书的精华内容 拆书 用5 10篇文章的长 2 听书 采用总分总的套路 第一部分 总领全文 看书的
  • 锂离子电池保护板你懂多少呢?

    锂离子电池保护板你懂多少呢 电子设备通常用的是聚合物电池和锂电池 但是聚合物电池容易鼓包 随着锂离子电池的出现 由于其能量密度高 充电效率高 而且对环境 友好 故得到制造商的垂爱 但是用锂电池必须对过压和过流进行检测 以保护锂离子电池 不然
  • 静态方法访问非静态变量

    使用态方法需要访问非静态变量会出现图中的问题 其解决方法有两种 1 将要访问的非静态变量改成静态的 2 使用类对象来访问 public class Main String string 1111111123456 public static
  • LocalDateTime和Date的比较(JDK8新特性:时间日期API)

    最近在项目升级框架查资料会涉及到LocalDateTime 当时看到这个觉得为什么大家都在用这个 为什么 说到这里我们要知道这个LocalDateTime来自哪里 实际上这个LocalDateTime是JDK8的新特性之一 JDK8发布了新
  • opencv 手势识别 【附源代码】

    我使用OpenCV2 4 4的windows版本 Qt4 8 3 VS2010的编译器做了一个手势识别的小程序 本程序主要使到了Opencv的特征训练库和最基本的图像处理的知识 包括肤色检测等等 废话不多 先看一下基本的界面设计 以及主要功
  • 程序分析-klee工具分析

    一 klee介绍 1 1 简单介绍 Klee是一个LLVM IR符号执行工具 OSDI 08 Paper地址 能够自动生成测试 实现对各种复杂且环境密集型程序的高覆盖率 klee有2个目标 命中目标程序中的每一行代码 检测到每一个危险操作
  • C++代码静态检测

    C 代码静态检测一 CppCheck二 TscanCode三 PVS studio 代码静态检测 指的是程序在非运行状态下 对代码进行语法分析 检测其规范性和语法错误的一种操作 主要借助的都是一些第三方工具 比如CppCheck Tscan
  • Ubuntu20.04正确的开启方式(美化+软件安装)

    目录 1 什么都没有的ubuntu20 04 第一步 系统设置 第二步 美化 1 安装gnome software和chrome gnome shell 2 安装dash to dock 3 打开插件和主题 第三步 软件安装 1 什么都没有
  • Unity3D——射箭游戏

    先上游戏截图 把靶子调远一点 风力的影响会很明显 编码过程 1 利用一个空对象包含五个同心圆柱构建靶子 空对象的参数 一个同心圆柱的参数 其他的类似 然后构造箭矢并制成预制 就是一个黄色的棍子 2 建立构造箭矢的工厂 这段代码根据之前的打飞
  • 机器学习—非零中心化、非零中心化会带来的问题

    众所周知 激活函数最好具有关于零点对称的特性 不关于零点对称会导致收敛变慢 这种说法看到几次了 但对于背后的原因却一直比较模糊 今天就来捋一捋 神经元模型 如图1所示是神经网络中一个典型的神经元设计 它完全仿照人类大脑中神经元之间传递数据的
  • 用邻接表存储无向图的深度优先遍历

    实现代码 include
  • 记Nani_xiao的CSDN成长历程

    从小白 一步一步走来 记下我的成长脚步 虽然现在还是一菜鸟 但是我在努力 在前进的路上永不止步 记下这些 希望五年后的自己 真的成为了自己所期望的样子 从第一篇博客 到原创97篇 虽然很多技术含量并不大 但都是平时学习中和学习后的心血 记录
  • SpringBoot webSocket 资源无法加载、tomcat启动报错解决方法

    问题描述 1 项目集成WebSocket 且打包发布tomcat时出现websocket is already in CLOSING or CLOSE state这样的问题 建议参考 解决方法二 但是 解决方法一 请要了解查看 因为解决方法
  • MCU集成-系统架构(不断更新)

    应用场景 mcu主要应用于手环 无人机等等场景 成本与工艺 一个晶圆7英寸 参考价格一个28nm工艺的晶圆大概400w USD 约等于7万平方毫米 mm2 一个mcu可能1mm2 但是die是长方形 晶圆是圆形 所以会有损耗 die和die
  • JMeter安装教程

    JMeter JMeter安装步骤 1 JMeter简介 2 下载 3 配置环境 4 启动JMeter JMeter安装步骤 1 JMeter简介 安装JMeter之前必须安装jdk8或以上版本 jdk安装教程 http t csdn cn
  • C++11新特性

    文章目录 一 auto 用法 注意点 二 decltype 用法 三 变量初始化 四 增强for循环 五 静态断言 六 noexcept 七 nullptr 八 强类型枚举 九 常量表达式 十 自定义字面量 十一 原生字符串字面值 十二 继