目录
泛型编程是一种编程范式,它允许编写出适用于多种数据类型的通用代码,从而实现代码的通用性和复用性。在 C++ 中,泛型编程主要通过模板来实现。
C++ 中使用模板来实现泛型编程。模板是一种用于创建通用类或函数的蓝图,在使用时可以用具体的类型来替代其中的类型参数。C++ 中有函数模板和类模板两种形式。
函数模板允许我们定义一个通用的函数,其中的某些参数或返回值的类型可以是模板参数。例如:
- template <typename T>
- T Max(T a, T b) {
- return a > b ? a : b;
- }
在这个例子中,Max 是一个模板函数,它可以接受任意类型的参数,并返回它们中的最大值。
类模板允许我们定义一个通用的类,其中的某些成员变量或成员函数的类型可以是模板参数。例如:
- template <typename T>
- class Stack {
- public:
- void Push(T value);
- T Pop();
- // ...
- };
在这个例子中,Stack 是一个模板类,它可以存储任意类型的数据。
总之,泛型编程是 C++ 中非常强大的特性,能够提高代码的通用性和复用性。合理地使用泛型编程可以使代码更加灵活、可扩展和易于维护。
STL(标准模板库)是C++标准库的一部分,提供了许多常用的数据结构和算法。STL包括容器、迭代器、算法等组件,可以帮助C++程序员编写高效、可维护的代码。
当谈到C++ STL中的容器(Containers)时,我们指的是一系列类模板,用于存储和管理数据。STL提供了多种不同类型的容器,每种都有其特定的特性和用途。以下是对常见容器的简要介绍以及示例代码:
1、向量(vector):向量是一个动态数组,可以根据需要自动扩展其大小。它支持快速的随机访问,并且在末尾插入和删除元素的操作也很高效。
- #include
- #include
- using namespace std;
-
- int main() {
- vector<int> 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这两个重要的属性。
当然,这两个属性分别对应两个方法:resize() 和 reserve()。
通过resize()方法可以改变容器中元素的数量,并实际分配相应大小的内存空间;而reserve()方法仅仅是修改了capacity的值,并没有分配实际的内存空间。因此,在使用reserve()后,如果直接使用[]操作符访问容器内的对象,很可能会出现数组越界的问题。这是一个非常重要的注意事项。
区别:
以下是一个简单的代码示例:
- #include
- #include
-
- int main() {
- // 创建一个空的 vector
- std::vector<int> 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<int> 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<int> 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<int> 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
int> 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<int>::iterator it = v.begin();
- cout << *it; // 解引用操作
- ++it; // 后缀自增操作
2、输出迭代器(Output Iterator):
输出迭代器能够向容器中写入元素值,但不能读取它们。它也支持后缀自增操作符++,解引用操作符*,以及相等和不等操作符==和!=等。
示例代码:
- vector<int>::iterator it = v.begin();
- *it = 10; // 向容器中写入元素值
- ++it;
3、前向迭代器(Forward Iterator):
前向迭代器具有输入和输出迭代器的所有功能,并且可以多次遍历同一容器。这意味着它可以多次使用自增操作符将迭代器移向容器中的下一个元素。
示例代码:
- forward_list<int>::iterator it = flist.begin();
- cout << *it; // 解引用操作
- ++it; // 后缀自增操作
4、双向迭代器(Bidirectional Iterator):
双向迭代器具有前向迭代器的所有功能,并且还支持反向遍历。它可以使用自减操作符--将迭代器移向容器中的前一个元素。
示例代码:
- list<int>::iterator it = l.begin();
- cout << *it; // 解引用操作
- ++it; // 后缀自增操作
- --it; // 自减操作
5、随机访问迭代器(Random Access Iterator):
随机访问迭代器具有双向迭代器的所有功能,并且还支持像指针一样的随机存取操作。这意味着它可以使用偏移量进行快速的跳跃式访问容器中的元素。
示例代码:
- vector<int>::iterator it = v.begin();
- cout << *(it + 3); // 随机访问,访问第四个元素
示例代码
- #include
- #include
- #include
-
- int main() {
- std::vector<int> vec = {1, 2, 3, 4, 5};
-
- // 使用迭代器查找特定元素并进行修改
- std::vector<int>::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::for_each 是C++ STL中的一个算法,它允许我们对容器(例如向量、数组)中的每个元素执行指定的操作。该算法通常与函数对象(function object)或者 Lambda 表达式结合使用,用于对容器中的每个元素进行自定义的处理。
函数签名
- template< class InputIt, class UnaryFunction >
- UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
示例代码
下面是一个简单的示例代码,演示了如何使用 std::for_each 算法来对向量中的每个元素进行操作:
- #include
- #include
- #include
-
- void printSquare(int n) {
- std::cout << n * n << " ";
- }
-
- int main() {
- std::vector<int> 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)是 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() 运算符,使得类对象可以像函数一样被调用,从而实现更灵活的编程和操作。
在 C++ 中,适配器(Adapters)是一种常见的模式,用于将一种接口转换成另一种接口,从而使得原本不兼容的组件能够协同工作。在 C++ 标准库中,容器适配器和迭代器适配器是两种常见的适配器类型。
容器适配器是指一种特殊的容器,它们提供了一种不同的接口,但实际上底层数据结构是由其他容器来支持的。C++ 标准库中包含了三种常见的容器适配器:stack(栈)、queue(队列)和 priority_queue(优先队列)。
这些容器适配器使用了不同的底层数据结构,例如 deque 或 vector,但提供了与栈、队列或优先队列相对应的操作接口,使得开发人员可以方便地使用它们进行栈、队列或优先队列的操作。
以下是一个简单的示例,演示了如何使用 stack 容器适配器实现后进先出(LIFO)的栈操作:
- #include
- #include
-
- int main() {
- std::stack<int> 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<int> 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。
总结而言,容器适配器和迭代器适配器都是非常有用的工具,它们可以帮助开发人员在使用容器和迭代器时更加灵活和高效地完成各种操作。
C++11 引入了
以下是一个简单的示例,演示了如何创建和使用元组:
- #include
- #include
-
- int main() {
- // 创建一个包含整数、字符串和浮点数的元组
- std::tuple<int, std::string, double> 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 类是 C++11 标准库中用于表示正则表达式的类。它提供了一组功能,可以用来进行文本的匹配、搜索和替换操作。
主要功能
构造函数:
匹配和搜索:
替换:
迭代器:
以下是一些常用的正则表达式模式及其含义:
除了这些基本的元字符外,还可以使用量词(如 *、+、?)来表示重复次数,以及括号来表示分组等。例如,"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):
2、时间点(Time Points):
3、持续时间(Durations):
4、时钟转换:
5、时间操作:
以下是一个简单的示例,演示了如何使用
- #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<double> elapsed = end - start;
- std::cout << "运行时间: " << elapsed.count() << " 秒\n";
-
- return 0;
- }
在这个示例中,我们使用
总之,