C++ 构造函数

在 C++ 中,“构造函数(Constructor)” 是一种特殊的成员函数。每当我们根据类创建对象时,编译器都会自动调用构造函数,用来完成成员变量的初始化工作。

前面操作类与对象时,我们每次写完 Student stu; 创建对象后,往往还要再写好几行代码给成员变量逐个赋值。有了构造函数,我们在创建对象的同时就能把初始值设置好,也不用担心漏写赋值语句而读出随机的垃圾值。

C++ 构造函数的基本用法

在 C++ 中,构造函数同样定义在类的内部,但它的声明写法和普通成员函数有明显区别。

语法:

class 类名 {
public:
    类名(参数列表) {
        // 初始化代码
    }
};

说明:

编写构造函数时,我们需要遵循以下 3 条语法规则:

  • 函数名与类名完全相同:构造函数的名字必须和类名一模一样,包括字母的大小写。
  • 没有返回值类型:构造函数前面不能写任何返回值类型,注意连 void 都不能写。一旦写了 void,编译器就会把它当成普通成员函数,而不再是构造函数。
  • 创建对象时自动调用:普通成员函数需要通过 对象名.函数名() 主动调用,而构造函数是在创建对象时由编译器自动执行的。

需要注意的是,类的访问修饰符默认为 private,因此构造函数通常要放在 public: 下面,这样我们才能在类的外部正常创建对象。如果一个构造函数不需要接收任何参数,我们就把它称为 “无参构造函数”,平时也常叫做 “默认构造函数”。下面我们来看一个例子。

示例 1:定义无参构造函数

#include <iostream>
#include <string>

class Book {
private:
    std::string title;
    int price;

public:
    // 无参构造函数
    Book() {
        title = "未命名图书";
        price = 0;
        std::cout << "构造函数已自动执行\n";
    }

    void showInfo() {
        std::cout << "《" << title << "》" << price << " 元\n";
    }
};

int main() {
    Book book;    // 自动调用 Book()
    book.showInfo();
    return 0;
}

运行结果如下。

构造函数已自动执行
《未命名图书》0 元

分析:

在这个例子中,我们在 Book 类内部定义了一个无参构造函数 Book()。当我们在 main() 函数中执行 Book book; 创建对象时,虽然没有写任何手动调用的代码,但在 book 创建出来的那一刻,编译器就已经自动执行了 Book()。

因此,屏幕上先打印出了 “构造函数已自动执行”,同时 book 内部的 title 和 price 也被赋值为 "未命名图书" 和 0。

注意 调用无参构造函数创建对象时,我们直接写 Book book; 或 Book book{}; 即可,千万不要加空括号写成 Book book();。因为 Book book(); 会被编译器当成普通的函数声明,而不是在创建对象。

带参构造函数与构造函数重载

上面的无参构造函数虽然省事,但创建出来的每一本图书,初始书名和价格都是固定不变的。如果我们希望在创建对象的同时,直接把具体的书名和价格传进去,就可以给构造函数加上参数。

示例 2:使用带参构造函数

#include <iostream>
#include <string>

class Book {
private:
    std::string title;
    int price;

public:
    // 带参构造函数
    Book(std::string bookTitle, int bookPrice) {
        title = bookTitle;
        price = bookPrice;
    }

    void showInfo() {
        std::cout << "《" << title << "》" << price << " 元\n";
    }
};

int main() {
    Book book("C++ 教程", 68);    // 创建对象时传入初始值
    book.showInfo();
    return 0;
}

运行结果如下。

《C++ 教程》68 元

分析:

Book book("C++ 教程", 68); 这句代码表示在创建 book 对象的同时,直接把小括号里的两个实参传给带参构造函数。其中,"C++ 教程" 传给形参 bookTitle,68 传给形参 bookPrice,并在函数体内赋值给当前对象的成员变量。

在实际开发中,创建对象的方式往往不止一种。有时我们还不知道具体信息,有时只确定了书名,有时则已经确定了完整的书名和价格。就像普通函数支持函数重载一样,同一个类中也可以同时定义多个构造函数,只要它们的参数个数或参数类型不同即可。请看下面例子。

示例 3:重载多个构造函数

#include <iostream>
#include <string>

class Book {
private:
    std::string title;
    int price;

public:
    // 1. 无参构造函数
    Book() {
        title = "未命名图书";
        price = 0;
    }

    // 2. 单参构造函数
    Book(std::string bookTitle) {
        title = bookTitle;
        price = 0;
    }

    // 3. 双参构造函数
    Book(std::string bookTitle, int bookPrice) {
        title = bookTitle;
        price = bookPrice;
    }

    void showInfo() {
        std::cout << "《" << title << "》" << price << " 元\n";
    }
};

int main() {
    Book book1;                        // 匹配无参构造函数
    Book book2("C++ 教程");            // 匹配单参构造函数
    Book book3("SQL 快速上手", 59);    // 匹配双参构造函数

    book1.showInfo();
    book2.showInfo();
    book3.showInfo();
    return 0;
}

运行结果如下。

《未命名图书》0 元
《C++ 教程》0 元
《SQL 快速上手》59 元

分析:

在这个例子中,Book 类重载了 3 个参数不同的构造函数,创建对象时编译器会根据实参自动匹配。其中,book1 不传参,匹配第 1 个无参构造函数。book2 只传入了书名,匹配第 2 个单参构造函数。book3 同时传入了书名和价格,匹配第 3 个双参构造函数。

默认构造函数的生成规则与 = default

可能有些小伙伴会问:“前两节我们在定义 Student 类时明明没有写任何构造函数,为什么依然能写出 Student stu; 正常创建对象呢?” 这就要说到编译器自动生成默认构造函数的 2 条规则了:

  • 不写任何构造函数时,编译器会自动生成默认构造函数:如果一个类中没有手写任何构造函数,C++ 编译器会自动为它生成一个空的无参默认构造函数,保证我们能正常创建对象。
  • 一旦手写了构造函数,编译器就不再自动生成无参构造函数:只要我们在类中手写了任何一个构造函数(哪怕只写了一个带参构造函数),编译器就会认为我们已经自己接管了初始化工作。此时,它不会再自动提供默认的无参构造函数。

提示 当类中没有手写构造函数,也没有给成员变量指定类内默认初始值时,编译器自动生成的默认构造函数并不会把 int、double 这类基本类型成员清零,它们的值依然是不确定的随机垃圾值。

很多初学的小伙伴最容易在第 2 条规则上踩坑:在类中只定义了一个带参构造函数,随后又去写不带参数的创建语句,结果直接引发编译报错。下面我们通过一个例子来看看。

示例 4:缺少无参构造函数报错

#include <iostream>

class Book {
private:
    int price;

public:
    Book(int bookPrice) {
        price = bookPrice;
    }
};

int main() {
    Book book1(68);    // 正常匹配带参构造
    Book book2;        // 错误:没有无参构造函数
    return 0;
}

运行结果如下。

(报错)error: no matching function for call to 'Book::Book()' 

分析:

因为我们在 Book 类中已经手写了带参构造函数 Book(int bookPrice),编译器不再自动生成无参构造函数。此时执行 Book book2; 时,编译器找不到无参的 Book(),就会直接报错,提示没有匹配的函数(no matching function)。

如果类中已经定义了带参构造函数,同时我们又希望保留无参创建对象的方式,应该怎么办呢?除了像示例 3 那样自己手写一个无参构造函数之外,从 C++11 起,我们还可以在无参构造函数声明末尾加上 = default;,要求编译器帮我们生成默认无参构造函数。

示例 5:使用 = default

#include <iostream>
#include <string>

class Book {
private:
    std::string title = "未命名图书";    // 类内默认初始值
    int price = 0;                       // 类内默认初始值

public:
    Book() = default;                    // 生成默认无参构造

    Book(std::string bookTitle, int bookPrice) {
        title = bookTitle;
        price = bookPrice;
    }

    void showInfo() {
        std::cout << "《" << title << "》" << price << " 元\n";
    }
};

int main() {
    Book book1;                          // 调用默认无参构造
    Book book2("Python 教程", 79);       // 调用带参构造

    book1.showInfo();
    book2.showInfo();
    return 0;
}

运行结果如下。

《未命名图书》0 元
《Python 教程》79 元

分析:

Book() = default; 这句代码表示要求编译器重新生成默认的无参构造函数,这样 Book book1; 就能正常编译通过了。

这里大家要留意一个细节:Book() = default; 本身并不会自动把 int 这类基本类型成员初始化为 0。例子中 book1 的价格之所以能安全输出 0,是因为我们在定义成员变量时直接写上了 int price = 0; 指定了类内默认初始值。

当成员变量已经指定了类内默认初始值,需要保留一个不做额外操作的无参构造函数时,下面 2 种方式是等价的。

// 方式 1(推荐)
Book() = default;

// 方式 2
Book() {}

在实际开发中,我们更推荐使用方式 1,配合类内默认初始值一起使用,比手写一对空花括号更简洁直观。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号