系列文章目录

后续会将类与对象中重要的六个默认成员函数(构造、析构、拷贝构造、赋值重载、普通对象和const对象的取地址重载)链接放在这目录里,有学习需求的可以先点个收藏~



前言

学 C++ 类和对象,构造函数绝对是入门第一道核心重点
我们用 C 语言写结构体时,每次都要手动写Init初始化函数、每次创建结构体对象都要手动调用初始化,非常麻烦还容易忘;
而 C++ 直接设计了构造函数对象一创建,自动执行初始化,不用手动调用,完美替代 C 语言的初始化函数。
本篇文章就来详细讲解一下构造函数内容,如有疏漏之处,还望各位不吝指正。


一、通俗理解:什么是构造函数?

官方定义 & 核心作用:
构造函数是类的特殊成员函数
核心任务:专门负责对象成员变量的初始化,不负责开辟空间(对象空间在栈帧 / 堆上提前开好)。

生活化比喻
比作【人出生模板】,对象就是具体的某一个人;
构造函数 = 对象出生时的专属初始化接生员

对象被实例化(创建),编译器自动调用构造函数,给成员变量初始化;
全程不用我们手动调用,天生自带初始化能力。

二、构造函数五大硬性特性(必背)

  1. 函数名必须和类名完全相同,不能改名、不能加前缀后缀
  2. 没有返回值,连void都不用写,这是C++ 语法硬性规定的
  3. 对象创建时系统自动调用,禁止手动主动调用
  4. 构造函数支持函数重载,一个类可以有多个构造
  5. 如果我们一个构造都不写,编译器会自动生成默认无参构造;只要你显式写了任意一个构造,编译器就不再自动生成。

三、构造函数三大类型

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;
}

关键注意事项(核心易错点)

  1. 与无参构造互斥:
    若同时定义无参构造全缺省构造,编译器会报 “调用歧义”(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;
}
  1. 默认值的规则:
    参数默认值必须从右往左连续赋值(不能中间参数无默认值),例如:
// 错误: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;
} 
  1. 属于 “默认构造函数”:
    能 “无参调用” 的构造函数都算默认构造(无参构造、全缺省构造),若类中定义了全缺省构造,编译器不再生成默认构造。

4. 经典易错语法坑

Date d1;   // 正确:定义一个无参对象
Date d2(); // 错误:这不是对象,是函数声明!

原因
C++ 语法歧义:
Date d2(); 编译器会理解成:声明一个返回值为 Date、无参的函数 d2,而不是创建对象。
这种情况下,VS编译器 会给出 C4930 警告,一定要避开这个写法。

四、编译器默认构造的底层行为

如果我们不写任何构造,编译器自动生成默认构造,它的规则:

  1. 内置类型(int、char、double、指针):
    编译器不保证初始化,成员值是随机垃圾值;
  2. 自定义类型成员(类里面嵌套另一个类对象):
    编译器自动调用这个类的默认构造完成初始化。

示例:

#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 种场景(否则会报错)

  1. 引用类型成员变量
  2. const 修饰的成员变量
  3. 没有默认构造的自定义类成员
  4. 继承中的基类初始化
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++ 面向对象的基石,后面拷贝构造、析构、运算符重载全都依赖它。把这篇知识点吃透,后续学习才能走得更远~

更多推荐