用 C 工程标准实现几个简单的 C 语言项目。

前言

因为转过专业的原因,有一门 C 语言实训还没有补,学弟帮我找了个老师的实训,跟完后感觉或许是我校为数不多的优质实训之一。

可以锻炼 make、google test、第三方库引入、debug 的应用能力,这是对低年级学生的珍贵礼物。

可以意识到项目的模块划分是一个噩梦,可以线上学习经典的软件工程课程: MIT 6.092 (20h) + MIT 6.031(130h) 。

C 工程标准实现一个 windows 时钟显示器

核心流程

热身项目,我们看一下它的核心流程

main() → 初始化 (srand)
   ↓
进入主循环
   ├── clear_screen() 清屏
   ├── get_current_time() 获取当前系统时间
   │   └── GetLocalTime / snprintf 生成时间字符串 "HH:MM:SS"
   ├── display_time(time_str) 显示时间
   │   └── 遍历时间字符串的每个字符 c
   │       └── get_font(c) 获取数字点阵
   │           └── putchar 逐行打印
   ├── Sleep(1000) 等待 1 秒
   └── 循环

模块划分

这个项目最困难点在于模块划分。我们可以基于 display.c 划分模块,显示器模块只负责现实的基本工作。对于整个项目,拆分掉显示器的工作后,剩下一个字符模块和时钟模块是合理的,我们这里以划分出 clock.c 和 font.c 模块。

这里我们隐藏具体实现,只暴露出最核心的功能。功能划分如下

时钟模块:时钟模块的核心功能为获取系统时间,并以 "HH:MM:SS" 字符长的格式返回。

#ifndef CLOCK_H
#define CLOCK_H

// 获取当前系统时间,格式为 "HH:MM:SS"
// 返回值:指向静态缓冲区的指针,每次调用会覆盖之前的内容
const char* get_current_time(void);

#endif

字体模块:核心功能为,根据传入的字符,返回改字符对应的点整。

#ifndef FONT_H
#define FONT_H

// 点阵尺寸
#define FONT_ROWS 5
#define FONT_COLS 3

// 获取字符对应的点阵:1 表示亮,0 表示暗
// 支持 '0'~'9' 和 ':'
const int (*get_font(char))[FONT_COLS];

#endif

显示模块:负责显示前的清屏,接受时间字符串并进行具体绘制

#ifndef DISPLAY_H
#define DISPLAY_H

// 清屏
void clear_screen(void);

// 在屏幕上绘制时间字符串(格式 "HH:MM:SS")
void display_time(const char* time_str);

#endif

测试

虽然要求项目本体严格使用 C 语言,但是并没有限制测试工具的使用。这里我们使用 C++ 测试工具 Google Test ,下载到本地第三方 third_party 中。

需要提供一下 C 对 C++ 的接口头文件

#ifndef C_API_H
#define C_API_H

extern "C" {
#include "font.h"
#include "clock.h"
}

#endif

测试是一个项目中非常重要的一块,和模块划分一样,可以实现得不优美,但不能没有。

如何写出具体的测试也是非常伤脑筋的事情,这依然提示着我们学习软将工程的必要性。

比如我在这里实现了常见的边界测试、指针测试、格式测试,还有各种不知道有没有意义的测试。

#include <gtest/gtest.h>
#include <cstring>
#include <cctype>
#include "c_api.h"

// 测试获取时间返回非空指针
TEST(ClockTest, ReturnsNonNull) {
    EXPECT_NE(get_current_time(), nullptr);
}

// 测试时间字符串长度为 8(HH:MM:SS)
TEST(ClockTest, LengthIs8) {
    const char* t = get_current_time();
    ASSERT_NE(t, nullptr);
    EXPECT_EQ(std::strlen(t), 8u);
}

// 测试格式:字符应为数字或冒号,且冒号在正确位置
TEST(ClockTest, FormatCorrect) {
    const char* t = get_current_time();
    ASSERT_NE(t, nullptr);
    for (int i = 0; i < 8; ++i) {
        if (i == 2 || i == 5) {
            EXPECT_EQ(t[i], ':');
        } else {
            EXPECT_TRUE(std::isdigit(t[i])) << "Expected digit at position " << i;
        }
    }
}

Makefile

Makefile 本身并没有多大的值得说明的,只是一些基础的 dirtywork ,熟悉 g++、gcc 的命令,make 本身的语法规则,项目从从编译开始到最后链接成一个可执行文件的基础流程。

额外需要稍微深刻地了解一些静态链接(这里只需要用到静态链接)。把各个模块单独编译成可重定位的目标文件,然后链接(静态链接)成可执行文件。

后面的项目也大同小异。

# ============================================================
#  点阵时钟 (Dot Matrix Clock) - Makefile
#  使用 MinGW-w64 在 Windows 上编译本项目。
#
#  主要目标:
#    all           - 编译主程序和测试程序(默认)
#    clean         - 删除所有编译产物和可执行文件
#
#  可执行文件输出到 bin/ 目录。
# ============================================================

# 编译器设置
CC      = gcc          # C 编译器
CXX     = g++          # C++ 编译器

# 编译选项
# -g : 生成调试信息
# -Wall : 启用所有常用警告
CFLAGS   = -g -Wall
CXXFLAGS = -g -Wall

# 头文件搜索路径
# -Isrc : 项目源码目录
# -Itests : 测试代码目录
# -Ithird_party/gtest/... : Google Test 头文件路径
INCLUDES = -Isrc \
           -Itests \
           -Ithird_party/gtest/googletest/include \
           -Ithird_party/gtest/googletest

# 最终可执行文件(输出到 bin/ 目录)
TARGET      = bin/dot_clock.exe     # 主程序
TEST_TARGET = bin/font_test.exe     # 测试程序(包含 font 和 clock 测试)

# 主程序依赖的目标文件
MAIN_OBJS = src/main.o \
            src/font.o \
            src/clock.o \
            src/display.o

# 测试程序依赖的目标文件
TEST_OBJS = src/font.o \
            src/clock.o \
            src/display.o \
            tests/font_test.o \
            tests/clock_test.o \
            third_party/gtest/googletest/src/gtest-all.o \
            third_party/gtest/googletest/src/gtest_main.o

# ============================================================
# 默认目标:编译主程序和测试程序
# ============================================================
all: $(TARGET) $(TEST_TARGET)

# ============================================================
# 链接主程序
# 依赖所有主程序目标文件,链接成可执行文件
# ============================================================
$(TARGET): $(MAIN_OBJS)
	$(CC) $(CFLAGS) -o $@ $^

# ============================================================
# 链接测试程序
# ============================================================
$(TEST_TARGET): $(TEST_OBJS)
	$(CXX) $(CXXFLAGS) -o $@ $^

# ============================================================
# 各模块的编译规则(显式指定头文件依赖)
# ============================================================
src/main.o: src/main.c
	$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

src/clock.o: src/clock.c src/clock.h
	$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

src/font.o: src/font.c src/font.h
	$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

src/display.o: src/display.c src/display.h
	$(CC) $(CFLAGS) $(INCLUDES) -c -o $@ $<

tests/font_test.o: tests/font_test.cpp
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $<

tests/clock_test.o: tests/clock_test.cpp
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $<

third_party/gtest/googletest/src/gtest-all.o: third_party/gtest/googletest/src/gtest-all.cc
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $<

third_party/gtest/googletest/src/gtest_main.o: third_party/gtest/googletest/src/gtest_main.cc
	$(CXX) $(CXXFLAGS) $(INCLUDES) -c -o $@ $<

# ============================================================
# 清理规则:删除所有编译产物和可执行文件
# 使用 Windows 反斜杠作为路径分隔符
# ============================================================
clean:
	del /Q src\main.o \
	       src\font.o \
	       src\clock.o \
	       src\display.o \
	       tests\font_test.o \
	       tests\clock_test.o \
	       third_party\gtest\googletest\src\gtest-all.o \
	       third_party\gtest\googletest\src\gtest_main.o \
	       bin\dot_clock.exe \
	       bin\font_test.exe

项目结构

补上项目的 LISENCE、README.md 文件对整个项目起到一个基本的解释和说明,之后一个 C 语言项目就这样结束了。

作业一/
├── src/                 # 源代码
│   ├── main.c           # 程序入口,主循环
│   ├── clock.c/clock.h  # 获取系统时间
│   ├── display.c/display.h # 清屏与绘制点阵
│   └── font.c/font.h    # 数字点阵定义
├── tests/               # 测试代码
│   ├── c_api.h          # 包裹头,统一引入 C 模块
│   ├── font_test.cpp    # font 模块测试
│   └── clock_test.cpp   # clock 模块测试
├── third_party/gtest/   # 本地 Google Test 源码
├── Makefile             # 构建规则
├── README.md
└── LICENSE

C 工程标准实现一个俄罗斯方块小游戏

posted @ 2026-07-18 16:30  03Goose  阅读(9)  评论(0)    收藏  举报
📑 文章目录