为了账号安全,请及时绑定邮箱和手机立即绑定

浅谈类的几个基础构造函数

标签:
C++


通过例子来介绍下C++类的几个基础构造函数。

 

我们以一个C类型的字符串为例:


class myString
{public:
    myString(const char* rhs = 0);                 // 默认(含参)构造函数
    myString(const myString& rhs);                 // 拷贝构造函数
    myString(myString&& rhs) noexcept;             // 移动构造函数
    myString& operator=(const myString& rhs);      // 拷贝赋值函数
    myString& operator=(myString&&) noexcept;      // 移动赋值函数

    ~myString();                                   // 析构函数private:    char* m_data;
};


 

  

(一)、我们定义一个myString类,仅包含一个char* 的指针。先来看看它的默认构造函数


inline myString::myString(const char* rhs)
{    if (rhs)
    {
        m_data = (char*)new char[strlen(rhs) + 1];
        strcpy_s(m_data,strlen(rhs)+1, rhs);
    }    else
    {
        m_data = new char[1];        *m_data = '\0';
    }
}


  这里仅是申请了一块内存,对传入字符串进行了拷贝。

 

 

(二)、关于拷贝构造函数。拷贝构造函数是仅是对于传入对象的一次深拷贝。记得使用引用传入,由于我们不需要对传入对象进行修改操作,那就对它声明为const吧。

inline myString::myString(const myString& rhs)
{
    m_data = (char*)new char[strlen(rhs.m_data) + 1];
    strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);
}

 

 

(三)、对于拷贝赋值函数我们尤其要注意自我赋值问题。如果我们不进行自我赋值检测,即传入对象和被赋值对象是同一个的话,当delete完之后,传入的对象也已经不存在了,这并不是我们想要的结果。


inline myString& myString::operator=(const myString& rhs)
{    if (this != &rhs)
    {        if (m_data)            delete m_data;
        m_data = (char*)new char[strlen(rhs.m_data) + 1];
        strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);
    }

    return *this;
}


 

  (四)、关于移动构造函数。移动构造函数给我带来一种 “ 偷 ” 的概念。如何理解呢?我们来列举2个移动构造函数的主要应用场景:1. 假设我们需要将一批myString对象存入vector,当vector由于原容量不够大而发生扩充时,之前的C++版本中vector内部会重新申请一块内存,然后把之前存储的对象一个一个拷贝到新内存上,并且释放原内存。

当C++11以后我们可以借助移动构造函数这个“偷”的概念。怎么偷? 先看下代码:

inline myString::myString(myString&& rhs) noexcept
    : m_data(rhs.m_data)
{
    rhs.m_data = NULL;
}

这不就是指针的拷贝,换言之浅拷贝吗? 可以这么说!既然原先的对象可以被拿来用,我们又何必大费周章先做一份拷贝,再删除原副本呢?这换来的是效率上的巨大提升。使用移动构造函数我们需要注意2点:1). 不能让移动构造函数抛出异常,我们将它设为noexcept;  2). “ 偷 ”完东西将原指针设为NULL, 否则要是原对象被delete,“ 偷 ”的东西也就没了,这让我们难以接受。2. 如果我们要将一个容器拷贝到另一个容器,将容器内的对象一个一个拷贝?天哪!我们还是来 “ 偷 ” 吧。C++11以后容器都内置有移动构造函数,当我们对容器进行拷贝时,它已经在背后悄悄地 “ 偷 ”了。(举个例子, 将一个300万个对象的vector进行拷贝, 是一个一个拷贝好呢, 还是只需要“ 偷 ” 3个指针好呢(start, finish, end_of_storage)?    果然还是 “ 偷 ” 起来爽呀)

 

 

(五)、移动赋值函数。移动赋值的原理同上,也是采用 “ 偷 ” 的方法,尤其注意自我赋值即可。


inline myString& myString::operator= (myString&& rhs) noexcept
{    if (this != &rhs)
    {        if (m_data)            delete m_data;
        m_data = rhs.m_data;
        rhs.m_data = NULL;

    }   return *this;
}


 

 

 

(六)、析构函数。析构函数的任务就是把申请的对象进行释放。

inline myString::~myString()
{    delete m_data;
}

 

 

 

 这里给出测试代码:

(我们对一些代码加了些提示性的语句。 测试环境: VS2017


#include <iostream>#include <cstring>#include <vector>using namespace std;class myString
{public:
    myString(const char* rhs = 0);
    myString(const myString& rhs);
    myString(myString&& rhs) noexcept;

    myString& operator=(const myString& rhs);
    myString& operator=(myString&&) noexcept;    ~myString();    char* getStr() { return m_data; }private:    char* m_data;
};

inline myString::myString(const char* rhs)
{    if (rhs)
    {
        m_data = (char*)new char[strlen(rhs) + 1];
        strcpy_s(m_data,strlen(rhs)+1, rhs);
    }    else
    {
        m_data = new char[1];        *m_data = '\0';
    }
}

inline myString::myString(const myString& rhs)
{
    m_data = (char*)new char[strlen(rhs.m_data) + 1];
    strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);

}

inline myString::myString(myString&& rhs) noexcept
    : m_data(rhs.m_data)
{
    rhs.m_data = NULL;
    cout << " 调用了我一次。myString(myString&& rhs) " << endl;
}

inline myString& myString::operator=(const myString& rhs)
{    if (this != &rhs)
    {        if (m_data)            delete m_data;
        m_data = (char*)new char[strlen(rhs.m_data) + 1];
        strcpy_s(m_data, strlen(rhs.m_data)+1, rhs.m_data);
    }return *this;
}

inline myString& myString::operator=(myString&& rhs) noexcept
{    if (this != &rhs)
    {        if (m_data)            delete m_data;
        m_data = rhs.m_data;
        rhs.m_data = NULL;

    }
    cout << " 调用了我一次。operator(myString&& rhs) " << endl;    return *this;
}

inline myString::~myString()
{    delete m_data;
}int main()
{
    myString str1;
    myString str2("wang");
    myString str3(str2);
    myString str4 = str2;

    vector<myString> vec;    int n = 20;    while (n--)      // 通过size 和 capacity 的值以及辅助性语句,查看容器扩充时是否调用移动拷贝。
    {
        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = " ;
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str1);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str2);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str3);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

        cout << endl;
        vec.push_back(str4);

        cout << "vector size = ";
        cout << vec.size() << endl;
        cout << "vector capacity = ";
        cout << vec.capacity() << endl;

    }

    vector<myString> vec2{ vec };           // 查看容器赋值时是否调用内部移动构造(无提示性语句。 可通过vs2017调试跟踪函数调用过程)    return 0;
}

作者:大猫吃西瓜

原文链接:https://www.cnblogs.com/xiguas/p/9973196.html


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消