默认构造函数
编译器提供一个不接受任何参数,也不执行任何操作的构造函数,称之为默认构造函数
这是因为创造对象的时候总会调用默认构造函数
Klunk::Klunk() {} //定义
Klunk lunk; //声明 使用默认构造函数
如果定义了构造函数,C++不会定义默认构造函数,如果希望创建对象时不显示地对他进行初始化,则必须显示的定义默认构造函数,这种默认的构造函数没有任何参数,但是可以用来设定特定的值,比如说:
Klunk::Klunk()
{
klunk_cnt = 0;
}
带参数的构造函数也可以是默认构造函数,只要所有的参数都有默认值
Klunk::Klunk(int n = 0)
{
klunk_cnt = n;
}
但是注意,下面两种构造函数不能共同声明,会引起歧义
Klunk::Klunk(int n = 0) {klunk_cnt = n;}
Klunk::Klunk() {klunk_cnt = n;}
Kulnk kar (10); //这回选择只用第一种
Kulnk bus; //两种都可以,所以会报错
复制构造函数
// 对象的声明
Stringbad sailor = sports
上面这句话是常见的一种对象声明方式,这种对象方式使用了哪种构造函数呢?不是默认构造函数,也不是 Stringbad ::Stringbad (const char *)这种构造函数,而是等效于下面这句话
StringBad sailor = StringBad (sports)
因为sports的类型为StringBad ,所以说此时构造函数的原型如下
StringBad (const StringBad &)
当使用一个对象初始化另一个对象的时候,编译器会自动生成构造函数,成为复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中。也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的复制过程,累的复制构造函数原型:
StringBad (const Class_name &) // 他接受一个指向类的对象的常量引用作为参数
何时调用复制构造函数
新建一个对象,并将其初始化为同类现有对象的时候,复制构造函数就会被调用,在很多情况下都可能发生,最常见的情况是将新对象显示的初始化为现有的对象。
StringBad ditto (motto) //使用复制构造函数构造ditto
StringBad metto = motto
StringBad also = StringBad (motto)
StringBad * pStringBad = new StringBad (motto)
其中中间两种的声明可能会使用复制构造函数直接创建metto 和 also,也可能会生成一个临时对象,然后将临时兑现的内容赋值给metto 和also。最后一种是初始化一个匿名对象,并将新对象复制给指针。
当函数按值传递对象或者函数返回对象时,都将会使用复制构造函数,每当程序生成了副本,编译器都将会使用复制构造函数
注意: 由于使用按值传递的时候会调用复制构造函数,那么会浪费一定的时间和空间,所以应该经常使用引用传递参数,这样的话,可以节省调用构造函数的时间以及存储空间。
默认的复制构造函数的功能
默认的复制构造函数逐个复制非静态成员(成员复制也成为浅复制),复制的是成员的值。
StringBad sailor = sports;
//与下面的函数等价
StringBad sailor;
sailor.str = sports.str;
sailor.len = sports.len;
浅复制构造函数带来的缺陷
浅复制构造函数会让新的对象和以前的对象使用同一个地址,那么当调用析构函数的时候,析构了一次,但是会让两个对象都失去地址,然后GG思密达
解决办法
定义一个显示复制构造函数以解决问题
所谓的显示复制构造函数,就是加了地址这一项。
StringBad::StringBad (const StringBad & st)
{
num_strings++;
len = st.len;
str = new char [len + 1]
std::strcpy (str, st.str);
}
警告:如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这类被称为深度复制。复制的另一种形式是浅复制,只是复制指针值,浅复制仅仅浅浅的复制指针信息,而不会深入挖掘复制指针引用的结构
赋值运算符
运算符原型:
Class_name & Class_name::operator=(const Class_name &);
赋值运算符的功能以及何时使用它
将已有的对象赋给另一个对象时,将使用重载的赋值运算符:
StringBad headline1 ("Celery Stalks at Midnight");
StringBad knot;
knot = headline1; // 使用复制构造函数创建一个临时对象然后通过赋值将临时对象复制到新的对象中。
/*
这样就是说初始化总是会调用复制构造函数创建一个临时的对象。
然后通过赋值将临时对象的值复制到新对象中。
*/
这个和上面的复制构造函数是有区别的
复制构造函数:
// 对象的声明
Stringbad metoo= knot
这里的metto是一个新创建的对象,被初始化knot的值,因此使用复制构造函数
这里的knot是一个先使用默认构造函数创建的对象,然后使用knot对其进行赋值操作
默认赋值运算符带来的问题
使用默认的赋值运算符所产生的问题的原因和复制运算符产生的问题原因是一样的,都是同一块地址,析构就没有了。
解决办法—-创建一个深复制函数
实现与复制函数一样,但是也有一些小区别
- 由于目标对象可能引用了以前分配的数据,所以在使用之前使用delete[]进行释放
- 函数应当避免将对象赋值给自身,否则,给新对象重新赋值前,释放内存操作可能删除的对象
- 函数返回一个指向调用对象的引用。
StringBad & StringBad::operator=(const StringBad & st)
{
if (&st == this)
return *this;
delete [] str;
len = st.len;
str = new char [len + 1];
std::strcpy (str, st.str);
return * this;
}
参考 《C++ Primer Plus》