
⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///
WARNING : DETECTING HIGH ENERGY
🌊 🌉 🌊 心手合一 · 水到渠成

|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| >>> ACCESS TERMINAL <<< ||
| 🦾 作者主页 | 🔥 C语言核心 |
| 💾 编程百度 | 📡 代码仓库 |
Running Process: 100% | Latency: 0ms
索引与导读
- [为什么会有 默认成员函数 ?](#为什么会有 默认成员函数 ?)
- 六大默认成员函数
-
- 构造函数
-
- [1. 函数名与类名相同](#1. 函数名与类名相同)
- [2. 无返回值(也不写 void)](#2. 无返回值(也不写 void))
- [3. 实例化时自动调用](#3. 实例化时自动调用)
- [4. 可以重载(可以有多个)](#4. 可以重载(可以有多个))
- [5. 编译器默认生成](#5. 编译器默认生成)
- [6. 默认构造函数的"三合一"与歧义](#6. 默认构造函数的“三合一”与歧义)
-
- [C++ 规则](#C++ 规则)
- [7. 内置类型 vs 自定义类型](#7. 内置类型 vs 自定义类型)
- 析构函数
-
- [1. 命名规则](#1. 命名规则)
- [2. 一个类只能有一个构造函数](#2. 一个类只能有一个构造函数)
- [3. 对象生命周期结束时自动调用](#3. 对象生命周期结束时自动调用)
- [4. 默认生成的析构函数](#4. 默认生成的析构函数)
- [5. 显式写的析构函数](#5. 显式写的析构函数)
- [6. 资源申请与资源泄露](#6. 资源申请与资源泄露)
- [7. 析构顺序:后定义的先析构](#7. 析构顺序:后定义的先析构)
- 拷贝构造函数
-
- [1. 声明形式](#1. 声明形式)
- [2. 拷贝构造函数的触发时机](#2. 拷贝构造函数的触发时机)
- [3. 浅拷贝 vs 深拷贝](#3. 浅拷贝 vs 深拷贝)
- [4. 传值返回 vs 引用返回](#4. 传值返回 vs 引用返回)
- [💻结尾--- 核心连接协议](#💻结尾— 核心连接协议)
为什么会有 默认成员函数 ?
在 C++ 中,当你定义一个"空类"时,它并不是真正的空。
编译器会为你自动生成一套默认成员函数
六大默认成员函数
为了演示,我们假设全篇代码都包含以下头文件:
cpp
#include <iostream>
#include <cstring> //用于 strlen, strcpy
#include <algorithm> // 用于 swap
using namespace std; // 全局引入命名空间
-
重点: 前
4个 (构造、析构、拷贝构造、赋值重载) 是最重要的,必须熟练掌握。 -
次要: 后
2个 (取地址重载) 通常不需要理会
C++ 6个默认成员函数
功能分类
初始化和清理
拷贝复制
取地址重载
构造函数
主要完成初始化工作
析构函数
主要完成清理工作
拷贝构造函数
使用同类对象初始化创建对象
赋值运算符重载
把一个对象赋值给另一个对象
取地址运算符重载
普通对象取地址
取地址运算符重载
const对象取地址
注释:这两个很少会自己实现

构造函数
构造函数是特殊的成员函数
需要注意的是,构造函数虽然名字叫构造,但其主要任务并不是开空间创建对象(我们常使用的局部对象在栈帧创建时,空间就已经开好了),而是在对象实例化时初始化对象。
构造函数的本质是为了替代我们以前在 Stack 和 Date 类中编写的 Init 函数的功能;构造函数自动调用的特点完美地替代了 Init ,从而避免了忘记初始化带来的风险。
cpp
class ClassName {
public:
ClassName(); // 构造函数声明
};
ClassName::ClassName() {
// 构造函数定义
}
1. 函数名与类名相同
cpp
//函数名与类名相同
class Date {
public:
Date() {}
private:
int _year;
int _month;
int _day;
};
2. 无返回值(也不写 void)
cpp
class Date {
public:
//无返回值
Date() {}
private:
int _year;
int _month;
int _day;
};
3. 实例化时自动调用
cpp
#include <iostream>
using namespace std;
//函数名与类名相同
class Date {
public:
//无返回值
Date() {}
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
cout << "-> 自动调用了带参构造函数" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
cout << "1. 准备创建 d1:" << endl;
Date d1; //实例化 d1 ,自动调用无参构造
cout << "2. 准备创建 d2:" << endl;
Date d2(2025, 1, 22); // 实例化 d2,自动调用带参构造
return 0;
}
4. 可以重载(可以有多个)
cpp
#include <iostream>
using namespace std;
class Date {
public:
// 【特点1 & 2】: 函数名是Date,前面没有 void,也没有返回值
// 【特点4】: 这是第一个构造函数(无参)
Date() {
_year = 1900;
_month = 1;
_day = 1;
// 【特点3】: 只要对象创建,这句话就会打印,证明它被自动调用了
cout << "-> 自动调用了无参构造函数" << endl;
}
// 【特点4】: 这是第二个构造函数(带参),体现了函数重载
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
cout << "-> 自动调用了带参构造函数" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
cout << "1. 准备创建 d1:" << endl;
Date d1; // 实例化 d1,自动调用无参构造
cout << "2. 准备创建 d2:" << endl;
Date d2(2025, 1, 22); // 实例化 d2,自动调用带参构造
return 0;
}
5. 编译器默认生成
如果类中没有显式定义,编译器生成默认的;一旦用户定义了,编译器就不再生成
cpp
class A {
// 里面什么都没写
// 编译器会生成一个 A() { ... }
};
class B {
public:
// 用户显式定义了一个带参构造
B(int x) {
cout << "B的构造" << endl;
}
// 此时,编译器不再生成无参的 B()
};
int main() {
A a; // ✅ 编译通过:调用了编译器自动生成的默认构造
// B b; // ❌ 编译报错!
// 错误原因:类 B 中已经有了一个 B(int),编译器就不送你 B() 了。
// 你必须要么手动写一个 B(),要么像下面这样传参:
B b(10); // ✅ 编译通过
return 0;
}
6. 默认构造函数的"三合一"与歧义
无参构造、全缺省构造、编译器 生成的构造,都叫"默认构造函数"(不用传参就能调用的)
但它们只能存在一个,否则会有歧义
cpp
#include <iostream>
using namespace std;
class Date {
public:
// 1. 无参构造函数
Date() {
cout << "无参构造" << endl;
}
// 2. 全缺省构造函数
// 理论上它也是"默认构造函数",因为可以不传参调用
Date(int year = 1, int month = 1, int day = 1) {
cout << "全缺省构造" << endl;
}
};
int main() {
Date d;
return 0;
}
❌ 编译报错
编译器懵了:你是指调用第1个不需要参数的?还是调用第2个使用默认参数的?
C++ 规则
- 一个类只能有一个"默认构造函数"
- "默认构造函数"指:可以不传递任何参数就能调用的构造函数
- 当类中同时存在无参构造函数 和全缺省构造函数时,编译器无法区分该调用哪一个
7. 内置类型 vs 自定义类型
编译器默认生成的构造函数:
-
对内置类型(
int,char,指针): 不处理(值是随机的)。 -
对自定义类型(
class,struct): 自动调用那个成员的默认构造函数
cpp
#include <iostream>
using namespace std;
// 自定义类型
class Stack {
public:
Stack() {
cout << "Stack 的构造函数被调用了(初始化栈)" << endl;
_capacity = 4; // 假设初始化容量
}
private:
int _capacity;
};
// 包含两种成员的类
class MyQueue {
public:
// ❌ 我们这里故意不写构造函数
// 测试编译器默认生成的构造函数会做什么
void Print() {
cout << "内置类型 _size 的值: " << _size << endl;
cout << "内置类型 _ptr 的值: " << _ptr << endl;
}
private:
// 1. 内置类型
int _size;
int* _ptr;
// 2. 自定义类型
Stack _st;
};
int main() {
MyQueue q;
// 观察结果:
// 1. Stack 的构造函数会被打印出来 -> 证明自定义类型被处理了。
// 2. _size 和 _ptr 的值通常是乱码(随机值) -> 证明内置类型没被处理。
q.Print();
return 0;
}
A. 对内置类型(int_size, int*_ptr)
- 行为: 不做处理(或者说初始化是不确定的,看编译器)。
- 结果: 这就是为什么你在 Print() 函数中看到的是随机值(乱码)。编译器只是分配了内存,但没有往里面填入具体的 0 或 nullptr。
B. 对自定义类型(Stack_st)
- 行为: 要求调用这个成员变量的默认构造函数初始化。
- 结果: 编译器生成的 MyQueue 构造函数内部会隐式调用 Stack()。这就是为什么你的屏幕上会打印出 "Stack 的构造函数被调用了"。
析构函数
🚩析构函数与构造函数功能相反
析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,它就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作
析构函数的功能 类比 我们之前
Stack实现Destroy功能,而像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的
1. 命名规则
- 知识点: 析构函数名是在类名前加上字符
~ - 解析:
~(取反符号)在逻辑上暗示了它与构造函数(创建)相反的操作(销毁) - 补充: 它是类的成员函数,且名称必须与类名完全一致
cpp
class Student {
public:
// 构造函数
Student() {
cout << "构造函数被调用" << endl;
}
~Student() {
cout << "析构函数被调用" << endl;
}
};
2. 一个类只能有一个构造函数
注意: 一个类只能有一个析构函数
若未显式定义,系统会自动生成默认的
cpp
class MyClass {
public:
~MyClass() {
// 合法的析构函数
}
// [错误示范]
// 析构函数不支持重载,因为它没有参数,编译器无法区分
// ~MyClass(int a) { } // 编译报错:destructor cannot have any parameters
};
解析: 构造函数可以有多个(重载),但因为析构函数没有参数,所以无法重载,只能存在一个。如果你不写,编译器会在后台默默生成一个空的 ~MyClass() {}
3. 对象生命周期结束时自动调用
cpp
#include <iostream>
using namespace std;
class Test {
public:
~Test() {
cout << "对象正在被销毁..." << endl;
}
};
int main() {
cout << "主函数开始" << endl;
{ // 创建一个局部作用域
Test t;
cout << "对象 t 在作用域内活着" << endl;
} // [重点] 出了这个右大括号,t 的生命周期结束,系统自动调用 ~Test()
cout << "主函数结束" << endl;
return 0;
}
输出结果:
-
主函数开始
-
对象 t 在作用域内活着
-
对象正在被销毁... (自动调用)
-
主函数结束
4. 默认生成的析构函数
对内置类型(如 int)不做处理,对自定义类型成员会调用它的析构
cpp
#include <iostream>
using namespace std;
// 自定义类型
class Custom {
public:
Custom() { cout << "Custom构造" << endl; }
~Custom() { cout << "Custom析构" << endl; }
};
// 测试类
class Test {
public:
Test() { cout << "Test构造" << endl; }
~Test() { cout << "Test析构" << endl; }
// 析构函数结束后,编译器自动处理成员:
// - 对内置类型(int,指针等):什么都不做
// - 对自定义类型(custom):自动调用其析构函数
private:
int num; // 内置类型 - 析构时什么也不做
Custom obj; // 自定义类型 - 自动调用~Custom()
};
int main() {
Test t; // 构造
return 0;
}
t离开作用域,调用~Test():
-
执行析构函数体(打印"
Test析构") -
编译器自动处理成员:
num: 什么都不做obj: 自动调用~Custom()
输出:

5. 显式写的析构函数
即使你手动写了析构函数,函数体执行完后,自定义类型成员的析构函数依然会被自动调用
cpp
#include <iostream>
using namespace std;
class Member {
public:
~Member() {
cout << "Member析构" << endl;
}
};
class MyClass {
public:
MyClass() {
cout << "MyClass构造" << endl;
}
// 手动写的析构函数
~MyClass() {
cout << "MyClass析构函数体开始" << endl;
// 做一些清理工作...
cout << "MyClass析构函数体结束" << endl;
// 函数体执行完后,编译器会自动调用所有自定义类型成员的析构函数
}
private:
Member m; // 自定义类型成员
};
int main() {
MyClass obj;
return 0;
}

6. 资源申请与资源泄露
- 无资源申请 (如
Date):可不写析构,用默认的
cpp
class Date {
private:
int year;
int month;
int day;
// 成员全是内置类型,且没有动态申请内存(new/malloc)
// 使用编译器默认生成的析构函数即可,什么都不用做
};
- 有资源申请 (如
Stack):必须写析构释放资源,否则内存泄漏
cpp
#include <iostream>
class Stack {
private:
int* _array; // 指针,指向堆内存
int _capacity;
int _top;
public:
Stack(int capacity = 4) {
_capacity = capacity;
// [资源申请] 在堆上动态开辟了空间
_array = new int[capacity];
}
// [重点 7] 必须显式写析构函数来释放资源
~Stack() {
if (_array) {
delete[] _array; // 归还操作系统内存
_array = nullptr;
std::cout << "Stack 内存已释放,防止泄漏" << std::endl;
}
}
};
解析: 如果 Stack 类不写析构函数,默认析构函数只会销毁 _array 这个指针变量本身(占据8字节),而指针指向的那一大块堆内存(比如 100MB)将永远无法释放,造成内存泄漏
7. 析构顺序:后定义的先析构
知识点回顾: 局部域的多个对象,C++ 规定后定义的先析构 (栈的特性:先进后出)
cpp
#include <iostream>
using namespace std;
class Object {
int _id;
public:
Object(int id) : _id(id) {
cout << "对象 " << _id << " 被构造" << endl;
}
~Object() {
cout << "对象 " << _id << " 被析构" << endl;
}
};
int main() {
// 构造顺序:1 -> 2
Object o1(1);
Object o2(2);
return 0;
// [重点 8] 析构顺序将是:2 -> 1
}

拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数叫做拷贝构造函数
也就是说,拷贝构造函数是一个特殊的构造函数
1. 声明形式
cpp
class ClassName {
public:
// 拷贝构造函数的声明
ClassName(const ClassName& other);
};
-
同名构造:函数名必须与类名完全一致。
-
引用传递(必须) :参数必须是该类类型的引用(通常是
ClassName&)。- 为什么不能传值? 如果传值(
ClassName other),为了将实参传给形参,系统会再次调用拷贝构造函数,从而导致无限递归,直到栈溢出。
- 为什么不能传值? 如果传值(
-
常量限定(建议) :通常使用
const修饰。- 原因 :这保证了在拷贝过程中不会意外修改源对象,同时也允许拷贝
const对象(即右值或常量对象)
- 原因 :这保证了在拷贝过程中不会意外修改源对象,同时也允许拷贝
2. 拷贝构造函数的触发时机
在以下三种情况下,编译器会自动调用拷贝构造函数:
2.1)对象初始化
用一个对象去定义并初始化另一个对象
cpp
Person p1;
Person p2 = p1; // 调用拷贝构造
Person p3(p1); // 调用拷贝构造
2.2)对象作为参数传递
函数参数是对象而非引用时
cpp
void func(Person p) { ... }
2.3)对象作为返回值
函数返回一个局部对象
cpp
Person func() {
Person temp;
return temp; // 返回时调用
}
3. 浅拷贝 vs 深拷贝
典例代码演示
cpp
#include <iostream>
using namespace std;
class Date {
public:
// 1. 普通构造函数:申请堆内存
Date(int year = 1900) {
_yearPtr = new int(year); // 在堆区开辟空间存储 year
cout << "【构造】申请内存地址: " << _yearPtr << " 值: " << *_yearPtr << endl;
}
// 2. 深拷贝构造函数
Date(const Date& d) {
// 核心区别:
// 浅拷贝写法: _yearPtr = d._yearPtr; (两个指针指向同一块内存,危险!)
// 深拷贝写法: 重新申请一块新的内存
_yearPtr = new int(*d._yearPtr);
cout << "【深拷贝】申请新内存地址: " << _yearPtr << " (原地址: " << d._yearPtr << ")" << endl;
}
// 3. 析构函数:释放内存
~Date() {
if (_yearPtr != nullptr) {
cout << "【析构】释放内存地址: " << _yearPtr << endl;
delete _yearPtr;
_yearPtr = nullptr;
}
}
// 用于打印查看
void print() {
cout << "当前年份: " << *_yearPtr << endl;
}
// 成员变量改为指针
int* _yearPtr;
};
int main() {
cout << "--- 创建对象 d1 ---" << endl;
Date d1(2025);
cout << "\n--- 创建对象 d2 (深拷贝 d1) ---" << endl;
Date d2(d1); // 调用深拷贝构造
cout << "\n--- 验证独立性 ---" << endl;
// 修改 d2 的值
*d2._yearPtr = 2099;
cout << "d1 的年份 (应保持 2025): " << *d1._yearPtr << endl;
cout << "d2 的年份 (已改为 2099): " << *d2._yearPtr << endl;
cout << "\n--- 作用域结束,自动析构 ---" << endl;
return 0;
}
浅拷贝
如果你没有手动定义拷贝构造函数,编译器会生成一个默认拷贝构造函数
执行位拷贝,即简单地复制成员变量的值
- 风险: 如果类中包含指针并指向堆内存,浅拷贝会导致两个对象的指针指向同一个内存地址。当对象析构时,同一块内存会被释放两次,导致程序崩溃
cpp
String(const String& other) {
this->data = other.data; // 仅仅复制了地址!
}
使用时机
浅拷贝只复制对象的第一层属性
- 如果属性是基本类型(如数字、字符串) ,拷贝值
- 如果属性是引用类型(如对象、数组) ,拷贝的是内存地址(引用)
如果你创建副本只是为了在一个函数内部进行读取、计算,并且保证绝对不会修改内部的可变对象,浅拷贝就足够了
深拷贝
当类中包含指针成员或动态分配的资源时,必须手动编写拷贝构造函数,为新对象分配独立的内存空间,并复制内容
cpp
class String {
private:
char* data;
public:
// 构造函数
String(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 手写深拷贝构造函数
String(const String& other) {
// 1. 为新对象分配独立内存
data = new char[strlen(other.data) + 1];
// 2. 拷贝实际内容
strcpy(data, other.data);
}
~String() { delete[] data; }
};
如果你使用浅拷贝:
cpp
String(const String& other) {
data = other.data;
}
❌ 危险!两个指针指向同一块堆内存
使用时机
深拷贝会递归地复制对象及其所有子对象,在内存中创建一个完全独立的副本
场景: 将一个复杂的表单数据对象传递给"提交"函数,如果提交失败需要回滚,或者用户点击了"取消",你需要原始数据保持原样
为什么要手写 深拷贝构造函数 ?
如果使用默认的拷贝构造函数(浅拷贝) ,系统只会简单地复制指针的值,编译器生成的代码逻辑类似于:
cpp
// 编译器自动生成的默认拷贝构造函数(浅拷贝)
String(const String& other) {
this->data = other.data; // 仅仅复制了地址!
}
-
后果 A(数据篡改): 修改对象 A 的字符串,对象 B 的也跟着变了。
-
后果 B(双重释放崩溃): 当 A 和 B 析构时,会对同一个地址调用两次 delete\[\],导致程序直接崩溃(Double Free)
❗ 崩溃代码示例
cpp
#include <iostream>
#include <cstring>
class String {
public:
char* data; // 为了方便演示,暂时设为 public
String(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 注意:这里故意删除了手写的深拷贝构造函数,使用系统默认的
~String() {
delete[] data;
std::cout << "析构函数被调用,内存已释放" << std::endl;
}
};
int main() {
{
String s1("Hello");
String s2 = s1; // 触发默认拷贝构造函数(浅拷贝)
std::cout << "s1 地址: " << (void*)s1.data << std::endl;
std::cout << "s2 地址: " << (void*)s2.data << std::endl;
// 此时 s1.data 和 s2.data 指向同一个内存地址
}
// 作用域结束,s1 和 s2 都会调用析构函数
return 0;
}
运行了上面的代码后 发现程序出现了崩溃
过程拆解:
- 内存重叠 :
s2 = s1之后,s1.data和s2.data保存的是同一个内存地址(例如0x1234)。 - 第一次释放 :当
s2离开作用域时,析构函数执行delete[] data,地址0x1234处的内存被回收。 - 第二次释放(崩溃点) :紧接着
s1离开作用域,析构函数再次执行delete[] data。由于0x1234已经被释放过了,再次释放会导致运行时错误。 - 数据风险 :在析构之前,如果你修改
s1的内容,s2的内容也会莫名其妙地改变,因为它们本质上在看同一块内存。
补充:
char* strcpy(char* destination, const char* source);
- 将
source字符串(包括结尾的'\0')拷贝到destination指向的内存中
🔗Lucy的空间骇客裂缝:字符函数与字符串函数
4. 传值返回 vs 引用返回
假设我们要实现一个简单的 Date 类:
cpp
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 1900) : _year(year) {
// 普通构造
}
Date(const Date& d) {
_year = d._year;
cout << "【拷贝构造调用】: 生成副本" << endl;
}
~Date() {
// cout << "析构函数调用" << endl;
}
int _year;
};
传值返回
-
会产生一个临时对象,调用拷贝构造。
-
缺点: 对于大对象,拷贝代价大,效率低
cpp
// 场景:函数内部创建局部对象,并传值返回
Date TestByValue() {
Date d(2024);
return d; // 【重点】:这里会触发拷贝构造,生成临时对象
}
int main() {
cout << "--- 开始调用 TestByValue ---" << endl;
Date ret = TestByValue();
cout << "--- 调用结束 ---" << endl;
return 0;
}

引用返回
返回的是对象的别名,没有拷贝,效率高
cpp
// 场景:返回静态变量(生命周期贯穿整个程序)
Date& TestByRef() {
static Date d(2025); // 静态变量,出了函数不销毁
return d; // 返回 d 的别名,没有拷贝
}
int main() {
cout << "--- 开始调用 TestByRef ---" << endl;
Date& ret = TestByRef(); // ret 也是引用,直接指向静态区的 d
cout << "--- 调用结束 ---" << endl;
return 0;
}

❌ 错误用法示例
返回局部变量的引用
cpp
Date& BadRef() {
Date d(2026);
return d;
// 警告:函数结束,d 的栈帧销毁,d 的内存被系统回收
}
int main() {
Date& ret = BadRef();
// 此时 ret 指向的是一块已经被释放的内存(野引用)
// 这里的打印可能是随机值,或者程序直接崩溃
cout << ret._year << endl;
return 0;
}
下一章节我们将详细讲讲默认成员函数中的运算符重载💪
💻结尾--- 核心连接协议
警告: 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠
【📡】 建立深度链接: 关注本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。
【⚡】 能量过载分发: 执行点赞操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。
【💾】 离线缓存核心: 将本页加入收藏。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。
【💬】 协议加密解密: 在评论区留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。
【🛰️】 信号频率投票: 通过投票发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。

