C++默认构造函数详解
发布于2026-06-30 阅读(0)
一、类的默认成员函数
当你定义一个看似“空”的类时,编译器并不会真的两手一摊——它会在背后默默为你准备一组特殊成员函数。在C++98标准下是6个,到了C++11又增加了几个新面孔。这些函数统称为**默认成员函数**,它们各自负责什么?看下表就清楚了:
| 函数 | 作用 |
|------|------|
| 默认构造函数 | 初始化对象 |
| 析构函数 | 清理对象资源 |
| 拷贝构造函数 | 用已有对象初始化新对象 |
| 赋值运算符重载 | 将已有对象赋值给另一对象 |
| 移动构造函数(C++11) | 用临时对象“转移”资源 |
| 移动赋值运算符(C++11) | 临时对象赋值 |
简单来说:只要你自己没主动声明这些函数,编译器就会自动生成一个“差不多能用”的默认版本。这听起来很方便,但实际用起来有不少坑——后面我们会一一拆解。
二、构造函数的基本概念
构造函数是类中一种特殊的成员函数,它在对象创建时自动被调用,专门用来初始化成员变量。它的规则其实很简洁:
- 函数名必须和类名完全相同
- 没有返回值(连`void`都不用写)
- 支持重载——你可以写多个参数不同的构造函数
- 对象创建那一刻自动执行,不需要手动调用
来看个直观的图(图注:构造函数调用流程示意图):

基本概念就这些,但真正容易让人困惑的是“默认构造函数”到底指什么——下面我们细说。
三、默认构造函数的含义
所谓默认构造函数,就是**可以不传任何实参就能调用的构造函数**。它有两种来源:
1. **程序员自己定义的默认构造函数**,包括:
- 无参构造函数(典型的`ClassName() {}`)
- 全缺省参数的构造函数(比如`ClassName(int a = 0, int b = 0) {}`)
2. **编译器自动生成的隐式默认构造函数**——也就是程序员一个构造函数都没写时,编译器“代劳”的那个。
看两个例子就明白了:
```cpp
class Student {
public:
// 无参构造函数
Student() {
name = "无名";
age = 0;
}
private:
string name;
int age;
};
class Point {
public:
// 全缺省参数的构造函数(也是默认构造函数)
Point(int x = 0, int y = 0) : x(x), y(y) {}
private:
int x, y;
};
```
注意:如果你同时定义了带参构造函数和全缺省构造函数,编译器会启动二义性判断——不过那是另一回事了。
四、编译器自动生成的默认构造函数
如果你一个构造函数都没写,编译器就会帮你生成一个隐式默认构造函数。比如:
```cpp
class Dog {
string name;
int age;
// 编译器自动生成 Dog() {}
};
```
那么这个自动生成的构造函数到底干了什么?两个关键点:
- **对于内置类型**(如`int`、`char`、指针等)**:什么都不做**——变量保持随机值(不同编译器的行为可能略有差异)。
- **对于自定义类型成员**:**调用它们各自的默认构造函数**。
来看一个能验证上述行为的例子:
```cpp
#include
using namespace std;
class A {
public:
A() { cout << "A的构造函数" << endl; }
};
class B {
int x; // 内置类型,不会初始化(垃圾值)
A a; // 类类型,会调用A的默认构造函数
};
int main() {
B b; // 输出:A的构造函数(x保持随机)
}
```
这里有个容易踩的坑:**一旦你主动实现了任何一个构造函数,编译器就不再自动生成默认构造函数**。比如:
```cpp
class Student {
public:
Student(string n) { name = n; } // 自定义构造函数
private:
string name;
};
int main() {
Student s1("张三"); // 正确
// Student s2; // 错误!没有默认构造函数可用
}
```
如果你既需要带参构造,又希望支持无参创建对象,那就必须自己手动提供一个默认构造函数。
五、默认构造函数的用途
**1. 定义对象而不初始化**
有些场景下需要先“空壳”对象,之后再赋值。比如一个数组包装类:
```cpp
class Array {
int* data;
public:
Array() { data = nullptr; } // 允许定义空数组对象
};
Array arr; // 调用默认构造
```
**2. 作为容器元素的默认构造要求**
STL容器(如`vector`)在扩容或创建元素时,经常需要元素的默认构造函数。如果你定义了一个没有默认构造的类,那么`vector v(10)`这种代码就会编译失败。
```cpp
vector v(10); // 需要Student有默认构造函数
```
**3. 继承体系中基类的初始化**
派生类的构造函数会自动调用基类的默认构造函数(如果基类没有默认构造,派生类就必须显式调用基类的带参构造)。
六、什么时候需要自定义默认构造函数?
并不是所有类都需要手写默认构造。看看下面这个判断表就知道了:
| 情况 | 是否需要自定义 |
|------|----------------|
| 类没有指针成员,且成员都有合理的默认值 | 不需要,编译器生成即可 |
| 成员中需要分配资源(如动态内存) | 需要(在构造中分配并初始化) |
| 需要给成员变量设置特定的初始值 | 需要 |
| 类有自定义的带参构造,但仍需无参创建对象 | 需要 |
举个例子:如果你的类里有指针成员,编译器生成的默认构造只会留下未初始化的野指针,这通常不是你想要的结果:
```cpp
class MyString {
char* buf;
public:
// 自定义默认构造:给指针一个安全的初始值
MyString() {
buf = new char[1];
buf[0] = '\0';
}
};
```
七、C++11 的改进:default 和 delete
C++11引入了两个非常有用的关键字,让你更精确地控制编译器的生成行为:
- `= default`:显式要求编译器生成默认版本(哪怕你已经定义了其他构造函数)
- `= delete`:禁止某个构造函数(或任何成员函数)
```cpp
class Widget {
public:
Widget() = default; // 手动要求编译器生成默认构造
Widget(const Widget&) = delete; // 禁止拷贝构造
};
```
这比之前那种“要么不写,要么全写”的二元选择灵活多了。
八、一个完整的例子
最后用一个完整的程序把今天讲的内容串起来:
```cpp
#include
#include
using namespace std;
class Book {
public:
// 默认构造函数
Book() {
title = "未命名";
price = 0.0;
cout << "调用默认构造函数" << endl;
}
// 带参构造函数(不是默认构造)
Book(string t, double p) : title(t), price(p) {
cout << "调用带参构造函数" << endl;
}
void print() {
cout << "书名:" << title << ",价格:" << price << endl;
}
private:
string title;
double price;
};
int main() {
Book b1; // 调用默认构造
Book b2("C++教程", 59.9); // 调用带参构造
b1.print();
b2.print();
return 0;
}
```
输出:
```
调用默认构造函数
调用带参构造函数
书名:未命名,价格:0
书名:C++教程,价格:59.9
```
从这段输出可以清楚看到:不同的构造函数在不同场景下被调用,各自执行了不同的初始化逻辑。这就是我们使用默认构造函数的基础场景。掌握好它,后续学习移动语义、资源管理等内容时才会更加顺畅。
本文转载于:https://www.jb51.net/program/365202nrp.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。