Menu
CoddyTech

单例模式

本课属于 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(),也无论调用多少次,得到的始终是同一个对象。

challenge icon

挑战

简单

让我们构建一个 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,并将结果存入两个不同的指针变量。使用第一个指针,根据输入值更新音量和亮度。然后使用第二个指针获取并打印这两个值。如果单例模式正常工作,通过一个指针所做的修改就能通过另一个指针看到。

程序会接收两个输入:

  1. 新的音量值
  2. 新的亮度值

通过一个指针修改音量和亮度,再通过另一个指针读取这两个值,并将它们分别打印在单独的行上。

输入为 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;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C 在线编译器