3. 带参构造函数:参数化构造、构造函数重载、默认参数在构造函数中的应用
好,咱们接着聊构造函数。
上一章我们看了默认构造函数——说白了就是那种啥参数都不传就能调用的构造方式。但实际开发中,你想想看,一个对象创建出来,总得有点初始数据吧?比如一个 Student 对象,总得有个名字、学号什么的。这时候,带参构造函数就派上用场了。
3.1 参数化构造:让对象带着数据出生
我个人习惯把带参构造函数叫做「参数化构造」。意思很直白:创建对象时,把初始值通过参数传进去。这样对象一出生,内部状态就是确定的,不用再额外调用 setter 方法去赋值。
来看个例子:
class Student {
public:
// 带参构造函数
Student(const string& name, int age, int id) {
m_name = name;
m_age = age;
m_id = id;
}
void printInfo() {
cout << "姓名: " << m_name << ", 年龄: " << m_age << ", 学号: " << m_id << endl;
}
private:
string m_name;
int m_age;
int m_id;
};
// 使用
Student stu("张三", 20, 1001);
stu.printInfo();
看到没?Student stu("张三", 20, 1001); 这一行,对象创建的同时就把三个成员变量都初始化好了。我在项目中遇到过很多新手喜欢先创建对象再一个个 set,其实完全没必要——构造函数就是干这个活的。
3.2 构造函数重载:同一个类,多种构造方式
C++ 支持函数重载,构造函数也不例外。你可以定义多个构造函数,参数个数或类型不同,编译器会根据你传的参数自动匹配对应的版本。
为什么要重载?说白了就是给使用者更多灵活性。比如有的场景下我只知道名字,不知道年龄和学号;有的场景下我三个信息都知道。那就可以提供多个构造入口。
class Student {
public:
// 三个参数
Student(const string& name, int age, int id) {
m_name = name;
m_age = age;
m_id = id;
}
// 两个参数:只有名字和年龄,学号给个默认值
Student(const string& name, int age) {
m_name = name;
m_age = age;
m_id = 0; // 临时学号
}
// 一个参数:只有名字
Student(const string& name) {
m_name = name;
m_age = 0;
m_id = 0;
}
private:
string m_name;
int m_age;
int m_id;
};
// 使用
Student s1("张三", 20, 1001); // 调用三个参数的
Student s2("李四", 22); // 调用两个参数的
Student s3("王五"); // 调用一个参数的
嗯,这里要注意:重载的构造函数之间不能有歧义。比如你写一个 Student(int) 和一个 Student(double),调用时传个整数,编译器可能会报错——因为它不知道该匹配哪个。我曾经踩过这个坑,后来养成了习惯:重载时尽量让参数类型差异明显。
Student(string) 和一个 Student(const char*),结果传字符串字面量时编译器报 ambiguous。后来统一用 string 类型,问题解决。记住:重载不是越多越好,够用就行。
3.3 默认参数:少写代码,多干活
构造函数重载虽然灵活,但代码量有点大。你看上面那个例子,三个构造函数,每个都要写一遍初始化逻辑,很啰嗦。有没有更优雅的方式?默认参数就是答案。
你可以在函数声明中给参数指定默认值,调用时如果没传这个参数,就自动用默认值。这样一个构造函数就能覆盖多种调用方式。
class Student {
public:
// 带默认参数的构造函数
Student(const string& name, int age = 0, int id = 0) {
m_name = name;
m_age = age;
m_id = id;
}
private:
string m_name;
int m_age;
int m_id;
};
// 使用
Student s1("张三", 20, 1001); // 三个参数全传
Student s2("李四", 22); // 只传两个,age=22, id=0
Student s3("王五"); // 只传一个,age=0, id=0
你看,一个构造函数就搞定了之前三个构造函数的事。代码量直接减少三分之二。我个人非常推荐这种写法,尤其是当参数有明确的「默认值」时。
但有个坑必须提醒你:默认参数必须从右向左连续提供。也就是说,你不能写成 Student(int age = 0, string name),因为编译器不知道你传的第一个参数是 age 还是 name。正确的做法是:把有默认值的参数放在参数列表的右边。
func(int a, int b = 1, int c = 2) 是合法的,但 func(int a = 1, int b, int c = 2) 是非法的。
3.4 重载 vs 默认参数:怎么选?
这个问题我经常被问到。我的建议是:
- 如果参数有自然的默认值(比如年龄默认为0,学号默认为0),优先用默认参数。代码简洁,维护方便。
- 如果不同构造方式的行为差异很大(比如一个从文件读取,一个从网络读取),那就用重载。因为不同构造函数可以有不同的实现逻辑。
- 不要混用。既写重载又写默认参数,很容易造成歧义。我见过一个同事写了
Student(string, int = 0)和Student(string),结果调用Student("test")时编译器报错——两个都能匹配。嗯,这种坑踩一次就够了。
3.5 知识结构图
下面这张图帮你理清本章的核心逻辑:
3.6 总结一下
带参构造函数是 C++ 面向对象编程的基础技能。记住三点:
- 参数化构造让对象带着数据出生,省去后续赋值操作。
- 构造函数重载提供多种构造方式,但要注意避免歧义。
- 默认参数是简化代码的利器,但必须遵守从右向左的规则。
我个人更倾向于先用默认参数,如果确实需要差异很大的构造行为,再考虑重载。这样代码既简洁又清晰。嗯,下一章我们聊聊初始化列表——那才是真正提升效率的地方。