模块九:进一步学习 (指引方向)
目录
- 标准模板库 (STL) 深入 1.1.
std::map
(进阶) 1.1.1. 迭代器的更多用法 1.1.2. 自定义比较函数 1.1.3.std::multimap
1.2.std::set
(进阶) 1.2.1. 迭代器的更多用法 1.2.2. 自定义比较函数 1.2.3.std::multiset
和std::unordered_set
1.3. 算法 (<algorithm>
) (进阶) 1.3.1. 更多常用算法 1.3.2. 迭代器和算法的配合 1.3.3. Lambda 表达式和函数对象 - 更深入的 OOP 2.1. 继承 (进阶) 2.1.1. 构造函数和析构函数的调用顺序 2.1.2. 虚继承和菱形继承问题 2.1.3. final 关键字 2.2. 多态 (进阶) 2.2.1. 静态多态(编译时多态) 2.2.2. 抽象类和纯虚函数 2.2.3. 接口 (Interface) 的概念
- 异常处理 3.1.
try
,catch
,throw
(进阶) 3.1.1. 标准异常类 3.1.2. 异常安全 3.1.3. 自定义异常类 3.1.4.noexcept
规范 - 模板 4.1. 函数模板 (进阶) 4.1.1. 多个模板参数 4.1.2. 模板特化 4.2. 类模板 (进阶) 4.2.1. 多个模板参数 4.2.2. 成员函数模板 4.2.3. 类模板的特化 4.2.4. 模板元编程 (简介)
- 智能指针 5.1.
std::unique_ptr
(进阶) 5.1.1. 自定义删除器 (Custom Deleters) 5.1.2.unique_ptr
和数组 5.2.std::shared_ptr
(进阶) 5.2.1. 循环引用和std::weak_ptr
5.2.2.std::shared_ptr
的线程安全性 - 调试技巧 6.1. 如何使用调试器 (更详细) 6.1.1. 常用调试器命令和操作 (以 GDB 和 Visual Studio 为例) 6.1.2. 高级调试技巧 6.1.3. 使用断言 (
assert
) - 构建系统 7.1. CMake (简介) (更详细) 7.1.1. 更复杂的
CMakeLists.txt
示例 7.1.2. 外部依赖管理 7.1.3. 构建类型 (Debug, Release) 7.1.4. 其他构建系统简介
1. 标准模板库 (STL) 深入
1.1. std::map
(进阶)
1.1.1. 迭代器的更多用法
除了基本的 begin()
和 end()
之外,std::map
还提供了其他迭代器,例如 rbegin()
和 rend()
用于反向迭代。
C++
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> ages = {{"Alice", 30}, {"Bob", 25}, {"Charlie", 35}};
std::cout << "Ages in reverse order:" << std::endl;
for (auto it = ages.rbegin(); it != ages.rend(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
return 0;
}
1.1.2. 自定义比较函数
std::map
默认使用 <
运算符对键进行排序。您可以提供自定义的比较函数或函数对象来改变排序方式。
C++
#include <iostream>
#include <map>
#include <string>
struct CaseInsensitiveCompare {
bool operator()(const std::string& a, const std::string& b) const {
return std::lexicographical_compare(a.begin(), a.end(),
b.begin(), b.end(),
[](char c1, char c2) {
return std::tolower(c1) < std::tolower(c2);
});
}
};
int main() {
std::map<std::string, int, CaseInsensitiveCompare> data;
data["Apple"] = 1;
data["banana"] = 2;
data["Cherry"] = 3;
std::cout << "Data (case-insensitive sorted):" << std::endl;
for (const auto& pair : data) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
1.1.3. std::multimap
std::multimap
允许存储具有相同键的多个键值对。
C++
#include <iostream>
#include <map>
#include <string>
int main() {
std::multimap<std::string, int> scores;
scores.insert({"Alice", 90});
scores.insert({"Bob", 85});
scores.insert({"Alice", 95}); // 允许重复的键
std::cout << "Scores:" << std::endl;
for (const auto& pair : scores) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
auto range = scores.equal_range("Alice");
std::cout << "\nAlice's scores:" << std::endl;
for (auto it = range.first; it != range.second; ++it) {
std::cout << it->second << std::endl;
}
return 0;
}
1.2. std::set
(进阶)
1.2.1. 迭代器的更多用法
类似于 std::map
,std::set
也支持 rbegin()
和 rend()
进行反向迭代。
1.2.2. 自定义比较函数
您可以为 std::set
提供自定义的比较函数或函数对象来定义元素的排序方式。
C++
#include <iostream>
#include <set>
struct ReverseIntCompare {
bool operator()(int a, int b) const {
return a > b;
}
};
int main() {
std::set<int, ReverseIntCompare> numbers = {5, 2, 8, 1, 9};
std::cout << "Numbers in reverse order:" << std::endl;
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
1.2.3. std::multiset
和 std::unordered_set
std::multiset
: 允许存储重复的元素,并保持排序。std::unordered_set
: 存储唯一的元素,但不保证排序。它通常使用哈希表实现,因此在平均情况下查找、插入和删除操作的时间复杂度为 O(1)。
C++
#include <iostream>
#include <set>
#include <unordered_set>
int main() {
std::multiset<int> multiNumbers = {5, 2, 8, 2, 9};
std::cout << "MultiNumbers:" << std::endl;
for (int num : multiNumbers) {
std::cout << num << " ";
}
std::cout << std::endl;
std::unordered_set<int> unorderedNumbers = {5, 2, 8, 1, 9, 2}; // 重复的 2 只会保留一个
std::cout << "UnorderedNumbers (no guaranteed order):" << std::endl;
for (int num : unorderedNumbers) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
1.3. 算法 (<algorithm>
) (进阶)
1.3.1. 更多常用算法
std::count()
: 统计容器中特定值的出现次数。std::remove()
和std::remove_if()
: 从容器中移除特定的值或满足特定条件的元素(注意:这些算法不会改变容器的大小,通常需要与容器的erase()
方法一起使用)。std::replace()
和std::replace_if()
: 将容器中特定的值或满足特定条件的元素替换为新的值。std::accumulate()
: 计算容器中元素的总和(或其他累积操作)。
C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric> // for std::accumulate
int main() {
std::vector<int> data = {1, 2, 2, 3, 4, 2, 5};
int countOfTwo = std::count(data.begin(), data.end(), 2);
std::cout << "Count of 2: " << countOfTwo << std::endl;
auto it = std::remove(data.begin(), data.end(), 2);
data.erase(it, data.end());
std::cout << "Data after removing 2s: ";
for (int num : data) {
std::cout << num << " ";
}
std::cout << std::endl;
std::vector<int> numbers = {1, 2, 3, 4, 5};
int sum = std::accumulate(numbers.begin(), numbers.end(), 0);
std::cout << "Sum of numbers: " << sum << std::endl;
return 0;
}
1.3.2. 迭代器和算法的配合
STL 算法通常通过迭代器来操作容器中的元素。理解不同类型的迭代器(例如,输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器)对于有效地使用算法至关重要。不同的算法对迭代器有不同的要求。
1.3.3. Lambda 表达式和函数对象
Lambda 表达式和函数对象(也称为仿函数)可以作为参数传递给算法,以自定义算法的行为。
C++
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用 lambda 表达式对偶数进行平方
std::vector<int> squaredEvens;
std::for_each(numbers.begin(), numbers.end(),
[&](int n) {
if (n % 2 == 0) {
squaredEvens.push_back(n * n);
}
});
std::cout << "Squared even numbers: ";
for (int num : squaredEvens) {
std::cout << num << " ";
}
std::cout << std::endl;
// 使用函数对象判断是否为奇数
struct IsOdd {
bool operator()(int n) const {
return n % 2 != 0;
}
};
auto it = std::find_if(numbers.begin(), numbers.end(), IsOdd());
if (it != numbers.end()) {
std::cout << "First odd number: " << *it << std::endl;
}
return 0;
}
2. 更深入的 OOP
2.1. 继承 (进阶)
2.1.1. 构造函数和析构函数的调用顺序
在派生类对象创建时,基类的构造函数先于派生类的构造函数被调用。当派生类对象销毁时,派生类的析构函数先于基类的析构函数被调用。这是为了确保基类的初始化在派生类之前完成,而清理工作则相反。
2.1.2. 虚继承和菱形继承问题
当一个类从多个基类继承,而这些基类又继承自同一个更上层的基类时,就会出现菱形继承问题。这会导致派生类中存在多个相同的上层基类子对象。虚继承(使用 virtual
关键字)可以解决这个问题,确保只有一个共享的上层基类子对象。
C++
#include <iostream>
class Grandparent {
public:
Grandparent() { std::cout << "Grandparent constructor" << std::endl; }
virtual ~Grandparent() { std::cout << "Grandparent destructor" << std::endl; }
void commonFunction() { std::cout << "Grandparent function" << std::endl; }
};
class Parent1 : public virtual Grandparent {
public:
Parent1() { std::cout << "Parent1 constructor" << std::endl; }
~Parent1() { std::cout << "Parent1 destructor" << std::endl; }
};
class Parent2 : public virtual Grandparent {
public:
Parent2() { std::cout << "Parent2 constructor" << std::endl; }
~Parent2() { std::cout << "Parent2 destructor" << std::endl; }
};
class Child : public Parent1, public Parent2 {
public:
Child() { std::cout << "Child constructor" << std::endl; }
~Child() { std::cout << "Child destructor" << std::endl; }
};
int main() {
Child c;
c.commonFunction();
return 0;
}
2.1.3. final
关键字
final
关键字可以用于防止类被继承或防止虚函数被进一步重写。
C++
class Base {
public:
virtual void method1() {}
virtual void method2() final {} // method2 不能在派生类中被重写
};
class Derived : public Base {
public:
void method1() override {} // 可以重写
// void method2() override {} // 错误:method2 是 final 的
};
class FinalClass final { // FinalClass 不能被继承
public:
void method() {}
};
// class AnotherDerived : public FinalClass {}; // 错误:FinalClass 是 final 的
2.2. 多态 (进阶)
2.2.1. 静态多态(编译时多态)
静态多态主要通过函数重载和模板来实现。编译器在编译时根据函数参数的类型或模板参数来确定要调用的具体函数。
函数重载示例:
C++
#include <iostream>
void print(int x) { std::cout << "Printing integer: " << x << std::endl; }
void print(double x) { std::cout << "Printing double: " << x << std::endl; }
int main() {
print(10); // 调用 print(int)
print(3.14); // 调用 print(double)
return 0;
}
模板示例 (之前已经展示过): 编译器会为每种实际使用的类型生成特定的函数或类。
2.2.2. 抽象类和纯虚函数
包含至少一个纯虚函数的类被称为抽象类。抽象类不能被实例化。派生类必须重写基类中的所有纯虚函数才能成为非抽象类并被实例化。纯虚函数通过在虚函数声明后添加 = 0
来声明。
C++
#include <iostream>
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
virtual double area() const = 0; // 纯虚函数
virtual ~Shape() {}
};
class Circle : public Shape {
private:
double radius_;
public:
Circle(double r) : radius_(r) {}
void draw() const override { std::cout << "Drawing a circle." << std::endl; }
double area() const override { return 3.14159 * radius_ * radius_; }
};
// Shape shape; // 错误:Shape 是抽象类,不能实例化
int main() {
Circle circle(5.0);
circle.draw();
std::cout << "Circle area: " << circle.area() << std::endl;
return 0;
}
2.2.3. 接口 (Interface) 的概念
在 C++ 中,没有显式的 interface
关键字(像在 Java 或 C# 中那样)。然而,可以通过只包含纯虚函数的抽象类来模拟接口的行为。接口定义了一组派生类必须实现的方法,从而实现“契约式编程”。
3. 异常处理
3.1. try
, catch
, throw
(进阶)
3.1.1. 标准异常类
<stdexcept>
头文件定义了一系列标准的异常类,可以用于报告不同类型的错误,例如 std::runtime_error
, std::logic_error
, std::out_of_range
等。
C++
#include <iostream>
#include <stdexcept>
#include <vector>
int main() {
std::vector<int> data = {1, 2, 3};
int index = 5;
try {
if (index >= data.size()) {
throw std::out_of_range("Index out of bounds");
}
std::cout << "Element at index " << index << ": " << data.at(index) << std::endl;
} catch (const std::out_of_range& error) {
std::cerr << "Error: " << error.what() << std::endl;
}
return 0;
}
3.1.2. 异常安全
编写异常安全的代码意味着即使在异常被抛出的情况下,程序也能保持其状态的完整性,并且不会发生资源泄漏(例如,内存泄漏)。有不同的异常安全级别:
- 基本保证: 如果异常被抛出,程序不会崩溃,并且所有对象都处于有效的状态(但可能不是原始状态)。
- 强保证: 如果异常被抛出,程序的状态不会发生改变(就像操作从未发生过一样)。这通常通过“先复制再交换”等技术实现。
- 无抛出保证: 操作永远不会抛出异常(通常使用
noexcept
标记)。
3.1.3. 自定义异常类
您可以创建自己的异常类,通常通过继承自 std::exception
或其派生类来提供更具体的错误信息。
C++
#include <iostream>
#include <exception>
#include <string>
class MyCustomError : public std::runtime_error {
public:
MyCustomError(const std::string& message) : std::runtime_error(message) {}
};
void processData(int value) {
if (value < 0) {
throw MyCustomError("Value cannot be negative: " + std::to_string(value));
}
std::cout << "Processing data: " << value << std::endl;
}
int main() {
try {
processData(-5);
} catch (const MyCustomError& error) {
std::cerr << "Custom Error: " << error.what() << std::endl;
} catch (const std::runtime_error& error) {
std::cerr << "Runtime Error: " << error.what() << std::endl;
} catch (...) {
std::cerr << "An unknown error occurred." << std::endl;
}
return 0;
}
3.1.4. noexcept
规范
noexcept
是一个函数规范,用于表明函数是否会抛出异常。如果一个声明为 noexcept
的函数抛出了异常,程序可能会立即终止。使用 noexcept
可以帮助编译器进行优化,并且对于某些操作(例如,移动构造函数和析构函数)非常重要。
C++
#include <iostream>
#include <stdexcept>
void mightThrow() {
throw std::runtime_error("This function might throw.");
}
void doesNotThrow() noexcept {
std::cout << "This function does not throw." << std::endl;
}
int main() {
try {
mightThrow();
} catch (const std::runtime_error& e) {
std::cerr << "Caught exception: " << e.what() << std::endl;
}
doesNotThrow();
// 如果 doesNotThrow 内部抛出异常,程序可能会终止
// try {
// throw std::runtime_error("Exception from noexcept function");
// } catch (...) {
// std::cerr << "Caught exception (should not happen): " << std::endl;
// }
return 0;
}
4. 模板
4.1. 函数模板 (进阶)
4.1.1. 多个模板参数
函数模板可以接受多个类型参数。
C++
#include <iostream>
template <typename T1, typename T2>
void printPair(const T1& a, const T2& b) {
std::cout << "First: " << a << ", Second: " << b << std::endl;
}
int main() {
printPair(10, "Hello");
printPair(3.14, true);
return 0;
}
4.1.2. 模板特化
模板特化允许您为特定的类型提供模板的不同实现。
C++
#include <iostream>
#include <string>
template <typename T>
void print(const T& value) {
std::cout << "Generic print: " << value << std::endl;
}
// 为 int 类型提供特化版本
template <>
void print<int>(const int& value) {
std::cout << "Specialized print for int: " << value * 2 << std::endl;
}
// 为 std::string 类型提供特化版本
template <>
void print<std::string>(const std::string& value) {
std::cout << "Specialized print for string: " << value.length() << std::endl;
}
int main() {
print(5); // 输出: Specialized print for int: 10
print(3.14); // 输出: Generic print: 3.14
print(std::string("World")); // 输出: Specialized print for string: 5
return 0;
}
4.2. 类模板 (进阶)
4.2.1. 多个模板参数
类模板也可以接受多个类型参数。
C++
#include <iostream>
template <typename T1, typename T2>
class Pair {
public:
Pair(const T1& first, const T2& second) : first_(first), second_(second) {}
void print() const {
std::cout << "First: " << first_ << ", Second: " << second_ << std::endl;
}
private:
T1 first_;
T2 second_;
};
int main() {
Pair<int, std::string> myPair(100, "Example");
myPair.print();
return 0;
}
4.2.2. 成员函数模板
类模板的成员函数也可以是模板。
C++
#include <iostream>
template <typename T>
class MyArray {
private:
T* data_;
int size_;
public:
MyArray(int size) : size_(size), data_(new T[size]()) {}
~MyArray() { delete[] data_; }
template <typename U>
void fill(const U& value) {
for (int i = 0; i < size_; ++i) {
data_[i] = static_cast<T>(value);
}
}
void print() const {
for (int i = 0; i < size_; ++i) {
std::cout << data_[i] << " ";
}
std::cout << std::endl;
}
};
int main() {
MyArray<double> doubleArray(5);
doubleArray.fill(3); // 使用 int 类型填充 double 数组
doubleArray.print();
return 0;
}
4.2.3. 类模板的特化
类似于函数模板,类模板也可以进行特化,为特定的类型提供不同的实现。可以特化整个类,也可以只特化类的某些成员函数。
C++
#include <iostream>
#include <string>
template <typename T>
class Printer {
public:
void print(const T& value) {
std::cout << "Generic Printer: " << value << std::endl;
}
};
// 特化 Printer<std::string>
template <>
class Printer<std::string> {
public:
void print(const std::string& value) {
std::cout << "String Printer: Length = " << value.length() << ", Value = " << value << std::endl;
}
};
int main() {
Printer<int> intPrinter;
intPrinter.print(123); // 输出: Generic Printer: 123
Printer<std::string> stringPrinter;
stringPrinter.print("Hello"); // 输出: String Printer: Length = 5, Value = Hello
return 0;
}
4.2.4. 模板元编程 (简介)
模板元编程是一种在编译时使用模板生成代码的技术。它允许在编译阶段执行一些计算和逻辑,可以用于提高程序的性能和灵活性。模板元编程通常涉及复杂的模板技巧和类型操作。
5. 智能指针
5.1. std::unique_ptr
(进阶)
5.1.1. 自定义删除器 (Custom Deleters)
std::unique_ptr
允许您指定一个自定义的删除器,当 unique_ptr
被销毁时,将调用该删除器而不是默认的 delete
运算符。这对于管理不是通过 new
分配的资源(例如,文件句柄、自定义的内存分配器分配的内存)非常有用。
C++
#include <iostream>
#include <memory>
#include <fstream>
// 自定义删除器,用于关闭文件
struct FileDeleter {
void operator()(std::ofstream* file) const {
if (file && file->is_open()) {
file->close();
std::cout << "File closed." << std::endl;
}
delete file;
}
};
int main() {
std::unique_ptr<std::ofstream, FileDeleter> logFile(new std::ofstream("log.txt"));
if (logFile) {
*logFile << "This is a log message." << std::endl;
}
// 当 logFile 离开作用域时,FileDeleter 会被调用,关闭文件。
return 0;
}
5.1.2. unique_ptr
和数组
std::unique_ptr
可以用于管理动态分配的数组,需要使用 unique_ptr<T[]>
的形式,并且会自动使用 delete[]
来释放内存。
C++
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int[]> intArray(new int[5]);
for (int i = 0; i < 5; ++i) {
intArray[i] = i * 2;
}
for (int i = 0; i < 5; ++i) {
std::cout << intArray[i] << " ";
}
std::cout << std::endl;
// 当 intArray 离开作用域时,delete[] 会被调用。
return 0;
}
5.2. std::shared_ptr
(进阶)
5.2.1. 循环引用和 std::weak_ptr
当两个或多个通过 std::shared_ptr
相互引用的对象形成一个环时,它们的引用计数永远不会降至零,导致内存泄漏。std::weak_ptr
是一种不增加引用计数的智能指针,可以用来打破这种循环引用。weak_ptr
可以指向由 shared_ptr
管理的对象,但它不会阻止该对象的销毁。在使用 weak_ptr
之前,需要先将其转换为 shared_ptr
(可以使用 lock()
方法),如果原始对象已经被销毁,则转换会失败。
C++
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> b_ptr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> a_ptr; // 使用 weak_ptr 打破循环引用
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->b_ptr = b;
b->a_ptr = a;
// 如果 b_ptr 在 A 中也是 weak_ptr,或者 a_ptr 在 B 中是 shared_ptr,
// 那么当 a 和 b 离开作用域时,A 和 B 的析构函数将不会被调用,导致内存泄漏。
return 0;
}
5.2.2. std::shared_ptr
的线程安全性
std::shared_ptr
的控制块(包含引用计数和删除器)是线程安全的,允许多个线程安全地增加和减少引用计数。然而,通过同一个 shared_ptr
访问所管理的对象本身并不是线程安全的,需要额外的同步机制(例如,互斥锁)。
6. 调试技巧
6.1. 如何使用调试器 (更详细)
6.1.1. 常用调试器命令和操作 (以 GDB 和 Visual Studio 为例)
GDB (GNU Debugger):
break <file>:<line>
或b <file>:<line>
: 在指定文件和行号设置断点。run
或r
: 运行程序。next
或n
: 执行下一行代码(不进入函数调用)。step
或s
: 执行下一行代码(如果当前行是函数调用,则进入函数内部)。finish
: 执行完当前函数并返回。continue
或c
: 继续执行程序直到下一个断点或程序结束。print <variable>
或p <variable>
: 打印变量的值。watch <expression>
: 设置监视点,当表达式的值发生变化时暂停程序。backtrace
或bt
: 显示当前的函数调用堆栈。frame <number>
: 切换到指定堆栈帧。info locals
: 显示当前作用域的局部变量。quit
或q
: 退出 GDB。
Visual Studio Debugger:
- 设置断点: 在代码行号旁边的空白区域单击,或选中一行代码后按 F9。
- 启动调试: 按 F5 或选择“调试” -> “开始调试”。
- 单步执行:
- Step Over (F10): 执行当前行,然后跳到下一行。
- Step Into (F11): 如果当前行是函数调用,则进入该函数内部。
- Step Out (Shift + F11): 执行完当前函数并返回。
- Continue (F5): 继续执行程序。
- 查看变量: 在“局部变量”、“监视”窗口中查看变量的值。
- 调用堆栈: 在“调用堆栈”窗口中查看函数调用序列。
- 条件断点: 右键单击断点,选择“条件”,设置断点触发的条件。
- 数据断点: 可以在变量的值发生改变时触发断点(右键单击变量,选择“当值更改时中断”)。
6.1.2. 高级调试技巧
- 使用日志记录: 在关键代码路径中添加日志输出,以便在不使用调试器的情况下也能追踪程序行为。
- 二分查找法调试: 如果您不知道错误发生在哪里,可以尝试在代码中间设置断点,逐步缩小错误范围。
- 单元测试: 编写单元测试可以帮助您在早期发现和修复代码中的错误。
- 代码审查: 让其他开发人员检查您的代码,可以发现您可能忽略的错误。
- 使用静态分析工具: 静态分析工具可以在不运行代码的情况下检测潜在的错误和代码质量问题。
6.1.3. 使用断言 (assert
)
assert
是一个预处理宏,用于在运行时检查条件是否为真。如果条件为假,程序会终止并显示错误消息。断言通常用于在开发阶段检查代码中的假设是否成立。在发布版本中,断言通常会被禁用。
C++
#include <iostream>
#include <cassert>
int divide(int a, int b) {
assert(b != 0); // 断言:除数不能为零
return a / b;
}
int main() {
int result = divide(10, 2);
std::cout << "Result: " << result << std::endl;
// divide(5, 0); // 这会触发断言,导致程序终止
return 0;
}
7. 构建系统
7.1. CMake (简介) (更详细)
7.1.1. 更复杂的 CMakeLists.txt
示例
假设您的项目包含多个源文件和一个头文件目录。
CMake
cmake_minimum_required(VERSION 3.10)
project(MyAdvancedProject)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 指定头文件搜索路径
include_directories(include)
# 添加可执行文件,源文件包括 main.cpp 和 utils.cpp
add_executable(MyAdvancedProject main.cpp utils.cpp)
# 如果需要链接库,可以使用 target_link_libraries
# target_link_libraries(MyAdvancedProject my_library)
7.1.2. 外部依赖管理
CMake 可以帮助管理外部库的依赖。常用的方法包括:
find_package()
: 用于查找系统中已安装的库。WorkspaceContent
模块: 允许在构建过程中下载和构建外部库。- 子模块 (Submodules): 将外部库作为 Git 子模块添加到项目中。
7.1.3. 构建类型 (Debug, Release)
CMake 支持不同的构建类型,通常包括 Debug 和 Release。Debug 构建包含调试信息,而 Release 构建会进行优化以提高性能。您可以在配置 CMake 时指定构建类型:
Bash
cmake -DCMAKE_BUILD_TYPE=Debug ..
# 或者
cmake -DCMAKE_BUILD_TYPE=Release ..
7.1.4. 其他构建系统简介
除了 CMake,还有其他一些流行的构建系统:
- Make: 一个经典的构建工具,使用 Makefile 来描述构建规则。
- Ninja: 一个小型且快速的构建系统,通常由其他构建系统(如 CMake)生成构建文件。
- Meson: 另一个跨平台的构建系统,旨在提供快速且用户友好的构建体验。
- Gradle 和 Maven: 主要用于 Java 项目,但也支持 C/C++ 项目。