【C++初阶】构造函数超详解(特性+类型+初始化方式+易错避坑)
系列文章目录
后续会将类与对象中重要的六个默认成员函数(构造、析构、拷贝构造、赋值重载、普通对象和const对象的取地址重载)链接放在这目录里,有学习需求的可以先点个收藏~
文章目录
前言
学 C++ 类和对象,构造函数绝对是入门第一道核心重点。
我们用 C 语言写结构体时,每次都要手动写Init初始化函数、每次创建结构体对象都要手动调用初始化,非常麻烦还容易忘;
而 C++ 直接设计了构造函数:对象一创建,自动执行初始化,不用手动调用,完美替代 C 语言的初始化函数。
本篇文章就来详细讲解一下构造函数内容,如有疏漏之处,还望各位不吝指正。
一、通俗理解:什么是构造函数?
官方定义 & 核心作用:
构造函数是类的特殊成员函数,
核心任务:专门负责对象成员变量的初始化,不负责开辟空间(对象空间在栈帧 / 堆上提前开好)。
生活化比喻
把类比作【人出生模板】,对象就是具体的某一个人;
构造函数 = 对象出生时的专属初始化接生员
对象被实例化(创建),编译器自动调用构造函数,给成员变量初始化;
全程不用我们手动调用,天生自带初始化能力。
二、构造函数五大硬性特性(必背)
- 函数名必须和类名完全相同,不能改名、不能加前缀后缀
- 没有返回值,连
void都不用写,这是C++ 语法硬性规定的 - 对象创建时系统自动调用,禁止手动主动调用
- 构造函数支持函数重载,一个类可以有多个构造
- 如果我们一个构造都不写,编译器会自动生成默认无参构造;只要你显式写了任意一个构造,编译器就不再自动生成。
三、构造函数三大类型
1. 无参构造函数
指没有任何形参的构造函数。
#include <iostream>
using namespace std;
class Date {
public:
// 无参构造函数
Date() {
_year = 1; // 初始化年
_month = 1; // 初始化月
_day = 1; // 初始化日
}
void print() {
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
调用方式(细节!!!)
实例化对象时不能传参,且不能加括号(加括号会被编译器识别为 “函数声明”,而非对象创建):
int main() {
Date d1; // 正确:调用无参构造函数
// Date d1(); // 错误:被识别为“声明一个返回Date类型、无参的函数d1”
d1.print(); // 输出:1/1/1
return 0;
}
关键注意事项:
- 若手动定义了无参构造,编译器不再自动生成默认构造函数;
- 无参构造的灵活性极低(只能固定初始化值),实际中很少单独使用;
- 无参构造不能和全缺省构造同时存在(下文会讲原因)。
2. 带参构造函数
带有一个或多个形参的构造函数,解决 “无参构造只能固定初始化” 的问题,支持自定义初始化值。
#include <iostream>
using namespace std;
class Date {
public:
// 带参构造函数
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void print() {
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
调用方式(细节!!!)
实例化对象时必须传全参数(参数个数 / 类型需匹配):
int main() {
Date d2(2026, 5, 20); // 正确:调用带参构造,自定义初始化
d2.print(); // 输出:2026/5/20
// Date d2; //错误:类中已手动定义带参构造,编译器不再生成无参构造,无参实例化会报错
return 0;
}
关键注意事项
- 支持**函数重载**:一个类可以定义多个带参构造(参数个数 / 类型不同),例如:
// 重载1:只初始化年
Date(int year) { _year = year; _month=1; _day=1; }
// 重载2:初始化年+月
Date(int year, int month) { _year=year; _month=month; _day=1; }
// 重载3:初始化年+月+日
Date(int year, int month, int day) { _year=year; _month=month; _day=day; }
- 带参构造的灵活性中等(必须传参,无法无参实例化),适合 “强制用户指定初始化值” 的场景。
3. 全缺省构造函数
所有参数都提供默认值的构造函数,是 “无参构造 + 带参构造” 的结合体,兼顾灵活性和易用性。
#include <iostream>
using namespace std;
class Date {
public:
// 全缺省构造函数(所有参数都有默认值)
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
void print() {
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
调用方式(细节!!!)
支持0 个、部分、全部参数传递(参数按 “从左到右” 匹配,默认值补全未传递的参数):
int main() {
Date d1; // 传0个参数:用所有默认值 → 1/1/1
Date d3(2026); // 传1个参数:year=2026,month/day用默认值 → 2026/1/1
Date d4(2026, 5); // 传2个参数:year=2026、month=5,day用默认值 → 2026/5/1
Date d5(2026, 5, 20); // 传3个参数:覆盖所有默认值 → 2026/5/20
d1.print(); // 1/1/1
d3.print(); // 2026/1/1
d4.print(); // 2026/5/1
d5.print(); // 2026/5/20
return 0;
}
关键注意事项(核心易错点)
- 与无参构造互斥:
若同时定义无参构造和全缺省构造,编译器会报 “调用歧义”(Date d1; 无法判断调用哪个):
class Date {
public:
// 无参构造
Date() {
_year = 1; // 初始化年
_month = 1; // 初始化月
_day = 1; // 初始化日
}
// 全缺省构造
Date(int year = 1, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
};
int main() {
Date d1; // 报错:ambiguous call to overloaded function
return 0;
}
- 默认值的规则:
参数默认值必须从右往左连续赋值(不能中间参数无默认值),例如:
// 错误:month无默认值,且在year(有默认)和day(有默认)中间
Date(int year=1, int month, int day=1){
_year = year;
_month = month;
_day = day;
}
// 正确:从右往左连续给默认值
Date(int year, int month=1, int day=1){
_year = year;
_month = month;
_day = day;
}
- 属于 “默认构造函数”:
能 “无参调用” 的构造函数都算默认构造(无参构造、全缺省构造),若类中定义了全缺省构造,编译器不再生成默认构造。
4. 经典易错语法坑
Date d1; // 正确:定义一个无参对象
Date d2(); // 错误:这不是对象,是函数声明!
原因
C++ 语法歧义:
Date d2(); 编译器会理解成:声明一个返回值为 Date、无参的函数 d2,而不是创建对象。
这种情况下,VS编译器 会给出 C4930 警告,一定要避开这个写法。
四、编译器默认构造的底层行为
如果我们不写任何构造,编译器自动生成默认构造,它的规则:
- 内置类型(int、char、double、指针):
编译器不保证初始化,成员值是随机垃圾值; - 自定义类型成员(类里面嵌套另一个类对象):
编译器自动调用这个类的默认构造完成初始化。
示例:
#include <iostream>
using namespace std;
typedef int STDateType;
// 栈类
class Stack {
public:
Stack(int n = 4) {
_a = (STDateType*)malloc(sizeof(STDateType) * n);
if (_a == nullptr) {
perror("malloc 申请空间失败\n");
exit(1);
}
_top = 0;
_capacity = n;
cout << "Stack初始化成功" << endl; // 打印是否初始化成功
}
private:
STDateType* _a;
int _top;
int _capacity;
};
// 两个Stack实现队列
class MyQueue {
public:
// 编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
private:
Stack pushst;
Stack popst;
};
int main() {
MyQueue mq;
return 0;
}

五、构造函数两种初始化方式
1. 构造函数体内赋值
只是赋值操作,不是真正意义的初始化。
Date(int year = 1, int month = 1, int day = 1) {
_year = year; // 将形参year的值赋给_year
_month = month; // 将形参month的值赋给_month
_day = day; // 将形参day的值赋给_day
}
2. 初始化列表(推荐)
语法:是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式
Date(int y = 1, int m = 1, int d = 1)
:_year(y), _month(m), _day(d)
{}
特点:
- 初始化列表才是真正初始化,比函数体内赋值效率更高;
- 无论你写不写初始化列表,编译器都会默认走初始化逻辑。
3. 必须用初始化列表的 4 种场景(否则会报错)
- 引用类型成员变量
- const 修饰的成员变量
- 没有默认构造的自定义类成员
- 继承中的基类初始化
class Date
{
public:
// 必须初始化列表,函数体内赋值编译报错
Date(int& x)
:_ref(x), _n(20), _t(10)
{}
private:
int& _ref; // 引用
const int _n; // const
Time _t; // 无默认构造的自定义成员
};
4. C++11 新特性:类内成员缺省值
可以在类声明时直接给成员默认值:
class Date
{
private:
int _year = 1; // 类内缺省值
int _month = 1;
int _day = 1;
};
特性:如果构造初始化列表没给该成员赋值,自动使用类内默认值。
六、explicit 关键字(禁止隐式转换)
普通单参构造会触发隐式类型转换:
class Date
{
public:
Date(int y)
{
_year = y;
}
private:
int _year;
};
int main()
{
Date d = 2025; // 隐式转换:int -> Date
return 0;
}
加explicit修饰构造,禁止隐式转换,只能显式创建:
explicit Date(int y)
{
_year = y;
}
int main(){
// Date d = 2025; // 编译报错
Date d(2025); // 只能显式调用
}
总结
构造函数是 C++ 面向对象的基石,后面拷贝构造、析构、运算符重载全都依赖它。把这篇知识点吃透,后续学习才能走得更远~
更多推荐



所有评论(0)