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是一样的效果,那么他们两个到底谁更好呢?
在这里插入图片描述

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
桥接模式是一种结构型设计模式,它将抽象和实现分离,使它们可以独立地变化。桥接模式的核心思想是将一个大类或一组类分解成抽象和实现两个独立的维度,使它们可以独立地变化和扩展,同时通过桥接来将它们连接起来。 在C++中,桥接模式通常通过虚函数实现。抽象部分通过基类定义接口,而实现部分通过派生类实现具体的功能。通过将抽象部分的指针作为参数传递给实现部分的函数,就可以实现两个部分的连接。 下面是一个简单的桥接模式的C++示例: ```c++ class Implementor { public: virtual void operation() = 0; virtual ~Implementor() {} }; class ConcreteImplementorA : public Implementor { public: void operation() override { // 具体的实现A } }; class ConcreteImplementorB : public Implementor { public: void operation() override { // 具体的实现B } }; class Abstraction { public: Abstraction(Implementor* implementor) : m_implementor(implementor) {} virtual void operation() = 0; virtual ~Abstraction() {} protected: Implementor* m_implementor; }; class RefinedAbstraction : public Abstraction { public: RefinedAbstraction(Implementor* implementor) : Abstraction(implementor) {} void operation() override { m_implementor->operation(); // 其他操作 } }; int main() { Implementor* implementorA = new ConcreteImplementorA(); Implementor* implementorB = new ConcreteImplementorB(); Abstraction* abstractionA = new RefinedAbstraction(implementorA); Abstraction* abstractionB = new RefinedAbstraction(implementorB); abstractionA->operation(); abstractionB->operation(); delete abstractionA; delete abstractionB; delete implementorA; delete implementorB; return 0; } ``` 在上面的示例中,Implementor是实现部分的抽象基类,ConcreteImplementorA和ConcreteImplementorB是具体的实现类。Abstraction是抽象部分的基类,RefinedAbstraction是抽象部分的具体实现类。在main函数中,我们创建了不同的Implementor和Abstraction对象,并通过它们来完成不同的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值