C++类的构造函数
详细讲解C++类中的构造函数,包含各种细节,一次讲明白!
C++的构造函数
一、什么是构造函数
在C++中构造函数是类的默认六大成员之一,即我们创建一个空类,里面什么成员都没有,C++也会为这个空类生成6个默认的成员函数,构造函数就是其中之一。
构造函数虽然取名构造,但它并不是用来构造对象,申请内存空间的,它的主要工作其实是完成对象的初始化工作。
在C语言中,我们通常会为一个struct设置init函数来进行初始化,但是这样非常繁琐,而且容易遗忘。
在C++中针对这一情况做出了改善,就为每个类都添加了构造函数。
二、构造函数的特性
构造函数是C++中每个类都有的特殊的成员函数,要注意它的作用是初始化对象,它有下面几个特征:
- 函数名称与类名相同;
- 无返回值;
- 创建类类型的对象时(对象实例化时)由编译器自动调用,保证每个数据成员都有一个合适的初始值;
- 在该对象的整个生命周期中只会调用一次;
- 构造函数支持函数重载。
class Student{
private:
int _no;//学号
string _name;//姓名
int _age;//年龄
public:
Student(){
//无参构造函数
}
Student(int no, string name, int age){
//有参构造函数
_no = no;
_name = name;
_age = age;
}
};
int main(){
Student s1;//这里调用的是无参的构造函数
Student s2(1, "zhangsan", 20);//这里调用有参的构造函数
//特别注意在s1这里,调用无参构造函数在s1后面务必不能加括号!!!否则就变成了函数声明!!!
//Student s3() 这个的含义是声明一个函数,函数名称叫s3,它的返回值是一个Student对象!
return 0
}
三、默认构造函数
假如在一个类中,程序员没有显式地为其定义一个构造函数,那么编译器就会自动生成一个无参的默认构造函数,一旦用户自己定义了构造函数,那么编译器就不会再生成默认的构造函数了。
class Student{
public:
//这里用户没有自己定义构造函数
private:
int _no;
string _name;
int _age;
};
int main(){
Student s;//虽然没有定义构造函数,但是编译器自动生成了无参的默认构造函数,所以这里也可以成功地创建对象
return 0;
}
在VS2019监视窗口中观察一下这个由默认构造函数生成的s对象
明显可以看出s的三个数据成员都被初始化了,int使用的随机值,而string则使用了空字符串。
重点是这个由编译器自动生成的无参的默认构造函数有一个规则:
对于该类的内置数据类型(int、double、char、指针等),不做处理(所以上面我们看到int就是随机值);自定义类型(struct、class),去调用自定义类型自己的无参构造函数来初始化。
打个比方,我们的Student类中还有一个类A
class A{
public:
A(int a0, int a1){
_a0 = a0;
_a1 = a1;
}
private:
int _a0;
int _a1;
}
class Student{
private:
int _no;
string _name;
int _age;
A _a;//Student内部有一个自定义类型A
};
int main(){
Student s;
return 0;
}
这时再观察一下监视窗口,发现Student中的A果然调用了它自己的构造函数,将自己初始化了
上面这里有一个点特别需要注意,如果采用类的默认构造函数,在初始化类内部的自定义类型时,一定调用的是内置自定义类型的无参构造函数,如果自定义类型没有无参构造函数,那么就会报错,自定义类型没有合适的构造函数!
class A {
private:
int _a0;
int _a1;
public:
A(int a0, int a1) {//A的构造函数不是默认的构造函数,是个有参的构造函数!
_a0 = a0;
_a1 = a1;
}
};
class Student {
public:
Student(){}//无参的默认构造函数
private:
int _no;
string _name;
int _age;
A _a;//自定义的A类型
};
int main() {
Student s;
return 0;
}
代码出错,错误如下所示
这里其实是C++在早期语法设计时候的一种失误,这种特定的机制导致在初始化带有自定义类型的类时非常麻烦,也给初学者带来了不少难度,所以在C++11标准中专门为初始化引入了一种新的规则,可以在成员变量定义的时候直接给缺省值,这样就不需要在构造函数中指明了,但是这里也容易混淆,也是一个重点:这里的缺省值,只是声明,并没有开辟空间,而非初始化,因为初始化是已经开辟空间之后,才要对已有的空间进行初始化。就如同函数的缺省值一样,如果在对象实例化时,我们没有特别指明,就用这个缺省值来初始化!
class A {
private:
int _a0 = 0;//缺省值
int _a1 = 1;//缺省值
public:
};
class Student {
public:
private:
int _no = 111;//缺省值111
string _name = "zhangsan";//缺省值
int _age = 20;//缺省值
A _a;
};
int main() {
Student s;
return 0;
}
因为我们在给定缺省值之后,也没有显示地给出构造函数,因此编译器为我们自动生成了一个默认构造函数,默认构造函数是无参的,我们没有对任何数据成员做出特殊处理,所以都会使用该成员的缺省值!下图为使用缺省值的监视窗口
四、默认构造函数与无参构造函数的关系
默认构造函数包含三个:分别是无参构造函数、全缺省构造函数、编译器自动为我们生成的构造函数。
它们的共性是都没有参数,即我们不需要传入任何参数就可以进行调用。但是默认构造函数只能有一个,编译器自动为我们生成的构造函数与其余两个不能同时出现,因为只要程序员自己定义了构造函数,编译器就不会自动生成了,所以只有当我们什么都不写的时候,编译器自动生成的默认构造函数是唯一存在的,这并不难理解;但是无参构造函数和全缺省构造函数,我们只能写一个,虽然它俩之间构成了函数重载,但是它们并不能同时存在,因为它们都不需要传入参数,如果我们调用了默认构造函数,那该调用它们两个之间的谁呢?这里就会给编译器产生歧义。所以如果程序员要自己显式地给出默认构造函数,只能在无参的默认构造函数和全缺省的默认构造函数之中选择一个。
class Student{
private:
int _no;
string _name;
int _age;
public:
//这两种默认构造函数只能同时存在一种
/*Student(){
_no = 111;
_name = "zhangsan";//无参构造
_age = 18
}*/
Student(int no = 111, string name = "zhangsan", int age = 18){
_no = no;
_name = name;//全缺省构造
_age = age;
}
};
有的人会认为,只有我们自己不写,编译器自动生成的构造函数才叫默认构造函数,经过上面的分析,我们就可以明确,这种观点是不正确的,只要是无需参数就可以调用的构造函数,都可以用作默认构造函数,但是默认构造函数只能有一个!不能同时存在多个默认构造函数!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)