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

通过例子来介绍下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

时间: 2024-08-06 01:22:31

浅谈类的几个基础构造函数的相关文章

C++之浅谈类与对象&lt;一&gt;

类与对象是C++语言的一个特点,类是对象的抽象,对象是类的实例. 类是抽象的需要占用内存,而对象时实例化的占用内存. 下面举个例子: class Time  //定义类 { int hour;   //定义数据成员 int min;    //定义数据成员 int sec;    //定义数据成员 }; Time t; 看着这个结构是不是有点眼熟呢,跟C语言中学过的struct的应用很类似,但是两者之间还是有区别的:在struct中若没有特别声明,结构体的成员都是public公有成员:而clas

浅谈类与对象

1.抽象:是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程. 数据抽象:某类对象的属性或状态 行为抽象:某类对象的共同行为或功能特征(方法) 2.封装:将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体.也就是将数据和操作数据的函数代码进行有机的结合,形成类. 3.继承 4.多态 1.带默认形参值的成员函数 类成员函数的默认值,一定要写在类定义中,而不能写在类定义之外的函数中. Class clock{ Public: Void setTime(int newH=0

浅谈类的组合

4. 类的组合 4.1 定义 类中的成员数据是另一个类的对象 4.2 类组合的构造函数设计原则 不仅要负责对本类中基本类型成员数据初始化,也要对对象成员初始化. 4.3 声明形式 类名::类名(对象成员所需的形参,本类成员形参):对象1(参数),对象2(参数),....... {  // 函数体其他语句 } 4.4 例子:类的组合,线段类. #include<iostream> #include<cmath> Using namespace std; Class Point{ Pu

ext 浅谈类的实例

 打开ext的API,如下 找到Class这个选项 将鼠标移到config那里可以看到有以下属性:  好了,让我们开始进入主题:     首先,来讲讲如何自定义一个类,在ext中,创建一个类其实与其他语言差不多,只是表达的方式不一样而已,下面是定义一个类的方法 <!--*********************************************-->        <!--类的创建-->    Ext.define('Father', {        name: '

浅谈:javascript的面向对象编程之基础知识的介绍

在进入javascript的面对对象之前,我们先来介绍一下javascript的几个概念. 1.javascript的面向对象的基本概念 function aa(){ } /* * 这里的aa,在我们以前看来他是一个函数,确实如此,但是从现在的角度来看 * aa不仅仅是一个函数,他还是一个对象.在javascript中,一切都是对象,函数也不例外 * 我们可以给对象的属性赋值,例如aa.b = 5,还可以获取对象的值,例如 * 这种赋值可以直接赋值,不需要声明,直接aa.属性=xxx,即可为aa

浅谈自然语言处理基础(下)

命名实体识别 命名实体的提出源自信息抽取问题,即从报章等非结构化文本中抽取关于公司活动和国防相关活动的结构化信息,而人名.地名.组织机构名.时间和数字表达式结构化信息的关键内容,所以需要从文本中去识别这些实体指称及其类别,即命名实体识别和分类. 21世纪以后,基于大规模语料库的统计方法成为自然语言处理的主流,以下是基于统计模型的命名实体识别方法归纳: 基于CRF的命名实体识别方法 基于CRF的命名实体识别方法简便易行,而且可以获得较好的性能,广泛地应用于人名.地名和组织机构等各种类型命名实体的识

浅谈Kotlin(三):类

浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 前言: 已经学习了前两篇文章,对Kotlin有了一个基本的认识,往后的文章开始深入介绍Kotlin的实战使用. 本篇介绍Kotlin中类的使用. 一.表现形式 首先看一段Java中定义类的形式,定义三个属性,每一个属性对应一个get.set方法,有一个toString()方法 /* * @author xqx * @emil [email protected] * create

【大话设计模式】——浅谈设计模式基础

初学设计模式给我最大的感受是:人类真是伟大啊!单单是设计模式的基础课程就让我感受到了强烈的生活气息. 个人感觉<大话设计模式>这本书写的真好.让貌似非常晦涩难懂的设计模式变的生活化.趣味化. 以下浅谈一下对设计模式基础的理解,假设理解的不好.还请大家指正. 首先设计模式是对面向对象的更专业的诠释.面向对象的三大基本特征是继承.封装.多态. 继承: 1.子类继承父类非private的属性和功能. 个人理解:有几个老婆是私有属性,小明他爸有好几个老婆.小明呢.恰好赶上了国家颁布法律一夫一妻 制(怎

浅谈java类集框架和数据结构(2)

继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主要有最重要的三种实现:ArrayList,Vector,LinkedList,三种List均来自AbstracList的实现,而AbstracList直接实现了List接口,并拓展自AbstractCollection. 在三种实现中,ArrayList和Vector使用了数组实现,可以认为这两个是