本篇 核心知识:构造函数、析构函数、拷贝构造、浅拷贝与深拷贝、断言、静态成员、单例模式
一、构造函数(Constructor)
概念
构造函数是类的特殊成员函数 ,创建对象时自动调用 ,用于初始化对象成员。
特性
-
语法规则:
-
函数名与类名完全相同。
-
无返回值(连 void 都没有),不能返回值。
-
创建对象时自动调用 ,无需手动调用,一定会调用构造函数。当类中没有显式定义构造函数,会有默认构造函数
类名(形参表){ //参数表可以自定义参数类型、个数,可以重载,通过实参确定调用的函数 函数体; }
-
-
默认构造函数:
-
编译器自动生成,无参数、空函数体。
样式:
类名(){} -
自定义构造后(有显式定义构造函数),默认构造自动消失。
-
-
构造函数重载:
-
支持多个构造,参数个数 / 类型不同。
-
包括:无参构造、有参构造、拷贝构造。
-
-
初始化顺序 :成员定义顺序决定初始化顺序,与初始化列表顺序无关。
代码示例
#include <iostream>
using namespace std;
class Person {
char name[20];
int age;
public:
// 无参构造
Person() {
strcpy(name, "未知");
age = 0;
}
// 有参构造
Person(const char* n, int a) {
strcpy(name, n);
age = a;
}
};
int main() {
Person p1; // 调用无参构造
Person p2("XXX", 20); // 调用有参构造
// Person p3 = {"XXX", 18}; //这种写法只能用在所有数据成员都是公有的
return 0;
}
拓展:初始化列表
-
构造函数可通过初始化列表初始化成员,效率更高,尤其适合 const / 引用成员。
Person(const char* n, int a) : age(a) {
// age(a) 前面是成员,括号内是参数,如果参数名相同可以不用this区分
strcpy(name, n);
}
二、析构函数(Destructor)
概念
析构函数是类的特殊成员函数 ,对象生命周期结束时自动调用 ,用于释放资源、清理内存。
特性
-
语法规则:
-
函数名:~ 类名(~是函数名的一部分)。
-
无返回类型和返回值、形参表必须是空的(不能重载)。
-
-
调用时机:
-
栈区对象:离开作用域自动调用
-
堆区对象(new创建):
delete时调用。 -
生命周期结束时自动调析构。(因果关系不是调用了析构就结束生命周期)
-
-
默认析构 :如果没有显式析构则编译器自动生成,空函数体。
-
执行顺序 :先构造、后析构 ;先创建后析构、后创建先析构。
代码示例
class Person {
public:
Person() { cout << "构造" << endl; }
~Person() { cout << "析构" << endl; }
};
int main() {
Person p; // 栈区创建
Person *p1 = new Person(); // 堆区创建
delete p1; // 堆区析构
return 0; // 栈区析构
}
拓展:析构与生命周期
-
析构是生命周期结束的善后操作,不是结束原因。
-
手动调用析构不结束对象,仅执行清理逻辑。
三、拷贝构造函数(Copy Constructor)
概念
拷贝构造是特殊构造函数 ,用已有对象初始化新对象。
特殊在首个显式定义的参数是当前类的引用类型
特性
-
语法 :
类名(const 类名& 别名),必须是 const 引用参数保护实参。 -
调用场景:
-
(1)用一个现有的对象初始化新对象 (隐式)
-
(2)用一个现有的对象创建另一个对象 (显式)
-
函数参数为当前类的对象(值传递)。
-
函数返回值为当前类的对象(拷贝值返回,return后出函数有析构函数会直接析构,流程:构造-拷贝-析构-析构)。
-
-
默认拷贝 :编译器自动生成,逐字节浅拷贝。
代码示例
class Person {
int age;
public:
Person(int a) : age(a) {}
// 拷贝构造(参数是当前类的引用类型)
Person(const Person &ref) { // 必须用引用 给形参分配临时内容,把实参拷贝给形参
// 也可以用初始化列表,当有成员必须初始化时(const int age)必须写初始化列表 :age(ref.age)
age = ref.age;
cout << "拷贝构造" << endl;
}
};
int main() {
Person p1(20);
Person p2 = p1; // 调用隐式拷贝构造
Person p3(p1); // 调用显式拷贝构造
// 注意
Person p4(0); //构造
Person p5; //构造
p5 = p4; // 这里是赋值函数,不是拷贝构造
}
相似概念:值传递 vs 引用传递
- 拷贝构造必须用引用 ,否则会无限递归(传值→拷贝→传值...)。
四、浅拷贝与深拷贝
概念
-
浅拷贝 :默认拷贝,一一给成员值,指针共享同一块内存。
-
深拷贝 :手动实现,指针重新申请内存、复制数据,各自独立。
特性
-
浅拷贝问题:
-
多个对象共享同一块堆内存。
-
析构时重复释放内存,触发断言错误。
-
-
深拷贝实现:
-
拷贝构造中为指针重新申请内存。
-
逐元素复制原数据。
-
代码示例
class Array {
public:
int* data;
int size;
public:
// 构造函数
Array(int size = 0) {
this->size = size;
data = new int[s]; // 申请内存
}
// 浅拷贝 一一对应给值
Array(const Array& other) {
this->size = other.size;
this->data = other.data; // 指针不能直接拷贝指向,会共用内存
}
// 深拷贝 先申请新的内存 再把原内存中的数据拷贝到新内存
Array(const Array& other) {
this->size = other.size;
data = new int[size]; // 新内存
// 拷贝方法一
memcpy(this->data, other.data, sizeof(int)*size);
// 拷贝方法二
for(int i=0; i<size; i++)
data[i] = other.data[i]; // 在新内存中循环赋值
}
~Array() {
delete[] data; // 释放指针内存
data = nullptr;
}
};
拓展:何时用深拷贝
- 类包含指针 / 动态内存成员 时,必须手动深拷贝。
五、断言(assert)
概念
断言是调试宏 ,用于检查条件是否为真,假则终止程序并报错。
特性
-
头文件 :
<assert.h>。 -
语法 :
assert(条件)。 -
作用 :调试阶段校验逻辑合法性(如指针非空、下标合法)。
-
发布模式 :定义
NDEBUG可关闭断言。
代码示例
#include <assert.h>
int main() {
int a = 5;
assert(a > 0); // 条件真,继续
assert(a < 0); // 条件假,终止程序,报错
return 0;
}
拓展:断言 vs if
-
断言:调试用 ,发布可关闭,用于内部逻辑校验。
-
if:正式逻辑 ,不可关闭,用于正常业务判断。
六、静态成员(static)
概念
静态成员属于整个类 ,所有对象共享 ,生命周期为程序全程。
特性
1. 静态成员变量
-
定义:
static 类型 变量名;(类内声明)。 -
初始化:必须在类外全局区初始化 (
类型 类名::变量名 = 值;)。 -
访问:类名::变量名 或 对象访问。
-
特点:所有对象共享同一份数据。
2. 静态成员函数
-
定义:
static 返回值 函数名(参数);。 -
访问:类名::函数名 ()。
-
特点:无 this 指针 ,访问不了普通数据成员,只能访问静态成员。
代码示例
class Student {
public:
static int count; // 静态变量
static void show() { // 静态函数
cout << "学生数:" << count << endl;
}
};
// 类外初始化
int Student::count = 0;
int main() {
Student::count = 10;
Student::show();
return 0;
}
拓展:静态成员用途
- 统计对象个数、全局配置、工具函数。
七、单例模式(Singleton)
概念
单例模式确保类只有一个实例,全局共享,常用于管理类、工具类。
特性
-
核心步骤:
-
私有构造:限制构造的使用,提供一个静态函数禁止外部创建对象。
-
静态实例:类内静态指针 / 对象。
-
静态获取函数:返回唯一实例。
-
-
两种实现:
-
饿汉模式:程序启动即创建实例。(不管是否需要,先创建并且初始化)
-
优点:简单、线程安全、没有并发问题
-
缺点:程序启动就占内存(如果一直不用就浪费)
-
-
懒汉模式:首次调用时创建实例。(等到需要使用的时候再创建)
-
优点:节省内存、用到才创建
-
缺点:多线程不安全,必须加锁(C++11 之后有最简单写法)
-
-
代码示例
// 饿汉模式
class Singleton {
private:
static Singleton instance; // 声明静态成员变量:属于整个类,唯一的单例对象
Singleton() {} // 构造函数私有化,禁止外部直接创建对象(保证唯一)
public:
static Singleton& getInstance() { // 公共静态接口:给外部获取唯一实例的入口
return instance; // 返回已经创建好的唯一实例
}
};
Singleton Singleton::instance; // 类外全局初始化(程序启动就创建)
// 懒汉模式
class LazySingleton {
private:
static LazySingleton* instance; // 静态指针:保存唯一实例的地址。初始为 nullptr,还没创建对象
LazySingleton() {} // 构造函数私有化 禁止外部 new 对象,保证只能内部创建
public:
static LazySingleton* getInstance() { // 获取唯一实例的静态接口
if(!instance) // 判断:如果还没创建实例
instance = new LazySingleton(); // 第一次调用才创建
return instance;
}
};
LazySingleton* LazySingleton::instance = nullptr; // 类外初始化静态指针,一开始 = nullptr,还没有创建对象
拓展:单例注意
需私有或禁用拷贝构造和赋值,防止拷贝生成新实例。