正确理解C++中的构造函数

1,790 阅读4分钟

构造函数的定义

构造函数也叫作构造器,在创建对象的时候自动调用,一般用于完成对象的初始化工作

特点

  • 函数名与类名相同,无返回值,可以有参数,可以重载,可以有多个构造函数
  • 一旦定义了构造函数,必须用其中一个自定义的构造函数来初始化对象

注意:通过malloc分配的对象不会调用构造函数

误区

大家在很多博客和书籍中都会看到这么一句话:默认情况下,编译器会为每一个类生成空的无参构造函数。这句话其实是错的,用一段代码来验证这句话是错误的

class Person {
public:
	void run() {
		cout << "run()" << endl;
	}
};

int main()
{
	Person p;
	p.run();
	getchar();
}

我定义了一个Person类,里面只有一个run方法。然后在主函数中创建了p对象,再调用这个run方法。按照之前的说法,那么在调用这个run方法之前对会Person这个类去生成无参构造函数。但是在实际中编译器不会去对齐生成一个无参的构造函数。我们可以通过汇编代码来窥探其本质。

可以看到,在创建了Person对象之后,并没有调用任何构造函数。而如果我们定义了一个无参构造函数的话,那么创建对象之后是会去调用的。

class Person {
public:
	Person(){}
	void run() {
		cout << "run()" << endl;
	}
};

int main()
{
	Person p;
	p.run();
	getchar();
}

所以可以证明之前的那个结论是错误的,不是任何时候编译器都会去为类去生成一个默认的构造函数,而是会在特定的情况下才会去做这个一个操作。

编译器什么情况下会为类生成默认构造函数

大家可以思考一下,为什么会要有构造函数,构造函数在之前的定义中是这样的:在创建对象的时候自动调用,一般用于完成对象的初始化工作。如果我们定义了构造函数的话那么肯定会被调用,但是如果没有创建构造函数的话那么我们在创建一个类之后并不需要对其成员属性进行一些初始化操作,也就意味着我们在创建对象的同时不需要做什么事,那么编译器干嘛要帮我们创建构造函数呢,这不是多此一举吗? 所以,正确的理解就是C++的编译器会在某些特定的情况下,会给类自动生成无参的构造函数,比如:

  • 成员函数变量在声明的同时进行了初始化
class Person {
	int age = 10;
public:
	void run() {
		cout << "run()" << endl;
	}
};
/*
    为什么成员函数变量在声明的同时进行了初始化就会生成默认构造函数呢。
    其实很好理解,因为初始化这个操作在创建对象的时候要在构造函数中进行完成
    下面的这个汇编代码是在Person构造函数中的汇编代码,可以看到,在构造函数中,会把10赋值给age这个成员变量
*/

  • 定义了虚函数
class Person {
public:
	virtual void run() {
		cout << "run()" << endl;
	}
};
/*
    为什么定义了虚函数就会生成默认构造函数呢。
    这个就想要理解虚表这个概念了。大家应该知道虚函数的地址都是放在虚表里面的。
    而虚表也是要被初始化之后才有的,那么这个初始化这个操作就是要在构造函数中进行。
*/

  • 虚继承了其他类
class Person {
public:
	void run() {
		cout << "run()" << endl;
	}
};

class Student : virtual public Person {

};

int main()
{
	Student s;
	getchar();
}

/*
    因为虚继承也是需要虚表的,虚表需要在构造函数中进行初始化
*/
  • 包含了对象类型的成员,并且这个成员有构造函数(编译器生成或自定义都可)
class Person {
	int age = 10;
public:
	void run() {
		cout << "run()" << endl;
	}
};

class Student :  public Person {
	Person p;
};

int main()
{
	Student s;
	getchar();
}

/*
    因为在Student中包含了Person的成员变量,而Person在创建对象的同时需要对成员变量age进行初始化,
    就会自动生成默认的构造函数。因为是继承关系,父类中有构造函数,
    那么子类对象在创建的同时需要调用父类的构造函数,那么这个调用就会在构造函数中进行,就会为Student生成一个默认的构造函数了。
    下面的这张图就是在子类Student的构造函数中掉用了父类的构造函数
*/

  • 父类中有构造函数
class Person {
public:
	Person(){}
	void run() {
		cout << "run()" << endl;
	}
};

class Student :  public Person {
};

int main()
{
	Student s;
	getchar();
}
/*
    同上理解
*/

总结

在对象创建后,需要做一些额外的操作时(比如内存操作函数调用),编译器都会为其自动生成无参的构造函数