构造函数: 只有单个形参 (另一个形参其实是const指针) , 该形参是对本类类型对象的引用(一般用const修饰), 在用已存在的类类型对象创建新对象时由编译器自动调用.
特征
1. 拷贝构造函数时构造函数的一个重载形式, 那么构造函数的特性也一并拥有.
2. 拷贝构造的参数只有一个且必须使用引用传参, 使用传值方式会引发无穷递归调用
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2 = d1; // 另一种写法 Date d2(d1);
}
Date(const Date&d)即是拷贝构造函数, 可以观察到, 它与构造函数的不同之处在于形参的不同, 拷贝构造函数的形参是一个类.
那么到底如果用传值方式去传参的话, 为什么会发生无穷递归调用呢?
以Date (const Date d)为例, Date 是拷贝构造函数, 以传值方式传递的话本质上是传递一份实参的拷贝, 那么传过去的就是Date这个拷贝构造函数, 那么这个拷贝构造函数又要往内部进行递归传递, 即引发无线递归调用, 自然就会崩溃.
3. 如未显示定义, 系统会生成默认拷贝构造函数. 默认的拷贝构造函数对象的内存存储按字节序完成拷贝, 这种拷贝我们叫做浅拷贝, 或者值拷贝.
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
同样还是上面那个日期类, 这次我们没有显式定义拷贝构造函数, 那么运行程序会发生什么呢?
可以看到d2也已经成功复制了d1的值, 那既然默认拷贝构造函数和我们自己显示定义的拷贝构造函数是一样的效果, 那么我们还需要自己实现么?
让我们看一下其它类
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
//Date d1;
//Date d2(d1);
String s1("Hello");
String s2 = s1;
return 0;
}
上述这段代码经过测试, 发现编译成功,但是会运行崩溃
我们在调用S1("Hello")的时候没有问题, 在S2的时候为什么就会崩溃呢?
编译器已经标识很清楚了, 是因为析构函数的free语句. 拷贝构造函数的形参用的式引用, 那么形参和实参本质上是同一块空间, 那么你连续调用两次拷贝构造函数, 在退出的时候析构函数也被调用两次, 其中free就对同一块空间进行了两次释放, 从而引发崩溃.
这里本质上其实就是浅拷贝的一个问题, 像这种情况, 可以用深拷贝或者我们自己去写一个函数用指针去实现类似的拷贝功能.