mirror of
https://github.com/changkun/modern-cpp-tutorial.git
synced 2025-12-16 20:27:08 +03:00
see #12: fix pandoc build fail because of regex
This commit is contained in:
@@ -136,6 +136,91 @@ and then introduces the use of the regular expression library
|
||||
through a practical example based on the main requirements of
|
||||
using regular expressions.
|
||||
|
||||
## Exercise
|
||||
|
||||
In web server development, we usually want to serve some routes that satisfy a certain condition.
|
||||
Regular expressions are one of the tools to accomplish this.
|
||||
Given the following request structure:
|
||||
|
||||
```cpp
|
||||
struct Request {
|
||||
// request method, POST, GET; path; HTTP version
|
||||
std::string method, path, http_version;
|
||||
// use smart pointer for reference counting of content
|
||||
std::shared_ptr<std::istream> content;
|
||||
// hash container, key-value dict
|
||||
std::unordered_map<std::string, std::string> header;
|
||||
// use regular expression for path match
|
||||
std::smatch path_match;
|
||||
};
|
||||
```
|
||||
|
||||
Requested resource type:
|
||||
|
||||
```cpp
|
||||
typedef std::map<
|
||||
std::string, std::unordered_map<
|
||||
std::string,std::function<void(std::ostream&, Request&)>>> resource_type;
|
||||
```
|
||||
|
||||
And server template:
|
||||
|
||||
```cpp
|
||||
template <typename socket_type>
|
||||
class ServerBase {
|
||||
public:
|
||||
resource_type resource;
|
||||
resource_type default_resource;
|
||||
|
||||
void start() {
|
||||
// TODO
|
||||
}
|
||||
protected:
|
||||
Request parse_request(std::istream& stream) const {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Please implement the member functions `start()` and `parse_request`. Enable server template users to specify routes as follows:
|
||||
|
||||
```cpp
|
||||
template<typename SERVER_TYPE>
|
||||
void start_server(SERVER_TYPE &server) {
|
||||
|
||||
// process GET request for /match/[digit+numbers], e.g. GET request is /match/abc123, will return abc123
|
||||
server.resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
string number=request.path_match[1];
|
||||
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||
};
|
||||
|
||||
// peocess default GET request; anonymous function will be called if no other matches
|
||||
// response files in folder web/
|
||||
// default: index.html
|
||||
server.default_resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
string filename = "www/";
|
||||
|
||||
string path = request.path_match[1];
|
||||
|
||||
// forbidden use `..` access content outside folder web/
|
||||
size_t last_pos = path.rfind(".");
|
||||
size_t current_pos = 0;
|
||||
size_t pos;
|
||||
while((pos=path.find('.', current_pos)) != string::npos && pos != last_pos) {
|
||||
current_pos = pos;
|
||||
path.erase(pos, 1);
|
||||
last_pos--;
|
||||
}
|
||||
|
||||
// (...)
|
||||
};
|
||||
|
||||
server.start();
|
||||
}
|
||||
```
|
||||
|
||||
An suggested solution can be found [here](../../exercises/6).
|
||||
|
||||
[Table of Content](./toc.md) | [Previous Chapter](./05-pointers.md) | [Next Chapter: Threads and Concurrency](./07-thread.md)
|
||||
|
||||
## Further Readings
|
||||
|
||||
@@ -10,16 +10,18 @@ order: 4
|
||||
|
||||
[TOC]
|
||||
|
||||
## 4.1 std::array 和 std::forward\_list
|
||||
## 4.1 `std::array` 和 `std::forward_list`
|
||||
|
||||
### std::array
|
||||
### `std::array`
|
||||
|
||||
看到这个容器的时候肯定会出现这样的问题:
|
||||
|
||||
1. 为什么要引入 `std::array` 而不是直接使用 `std::vector`?
|
||||
2. 已经有了传统数组,为什么要用 `std::array`?
|
||||
|
||||
先回答第一个问题,与 `std::vector` 不同,`std::array` 对象的大小是固定的,如果容器大小是固定的,那么可以优先考虑使用 `std::array` 容器。另外由于 `std::vector` 是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作,容器并不会自动归还被删除元素相应的内存,这时候就需要手动运行 `shrink_to_fit()` 释放这部分内存。
|
||||
先回答第一个问题,与 `std::vector` 不同,`std::array` 对象的大小是固定的,如果容器大小是固定的,那么可以优先考虑使用 `std::array` 容器。
|
||||
另外由于 `std::vector` 是自动扩容的,当存入大量的数据后,并且对容器进行了删除操作,
|
||||
容器并不会自动归还被删除元素相应的内存,这时候就需要手动运行 `shrink_to_fit()` 释放这部分内存。
|
||||
|
||||
```cpp
|
||||
std::vector<int> v;
|
||||
@@ -98,21 +100,28 @@ foo(arr.data(), arr.size());
|
||||
std::sort(arr.begin(), arr.end());
|
||||
```
|
||||
|
||||
### std::forward\_list
|
||||
### `std::forward_list`
|
||||
|
||||
`std::forward_list` 是一个列表容器,使用方法和 `std::list` 基本类似,因此我们就不花费篇幅进行介绍了。
|
||||
|
||||
需要知道的是,和 `std::list` 的双向链表的实现不同,`std::forward_list` 使用单向链表进行实现,提供了 `O(1)` 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 `size()` 方法的容器。当不需要双向迭代时,具有比 `std::list` 更高的空间利用率。
|
||||
需要知道的是,和 `std::list` 的双向链表的实现不同,`std::forward_list` 使用单向链表进行实现,
|
||||
提供了 `O(1)` 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),
|
||||
也是标准库容器中唯一一个不提供 `size()` 方法的容器。当不需要双向迭代时,具有比 `std::list` 更高的空间利用率。
|
||||
|
||||
## 4.2 无序容器
|
||||
|
||||
我们已经熟知了传统 C++ 中的有序容器 `std::map`/`std::set`,这些元素内部通过红黑树进行实现,插入和搜索的平均复杂度均为 `O(log(size))`。在插入元素时候,会根据 `<` 操作符比较元素大小并判断元素是否相同,并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时,输出结果会按照 `<` 操作符的顺序来逐个遍历。
|
||||
我们已经熟知了传统 C++ 中的有序容器 `std::map`/`std::set`,这些元素内部通过红黑树进行实现,
|
||||
插入和搜索的平均复杂度均为 `O(log(size))`。在插入元素时候,会根据 `<` 操作符比较元素大小并判断元素是否相同,
|
||||
并选择合适的位置插入到容器中。当对这个容器中的元素进行遍历时,输出结果会按照 `<` 操作符的顺序来逐个遍历。
|
||||
|
||||
而无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 `O(constant)`,在不关心容器内部元素顺序时,能够获得显著的性能提升。
|
||||
而无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 `O(constant)`,
|
||||
在不关心容器内部元素顺序时,能够获得显著的性能提升。
|
||||
|
||||
C++11 引入了两组无序容器:`std::unordered_map`/`std::unordered_multimap` 和 `std::unordered_set`/`std::unordered_multiset`。
|
||||
C++11 引入了两组无序容器:`std::unordered_map`/`std::unordered_multimap` 和
|
||||
`std::unordered_set`/`std::unordered_multiset`。
|
||||
|
||||
它们的用法和原有的 `std::map`/`std::multimap`/`std::set`/`set::multiset` 基本类似,由于这些容器我们已经很熟悉了,便不一一举例,我们直接来比较一下`std::map`和`std::unordered_map`:
|
||||
它们的用法和原有的 `std::map`/`std::multimap`/`std::set`/`set::multiset` 基本类似,
|
||||
由于这些容器我们已经很熟悉了,便不一一举例,我们直接来比较一下`std::map`和`std::unordered_map`:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
@@ -147,7 +156,7 @@ int main() {
|
||||
|
||||
最终的输出结果为:
|
||||
|
||||
```
|
||||
```txt
|
||||
std::unordered_map
|
||||
Key:[2] Value:[2]
|
||||
Key:[3] Value:[3]
|
||||
@@ -159,9 +168,11 @@ Key:[2] Value:[2]
|
||||
Key:[3] Value:[3]
|
||||
```
|
||||
|
||||
## 4.3 元组 std::tuple
|
||||
## 4.3 元组 `std::tuple`
|
||||
|
||||
了解过 Python 的程序员应该知道元组的概念,纵观传统 C++ 中的容器,除了 `std::pair` 外,似乎没有现成的结构能够用来存放不同类型的数据(通常我们会自己定义结构)。但 `std::pair` 的缺陷是显而易见的,只能保存两个元素。
|
||||
了解过 Python 的程序员应该知道元组的概念,纵观传统 C++ 中的容器,除了 `std::pair` 外,
|
||||
似乎没有现成的结构能够用来存放不同类型的数据(通常我们会自己定义结构)。
|
||||
但 `std::pair` 的缺陷是显而易见的,只能保存两个元素。
|
||||
|
||||
### 元组基本操作
|
||||
|
||||
@@ -267,7 +278,8 @@ std::cout << tuple_index(t, i) << std::endl;
|
||||
auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
|
||||
```
|
||||
|
||||
马上就能够发现,应该如何快速遍历一个元组?但是我们刚才介绍了如何在运行期通过非常数索引一个 `tuple` 那么遍历就变得简单了,首先我们需要知道一个元组的长度,可以:
|
||||
马上就能够发现,应该如何快速遍历一个元组?但是我们刚才介绍了如何在运行期通过非常数索引一个 `tuple` 那么遍历就变得简单了,
|
||||
首先我们需要知道一个元组的长度,可以:
|
||||
|
||||
```cpp
|
||||
template <typename T>
|
||||
|
||||
@@ -12,21 +12,30 @@ order: 5
|
||||
|
||||
## 5.1 RAII 与引用计数
|
||||
|
||||
了解 `Objective-C`/`Swift` 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
|
||||
了解 `Objective-C`/`Swift` 的程序员应该知道引用计数的概念。引用计数这种计数是为了防止内存泄露而产生的。
|
||||
基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,
|
||||
每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。
|
||||
|
||||
在传统 C++ 中,『记得』手动释放资源,总不是最佳实践。因为我们很有可能就忘记了去释放资源而导致泄露。所以通常的做法是对于一个对象而言,我们在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间,也就是我们常说的 RAII 资源获取即初始化技术。
|
||||
在传统 C++ 中,『记得』手动释放资源,总不是最佳实践。因为我们很有可能就忘记了去释放资源而导致泄露。
|
||||
所以通常的做法是对于一个对象而言,我们在构造函数的时候申请空间,而在析构函数(在离开作用域时调用)的时候释放空间,
|
||||
也就是我们常说的 RAII 资源获取即初始化技术。
|
||||
|
||||
凡事都有例外,我们总会有需要将对象在自由存储上分配的需求,在传统 C++ 里我们只好使用 `new` 和 `delete` 去『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。这些智能指针就包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 `<memory>`。
|
||||
凡事都有例外,我们总会有需要将对象在自由存储上分配的需求,在传统 C++ 里我们只好使用 `new` 和 `delete` 去
|
||||
『记得』对资源进行释放。而 C++11 引入了智能指针的概念,使用了引用计数的想法,让程序员不再需要关心手动释放内存。
|
||||
这些智能指针就包括 `std::shared_ptr`/`std::unique_ptr`/`std::weak_ptr`,使用它们需要包含头文件 `<memory>`。
|
||||
|
||||
> 注意:引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰明确的表明资源的生命周期。
|
||||
> 注意:引用计数不是垃圾回收,引用计数能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,
|
||||
> 更能够清晰明确的表明资源的生命周期。
|
||||
|
||||
## 5.2 std::shared_ptr
|
||||
## 5.2 `std::shared_ptr`
|
||||
|
||||
`std::shared_ptr` 是一种智能指针,它能够记录多少个 `shared_ptr` 共同指向一个对象,从而消除显示的调用 `delete`,当引用计数变为零的时候就会将对象自动删除。
|
||||
`std::shared_ptr` 是一种智能指针,它能够记录多少个 `shared_ptr` 共同指向一个对象,从而消除显示的调用
|
||||
`delete`,当引用计数变为零的时候就会将对象自动删除。
|
||||
|
||||
但还不够,因为使用 `std::shared_ptr` 仍然需要使用 `new` 来调用,这使得代码出现了某种程度上的不对称。
|
||||
|
||||
`std::make_shared` 就能够用来消除显式的使用 `new`,所以`std::make_shared` 会分配创建传入参数中的对象,并返回这个对象类型的`std::shared_ptr`指针。例如:
|
||||
`std::make_shared` 就能够用来消除显式的使用 `new`,所以`std::make_shared` 会分配创建传入参数中的对象,
|
||||
并返回这个对象类型的`std::shared_ptr`指针。例如:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
@@ -47,7 +56,8 @@ int main()
|
||||
}
|
||||
```
|
||||
|
||||
`std::shared_ptr` 可以通过 `get()` 方法来获取原始指针,通过 `reset()` 来减少一个引用计数,并通过`use_count()`来查看一个对象的引用计数。例如:
|
||||
`std::shared_ptr` 可以通过 `get()` 方法来获取原始指针,通过 `reset()` 来减少一个引用计数,
|
||||
并通过`use_count()`来查看一个对象的引用计数。例如:
|
||||
|
||||
```cpp
|
||||
auto pointer = std::make_shared<int>(10);
|
||||
@@ -70,7 +80,7 @@ std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl; //
|
||||
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl; // 0, pointer3 已 reset
|
||||
```
|
||||
|
||||
## 5.3 std::unique_ptr
|
||||
## 5.3 `std::unique_ptr`
|
||||
|
||||
`std::unique_ptr` 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:
|
||||
|
||||
@@ -79,7 +89,7 @@ std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++
|
||||
std::unique_ptr<int> pointer2 = pointer; // 非法
|
||||
```
|
||||
|
||||
> make_unique 并不复杂,C++11 没有提供 std::make_unique,可以自行实现:
|
||||
> `make_unique` 并不复杂,C++11 没有提供 `std::make_unique`,可以自行实现:
|
||||
>
|
||||
> ```cpp
|
||||
> template<typename T, typename ...Args>
|
||||
|
||||
@@ -18,7 +18,8 @@ order: 6
|
||||
2. 将匹配的子串替换;
|
||||
3. 从某个串中取出符合条件的子串。
|
||||
|
||||
正则表达式是由普通字符(例如 a 到 z)以及特殊字符组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
|
||||
正则表达式是由普通字符(例如 a 到 z)以及特殊字符组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。
|
||||
正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
|
||||
|
||||
|
||||
### 普通字符
|
||||
@@ -49,12 +50,12 @@ order: 6
|
||||
|
||||
|
||||
|字符|描述|
|
||||
|:---:|:------------------------------------------------------|
|
||||
|`*`|匹配前面的子表达式零次或多次。例如,`foo*` 能匹配 `fo` 以及 `foooo`。`*` 等价于`{0,}`。|
|
||||
|`+`|匹配前面的子表达式一次或多次。例如,`foo+` 能匹配 `foo` 以及 `foooo`,但不能匹配 `fo`。`+` 等价于 `{1,}`。|
|
||||
|`?`|匹配前面的子表达式零次或一次。例如,`Your(s)?` 可以匹配 `Your` 或 `Yours` 中的`Your` 。`?` 等价于 `{0,1}`。|
|
||||
|`{n}`| `n` 是一个非负整数。匹配确定的 `n` 次。例如,`o{2}` 不能匹配 `for` 中的 `o`,但是能匹配 `foo` 中的两个 `o`。|
|
||||
|`{n,}`| `n` 是一个非负整数。至少匹配 `n` 次。例如,`o{2,}` 不能匹配 `for` 中的 `o`,但能匹配 `foooooo` 中的所有 `o`。`o{1,}` 等价于 `o+`。`o{0,}` 则等价于 `o*`。|
|
||||
|:-----:|:------------------------------------------------------|
|
||||
|`*` |匹配前面的子表达式零次或多次。例如,`foo*` 能匹配 `fo` 以及 `foooo`。`*` 等价于`{0,}`。|
|
||||
|`+` |匹配前面的子表达式一次或多次。例如,`foo+` 能匹配 `foo` 以及 `foooo`,但不能匹配 `fo`。`+` 等价于 `{1,}`。|
|
||||
|`?` |匹配前面的子表达式零次或一次。例如,`Your(s)?` 可以匹配 `Your` 或 `Yours` 中的`Your` 。`?` 等价于 `{0,1}`。|
|
||||
|`{n}` | `n` 是一个非负整数。匹配确定的 `n` 次。例如,`o{2}` 不能匹配 `for` 中的 `o`,但是能匹配 `foo` 中的两个 `o`。|
|
||||
|`{n,}` | `n` 是一个非负整数。至少匹配 `n` 次。例如,`o{2,}` 不能匹配 `for` 中的 `o`,但能匹配 `foooooo` 中的所有 `o`。`o{1,}` 等价于 `o+`。`o{0,}` 则等价于 `o*`。|
|
||||
|`{n,m}`| `m` 和 `n` 均为非负整数,其中 `n` 小于等于 `m`。最少匹配 `n` 次且最多匹配 `m` 次。例如,`o{1,3}` 将匹配 `foooooo` 中的前三个 `o`。`o{0,1}` 等价于 `o?`。注意,在逗号和两个数之间不能有空格。|
|
||||
|
||||
有了这两张表,我们通常就能够读懂几乎所有的正则表达式了。
|
||||
@@ -77,7 +78,10 @@ C++11 提供的正则表达式库操作 `std::string` 对象,
|
||||
|
||||
我们通过一个简单的例子来简单介绍这个库的使用。考虑下面的正则表达式:
|
||||
|
||||
- `[a-z]+\.txt`: 在这个正则表达式中, `[a-z]` 表示匹配一个小写字母, `+` 可以使前面的表达式匹配多次,因此 `[a-z]+` 能够匹配一个小写字母组成的字符串。在正则表达式中一个 `.` 表示匹配任意字符,而 `\.` 则表示匹配字符 `.`,最后的 `txt` 表示严格匹配 `txt` 则三个字母。因此这个正则表达式的所要匹配的内容就是由纯小写字母组成的文本文件。
|
||||
- `[a-z]+\.txt`: 在这个正则表达式中, `[a-z]` 表示匹配一个小写字母, `+` 可以使前面的表达式匹配多次,
|
||||
因此 `[a-z]+` 能够匹配一个小写字母组成的字符串。
|
||||
在正则表达式中一个 `.` 表示匹配任意字符,而 `\.` 则表示匹配字符 `.`,
|
||||
最后的 `txt` 表示严格匹配 `txt` 则三个字母。因此这个正则表达式的所要匹配的内容就是由纯小写字母组成的文本文件。
|
||||
|
||||
`std::regex_match` 用于匹配字符串和正则表达式,有很多不同的重载形式。
|
||||
最简单的一个形式就是传入 `std::string` 以及一个 `std::regex` 进行匹配,
|
||||
@@ -90,7 +94,7 @@ C++11 提供的正则表达式库操作 `std::string` 对象,
|
||||
|
||||
int main() {
|
||||
std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};
|
||||
// 在 C++ 中 `\` 会被作为字符串内的转义符,为使 `\.` 作为正则表达式传递进去生效,需要对 `\` 进行二次转义,从而有 `\\.`
|
||||
// 在 C++ 中 \ 会被作为字符串内的转义符,为使 \. 作为正则表达式传递进去生效,需要对 \ 进行二次转义,从而有 \\.
|
||||
std::regex txt_regex("[a-z]+\\.txt");
|
||||
for (const auto &fname: fnames)
|
||||
std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;
|
||||
@@ -121,7 +125,7 @@ for(const auto &fname: fnames) {
|
||||
|
||||
以上两个代码段的输出结果为:
|
||||
|
||||
```
|
||||
```txt
|
||||
foo.txt: 1
|
||||
bar.txt: 1
|
||||
test: 0
|
||||
@@ -133,7 +137,6 @@ sub-match[0]: bar.txt
|
||||
bar.txt sub-match[1]: bar
|
||||
```
|
||||
|
||||
|
||||
## 总结
|
||||
|
||||
本节简单介绍了正则表达式本身,然后根据使用正则表达式的主要需求,通过一个实际的例子介绍了正则表达式库的使用。
|
||||
@@ -191,7 +194,7 @@ template<typename SERVER_TYPE>
|
||||
void start_server(SERVER_TYPE &server) {
|
||||
|
||||
// process GET request for /match/[digit+numbers], e.g. GET request is /match/abc123, will return abc123
|
||||
server.resource["^/match/([0-9a-zA-Z]+)/?$"]["GET"] = [](ostream& response, Request& request) {
|
||||
server.resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
string number=request.path_match[1];
|
||||
response << "HTTP/1.1 200 OK\r\nContent-Length: " << number.length() << "\r\n\r\n" << number;
|
||||
};
|
||||
@@ -199,7 +202,7 @@ void start_server(SERVER_TYPE &server) {
|
||||
// peocess default GET request; anonymous function will be called if no other matches
|
||||
// response files in folder web/
|
||||
// default: index.html
|
||||
server.default_resource["^/?(.*)$"]["GET"] = [](ostream& response, Request& request) {
|
||||
server.default_resource["fill_your_reg_ex"]["GET"] = [](ostream& response, Request& request) {
|
||||
string filename = "www/";
|
||||
|
||||
string path = request.path_match[1];
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user