另外一篇博客说到了构造器的初步,即对对象的初始化,以及默认构造器相关的内容
接下来这篇着重总结构造器与祈构函数的其他内容。
重载构造器
在编写类时,可以编写多个参数列表不同的构造器,当根据不同的参数创建对象时,编译器会根据所提供的参数信息来决定该调用哪个构造器来初始化成员变量。
class MyClass
{
int a;
double b;
string s;
MyClass(int _a,int _b,string _s)
{
a = _a;
b = _b;
s = _s;
}
MyClass(int _a,int _b = 0,stirng _s="hello")
{
a = _a;
}
};
int main()
{
MyClass mc1; //编译报错,因为已经编写了构造器,编译器不会自动产生默认构造器。
MyClass mc2(10,20,"hello"); //第一个构造器被调用;
MyClass mc3(10); //第二个构造器被调用;
return 0;
}
复制构造器
复制构造器是一种特殊的构造器,它的表现形式为其只有一个参数,且参数类型是本类的引用。如果类在设计时没有编写复制构造器,则编译器会自动为类产生一个复制构造器。
复制构造器的作用是实现从源对象,到目标对象的字节复制,即使得目标对象的每个成员变量都等于源变量中相应的成员变量。
为了使得构造函数和复制构造函数有足够的区分度,构造函数不能以本类对象作为唯一参数。
复制构造函数在以下情况时,会被调用:
- 一个对象的初始化参数是另外一个同类的对象时,会引发复制构造器的调用。
class A
{
public:
int a;
double b;
A(int _a,double _b):a(_a),b(_b){} //构造函数;
A(const A& a):a(_a.a),b(_a.b){} //复制构造函数;
//当复制构造函数使用const引用参数时,可以使得复制构造函数的使用范围更为广泛。
//可以用于以常量对象作为参数去初始化另一对象。
};
int main()
{
A a1(10,20);
A a2=a1; //或A a2(a1),二者都等价,都是用a1来初始化a2;
return 0
}
- 一个对象作为形参时,是用对象本身调用复制构造器创建复制对象,再通过该复制对象再调用该函数的。
class A
{
public:
int a;
double b;
A(int _a,double _b):a(_a),b(_b){}
A(const A& a):a(_a.a),b(_a.b){}
};
void func(A a){}
int main()
{
A a(10,20);
func(a); //调用func函数时,传入的参数并不是a,而是临时创建对象,
//并将该对象通过a调用的复制构造器赋进行初始化。
return 0;
}
- 作为函数返回值的对象,是用复制构造器进行初始化的复制对象。
class A
{
public:
int a;
double b;
A(int _a,double _b):a(_a),b(_b){}
A(const A& a):a(_a.a),b(_a.b){}
};
A func()
{
A a(10,20);
return a;
}
int main()
{
func();
//调用该函数返回的并不是对象a,而是创建一个临时对象,
//并用a调用复制构造器进行初始化,并返回。
return 0;
}
另外注意到,默认的复制构造器进行的是将源对象对目标对象在字节层面的复制,而自行创建的复制构造器可以任意编写,所以所有构造器都会执行复制的结论是片面、不正确的。
类型转换构造器
除了复制构造器以外(以本类的引用作为参数)的单参数构造器都可以称为类型转换构造器,因为它可以起到自动类型转换的作用。
class A
{
public:
int a;
double b;
A(int _a,double _b):a(_a),b(_b){}
A(const A& a):a(_a.a),b(_a.b){}
A(int i):a(i),b(0){}
//类型转换构造器,可以完整int型和A型的自动转换。
};
int main()
{
A a1(10,20);
a1=12;
//如果没有类型转换构造器这句会编译报错;因为赋值符号左边类型是A,右边是int;
//赋值符号右边编译器创建一个临时的A类型的对象,并以int的值'1'作为实参调用类型转换构造器。
//并将这个临时对象赋值给a。
A a2 = 15;
//以上语句不是赋值,而是初始化,所以不会创建临时对象,而是直接调用类型转换构造器初始化a2。
return 0;
}
禁用构造器与默认构造器
编译器会默认为一个类生成以下默认构造器:
- 默认构造器
- 默认祈构函数
- 默认复制构造器
- 默认赋值函数(’='号重载)
- 移动构造器
- 移动复制函数
例如,对于默认构造器。我们知道如果在编写类的时候,没有编写构造函数,则编译器会自动生成一个默认的构造函数,如果编写类的时候编写了另外一个有参的构造器,默认构造器则不会自动生成,此时我们可以手动编写一个默认构造器,或者使用C++11的新关键字default;
class MyClass
{
public:
int a;
MyClass(int _a):a(_a){}
MyClass() = default;
};
default关键字用于显式的创建一个应该自动生成而被替代的默认构造器或成员函数。
而delete用于禁止编译器自动生成以上成员函数:
class MyClass
{
public:
int a;
MyClass() = delete;
MyClass(MyClass &mc) = delete;
};
以上禁止了编译器自动生成默认构造器,而且没有自定义的构造器,所以外界无法创建该类对象。同时禁止了编译器自动生成复制构造器,所以用其他对象对生成对象进行初始化的操作是不允许的。
祈构函数
祈构函数(destructor)是成员函数的一种,它的名字和类型相同,前面要加’~'符号,且和构造器一样没有返回值。
一个类只能有一个祈构函数,如果类中没有自定义,编译器会自动生成一个默认的祈构函数。如果自己定义了,则不会自动生成祈构函数。
祈构函数在对象的到达生存期限时自动调用。可以在祈构函数在对象消亡前做一些“善后”工作。
祈构函数并不是必要的,但是往往在类的成员变量中有指针类变量的时候需要编写祈构函数在对象消亡时,释放这些指针变量,因为它们在堆空间中不会随着对象的消亡而消亡,会继续占用内存资源。
class MyString
{
public:
char *s;
MyString(int len);
~MyString();
};
MyString::MyString(int len)
{
s = new char[len];
}
MyString::~MyString()
{
delete[] s;
}
如上,当MyString对象消亡时,成员变量s指向的地址也会被释放。