构造函数初始化列表

构造函数初始化列表用于显式初始化类成员,避免非内置类型二次创建,提高效率。初始化顺序遵循成员在类中的声明顺序,对于无默认构造函数的对象是必需的。通过实例分析了使用和不使用初始化列表的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

构造函数初始化列表

与普通构造函数的区别

构造函数初始化列表用于在显式初始化类成员。在平常的使用构造函数创建对象时,如果对象中将另一个对象作为成员,那么在调用构造函数之前,编译器会用成员对象的默认构造函数进行初始化。如果不想让编译器用默认的构造函数进行初始化,就要使用构造函数初始化列表来显式规定成员对象的初始化形式。

构造函数进行的操作是,将成员先初始化,再用给定值赋值给初始化后的成员,而构造函数初始化列表则是直接对成员进行初始化,两者之间的区别跟下面两种创建变量的方式区别类似:

//在创建的时候直接初始化,相当于构造函数初始化列表
int x = 3;

//先创建x,再赋值给x,相当于普通构造函数的初始化方式
int x;
x = 3;

防止非内置类型二次创建

对于内置类型,两种方式的初始化方法都是一样的效果。但是对于成员是一个对象的情况,则会有很大的区别,它会导致对成员对象进行多次不必要的复制,进而降低运行效率。有些时候如果管理不当,甚至会导致内存泄漏,更严重的是内存混乱,从而程序崩溃。下面的例子展示了这样的情况:

class Car{
	private:
		char * brand;
		double price;
	public:
		Car(){
			cout << "Using default constructor for Car." << endl;
			brand = new char[3];
			strcpy(brand, "No");
			price = 0;
		};
		Car(const char * b, double p = 0){
			cout << "Using constructor for Car." << endl;
			price = p;
			brand = new char[strlen(b) + 1];
			strcpy(brand, b);
		}
		Car(const Car & c){
			cout << "Using copy constructor for Car." << endl;
			brand = new char[strlen(c.brand) + 1];
			strcpy(brand, c.brand);
			price = c.price;
		}
		~Car(){
			cout << "deleting car" << endl;
			delete [] brand;
		}
		void show(){
			cout << "The " << brand << " car worth of $" << price << endl;
		}
		Car operator=(const Car & c){
			Car res(c);
			return res;
		}
};

class Person{
	private:
		char * name;
		Car car;
	public:
		Person(){
			cout << "Using defualt constructor for Person." << endl;
		}
		Person(const char * n, Car c){
			cout << "Using constructor for Person." << endl;
			name = new char[strlen(n) + 1];
			strcpy(name, n);
			car = c;
		}

Car c("Ford", 23);
Person p("Jhon", c);

相比之下,Person类的构造函数比较简单,而Car类的构造函数则要复杂得多,这是为了对内存进行管理,以防止内存泄漏。要想知道Car类的构造函数为什么要这么写,可以看我的另一篇文章,不过不看也没有关系,不影响对构造函数初始化列表的理解。

运行上面的程序,会得到下面的输出结果:

Using constructor for Car.
Using copy constructor for Car.
Using default constructor for Car.
Using constructor for Person.
Using copy constructor for Car.
deleting car
deleting car
deleting car
deleting car

显然这个程序为了创建一个Person对象,创建并销毁了太多Car对象,下面解释为什么会产生这些输出。

  • 第一行没有什么特殊的,调用Car中定义的构造函数,创建对象。
  • 第二行,开始进入Person类的构造函数Person(const char * n, Car c),这个函数中Car是以值传递的,所以要复制一个副本,所以调用了Car类的复制构造函数。
  • 第三行,重点来了,这里调用的是Car类的默认构造函数,并且这个时候还没有进入Person类构造函数的函数体内执行代码,因为第一行代码是打印语句,而打印语句在第四行。说明编译器在进入Person类构造函数执行代码之前,用Car的默认构造函数创建了一个Car对象。这就是之前说的,先用默认构造函数创建对象,再对这个对象赋值。
  • 第四行,开始进入Person类的构造函数并执行代码。
  • 第五行,使用Car的复制构造函数将外部传进来的Car对象赋值给内部对象。

综上所述,为了初始化Person类中的Car成员,创建了三个Car对象。

下面通过构造函数初始化列表的方式对其进行改进,将Person类中的构造函数Person(const char * n, Car c)替换成:

Person(const char * n, Car c):car(c){
			cout << "Using constructor initiation list." << endl;
			name = new char[strlen(n) + 1];
			strcpy(name, n);
		}

该程序输出如下所示:

Using constructor for Car.
Using copy constructor for Car.
Using copy constructor for Car.
Using constructor initiation list.
deleting car
deleting car

可以看到,使用了构造函数初始化列表之后,编译器没有再调用Car的默认构造函数。而是直接用Car的复制构造函数初始化car成员。

初始化顺序

另一个需要注意的点是,构造函数初始化列表初始化成员的顺序是根据成员在类中的声明顺序来的,而不是成员在列表中出现的顺序。例如:

class Test{
	private:
		int x, y;
	public:
		Test(int a): y(a), x(y){}
		void show(){
			cout << "x = " << x << ", y = " << y << endl;
		}
};

Test t(3);
t.show();

程序的输出结果为:

x = 0, y = 3

逻辑上来说,应该xy都等于3,但是因为是先初始化x,然后再初始化y,所以初始化x的时候y的值还是0,所以x被初始化成了0

必须用构造函数初始化列表的类型

必须用构造函数初始化列表的类型有以下特点:

  • 初始化之后不能修改
  • 没有默认构造函数的对象

因为如果不适用构造函数初始化列表,编译器就会调用成员对象的默认构造函数先初始化对象,所以没有默认构造函数的对象必须使用构造函数初始化列表。

因为普通构造函数初始化成员的过程是先将其初始化为系统默认值,再将给定值复制给初始化之后的变量。所以那些初始化之后就不能改变的变量就只能使用构造函数初始化列表。例如引用变量const变量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值