零基础C++(36) 完结-封装继承多态

C++ 静态库与动态库详解及 CMake 构建指南

一、引言

在软件开发中,**库(Library)**是指一组预先编写好的、可复用的代码模块,旨在简化开发过程、提高代码复用性和维护性。库本质上是一种可执行代码的二进制形式,可以被操作系统载入内存执行。根据链接方式的不同,库主要分为**静态库(Static Library)**和**动态库(Dynamic Library)**。

二、库的基本概念

1. 静态库(Static Library)

  • 文件后缀
    • Windows 下:.lib
    • Unix/Linux 下:.a
  • 特点
    • 编译时链接:在编译过程中,静态库的代码会被复制并链接到最终的可执行文件中。
    • 独立性:生成的可执行文件不依赖于外部库文件,运行时无需额外的库支持。
    • 体积较大:因为库代码被嵌入到每个使用它的可执行文件中,导致可执行文件体积增加。
    • 版本一致性:不同项目使用的静态库版本是固定的,更新库后需重新编译依赖的可执行文件以应用新版本。

2. 动态库(Dynamic Library)

  • 文件后缀
    • Windows 下:.dll
    • Unix/Linux 下:.so
  • 特点
    • 运行时链接:动态库在程序运行时加载,库代码不被嵌入到可执行文件中。
    • 共享性:多个可执行文件可以共享同一个动态库,节省内存和存储空间。
    • 灵活性:更新动态库后,所有依赖该库的程序无需重新编译即可使用新版本。
    • 依赖性:运行时需要确保动态库文件存在,否则程序无法启动。

三、静态库与动态库的对比

特性 静态库(.lib/.a) 动态库(.dll/.so)
链接时间 编译时 运行时
文件大小 可执行文件较大 可执行文件较小,库单独存放
更新与维护 更新库需要重新编译所有依赖项 更新库无需重新编译,多个程序可共享库
内存使用 每个程序独立加载库代码 多个程序共享同一库的内存空间
平台依赖性 较少(生成的可执行文件独立) 需要确保运行环境中存在相应的动态库
性能 链接时已完成,可能略快于动态库 运行时需加载库,初次加载可能稍慢

四、C++ 库的创建与使用

1. 创建库的源代码示例

以一个简单的数学库 mymath 为例,提供计算整数和浮点数之和的功能。

a. 头文件 mymath.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef MYMATH_H
#define MYMATH_H

#ifdef MYMATH_EXPORTS
#define MYMATH_API __declspec(dllexport)
#else
#define MYMATH_API __declspec(dllimport)
#endif

extern "C" {
MYMATH_API int add_int(int a, int b);
MYMATH_API float add_float(float a, float b);
}

#endif // MYMATH_H
  • 解释

    • MYMATH_API 宏用于控制符号的导出与导入。在构建动态库时定义 MYMATH_EXPORTS 以导出符号;在使用库时不定义该宏,以导入符号。
    • extern "C" 避免 C++ 的名字修饰(Name Mangling),确保库函数具有 C 风格的接口,便于跨语言调用。

b. 源文件 mymath.cpp

1
2
3
4
5
6
7
8
9
#include "mymath.h"

int add_int(int a, int b) {
return a + b;
}

float add_float(float a, float b) {
return a + b;
}

2. 使用 CMake 构建库

CMake 是一个跨平台的自动化构建系统,能够简化构建过程。以下介绍如何使用 CMake 构建静态库和动态库。

a. 项目结构

假设项目结构如下:

1
2
3
4
5
6
MyMathLib/
├── CMakeLists.txt
├── include/
│ └── mymath.h
└── src/
└── mymath.cpp

b. 创建静态库的 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cmake_minimum_required(VERSION 3.0)
project(MyMathLib)

# 指定库的头文件路径
include_directories(${CMAKE_SOURCE_DIR}/include)

# 创建静态库
add_library(mymath STATIC src/mymath.cpp)

# 设置库的版本号(可选)
set_target_properties(mymath PROPERTIES VERSION 1.0.0 SOVERSION 1)

# 安装目标
install(TARGETS mymath
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)

# 安装头文件
install(FILES include/mymath.h DESTINATION include)

c. 创建动态库的 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmake_minimum_required(VERSION 3.0)
project(MyMathLib)

# 定义宏用于导出符号(仅在 Windows 平台)
if (WIN32)
add_definitions(-DMYMATH_EXPORTS)
endif()

# 指定库的头文件路径
include_directories(${CMAKE_SOURCE_DIR}/include)

# 创建动态库
add_library(mymath SHARED src/mymath.cpp)

# 设置库的版本号(可选)
set_target_properties(mymath PROPERTIES VERSION 1.0.0 SOVERSION 1)

# 安装目标
install(TARGETS mymath
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)

# 安装头文件
install(FILES include/mymath.h DESTINATION include)

d. 同时构建静态库和动态库的 CMakeLists.txt

如果需要同时生成静态库和动态库,可以在同一个 CMakeLists.txt 中添加多个库目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cmake_minimum_required(VERSION 3.0)
project(MyMathLib)

# 定义宏用于导出符号(仅在 Windows 平台)
if (WIN32)
add_definitions(-DMYMATH_EXPORTS)
endif()

# 指定库的头文件路径
include_directories(${CMAKE_SOURCE_DIR}/include)

# 创建静态库
add_library(mymath_static STATIC src/mymath.cpp)

# 创建动态库
add_library(mymath_shared SHARED src/mymath.cpp)

# 设置动态库的版本号(可选)
set_target_properties(mymath_shared PROPERTIES VERSION 1.0.0 SOVERSION 1)

# 安装静态库和动态库
install(TARGETS mymath_static mymath_shared
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)

# 安装头文件
install(FILES include/mymath.h DESTINATION include)

3. 编译库

在项目根目录下执行以下命令以编译库:

1
2
3
4
mkdir build
cd build
cmake ..
cmake --build .

根据 CMakeLists.txt 的配置,这将在 build 目录下生成静态库和/或动态库文件。

五、在项目中使用库

1. 示例项目结构

假设有一个应用程序项目 MyApp,其结构如下:

1
2
3
MyApp/
├── CMakeLists.txt
├── main.cpp

2. 编写应用程序源代码 main.cpp

1
2
3
4
5
6
7
8
9
10
#include "mymath.h"
#include <iostream>

int main() {
int sum_int = add_int(3, 4);
float sum_float = add_float(3.5f, 4.2f);
std::cout << "Sum (int): " << sum_int << std::endl;
std::cout << "Sum (float): " << sum_float << std::endl;
return 0;
}

3. 编写应用程序的 CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required(VERSION 3.0)
project(MyApp)

# 指定库的头文件路径
include_directories(${CMAKE_SOURCE_DIR}/../MyMathLib/include)

# 添加可执行文件
add_executable(MyApp main.cpp)

# 链接静态库或动态库
# 静态库
target_link_libraries(MyApp ${CMAKE_SOURCE_DIR}/../MyMathLib/build/lib/libmymath_static.a)

# 或者动态库(需要确保运行时可以找到动态库)
# target_link_libraries(MyApp ${CMAKE_SOURCE_DIR}/../MyMathLib/build/lib/libmymath_shared.so)

注意

  • 对于 Windows 平台,动态库的链接和部署可能需要额外配置,确保 .dll 文件在可执行文件的同一目录或系统路径下。
  • 使用相对路径链接库时,应确保路径的正确性,或考虑使用 CMake 的 find_library 等功能进行更灵活的库查找。

4. 构建并运行应用程序

MyApp 项目根目录下执行:

1
2
3
4
5
mkdir build
cd build
cmake ..
cmake --build .
./MyApp

运行后,应输出:

1
2
Sum (int): 7
Sum (float): 7.7

六、注意事项

  1. 宏定义与符号导出
    • 在动态库的头文件中,通过宏定义控制符号的导出和导入。在 Windows 平台,使用 __declspec(dllexport) 导出符号,__declspec(dllimport) 导入符号;而在 Unix/Linux 平台,通常不需要特殊标记。
  2. 跨平台兼容性
    • 不同平台的动态库文件后缀和符号导出方式不同。CMake 可以根据平台条件设置宏定义和库属性,确保库在各个平台上的正确构建和使用。
  3. 安装与部署
    • 使用 install 指令指定库文件和头文件的安装路径,使得其他项目可以方便地引用和链接这些库。
    • 动态库需要在运行时能够被找到,可以通过环境变量 PATH(Windows)或 LD_LIBRARY_PATH(Unix/Linux)设置库的查找路径,或将库文件放在系统默认路径下。
  4. 依赖管理
    • 对于较大的项目,可以使用 CMake 的 find_packageadd_subdirectory 来管理多个库和依赖,保持项目结构清晰。
  5. 版本控制
    • 设置库的版本号,有助于管理不同版本的库及其兼容性。例如,使用 VERSIONSOVERSION 属性控制动态库的主版本和次版本,便于动态链接器进行版本匹配。

封装任务管理系统

日志库设计

Logger.h声明不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef LOGGER_LOGGER_H
#define LOGGER_LOGGER_H
#include <fstream>
#include <mutex>

#ifdef _WIN32
#ifdef LOGGER_BUILDING_DLL
#define LOGGER_API __declspec(dllexport)
#else
#define LOGGER_API __declspec(dllimport)
#endif
#else
#define LOGGER_API
#endif

LOGGER_API class Logger {
public:
//获取单例
static Logger& getInstance();
//禁止拷贝和赋值
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
//记录日志
void log(const std::string & message);
~Logger();
private:
Logger();
std::ofstream logFile;
std::mutex mutex;
};

#endif //LOGGER_LOGGER_H

Logger.cpp具体实现也不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "Logger.h"

Logger& Logger::getInstance() {
static Logger logger;
return logger;
}

Logger::Logger(){
logFile.open("log.txt", std::ios::app);
if(!logFile.is_open()){
throw std::runtime_error("Failed to open log file");
}
}

Logger::~Logger(){
if(logFile.is_open()){
logFile.close();
}
}

void Logger::log(const std::string& message){
std::lock_guard<std::mutex> lock(mutex);
if(logFile.is_open()){
auto now = std::chrono::system_clock::now();
auto now_time = std::chrono::system_clock::to_time_t(now);
char buffer[100];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now_time));
logFile << std::string(buffer) << ": " << message << std::endl;
}
}

Logger库编写

1
2
3
4
5
6
7
8
cmake_minimum_required(VERSION 3.28)
project(Logger)

set(CMAKE_CXX_STANDARD 17)

add_library(Logger SHARED Logger.cpp
)
target_compile_definitions(Logger PRIVATE LOGGER_BUILDING_DLL)

纯虚类设计

我们之前采用CRTP方式封装了任务管理系统,这一节用封装继承多态的思想封装这个任务,我们期望将Command以及任务类等统一封装到一个libCommand.dll中,对外只提供Command.h文件,外界无法知晓Command内部实现细节。

Command.h仅提供纯虚类的声明,以及唯一一个获取纯虚类指针的函数GetCommand

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef COMMAND_COMMAND_H
#define COMMAND_COMMAND_H
#include <string>
#include <memory>
#ifdef _WIN32
#ifdef COMMAND_BUILDING_DLL
#define COMMAND_API __declspec(dllexport)
#else
#define COMMAND_API __declspec(dllimport)
#endif
#else
#define COMMAND_API
#endif
class COMMAND_API Command{
public:
virtual void execute(const std::string& args) = 0;
};

COMMAND_API std::shared_ptr<Command> GetCommand(const std::string& command);

#endif //COMMAND_COMMAND_H

Command.cpp提供GetCommand的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include "Command.h"
#include "CommandImpl.h"
#include <memory>

TaskManager mgsr;
std::shared_ptr<Command> GetCommand(const std::string& command){
if(command == "add"){
return std::make_shared<AddCommand>(mgsr);
}

if(command == "del"){
return std::make_shared<DeleteCommand>(mgsr);
}

if(command == "list"){
return std::make_shared<ListCommand>(mgsr);
}

if(command == "update"){
return std::make_shared<UpdateCommand>(mgsr);
}
return nullptr;
}

Command.cpp中创建了不同的命令,并且定义了全局的TaskManager

重写纯虚类

命令的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//
// Created by secon on 2025/3/14.
//

#ifndef COMMAND_COMMANDIMPL_H
#define COMMAND_COMMANDIMPL_H
#include "Command.h"
#include "TaskManager.h"
#include "Logger.h"
class AddCommand: public Command {
public:
AddCommand(TaskManager& manager):taskManager(manager){}
void execute(const std::string& args){
//解析参数
size_t pos1 = args.find(',');
size_t pos2 = args.find(',',pos1+1);
if(pos1 == std::string::npos || pos2 == std::string::npos){
// Logger::getInstance().log("参数格式错误");
std::cout << "参数格式错误。请使用: add <描述>,<优先级>,<截止日期>" << std::endl;
return;
}

std::string description = args.substr(0,pos1);
int priority = std::stoi(args.substr(pos1+1,pos2-pos1-1));
std::string dueDate = args.substr(pos2+1);
taskManager.addTask(description,priority,dueDate);
std::cout << "任务添加成功."<< std::endl;
}

private:
TaskManager& taskManager;
};

class DeleteCommand: public Command {
public:
DeleteCommand(TaskManager& manager):taskManager(manager){}
void execute(const std::string& args){
try{
size_t pos ;
int id = std::stoi(args,&pos);
if(pos != args.length()){
std::cout << "参数格式错误。请使用: delete <ID>" << std::endl;
return;
}
taskManager.deleteTask(id);

std::cout << "任务删除成功."<< std::endl;
}catch(const std::invalid_argument& e){
Logger::getInstance().log("参数格式错误");
return;
}catch(const std::out_of_range& e){
Logger::getInstance().log("参数格式错误");
return;
}

}
private:
TaskManager& taskManager;
};

class ListCommand: public Command {
public:
ListCommand(TaskManager& manager):taskManager(manager){}
void execute(const std::string& args){
try{
int sortOption = 0;
if(!args.empty()){
sortOption = std::stoi(args);
}

std::cout << "当前任务列表:"<< std::endl;
taskManager.listTasks(sortOption);
}catch (const std::invalid_argument& e){
Logger::getInstance().log("参数格式错误");
return;
}catch (const std::out_of_range& e){
Logger::getInstance().log("参数格式错误");
return;
}

}
private:
TaskManager& taskManager;
};


class UpdateCommand: public Command {
public:
UpdateCommand(TaskManager& manager):taskManager(manager){}
void execute(const std::string& args){
try{
size_t pos1 = args.find(',');
size_t pos2 = args.find(',',pos1+1);
size_t pos3 = args.find(',',pos2+1);
if(pos1 == std::string::npos || pos2 == std::string::npos || pos3 == std::string::npos){
std::cout << "参数格式错误。请使用: update <ID>,<描述>,<优先级>,<截止日期>" << std::endl;
return;
}
int id = std::stoi(args.substr(0,pos1));
std::string description = args.substr(pos1+1, pos2-pos1-1);
int priority = std::stoi(args.substr(pos2+1, pos3-pos2-1));
std::string dueDate = args.substr(pos3+1);
taskManager.updateTask(id, description, priority, dueDate);
std::cout << "任务更新成功."<< std::endl;
}catch (const std::invalid_argument& e){
Logger::getInstance().log("参数格式错误");
return;
}catch (const std::out_of_range& e){
Logger::getInstance().log("参数格式错误");
return;
}
}
private:
TaskManager& taskManager;
};

#endif //COMMAND_COMMANDIMPL_H

TaskManager和之前一样,无需改动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//
// Created by secon on 2025/3/14.
//

#ifndef COMMAND_TASKMANAGER_H
#define COMMAND_TASKMANAGER_H


#include "Task.h"
#include <vector>
#include <iostream>
#include <string>
#include <algorithm>
#include <fstream>
#include <sstream>

class TaskManager {
public:
TaskManager();
void addTask(const std::string& description, int priority, const std::string& date);
void deleteTask(int id);
void updateTask(int id, const std::string& description, int priority, const std::string& date);
void listTasks(int sortOption) const ; // 0: 按ID, 1:按优先级升序,2:按日期升序
void loadTasks();
void saveTasks() const;
private:
std::vector<Task> tasks;
int nextId;
static bool compareByPriority(const Task& a, const Task& b);
static bool compareByDueDate(const Task& a, const Task& b);
};



#endif //COMMAND_TASKMANAGER_H

具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//
// Created by secon on 2025/3/14.
//

#include "TaskManager.h"

#include "TaskManager.h"
#include "Logger.h"
#include <iostream>

TaskManager::TaskManager():nextId(1) {
loadTasks();// 始化任务管理器,例如设置默认的线程池大小等。
}

void TaskManager::addTask(const std::string &description, int priority, const std::string &dueDate) {
Task task;
task.id = nextId++;
task.description = description;
task.priority = priority;
task.dueDate = dueDate;
tasks.push_back(task);
Logger::getInstance().log("Task added: " + task.toString());
saveTasks();// 保存任务到文件。
}

void TaskManager::deleteTask(int id){
auto it = std::find_if(tasks.begin(), tasks.end(), [id](const Task &task){
return task.id == id;
});
if(it != tasks.end()){
tasks.erase(it);
Logger::getInstance().log("Task deleted: " + it->toString());
saveTasks();// 保存任务到文件。
}else{
std::cout << "Task not found." << std::endl;
}
}

void TaskManager::updateTask(int id, const std::string &description, int priority, const std::string &date) {
auto it = std::find_if(tasks.begin(), tasks.end(), [id](const Task &task){
return task.id == id;
});
if(it != tasks.end()){
it->description = description;
it->priority = priority;
it->dueDate = date;
Logger::getInstance().log("Task updated: " + it->toString());
saveTasks();// 保存任务到文件。
}else{
std::cout << "Task not found." << std::
endl;
}
}

void TaskManager::saveTasks() const {
std::ofstream outFile("tasks.txt");
if(!outFile.is_open()) {
Logger::getInstance().log( "Failed to open tasks file for writing.");
return;
}

for(const auto &task : tasks){
outFile << task.id << "," << task.description << "," << task.priority << "," << task.dueDate << std::endl;
}

outFile.close();
Logger::getInstance().log("Tasks saved successfully.");
}

void TaskManager::listTasks(int sortOption) const {
std::vector<Task> sortedTasks = tasks;
switch (sortOption) {
case 1:
std::sort(sortedTasks.begin(), sortedTasks.end(), compareByPriority);
break;
case 2:
std::sort(sortedTasks.begin(), sortedTasks.end(), compareByDueDate);
break;
default:
// 不排序,直接输出原始顺序。
break;
}

for(const auto &task : sortedTasks){
std::cout << task.toString() << std::endl;
}
}

void TaskManager::loadTasks() {
std::ifstream inFile("tasks.txt");
if(!inFile.is_open()) {
Logger::getInstance().log( "Failed to open tasks file.");
return;
}

std::string line;
while(std::getline(inFile,line)){
std::istringstream iss(line);
Task task;
char delimiter;
iss >> task.id >> delimiter;
std::getline(iss, task.description,',');
iss >> task.priority >> delimiter;
iss >> task.dueDate;
tasks.push_back(task);
if(task.id >= nextId){
nextId = task.id + 1;
}
}

inFile.close();
Logger::getInstance().log("Tasks loaded successfully.");
}

bool TaskManager::compareByPriority(const Task &a, const Task &b) {
return a.priority < b.priority;
}

bool TaskManager::compareByDueDate(const Task &a, const Task &b) {
return a.dueDate < b.dueDate;
}

Task实现也没有改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef UNTITLED_TASK_H
#define UNTITLED_TASK_H
#include <string>
#include <sstream>
#include <iomanip>

class Task {
public:
int id;
std::string description;
int priority;
std::string dueDate;

std::string toString() const{
std::ostringstream oss;
oss << "ID: " << id
<<", 描述: " << description
<<", 优先级: " << priority
<<", 截止日期: " << dueDate;

return oss.str();
}
};

Command库编写

我们重写Command库的CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cmake_minimum_required(VERSION 3.28)
project(Command)
set(CMAKE_CXX_STANDARD 17)
add_library(Command SHARED Command.cpp
Command.h
CommandImpl.cpp
CommandImpl.h
TaskManager.cpp
TaskManager.h
Task.cpp
Task.h)

# 定义导出宏
target_compile_definitions(Command PRIVATE COMMAND_BUILDING_DLL)
# 添加 Logger 的包含目录
target_include_directories(Command PRIVATE
${CMAKE_CURRENT_SOURCE_DIR} # 包含当前目录的头文件(如CommandExport.h)
../Logger # 包含Logger的头文件
)


# 指定Logger的库文件路径并链接
target_link_directories(Command PRIVATE ../Logger) # 假设Logger.lib和Logger.dll在此目录
target_link_libraries(Command PRIVATE Logger) # 链接Logger库

主函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include "Command.h"
#include "Logger.h"
#include "unordered_map"

int main() {
system("chcp 65001 > nul");
std::unordered_map<std::string, std::shared_ptr<Command>> commands;
commands.emplace("add", GetCommand("add"));
commands.emplace("delete", GetCommand("delete"));
commands.emplace("list", GetCommand("list"));
commands.emplace("update", GetCommand("update"));

std::cout << "欢迎使用任务管理系统!" << std::endl;
std::cout << "可用命令: add, delete, list, update, exit" << std::endl;

std::string input;
while (true) {
std::cout << "\n> ";
std::getline(std::cin, input);
if (input.empty()) continue;

// 分离命令和参数
size_t spacePos = input.find(' ');
std::string cmd = input.substr(0, spacePos);
std::string args;
if (spacePos != std::string::npos) {
args = input.substr(spacePos + 1);
}

if (cmd == "exit") {
std::cout << "退出程序。" << std::endl;
break;
}

auto it = commands.find(cmd);
if (it != commands.end()) {
it->second->execute(args);
} else {
std::cout << "未知命令:" << cmd << std::endl;
}
}
return 0;
}

CMakeLists.txt编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
cmake_minimum_required(VERSION 3.28)
project(untitled)

set(CMAKE_CXX_STANDARD 17)

add_executable(untitled main.cpp
)

# 添加 Logger 的包含目录
target_include_directories(untitled PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Logger
${CMAKE_CURRENT_SOURCE_DIR}/Command
)


# 指定Logger的库文件路径并链接
target_link_directories(untitled PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/Logger
${CMAKE_CURRENT_SOURCE_DIR}/Command
)

target_link_libraries(untitled PRIVATE
Logger
Command
) # 链接Logger库

完结撒花

从2024年8月开始了第一个零基础C++视频的创作,历时七个多月完成了这一系列的创作,很多粉丝留言跟着我的节奏学会了C++基础用法,也能自己动手开发一些功能了,这让我很开心,比我自己学会了一套知识带来的快感还多。这套教程涵盖了C++98到C++20的内容,旨在帮助更多迷茫中找不到学习道路的人。

这套教程做完了,是结束也是新的开始,我会回归到聊天项目第二季的开发中,我觉得分享最好的状态就是陪伴,所以大家不用担心我更新完基础就离开。很多人问我为什么做免费教程,知识付费的时代做付费教程已成为大趋势,但我和别人不同的一点是我是真的喜欢技术的那类人,也喜欢把自己的认知分享出来,哪怕以后被时代淘汰,找不到合适的开发工作,甚至去做了其他行业,我也会在业余时间学习和分享编程技术,编程已经成为我生活中不可分割的一个乐趣。在分享中与大家交流,让我不再孤单,很多人素未相识,但是通过共同的兴趣走到一起,没有任何利益关系,这是一种君子之交淡如水的快乐,这种快乐和解忧杂货店那本书有些共鸣。

也祝福大家能学会C++并且找到合适的工作。再次感谢大家一路陪伴。 ————————-恋恋风辰 写于2025年3月初春