学习c++的第十七天

目录

泛型

泛型编程的优点

泛型编程的缺点

C++ 中的泛型编程

函数模板

类模板

使用泛型的注意事项

STL(标准模板库)

容器

迭代器

算法

函数对象(Functors)

适配器(Adapters)

容器适配器

迭代器适配器

其他组件

元组(Tuple)

正则表达式

时间和日期

C++ 有用的网站


泛型

泛型编程是一种编程范式,它允许编写出适用于多种数据类型的通用代码,从而实现代码的通用性和复用性。在 C++ 中,泛型编程主要通过模板来实现。

泛型编程的优点

  1. 代码复用:泛型编程允许我们编写一次代码,然后可以用于多种不同的数据类型,提高了代码的复用性。
  2. 类型安全:通过泛型编程,我们可以在编译时进行类型检查,避免在运行时出现类型错误。
  3. 抽象能力:泛型编程可以帮助我们更好地表达抽象概念,提高代码的可读性和易维护性。
  4. 性能优化:泛型代码可以在不损失性能的情况下适应不同的数据类型,减少了重复编写针对特定类型的代码,也有助于优化性能。

泛型编程的缺点

  1. 编译时间增加:使用泛型编程可能会导致编译时间的增加,特别是在涉及大量模板实例化的情况下。因为模板代码通常会在每次使用时都进行实例化,而不同的实例化可能会导致生成大量重复的代码,从而增加了编译时间。
  2. 可读性和维护性:泛型代码可能比非泛型代码更加抽象和复杂,这可能影响代码的可读性和维护性。特别是在使用复杂的模板技术、嵌套模板或者元编程时,代码可能会变得难以理解和调试。
  3. 错误信息可读性差:模板错误信息通常会比较晦涩难懂,当出现模板相关的编译错误时,可能需要花费更多的时间来定位和解决问题。
  4. 代码膨胀:由于模板代码会在每次实例化时生成新的代码,这可能会导致生成的可执行文件变得更大,从而增加了存储和加载的开销。
  5. 隐式实例化:在某些情况下,编译器会隐式实例化模板,这可能会导致意想不到的结果或者潜在的性能问题。

C++ 中的泛型编程

C++ 中使用模板来实现泛型编程。模板是一种用于创建通用类或函数的蓝图,在使用时可以用具体的类型来替代其中的类型参数。C++ 中有函数模板和类模板两种形式。

函数模板

函数模板允许我们定义一个通用的函数,其中的某些参数或返回值的类型可以是模板参数。例如:

template 
T Max(T a, T b) {
    return a > b ? a : b;
}

在这个例子中,Max 是一个模板函数,它可以接受任意类型的参数,并返回它们中的最大值。

类模板

类模板允许我们定义一个通用的类,其中的某些成员变量或成员函数的类型可以是模板参数。例如:

template 
class Stack {
public:
    void Push(T value);
    T Pop();
    // ...
};

在这个例子中,Stack 是一个模板类,它可以存储任意类型的数据。

使用泛型的注意事项

  • 类型推导:C++ 11 引入了自动类型推导 auto 和统一初始化语法 {},可以简化泛型代码的书写。
  • 特化和偏特化:可以为特定的数据类型提供特殊化的实现,以处理特定类型的情况。
  • 编译时间开销:泛型代码可能会增加编译时间和生成的代码大小,尤其是在模板被实例化多次的情况下。

总之,泛型编程是 C++ 中非常强大的特性,能够提高代码的通用性和复用性。合理地使用泛型编程可以使代码更加灵活、可扩展和易于维护。

STL(标准模板库)

STL(标准模板库)是C++标准库的一部分,提供了许多常用的数据结构和算法。STL包括容器、迭代器、算法等组件,可以帮助C++程序员编写高效、可维护的代码。

容器

当谈到C++ STL中的容器(Containers)时,我们指的是一系列类模板,用于存储和管理数据。STL提供了多种不同类型的容器,每种都有其特定的特性和用途。以下是对常见容器的简要介绍以及示例代码:

1、向量(vector):向量是一个动态数组,可以根据需要自动扩展其大小。它支持快速的随机访问,并且在末尾插入和删除元素的操作也很高效。

#include 
#include 
using namespace std;

int main() {
    vector v; // 创建一个整数向量
    v.push_back(1); // 在向量末尾插入元素1
    v.push_back(2); // 在向量末尾插入元素2
  
    for (int i = 0; i < v.size(); ++i) {
        cout << v[i] << " "; // 遍历并输出向量中的元素
    }
    return 0;
}

扩展:C++ STL 之 vector 的 capacity 和 size 属性区别

在C++ STL中,vector是一个动态数组,它具有capacity和size这两个重要的属性。

  • Size(大小):指的是vector当前包含的元素数量。
  • Capacity(容量):指的是vector在重新分配内存之前能够容纳的元素数量。换句话说,它代表了当前vector分配的内存空间大小。

当然,这两个属性分别对应两个方法:resize() 和 reserve()。

通过resize()方法可以改变容器中元素的数量,并实际分配相应大小的内存空间;而reserve()方法仅仅是修改了capacity的值,并没有分配实际的内存空间。因此,在使用reserve()后,如果直接使用[]操作符访问容器内的对象,很可能会出现数组越界的问题。这是一个非常重要的注意事项。

区别:

  • 当你向vector中添加元素时,如果size达到了capacity,vector就需要重新分配内存以增加容量。这意味着vector的capacity会增加,而size会相应地增加。
  • 一般情况下,size小于等于capacity。当vector的size等于capacity时,再添加新元素会触发内存重新分配和复制,导致性能开销。因此,为了避免频繁的内存重新分配,通常会在必要时通过vector::reserve()方法提前预留一定的容量,从而减少重新分配的次数。

以下是一个简单的代码示例:

#include 
#include 

int main() {
    // 创建一个空的 vector
    std::vector myVector;

    // 打印当前 size 和 capacity
    std::cout << "Size: " << myVector.size() << std::endl;
    std::cout << "Capacity: " << myVector.capacity() << std::endl;

    // 使用 resize 方法改变容器大小
    myVector.resize(10); // 将容器大小改为 10
    std::cout << "Size after resize: " << myVector.size() << std::endl;

    // 使用 reserve 方法修改容器的 capacity
    myVector.reserve(20); // 修改容器的 capacity 为 20
    std::cout << "Capacity after reserve: " << myVector.capacity() << std::endl;

    return 0;
}

在这个简单的示例中,我们创建了一个空的vector,并使用size()和capacity()方法来打印当前的size和capacity。然后,我们使用resize()方法将容器的大小改变为10,并使用reserve()方法将容器的capacity修改为20。通过运行这段代码,你可以更好地理解size、capacity、resize()和reserve()之间的关系。 

总之,size表示实际元素的数量,而capacity表示当前内存空间的大小。要注意这两个属性的变化,以优化vector的性能和内存使用。

2、列表(list):列表是一个双向链表,支持在任意位置高效地插入和删除元素。然而,与向量相比,它的随机访问效率较低。

#include 
#include 
using namespace std;

int main() {
    list l; // 创建一个整数列表
    l.push_back(1); // 在列表末尾插入元素1
    l.push_front(2); // 在列表头部插入元素2
    
    for (auto it = l.begin(); it != l.end(); ++it) {
        cout << *it << " "; // 使用迭代器遍历并输出列表中的元素
    }
    return 0;
}

3、双端队列(deque):双端队列支持在两端进行快速插入和删除操作,是一个允许高效随机存取的序列容器。它结合了向量和列表的优点。

#include 
#include 
using namespace std;

int main() {
    deque d; // 创建一个整数双端队列
    d.push_back(1); // 在队列尾部插入元素1
    d.push_front(2); // 在队列头部插入元素2
    
    for (int i = 0; i < d.size(); ++i) {
        cout << d[i] << " "; // 遍历并输出双端队列中的元素
    }
    return 0;
}

4、集合(set):集合是一个基于红黑树实现的关联容器,其中的元素按照排序顺序排列。它不允许重复元素的存在。

#include 
#include 
using namespace std;

int main() {
    set s; // 创建一个整数集合
    s.insert(3); // 向集合中插入元素3
    s.insert(1); // 向集合中插入元素1
    s.insert(2); // 向集合中插入元素2
    
    for (auto it = s.begin(); it != s.end(); ++it) {
        cout << *it << " "; // 使用迭代器遍历并输出集合中的元素
    }
    return 0;
}

5、映射(map):映射是一个关联容器,存储键-值对,并按照键的排序顺序进行组织。每个键只能在映射中出现一次。

#include 
#include 
using namespace std;

int main() {
    map m; // 创建一个从字符串到整数的映射
    m["one"] = 1; // 插入键值对
    m["two"] = 2; // 插入键值对
    m["three"] = 3; // 插入键值对
    
    for (auto it = m.begin(); it != m.end(); ++it) {
        cout << it->first << ": " << it->second << endl; // 遍历并输出映射中的键值对
    }
    return 0;
}

迭代器

迭代器(Iterators)是C++ STL中用来遍历容器中元素的重要工具,它提供了一种统一的访问容器元素的方式,使得算法可以独立于容器而操作。迭代器实际上类似于指针,可以指向容器中的元素,并且支持类似指针的操作,例如解引用、自增、自减等。C++ STL中的迭代器被设计为一种泛型的概念,可以应用于不同类型的容器。

迭代器分类
在C++ STL中,迭代器按照其功能和特性可以分为不同的分类:

1、输入迭代器(Input Iterator):

输入迭代器能够读取容器中的元素值,但不能修改它们。它支持后缀自增操作符++,解引用操作符*,以及相等和不等操作符==和!=等。

示例代码:

vector::iterator it = v.begin();
cout << *it;  // 解引用操作
++it;         // 后缀自增操作

2、输出迭代器(Output Iterator):

输出迭代器能够向容器中写入元素值,但不能读取它们。它也支持后缀自增操作符++,解引用操作符*,以及相等和不等操作符==和!=等。

示例代码:

vector::iterator it = v.begin();
*it = 10;  // 向容器中写入元素值
++it;

3、前向迭代器(Forward Iterator):

前向迭代器具有输入和输出迭代器的所有功能,并且可以多次遍历同一容器。这意味着它可以多次使用自增操作符将迭代器移向容器中的下一个元素。

示例代码:

forward_list::iterator it = flist.begin();
cout << *it;  // 解引用操作
++it;         // 后缀自增操作

4、双向迭代器(Bidirectional Iterator):

双向迭代器具有前向迭代器的所有功能,并且还支持反向遍历。它可以使用自减操作符--将迭代器移向容器中的前一个元素。

示例代码:

list::iterator it = l.begin();
cout << *it;  // 解引用操作
++it;         // 后缀自增操作
--it;         // 自减操作

5、随机访问迭代器(Random Access Iterator):

随机访问迭代器具有双向迭代器的所有功能,并且还支持像指针一样的随机存取操作。这意味着它可以使用偏移量进行快速的跳跃式访问容器中的元素。

示例代码:

vector::iterator it = v.begin();
cout << *(it + 3);  // 随机访问,访问第四个元素

示例代码

#include 
#include 
#include 

int main() {
    std::vector vec = {1, 2, 3, 4, 5};

    // 使用迭代器查找特定元素并进行修改
    std::vector::iterator it = std::find(vec.begin(), vec.end(), 3);  // 查找值为3的元素

    if (it != vec.end()) {
        std::cout << "找到元素:" << *it << std::endl;
        *it = 30;  // 修改找到的元素的值
        std::cout << "修改后的元素:" << *it << std::endl;
    } else {
        std::cout << "未找到元素3" << std::endl;
    }

    // 输出修改后的向量
    std::cout << "修改后的向量:";
    for (const auto& element : vec) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们首先创建了一个整数类型的向量 vec,然后使用 std::find 算法和迭代器来查找值为3的元素。如果找到了该元素,我们就通过迭代器进行修改,并输出修改后的向量内容。

算法

C++ STL中的算法(Algorithms)部分包含了丰富而强大的算法库,提供了大量用于处理容器内容的常用算法,例如查找、排序、遍历、修改等。这些算法都被设计成可以适用于不同类型的容器,并且能够与迭代器紧密配合,实现了算法和数据结构的分离。

算法分类

C++ STL中的算法可以按照其功能进行分类,常见的算法包括但不限于:

  • 非修改序列操作算法:例如 std::find 用于在序列中查找元素,std::count 用于计算指定值出现的次数,std::all_of 用于检查序列中所有元素是否满足特定条件等。
  • 修改序列操作算法:例如 std::copy 用于将一个序列的元素复制到另一个序列,std::fill 用于将序列中的元素赋予特定值,std::transform 用于对序列中的元素进行转换等。
  • 排序和相关算法:例如 std::sort 用于对序列进行排序,std::binary_search 用于二分查找,std::merge 用于合并两个有序序列等。
  • 数值操作算法:例如 std::accumulate 用于对序列中的元素进行累积操作,std::inner_product 用于计算两个序列的内积,std::partial_sum 用于计算部分和等。
  • 集合操作算法:例如 std::set_union 用于求两个集合的并集,std::set_intersection 用于求两个集合的交集,std::set_difference 用于求两个集合的差集等。

扩展

std::for_each 是C++ STL中的一个算法,它允许我们对容器(例如向量、数组)中的每个元素执行指定的操作。该算法通常与函数对象(function object)或者 Lambda 表达式结合使用,用于对容器中的每个元素进行自定义的处理。

函数签名

template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
  • InputIt:表示输入迭代器类型,指向容器中第一个元素的迭代器。
  • UnaryFunction:表示函数对象或者 Lambda 表达式类型,用于对容器中的每个元素执行特定的操作。
  • first 和 last:表示容器中待处理元素的范围,即 [first, last) 为有效区间,包括 first 而不包括 last。
  • f:表示要应用于每个元素的函数对象或 Lambda 表达式。 

示例代码

下面是一个简单的示例代码,演示了如何使用 std::for_each 算法来对向量中的每个元素进行操作:

#include 
#include 
#include 

void printSquare(int n) {
    std::cout << n * n << " ";
}

int main() {
    std::vector vec = {1, 2, 3, 4, 5};

    // 使用 for_each 算法对向量中的每个元素进行平方操作并输出
    std::for_each(vec.begin(), vec.end(), printSquare);

    return 0;
}

在这个示例中,我们定义了一个函数 printSquare,它接受一个整数参数并输出其平方值。然后我们使用 std::for_each 算法,对向量 vec 中的每个元素调用 printSquare 函数,从而实现对每个元素进行平方操作并输出。 

函数对象(Functors)

函数对象(Functors)是 C++ 中的一个重要概念,它实际上就是重载了函数调用操作符 operator() 的类。通过重载 operator(),函数对象可以像函数一样被调用,从而实现自定义的操作。这种特性使得函数对象在 STL 算法、泛型编程和其他场景中非常有用。

下面是一个简单的示例,演示了如何定义一个函数对象,并在其中重载 operator() 运算符:

#include 

// 定义一个函数对象 Add
class Add {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};

int main() {
    Add add; // 创建函数对象实例
    std::cout << add(3, 4) << std::endl; // 调用函数对象
    return 0;
}

在上面的示例中,我们定义了一个名为 Add 的函数对象类,它重载了 operator() 运算符,用于对两个整数进行相加操作。在 main 函数中,我们创建了 Add 类的一个实例 add,并使用 add(3, 4) 来调用函数对象,得到结果输出为 7。

函数对象可以包含状态,因为它们是类的实例,可以拥有成员变量。这意味着函数对象可以用来代替函数指针,并且可以更灵活地在运行时保存和操纵状态。

总结而言,函数对象是一种将函数调用封装在类对象中的方式,通过重载 operator() 运算符,使得类对象可以像函数一样被调用,从而实现更灵活的编程和操作。

适配器(Adapters)

在 C++ 中,适配器(Adapters)是一种常见的模式,用于将一种接口转换成另一种接口,从而使得原本不兼容的组件能够协同工作。在 C++ 标准库中,容器适配器和迭代器适配器是两种常见的适配器类型。

容器适配器

容器适配器是指一种特殊的容器,它们提供了一种不同的接口,但实际上底层数据结构是由其他容器来支持的。C++ 标准库中包含了三种常见的容器适配器:stack(栈)、queue(队列)和 priority_queue(优先队列)。

这些容器适配器使用了不同的底层数据结构,例如 deque 或 vector,但提供了与栈、队列或优先队列相对应的操作接口,使得开发人员可以方便地使用它们进行栈、队列或优先队列的操作。

以下是一个简单的示例,演示了如何使用 stack 容器适配器实现后进先出(LIFO)的栈操作:

#include 
#include 

int main() {
    std::stack myStack;

    myStack.push(1);
    myStack.push(2);
    myStack.push(3);

    while (!myStack.empty()) {
        std::cout << myStack.top() << " "; // 输出栈顶元素
        myStack.pop(); // 弹出栈顶元素
    }

    return 0;
}

在这个示例中,我们使用了 std::stack 容器适配器,它提供了 push、pop、top 等操作,实现了栈的基本功能。

迭代器适配器

迭代器适配器是一种用于修改迭代器行为的工具,它们可以改变迭代器的行为或范围,以满足特定的需求。C++ 标准库中包含了多种迭代器适配器,例如 std::reverse_iterator、std::back_insert_iterator、std::front_insert_iterator 等。

一个常见的迭代器适配器是 std::reverse_iterator,它可以用来逆向遍历容器的元素。下面是一个简单的示例:

#include 
#include 
#include 

int main() {
    std::vector vec = { 1, 2, 3, 4, 5 };

    // 使用 reverse_iterator 逆向输出向量元素
    for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
        std::cout << *it << " ";
    }

    return 0;
}

在这个示例中,我们首先创建了一个包含整数的向量 vec,然后使用 std::reverse_iterator 适配器将正向迭代器 vec.rbegin() 转换成逆向迭代器,通过 vec.rend() 表示结束位置。在 for 循环中,我们使用逆向迭代器 it 逆向遍历向量 vec 的元素,并输出每个元素的值。最终的输出结果将是:5 4 3 2 1。

总结而言,容器适配器和迭代器适配器都是非常有用的工具,它们可以帮助开发人员在使用容器和迭代器时更加灵活和高效地完成各种操作。

其他组件

元组(Tuple)

C++11 引入了 头文件,其中定义了 std::tuple 类模板,允许将多个值组合成一个单一的结构。通过元组,我们可以在不使用类或结构体的情况下,方便地处理多个值,并且可以很容易地对其进行解包和访问。

以下是一个简单的示例,演示了如何创建和使用元组:

#include 
#include 

int main() {
    // 创建一个包含整数、字符串和浮点数的元组
    std::tuple myTuple(1, "Hello", 3.14);

    // 访问元组中的元素
    std::cout << "第一个元素: " << std::get<0>(myTuple) << std::endl;
    std::cout << "第二个元素: " << std::get<1>(myTuple) << std::endl;
    std::cout << "第三个元素: " << std::get<2>(myTuple) << std::endl;

    return 0;
}

在这个示例中,我们创建了一个包含整数、字符串和浮点数的元组 myTuple,并使用 std::get 函数访问其中的元素。元组可以帮助我们方便地组合和处理多个不同类型的值。

正则表达式

C++11 引入了对正则表达式的支持,通过 头文件,我们可以使用 std::regex 类来表示正则表达式,从而可以进行文本匹配和搜索操作。

std::regex 类是 C++11 标准库中用于表示正则表达式的类。它提供了一组功能,可以用来进行文本的匹配、搜索和替换操作。

主要功能
构造函数:

  • 可以使用不同的构造函数创建 std::regex 对象,传入表示正则表达式的字符串即可。

匹配和搜索:

  • std::regex_search:在输入字符串中搜索与正则表达式匹配的子串。
  • std::regex_match:检查整个输入字符串是否与正则表达式匹配。

替换:

  • std::regex_replace:将输入字符串中与正则表达式匹配的部分替换为指定的内容。

迭代器:

  • std::regex_iterator 类可以帮助我们在字符串中迭代匹配的子串,以便进行进一步的处理。

以下是一些常用的正则表达式模式及其含义:

  • \d:匹配一个数字字符。
  • \w:匹配一个单词字符(字母、数字或下划线)。
  • \s:匹配一个空白字符(空格、制表符等)。
  • .:匹配任意一个字符。
  • [abc]:匹配包含在方括号内的任意一个字符(a、b 或 c)。
  • [0-9]:匹配一个数字字符(0 到 9 中的任意一个)。
  • ^:匹配行的开头。
  • $:匹配行的结尾。
  • \b:匹配单词的边界。

除了这些基本的元字符外,还可以使用量词(如 *、+、?)来表示重复次数,以及括号来表示分组等。例如,"W[a-z]+" 表示匹配以大写字母 W 开头,后跟一个或多个小写字母的字符串。

需要注意的是,在 C++ 的正则表达式中,通常需要使用双反斜杠(\)来转义特殊字符,例如 \\d 表示匹配一个数字字符。

以下是一个简单的示例,演示了如何使用正则表达式进行文本匹配:

#include 
#include 

int main() {
    std::string text = "The quick brown fox jumps over the lazy dog.";

    // 定义一个正则表达式模式
    std::regex pattern("\\b\\w{5}\\b");

    // 在文本中搜索匹配的模式
    std::sregex_iterator iter(text.begin(), text.end(), pattern);
    std::sregex_iterator end;

    // 遍历匹配的子串并输出
    while (iter != end) {
        std::smatch match = *iter;
        std::cout << "匹配单词: " << match.str() << std::endl;
        ++iter;
    }

    return 0;
}

在这个示例中,我们使用 std::regex 类创建了一个包含正则表达式模式的对象 pattern,然后使用 std::sregex_iterator 遍历文本中匹配的子串,并输出每个匹配的单词(长度为 5 的单词)。

std::regex 类提供了丰富的功能,可以帮助我们进行各种复杂的文本匹配、搜索和替换操作。

时间和日期

C++11 引入了 头文件,提供了对时间和日期的支持,包括时钟、时间点、时间间隔等概念,以及各种与时间相关的函数和类。

主要组成部分
1、时钟类型(Clocks):
头文件定义了三种时钟类型:

  • std::chrono::system_clock:提供当前的系统时间。
  • std::chrono::steady_clock:适合用于度量时间间隔,它不会因系统时钟调整而改变。
  • std::chrono::high_resolution_clock:提供最高可用的时钟精度。

2、时间点(Time Points):

  • std::chrono::time_point:表示特定时钟类型上的时间点。

3、持续时间(Durations):

  • std::chrono::duration:表示时间间隔的持续时间。

4、时钟转换:

  • std::chrono::time_point_cast:用于在不同的时钟类型之间进行时间点的转换。

5、时间操作:

  • std::chrono::duration_cast:用于将持续时间从一个单位转换为另一个单位。
  • std::chrono::time_point::operator+ 和 std::chrono::time_point::operator-:用于时间点的加法和减法运算。

以下是一个简单的示例,演示了如何使用 头文件中的一些类和函数:

#include 
#include 

int main() {
    // 获取当前系统时间
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();

    // 转换为时间戳
    std::time_t now_c;
    if (std::time(&now_c) != -1) {
        char buffer[26];
        if (ctime_s(buffer, 26, &now_c) == 0) {
            std::cout << "当前时间: " << buffer;
        }
    }

    // 计算程序执行时间
    auto start = std::chrono::high_resolution_clock::now();
    // 执行一些耗时操作
    for (int i = 0; i < 1000000; ++i) {}
    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration elapsed = end - start;
    std::cout << "运行时间: " << elapsed.count() << " 秒\n";

    return 0;
}

在这个示例中,我们使用 头文件中的 std::chrono::system_clock 和 std::chrono::high_resolution_clock 获取当前系统时间和计算程序执行时间。通过这些类和函数,我们可以方便地处理时间和日期,并进行时间间隔的计算。

总之, 头文件为 C++ 提供了强大的时间处理能力,使得在程序中处理时间和日期变得更加简单和灵活。

C++ 有用的网站

  • C++ Standard Library headers − C++ 标准库。
  • C++ Programming − 这本书涵盖了 C++ 语言编程、软件交互设计、C++ 语言的现实生活应用。
  • C++ FAQ − C++ 常见问题
  • Free Country − Free Country 提供了免费的 C++ 源代码和 C++ 库,这些源代码和库涵盖了压缩、存档、游戏编程、标准模板库和 GUI 编程等 C++ 编程领域。
  • C and C++ Users Group − C 和 C++ 的用户团体提供了免费的涵盖各种编程领域 C++ 项目的源代码,包括 AI、动画、编译器、数据库、调试、加密、游戏、图形、GUI、语言工具、系统编程等。

你可能感兴趣的:(C++,学习,c++,开发语言)