PoEdu培训 C++班 第六课 类和对象(4)
文章类别: 培训笔记 0 评论

PoEdu培训 C++班 第六课 类和对象(4)

文章类别: 培训笔记 0 评论

类和对象(4)

注: 本文接上一篇博文!
// 本文所用例子

// ClassDemo.h
#ifndef _CLASS_DEMO_H_
#define _CLASS_DEMO_H_

namespace Hades
{
    class ClassDemo
    {
    public:
        ClassDemo(){}

        ClassDemo(int num)
        {
            _num = num;
        }

        ~ClassDemo(){}

        int GetNum()
        {
            return _num;
        }
    private:
        int _num;
    };
}

#endif //!_CLASS_DEMO_H_

拷贝构造函数

// 是不是赋值?
ClassDemo demo(0);
ClassDemo demo2 = demo; 

注意: 但凡有一个新的对象产生, 100%就需要调用构造函数.
所以, 它不是赋值.

那它是什么?

编译器会自动生成一个特殊的构造函数

ClassDemo(const ClassDemo& other)
{
    // 所有成员变量都会拷贝
    _num = other._num;
}

这个函数被称为拷贝构造函数(复制构造函数)

拷贝构造函数的特点

拷贝构造函数格式是固定的!
在一个类中, 有且只有一个拷贝构造函数.
拷贝构造函数的参数一定是该类的引用.

为什么拷贝构造函数的参数需要是引用?

如果我们将拷贝构造函数设计成如下方式:

ClassDemo(const ClassDemo other)
{
    // 成员拷贝代码
}

它会引起 无限递归
为什么?

  1. 首先, 参数other是个形参.
  2. 形参在被调用的时候, 会被实参化.

    • 所谓实参化, 就是要被拷贝.
  3. 既然要拷贝, 就需要调用拷贝构造函数.
  4. 而拷贝构造函数中, 又发现形参other.
  5. 跳转到步骤2.

无限递归产生了

// 可配合例子进行输出结果展示

默认生成函数的小小总结

一个 空类 中会有那些方法?

class ClassDemo 
{
public:
    // 默认构造
    // ClassDemo(){}
    // 默认析构
    // ~ClassDemo(){}
    // 默认拷贝构造
    // ClassDemo(const ClassDemo& other){}
    // 默认赋值
    // ClassDemo& operator=(const ClassDemo& other){return *this}
};

我们来给上诉 ClassDemo 加上一个成员变量.

class ClassDemo 
{
public:
    // 默认构造
    // ClassDemo(){}
    // 默认析构
    // ~ClassDemo(){}
    // 默认拷贝构造
    
    // 默认赋值
    
private:
    int _num;
};

在我们改变了之后,

浅拷贝和深拷贝

通过以上总结, 我们知道,
编译器为我们默认生成的函数非常好, 都能自主根据成员变量来发生变化.
多么牛逼的东西, 多么贴心的设计!
那我们还需要自己来写吗?

#ifndef _HADES_STRING_H_
#define _HADES_STRING_H_

#include <cstring>
using namespace std;

class HadesString
{
private:
    char* _szStr = nullptr;
    int _iLen = 0;
    
public:

    HadesString(char* str)
    {
        _iLen = strlen(str);
        _szStr = new char[_iLen + sizeof(char)];
        strcpy(_szStr, str);
    }

    // 析构函数
    ~HadesString()
    {
        delete[] _szStr;
        _iLen = 0;
    }

    // 获取字符串长度 记录的
    int GetLen()
    {
        return _iLen;
    }

    // 获取字符串
    char* GetString()
    {
        return _szStr;
    }

    // 设置字符串
    char* SetString(char* str)
    {
        if (_szStr)
            delete[] _szStr;
        _iLen = strlen(str);
        _szStr = new char[_iLen + sizeof(char)];
        strcpy(_szStr, str);
        return _szStr;
    }
};
#endif //!_HADES_STRING_H_

然后我们使用如下测试代码:

HadesString str("HadesStudio");
HadesString sb = str;  // 复制构造函数
cout << sb.GetString() << endl;
sb = "Hello HadesStudio";   // 赋值函数 
cout << sb.GetString() << endl;

结果1:
Alt 运行结果1

分析:

HadesString* str = new HadesString("HadesStudio");
HadesString sb = *str;  // 复制构造函数
delete str;
cout << sb.GetString() << endl;

结果2:
Alt 运行结果2

所以我们需要自定义复制构造函数赋值函数来进行自定义的字符串操作.
我们将程序改造如下:

#ifndef _HADES_STRING_H_
#define _HADES_STRING_H_

#include <cstring>
using namespace std;

class HadesString
{
private:
    char* _szStr = nullptr;
    int _iLen = 0;
    
public:

    HadesString(char* str)
    {
        _iLen = strlen(str);
        _szStr = new char[_iLen + sizeof(char)];
        strcpy(_szStr, str);
    }

    // 析构函数
    ~HadesString()
    {
        delete[] _szStr;
        _iLen = 0;
    }

    // 拷贝构造函数
    HadesString(const HadesString& other)
    {
        if (this != &other)
        {
            _iLen = other._iLen;
            if (_szStr)
                delete[] _szStr;
            _iLen = strlen(other._szStr);
            _szStr = new char[_iLen + sizeof(char)];
            strcpy(_szStr, other._szStr);
        }
    }

    // 赋值函数
    HadesString& operator=(const HadesString& other)
    {
        if (this != &other)
        {
            _iLen = other._iLen;
            if (_szStr)
                delete[] _szStr;
            _iLen = strlen(other._szStr);
            _szStr = new char[_iLen + sizeof(char)];
            strcpy(_szStr, other._szStr);
        }
        return *this;
    }

    // 获取字符串长度 记录的
    int GetLen()
    {
        return _iLen;
    }

    // 获取字符串
    char* GetString()
    {
        return _szStr;
    }

    // 设置字符串
    char* SetString(char* str)
    {
        if (_szStr)
            delete[] _szStr;
        _iLen = strlen(str);
        _szStr = new char[_iLen + sizeof(char)];
        strcpy(_szStr, str);
        return _szStr;
    }
};
#endif //!_HADES_STRING_H_

我们在来进行测试:

HadesString str("HadesStudio");
HadesString sb = str;  // 复制构造函数
cout << sb.GetString() << endl;
sb = "Hello HadesStudio";   // 赋值函数 
cout << sb.GetString() << endl;

结果3:
Alt 运行结果3

是时候来一波总结了
对指针进行直接赋值的行为, 我们称之为 浅拷贝
_浅拷贝 没有 维护参数的生命周期_, 参数的生命周期交由别处控制.
浅拷贝是存在风险的

将内存整体进行拷贝的方式, 我们称之为 深拷贝
在对象中维护了所有参数的声明周期, 所有参数的声明周期都和我的对象是同步的.
深拷贝是安全的.

小知识


下节预告:

运算符的重载


未完待续

如有错误,请提出指正!谢谢.

回复