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 知识结构图

下面这张图帮你理清本章的核心逻辑:

带参构造函数 参数化构造 构造函数重载 默认参数 对象创建时直接传参 避免后续 setter 调用 参数个数/类型不同 编译器自动匹配 从右向左连续提供 一个函数覆盖多种调用

3.6 总结一下

带参构造函数是 C++ 面向对象编程的基础技能。记住三点:

  • 参数化构造让对象带着数据出生,省去后续赋值操作。
  • 构造函数重载提供多种构造方式,但要注意避免歧义。
  • 默认参数是简化代码的利器,但必须遵守从右向左的规则。

我个人更倾向于先用默认参数,如果确实需要差异很大的构造行为,再考虑重载。这样代码既简洁又清晰。嗯,下一章我们聊聊初始化列表——那才是真正提升效率的地方。


公众号:蓝海资料掘金营,微信deep3321