构造函数体赋值和初始化列表
在C++中,对象的初始化可以通过两种方式完成:构造函数体内的赋值和使用成员初始化列表。这两种方式有本质区别,理解它们的差异对编写高效、正确的C++代码至关重要。
一、基本概念对比
1. 构造函数体赋值
class MyClass
{
int a;
std::string b;
public:
MyClass(int x, const std::string& y)
{
a = x; // 赋值操作
b = y; // 赋值操作
}
};
2. 初始化列表
class MyClass
{
int a;
std::string b;
public:
MyClass(int x, const std::string& y)
: a(x), // 初始化
b(y) // 初始化
{
// 构造函数体
}
};
二、关键区别
1. 执行时机不同
- 初始化列表:在对象构造阶段完成初始化
- 构造函数体赋值:对象已构造完成后进行赋值
2. 性能差异
- 对于类类型成员:
- 初始化列表直接调用拷贝构造函数
- 构造函数体赋值会先调用默认构造函数,再调用赋值运算符
3. 必须使用初始化列表的情况
以下情况必须使用初始化列表:
- const成员变量
- 引用类型成员
- 没有默认构造函数的类类型成员
- 基类初始化
三、初始化列表详解
1. 语法规则
ClassName::ClassName(params)
: member1(value1),
member2(value2),
...
{
// 构造函数体
}
2. 初始化顺序
初始化顺序由成员在类中的声明顺序决定,与初始化列表中的顺序无关。
class Example
{
int a;
int b;
public:
Example(int x) : b(x), a(b) {} // 危险!a先初始化,此时b未初始化
};
3. 基类和成员混合初始化
class Derived : public Base
{
int x;
std::string s;
public:
Derived(int a, const std::string& str)
: Base(a), // 基类初始化
x(a), // 成员变量初始化
s(str) // 成员变量初始化
{
// 构造函数体
}
};
四、构造函数体赋值的适用场景
虽然初始化列表通常是更好的选择,但在以下情况可能需要在构造函数体内赋值:
1. 需要复杂逻辑初始化
class ComplexInit
{
int value;
public:
ComplexInit(bool flag)
{
if(flag)
{
value = computeValue1();
}
else
{
value = computeValue2();
}
}
};
2. 需要异常处理
class FileHandler
{
FILE* file;
public:
FileHandler(const char* filename)
{
file = fopen(filename, "r");
if(!file)
{
throw std::runtime_error("File open failed");
}
}
};
五、性能优化建议
1. 优先使用初始化列表:特别是对于类类型成员
2. 简单类型成员:内置类型(int, float等)两种方式性能差异不大
3. 避免重复初始化:使用初始化列表避免不必要的默认构造+赋值
4. 保持声明顺序与初始化顺序一致:提高代码可读性,避免潜在问题
六、综合示例
class Student
{
const int id; // const成员
std::string name; // 类类型成员
int& ageRef; // 引用成员
double scores[3]; // 数组
public:
Student(int sid, const std::string& n, int& age)
: id(sid), // const成员必须初始化
name(n), // 直接初始化,效率高
ageRef(age), // 引用必须初始化
scores{0,0,0} // 数组初始化
{
// 构造函数体内可进行复杂操作
if(name.empty())
{
name = "Unknown";
}
}
};
求关注!!!!