c++类初始化列表初探
1 初始化和赋值
- 初始化:创建一个对象并赋予一个初值;
- 赋值:通过赋值运算符(=),将“=”右侧值赋给“=”左侧对象;
int a=5; //创建一个int对象a,初始值为5
a=12; //赋值
对于内置数据类型,初始化和赋值的区别不大;
对于自定义类型,初始化操作调用拷贝构造函数,赋值操作调用拷贝赋值运算符,下面以Person类为例;
//Person类
class Person {
public:
Person()
{
cout << "Person 默认构造调用" << endl;
}
Person(const char *name,int age)
{
cout << "Person有参构造调用" << endl;
this->m_Name = new char[strlen(name) + 1];
strcpy_s(this->m_Name, strlen(name) + 1, name);
this->m_Age = age;
}
Person(const Person&p)
{
cout << "Person 拷贝构造调用" << endl;
if (this->m_Name != NULL)
{
delete[]this->m_Name;
this->m_Name = NULL;
}
this->m_Name = new char[strlen(p.m_Name) + 1];
strcpy_s(this->m_Name,strlen(p.m_Name)+1, p.m_Name);
this->m_Age = p.m_Age;
}
Person&operator=(const Person &p)
{
cout << "Person拷贝赋值" << endl;
if (this->m_Name != NULL)
{
delete[]this->m_Name;
this->m_Name = NULL;
}
this->m_Name = new char[strlen(p.m_Name) + 1];
strcpy_s(this->m_Name, strlen(p.m_Name) + 1, p.m_Name);
this->m_Age = p.m_Age;
return *this;
}
void showInfo()
{
cout << this->m_Name << "今年" << this->m_Age << "岁!" << endl;
}
~Person()
{
cout << "Person析构调用" << endl;
if (m_Name != NULL)
{
delete[]m_Name;
m_Name = NULL;
}
}
char* m_Name;
int m_Age;
};
//测试函数
void test()
{
Person p("小明", 23);
p.showInfo();
cout<<endl;
Person p2(p);//也可写为Person p2=p,初始化
p2.showInfo();
cout<<endl;
Person p3;
p3 = p;//赋值
p3.showInfo();
cout<<endl;
}
运行结果
1.1 结论
通过程序运行结果可以知道,Person p2(p)语句调用类的拷贝构造函数,由于Person类中的m_Name是指向堆区空间的数据,所以我们必须提供自定义的拷贝构造函数,因为默认的拷贝构造函数只是简单的值拷贝;p3 = p语句调用类的拷贝赋值,同理编译器会提供一个默认的拷贝赋值运算符,也是简单的值拷贝,所以我们必须提供自定义的拷贝赋值运算符;
2 构造函数初始化列表
我们知道,构造函数是用来初始化类对象的数据成员,编写构造函数一般有两种方式,一种是通过在函数体里赋值实现,另一种是通过初始值列表,对于内置数据类型,这两种方式的相差无几,但是如果该类的数据成员有自定义的类型,那么就效率上讲,两者有些差别;
class MyClass1 {
public:
MyClass1()
{
cout << "myclass1 默认构造调用" << endl;
}
MyClass1(const MyClass1 &)
{
cout << "myclass1 默认拷贝构造调用" << endl;
}
MyClass1&operator=(const MyClass1 &)
{
cout << "myclass1 默认拷贝赋值调用" << endl;
return *this;
}
};
class MyClass2 {
public:
MyClass2(int a,MyClass1 &c)
{
this->m_A = a;
this->m_class = c;
}
//MyClass2(int a,MyClass1&c):m_A(a),m_class(c){}
int m_A;
MyClass1 m_class;
};
//测试函数
void test()
{
MyClass1 my1;
MyClass2 my2(3, my1);
}
运行结果
- 使用函数体内赋值
- 使用初始化列表
2.1 结论
通过运行结果可以知道对于自定义类型,通过初始化列表进行初始化,只需调用一次拷贝构造函数,而在构造函数中初始化,需要调用一次默认构造函数和一次赋值操作。
3 必须使用初始化列表的情况
如果类的数据成员是const,引用或者某种未提供默认构造函数的类类型,那么我们必须通过构造函数初始值列表为这些成员提供初值(c++prime第五版)
3.1 结论
建议养成使用构造函数初始值的习惯,这样能避免某些意想不到的编译错误
4 成员初始化顺序
构造函数初始值列表只说明用于初始化成员的值,并不限定初始化的具体顺序,成员的初始化顺序与它们在类定义中出现的顺序一致
class MyClass {
public:
MyClass(MyClass2 m2, MyClass3 m3, MyClass4 m4) :mc3(m3), mc4(m4), mc2(m2) {}//这里的顺序并不影响mc2,mc3,mc4的初始化顺序
//由下面出现的顺序决定,所以成员初始化顺序为:mc2->mc3->mc4
MyClass2 mc2;
MyClass3 mc3;
MyClass4 mc4;
};