c++项目学习

对于c++学习,在GitHub上有这几个开源的项目值得推荐:

  对于c++程序中经常出现的越界或者内存溢出等问题,这里推荐ASAN进行扫描.

对于c++一般项目而言,整个项目结构如下,

project/
├── include/
│   ├── MyClass.h
│   └── Utils.h
├── src/
│   ├── MyClass.cpp
│   └── Utils.cpp
└── main.cpp

其中需要知道的几点如下:

(1)头文件

  • #include 主要用于系统头文件和标准库头文件。
  • #include "filename" 主要用于用户定义的头文件,并且优先在当前目录中搜索。

(2)项目细节

将头文件放在 include 文件夹内,对应的实现文件(.cpp 文件)放在 src 文件夹内是一种常见的项目组织方式。头文件和实现文件的名称不一定需要完全对应,但为了方便管理和维护,通常会遵循这种对应的命名约定。

头文件 MyClass.h

#ifndef MYCLASS_H
#define MYCLASS_H

class MyClass {
public:
    void doSomething();
};

#endif // MYCLASS_H

实现文件 MyClass.cpp

#include "MyClass.h"
#include <iostream>

void MyClass::doSomething() {
    std::cout << "Doing something!" << std::endl;
}

头文件 Utils.h

#ifndef UTILS_H
#define UTILS_H

namespace Utils {
    void helperFunction();
}

#endif // UTILS_H

实现文件 Utils.cpp

#include "Utils.h"
#include <iostream>

void Utils::helperFunction() {
    std::cout << "Helper function!" << std::endl;
}

主函数 main.cpp

#include "MyClass.h"
#include "Utils.h"

int main() {
    MyClass obj;
    obj.doSomething();

    Utils::helperFunction();

    return 0;
}

将文件链接起来

》为了编译和链接这些文件,你需要一个编译器和构建工具。这里以 g++ 为例:

g++ -Iinclude -o main src/MyClass.cpp src/Utils.cpp main.cpp
  • -Iinclude:告诉编译器头文件所在的目录。
  • -o main:指定输出可执行文件的名称。
  • src/MyClass.cpp src/Utils.cpp main.cpp:列出所有源文件。

》使用 Makefile

为了简化编译过程,可以使用 Makefile:

# Makefile

CXX = g++
CXXFLAGS = -Iinclude -std=c++11
SRC = src/MyClass.cpp src/Utils.cpp main.cpp
OBJ = $(SRC:.cpp=.o)
TARGET = main

all: $(TARGET)

$(TARGET): $(OBJ)
	$(CXX) -o $@ $^

%.o: %.cpp
	$(CXX) $(CXXFLAGS) -c $< -o $@

clean:
	rm -f $(OBJ) $(TARGET)

运行 make 命令即可编译整个项目。

》使用CMakeLists.txt

简单使用示例

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 包含头文件目录
include_directories(include)

# 添加源文件
set(SOURCES
    src/MyClass.cpp
    src/Utils.cpp
    main.cpp
)

# 生成可执行文件
add_executable(MyProject ${SOURCES})

》如果你的项目更复杂,可以扩展 CMakeLists.txt,如添加子目录或设置更详细的编译选项。

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# 包含头文件目录
include_directories(include)

# 添加子目录
add_subdirectory(src)

# 生成可执行文件
add_executable(MyProject main.cpp)
target_link_libraries(MyProject MyLib)

同时增加src/CMakeLists.txt

# 添加源文件
set(SOURCES
    MyClass.cpp
    Utils.cpp
)

# 创建静态库
add_library(MyLib STATIC ${SOURCES})

新建build文件夹,然后运行cmake和make指令即可完成编译。其中在 src 目录中添加 CMakeLists.txt 文件,可以为项目带来以下好处:

  1. 模块化构建:将构建配置分成多个小的、独立的部分,使项目结构更加清晰和可维护。每个子目录可以包含自己的 CMakeLists.txt,这使得管理大项目变得更容易。
  2. 重用代码:通过在子目录中创建库,可以在多个可执行文件或项目中重用代码。这样做避免了代码重复,并且使得库的更新和维护更加集中化。
  3. 分离关注点:可以将库的构建与可执行文件的构建分离开来,使得各部分的构建规则独立,从而简化了复杂项目的管理。

这种方式在大型项目中尤为重要,因为它有助于组织代码、减少复杂性并提高可维护性。

这个项目结构和编译方法可以帮助你有效地组织和管理代码,并确保所有头文件和源文件正确链接在一起。

(3)编译

       对于一个完整的c++项目,尤其是一个较为复杂的大型项目,这里一般推荐基于cmakelists.txt进行编译,这时候需要对cmakelists.txt的一些基本语法有一定了解。在 `CMakeLists.txt` 文件中,常见的命令用于定义项目的构建规则和配置。以下是常用的命令简要说明和使用方式:

1. file(GLOB_RECURSE ...):

   - 用途:递归地收集指定模式的文件。

   - 使用方式:通常用于收集源文件和头文件,例如: 下面使用 GLOB_RECURSE 子命令来收集匹配指定模式的文件,并将它们存储在一个名为 SOURCES 的变量中。这里的 SOURCES 是一个列表变量,用于存储所有找到的文件的路径。


file(GLOB_RECURSE SOURCES "src/*.cpp" "include/*.h")

收集到的 SOURCES 变量之后可以用于:

  • 创建可执行文件或库:使用 add_executable 或 add_library 命令,并传递 SOURCES 变量作为源文件列表。
  • 指定为其他命令的参数:例如,您可以将 SOURCES 变量作为参数传递给 target_sources 命令,以向目标添加源文件。

2. set(...):

   - 用途:用于创建新变量或修改已存在的变量。

   - 使用方式:可以设置项目特定的变量或CMake的变量,后面如果想要取该变量只需要${变量名}即可,例如:

# 设置一个简单变量
set(EXAMPLE_VAR "This is a variable")

# 设置具有多个值的变量
set(MULTIVALUES_VAR value1 value2 value3)

# 设置缓存变量
set(CACHE_VAR "This is a cache variable" CACHE STRING "Description of CACHE_VAR")

# 条件设置变量
if(MY_CONDITION)
  set(CONDITION_VAR "Value if condition is true")
endif()

# 向列表添加元素
set(LIST_VAR value1 value2)
list(APPEND LIST_VAR value3)

3. find_package(...):

   - 用途:查找并加载外部项目的配置文件,取它们配置信息以便在项目中使用,这些项目可能是第三方库。

   - 使用方式:指定要查找的包和版本,

   -基本格式: find_package(<PackageName> [version] [EXACT] [QUIET] [REQUIRED])

其中,

  • <PackageName> 是要查找的包的名称,例如 Eigen3
  • version 可以指定需要的包的版本号,如果没有则基于本机安装版本操作。
  • EXACT 如果指定,表示只接受精确的版本号。
  • QUIET 如果指定,查找过程中不会输出任何消息。
  • REQUIRED 如果指定,如果找不到包,CMake 将报错并停止构建过程。

例如:

find_package(Eigen3 3.3 REQUIRED)
if(EIGEN3_FOUND)
  include_directories(${EIGEN3_INCLUDE_DIRS})
  target_link_libraries(my_target Eigen3::Eigen)
endif()

成功找到包后,配置文件会设置一系列的变量,提供关于包的信息,例如包含目录、库文件、编译定义等。如果包提供了导入的目标(Imported Targets),可以直接使用 target_link_libraries 命令链接这些目标,而不需要显式指定库文件路径。配置文件会设置如 EIGEN3_INCLUDE_DIRS 这样的变量,包含包的头文件路径,这些路径可以传递给 include_directories 或 target_include_directories


在使用 `find_package(Eigen3 REQUIRED)` 命令后,除了 `EIGEN3_INCLUDE_DIRS` 之外,可能还会定义其他与 Eigen3 相关的变量,这些变量通常取决于 Eigen3 库的 CMake 配置文件。以下是一些常见的变量,它们可能在加载 Eigen3 配置后被设置:

》EIGEN3_FOUND:
   - 一个布尔变量,如果找到 Eigen3 库,则设置为 `TRUE`。

》EIGEN3_VERSION 或 EIGEN3_VERSION_STRING:
   - 包含找到的 Eigen3 库的版本号。

》EIGEN3_DEFINITIONS:
   - 包含编译时需要定义的宏,例如条件编译标志。

》EIGEN3_LIBRARIES:
   - 如果 Eigen3 需要链接到特定的库,这个变量可能会被设置。

》EIGEN3_INCLUDE_DIR 或 EIGEN3_INCLUDE_DIRS:
   - 包含头文件的路径,类似于 `EIGEN3_INCLUDE_DIRS`,但可能是一个单一的路径或路径列表。

》EIGEN3_ROOT_DIR:
   - Eigen3 库安装的根目录。

》EIGEN3_TARGETS 或 EIGEN3_TARGET:
   - 如果 Eigen3 使用 CMake 的 `install(EXPORT)` 命令安装,可能会导出目标,这些目标可以被导入到其他项目中。

》EIGEN3_USE_FILE:
   - 一个路径,指向一个 CMake 脚本文件,该文件包含了使用 Eigen3 所需的所有变量和函数。

请注意,这些变量的确切名称和存在性取决于 Eigen3 的安装和配置方式。一些变量可能在某些安装中不存在,或者可能有不同的名称。

#要确定哪些变量可用,您可以在 CMake 命令行中使用 `-c` 选项查看变量
cmake -LAH | grep EIGEN3

####或者在 CMakeLists.txt 文件中使用 `message` 命令打印变量:
message(STATUS "Eigen3 include directories: ${EIGEN3_INCLUDE_DIRS}")

这将输出 `EIGEN3_INCLUDE_DIRS` 变量的值,如果该变量被设置的话。您可以用这种方式检查其他可能的变量。


4. include_directories(...):

   - 用途:用于向项目添加头文件的搜索路径,这样编译器在编译源文件时能够找到所需的头文件。

   -基本语法是 include_directories(<path1> [<path2> ...<pathN>]),其中 <pathX> 是要添加的头文件路径,其中路径应该指向包含头文件的目录,而不是单个头文件,而且多路径中每个路径都应该用空格分隔

   - 使用方式:指定头文件所在的目录,include_directories 默认是全局的,添加的路径对项目中的所有目标都有效。也可以通过特定的语法添加局部路径,只对特定的目标有效。

例如:

# 添加单个路径
include_directories(include)

# 添加多个路径
include_directories(include src/external_lib)

# 使用 find_package 获取的路径
include_directories(${EIGEN3_INCLUDE_DIRS})

当项目依赖于第三方库或框架时,通常需要包含这些库的头文件。用 include_directories 可以指定这些库的头文件目录。经常与 find_package 命令结合使用,通过获取外部库的头文件路径(例如通过 EIGEN3_INCLUDE_DIRS 这样的变量),然后将这些路径传递给 include_directories。include_directories 是一个简单直接的命令,但在大型或复杂的项目中,使用更现代的命令如 target_include_directories 可以提供更好的模块化和灵活性。

5. target_link_libraries(...):

   - 用途:用于将一个或多个库链接到指定的目标上,确保在链接阶段目标能够找到并使用这些库。比如可以链接静态库(.lib 或 .a 文件)、动态库(.so 或 .dll 文件)以及别名库(通过 CMake 的 alias 创建)。

   - 基本语法是 target_link_libraries(<target> <library1> [<library2> ...]),其中 <target> 是要链接库的目标名称,<library1> 等是库的名称或别名。

   - 使用方式:指定目标和需要链接的库,例如:

# 链接单个库
target_link_libraries(my_app Eigen3::Eigen)

# 链接多个库
target_link_libraries(my_app Eigen3::Eigen ${OpenCV_LIBS})

通常与 find_package 命令结合使用,通过获取外部库的目标名称或路径,然后将这些信息传递给 target_link_libraries

6. add_library(...):

   - 用途:用于创建库目标,可以是静态库(Static Library,通常以 .a 结尾)或动态库(Shared Library,通常以 .so 或 .dll 结尾)。其中

  • 静态库:通常用于在编译时将库的代码整合到最终可执行文件中。
  • 动态库:在运行时被加载,允许多个程序共享同一份库代码。

    -基本语法: add_library(<name> [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] <source1> <source2> ...)

  • <name> 是库目标的名称。
  • STATICSHARED 或 MODULE 指定库的类型,默认为 SHARED
  • EXCLUDE_FROM_ALL 表示该库不会自动被包含在 all 目标中
  • 通过 <source1> <source2> ... 参数指定库的源文件列表。

   - 使用方式:指定库的名称和源文件,可以是静态库或共享库,例如:

# 创建一个名为 my_lib 的静态库
add_library(my_lib STATIC src/lib1.cpp src/lib2.cpp)

# 创建一个名为 my_lib 的动态库
add_library(my_lib SHARED src/lib1.cpp src/lib2.cpp)

一般结合使用 target_link_libraries 将其他目标链接到这个库,或者将这个库链接到其他目标。使用 target_include_directories 为使用该库的目标指定头文件搜索路径。

     7. add_executable(...):

   - 用途:用于创建一个可执行文件目标,即最终将生成可直接运行的程序。

   -基本语法: add_executable(<name> [EXCLUDE_FROM_ALL] <source1> <source2> ...)

  • <name> 是可执行文件目标的名称,也是最终生成的可执行文件的名称。
  • EXCLUDE_FROM_ALL 选项表示该目标不会自动被包含在 all 目标中,允许用户通过指定目标名称来构建。
  • 通过 <source1> <source2> ... 参数指定构成可执行文件的源文件列表。

   - 使用方式:指定可执行文件的名称和源文件,例如:

# 创建一个名为 my_exe 的可执行文件,由 main.cpp 和 other_source.cpp 组成
add_executable(my_exe main.cpp other_source.cpp)

一般结合使用 target_link_libraries 将所需的库链接到可执行文件目标上,使用 target_include_directories 为可执行文件目标指定头文件搜索路径。

       这些命令是构建C++项目时常用的CMake命令。使用时,您需要根据项目的具体需求和结构来编写 `CMakeLists.txt` 文件。例如,如果您的项目依赖于外部库,您将使用 `find_package` 来查找这些库,并使用 `target_link_libraries` 将它们链接到您的目标。如果您的项目包含多个源文件,您可以使用 `file(GLOB_RECURSE ...)` 来收集它们,然后使用 `add_library` 或 `add_executable` 来定义您的目标。

(4)总结

编写 `CMakeLists.txt` 时,您应该遵循以下步骤:

  • - 使用 `cmake_minimum_required` 指定CMake的最低版本要求。(必须的)
  • - 使用 `project` 定义项目名称和语言。(必须的)
  • - 设置编译选项,如C++标准。(没设置则会根据系统选择)
  • - 使用 `find_package` 查找并配置外部依赖。(需要依赖第三方脚本,配合target_link_libraries使用)
  • - 使用 `include_directories` 添加头文件搜索路径,推荐target_include_directories。( 添加头文件搜索路径)
  • - 使用 `add_library` 或 `add_executable` 定义构建目标。(必须的,确定项目目标,有时配合file一块使用)
  • - 使用 `target_link_libraries` 将依赖库链接到目标。(需要依赖第三方库时用,放在add_executable等后面)
  • - 可选地,使用 `install` 命令定义安装规则。(将项目编译安装)

高质量 CMakeLists.txt 文件的指导原则和最佳实践:

  • 项目初始化:使用 cmake_minimum_requiredproject 明确项目信息和 CMake 版本。

  • 目标定义:用 add_executableadd_library 清晰定义可执行文件和库。

  • 依赖管理:通过 find_package 和导入目标管理外部依赖。

  • 条件编译:利用条件语句处理不同平台或配置的编译选项。

  • 编译特性:使用 target_compile_features 设置 C++ 标准和编译特性。

  • 源码组织:避免过度使用 file(GLOB...),尽量手动列出源文件。

  • 链接管理:使用 target_link_libraries 精确链接库,避免使用 link_directories

  • 包含路径:使用 target_include_directories 指定目标的头文件搜索路径。

  • 安装规则:用 install 命令定义安装目标和文件的规则。

  • 构建类型:为 Debug 和 Release 等构建类型设置不同的编译选项。

  • 测试支持:利用 CTest 框架添加自动化测试。

  • 文档注释:在 CMakeLists.txt 中使用注释清晰地说明构建逻辑。

  • 避免全局变量:尽量使用目标特定的变量,避免全局变量污染。

  • 跨平台兼容性:确保 CMakeLists.txt 可在不同平台和编译器上工作。

  • 持续集成:确保 CMakeLists.txt 兼容 CI 系统,提供清晰的构建和测试脚本。

  • 错误处理:在找不到依赖或配置错误时提供清晰的错误信息。

  • 示例和指南:提供 CMakeLists.txt 使用示例和开发者指南。

编写 CMakeLists.txt 时,保持简洁、明确和可维护性是关键。遵循这些精炼的原则,可以创建出既强大又易于理解的构建系统。

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值