部分代码参考:https://github.com/zhedahht/CodingInterviewChinese2
题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数。
class CMyString
{
public:
CMyString(char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);
CMyString& operator = (const CMyString& str);
void Print();
private:
char* m_pData;
};
CMyString::CMyString(char *pData)
{
if(pData == nullptr)
{
m_pData = new char[1];
m_pData[0] = '\0';
}
else
{
int length = strlen(pData);
m_pData = new char[length + 1]; //strlen是计算字符串有多少字符的,不包括结束符,所以需要+1
strcpy(m_pData, pData);
}
}
CMyString::CMyString(const CMyString &str)
{
int length = strlen(str.m_pData);
m_pData = new char[length + 1];
strcpy(m_pData, str.m_pData);
}
CMyString::~CMyString()
{
delete[] m_pData;
}
//赋值操作符首先要注意是不是自己赋给自己,如果是这样的话什么也不做,把自己返回即可。
//其次就是别人赋值给自己,这时首先要自己把原来的值扔到,根据别人的大小开辟一块空间
//准备盛放别人的内容,最后不要忘了返回对自己的引用。
CMyString & CMyString::operator = (const CMyString & str) //定义赋值操作符函数
{
if(this != &str) //赋值操作符首先要注意是不是自己赋给自己,如果是这样的话什么也不做,把自己(this指针)返回即可。
{
delete[] m_data; //其次就是别人赋值给自己,这时首先要自己把原来的值扔到,根据别人的大小开辟一块空间,不过在此之前先判断
if (str.m_data == nullptr) //判断str.m_data是不是一个空指针
{
this->m_data = 0;
}
else
{
this->m_data = new char[strlen(str.m_data) + 1];
strcpy(this->m_data, str.m_data);
}
}
return *this; //准备盛放别人的内容,最后不要忘了返回对自己的引用。
}
答案代码写的很详细,而对于我这个渣渣来说需要完全搞明白代码所表示的意思需要知道如下概念:
(1)类的定义,语法表示;
(2)默认构造函数与析构函数定义,以及它们的作用;
(3)拷贝(复制)构造函数以及拷贝赋值操作语法;
有关构造函数与析构函数可以参考:
https://blog.csdn.net/jofranks/article/details/17438955
https://blog.csdn.net/shenwanjiang111/article/details/53576196
知识点:
构造函数:在创建对象的时候用来初始化对象的,构造函数是特殊的成员函数,只要创建类类型的新对象,都要执行构造函数。构造函数的工作就是保证每个对象的数据成员具有合适的初始值。
(1)通常来说,如果在定义类的时候未定义构造函数,那么编译器会自动产生一个默认构造函数,该构造函数的内容为空,但是它一定存在。
(2)如果定义了构造函数,那么在创建对象时,就需要将构造函数所需参数传入进去。
(3)构造函数的名称与类名相同,不需要指定类型。
(4)构造函数可以重载,可以定义多个重载构造函数,根据实际选择需要的构造函数。
(5)如果定义了构造函数,那么系统不会生成默认构造函数,创建对象时需要使用已经定义的构造函数,否则会出错。
构造函数内存分析:
默认构造函数:不开辟堆空间,仅仅是分配一个栈内存,它可以由编译器自动创建和销毁。
自定义构造函数:完全可以替代默认构造函数,并且可以开辟动态内存,申请堆空间,后期必须显示地调用析构函数销毁。若类的成员变量含有指针,则必须自定义构造函数,来申请动态内存!
析构函数 :析构函数一般与构造函数成对出现,主要用于类对象在声明周期结束时释放对象所占内存(在对象声明周期结束时,释放对象创建过程中所占用的外部资源)。
(1)是构造函数的互补,当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。管类是否定义了自己的析构函数,编译器都会自动执行类中非static数据成员的析构函数。
(2)析构函数无法重载,只会存在一个。
析构函数的运行:当对象引用或指针越界的时候不会执行析构函数,只有在删除指向动态分配对象的指针或实际对象超出作用域时才会调用析构函数。
合成析构函数:
编译器总是会合成一个析构函数,合成析构函数按对象创建时的逆序撤销每个非static成员。要注意的是,合成的析构函数不会删除指针成员所指向的对象。
拷贝(复制)构造函数:它是一种特殊的构造函数,具有单个形参,形参是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用拷贝构造函数。当将该类型的对象传递给函数或从函数返回该类型的对象时,将隐式使用拷贝构造函数。
一般也是有编译器添加默认拷贝函数,但是一旦使用自定义构造函数申请了动态内存,也必须自定义拷贝构造函数,才能达到深度拷贝(拷贝内容)的作用。否则只是浅拷贝(拷贝的是指针)。
拷贝构造函数的定义:
Myclass(const Myclass& str):a(str.a) {}
赋值操作符:跟构造函数一样,赋值操作符可以通过制定不同类型的右操作数而重载。赋值和复制经常是一起使用的,这个要注意。下面给出赋值操作符的写法:
A& operator = (const A& h)
{
assert(this != &h);
this->a = h.a;
return *this;
}
拷贝赋值操作符是一种特殊的函数,如果一个类没有用户定义的拷贝或者移动赋值(或者移动构造函数)就会隐含的声明拷贝赋值操作符。
对象的拷贝复制操作是操作符“=“的一种重载形式。返回值是*this指针的引用(尽管这里没有要求)。语法如下:
CMyString& operator= (const CMyString& str); //在类内申明
CMyString & CMyString::operator = (const CMyString & str) //类外定义
{
if(this != &str) //赋值操作符首先要注意是不是自己赋给自己,如果是这样的话什么也不做,把自己(this指针)返回即可。
{
delete[] m_data; //其次就是别人赋值给自己,这时首先要自己把原来的值扔到,根据别人的大小开辟一块空间,不过在此之前先判断
if (str.m_data == nullptr) //判断str.m_data是不是一个空指针
{
this->m_data = 0;
}
else
{
this->m_data = new char[strlen(str.m_data) + 1];
strcpy(this->m_data, str.m_data);
}
}
return *this; //准备盛放别人的内容,最后不要忘了返回对自己的引用。
}
最后要注意的是:类如果需要析构函数,那么他肯定也需要复制构造函数和赋值操作符。
strcpy这个可以指定拷贝字符的长度,指定源地址,目标地址,还有需要拷贝的字符的长度; strcpy只能传入两个参数,只指定拷贝的起始地址跟目标地址,然后整体拷贝。
思考:拷贝构造函数语法,拷贝赋值运算符函数(返回值类型、返回值、参数类型以及实现操作等等)。
为什么传入的参数类型是引用?为什么参数要用const关键字?
为什么要释放自身已有的内存?为什么要判断传入的参数和当前的实例(*this)是不是同一个实例?