前言
MinGW 安装后带 g++ 和 cmake 两个工具。
C:\Users\Administrator>cmake -version
cmake version 3.30.4
CMake suite maintained and supported by Kitware (kitware.com/cmake).
会从简单直接到复杂工程化。
一、基础测试
- 首先建立一个测试文件夹用作使用,查看文件
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>dir /b Log.cpp Log.h main.cpp
- 主函数文件中写好测试,编译运行
#include <iostream>
int main(){
std::cout << "Hello World" << std::endl;
return 0;
}
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>g++ main.cpp
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>a.exe
Hello World
- 此.cpp 文件经预处理,产生了.i 文件,即将头文件复制到当前文本文件中
- 编译.i 纯文本文件,编译器生成平台相关汇编代码文件.s
- 汇编.s 文件,转换为平台相关二进制机器码.o 文件
- 链接多个.o 文件,当编译了 LOG.h 和 LOG.cpp 文件中的 LOG.cpp 文件时,会生成 LOG.o 文件,而当主文件引用 LOG.h 文件,会在主.cpp 中做一个标记,名为重定位表,记录了连接的.o 文件需要插入哪个位置
- 编写 Log.h 和 Log.cpp 做一个测试
// Log.h
#ifndef LOG_H // 头文件保护,防止重复包含
#define LOG_H
#include <string>
class Log {
private:
std::string str;
public:
Log(const std::string& s = "Hello Cpp !");
void helloworld() const;
};
#endif
// Log.cpp
#include "Log.h"
#include <iostream>
// 构造函数的实现,使用初始化列表来初始化成员变量
Log::Log(const std::string& s):str(s){
// 初始化列表 : str(s) 完成了赋值
}
// 成员函数的实现,使用正确的类名限定符
void Log::helloworld() const{
// 注意是 Log::helloworld
std::cout << str << std::endl;
}
// main.cpp
#include <iostream>
#include "Log.h"
int main(){
Log log;
log.helloworld();
return 0;
}
对于代码细节不再赘述,都是 C++ 最佳性能和安全实践。编译运行
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>g++ main.cpp Log.cpp
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>a.exe
Hello Cpp !
- 以上测试流程跑通后,我们继续进行简易日志的编写
二、日志类的最小实现
1.特点
- 不同日志级别(DEBUG、INFO、WARN、ERROR)
- 输出到 std::cout(控制台),带时间戳和级别标签
- 使用 RAII 方式确保线程安全(这里使用 std::mutex 简单保护)
- 宏定义方便使用(类似常见日志库的风格)
2.Log.h头部文件的逐步实现
- 头文件引入
// Log.h
#ifndef LOG_H // 头文件保护,防止重复包含
#define LOG_H
#include <iostream>//标准输入输出
#include <sstream>//字符串流,用于构建时间
#include <string>//字符串
#include <mutex>//互斥锁
#include <chrono>//时间库,精确时间
#include <iomanip>//输出格式
#include <ctime>//c 风格时间函数
- 级别枚举
enum class LogLevel{ DEBUG, INFO, WARN, ERROR };
- 私有定义
private:
Log();// 私有构造函数
std::mutex mutex_;
LogLevel currentLevel_ = LogLevel::DEBUG;// 获取当前时间戳字符串
std::string getTimestamp()const;// 获取级别字符串
std::string getLevelString(LogLevel level)const;
- 公有接口
public:// 单例模式(可选,最小实现中也常用)
static Log&instance();// 设置日志级别(低于该级别的日志不会输出)
voidsetLevel(LogLevel level);// 日志输出函数
voidlog(LogLevel level,const std::string& message);// 便捷函数
voiddebug(const std::string& message);
voidinfo(const std::string& message);
voidwarn(const std::string& message);
voiderror(const std::string& message);
- 宏定义
// 常用宏(放在头文件中,方便使用时像 LOG_INFO("xxx") 一样写)
#define LOG_DEBUG(msg) Log::instance().debug(msg)
#define LOG_INFO(msg) Log::instance().info(msg)
#define LOG_WARN(msg) Log::instance().warn(msg)
#define LOG_ERROR(msg) Log::instance().error(msg)
3.Log.cpp的逐步实现
- 单例构造函数
- 设置日志级别(生产环境和开发环境区分开)
- 获取当前时间
- 获取日志级别字符串
- 通用日志输出
// Log.cpp
#include "Log.h"
Log& Log::instance(){
static Log log;
return log;
}
Log::Log()=default;
void Log::setLevel(LogLevel level){
std::lock_guard<std::mutex> lock(mutex_);
currentLevel_ = level;
}
std::string Log::getTimestamp() const{
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())%1000;
std::stringstream ss;
ss << std::put_time(std::localtime(&time_t),"%Y-%m-%d %H:%M:%S");
ss <<'.'<< std::setfill('0')<< std::setw(3)<< ms.count();
return ss.str();
}
std::string Log::getLevelString(LogLevel level) const{
switch(level){
case LogLevel::DEBUG:return"DEBUG";
case LogLevel::INFO:;
LogLevel::WARN:;
LogLevel::ERROR:;
:;
}
}
{
;
(level < currentLevel_){
;
}
std::cout <<<<()<<<<(level)<<<< message << std::endl;
}
{(LogLevel::DEBUG, message);}
{(LogLevel::INFO, message);}
{(LogLevel::WARN, message);}
{(LogLevel::ERROR, message);}
编译运行测试:
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>g++ main.cpp Log.cpp
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>a.exe
[2026-01-05 16:26:55.196] INFO: The program has been successfully started
[2026-01-05 16:26:55.197] WARN: Memory usage is high
[2026-01-05 16:26:55.197] ERROR: A serious error has occurred!
4.CMake工具的使用
此时产生了一个问题,如果有新的.cpp 文件开发,那么就需要单独编译甚至重新编译,所以推荐使用构建工具 cmake,cmake 的使用也非常简单,首先编写一个 CMakeLists.txt
# CMakeLists.txt
cmake_minimum_required(VERSION 3.30)
# 建议使用较新版本
project(Log VERSION 1.0.0 DESCRIPTION "一个简单的日志系统示例" LANGUAGES CXX )
# 设置 C++ 标准(推荐 C++17 或 C++20)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 纯标准,不使用 GNU 扩展
# 创建可执行文件
add_executable(${PROJECT_NAME} main.cpp Log.cpp Log.h # 写上让 IDE(如 CLion、VSCode)正确识别头文件)
随后打开 cmd 命令行,逐行执行:
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>mkdir build
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG>cd build
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG\build>cmake ..
-- Building for: Ninja
-- The CXX compiler identification is GNU 14.2.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: C:/Program Files/mingw-w64/mingw64/bin/c++.exe - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done(0.8s)
-- Generating done(0.0s)
-- Build files have been written to: C:/Users/Administrator/Desktop/开发测试项目/MyCpp/LOG/build
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG\build>cmake --build .
[3/3] Linking CXX executable Log.exe
C:\Users\Administrator\Desktop\开发测试项目\MyCpp\LOG\build>Log.exe
[2026-01-05 16:24:14.510] INFO: The program has been successfully started
[2026-01-05 16:24:14.510] WARN: Memory usage is high
[2026-01-05 16:24:14.511] ERROR: A serious error has occurred!
总结
本文首先编写了简单的 C++ 多文件程序,捋清了编译连接流程。随后编写了一个非常通用的最简化日志系统作为演示,最后使用 CMake 工具演示了如何做现代构建,在大量多文件编译时,可以方便管理依赖。
最简化日志系统的优点
- 符合现代 C++ 规范(C++11/14/17)
- 头文件包含防护宏
- 单例模式(Meyers' Singleton,线程安全)
- 使用 RAII 锁保护输出
- 提供宏方便调用
- 代码结构清晰、可扩展(后续可轻易添加文件输出、颜色等功能)
可扩展角度
- 日志输出到文件中,并实现轮转
- 加颜色输出
- 异步非阻塞日志

