拷贝构造函数也是特殊的成员函数, 具有下面的特征:
- 拷贝构造函数也是构造函数的一个重载形式
- 拷贝构造函数的参数只有一个并且必须使用引用传参(注意如果使用传值的方式会引起无限递归调用)
这里我们仍然使用之前的Date类来作为例子:
#include <iostream>
using namespace std;
class Date
{
int m_year;
int m_month;
int m_day;
public:
Date(int year = 2019, int month = 12, int day = 5)
{
m_year = year;
m_month = month;
m_day = day;
}
Date(const Date & d) //拷贝构造
{
m_year = d.m_year;
m_month = d.m_month;
m_day = d.m_day;
}
};
int main()
{
Date d1;
Date d2(d1);
system("pause");
return 0;
}
如果我们没有显示定义拷贝构造函数, 系统会生成默认的拷贝构造函数. 默认的拷贝构造函数对象按内存存储, 按字节序完成拷贝, 这种拷贝我们叫做浅拷贝, 或者值拷贝.
#include <iostream>
using namespace std;
class Date
{
int m_year;
int m_month;
int m_day;
public:
Date(int year = 2019, int month = 12, int day = 5)
{
m_year = year;
m_month = month;
m_day = day;
}
};
int main()
{
Date d1;
Date d2(d1); //这里d2调用的默认拷贝构造完成拷贝, d2和d1的值是一样的.
system("pause");
return 0;
}
既然编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了, 这样就有一个问题, 那么我们是不是不用自己实现一个拷贝构造函数了, 只看我们上面的日期类例子, 好像是没有这个必要, 那么我们再来看下面这个例子:
class String
{
char * m_str;
public:
String(const char * str = "nihao")
{
m_str = (char *)malloc(strlen(str) + 1);
strcpy(m_str, str);
}
~String()
{
cout << "~String" << endl;
free(m_str);
}
};
int main()
{
String s1("Hello");
String s2(s1);
system("pause");
return 0;
}
这里会发现程序会崩溃, 这就涉及到了浅拷贝和深拷贝的问题.
浅拷贝和深拷贝
浅拷贝: 直接复制内存
深拷贝: 当内存成员中有指向堆的指针,就必须重新给该指针分配空间,然后将目标对象指针所指空间的内容拷贝到新分配的空间.(如果不这样做,会导致两个指针指向同一片空间,从而在析构中多次释放)
赋值运算符重载
运算符重载
C++中为了增强代码可读性, 引入了运算符重载, 运算符重载是具有特殊函数名的函数, 也具有其返回值类型, 函数名以及参数列表, 其返回值类型和参数列表与普通函数类似.
函数名字: 关键字 operator 后面接需要重载的运算符符号
函数语法: 返回值类型 operator 操作符(参数列表)
注意:
- 不能通过连接其他的符号来创建新的操作符: 比如 operator @
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符, 其含义不能改变, 比如: 内置的整型+, 不能改变它的含义
- 作为类成员的重载函数时, 它的形参看起来比操作数数目少1, 成员函数有一个默认的形参this, 限定为第一个形参
赋值运算符重载
#include <iostream>
using namespace std;
class Date
{
int m_year;
int m_month;
int m_day;
public:
Date(int year = 2019, int month = 12, int day = 5)
{
m_year = year;
m_month = month;
m_day = day;
}
Date & operator = (const Date & d)
{
if (this != &d){
m_year = d.m_year;
m_month = d.m_month;
m_day = d.m_day;
}
}
Date(const Date & d) //拷贝构造
{
m_year = d.m_year;
m_month = d.m_month;
m_day = d.m_day;
}
};
int main2()
{
Date d1;
Date d2(d1);
system("pause");
return 0;
}
赋值运算符主要有4点:
- 参数类型
- 返回值
- 检测是否给自己赋值
- 返回 *this
- 一个类如果没有显示定义赋值运算符重载, 编译器也会生成一个, 完成对象按字节序的值拷贝
同样的, 默认的赋值运算符重载只能完成浅拷贝!