C++ 标准库快速参考(二)
四、算法
前一章讨论了标准库提供的存储数据的容器。除此之外,该库还提供了许多算法来处理这些数据或其他数据。算法独立于容器:它们只基于迭代器工作,因此只要提供合适的迭代器,就可以在任何范围的元素上执行。
这一章从输入/输出迭代器的简单定义开始,接着是按功能组织的所有可用算法的详细概述。本章最后讨论了迭代器适配器。
输入和输出迭代器
前一章简要解释了容器提供的不同种类的迭代器:正向、双向和随机访问。算法上下文中使用了另外两种迭代器类别,与其他三种相比,它们的要求更少。本质上:
- 输入迭代器:必须可取消引用才能读取元素。除此之外,只需要
++、==和!=操作符。 - 输出迭代器:只需要
++操作符,但是你必须能够在解引用后向它们写入元素。
对于这两者,它们提供单路访问也就足够了。也就是说,一旦增加,它们原则上可以使它们的所有先前副本无效。两个相应的迭代器标签,如在第三章中讨论的,也为这些类别提供:std::input_iterator_tag和output_iterator_tag。
标准容器返回的所有迭代器,以及指向 C 风格数组的指针,都是有效的输入迭代器。它们也是有效的输出迭代器,只要它们不指向const元素。
算法<algorithm>
本节概述了所有可用的算法,根据功能分为几个小节。除非另有说明,所有算法都在<algorithm>头文件中定义。
术语
以下术语和缩写用于算法定义中的类型:
- function:Callable——即 lambda 表达式、函数对象或函数指针。
- InIt、OutIt、FwIt、BidIt、RanIt:输入、输出、正向、双向或随机访问迭代器。
- UnaOp、BinOp:一元或二元运算,即接受一个 resp 的可调用操作。两个论点。
- UnaPred,BinPred:一元或二元谓词,谓词是返回布尔值的操作。
- Size:表示大小的类型,例如,元素的数量。
- DiffType:表示两个迭代器之间距离的类型。
- t:一个元素类型。
- Compare:用于比较元素的函数对象。如果未指定,则使用
operator<。函数对象接受两个参数,如果第一个参数小于第二个参数,则返回true,否则返回false。强加的排序必须是严格的弱排序,就像默认的operator<一样。
算法通常接受一个可调用的参数:例如,一元或二元操作或谓词。这个可调用函数可以是 lambda 表达式、函数对象或函数指针。Lambda 表达式和函数对象将在第二章中讨论。
一般准则
首先,尽可能使用标准算法,而不是自己编写的循环,因为它们通常更有效,而且更不容易出错。此外,尤其是在引入 lambda 表达式之后,算法的使用通常会产生更短、可读性更强、不言自明的代码。
其次,对于一些算法,某些容器提供了等价的专用成员函数(见第三章)。这些算法效率更高,因此应该优先于一般算法。在接下来的算法描述中,我们总是列出这些备选方案。
最后,许多算法移动或交换元素。如果没有隐式或显式的移动和/或交换函数可用,这些算法会退回到复制元素。为了获得最佳性能,您应该始终考虑为重要的自定义数据类型实现专门的移动和/或交换函数。标准库提供的类型总是在适当的地方提供这些。关于移动语义和交换功能的更多信息,我们参考第二章。
对范围应用函数
- 为范围
first, last)中的每个元素调用给定函数,并返回std::move(function)。注意,当迭代整个容器或 C 风格数组时,基于范围的for循环更方便。
Function for_each(InIt first, InIt last, Function function)
- 转换范围
[first1, last1)中的所有元素,并将结果存储在从target开始的范围中,该范围允许等于first1或first2以执行就地转换。对于第一个版本,对每个转换后的元素执行一元运算。对于第二种情况,对每个转换后的元素和第二个范围中的相应元素执行二元运算。设 length =(last1 - first1),则对长度为 0 ≤ n <的对(*(first1 +n), *(first2 +n))执行二进制运算。返回目标范围的结束迭代器,所以(target +长度)。
OutIt transform(InIt first1, InIt last1, OutIt target, UnaOp operation)
OutIt transform(InIt1 first1, InIt1 last1, InIt2 first2,
OutIt target, BinOp operation)
例子
下面的示例使用transform()通过 lambda 表达式将vector中的所有元素加倍,然后使用transform()通过标准函数对象对元素求反,最后使用for_each()将所有元素输出到控制台。这段代码还需要<functional>:
std::vector<int> vec{ 1,2,3,4,5,6 };
std::transform(cbegin(vec), cend(vec), begin(vec),
[ { return element * 2; });
std::transform(cbegin(vec), cend(vec), begin(vec), std::negate<>());
std::for_each(cbegin(vec), cend(vec),
[](auto& element) { std::cout << element << " "; });
输出如下所示:
-2 -4 -6 -8 -10 -12
检查元素是否存在
- 如果范围
[first, last)中的所有元素、无元素或至少有一个元素满足一元predicate,则返回true。如果范围为空,all_of()和none_of()返回true,而any_of()返回false。
bool all_of(InIt first, InIt last, UnaPred predicate)
bool none_of(InIt first, InIt last, UnaPred predicate)
bool any_of(InIt first, InIt last, UnaPred predicate)
- 返回
[first, last)中等于给定value或满足一元predicate的元素数量。[替代:所有有序和无序的关联容器都有一个count()成员。]
DiffType count(InIt first, InIt last, const T& value)
DiffType count_if(InIt first, InIt last, UnaPred predicate)
例子
以下示例演示了如何使用all_of()来检查所有元素是否都是偶数:
查找元素
- 在范围
[first, last)的所有元素中搜索第一个等于value、满足一元predicate或不满足predicate的元素。返回找到的元素的迭代器,如果没有找到,返回last。[替代:所有有序和无序的关联容器都有一个find()成员。]
InIt find(InIt first, InIt last, const T& value)
InIt find_if(InIt first, InIt last, UnaPred predicate)
InIt find_if_not(InIt first, InIt last, UnaPred predicate)
- 返回一个迭代器到
[first1, last1)中的第一个元素,它等于[first2, last2)中的一个元素。如果没有找到这样的元素或者如果[first2, last2)为空,则返回last1。如果给出了一个二元谓词,它将用于判断两个范围之间的元素是否相等。
InIt find_first_of(InIt first1, InIt last1,
FwIt first2, FwIt last2[, BinPred predicate])
- 返回范围
[first, last)中第一对相邻元素的第一个元素的迭代器,这些元素彼此相等或匹配一个二进制数predicate。如果没有找到合适的相邻元素,返回last。
FwIt adjacent_find(FwIt first, FwIt last[, BinPred predicate])
例子
以下代码片段使用find_if()算法在人员列表中查找一个名为 Waldo 的人:
auto people = { Person("Wally"), Person("Wilma"), Person("Wenda"),
Person("Odlaw"), Person("Waldo"), Person("Woof") };
auto iter = std::find_if(begin(people), end(people),
[](const Person& p) { return p.GetFirstName() == "Waldo"; });
二进位检索
以下所有算法都要求给定范围[ first,last]在value上排序或至少分区(分区稍后解释)。如果不满足这个前提条件,算法的行为是未定义的。
- 如果在范围
[first, last)中有一个等于value的元素,则返回true。
bool binary_search(FwIt first, FwIt last, const T& value[, Compare comp])
- 将迭代器返回到
[first, last)中第一个对lower_bound()的比较不小于value的元素,以及第一个对upper_bound()的比较大于value的元素。当在一个排序范围内插入时,如果插入发生在迭代器之前,这两个位置都适合插入value(就像顺序容器的insert()方法一样;参见下一个“示例”小节)。[替代:所有有序关联容器都有lower_bound()和upper_bound()成员。]
FwIt lower_bound(FwIt first, FwIt last, const T& value[, Compare comp])
FwIt upper_bound(FwIt first, FwIt last, const T& value[, Compare comp])
- 返回一个包含下限和上限的
pair。[替代:所有有序和无序的关联容器都有一个equal_range()成员。]
pair<FwIt, FwIt> equal_range(FwIt first, FwIt last,
const T& value[, Compare comp])
例子
下面的代码片段演示了如何在vector的正确位置插入一个新值,以保持元素的排序:
下一个例子使用equal_range()找到等于 2 的值的范围。它返回一个迭代器的pair。第一个指向第一个等于 2 的元素,第二个指向最后一个 2:
后续搜索
所有的子序列搜索算法都接受一个可选的二元谓词,用于判断元素是否相等。
- For
search()/find_end(),分别返回一个迭代器到[first1, last1)中第一个/最后一个子序列的开头,等于范围[first2, last2)。如果第二个范围为空,则返回first1/last1,如果没有找到相等的子序列,则返回last1。
FwIt1 search(FwIt1 first1, FwIt1 last1,
FwIt2 first2, FwIt2 last2[, BinPred predicate])
FwIt1 find_end(FwIt1 first1, FwIt1 last1,
FwIt2 first2, FwIt2 last2[, BinPred predicate])
- 返回第一个子序列的迭代器,这个子序列由重复了
count次的value组成。如果count为零,则返回first,如果没有找到合适的子序列,则返回last。
FwIt search_n(FwIt first, FwIt last, Size count,
const T& value[, BinPred predicate])
最小/最大
- 返回对两个值中最小值或最大值的引用,如果两个值相等,则返回第一个值。
constexpr const T& min(const T& a, const T& b[, Compare comp])
constexpr const T& max(const T& a, const T& b[, Compare comp])
- 返回给定
initializer_list中最小值或最大值的副本,或者如果有几个元素等于这个极值,则返回最左边元素的副本。
constexpr T min(initializer_list<T> t[, Compare comp])
constexpr T max(initializer_list<T> t[, Compare comp])
- 返回一个包含对两个值的最小值和最大值的引用的
pair,按此顺序。如果两个值相等,则返回pair(a, b)。
constexpr pair<const T&, const T&> minmax(
const T& a, const T& b[, Compare comp])
- 返回一个
pair,包含一个initializer_list中的最小值和最大值的副本,按此顺序。如果几个元素都等于最小值,那么返回最左边一个的副本;如果几个元素等于最大值,则返回最右边的一个副本。
constexpr pair<T, T> minmax(initializer_list<T> t[, Compare comp])
- 返回一个最小值迭代器,一个最大值迭代器,或者分别返回一个包含范围
[first, last)中最小和最大元素迭代器的pair。如果范围为空,则返回last或pair(first, first)。
FwIt min_element(FwIt first, FwIt last[, Compare comp])
FwIt max_element(FwIt first, FwIt last[, Compare comp])
pair<FwIt, FwIt> minmax_element(FwIt first, FwIt last[, Compare comp])
序列比较
所有的序列比较算法都接受一个可选的二元谓词,用于判断元素是否相等。
- 假设 n =
(last1 - first1),如果范围[first1, last1)和[first2, first2 +n)中的所有元素成对匹配,则返回true。第二个范围必须至少有 n 个元素。因此,后面讨论的四参数版本是避免越界访问的首选。
bool equal(InIt1 first1, InIt1 last1, InIt2 first2[, BinPred predicate])
- 设 n =
(last1 - first1),然后返回一个pair迭代器,指向范围[first1, last1)和[first2, first2 +n)中不匹配的第一个元素。第二个范围必须至少有 n 个元素。因此,为了避免越界访问,最好使用下面的四参数版本。
pair<InIt1, InIt2> mismatch(InIt1 first1, InIt1 last1,
InIt2 first2[, BinPred predicate])
- 早期三参数版本的安全版本,也知道第二个范围的长度。为了使
equal()成为true,两个范围必须等长。对于mismatch(),如果在到达last1或last2之前没有发现不匹配对,则返回一对(first1 + m, first2 + m)和m = min(last1 - first1, last2 - first2)。
bool equal(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2[, BinPred predicate])
pair<InIt1, InIt2> mismatch(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2[, BinPred predicate])
复制、移动、交换
- 将范围
[first, last)中的所有元素(copy())或仅满足一元元素predicate(copy_if())的元素复制到从targetFirst开始的范围。对于copy(),不允许targetFirst在[first, last)中:如果是这样的话,copy_backward()可能是一个选项。对于copy_if(),范围不允许重叠。对于这两种算法,目标范围必须足够大,以容纳复制的元素。返回结果范围的结束迭代器。
OutIt copy(InIt first, InIt last, OutIt targetFirst)
OutIt copy_if(InIt first, InIt last, OutIt targetFirst, UnaPred predicate)
- 将范围
[first, last)中的所有元素复制到结束于targetLast的范围,该范围不在范围[first, last)中。目标范围必须足够大,以容纳复制的元素。复制是反向进行的,从复制元素(last-1)到(targetLast-1)开始,再回到first。返回一个迭代器到目标范围的开始,所以(targetLast - (last - first))。
BidIt2 copy_backward(BidIt1 first, BidIt1 last, BidIt2 targetLast)
- 将从
start开始的count元素复制到从target开始的范围。目标范围必须足够大,以容纳这些元素。返回目标结束迭代器,所以(target + count)。
OutIt copy_n(InIt start, Size count, OutIt target)
- 类似于
copy()和copy_backward(),但是移动元素而不是复制它们。
OutIt move(InIt first, InIt last, OutIt targetFirst)
BidIt2 move_backward(BidIt1 first, BidIt1 last, BidIt2 targetLast)
- 将范围
[first1, last1)中的元素与范围[first2, first2 + (last1 - first1))中的元素交换。两个范围不允许重叠,第二个范围必须至少与第一个范围一样大。返回一个迭代器,从第二个范围中最后一个交换的元素开始。
FwIt2 swap_ranges(FwIt1 first1, FwIt1 last1, FwIt2 first2)
- 将由
x指向的元素与由y指向的元素交换,所以swap(*x, *y)。
void iter_swap(FwIt1 x, FwIt2 y)
生成序列
- 将
value分配给范围[first, last)或[first, first + count)中的所有元素。如果count为负,则不会发生任何事情。fill_n()的范围必须足够大,以容纳count元素。fill_n()返回(first + count),如果count为负,则返回first。【替代品:array::fill()。]
void fill(FwIt first, FwIt last, const T& value)
OutIt fill_n(OutIt first, Size count, const T& value)
- 生成器是一个没有任何返回值的参数的函数。调用它来计算范围
first, last)或[first, first + count)中每个元素的值。如果count是负的,什么都不会发生。generate_n()的范围必须足够大,以容纳count元素。generate_n()返回(first + count),如果count为负,则返回first。
void generate(FwIt first, FwIt last, Generator gen)
OutIt generate_n(OutIt first, Size count, Generator gen)
- 该算法在
<numeric>标题中定义。范围[first, last)中的每个元素被设置为value,之后value递增,因此:
void iota(FwIt first, FwIt last, T value)
*first = value++
*(first + 1) = value++
*(first + 2) = value++
...
例子
以下示例演示了generate()和iota():
![A417649_1_En_4_Figd_HTML.gif
拆卸和更换
- 将范围
first, last)中不等于value或不满足一元predicate的所有元素向范围的开头移动,之后[first, result)包含要保留的所有元素。返回result迭代器,指向传递了最后一个要保留的元素的迭代器。算法是稳定的,这意味着保留的元素保持它们的相对顺序。不应该使用[result, last)中的元素,因为它们可能因移动而处于未指定的状态。通常这些算法后面是对erase()的调用。这被称为删除-擦除习惯用法,在第 [3 章中讨论。
FwIt remove(FwIt first, FwIt last, const T& value)
FwIt remove_if(FwIt first, FwIt last, UnaPred predicate)
【备选:】和forward_list有remove()和remove_if()成员。]
- 从范围
[first, last)中的连续相等元素中删除除一个元素之外的所有元素。如果给定一个二元谓词,它将用于判断元素是否相等。否则等同于remove(),包括它后面通常应该跟一个erase()的事实。下一个“示例”小节显示了unique()的典型用法。【替代品:】、forward_list::unique()。]
FwIt unique(FwIt first, FwIt last[, BinPred predicate])
- 用
newVal替换范围[first, last)中等于oldVal或满足一元predicate的所有元素。
void replace(FwIt first, FwIt last, const T& oldVal, const T& newVal)
void replace_if(FwIt first, FwIt last, UnaPred predicate, const T& newVal)
- 类似于前面的算法,但是将结果复制到从
target开始的范围。目标范围必须足够大,以容纳复制的元素。输入和目标范围不允许重叠。返回目标范围的结束迭代器。
OutIt remove_copy(InIt first, InIt last, OutIt target, const T& value)
OutIt remove_copy_if(InIt first, InIt last, OutIt target, UnaPred predicate)
OutIt unique_copy(InIt first, InIt last, OutIt target [, BinPred predicate])
OutIt replace_copy(InIt first, InIt last, OutIt target,
const T& oldVal, const T& newVal)
OutIt replace_copy_if(InIt first, InIt last, OutIt target,
UnaPred predicate, const T& newVal)
例子
下面的例子演示了如何使用unique()和 remove-erase 习惯用法从vector中过滤出所有连续的相等元素:
反转和旋转
- 反转范围
[first, last)中的元素。【替代品:list::reverse(),forward_list::reverse()。]
void reverse(BidIt first, BidIt last)
- 向左旋转范围
[first, last)中的元素,使middle指向的元素成为范围中的第一个元素,而(middle - 1)指向的元素成为范围中的最后一个元素(参见下一个“示例”小节)。返回(first + (last - middle))。
FwIt rotate(FwIt first, FwIt middle, FwIt last)
- 类似于
reverse()和rotate(),但是将结果复制到从target开始的范围。目标范围必须足够大,以容纳复制的元素。输入和目标范围不允许重叠。返回目标范围的结束迭代器。
OutIt reverse_copy(BidIt first, BidIt last, OutIt target)
OutIt rotate_copy(FwIt first, FwIt middle, FwIt last, OutIt target)
例子
下一个代码片段旋转了vector中的元素。结果是5,6,1,2,3,4:
std::vector<int> vec{ 1,2,3,4,5,6 };
std::rotate(begin(vec), begin(vec) + 4, end(vec));
分割
- 如果范围
[first, last)中的元素被分区,使得满足一元谓词的所有元素都在不满足该谓词的所有元素之前,则返回true。如果范围为空,也返回true。
bool is_partitioned(InIt first, InIt last, UnaPred predicate)
- 对范围
[first, last)进行分区,使得满足一元谓词的所有元素都在不满足谓词的所有元素之前。返回不满足谓词的第一个元素的迭代器。stable_partition()保持两个分区中元素的相对顺序。
FwIt partition(FwIt first, FwIt last, UnaPred predicate)
BidIt stable_partition(BidIt first, BidIt last, UnaPred predicate)
- 通过将满足或不满足一元谓词的所有元素复制到分别从
outTrue或outFalse开始的输出范围来划分范围[first, last)。两个输出范围都必须足够大,以容纳复制的元素。输入和输出范围不允许重叠。返回一个包含两个输出范围的结束迭代器的pair。
pair<OutIt1, OutIt2> partition_copy(InIt first, InIt last,
OutIt1 outTrue, OutIt2 outFalse, UnaPred predicate)
- 要求基于一元
predicate对范围[first, last)进行分区。向第二个分区的第一个元素返回一个迭代器:即不满足谓词的第一个元素。
FwIt partition_point(FwIt first, FwIt last, UnaPred predicate)
整理
- 对范围
[first, last)中的元素进行排序。稳定版本保持相等元素的顺序。【替代品:list::sort(),forward_list::sort()。]
void sort(RanIt first, RanIt last[, Compare comp])
void stable_sort(RanIt first, RanIt last[, Compare comp])
The (middle - first)范围[first, last)中最小的元素被排序并移动到范围[first, middle)。未排序的元素以未指定的顺序移动到范围[middle, last)。
void partial_sort(RanIt first, RanIt middle, RanIt last[, Compare comp])
min(last - first, targetLast - targetFirst)范围[first, last)中的元素被排序并复制到目标范围。返回min(targetLast, targetFirst + (last - first))。
RanIt partial_sort_copy(InIt first, InIt last,
RanIt targetFirst, RanIt targetLast[, Compare comp])
- 范围
[first, last)中的元素以这样的方式移动,即在重新排列后,给定的迭代器nth指向如果整个范围被排序时该位置的元素。但是,实际上并没有对整个范围进行排序。然而,它是在nth指向的元素上(非稳定)分区的。
void nth_element(RanIt first, RanIt nth, RanIt last[, Compare comp])
- 如果范围
[first, last)是排序序列,则返回true。
bool is_sorted(FwIt first, FwIt last[, Compare comp])
- 返回最后一个迭代器
iter,这样[first, iter)就是一个有序序列。
FwIt is_sorted_until(FwIt first, FwIt last[, Compare comp])
- 返回范围
[first1, last1)中的元素是否比范围[first2, last2)中的元素少。
bool lexicographical_compare(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2[, Compare comp])
例子
partial_sort()和partial_sort_copy()算法可用于找出 n 个最大、最小、最差、最佳,...序列中的元素。这比排序整个序列要快。例如:
std::vector<int> vec{ 9,2,4,7,3,6,1 };
std::vector<int> threeSmallestElements(3);
std::partial_sort_copy(begin(vec), end(vec),
begin(threeSmallestElements), end(threeSmallestElements));
nth_element()是一种所谓的选择算法,用于寻找序列中第 n 个最小的数,平均具有线性复杂度。例如,它可用于计算具有奇数个元素的序列的中值:
洗牌
- 使用由统一随机数生成器生成的随机性打乱范围
first, last)中的元素。随机数生成库在第 [1 章中解释。
void shuffle(RanIt first, RanIt last, UniformRanGen generator)
- 不赞成使用
shuffle(),但为了完整性而提及。它打乱了范围[first, last)中的元素。随机数生成器rng是一个仿函数,其函数调用操作符接受一个整数参数n,并返回一个在[0, n)范围内的整数随机数,其n>为 0。如果没有提供随机数生成器,实现可以自由决定如何生成随机数。
void random_shuffle(RanIt first, RanIt last[, RNG&& rng])
例子
下面的例子打乱了vector中的元素。参见第一章了解更多关于随机数生成库的信息。代码片段还需要<random>和<ctime>:
排序范围上的操作
以下所有操作都需要对输入范围进行排序。如果不满足这个前提条件,算法的行为是未定义的。
- 将排序范围
[first1, last1)和[first2, last2)中的所有元素合并到一个从target开始的范围中,这样目标范围也被排序。目标范围必须足够大,以容纳所有元素。输入范围不允许与目标范围重叠。返回目标范围的结束迭代器。算法稳定;也就是说,相同元素的顺序保持不变。【替代品:list::merge(),forward_list::merge()。]
OutIt merge(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2, OutIt target[, Compare comp])
- 将排序后的范围
[first, middle)和[middle, last)合并成一个排序后的序列,存储在范围[first, last)中。该算法是稳定的,因此保持了相等元素的顺序。
void inplace_merge(BidIt first, BidIt middle, BidIt last[, Compare comp])
- 如果排序范围
[first2, last2)中的所有元素都在排序范围[first1, last1)中,或者前者为空,则返回true,否则返回false。
bool includes(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2[, Compare comp])
- 对两个排序范围
[first1, last1)和[first2, last2)执行集合运算(见下表),并将结果存储在从target开始的范围内。对目标范围内的元素进行排序。目标范围必须足够大,以容纳集合运算的元素。输入和输出范围不允许重叠。返回构造的目标范围的结束迭代器。- 联合:两个输入范围的所有元素。如果一个元素在两个输入范围内,那么它在输出范围内只出现一次。
- 交集:两个输入范围内的所有元素。
- 差异:所有在
[first1, last1)中的元素和不在[first2, last2)中的元素。 - 对称差:所有在
[first1, last1)和不在[first2, last2)的元素,以及所有在[first2, last2)和不在[first1, last1)的元素。
OutIt set_union(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2, OutIt target[, Compare comp])
OutIt set_intersection(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2, OutIt target[, Compare comp])
OutIt set_difference(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2, OutIt target[, Compare comp])
OutIt set_symmetric_difference(InIt1 first1, InIt1 last1,
InIt2 first2, InIt2 last2, OutIt target[, Compare comp])
排列
- 如果第二个范围是第一个范围的排列,则返回
true。对于三参数版本,第二个范围被定义为[first2, first2 + (last1 - first1)),并且该范围必须至少与第一个范围一样大。因此,四参数版本更适合防止越界访问(如果范围长度不同,它们将返回false)。如果给定一个二进制数predicate,它将用于判断两个范围之间的元素是否相等。
bool is_permutation(FwIt1 first1, FwIt1 last1,
FwIt2 first2[, BinPred predicate])
bool is_permutation(FwIt1 first1, FwIt1 last1,
FwIt2 first2, FwIt2 last2[, BinPred predicate])
- 将范围
[first, last)中的元素转换为按字典顺序排列的下一个/上一个排列。如果这样的下一个/前一个排列存在,则返回true,否则返回false,并按照可能的最小/最大排列转换元素。
bool next_permutation(BidIt first, BidIt last[, Compare comp])
bool prev_permutation(BidIt first, BidIt last[, Compare comp])
很
在这个上下文中,术语堆不是指 C++ 运行时的动态内存池。在计算机科学中,堆也是一组基本的基于树的数据结构(众所周知的变体包括二进制、二项式和斐波那契堆)。这些数据结构是有效实现各种图形和排序算法的关键构件(经典的例子包括 Prim 算法、Dijkstra 算法和 heapsort)。这也是优先级队列的一种常见实现策略:事实上,前一章讨论的 C++ priority_queue容器适配器是使用下面定义的堆算法实现的。
对于下面的 C++ 算法,堆的树被展平成以特定方式排序的连续元素序列。虽然确切的排序是特定于实现的,但它必须满足以下关键属性:没有元素大于它的第一个元素,并且移除这个最大的元素和添加任何新元素都可以在对数时间内完成。
- 将范围
[first, last)变成一个堆(在线性时间内)。
void make_heap(RanIt first, RanIt last[, Compare comp])
- 范围
[first, last)的最后一个元素被移动到正确的位置,从而成为一个堆。在调用push_heap()之前,范围[first, last - 1)需要是一个堆。
void push_heap(RanIt first, RanIt last[, Compare comp])
- 通过用
*(last - 1)交换*first并确保新的范围[first, last - 1)仍然是堆,从堆[first, last)中移除最大的元素。
void pop_heap(RanIt first, RanIt last[, Compare comp])
- 对范围
[first, last)中的所有元素进行排序。在调用sort_heap()之前,该范围需要是一个堆。
void sort_heap(RanIt first, RanIt last[, Compare comp])
- 如果范围
[first, last)表示堆,则返回true。
bool is_heap(RanIt first, RanIt last[, Compare comp])
- 返回最后一个迭代器
iter,这样[first, iter)表示一个堆。
RanIt is_heap_until(RanIt first, RanIt last[, Compare comp])
数字算法<numeric>
以下算法在<numeric>标题中定义:
- 返回
result,从result等于startValue开始,然后对范围[first, last)内的每个element执行result += element或result = op(result, element)计算得到。
T accumulate(InIt first, InIt last, T startValue[, BinOp op])
- 返回
result,从等于startValue的result开始计算,然后依次对范围[first1, last1)中的每个el1和范围[first2, first2 + (last1 - first1))中的每个el2执行result += (el1 * el2)或result = op1(result, op2(el1, el2))。第二个范围必须至少与第一个范围一样大。
T inner_product(InIt1 first1, InIt1 last1, InIt2 first2,
T startValue[, BinOp1 op1, BinOp2 op2])
- 计算从
[first, last)开始的递增子范围的部分和,并将结果写入从target开始的范围。使用默认运算符+,结果就好像是按如下方式计算的:
OutIt partial_sum(InIt first, InIt last, OutIt target[, BinOp op])
- 返回目标范围的结束迭代器,所以
(target + (last - first))。目标范围必须足够大以容纳结果。通过指定target等于first,可以就地完成计算。
*(target) = *first
*(target + 1) = *first + *(first + 1)
*(target + 2) = *first + *(first + 1) + *(first + 2)
...
- 计算范围
[first, last)中相邻元素的差值,并将结果写入从target开始的范围。对于默认运算符-,计算结果如下:
OutIt adjacent_difference(InIt first, InIt last, OutIt target[, BinOp op])
- 返回目标范围的结束迭代器,所以
(target + (last - first))。目标范围必须足够大以容纳结果。通过指定target等于first,可以就地完成计算。
*(target) = *first
*(target + 1) = *(first + 1) - *first
*(target + 2) = *(first + 2) - *(first + 1)
...
例子
以下代码片段使用accumulate()算法计算序列中所有元素的总和:
inner_product()算法可用于计算两个数学向量的所谓点积:
迭代器适配器<iterator>
标准库提供了以下迭代器适配器:
reverse_iterator:反转正在修改的迭代器的顺序。用make_reverse_iterator(Iterator iter)造一个。move_iterator:解引用被修改为右值的迭代器。用make_move_iterator(Iterator iter)造一个。back_insert_iterator:使用push_back()在容器后面插入新元素的迭代器适配器。使用back_inserter(Container& cont)建造一个。front_insert_iterator:迭代器适配器,使用push_front()在容器前面插入新元素。使用front_inserter(Container& cont)建造一个。insert_iterator:使用insert()在容器中插入新元素的迭代器适配器。要构建一个,使用inserter(Container& cont, Iterator iter),其中iter是插入位置。
下面的例子通过使用deque上的front_insert_iterator适配器,以相反的顺序将所有元素从vector复制到deque。接下来,它使用accumulate()连接vector中的所有string(其默认组合运算符+执行string的连接)。因为这里使用了move_iterator适配器,所以string是移动的,而不是从vector复制的:
五、流输入输出
基于 C++ 流的 I/O 库允许您执行 I/O 操作,而不必知道有关目标或源的详细信息。流的目标或源可以是字符串、文件、内存缓冲区等等。
流的输入和输出
标准库提供的流类组织在一个层次结构和一组头中,如图 5-1 所示。
图 5-1。
The hierarchy of stream-related classes
更准确地说,该库定义了名为basic_ios、basic_ostream、basic_istringstream等的模板,所有模板都基于一种字符类型。层次结构中的所有类,除了ios_base,都是这些模板化类的typedef,模板类型为char。比如std::ostream就是std::basic_ostream<char>的一个typedef。对于称为wios、wostream、wofstream等的wchar_t字符类型,有对应的typedef。本章剩余部分仅使用图 5-1 中所示的char typedef s。
除了图中的表头,还有<iostream>。有点令人困惑的是,这并没有真正定义std::iostream本身,因为这是由<istream>完成的。相反,<iostream>包括<ios>、<streambuf>、<istream>、<ostream>和<iosfwd>,同时自身增加了标准输入和输出流(w ) cin、(w ) cout、(w ) cerr、(w ) clog。后两个分别用于输出错误和日志信息。它们的目的地是特定于实现的。
该库还提供了std::basic_streambuf、basic_filebuf和basic_stringbuf模板及其各种typedef,加上istreambuf_iterator和ostreambuf_iterator。这些是流缓冲区,是其他流类实现的基础,比如ostream、ifstream等等。在这一章的结尾会对它们进行简要的讨论。
头文件<iosfwd>包含所有标准 I/O 库类型的前向声明。将它包含在其他头文件中是很有用的,而不必包含您需要的所有类型的完整模板定义。
助手类型<ios>
以下助手类型在<ios>中定义:
STD::IOs _ base<ios>
在<ios>中定义的ios_base类是所有输入和输出流类的基类。它跟踪格式化选项和标志,以操纵数据的读写方式。提供了以下方法:
还可以通过流 I/O 操纵器来修改标志,这将在下一节中讨论。
表 5-2。
std::ios_base::fmtflags Masks Defined in <ios>
表 5-1。
std::ios_base::fmtflags Formatting Flags Defined in <ios>
输入/输出操纵器<ios>, <iomanip>
操纵器允许你使用operator<<和operator>>而不是flags(fmtflags)或setf()来改变旗帜。
<ios>头为表 5-1 : std::scientific、std::left等中定义的所有标志定义了全局std范围内的 I/O 操纵器。对于属于表 5-2 中定义的掩码的标志,I/O 操纵器使用该掩码。比如std::dec其实叫ios_base::setf(dec, basefield)。
对于boolalpha、showbase、showpoint、showpos、skipws、uppercase、unitbuf,也可以使用负面操纵器,它们的名称相同,但以no为前缀:例如std::noboolalpha。
除了std::fixed和scientific之外,还有std::hexfloat ( scientific | fixed)和std::defaultfloat(无floatfield标志设置)机械手。
此外,<iomanip>标题定义了以下操纵器:
例子
这段代码还需要<locale>:
在美国系统上,输出如下:
Left: $1.23__
Right: __$1.23
Internal: 0x___7b
STD::IOs<ios>
在<ios>中定义的ios类继承自ios_base,并提供了许多检查和修改流状态的方法,它是表 5-3 中列出的状态标志的按位组合。
表 5-3。
std::ios_base::iostate State Constants Defined in <ios>
提供了以下与状态相关的方法:
| 方法 | 描述 | | --- | --- | | `good()``eof()``bad()` | 如果分别没有设置`badbit`、`failbit`和`eofbit`,设置了`eofbit`,设置了`badbit`,或者设置了`failbit`或`badbit`,则返回`true`。 | | `operator!` | 相当于`fail()`。 | | `operator bool` | 相当于`!fail()`。 | | `rdstate()` | 返回当前的`ios_base::iostate`状态。 | | `clear(state)` | 如果附加了有效的流缓冲区,则将流的状态更改为给定的状态(见下文);否则将其设置为`state | badbit`。 | | `setstate(state)` | 调用`clear(state | rdstate())`。 |除了这些与状态相关的方法之外,以下附加方法由ios定义:
std::ios的默认初始化有以下效果:
- 标志被设置为
skipws | dec。 - 精度设置为 6。
- 字段宽度设置为 0。
- 填充字符被设置为
widen(' ')。 - 如果附加了有效的流缓冲区(见下文),则将状态设置为
goodbit,否则设置为badbit。
错误处理
默认情况下,流操作通过设置流的状态位(good、bad、fail和eof)来报告错误,但它们不会抛出异常。不过,可以使用exceptions()方法来启用异常。它要么返回当前异常掩码,要么接受一个。该掩码是std::ios_base::iostate状态标志的按位组合(见表 5-3 )。对于掩码中设置为 1 的每个状态标志,当该状态位为流设置时,流将引发异常。
例如,下面的代码试图使用文件流打开一个不存在的文件(将在本章后面详细解释)。不会引发任何异常;只有流的失败位被设置为 1:
如果您想使用异常,代码可以重写如下:
一个可能的输出可能是
ios_base::failbit set: iostream stream error
std::ostream <ostream>
ostream类支持对基于char的流进行格式化和非格式化输出。格式化输出意味着所写内容的格式会受到格式化选项的影响,例如字段的宽度、浮点数的十进制位数等等。格式化输出通常也会受到流的locale的影响,如第六章所述。无格式输出只需要按原样写入字符或字符缓冲区。
ostream提供了一个swap()方法和下面的高级输出操作。如果没有提到返回类型,操作返回一个ostream&,允许操作被链接:
表 5-4。
std::ios_base::seekdir Constants Defined in <ios>
<ostream>还定义了以下额外的 I/O 操纵器:
<iostream>头提供了以下全局ostream实例:
cout/wcout:输出到标准 C 输出流stdoutcerr/wcerr:标准 C 错误流的输出,stderrclog/wclog:标准 C 错误流的输出,stderr
(w)cout自动绑定到(w)cin。这意味着对(w)cin的输入操作导致(w)cout刷新其缓冲区。(w)cout也自动绑定到(w)cerr,因此(w)cerr上的任何输出操作都会导致(w)cout刷新。
std::ios_base提供了一个名为sync_with_stdio()的静态方法,用于在每次输出操作后将这些全局ostream与底层 C 流同步。这确保了它们使用相同的缓冲区,允许您安全地混合 C++ 和 C 风格的输出。它还保证了标准流是线程安全的:也就是说,没有数据竞争。尽管如此,字符交错仍然是可能的。
Note
当使用标准流cout、cerr、clog和cin(稍后讨论)时,您不必考虑与平台相关的行尾字符。例如,在 Windows 上,一行通常以\r\n结尾,而在 Linux 上以\n结尾。然而,翻译会自动发生,所以您可以总是使用\n。
例子
以下示例演示了三种不同的输出方法:
std::cout << "PI = " << 3.1415 << std::endl;
std::cout.put('\t');
std::cout.write("C++", 3);
STD::ist stream<istream>
istream类支持来自基于char的流的格式化和非格式化输入。它提供swap()及以下高级输入操作。除非另有说明,否则操作返回一个istream&,这有助于链接:
<istream>还定义了以下额外的 I/O 操纵器:
<iostream>头提供了以下全局istream实例:
cin/wcin:从标准 C 输入流中读取,stdin
ios_base::sync_with_stdio()功能也会影响(w)cin。参见前面对cout、cerr、clog的解释。
如前所述,istream提供了一个getline()方法来提取字符。不幸的是,你必须传递给它一个适当大小的char*缓冲区。<string>头定义了一个更容易使用的std::getline()方法,它接受一个std::string作为目标缓冲区。下面的例子说明了它的用法。
例子
int anInt;
double aDouble;
std::cout << "Enter an integer followed by some whitespace\n"
<< "and a double, and press enter: ";
std::cin >> anInt >> aDouble;
std::cout << "You entered: ";
std::cout << "Integer = " << anInt << ", Double = " << aDouble << std::endl;
std::string message;
std::cout << "Enter a string. End input with a * and enter: ";
std::getline(std::cin >> std::ws, message, '*');
std::cout << "You entered: '" << message << "'" << std::endl;
下面是该程序的一个可能输出:
Enter an integer followed by some whitespace
and a double, and press enter: 1 3.2 ↩
You entered: Integer = 1, Double = 3.2
Enter a string. End input with a * and enter: This is ↩
a multiline test* ↩
You entered: 'This is ↩
a multiline test'
std::iostream <istream>
iostream类,在<istream>中定义(不在<iostream>中!),继承自ostream和istream,提供高级输入输出操作。它跟踪流中的两个独立位置:一个输入位置和一个输出位置。这就是为什么ostream有tellp()和seekp()方法,而istream有tellg()和seekg() : iostream包含所有四个方法,所以它们需要一个不同的名字。除了继承的功能之外,它不提供额外的功能。
字符串流<sstream>
字符串流允许您在字符串上使用流 I/O。该库提供了istringstream(输入,继承自istream)、ostringstream(输出,继承自ostream)、stringstream(输入输出,继承自iostream)。继承图见图 5-1 。这三个类都有一组相似的构造函数:
-
[i|o]stringstream(ios_base::openmode): Constructs a new string stream with the givenopenmode, a bitwise combination of the flags defined in Table 5-5表 5-5。
| 开放模式 | 描述 | | --- | --- | | `app` | 追加的简称。在每次写入前查找到流的末尾。 | | `binary` | 以二进制模式打开的流。如果未指定,则以文本模式打开流。差异参见文件流部分。 | | `in / out` | 分别为读/写而打开的流。 | | `trunc` | 打开流后移除流的内容。 | | `ate` | 打开流后查找到流的末尾。 |std::ios_base::openmodeConstants Defined in<ios> -
[i|o]stringstream(string&, ios_base::openmode):用给定字符串的副本作为初始流内容,用给定的openmode构造一个新的字符串流 -
[i|o]stringstream([i|o]stringstream&&):移动构造器
前两个构造函数中的openmode有一个默认值:out代表ostringstream,in代表istringstream,out|in代表stringstream。对于ostringstream和istringstream,给定的openmode总是和默认的组合在一起;比如对于ostringstream,实际的openmode是给定 _openmode |ios_base::out。
这三个类只添加了两个方法:
string str():返回底层字符串对象的副本void str(string&):将底层字符串对象设置为给定对象的副本
例子
std::ostringstream oss;
oss << 123 << " " << 3.1415;
std::string myString = oss.str();
std::cout << "ostringstream contains: '" << myString << "'" << std::endl;
std::istringstream iss(myString);
int myInt; double myDouble;
iss >> myInt >> myDouble;
std::cout << "int = " << myInt << ", double = " << myDouble << std::endl;
对象
文件流允许您对文件使用流 I/O。该库提供了一个ifstream(输入,继承自istream)、ofstream(输出,继承自ostream)、fstream(输入输出,继承自iostream)。继承图见图 5-1 。这三个类都有一组相似的构造函数:
[i|o]fstream(filename, ios_base::openmode):构造一个文件流,用给定的openmode打开给定的文件。文件可以指定为const char*或std::string&。[i|o]fstream([i|o]fstream&&):移动构造器。
这三个类都添加了以下方法:
open(filename, ios_base::openmode):打开一个类似于第一个构造函数的文件is_open():如果打开文件进行输入和/或输出,则返回trueclose():关闭当前打开的文件
构造函数和open()方法中的openmode(见表 5-5 )有一个默认:ofstream用out,ifstream用in,fstream用out|in。对于ofstream和ifstream,给定的openmode总是和默认的组合在一起;例如:对于ofstream,实际openmode是给定 _openmode |ios_base::out。
如果指定了ios_base::in标志,无论是否与ios_base::out结合,您试图打开的文件必须已经存在。以下代码打开一个用于输入和输出的文件,如果该文件尚不存在,则创建该文件:
如果一个文件以文本模式打开,而不是二进制模式,库被允许翻译某些特殊字符来匹配平台如何使用这些字符。例如,在 Windows 上,行通常以\r\n结尾,而在 Linux 上,它们通常以\n结尾。当一个文件在文本模式下打开时,你并不是自己在 Windows 上读/写\r;库为您处理这种翻译。
与其他组合的输入和输出流(如stringstream)相比,fstream类支持输入和输出,处理当前位置的方式不同。文件流只有一个位置,因此输出和输入位置总是相同的。
Tip
文件流的析构函数自动关闭文件。
例子
下面的示例类似于前面给出的字符串流示例,但使用了一个文件。在这个例子中,ofstream是使用close()显式关闭的,ifstream是通过ifs的析构函数隐式关闭的:
const std::string filename = "output.txt";
std::ofstream ofs(filename);
ofs << 123 << " " << 3.1415;
ofs.close();
std::ifstream ifs(filename);
int myInt; double myDouble;
ifs >> myInt >> myDouble;
std::cout << "int = " << myInt << ", double = " << myDouble << std::endl;
自定义类型的运算符<< and >
您可以编写自己版本的流输出和提取操作符operator<<和operator>>。下面是一个关于Person类的两个操作符的例子,使用std::quoted()操作符来处理名字中的空格:
std::ostream& operator<<(std::ostream& os, const Person& person) {
os << std::quoted(person.GetFirstName()) << ' '
<< std::quoted(person.GetLastName());
return os;
}
std::istream& operator>>(std::istream& is, Person& person) {
std::string firstName, lastName;
is >> std::quoted(firstName) >> std::quoted(lastName);
person.SetFirstName(firstName); person.SetLastName(lastName);
return is;
}
这些运算符可以如下使用(<sstream>也是必需的):
流迭代器
除了在第 3 和 4 章节中讨论的其他迭代器之外,<iterator>头定义了两个流迭代器std::istream_iterator和std::ostream_iterator。
std::ostream_iterator
ostream_iterator是一个输出迭代器,能够使用operator<<向ostream输出某种类型的对象序列。要输出的对象的类型被指定为模板类型参数。有一个构造函数接受对要使用的ostream的引用和一个可选的分隔符,该分隔符在每次输出后被写入流中。
结合第四章讨论的算法,流迭代器非常强大。例如,下面的代码片段使用std::copy()算法将double的vector写入控制台,其中每个double后跟一个制表符(另外还需要<vector>和<algorithm>):
std::vector<double> vec{ 1.11, 2.22, 3.33, 4.44 };
std::copy(cbegin(vec), cend(vec),
std::ostream_iterator<double>(std::cout, "\t"));
std::istream_iterator
istream_iterator是一个输入迭代器,能够通过使用operator>>逐个提取对象来迭代istream中的某种类型的对象。要从流中提取的对象的类型被指定为模板类型参数。有三个构造函数:
istream_iterator():默认构造函数,导致迭代器指向流的末尾istream_iterator(istream&):构造一个迭代器,从给定的istream中提取对象istream_iterator(istream_iterator&):复制构造函数
就像一个ostream_iterator,istream_iterator s 结合算法非常厉害。以下示例结合使用for_each()算法和istream_iterator从标准输入流中读取未指定数量的double值,并将它们相加以计算平均值(还需要<algorithm>):
std::istream_iterator<double> begin(std::cin), end;
double sum = 0.0; int count = 0;
std::for_each(begin, end, &{ sum += value; ++count;});
std::cout << sum / count << std::endl;
在 Windows 上按 Ctrl+Z 或在 Linux 上按 Ctrl+D 终止输入,然后按 Enter。
第二个例子使用一个istream_iterator从控制台读取不确定数量的double和一个ostream_iterator将读取的double写入由制表符分隔的stringstream(另外需要<sstream>和<algorithm>):
std::ostringstream oss;
std::istream_iterator<double> begin(std::cin), end;
std::copy(begin, end, std::ostream_iterator<double>(oss, "\t"));
std::cout << oss.str() << std::endl;
流缓冲器<streambuf>
流类不直接处理目标,如内存中的字符串、磁盘上的文件等。相反,他们使用由std::basic_streambuf<CharType>定义的流缓冲区的概念。提供两个typedef、std::streambuf和std::wstreambuf,模板类型分别为char或wchar_t。文件流使用std::(w)filebuf,字符串流使用std::(w)stringbuf,两者都继承自(w)streambuf。
每个流都有一个与之相关联的流缓冲区,您可以使用rdbuf()获得指向该缓冲区的指针。对rdbuf(streambuf*)的调用返回当前关联的流缓冲区,并将其更改为给定的流缓冲区。
流缓冲区可用于编写流重定向器类,将一个流重定向到另一个流。作为一个基本的例子,下面的代码片段将所有的std::cout输出重定向到一个文件(另外还需要<fstream>):
Caution
当更改一个标准流的缓冲区时,不要忘记在终止应用程序之前恢复旧的缓冲区,就像上一个示例中所做的那样。否则,您的代码可能会因某些库实现而崩溃。
它还可以用于实现一个 tee 类,该类将输出重定向到两个或多个目标流。另一个用途是轻松读取整个文件:
std::ifstream ifs("test.txt");
std::stringstream buffer;
buffer << ifs.rdbuf();
流缓冲区的确切行为取决于实现。直接使用流缓冲区是一个高级主题,由于页面限制,我们不能进一步详细讨论。
c 型输出和输入<cstdio>
除了在第二章中解释的文件实用程序外,<cstdio>头文件还定义了 C 风格的 I/O 库,包括基于字符的 I/O ( getc()、putc()),...)和格式化的 I/O ( printf(),scanf(),...).所有 C 风格的 I/O 功能都包含在类型安全的 C++ 流中,它也有更好的定义,可移植的错误处理。 1 这一节确实讨论了std::printf()和std::scanf()函数族,而且只讨论这些,因为它们的格式语法紧凑,有时比 C++ 流更方便。
std::printf()系列
以下printf()系列函数在<cstdio>中定义:
std::printf(const char* format, ...)
std::fprintf(FILE* file, const char* format, ...)
std::snprintf(char* buffer, size_t bufferSize, const char* format, ...)
std::sprintf(char* buffer, const char* format, ...)
它们将格式化的输出分别写入标准输出、文件、给定大小的缓冲区或缓冲区,并返回写出的字符数。最后一个sprintf(),不如snprintf()安全。它们在format字符串后都有数量可变的参数。也有以v为前缀的版本接受va_list作为参数:例如vprintf(const char* format, va_list)。对于前三种,还提供了宽字符版本:(v)wprintf()、(v)fwprintf()和(v)swprintf()。
如何格式化输出由给定的format字符串控制。除了以%开头的序列之外,它的所有字符都按原样写出。格式化选项的基本语法是%后跟一个转换说明符。这告诉printf()如何解释变长参数列表中的下一个值。传递给printf()的参数必须与format中的%指令顺序相同。表 5-6 解释了可用的转换说明符。列出的预期参数类型适用于不使用长度修饰符的情况(稍后讨论)。
表 5-6。
Available Conversion Specifiers for printf()-Like Functions
Caution
C 风格的 I/O 函数不是类型安全的。如果您的转换说明符要求将参数值解释为double,那么该参数必须是真的double(而不是,例如,float或整数)。如果传递了错误的类型,它会编译并运行,但这很少会有好结果。这也意味着永远不要将 C++ std::string原样作为字符串转换说明符的参数传递:而是使用c_str(),如下例所示。
下面的例子打印了美国传统民歌“99 瓶啤酒”的歌词(假设有一个using namespace std):
string bottles = "bottles of beer";
char on_wall[99];
for (int i = 99; i > 0; --i) {
snprintf(on_wall, sizeof(on_wall), "%s on the wall", bottles.c_str());
printf("%d %s, %d %s.\n", i, on_wall, i, bottles.c_str());
printf("Take one down, pass it around, %d %s.\n", i-1, on_wall);
}
格式化选项比到目前为止讨论的基本转换要强大得多。%指令的完整语法如下:
%<flags><width><precision><length_modifier><conversion>
随着
-
<flags>: Zero or more flags that change the meaning of the conversion specifier. See Table 5-7.表 5-7。
Available Flags
| 旗 | 描述 | | --- | --- | | `-` | 左对齐输出。默认情况下,输出右对齐。 | | `+` | 始终输出数字的符号,即使是正数。 | | 空格字符 | 如果要输出的数字是非负的或者没有字符,则在输出前加上一个空格。如果还指定了`+`,则忽略。 | | `#` | 输出一个所谓的另类形式。对于`x`和`X`,如果数字不为零,则结果以`0x`或`0X`为前缀。对于所有浮点说明符(`a`、`A`、`e`、`E`、`f`、`F`、`g`和`G`),输出总是包含一个小数点字符。对于`g`和`G`,尾随零不会被删除。对于 o,精度增加,因此输出的第一个数字是零。 | | `0` | 对于所有的整数和浮点转换说明符(`d`、`i`、`o`、`u`、`x`、`X`、`a`、`A`、`e`、`E`、`f`、`F`、`g`和`G`),用零代替空格进行填充。如果也指定了`-`,或者对于所有整数说明符与精度的组合,则忽略此选项。 | -
<width>:可选最小字段宽度(不截断:仅填充)。如果转换后的值的字符数少于指定的宽度,则应用填充。默认情况下,空格用于填充。<width>可以是非负整数,也可以是*,这意味着从参数列表中的整数参数中获取宽度。此宽度必须在要格式化的值之前。 -
<precision>:一个点,后面跟一个可选的非负整数(如果没有指定,则假定为 0),或者一个*,这也意味着从参数列表中的一个整数参数中获取精度。精度是可选的,它决定了以下内容:- s 的最大字节数,默认情况下,应该是以零结尾的字符数组。
- 所有整数转换说明符(d、I、o、u、X 和 X)的最小输出位数。默认值:1。
- 对于大多数浮点转换说明符(A、A、E、E、F 和 F),小数点后要输出的位数。如果未指定,默认精度为 6。
- g 和 g 的最大有效位数。默认值也是 6。
-
<length_modifier>: An optional modifier that alters the type of the argument to be passed. Table 5-8 gives an overview of all supported modifiers for numeric conversions. For character and strings (candsconversion specifiers, respectively), thellength modifier (note: this is the letterl) changes the expected input type fromintandchar*towint_tandwchar_t*, respectively.2表 5-8。
Length Modifiers for All Numeric Conversion Specifiers
| 修饰语 | `d`,`i` | `o`、`u`、`x`、`X` | `n` | `a`、`A`、`e`、`E`、`f`、`F`、`g`、`G` | | --- | --- | --- | --- | --- | | (无) | `int` | `unsigned int` | `int*` | `double` | | `hh` | `char` | `unsigned char` | `char*` | | | `h` | `short` | `unsigned short` | `short*` | | | `l` | `long` | `unsigned long` | `long*` | | | `ll` | `long long` | `unsigned long long` | `long long*` | | | `j` | `intmax_t` | `uintmax_t` | `intmax_t*` | | | `z` | `size_t` | `size_t` | `size_t*` | | | `t` | `ptrdiff_t` | `ptrdiff_t` | `ptrdiff_t*` | | | `L` | | | | `long double` | -
<conversion>:唯一必需的组件,指定要应用于参数的转换。(见表 5-6 。)
表 5-8 中的修饰符决定了必须按指示传递的输入类型。std::intmax_t、uintmax_、t在<cstdint>(见第章 1 )中定义,size_t、ptrdiff_t在<cstddef>中定义。还要注意的是long和 l ong long修饰符使用字母l,而不是数字1。
例子
std::scanf()系列
以下scanf()系列函数在<cstdio>中定义:
std::scanf(const char* format, ...)
std::fscanf(FILE* file, const char* format, ...)
std::sscanf(const char* buffer, const char* format, ...)
它们分别从标准输入、文件或缓冲区中读取。除了这些在format字符串后面有可变数量的参数的函数之外,还有一些名称以v为前缀并接受va_list作为参数的版本:例如,vscanf(const char* format, va_list)。还提供了宽字符版本:(v)wscanf()、(v)fwscanf()和(v)swscanf()。
它们都根据给定的format字符串读取格式化数据。使用的scanf()格式语法类似于前面看到的printf()格式语法。格式字符串中的所有字符只是用来与输入进行比较,除了以%开头的序列。这些%指令导致值被解析并按顺序存储在函数参数所指向的位置。基本语法是一个%符号,后跟表 5-9 中的一个转换说明符。最后一列显示了未指定长度修饰符时的参数类型(见表 5-10 )。
表 5-10。
Available Length Modifiers for the Numeric Conversion Specifiers of scanf()-Like Functions
表 5-9。
Available Conversion Specifiers for scanf()-Like Functions
对于除转换说明符c、s或[...]之外的所有指令,任何空白字符都会被跳过,直到第一个非空白字符出现。当到达输入字符串的末尾、出现流输入错误或出现解析错误时,解析会停止。返回值等于指定值的数量,或者如果在开始第一次转换前发生输入故障,则返回值为EOF。如果到达流的末尾或出现解析错误,则赋值的数量将小于指令的数量:例如,如果在第一次转换期间出现这种情况,则赋值的数量为零。
%指令的完整语法如下:
%<*><width><length_modifier><conversion>
与:
<*>:一个可选的*符号,使scanf()从输入中解析数据,而不把它存储在任何参数中。<width>:可选最大字段宽度,以字符为单位。<length_modifier>:可选长度修改量:见表 5-10 。当应用于c、s或[...]说明符时,l(字母l)将所需的输入类型从char**修改为wchar_t**。<conversion>:必选。指定要应用的转换;见表 5-9 。
表 5-10 和表 5-8 之间唯一不明显的区别是,默认情况下,浮点参数必须指向一个float而不是一个double。
例子
std::string s = "int: +123; double: -2.34E-3; chars: abcdef";
int i = 0; double d = 0.0; char chars[4] = { 0 };
std::sscanf(s.data(), "int: %i; double: %lE; chars: %[abc]", &i, &d, chars);
std::printf("int: %+i; double: %.2lE; chars: %s", i, d, chars);
Footnotes 1
一些库实现使用errno(参见第八章)来报告 C 风格 I/O 函数的错误,包括printf()和scanf()函数:请查阅您的库文档以确认。
2
wint_t在<cwchar>中定义,是一个足够大的整型的typedef,可以容纳任何宽字符(wchar_t值)和至少一个不是有效宽字符的值(WEOF)。
六、字符和字符串
字符串<string>
该标准定义了四种不同的字符串类型,每种类型对应一种不同的类似于char的类型:
第一列中的名字纯粹是指示性的,因为字符串完全不知道它们所包含的类似于char的项目——或者代码单元——所使用的字符编码。例如,窄字符串可用于存储 ASCII 字符串,以及使用 UTF-8 或 DBCS 编码的字符串。
为了说明,我们将主要使用std::string。不过,本节中的所有内容同样适用于所有类型。除非另有说明,此后讨论的区域设置和正则表达式功能只需要为窄字符串和宽字符串实现。
所有四种字符串类型都是同一个类模板std::basic_string<CharT>的实例化。一个basic_string<CharT>本质上是一个vector<CharT>,它有额外的函数和重载,或者是为了方便普通的字符串操作,或者是为了兼容 C 风格的字符串(const CharT*)。vector的所有成员也都是为字符串提供的,除了就位功能(对字符用处不大)。这意味着,与其他主流语言不同,如。NET、Python 和 Java,C++ 中的字符串是可变的。这也意味着,例如,字符串可以很容易地用于第四章中的所有算法:
本节的剩余部分将重点介绍与vector相比,字符串增加的功能。对于字符串与vector共有的功能,我们参考第三章。有一点需要注意,特定于字符串的函数和重载大多是基于索引的,而不是基于迭代器的。例如,前一个示例中的最后三行可以更方便地写成
或者
在处理字符串索引时,end()迭代器的等效物是basic_string::npos。这个常量一贯用于表示半开放范围(也就是说,表示“直到字符串末尾”),并且,正如您接下来看到的,作为类似find()的函数的“未找到”返回值。
在字符串中搜索
字符串提供了六个成员函数来搜索子字符串或字符:find()和rfind()、find_first_of()和find_last_of()以及find_first_not_of()和find_last_not_of()。这些总是成对出现:一个从前到后搜索,一个从后到前搜索。所有这些也都具有以下形式的相同的四个重载:
要搜索的模式可以是单个字符,也可以是字符串,后者表示为 C++ 字符串、以 null 结尾的 C-string,或者是使用第一个n值的字符缓冲区。(r)find()函数搜索完整模式的一次出现,find_xxx_of() / find_xxx_not_of()函数族搜索模式中出现/未出现的任何单个字符。结果是从开头或结尾开始的第一个匹配项的索引,如果没有找到匹配项,则为npos。
最可选的pos参数是搜索应该开始的索引。对于向后搜索的功能,pos的默认值为npos。
修改字符串
要修改一个字符串,可以使用从vector开始已知的所有成员,包括erase()、clear()、push_back()等等(参见第三章)。附加函数或具有字符串特定重载的函数有assign()、insert()、append()、+=和replace()。他们的行为应该是明显的;只有replace()可能需要一些解释。首先,让我们介绍一下这五个函数的大量有用的重载。这些通常是这样的形式:
对于移动字符串,assign(string&&)也被定义。因为+=操作符本质上只有一个参数,自然只有C++ string、C 风格的字符串和初始化列表重载是可能的。
类似于它的vector对应物,对于insert(),标有(*)的重载返回一个iterator而不是一个string。出于同样的原因,insert()函数有两个额外的重载:
只有insert()和replace()需要一个Position。对于insert(),这通常是一个索引(一个size_t,除了最后两个重载,它是一个迭代器(再次类似于vector::insert())。对于replace(),Position是一个范围,使用两个const_iterator指定(不适用于substring重载),或者使用一个起始索引和一个长度指定(不适用于最后两个重载)。
换句话说,replace()并不像您所期望的那样,用另一个字符或字符串替换出现的给定字符或字符串。相反,它用一个新的序列(字符串、子字符串、填充模式等,长度可能不同)替换指定的子范围。您之前已经看到了它的使用示例(2 是被替换范围的长度):
s.replace(s.find("be"), 2, "are");
要替换所有出现的子字符串或给定模式,您可以使用正则表达式和本章稍后解释的std::regex_replace()函数。为了替换单个字符,第四章中的通用std::replace()和replace_if()算法也是一种选择。
最后一个修改函数与它的vector对应物有一个显著的不同,那就是erase():除了两个基于迭代器的重载,它还有一个处理索引的重载。用它来删除尾部或子区域,或者,如果你愿意,可以clear()它:
string& erase(size_t pos = 0, size_t len = npos);
构造字符串
除了创建一个空字符串的默认构造函数之外,该构造函数还有与前一小节中的函数相同的七个重载,当然还有一个用于string&&。(和其他容器一样,所有的字符串构造函数都有一个可选的自定义分配器参数,但这只是高级用法。)
从 C++14 开始,各种字符类型的basic_string对象也可以通过添加后缀s从相应的字符串文字中构造。这个文字运算符是在std::literals::string_literals名称空间中定义的:
字符串长度
要获得string的长度,可以使用典型的容器成员size()或其特定于字符串的别名length()。两者都返回字符串包含的类似于char的元素的数量。但是要注意:C++ 字符串不知道所使用的字符编码,所以它们的长度等于技术上所说的代码单元的数量,这可能大于代码点或字符的数量。众所周知的编码是可变长度 Unicode 编码 UTF-8 和 UTF-16,其中并非所有字符都表示为单个代码单元:
获得码位数的一种方法是先转换成 UTF-32 编码的字符串,使用本章后面介绍的字符编码转换工具。
复制(子)字符串
另一个有特定字符串别名的vector函数(紧挨着size())是data(),它的等价函数是c_str()。两者都返回一个指向内部字符数组的const指针(没有复制)。要将字符串复制到 C 风格的字符串,请使用copy():
size_t copy(char* out, size_t len, size_type pos = 0) const;
这会将从pos开始的len char值复制到out。也就是说,它也可以用来复制子串。要将子字符串创建为 C++ 字符串,请使用substr():
string substr(size_t pos = 0, size_t len = npos) const;
比较字符串
可以使用非成员比较运算符(==、<、>=等)或它们的compare()成员,将字符串与其他 C++ 字符串或 C 风格字符串进行词典式比较。后者具有以下重载:
int compare(const string& str) const noexcept;
int compare(size_type pos1, size_type n1, const string& str
[, size_type pos2, size_type n2 = npos]) const;
int compare(const char* s) const;
int compare(size_type pos1, size_type n1, const char* s
[, size_type n2]) const;
pos1 / pos2是第一个/第二个字符串中应该开始比较的位置,n1 / n2是从第一个/第二个字符串开始比较的字符数。如果两个字符串相等,返回值为零;如果第一个字符串小于/大于第二个字符串,返回值为负/正数。
字符串转换
为了从字符串中解析各种类型的整数,定义了以下形式的一系列非成员函数:
int stoi(const (w)string&, size_t* index = nullptr, int base = 10);
有以下几种变体:stoi()、stol()、stoll()、stoul()、stoull(),其中i代表int、l代表long、u代表unsigned。这些函数跳过所有前导空白字符,之后解析由base确定的语法所允许的尽可能多的字符。如果提供了一个index指针,它将接收未转换的第一个字符的索引。
类似地,为了解析浮点数,存在一组如下形式的函数:
float stof(const (w)string&, size_t* index = nullptr);
提供stof()、stod()、stold()分别转换为float、double、long double。
为了进行相反的转换,将数值型转换为 a (w)string,提供了函数to_(w)string( X ),其中 X 可以是int、unsigned、long、unsigned long、long long、unsigned long long、float、double或long double。返回值是一个std::(w)string。
人物分类,
<cctype>和<cwctype>标题提供了一系列函数来分别对char和wchar_t字符进行分类。这些函数是std::is类(int)(只为代表char s 的int s 定义)和std::isw类(wint_t)(类比;wint_t是一个整数typedef,其中 class 等于表 6-1 中的一个值。如果给定的字符属于该类,所有函数都返回非零值int,否则返回零。
表 6-1。
The 12 Standard Character Classes
| 班级 | 描述 | | --- | --- | | `cntrl` | 控制字符:所有非`print`字符。包括:`'\0'`、`'\t'`、`'\n'`、`'\r'`等。 | | `print` | 可打印字符:数字、字母、空格、标点符号等。 | | `graph` | 带图形表示的字符:除“”以外的所有`print`字符。 | | `blank` | 一行中分隔单词的白色字符。至少“”和`'\t'`。 | | `space` | 空白字符:至少所有的`blank`字符、`'\n'`、`'\r'`、`'\v'`和`'\f'`。从不`alpha`人物。 | | `digit` | 十进制数字(`0`–`9`)。 | | `xdigit` | 十六进制数字(`0`–`9`、`A`–`F`、`a`–`f`)。 | | `alpha` | 字母字符。至少是所有的`lowercase`和`uppercase`字符,而绝不是`cntrl`、`digit`、`punct`和`space`字符中的任何一个。 | | `lower` | 小写字母`alpha`(默认区域设置为`a`–`z`)。 | | `upper` | 大写字母`alpha`(默认区域设置为`A`–`Z`)。 | | `alnum` | 字母数字字符:所有`alpha`和`digit`字符的组合。 | | `punct` | 标点符号(`! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ∼`为默认区域设置)。从来没有一个`space`或`alnum`的角色。 |相同的头还提供了tolower() / toupper()和towlower() / towupper()函数,用于在小写和大写字符之间进行转换。字符再次使用整数int和wint_t类型表示。如果转换没有定义或不可能,这些函数只返回它们的输入值。
所有字符分类和转换的确切行为取决于活动的 C 语言环境。本章稍后将详细解释区域设置,但本质上这意味着活动的语言和区域设置可能会导致不同的字符集被认为是字母、小写或大写、数字、空白等等。表 6-1 列出了不同字符类别的所有一般属性和关系,并给出了一些默认C区域设置的例子。
Note
在“本地化”部分,您还可以看到 C++ <locale>头提供了一个重载列表,这些重载用于使用给定的locale而不是活动的C语言环境的std::is class ()和std::tolower() / toupper()(都在字符类型上模板化)。
字符编码转换,
字符编码决定了代码点(许多但不是所有代码点都是字符)如何表示为二进制代码单元。示例包括 ASCII(具有 7 位代码单元的经典编码)、固定长度的 UCS-2 和 UCS-4 编码(分别为 16 位和 32 位代码单元),以及三种主要的 Unicode 编码:固定长度的 UTF-32(对每个代码点使用单个 32 位代码单元)和可变长度的 UTF-8 和 UTF-16 编码(分别将每个代码点表示为一个或多个 8 位或 16 位代码单元;UTF-8 最多 4 个单元,UTF-16 最多 2 个单元)。Unicode 和各种字符编码和转换的细节可以写满一本书;我们在这里解释在实践中在编码之间转换你需要知道什么。
包含低级编码转换逻辑的对象的类模板是std::codecvt<CharType1, CharType2, State> ( cvt可能是converter的简称)。它是在<locale>中定义的(正如您在下一节中看到的,这实际上是一个 locale 方面)。前两个参数是 C++ 字符类型,用于表示两种编码的代码单元。对于所有标准实例化,CharType2是char. State是一个高级参数,我们不做进一步解释(所有标准专门化都使用来自<cwchar>的std::mbstate_t)。
表 6-2 中列出的四个codecvt专门化在<locale>中定义。此外,<codecvt>标题定义了表 6-3 中列出的三个std::codecvt子类。 1 对于这些,CharT对应codecvt基类的CharType1参数;如前所述,CharType2永远是char。
表 6-3。
Character-Encoding Conversion Classes Defined in <codecvt>
表 6-2。
Character-Encoding Conversion Classes Defined in <locale>
尽管理论上可以直接使用codecvt实例,但是使用来自<locale>的std::wstring_convert<CodecvtT, WCharT=wchar_t>类要容易得多。这个助手类方便了char字符串和(通常更宽的)字符类型WCharT字符串之间的双向转换。尽管wstring_convert的名字容易引起误解(已经过时),但它也可以与u16strings 或u32strings 相互转换,而不仅仅是与wstrings 相互转换。
回想一下std::string长度部分的以下例子:
要将该字符串转换为 UTF-32,您可能希望实现以下功能:
不幸的是,这不能编译。对于在<codecvt>中定义的转换器子类,这将编译。但是codecvt基类的析构函数是protected(像所有标准的 locale facets 一样:稍后讨论),并且wstring_convert析构函数调用它来删除它拥有的转换器实例。这种设计缺陷可以通过使用如下的帮助器包装器来规避(类似的技巧可以应用于使任何受保护的函数可公开访问,而不仅仅是析构函数):
为了编译代码,您可以将第一行替换为下面的 2 :
typedef deletable<std::codecvt<char32_t,char,std::mbstate_t>> cvt;
要使用这些转换器潜在的特定于语言环境的变体(见下一节),请使用以下代码(除了""之外,也可以使用其他语言环境名称):
typedef deletable<std::codecvt_byname<char32_t,char,std::mbstate_t>> cvt;
std::wstring_convert<cvt, char32_t> convertor(new cvt(""));
一个相关的类是wbuffer_convert<CodecvtT, WCharT=wchar_t>,它包装了一个basic_streambuf<char>并使其充当一个basic_streambuf<WCharT>(流缓冲区在第五章中有非常简要的解释)。一个wbuffer_convert实例由一个可选的basic_streambuf<char>*、CodecvtT*和状态构成。包装缓冲区的 getter 和 setter 都被称为rdbuf(),当前转换状态可以使用state()获得。下面的代码构造了一个接受宽字符串的流,但是将它写入一个 UTF-8 编码的文件(需要<fstream>):
本地化<locale>
日期、货币值和数字的文本表示由区域和文化约定控制。举例来说,以下三个句子是类似的,但是使用本地货币、数字和日期格式编写:
In the U.S., John Doe has won $100,000.00 on the lottery on 3/14/2015.
In India, Ashok Kumar has won ₹1,00,000.00 on the lottery on 14-03-2015.
En France, Monsieur Brun a gagné 100.000,00 € à la loterie sur 14/3/2015.
在 C++ 中,所有与以特定于语言环境的方式处理文本相关的参数和功能都包含在一个std::locale对象中。这些不仅包括刚刚说明的数值和日期的格式化,还包括特定于地区的字符串排序和转换。
区域名称
标准的locale对象是由一个地区名构成的:
std::locale(const char* locale_name);
std::locale(const std::string& locale_name);
这些名称通常由两个字母的 ISO-639 语言代码和两个字母的 ISO-3166 国家代码组成。然而,精确的格式是特定于平台的:例如,在 Windows 上,英美地区的名称是"en-US",而在基于 POSIX 的系统上是"en_US"。大多数平台支持,或者有时需要额外的规范,比如区域代码、字符编码等等。有关支持的区域设置名称和选项的完整列表,请参考您的平台文档。
只有两个可移植的语言环境名称,""和"C":
- 使用
"",您可以从程序的执行环境(也就是操作系统)中获取用户偏好的区域和语言设置来构造一个std::locale。 "C"语言环境表示经典或中性语言环境,这是所有 C 和 C++ 程序默认使用的标准化、可移植的语言环境。
使用"C"语言环境,前面的例句变成了
Anywhere, a C/C++ programmer may win 100000 on the lottary on 3/14/2015.
Tip
当写入计算机程序要读取的文件(配置文件、数字数据输出等)时,强烈建议您使用非特定的"C"语言环境,以避免解析过程中出现问题。当向用户显示值时,应该考虑使用基于用户偏好的语言环境("")。
全球语言环境
活动的全局语言环境影响各种格式化或解析文本的标准 C++ 函数,最直接的是本章后面讨论的正则表达式算法和第五章中看到的 I/O 流。是有一个程序范围的全局区域设置实例,还是每个执行线程有一个,这取决于实现。
全球语言环境总是以经典的"C"语言环境开始。要设置全局语言环境,可以使用静态的std::locale::global()函数。要获得当前活动的全局语言环境的副本,只需默认构造一个std::locale。例如:
Note
为了避免竞争情况,标准 C++ 对象(比如新创建的流或regex对象)总是在构造时复制全局locale。因此调用global()不会影响现有的对象,包括std::cout和<iostream>的其他标准流。要更改他们的区域设置,您必须调用他们的imbue()成员。
基本标准::区域设置成员
下表列出了由一个std::locale提供的最基本的功能,不包括复制成员。用于组合或自定义区域设置的更多高级成员将在本节末尾讨论:
局部多面
从上一小节可以明显看出,std::locale公共接口没有提供太多的功能。所有的本地化工具都以 facets 的形式提供。每个locale对象封装了许多这样的方面,对这些方面的引用可以通过std::use_facet<FacetType>()函数获得。例如,下面的示例使用经典区域设置的数字标点符号方面来打印区域设置的十进制标记,以格式化浮点数:
对于所有标准刻面,不能复制、移动、交换或删除由use_facet()的结果引用的实例。该方面由给定的locale共同拥有,并与拥有它的(最后一个)locale一起被删除。当请求给定的locale不拥有的FacetType时,引发bad_cast异常。要验证刻面的存在,您可以使用std::has_facet<FacetType>()。
Caution
永远不要做类似于auto& f = use_facet<...>(std::locale("..."));的事情:facet f由临时locale对象拥有,所以使用它可能会崩溃。
默认情况下,locale s 包含本节剩余部分中介绍的所有刻面的专门化,每个刻面又至少专门化了char和wchar_t字符类型(其他最低要求将在本节中讨论)。实现可能包括更多的方面,程序甚至可以自己添加定制的方面,这将在后面解释。
我们现在按顺序讨论表 6-4 中列出的 12 个标准刻面类别,按类别分组。之后,我们将展示如何组合不同地区的方面并创建定制的方面。虽然这可能不是大多数程序员经常使用的东西,但是偶尔确实需要定制方面。无论如何,了解本地化的范围和各种影响,并在开发显示或处理用户文本的程序(即大多数程序)时将它们牢记在心是值得的。
表 6-4。
Overview of the 12 Basic Facet Classes , Grouped by Category
| 种类 | 面状 | | --- | --- | | `numeric` | `numpunct`、`num_put`、`num_get` | | `monetary` | `moneypunct`、`money_put`、`money_get` | | `time` | `time_put`,`time_get` | | `ctype` | `ctype`,`codecvt` | | `collate` | `collate` | | `messages` | `messages` |数字格式
numeric和monetary类别的方面遵循相同的模式:有一个punct方面(标点符号的缩写)带有特定于地区的格式参数,另外还有一个put和一个get方面分别负责实际的格式和值的解析。后两个方面主要供第五章中介绍的流对象使用。它们用来读取或写入值的具体格式由在punct facet 中设置的参数和使用流成员或流操纵器设置的其他参数的组合决定。
数字标点符号
std::numpunct<CharT>方面提供了检索以下与数值和布尔值格式相关的信息的函数:
decimal_point():返回小数点分隔符thousands_sep():返回千位分隔符grouping():返回一个编码数字分组的std::stringtruename()和falsename():返回带有布尔值文本表示的basic_string<CharT>
在本节开头的彩票示例中,数值 100000.00 使用三种不同的语言环境格式化:"100,000.00"、"1,00,000.00"和"100.000,00"。前两个区域分别使用逗号(,)和点(.)作为千位和小数点分隔符,而第三个区域则相反。
数字grouping()被编码为一系列char值,表示每组中的数字数量,从最右边组中的数字开始。序列中的最后一个char也用于所有后续组。例如,大多数地区将数字三个一组,编码为"\3"。(注意:不要使用"3",因为'3' ASCII 字符会产生值为 51 的char;也就是:'3' == '\51'。)然而,对于印度地区,如"1,00,000.00"所示,只有最右边的组包含三位数;所有其他组只包含两个。这被编码为"\3\2"。为了表示无限组,可以在最后一个位置使用std::numeric_limits<char>::max()值。空的grouping() string表示根本不应该使用分组,例如,对于经典的"C"语言环境就是这种情况。
数值的格式化和解析
std::num_put和num_get方面构成了第五章中描述的<<和>>流操作符的实现,并提供了具有以下签名的两组方法:
Iter put(Iter target, ios_base& stream, char fill, X value)
Iter get(Iter begin, Iter end, ios_base& stream, iostate& error, X& result)
这里的X可以是bool、long、long long、unsigned int、unsigned long、unsigned long long、double、long double,也可以是一个void指针。对于get(),unsigned short,float也是可以的。这些方法要么格式化一个给定的数字value,要么尝试解析范围begin, end)中的字符。在这两种情况下,ios_base参数是对一个流的引用,从该流中获取区域设置和格式信息(例如,包括流的格式标志和精度:参见第 [5 章)。
所有的put()函数在写入格式化后的字符序列后简单地返回target。如果格式化长度小于stream.width(),则使用fill字符进行填充(填充规则参见第五章)。
如果解析成功,get()将数值存储在result中。如果输入与格式不匹配,result被设置为零,并且iostate参数中的failbit被设置(参见第五章)。如果解析值对于类型X过大/过小,则failbit也被设置,并且result被设置为std::numeric_limits<X>::max() / lowest()(参见第一章)。如果到达输入的结尾(可能是成功或失败),则eofbit被设置。返回解析序列后第一个字符的迭代器。
我们在这里没有展示示例代码,但是这些方面类似于接下来介绍的货币格式化方面,对此我们包括了一个完整的示例。
货币格式
货币标点符号
std::moneypunct<CharType, International=false>方面提供了检索以下与格式化货币值相关的信息的函数:
decimal_point()、thousands_sep()、grouping():类似前面看到的数字标点符号成员。frac_digits():返回小数点后的位数。典型值是2。curr_symbol():如果International模板参数为false,则返回货币符号,如'€',如果International为true,则返回国际货币代码(通常为三个字母)后加一个空格,如"EUR"。pos_format()和neg_format()返回一个money_base::pattern结构(稍后讨论),描述如何格式化正负货币值。positive_sign()和negative_sign():返回正负货币值的格式string。
后四个成员需要更多的解释。它们使用在moneypunct的基类std::money_base中定义的类型。定义为struct pattern{ char field[4]; }的money_base::pattern结构是一个包含money_base::part枚举的四个值的数组,这些值受支持:
比如,假设neg_format()模式是{none, symbol, sign, value},货币符号是'$',negative_sign()返回"()",frac_digits()返回2。然后值-123456被格式化为"$(1,234.56)"。
Note
对于美国和许多欧洲地区,frac_digits()等于2,这意味着无格式的值要用分来表示,而不是美元或欧元。不过,情况并不总是这样:例如,对于日本的语言环境来说,frac_digits()就是0。
货币值的格式化和解析
方面std::money_put和money_get处理货币值的格式化和解析,主要供第五章中讨论的put_money()和get_money() I/O 操纵器使用。方面提供了这种形式的方法:
Iter put(Iter target, bool intl, ios_base& stream, char fill, X value)
Iter get(Iter begin, Iter end, bool intl, ios_base& stream,
iostate& error, X& result)
这里的X不是std::string就是long double。参数的行为和含义与之前讨论的num_put和num_get相似。如果intl为false,则使用类似$的货币符号;否则,使用类似USD的字符串。
下面说明了如何使用这些方面,尽管您通常简单地使用std::put_ / get_money()(使用<cassert>和<sstream>):
时间和日期格式
两个面std::time_get和time_put处理时间和日期的解析和格式化,并为第五章中的get_time()和put_time()操纵器提供动力。它们提供具有以下签名的方法:
Iter put(Iter target, ios_base& stream, char fill, tm* value, <format>)
Iter get(Iter begin, Iter end, ios_base& stream, iostate& error, tm* result,
<format>)
<format>或者是'const char* from, const char* to',指向使用与第二章中解释的strftime()相同的语法表达的时间格式模式,或者是具有可选修饰符'char format, char modifier'的相同语法的单个时间格式说明符。参数的行为和含义类似于数字和货币格式方面的行为和含义。第二章也解释了std::tm的结构。只有那些在格式化模式中提到的成员被使用/写入。
除了通用的get()函数之外,time_get方面还有一系列更受限制的解析函数,所有这些函数都有以下签名:
Iter get_x(Iter begin, Iter end, ios_base& stream, iostate& error, tm*)
字符分类、变换和转换
字符分类和转换
ctype<CharType>方面提供了一系列依赖于地区的字符分类和转换函数,包括前面看到的<cctype>和<cwctype>头文件的等价函数。
为了在下面列出的字符分类函数中使用,定义了 12 个位掩码类型的成员常量ctype_base::mask(ctype_base是ctype的基类),每个字符类一个。它们的名称与表 6-1 中给出的类名相同。虽然它们的值未指定,alnum == alpha|digit和graph == alnum|punct。下表列出了所有分类功能(输入字符范围用两个CharType*指针b和e表示):
相同的方面也提供这些转换功能:
| 成员 | 描述 | | --- | --- | | `tolower(c)``toupper(c)``tolower(b,e)` | 对单个字符(返回结果)或字符范围[ `b`,`e`)(就地转换)执行从上至下的转换,反之亦然;`e`又回来了)。无法转换的字符保持不变。 | | `widen(c)` `widen(b,e,o)` | 将`char`值转换为单个字符(返回结果)或字符范围[ `b`,`e`]上的刻面的字符类型(转换后的字符放入从`*o`开始的输出范围中);`e`又回来了)。变换的角色从不属于它们的源角色不属于的类。 | | `narrow(c,d)` `narrow(b,e,d,o)` | 转换到`char`;与`widen()`相反。然而,只有对于 96 个基本源字符(除了`$`、```cpp 和`@`之外的所有`space`和【ASCII 字符),关系`widen(narrow(c,0)) == c`才保证成立。如果没有现成的转换字符,则使用给定的默认值`char d`。 |<locale>头为ctype面的那些函数定义了一系列方便函数,这些函数也存在于<cctype> and <cwctype> : std::is类(c, locale&)中,a 类名称来自表 6-1 和tolower(c, locale&) / toupper(c, locale&)。它们的实现都有如下形式(返回类型不是bool就是CharT):
template <typename CharT> ... function(CharT c, const std::locale& l) {
return std::use_facet<std::ctype<CharT>>(l).function(c);
}
```cpp
##### 字符编码转换
一个`std::codecvt`刻面在两种字符编码之间转换字符序列。这在前面的“字符编码转换”中有所解释,因为这些方面在语言环境之外也很有用。每个`std::locale`至少包含表 6-2 中列出的四个`codecvt`专门化的实例,它们实现了潜在的特定于地区的转换器。这些被 Chapter 5 的流隐式地使用,例如在宽字符串和窄字符串之间进行转换。因为不建议直接使用这些低级方面,所以我们在这里不解释它们的成员。请始终使用“字符编码转换”一节中讨论的助手类。
#### 字符串排序和散列
`std::collate<CharType>`方面实现了以下依赖于地区的字符串排序比较和散列函数。使用 begin(包含)和 end(不包含)`CharType*`指针指定所有字符序列:
<colgroup><col> <col></colgroup>
| 成员 | 描述 |
| --- | --- |
| `compare()` | 两个字符序列的依赖于区域设置的三向比较,如果第一个在第二个之前,则返回- `1`,如果两个相等,则返回`0`,否则返回`+1`。不一定等同于简单的字典序比较。 |
| `transform()` | 将给定的字符序列转换为特定的规范化形式,该形式作为`basic_string<CharType>`返回。对两个转换后的字符串应用简单的词典排序(就像对它们的`operator<`)会返回与对未转换的序列应用 facet 的`compare()`函数相同的结果。 |
| `hash()` | 返回给定序列的一个`long`散列值(参见第三章的散列),该散列值对于所有`transform()`具有相同规范化形式的序列都是相同的。 |
一个`std::locale`本身是一个类似于`std::less<std::basic_string<CharT>>`的函子(见第二章),使用其`collate<CharT>` facet 的`compare()`函数比较两个`basic_string<CharT>`。下面的示例使用经典区域设置和法语区域设置(要使用的区域设置名称是特定于平台的)按字典顺序对法语字符串进行排序。除了`<locale>`,还需要`<vector>`、`<string>`、`<algorithm>`:

#### 消息检索
`std::messages<CharT>`方面有助于从消息目录中检索文本消息。这些目录本质上是将一系列整数映射到本地化字符串的关联数组。原则上,这可用于根据错误类别和代码检索翻译后的错误信息(参见第八章)。哪些目录可用,以及它们的结构如何,完全是特定于平台的。对于某些应用程序,使用了标准化的消息目录 API(比如 POSIX 的`catgets()`或 GNU 的`gettext()`),而其他应用程序可能不提供任何目录(Windows 通常就是这种情况)。方面提供了以下功能:
<colgroup><col> <col></colgroup>
| 成员 | 描述 |
| --- | --- |
| `open(n,l)` | 基于给定的特定于平台的字符串`n` (a `basic_string<CharT>`)和给定的`std::locale l`打开一个目录。返回某个有符号整数类型的唯一标识符`catalog`。 |
| `get(c,set,id,def)` | 从具有给定的`catalog`标识符`c`的目录中检索由`set`和`id`(两个`int`值,其解释特定于目录)标识的消息,并将其作为`basic_string<CharT>`返回。如果没有找到这样的消息,则返回`def`。 |
| `close(c)` | 用给定的`catalog`标识符`c`关闭目录。 |
### 组合和自定义语言环境
在组合或定制语言环境方面时,`<locale>`库的构造被设计得非常灵活。
#### 组合面
`std::locale`提供`combine<FacetType>(const locale& c)`,它返回调用`combine()`的`locale`的副本,除了从给定参数复制的`FacetType`方面。这里有一个例子(`using namespace std`是假设的):

或者,`std::locale`有一个构造函数,它接受一个基类`locale`和一个重载方面,这个重载方面做的和`combine()`一样。例如,前面示例中的`combined`的创建可以表示如下:
locale combined(locale::classic(), &use_facet<moneypunct>(chinese));
此外,`std::locale`有许多构造函数可以一次覆盖一个或多个类别的所有方面(`String`或者是一个`std::string`或者是一个表示特定地区名称的 C 风格字符串):
locale(const locale& base, String name, category cat) locale(const locale& base, const locale& overrides, category cat)
对于表 6-4 中列出的六个类别中的每一个,`std::locale`定义了一个具有该名称的常数。`std::locale::category`类型是位掩码类型,这意味着可以使用位运算符组合类别。例如,`all`常数被定义为`collate | ctype | monetary | numeric | time | messages`。这些构造函数可以用来创建一个类似于前面的`combined`方面:
locale combined(locale::classic(), chinese, locale::monetary);
#### 自定义方面
facet 的所有公共函数 func `()`简单地调用 facet 上的一个受保护的虚拟方法,这个方法叫做`do_` func `()`。 <sup>3</sup> 你可以通过继承现有的方法并覆盖这些`do`-方法来实现定制的方面。
第一个简单的例子改变了`numpunct`方面的行为,使用字符串`"yes"`和`"no"`代替`"true"`和`"false"`进行布尔输入和输出:
class yes_no_numpunct : public std::numpunct { protected: virtual string_type do_truename() const override { return "yes"; } virtual string_type do_falsename() const override { return "no"; } };
您可以使用这个自定义方面,例如,通过将它注入到流中。下面将`"yes / no"`打印到控制台:
std::cout.imbue(std::locale(std::cout.getloc(), new yes_no_numpunct)); std::cout << std::boolalpha << true << " / " << false << std::endl;
回想一下,facet 是引用计数的,`std::locale`的析构函数因此正确地清理了您的自定义 facet。
从像`numpunct`和`moneypunct`这样的方面派生的缺点是,这些通用基类实现了与地区无关的行为。相反,要从特定于地区的方面开始,可以使用方面类,如`numpunct_byname`。对于到目前为止看到的所有刻面,除了`numeric`和`monetary put`和`get`刻面之外,还有一个同名的刻面子类,但是附加了`_byname`。它们是通过传递一个地区名(`const char*`或`std::string`)来构造的,然后表现得好像取自相应的`locale`。您可以从这些方面重写,以便只修改给定区域设置的方面的特定方面。
下一个示例修改了货币标点方面,以便使用会计中的格式标准进行输出:负数放在括号中,填充以特定的方式完成。您可以从`std::moneypunct_byname` ( `string_type`在`std::moneypunct`中定义)开始,而无需覆盖一个地区的货币符号或大多数其他设置:

该面可用于如下用途(参见第五章了解`<iomanip>`的流 I/O 操纵器的详细信息):

这个程序的输出应该是
(5.00)
理论上,您可以通过直接从`std::facet`继承来创建一个新的 facet 类,并使用相同的构造函数将其添加到`locale`中,以便稍后在您自己的库代码中使用。唯一的额外要求是定义一个默认构造的静态常量,名为`std::locale::id`类型的`id`。
### c 处所
C 标准库中的区域敏感函数(包括`<cctype>`中的大多数函数以及`<cstdio>`和`<ctime>`的 I/O 操作)不会直接受到全局 C++ `locale`的影响。相反,它们由相应的 C 语言环境控制。这个 C 语言环境通过以下两个函数之一进行更改:
* `std::locale::global()`保证修改 C 语言环境以匹配给定的 C++ `locale`,只要后者有名字。否则,它对 C 语言环境的影响(如果有的话)是由实现定义的。
* 使用`<clocale>`的`std::setlocale()`功能。这丝毫不会影响 C++ 的全局`locale`。
换句话说,当使用标准语言环境时,C++ 程序应该简单地调用`std::locale::global()`。然而,为了在组合多个地区时编写可移植的代码,您必须同时调用 C++ 和 C 函数,因为当将`global()` C++ 地区更改为组合的`locale`时,并非所有的实现都如预期的那样设置 C 地区。这是按如下方式完成的:

`setlocale()`函数采用单个类别号(不是位掩码类型;支持的值至少包括`LC_ALL, LC_COLLATE`、`LC_CTYPE`、`LC_MONETARY`、`LC_NUMERIC`和`LC_TIME`以及一个语言环境名,所有这些都类似于它们的 C++ 等价物。如果成功,它将返回活动 C 语言环境的名称,作为指向重用的全局缓冲区的`char*`指针,如果失败,则返回`nullptr`。如果为语言环境名传递了`nullptr`,则 C 语言环境不会被修改。
不幸的是,C 语言环境的功能远不如 C++ 强大:定制方面或选择单个方面进行组合是不可能的,这使得在可移植代码中使用这种高级语言环境变得不可能。
`<clocale>`表头还有一个功能:`std::localeconv()`。它返回一个指向全局`std::lconv struct`的指针,其公共成员相当于`std::numpunct` ( `decimal_point`、`thousands_sep`、`grouping`)和`std::moneypunct`方面(`mon_decimal_point`、`mon_thousands_sep`、`mon_grouping`、`positive_sign`、`negative_sign`、`currency_symbol`、`frac_digits`等)的函数。这些值应该被视为只读的:写入它们会导致未定义的行为。
## 正则表达式`<regex>`
正则表达式是与目标字符序列匹配的一个或多个模式的文本表示。例如,正则表达式`ab*a`匹配任何以字符`a`开始、后跟零个或多个`b`并再次以`a`结束的目标序列。正则表达式可用于搜索或替换目标中的特定模式,或者验证它是否匹配所需的模式。稍后您将看到如何使用`<regex>`库来执行这些操作;首先,我们介绍如何形成和创建正则表达式。
### ECMAScript 正则表达式语法
用于以文本形式表达模式的语法是由语法定义的。默认情况下,`<regex>`使用 ECMAScript 脚本语言(以其广泛使用的方言 JavaScript、JScript 和 ActionScript 而闻名)使用的语法的修改版本。以下是这种语法的简明、全面的参考。
正则表达式模式是术语序列的析取,每个术语要么是一个原子,要么是一个断言,要么是一个量化的原子。表 6-5 和表 6-6 中列出了支持的原子和断言,表 6-7 显示了原子如何被量化以表达重复模式。这些术语在没有分隔符的情况下连接在一起,然后使用`|`运算符选择性地组合成析取项。允许空析取,模式`|`匹配给定模式或空序列。一些例子应该阐明:
* `\r\n?|\n`匹配所有主要平台的换行符序列(即`\r`、`\r\n`或`\n`)。
* `<(.+)>(.*)</\1>`匹配一个类似 XML 的序列,其形式为`<`标签`>`任何东西`</`标签`>`,使用反向引用来匹配结束标签,并在中间进行额外的分组以允许检索第二子匹配(稍后讨论)。
* `(?:\d{1,3}\.){3}\d{1,3}`匹配 IPv4 地址。不过,这个天真的版本也匹配非法地址,比如`999.0.0.1`,而且糟糕的分组会阻止四个匹配的数字在以后被检索。注意,如果没有`?:`,`\1`仍然只表示第三个匹配的号码。
表 6-7。
Quantifiers That Can Be Used for Repeated Matches of Atoms
<colgroup><col> <col></colgroup>
| 数量词 | 意义 |
| --- | --- |
| 原子 `*` | 贪婪地匹配 atom 零次或多次。 |
| 原子 `+` | 贪婪地匹配 atom 一次或多次。 |
| 原子 `?` | 贪婪地匹配原子零次或一次。 |
| 原子 `{i}` | 贪婪地精确匹配 atom】次。 |
| 原子 `{i,}` | 贪婪地匹配 atom `i`或更多次。 |
| 原子 `{i,j}` | 贪婪地在`i`和`j`之间匹配 atom 次。 |
表 6-6。
Assertions Supported by the ECMAScript Grammar
<colgroup><col> <col></colgroup>
| 主张 | 如果当前位置为,则匹配... |
| --- | --- |
| ^ | 目标的开始(除非指定了`match_not_bol`),或者紧随行结束符之后的位置。<sup>4</sup> |
| `$` | 目标的结尾(除非指定了`match_not_eol`),或者行结束符的位置。 |
| `\b` | 单词边界:下一个字符是单词字符 <sup>5</sup> ,而前一个字符不是,反之亦然。如果目标以单词字符开始/结束(并且没有分别指定`match_not_bow` / `match_not_eow`),则目标的开始和结束也是单词边界。 |
| `\B` | 不是单词边界:上一个和下一个字符都是单词或非单词字符。当目标的开始和结束是字边界时,见`\b`。 |
| `(?=`图案`)` | 给定模式的下一个匹配位置。这被称为积极的前瞻。 |
| `(?!`图案`)` | 给定模式下一次不匹配的位置。这被称为消极前瞻。 |
表 6-5。
All Atoms with a Special Meaning in the ECMAScript Grammar
<colgroup><col> <col></colgroup>
| 原子 | 比赛 |
| --- | --- |
| `.` | 除行结束符 4 之外的任何单个字符。 |
| `\0`、`\f`、`\n`、`\r`、`\t`、`\v` | 常见的控制字符之一:空、换页(FF)、换行(LF)、回车(CR)、水平制表符(HT)和垂直制表符(VT)。 |
| `\c`信 | 其代码单位等于给定 ASCII 小写或大写字母模 32 的控制字符。例如`\cj == \cJ == \n` (LF) as(码元为`j`或`J` ) % 32 = (106 或 74)% 32 = 10 = LF 的码元。 |
| `\x` hh | 带有十六进制代码单位 hh(正好两个十六进制数字)的 ASCII 字符。例如`\x0A == \n` (LF),以及\x6A `== J`。 |
| `\u` hhhh | 带有十六进制代码单位 hhhh(正好四个十六进制数字)的 Unicode 字符。例如`\u006A == J`和`\u03c0 ==` π(希腊字母 pi)。 |
| `[`类`]` | 给定类的一个角色(见正文):`[abc]`、`[a-z]`、`[[:alpha:]]`等等。 |
| `[^`类`]` | 不属于给定类别的字符(见正文)。例如:`[⁰-9]`、`[^[:s:]]`等等。 |
| `\d` | 十进制数字字符(简称`[[:d:]]`或`[[:digit:]]`)。 |
| `\s` | 空白字符(是`[[:s:]]`或`[[:space:]]`的缩写)。 |
| `\w` | 一个单词字符,即:一个字母数字或下划线字符(简称`[[:w:]]`或`[_[:alnum:]]`)。 |
| `\D`、`\S`、`\W` | `\d`、`\s`、`\w`的补语。换句话说,任何不是十进制数字、空格或单词字符的字符(简称`[^[:d:]]`等等)。 |
| `\`人物 | 给定的字符。只对`\ . * + ? ^ $ ( ) [ ] { } |`有要求,因为没有转义,这些有特殊意义;但是可以使用任何字符,只要`\`字符没有特殊含义。 |
| `(`图案`)` | 匹配模式并创建一个标记的子表达式,将它转换成可以量化的原子。它匹配的序列(称为子匹配)可以从`match_results`中检索或使用反向引用(稍后讨论)引用,当使用`regex_replace()`时,可以在周围模式或替换模式中进一步引用。 |
| `(?:`图案`)` | 同上,但是子表达式没有标记,这意味着子匹配没有存储在`match_results`中,也不能被引用。 |
| `\`整数 | 反向引用:匹配与前面带有索引整数的标记子表达式完全相同的序列。子表达式按照它们的左括号在完整模式中出现的顺序从左到右计数,从 1 开始(回忆:`\0`匹配空字符)。 |
Tip
当在 C++ 程序中以字符串形式输入正则表达式时,必须对所有反斜杠进行转义。第一个例子变成了`"\\r\\n?|\\n"`。因为这既乏味又晦涩,我们建议使用原始字符串:例如,`R"(\r\n?|\n)"`。请记住,括号是原始字符串文字符号的一部分,并不构成正则表达式组。
原子和断言的区别在于,前者消耗目标序列中的字符(通常是一个),而后者不消耗。模式中的(量化的)原子一个接一个地消耗目标字符,同时从左到右通过模式和目标序列。对于匹配的断言,特定的条件必须保持在目标中的当前位置(在键入文本时,将其视为插入符号位置)。
表 6-5 中的大部分原子匹配单个字符;只有子表达式和反向引用可以匹配一个序列。任何其他单个字符也是简单匹配该字符的原子。表 6-6 中提到的 match_ xxx 标志可选地传递给后面讨论的匹配函数或迭代器。
#### 字符类别
一个字符类是一个`[` d `]`或`[^` d `]`原子,它定义了一组可能(对于`[` d `]`)或可能(`[^` d `]`)匹配的字符。类定义 d 是一系列的类原子,每一个都
* 个性。
* 从`-`到(包括边界)的字符范围。
* 以反斜杠(`\`)开始:表 6-5 中任何原子的等价物,除了反向引用,具有明显的含义。注意,在这个上下文中,像`* + . $`这样的字符不需要转义,但是`- [ ] : ^`可能需要。同样,在类定义中,`\b`表示退格字符(`\u0008`)。
* 包围在嵌套方括号中的三种特殊字符类原子之一(稍后描述)。
描述符没有分隔符连接在一起。例如:`[_a-zA-Z]`匹配下划线或 A-Z 或 A-Z 范围内的单个字符,而`[^\d]`匹配任何非十进制数字的单个字符。
第一个特殊类原子具有形式`[:`名称`:]`。至少支持以下名称:字符分类一节中解释的所有 12 个字符类的等价物— `alnum`、`alpha`、`blank`、`cntrl`、`digit`、`graph`、`lower`、`print`、`punct`、`space`、`upper`、`xdigit`—以及`d`、`s`、`w`。后者中,`d`和`s`是`digit`和`space`的简称,`w`是与`[:w:]`相当于`_[:alnum:]`的一类字符(注意下划线!).也就是对于经典的`"C"`地区,`[[:w:]] == [_a-zA-Z]`。再比如,`[\D] == [^\d] == [^[:d:]] == [^[:digit:]] == [⁰-9]`。
第二种特殊的类原子类似于`[.` name `.]`,其中 name 是特定于地区和实现的整理元素名。这个名字可以是单个字符 c,在这种情况下`[[.` c `.]]`相当于`[` c `]`。类似地,`[[.comma.]]`可能等于`[,]`。有些名称指的是多字符排序元素:即,在特定字母表及其排序顺序中被视为单个字符的多个字符。后者的可能名称包括有向图的名称:`ae`、`ch`、`dz`、`ll`、`lj`、`nj`、`ss`等等。例如,`[[.ae.]]`匹配两个字符,而`[ae]`匹配一个字符。
最后,形式为`[=` name `=]`的类原子类似于`[.` name `.]`,除了它们匹配作为命名整理元素的相同主等价类的一部分的所有字符。本质上,这意味着法语中的`[=e=]`不仅要匹配 E,还要匹配é、è、ê、E、é等等。同样,德语中的`[=ss=]`应该匹配有向图 ss,但也要匹配 Eszett 字符()。
#### 贪婪与非贪婪量化
默认情况下,表 6-7 中定义的量化原子是贪婪的:它们首先匹配尽可能长的序列,如果匹配不成功,只尝试较短的序列。为了使它们不贪婪,也就是说,让它们首先尝试最短的可能序列,在量词后面添加一个问号(`?`)。
回想一下,比如之前的例子`"<(.+)>(.*)</\1>"`。当在`"<b>Bold</b>, not bold, <b>bold again</b>"`中搜索或替换其第一个匹配时,该模式匹配整个序列。非贪婪版本`"<(.+)>(.*?)</\1>"`,只匹配想要的`"<b>Bold</b>"`。
作为非贪婪量词的替代,也可以考虑负字符类(可能更有效),比如`"<(.+)>([^<]*)</\1>"`。
### 正则表达式对象
`<regex>`库将正则表达式建模为`std::basic_regex<CharT>`对象。其中,至少有两种专门化可用于窄串(`char`序列)和宽串(`wchar_t`序列):`std::regex`和`std::wregex`。示例使用了`regex`,但是`wregex`完全类似。
#### 构造和语法选项
默认构造的`regex`不匹配任何序列。更有用的`regular expressions`是使用以下形式的构造函数创建的:
regex(Pattern, regex::flag_type flags = regex::ECMAScript);
期望的正则表达式`Pattern`可以表示为`std::string`、空终止的`char*`数组、具有`size_t`长度的`char*`(要从缓冲区读取的`char`的数量)、`initializer_list<char>`或由开始和结束迭代器形成的范围。
当给定的模式无效时(不匹配的括号、错误的反向引用等等),抛出一个`std::regex_error`。这是一个带有额外的`code()`成员的`std::runtime_exception`,返回类型为`std::regex_constants::error_type` ( `error_paren`、`error_backref`等等)的 11 个错误代码之一。
最后一个参数决定使用哪种语法,并可用于切换某些语法选项。`flag_type`值是`std::regex_constants::syntax_option_type`值的别名。因为它是位掩码类型,所以它的值可以使用`|`操作符进行组合。支持以下语法选项:
<colgroup><col> <col></colgroup>
| [计]选项 | 影响 |
| --- | --- |
| `collate` | 形式`[a-z]`的字符范围变得对地区敏感。例如,对于法语地区,`[a-z]`应该匹配é、è等等。 |
| `icase` | 字符匹配以不区分大小写的方式进行。 |
| `nosubs` | 没有子表达式的子匹配存储在`match_results`(稍后讨论)。反向引用也可能会失败。 |
| `optimize` | 在构造正则表达式对象的过程中,提示实现优先考虑提高匹配速度而不是性能。 |
| `ECMAScript` | 使用基于 ECMAScript 的正则表达式语法(默认)。 |
| `basic` | 使用 POSIX 基本正则表达式语法(BRE)。 |
| `extended` | 使用 POSIX 扩展正则表达式语法(ERE)。 |
| `grep` | 使用 POSIX 实用程序`grep`(一个 BRE 变种)的语法。 |
| `egrep` | 使用 POSIX 实用程序`grep –E`(一个 ERE 变体)的语法。 |
| `awk` | 使用 POSIX 实用程序`awk`(另一个 ERE 变体)的语法。 |
最后六个选项中,只允许指定一个;如果未指定,则默认使用`ECMAScript`。所有 POSIX 语法都比 ECMAScript 语法老,功能也不如 ECMAScript 语法。因此,使用它们的唯一原因是您已经熟悉它们,或者已经有了预先存在的正则表达式。不管怎样,没有理由在这里详述这些语法。
#### 基本成员函数
`A regex` object 主要用于传递给一个全局函数或迭代器适配器,这将在后面解释,因此没有多少成员函数对其进行操作:
* 一个`regex`可以被复制、移动和交换。
* 可以使用一个新的正则表达式和可选的语法选项使用`assign()`对它进行(重新)初始化,它具有与其非默认构造函数完全相同的一组签名。
* `flags()`成员返回初始化时使用的语法选项标志,`mark_count()`返回其正则表达式中被标记的子表达式的个数(见表 6-5 )。
* 正则表达式`std::locale`由`getloc()`返回。这以多种方式影响匹配行为,并在构造时用活动的全局 C++ 语言环境进行初始化。施工后,可使用`imbue()`功能进行更改。
### 匹配和搜索模式
`std::regex_match()`函数验证完整的目标序列与给定的模式匹配,而类似的`std::regex_search()`函数搜索目标中模式的第一次出现。如果没有找到匹配,两者都返回`false`。这些函数模板有一组类似的重载,所有重载都具有以下形式的签名:
bool regex_match (Target [, Results&], const Regex&, match_flag_type = 0); bool regex_search(Target [, Results&], const Regex&, match_flag_type = 0);
除了最后一个参数之外,所有参数都以相同的字符类型`CharT`为模板,至少有`char`和`wchar_t`可以实现。至于论点:
<colgroup><col> <col></colgroup>
| 匹配标志 | 影响 |
| --- | --- |
| `match_default` | 使用默认匹配行为(该常量的值为零)。 |
| `match_not_bol``match_not_eol``match_not_bow` | 目标序列中的第一个或最后一个位置不再被认为是行/词的开始/结束。影响`^`、`$`、`\b`和`\B`注释,如表 6-6 中所述。 |
| `match_any` | 如果一个析取关系中的多个析取关系匹配,不需要找到它们中最长的匹配:任何匹配都可以(例如,找到第一个,如果这样可以加快速度的话)。与 ECMAScript 语法无关,因为它已经规定了对析取使用最左边的成功匹配。 |
| `match_not_null` | 该模式将与空序列不匹配。 |
| `match_continuous` | 该模式只匹配从目标序列开始处开始的序列(暗示用于`regex_match()`)。 |
| `match_prev_avail` | 当决定`^`、`$`、`\b`和`\B`注释的行和词的边界时,匹配算法查看`--first`处的字符,其中`first`指向目标序列的开始。置位时,`match_not_bol`和`match_not_bow`被忽略。在连续的目标子序列上重复调用`regex_search()`时非常有用。后面解释的迭代器可以正确地做到这一点,并且是枚举匹配的推荐方法。 |
* 前三个参数的典型组合是`(w)string`、`(w)smatch`、`(w)regex`。
* 除了`basic_string<CharT>`,`Target`序列也可以表示为空终止的`CharT` *数组(也用于字符串),或者一对双向迭代器,用于标记`CharT`序列的边界。在这两种情况下,正常的`Results`类型变成了`std::(w)cmatch`。
* 用于可选匹配输出参数的类型将在下一小节中讨论。
* 传递的`Regex`对象没有被复制,所以这些函数不能使用临时对象调用。
* 为了控制匹配行为,可以传递位掩码类型`std::regex_constants::match_flag_type`的值。下表显示了支持的值:
如果任何一个算法失败,就会产生一个`std::regex_error`。因为正则表达式的语法已经在构造`regex`对象时得到了验证(见前面),如果算法耗尽了资源,这种情况很少发生在非常复杂的表达式中。
#### 匹配结果
一个`std::match_results<CharIter>`实际上是一个`sub_match<CharIter>`元素的顺序容器(参见第三章),这些元素是指向目标序列的双向`CharIter`的`std::pair`,标记子匹配序列的边界。在索引 0 处,有一个用于完全匹配的`sub_match`,后面是每个标记的子表达式的一个`sub_match`,按照它们的左括号在正则表达式中出现的顺序(见表 6-5 )。提供了以下模板专门化:
<colgroup><col> <col> <col> <col></colgroup>
| 目标 | 匹配结果 | 子匹配 | 宪章 |
| --- | --- | --- | --- |
| `std::string` `std::wstring` | `std::smatch` `std::wsmatch` | `std::ssub_match` `std::wssub_match` | `std::string::const_iterator` `std::wstring::const_iterator` |
| `const char*` `const wchar_t*` | `std::cmatch` `std::wcmatch` | `std::csub_match``std::wcsub_` | `const char*` `const wchar_t*` |
##### 标准::子匹配
除了从`std::pair`继承的`first`和`second`成员,`sub_match` es 还有第三个成员变量叫做`matched`。如果匹配失败或者如果相应的子表达式没有参与匹配,则该布尔为`false`。例如,如果子表达式是非匹配析取项的一部分,或者是用`?`、`*`或`{0,` n `}`量化的非匹配原子的一部分,则会出现后一种情况。例如,当将`"(a)?b|(c)"`与`"b"`进行匹配时,匹配成功,匹配的`match_result`包含两个空的`sub_match`和`matched == false`。
下表总结了可用于`sub_match` es 的操作:
<colgroup><col> <col></colgroup>
| 操作 | 描述 |
| --- | --- |
| `length()` | 匹配序列的长度(如果不是`matched`则为 0) |
| `str()` /演职人员 | 将匹配序列作为`std::basic_string`返回 |
| `compare()` | 如果`sub_match`等于给定的`sub_match`、`basic_string`或空终止字符数组,则返回 0,如果大于/小于给定的`sub_match`、【】或空终止字符数组,则返回正/负数 |
| `==, !=,` `<, <=, >, >=` | 用于在`sub_match`和`sub_match`、`basic_string`或字符数组之间进行`compare()`运算的非成员运算符,反之亦然 |
| `<<` | 流向输出流的非成员运算符 |
##### 标准::匹配结果
使用`==`和`!=`可以复制、移动、交换和比较`match_results`是否相等。除了这些操作之外,还可以使用以下成员函数(省略了与自定义分配器相关的函数)。注意,与字符串不同,`size()`和`length()`在这里是不等价的:
<colgroup><col> <col></colgroup>
| 操作 | 描述 |
| --- | --- |
| `ready()` | 默认构造的`match_results`未就绪,在执行匹配算法后变为就绪。 |
| `empty()` | 返回`size()==0`(如果不是`ready()`或者匹配失败后返回`true`)。 |
| `size()` | 如果`ready()`匹配成功,返回包含的`sub_match`的数量(1 加上标记的子表达式的数量),否则返回零。 |
| `max_size()` | 由于实施或内存限制,理论上的最大值`size()`。 |
| `operator[]` | 返回带有指定索引 n 的`sub_match`(见前面)或带有`sub.matched == false`的空`sub_match sub`(如果 n `>= size()`)。 |
| `length(size_t=0)` | `results.length(` n `)`相当于`results[` n `].length()`。 |
| `str(size_t=0)` | `results.str(` n `)`相当于`results[` n `].str()`。 |
| `position(size_t=0)` | 目标序列的起点和`results[` n `].first`之间的距离。 |
| `prefix()` | 返回从目标序列开始(包含)到匹配序列开始(不包含)的范围内的一个`sub_match`。对于`regex_match()`总是空的。未定义如果不是`ready()`。 |
| `suffix()` | 返回一个`sub_match`,范围从完全匹配的结尾(不包括)到目标序列的结尾(包括)。对于`regex_match()`总是空的。未定义如果不是`ready()`。 |
| `begin()`、`cbegin()`、`end()`、`cend()` | 返回指向包含在`match_results`中的第一个或者倒数第二个`sub_match`的迭代器。 |
| `format()` | 根据指定的格式格式化匹配的序列。不同的重载(基于字符串或基于迭代器)有输出、模式和格式标志参数,类似于后面解释的`std::regex_replace()`函数。任何`match_xxx`标志都被忽略;只使用`format_yyy`旗。 |
#### 例子
下面的例子说明了`regex_match()`、`regex_search()`和`match_results` ( `smatch`)的用法:

但是枚举所有匹配的首选方法是使用下一小节中讨论的迭代器。
### 匹配迭代器
`std::regex_iterator`和`regex_token_iterator`类有助于遍历目标序列中模式的所有匹配。和`match_results`一样,两者都是用一种字符迭代器(`CharIter`)模板化的。对于最常见的情况,也存在四种类似的`typedef`:前缀为`s`、`ws`、`c`或`wc`的迭代器类型。例如,上一小节末尾示例中的`while`循环可以重写如下:

换句话说,`regex_iterator`是一个前向迭代器,它枚举一个模式的所有`sub_match` es,就像通过重复调用`regex_search()`找到的一样。之前的`for_each()`循环不仅更短更清晰,而且总体上比我们天真的`while`循环更正确:例如,迭代器在第一次迭代后设置`match_prev_avail`标志。只有一个非平凡的构造函数可用,创建一个指向目标序列中给定`Regex`的第一个`sub_match`(如果有的话)的`regex_iterator<CharIter>`,该目标序列由两个双向`CharIters`限定:
regex_iterator(CharIter, CharIter, const Regex&, match_flag_type = 0);
类似于 a `regex_iterator`枚举`match_results`,a `regex_token_iterator`枚举这些`match_results`中包含的所有或特定的`sub_match` es。例如,同一个示例可以写成

`regex_token_iterator`的构造函数类似于`regex_iterator`的构造函数,但是有一个额外的参数来指示要枚举哪些`sub_match`。为单个`int`(如示例中所示)、`vector<int>`、`int[`、`]`和`initializer_list<int>`定义重载。例如,将示例中的`2`替换为`{0,1}`,输出`"<b>Bold</b>"`、`"b"`、`"<b>bold again</b>"`,然后输出`"b"`。省略时,该参数默认为`0`,表示仅枚举完整模式`sub_match`(然后该示例打印`"<b>Bold</b>"`和`"<b>bold again</b>"`)。
regex_token_iterator 的最后一个参数也可以是-1,这将把它变成一个字段拆分器或标记器。这是对来自<cstring>的 C 函数 strtok()的安全替代。在这种模式下,regex_token_iterator 遍历所有不匹配正则表达式模式的子序列。例如,它可以用于将逗号分隔的字符串拆分成不同的字段(或标记)。在这种情况下使用的正则表达式只是“,”。</cstring>
### 替换模式
最终的正则表达式算法`std::regex_replace()`,用另一个模式替换给定模式的所有匹配。签名如下:
String regex_replace(Target, Regex&, Format, match_flag_type = 0);
Out regex_replace(Out, Begin, End, Regex&, Format, match_flag_type = 0);
和以前一样,参数类型被模板化为相同的字符类型`CharT`,至少支持`char`和`wchar_t`。替换的`Format`被表示为一个`(w)string`或者一个空终止的 C 风格字符串。对于目标序列,有两组重载。第一个函数将`Target`表示为一个`(w)string`或 C 风格的字符串,并将结果作为`(w)string`返回。第二个使用双向`Begin`和`End`字符迭代器表示目标,并将结果复制到输出迭代器`Out`。后者的返回值是一个迭代器,指向输出的最后一个字符之后的一个字符。
给定`Regex`的所有匹配被替换为`Format`序列,默认情况下可能包含以下特殊字符序列:
<colgroup><col> <col></colgroup>
| 格式 | 更换 |
| --- | --- |
| 【例】n | 匹配的第 n 个标记子表达式的副本,其中 n > 0 被计为具有反向引用:参见表 6-5 。 |
| `$&` | 整场比赛的拷贝。 |
| `$`` | 前缀的副本,即匹配之前的目标部分。 |
| `$´` | 后缀的一个副本,后缀是匹配之后的目标的一部分。 |
| `$$` | 一个`$`字符(这是唯一需要的转义)。 |
与前面类似,只有当算法没有足够的资源来评估匹配时,才会抛出一个`std::regex_` `error`。
例如,下面的代码打印了`"d*v*w*l*d"`和`"debolded"`:
std::regex vowels("[aeiou]"); std::cout << std::regex_replace("devoweled", vowels, "*") << '\n';
std::regex bolds("(.*?)"); std::string target = "debolded"; std::ostream_iterator out(std::cout); std::regex_replace(out, target.cbegin(), target.cend(), bolds, "$1");
最后一个参数也是一个`std::regex_constants::match_flag_` `type`,对于`regex_replace()`,它可以用来调整正则表达式的匹配行为——使用前面列出的相同的`match_xxx`值——以及替换的格式。对于后者,支持以下值:
<colgroup><col> <col></colgroup>
| 格式标志 | 影响 |
| --- | --- |
| `format_default` | 使用默认格式(该常量的值为零)。 |
| `format_sed` | 对`Format`使用与 POSIX 实用程序`sed`相同的语法。 |
| `format_no_copy` | `Target`序列中与正则表达式模式不匹配的部分不会被复制到输出中。 |
| `format_first_only` | 只有第一次出现的模式会被替换。 |
Footnotes 1
这些类还有两个可选的模板参数:一个指定要无错误输出的最大代码点的数字,一个带有可能值`little_endian`(输出编码)和`consume_header` / `generate_header`(读/写初始 BOM 头以确定字符顺序)的`codecvt_mode`位掩码值(默认为`0`)。
2
此示例在 Visual Studio 2015 中不起作用。将`char32_t`替换为`__int32`,将`u32string`替换为`basic_string<__int32>`后编译,但结果是错误的。
3
几乎所有的函数:为了性能,`ctype<char>`专门化的`is()`、`scan_is()`和`scan_not()`不调用虚函数,而是在`mask*`数组中执行查找(`ctype::classic_table()`用于`"C"`地区)。可以通过将自定义查找数组传递给方面的构造函数来创建自定义实例。
4
行结束符是以下四种字符之一:换行符(`\n`)、回车符(`\r`)、行分隔符(`\u2028`)或段落分隔符(`\u2029`)。
5
单词字符是`[[:w:]]`或`[_[:alnum:]]`类中的任何字符:即下划线或任何字母或数字字符。