C++——C++11(2)

我们今天继续来学习C++11的特性:

右值被引入之后,库的变化

在右值被引入之后,一些库和全局函数发生了变化,第一个是我们的全局函数:swap
在这里插入图片描述但是在C++11中:
在这里插入图片描述实现了移动构造版本,大大提高了效率:

还有就是我们熟悉的各种STL容器,比如vector,list,map,set等等都实现了移动构造和移动赋值的版本:
在这里插入图片描述在这里插入图片描述还有其他的,这里就不一一列举了。

还有就是STL的容器的插入函数都实现了右值引用版本,这样在传参传值的时候都大大提升了效率。

一个问题

之前我们说过move这个函数不会影响数据本身的属性,只会影响返回值的属性,并且返回值的属性是右值。

这就有一个问题:
我现在有一个简单的单链表,并且我完成了移动构造:

#pragma once
#include<stdio.h>

namespace Mylist
{
    template <class T> //模板参数
    struct listonde
    {
        T _data; //数据
        listonde<T>* _next; //指向下一个结点

        //构造函数
        listonde(const T& data= T()):
            _data(data),
            _next(nullptr)
        {
             cout<<"Mylistnode----->"<<"构造函数"<<endl;
        }

        //移动构造
        listonde(T&& data):
            _data(data),
            _next(nullptr)
        {
            cout<<"Mylistnode----->"<<"移动构造"<<endl;
        }

    };

    template <class T>
    class list
    {
    public:
        typedef listonde<T> _Node;

        void empty_list()
        {
            _head = new _Node; //给头结点开辟空间
            _head->_next = nullptr;
        }

        //构造函数
        list()
        {
            empty_list();
        }

        // 移动构造函数
        list(list&& other) noexcept :
                _head(other._head)
        {
            // 重置其他对象的指针,避免悬挂指针
            other._head = nullptr;
        }

        bool push_back(const T& data)
        {
            //给结点开辟新的空间
            _Node* newnode = new _Node(data);

            if(newnode == nullptr)
            {
                perror("new fail");
                return false;
            }

            // 假设_head始终指向一个哑元节点
            _Node* current = _head;
            while (current->_next != nullptr)
            {
                current = current->_next;
            }
            current->_next = newnode;
            newnode->_next = nullptr;

            return true;
        }

        bool push_back(T&& data)
        {
            //给结点开辟新的空间
            _Node* newnode = new _Node(data);

            if(newnode == nullptr)
            {
                perror("new fail");
                return false;
            }

            // 假设_head始终指向一个哑元节点
            _Node* current = _head;
            while (current->_next != nullptr)
            {
                current = current->_next;
            }
            current->_next = newnode;
            newnode->_next = nullptr;

            return true;
        }

    private:
        _Node* _head;
    };

}

这个时候我插入一个右值:

   private:
        _Node* _head;
    };

    void Test_1()
    {
        Mylist::list<int> head1;

        head1.push_back(move(23));
    }

这个时候,发生了怪事:
在这里插入图片描述
我们头结点是左值的析构函数,没问题,但是我不是push的是一个右值吗?咋还是左值的析构函数呢?
问题出在这里:
在这里插入图片描述尽管我们这里传的是右值,但是还记得吗move之后data自身的属性是没变的,还是一个左值,所以,它会去调左值的构造:
在这里插入图片描述
在这里插入图片描述
这就解释了为啥明明传的右值,却没有调用移动构造。

这也向我们传达一个信息:右值引用本身是左值(即右值引用的对象),有点绕,我们举个例子:

int&& rref = 42; // rref 是右值引用本身,它绑定到右值 42 上  
&rref; // 合法,因为 rref 是一个左值  
// &42; // 不合法,42 是一个右值,不能取地址

这样大家应该就比较清楚了,但是应该怎样解决这个问题呢?所以我们在new的时候,要再转一次右值:
在这里插入图片描述在这里插入图片描述
这样解决了我们的问题,

完美转发

我们来看这样子的一个栗子:

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
template<typename T>

void PerfectForward(T&& t)
{
    Fun(t); //完美转发
}



int main()
{
    //Mylist::Test_1();

    PerfectForward(10);           // 右值
    int a;
    PerfectForward(a);            // 左值
    PerfectForward(std::move(a)); // 右值
    const int b = 8;
    PerfectForward(b);      // const 左值
    PerfectForward(std::move(b)); // const 右值
    return 0;

}

运行结果:
在这里插入图片描述
也在我们的意料之中,但是如果我们使用move全都又会变成右值:

在这里插入图片描述
在这里插入图片描述
这不是我们想要的结果,我们想要保持实参的属性,这个时候我们就要用到完美转发
在这里插入图片描述这个函数会帮助我们完美保持实参的属性:
在这里插入图片描述这个时候,我们再来看:
在这里插入图片描述是不是很神奇:
在这里插入图片描述我们可以用完美转发解决我们上面的问题:
在这里插入图片描述在这里插入图片描述

新的类功能

默认成员函数
原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

C++11 新增了两个:移动构造函数和移动赋值运算符重载

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个(都没有实现)。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

namespace MyString
{
    class string
    {
    public:
        typedef char* iterator;
        iterator begin()
        {
            return _str;
        }

        iterator end()
        {
            return _str + _size;
        }

        string(const char* str = "")
                :_size(strlen(str))
                , _capacity(_size)
        {
            //cout << "string(char* str) -- 构造" << endl;

            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        // s1.swap(s2)
        void swap(string& s)
        {
            ::swap(_str, s._str);
            ::swap(_size, s._size);
            ::swap(_capacity, s._capacity);
        }

        // 拷贝构造
        string(const string& s)
        {
            cout << "string(const string& s) -- 深拷贝" << endl;

            string tmp(s._str);
            swap(tmp);
        }

        //移动构造
        string(string&& s)
        {
            cout << "string(string&& s) -- 移动拷贝" << endl;

            swap(s);
        }

        // 赋值重载
        string& operator=(const string& s)
        {
            cout << "string& operator=(const string& s) -- 深拷贝" << endl;
            /*string tmp(s);
            swap(tmp);*/
            if (this != &s)
            {
                char* tmp = new char[s._capacity + 1];
                strcpy(tmp, s._str);

                delete[] _str;
                _str = tmp;
                _size = s._size;
                _capacity = s._capacity;
            }

            return *this;
        }

        // 移动赋值
        string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s)-- 移动赋值" << endl;

            swap(s);
            return *this;
        }

        ~string()
        {
            delete[] _str;
            _str = nullptr;
        }

        char& operator[](size_t pos)
        {
            assert(pos < _size);
            return _str[pos];
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                strcpy(tmp, _str);
                delete[] _str;
                _str = tmp;

                _capacity = n;
            }
        }

        void push_back(char ch)
        {
            if (_size >= _capacity)
            {
                size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
                reserve(newcapacity);
            }

            _str[_size] = ch;
            ++_size;
            _str[_size] = '\0';
        }

        //string operator+=(char ch)
        string& operator+=(char ch)
        {
            push_back(ch);
            return *this;
        }

        const char* c_str() const
        {
            return _str;
        }
    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0; // 不包含最后做标识的\0
    };

    MyString::string to_string(int x)
    {
        MyString::string ret;
        while (x)
        {
            int val = x % 10;
            x /= 10;
            ret += ('0' + val);
        }

        reverse(ret.begin(), ret.end());

        return ret;
    }
}

class Person
{
public:
    Person(const char* name = "", int age = 0)
            :_name(name)
            , _age(age)
    {}

    /*Person(const Person& p)
    :_name(p._name)
    ,_age(p._age)
    {}*/

//    Person& operator=(const Person& p)
//    {
//    if(this != &p)
//    {
//    _name = p._name;
//    _age = p._age;
//    }
//    return *this;
//    }

    /*~Person()
    {}*/
private:
    MyString::string _name;
    int _age;
};
int main()
{
    Person s1;
    Person s2 = s1;
    Person s3 = std::move(s1);

//    Person s4;
//    s4 = std::move(s2);
    return 0;
}

我们看到这里:
在这里插入图片描述我们的s2会默认去调构造函数,然后s3,因为没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,自定义类型会去调用他自己的移动构造函数或者移动拷贝:
在这里插入图片描述如果我们放开析构函数 、拷贝构造、拷贝赋值重载中的任意一个,就不会调用移动构造或者移动赋值:
在这里插入图片描述在这里插入图片描述

default

如果一个类没有定义任何构造函数,编译器会自动生成一个默认构造函数。但是,如果类定义了其他构造函数,并且没有显式地定义默认构造函数,编译器就不会自动生成默认构造函数。在这种情况下,可以使用default关键字来显式地请求编译器生成默认构造函数

比如说:

class MyClass {  
public:  
    MyClass(int value) { /* ... */ }  
    MyClass() = default; // 显式请求默认构造函数  
};

default可以帮助我们强制让编译器生成我们想要的函数(析构,移动构造,赋值等等…)。

delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上 =delete 即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数:

class Person
{
public:
 Person(const char* name = "", int age = 0)
	 :_name(name)
	 , _age(age)
	 {}
 Person(const Person& p) = delete; //删除拷贝赋值
private:
	 bit::string _name;
	 int _age;
};

可变参数模板

在C++中,可变参数模板(variadic templates)是C++11引入的一项特性,它允许用户定义能够接受任意数量和类型的模板参数的函数、类或别名模板。通过使用可变参数模板,可以创建非常灵活和通用的代码结构。

可变参数模板的基本语法如下:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

可变参数模板可以接受任意类型的参数:
在这里插入图片描述
我们还可以计算它的大小:
在这里插入图片描述在这里插入图片描述

如何拿出可变参数包中的元素

不要小看这个问题,大家可能一开始会这么写:
在这里插入图片描述我们是这样想的,但是人家不准我们这样用,要想拿出可变参数包中的元素,我们得这样写:

在这里插入图片描述在这里插入图片描述这里我们来解释一下:
在这里插入图片描述

补充C++17:折叠表达式

如果嫌弃这样写太麻烦,C++17新增了折叠表达式

在C++17及以后的版本中,折叠表达式(Fold Expressions)是一个新特性,它允许我们在编译时递归地展开参数包。折叠表达式主要有三种形式:左折叠、右折叠和二元折叠

这里我们了解一下左折叠即可:
左折叠从参数包的第一个元素开始,依次向右展开。在逗号操作符的上下文中,左折叠看起来像这样:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{
    ((cout<< " " <<args),...); //C++17
    cout<<endl;
}

在这里插入图片描述这里简单了解即可。注意的是,我们这里没有了模板参数First,是因为折叠表达式帮我们从第一个元素展开了,如果我们写了模板参数,那么args就会把自己的第一个元素拿个这个模板参数,然后剩下的元素放进args:
在这里插入图片描述在这里插入图片描述

push_back和empalce_back

学了可变参数包后,我们再去看STL的话,发现会有这么一个函数:
在这里插入图片描述这个函数和push_back是一样的效果,那么他们两个到底谁更好呢?
在这里插入图片描述

相关推荐

  1. Pytorch安装小坑(Windows+cu111

    2024-03-14 11:22:01       38 阅读
  2. CF1152C Neko does Maths

    2024-03-14 11:22:01       28 阅读
  3. <span style='color:red;'>CI</span>/<span style='color:red;'>CD</span>

    CI/CD

    2024-03-14 11:22:01      49 阅读
  4. CCS 2023

    2024-03-14 11:22:01       33 阅读
  5. VGG16-CF-VGG11实验报告

    2024-03-14 11:22:01       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-03-14 11:22:01       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-03-14 11:22:01       106 阅读
  3. 在Django里面运行非项目文件

    2024-03-14 11:22:01       87 阅读
  4. Python语言-面向对象

    2024-03-14 11:22:01       96 阅读

热门阅读

  1. vuex怎么防止数据刷新丢失?

    2024-03-14 11:22:01       42 阅读
  2. Qt+FFmpeg+opengl从零制作视频播放器-12.界面美化

    2024-03-14 11:22:01       38 阅读
  3. 设计模式 — — 工厂模式

    2024-03-14 11:22:01       39 阅读
  4. - 概述 - 《设计模式(极简c++版)》

    2024-03-14 11:22:01       36 阅读
  5. c++qt函数中如何返回一个类对象或对象的引用

    2024-03-14 11:22:01       49 阅读
  6. Nginx和Ribbon实现负载均衡的区别

    2024-03-14 11:22:01       43 阅读
  7. 【OJ】K 个一组翻转链表

    2024-03-14 11:22:01       45 阅读
  8. Stream流

    Stream流

    2024-03-14 11:22:01      35 阅读
  9. Spring Boot 自动配置原理

    2024-03-14 11:22:01       38 阅读
  10. MATLAB使用OMP实现图像的压缩感知实例

    2024-03-14 11:22:01       39 阅读