单例模式
本课属于 Coddy C 学习旅程的“面向对象编程”单元。第 54 课,共 61 课。
有时,你需要在整个程序中只创建一个对象实例,例如配置管理器、日志系统或数据库连接。单例模式可确保只存在一个实例,并提供全局访问入口。
在 C 中,我们通过在源文件内使用 static 变量来实现这一点。请记住,在文件作用域中,static 表示该变量仅在此文件中可见,并且会在程序的整个生命周期内一直存在。再结合一个 getter 函数,就能创建单例:
// config.c
#include "config.h"
#include <stdlib.h>
struct Config {
int max_connections;
int timeout;
};
static Config* instance = NULL;
Config* get_config(void) {
if (instance == NULL) {
instance = malloc(sizeof(Config));
instance->max_connections = 100;
instance->timeout = 30;
}
return instance;
}神奇之处就在于 get_config()。第一次调用会创建并初始化实例。之后每次调用都会返回同一个指针。
static 关键字可确保 instance 在函数调用之间保留其值,并对其他文件保持隐藏。
从头文件中,用户只能看到获取函数。他们无法直接访问或修改静态变量。无论你从哪里调用 get_config(),也无论调用多少次,得到的始终是同一个对象。
挑战
简单让我们构建一个 Settings 模块,使用单例模式管理整个应用的配置。无论代码请求设置多少次,都应始终获得完全相同的实例。
你将把代码分成 3 个文件:
settings.h:使用前向声明声明不透明的Settings类型(结构体的定义保持隐藏)。声明一个get_settings函数,用于返回指向单例实例的指针;此外,还要为两个配置值声明获取函数和设置函数:volume(整数)和brightness(整数)。这里必须使用包含保护。settings.c:核心逻辑就在这里。定义实际的Settings结构体,其中包含volume和brightness字段。创建一个static指针变量来保存唯一实例(初始值为NULL)。实现get_settings,使其在首次调用时分配实例并使用默认值初始化(volume:50,brightness:75),之后的调用则只返回已有实例。实现获取函数和设置函数,以访问并修改隐藏字段。main.c:演示单例模式能否正常工作。读取两个整数,分别表示新的音量值和亮度值。调用两次get_settings,并将结果存入两个不同的指针变量。使用第一个指针,根据输入值更新音量和亮度。然后使用第二个指针获取并打印这两个值。如果单例模式正常工作,通过一个指针所做的修改就能通过另一个指针看到。
程序会接收两个输入:
- 新的音量值
- 新的亮度值
通过一个指针修改音量和亮度,再通过另一个指针读取这两个值,并将它们分别打印在单独的行上。
输入为 80 和 100 时的输出示例:
80
100输入为 25 和 60 时的输出示例:
25
60输入为 0 和 50 时的输出示例:
0
50关键在于,这两个指针都指向内存中的同一个对象,这正是单例模式的本质。源文件中的静态变量确保始终只存在一个实例,并且该实例会在整个程序的运行期间一直存在。
亲手试试
#include <stdio.h>
#include "settings.h"
int main() {
int new_volume, new_brightness;
scanf("%d", &new_volume);
scanf("%d", &new_brightness);
// TODO:调用 get_settings 两次,将结果分别存入两个不同的指针变量
// TODO:用第一个指针根据输入值更新音量和亮度
// TODO:用第二个指针获取并打印音量和亮度
// (这说明两个指针都指向同一个单例实例)
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C 在线编译器