C++中的初始化列表
C++中的初始化列表是进行初始化的,而构造函数对成员变量是赋值而非初始化
初始化和赋值的区别:
初始化是在对象(或变量)创建时为其赋予初始值的过程。这个过程通常只发生一次,就在对象诞生的那一刻。
int a=5;
赋值:赋值是在对象(或变量)已经存在之后,改变其存储的值的操作。
int b;
b=7;
在C++中,构造函数初始化列表是一种在对象创建时初始化成员变量的方式。它在构造函数执行之前执行,确保所有成员变量在进入构造函数体之前已经被初始
化。初始化列表以一个冒号开始,后跟以逗号分隔的成员变量及其初始化值。
语法: 构造函数(): 属性1(值1),属性2(值2),属性3(值3).....{}
代码如下:
class A {
public:
int a;
int b;
int c;
int& e;
const int d;
A(int a1, int b1, int c1) :b(a), a(a1), c(c1), e(a1), d(c1)//b用a初始化,a用a1初始化,c用c1初始化
{
//e = a1;//报错:构造函数的函数体是赋值,引用不能在这初始化
//d = 3;//报错:构造函数的函数体是赋值,常量不能在这初始化
cout << a << " " << b << " " << c << " " << e << " " << d;
}
};
int main()
{
A a(1, 2, 3);//1 1 3 1 3
return 0;
}
初始化列表的作用是:
在对象创建时,对成员变量进行初始化,特别是对于const成员变量,引用成员变量和没有默认构造函数的成员对象,必须用初始化列表进行初始化。
使用初始化列表的必要原因:
一、数据成员是类对象,且对象只有有参数的构造函数
当一个类的数据成员是另一个类的对象,并且这个对象的类没有默认构造函数(只有带参数的构造函数)时,必须使用初始化列表来初始化该数据成员。因为如果不使用初始化列表,编译器无法调用默认构造函数来初始化这个成员对象,会导致编译错误。
class A {
public:
A(int num)
{
cout << "A的有参构造" << endl;
}
};
class B {
public:
A a;//类A的对象a是类B的成员变量,成员对象
//由于A类中没有无参构造,只能通过初始化列表调用类A的有参构造来初始化成员对象a
B(int num) :a(num) {}
};
int main()
{
B b(3);//"A的有参构造"
return 0;
}
二、引用成员或者const修饰的数据成员
对于引用成员和const成员,它们在初始化后就不能再被赋值。引用必须在定义时初始化,const成员一旦初始化就不能被修改,使用初始化列表可以确保它们在对象创建时就被正确初始化。
class Person {
public:
const int id;
string& name;
Person(int m, string& s) :id(m), name(s)
{
cout << "id:" << id << endl << "name:" << name;
}
};
int main()
{
string name = "john";
Person p(1, name);
}
三、子类初始化父类的私有成员,需要在(只能在)初始化列表中显式地调用父类的构造函数
在继承关系中,子类构造函数在创建对象时会自动调用父类的构造函数来初始化从父类继承的成员。如果父类没有默认构造函数(只有带参数的构造函数),子类必须在初始化列表中显式地调用父类的构造函数,以确保父类部分的成员正确初始化。这是因为在子类对象构造过程中,父类部分的构造先于子类部分进行。
class A
{
private:
int a;
int b;
int c;
public:
A(int a, int b, int c)//有参构造
{
this->a = a;
this->b = b;
this->c = c;
}
int getA() { return a; }
int getB() { return b; }
int getC() { return c; }
};
class B :public A
{
private:
int d;
public:
//在初始化列表中显式地调用父类的构造函数,不能在构造函数内部被显示调用
B(int a, int b, int c, int d) :A(a, b, c)
{
this->d = d;
}
int getD() { return d; }
};
int main(int argc, char* argv[])
{
B b(1, 2, 3, 4);
printf("a=%d,b=%d,c=%d,d=%d\n",b.getA(),b.getB(),b.getC(),b.getD());
return 0;
}
初始化参数列表的特点:
1.只能在构造函数中使用(比构造函数体里面运行得快)
2.初始化参数列表的初始化顺序和成员变量的顺序一致,而不是和初始化参数列表中的顺序一致。
尽管在初始化列表中先写的是b(y),但实际上成员变量a会先被初始化,因为a在类的声明中位于b之前。
class A {
private:
int a;
int b;
public:
A(int x, int y) : b(y), a(x) {}
};
3.常量和引用在初始化参数列表中初始化
4.初始化参数列表可以调用成员对象的构造函数
5.当父类没有默认构造函数时,可以利用初始化参数列表调用父类的构造函数
初始化列表和在构造函数体内赋值的区别
初始化列表在进入构造函数体之前就对成员变量进行初始化,效率更高。对于一些需要在构造函数体内进行复杂计算的成员变量,可以先在初始化列表中进行默认初始化,然后在构造函数体内进行赋值。但对于const修饰的成员变量和引用成员变量,只能用初始化列表进行初始化。
1.语法形式上的区别:
初始化列表:初始化列表出现在构造函数的参数列表之后,以冒号(:)开始,后面跟着成员变量及其初始值。
构造函数体内赋值:先在构造函数的参数列表后定义函数体,在函数体内部使用赋值语句来给成员变量赋值。
2.执行上的顺序:
初始化列表:初始化列表中的初始化操作是按照成员变量在类中声明的顺序进行的,而不是按照初始化列表中的书写顺序。这是因为编译器会根据成员变量的声明顺序来安排初始化顺序,这样可以确保对象的正确构建。
构造函数体内赋值:赋值操作是按照构造函数体内语句的执行顺序进行的。
3.效率:
初始化列表更高效的情况:
对于基本数据类型,在初始化列表和构造函数体内赋值可能效率差异不大。但是对于类类型的成员变量,尤其是那些没有默认构造函数的类,初始化列表效率更高。使用构造函数体内赋值,编译器会先尝试使用默认构造函数来初始化,如果没有默认构造函数会导致错误。即使有默认构造函数,也会先调用默认构造函数创建,然后再进行赋值操作,这会产生额外的开销。
构造函数体内更高效的情况:
当需要进行一些复杂的计算或者条件判断来确定成员变量的值时,构造函数体内赋值可能更合适。
4.对特殊数据类型的处理:
初始化列表:对于const(常量)成员变量和引用成员变量,必须使用初始化列表进行初始化。因为const成员变量一旦定义就不能被修改,引用成员变量在定义时必须被初始化。
构造函数体内赋值:由于构造函数体内赋值是在对象已经创建后进行操作,所以无法用于初始化const成员变量和引用成员变量。如果尝试在构造函数体内对const成员变量赋值,编译器会报错。同样,对于引用成员变量,在构造函数体内赋值不符合引用的定义,因为引用必须在定义时初始化。