• 【C++】《C++ Primer 》第十七章


    第十七章 标准库特殊设施

    一、tuple类型

    • tuple类似pair的模板,每个pair的成员类型都不相同,但每个pair都恰好有两个成员。
    • 不同的tuple类型的成员类型也不相同,一个tuple可以有任意数量的成员。
    • 每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同。
    • 可以将tuple看做一个"快速而随意"的数据结构。
    • tuple支持的操作
    操作 解释
    tuple<T1, T2, ..., Tn> t; t是一个tuple,成员数为n,第i个成员的类型是Ti所有成员都进行值初始化。
    tuple<T1, T2, ..., Tn> t(v1, v2, ..., vn); 每个成员用对应的初始值vi进行初始化。此构造函数是explicit的。
    make_tuple(v1, v2, ..., vn) 返回一个用给定初始值初始化的tuple。tuple的类型从初始值的类型推断。
    t1 == t2 当两个tuple具有相同数量的成员且成员对应相等时,两个tuple相等。
    t1 relop t2 tuple的关系运算使用字典序。两个tuple必须具有相同数量的成员。
    get<i>(t) 返回t的第i个数据成员的引用:如果t是一个左值,结果是一个左值引用;否则,结果是一个右值引用。tuple的所有成员都是public的。
    tuple_size<tupleType>::value 一个类模板,可以通过一个tuple类型来初始化。它有一个名为value的public constexpr static数据成员,类型为size_t,表示给定tuple类型中成员的数量。
    tuple_element<i, tupleType>::type 一个类模板,可以通过一个整型常量和一个tuple类型来初始化。它有一个名为type的public成员,表示给定tuple类型中指定成员的类型。

    1. 定义和初始化tuple

    // 定义和初始化示例
    tuple<size_t, size_t, size_t> threeD;   // 三个成员都设置为0
    tuple<string, vector<double>, int, list<int>> someVal("constants", {3.14, 2.7}, 42, {0, 1, 2});
    
    // 由于tuple的每个构造函数都是explicit的,所以必须使用直接初始化语法
    tuple<size_t, size_t, size_t> threddD = {1, 2, 3};  // 错误
    tuple<size_t, size_t, size_t> threddD{1, 2, 3}; // 正确
    
    auto item = make_tuple("TTT", 3, 342.7);
    
    // 访问tuple成员
    auto book  = get<0>(item);  // 返回item的第一个成员
    auto cnt = get<1>(item);    // 返回item的第二个成员
    auto price = get<2>(item) / cnt;    // 返回item的最后一个成员
    get<2>(item) *= 0.8;
    

    2. 使用tuple返回多个值

    • tuple最常见的用途就是从一个函数返回多个值

    二、bitset类型

    • 处理二进制位的有序集;
    • bitset类也是一个类模板,它类似array类,具有固定的大小。定义一个bitset时,需要声明它包含多少个二进制位。
    bitset<32> bitVect(1U); // 32位,低位为1,其他位为0
    
    • 初始化bitset的方法
    操作 解释
    bitset<n> b; b有n位;每一位均是0.此构造函数是一个constexpr。
    bitset<n> b(u); b是unsigned long long值u的低n位的拷贝。如果n大于unsigned long long的大小,则b中超出unsigned long long的高位被置为0。此构造函数是一个constexpr。
    bitset<n> b(s, pos, m, zero, one); b是string s从位置pos开始m个字符的拷贝。s只能包含字符zero或one:如果s包含任何其他字符,构造函数会抛出invalid_argument异常。字符在b中分别保存为zero和one。pos默认为0,m默认为string::npos,zero默认为'0',one默认为'1'。
    bitset<n> b(cp, pos, m, zero, one); 和上一个构造函数相同,但从cp指向的字符数组中拷贝字符。如果未提供m,则cp必须指向一个C风格字符串。如果提供了m,则从cp开始必须至少有m个zero或one字符。
    • 初始化示例
    // bitVec1比初始值小;初始值中的高位被丢弃
    bitset<13> bitVec1(0xbeef);     // 二进制位序列为 1111011101111
    
    // bitVec2比初始值大;它的高位被置为0
    bitset<20> bitVec2(oxbeef); // 二进制位序列为 00001101111011101111
    
    // 在64位机器中,long long 0ULL是64个0比特,因此~0ULL是64个1
    bitset<128> bitVec3(~0ULL); // 0-63位为1;63-127位为0
    
    // 从一个string初始化bitset
    bitset<32> bitVec4("1100"); //2、3两位为1,剩余两位为0
    
    • 注意:string的下标编号习惯与bitset恰好相反,string下标最大的字符用来初始化bitset中的低位。
    • bitset操作
    操作 解释
    b.any() b中是否存在1
    b.all() b中都是1
    b.none() b中是否没有1
    b.count() b中1的个数
    b.size() b的位数
    b.test(pos) pos下标是否是1
    b.set(pos) pos置1
    b.set() 所有都置1
    b.reset(pos) 将位置pos处的位复位
    b.reset() 将b中所有位复位
    b.flip(pos) 将位置pos处的位取反
    b.flip() 将b中所有位取反
    b[pos] 访问b中位置pos处的位;如果b是const的,则当该位置位时,返回true;否则返回false
    b.to_ulong() 返回一个unsigned long值,其位模式和b相同。如果b中位模式不能放入指定的结果类型,则抛出一个overflow_error异常
    b.to_ullong() 类似上面,返回一个unsigned long long值
    b.to_string(zero, one) 返回一个string,表示b中位模式。zero和one默认为0和1
    os << b 将b中二进制位打印为字符1或0,打印到流os
    is >> b 从is读取字符存入b。当下一个字符不是1或0时,或是已经读入b.size()个位时,读取过程停止
    • bitset操作示例
    bitset<32> bitVec(1U);  // 32位,低位为1,剩余位为0
    bool is_set = bitVec.any(); // true
    bool is_not_set = bitVec.none();   // false
    bool all_set = bitVec.all();   // false
    size_t onBits = bitVec.count();    // 1
    size_t sz = bitVec.size();  // 32
    bitVec.filp();  // 翻转bitVec中的所有位
    bitVec.reset(); // 将所有位复位
    bitVec.set();   // 将所有位置为1
    

    三、正则表达式

    • 正则表达式是一种描述字符序列的方法,是一种极其强大的计算工具。
    • 作为新标准的一部分,RE库定义在头文件regex中。
    • 正则表达式库组件
    组件 解释
    regex 表示一个正则表达式的类
    regex_match 将一个字符序列与一个正则表达式匹配
    regex_search 寻找第一个与正则表达式匹配的子序列
    regex_replace 使用给定格式替换一个正则表达式
    sregex_iterator 迭代器适配器,调用regex_searcg来遍历一个string中所有匹配的子串
    smatch 容器类,保存在string中搜索的结果
    ssub_match string中匹配的子表达式的结果
    • regex_search和regex_match的参数
    操作 解释
    注意:这些操作返回bool值,指出是否找到匹配
    (seq, m, r, mft)(seq, r, mft) 在字符序列seq中查找regex对象r中的正则表达式。
    seq可以是一个string、标识范围的一对迭代器、一个指向空字符结尾的字符数组的指针。
    m是一个match对象,用来保存匹配结果的相关细节。mseq必须具有兼容的类型。
    mft是一个可选的regex_constants::match_flag_type值。

    1. 使用正则表达式库

    • 简单的例子:查找违反众所周知的拼写规则"i除非在c之后,否则必须在e之前"的单词:
    // 查找不在字符c之后的字符串ei
    string pattern("[^c]ei");
    
    // 需要包含pattern的整个单词
    pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
    regex r(pattern);   // 构造一个用于查找模式的regex
    smatch results; // 定义一个对象保存搜索结果
    
    // 定义一个string保存与模式匹配和不匹配的文本
    string test_str = "receipt freind theif receive";
    
    // 用r在test_str中查找与pattern匹配的子串
    if (regex_search(test_str, results, r) {    // 如果有匹配子串
        cout << results.str() << endl;  // 打印匹配的单词,输出freind
    }
    
    • regex(和wregex)选项
    操作 解释
    regex r(re) regex r(re, f) re表示一个正则表达式,它可以是一个string、一对表示字符范围的迭代器、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器、一个花括号包围的字符列表。f是指出对象如何处理的标志。f通过下面列出来的值来设置。如果未指定f,其默认值为ECMAScript
    r1 = re r1中的正则表达式替换rere表示一个正则表达式,它可以是另一个regex对象、一个string、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表。
    r1.assign(re, f) 和使用赋值运算符(=)的效果相同:可选的标志f也和regex的构造函数中对应的参数含义相同。
    r.mark_count() r中子表达式的数目。
    r.flags() 返回r的标志集。
    • 定义regex时指定的标志(f)
    操作 解释
    icase 在匹配过程中忽略大小写
    nosubs 不保存匹配的子表达式
    optimize 执行速度优先于构造速度
    ECMAScript 使用ECMA-262指定的语法
    basic 使用POSIX基本的正则表达式语法
    extended 使用POSIX扩展的正则表达式语法
    awk 使用POSIX版本的awk语言的语法
    grep 使用POSIX版本的grep的语法
    egrep 使用POSIX版本的egrep的语法
    • 可以将正则表达式本身看做是一种简单程序语言设计的程序。在运行时,当一个regex对象被初始化或被赋予新模式时,才被“编译”。
    • 如果编写的正则表达式存在错误,会在运行时抛出一个regex_error的异常。
    • 避免创建不必要的正则表达式。构建一个regex对象可能比较耗时。

    2. 匹配与Regex迭代器类型

    • sregex_iterator操作(用来获得所有匹配)
    操作 解释
    sregex_iterator it(b, e, r); 一个sregex_iterator,遍历迭代器be表示的string。它调用sregex_search(b, e, r)it定位到输入中第一个匹配的位置。
    sregex_iterator end; sregex_iterator的尾后迭代器。
    *it, it-> 根据最后一个调用regex_search的结果,返回一个smatch对象的引用或一个指向smatch对象的指针。
    ++it , it++ 从输入序列当前匹配位置开始调用regex_search。前置版本返回递增后迭代器;后置版本返回旧值。
    it1 == it2 如果两个sregex_iterator都是尾后迭代器,则它们相等。两个非尾后迭代器是从相同的输入序列和regex对象构造,则它们相等。
    // 查找不在字符c之后的字符串ei
    string pattern("[^c]ei");
    
    // 需要包含pattern的整个单词
    pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
    regex r(pattern, regex::icase);   // 构造一个用于查找模式的regex,忽略大小写
    
    // 它将反复调用regex_search来寻找文件中的所有匹配
    if (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it) {    // 如果有匹配子串
        cout << it->str() << endl;  // 打印匹配的单词,输出freind theif
    }
    
    • smatch操作
    操作 解释
    m.ready() 如果已经通过调用regex_searchregex_match设置了m,则返回true;否则返回false。如果ready返回false,则对m进行操作是未定义的
    m.size() 如果匹配失败,则返回0;否则返回最近一次匹配的正则表达式中子表达式的数目
    m.empty() 等价于m.size() == 0
    m.prefix() 一个ssub_match对象,标识当前匹配之前的序列
    m.suffix() 一个ssub_match对象,标识当前匹配之后的部分
    m.format(...)
    m.length(n) n个匹配的子表达式的大小
    m.position(n) n个子表达式距离序列开始的长度
    m.str(n) n个子表达式匹配的string
    m[n] 对应第n个子表达式的ssub_match对象
    m.begin(), m.end() 表示mssub_match元素范围的迭代器
    m.cbegin(), m.cend() 常量迭代器

    3. 使用子表达式

    • 正则表达式语法通常用括号表示子表达式
    • 子表达式的索引从1开始
    • fmt中用$后跟子表达式的索引号来标识一个特定的子表达式
    • 例子
    if(regex_search(filename, results, r))
        cout << results.str(1) << endl; // .str(1)获取一个子表达式匹配结果
    
    • 子匹配操作
    操作 解释
    matched 一个public bool数据成员,指出ssub_match是否匹配了
    first, second public数据成员,指向匹配序列首元素和尾后位置的迭代器。如果未匹配,则firstsecond是相等的
    length() 匹配的大小,如果matchedfalse,则返回0
    str() 返回一个包含输入中匹配部分的string。如果matchedfalse,则返回空string
    s = ssub ssub_match对象ssub转化为string对象s等价s=ssub.str(),转换运算符不是explicit

    4. 使用regex_replace

    • 使用正则表达式替换操作:
    操作 解释
    m.format(dest, fmt, mft), m.format(fmt, mft) 使用格式字符串fmt生成格式化输出,匹配在m中,可选的match_flag_type标志在mft中。第一个版本写入迭代器dest指向的目的为止,并接受fmt参数,可以是一个string,也可以是一个指向空字符结尾的字符数组的指针。mft的默认值是format_default
    regex_replace(dest, seq, r, fmt, mft)regex_replace(seq, r, fmt, mft) 遍历seq,用regex_search查找与regex对象r相匹配的子串,使用格式字符串fmt和可选的match_flag_type标志来生成输出。mft的默认值是match_default
    • 例子:
    string phone = "(\()?(\d{3})(\))?([-. ])?(\d{3})([-. ]?)(\d{4})"
    string fmt = "$2.$5.$7";  // 将号码格式改为ddd.ddd.dddd
    regex r(phone);  // 用来寻找模式的regex对象
    string number = "(908) 555-1800";
    cout << regex_replace(number, r, fmt) << endl;  // 908.555.1800
    
    
    • 匹配标志:
    操作 解释
    match_default 等价于format_default
    match_not_bol 不将首字符作为行首处理
    match_not_eol 不将尾字符作为行尾处理
    match_not_bow 不将首字符作为单词首处理
    match_not_eow 不将尾字符作为单词尾处理
    match_any 如果存在多于一个匹配,则可以返回任意一个匹配
    match_not_null 不匹配任何空序列
    match_continuous 匹配必须从输入的首字符开始
    match_prev_avail 输入序列包含第一个匹配之前的内容
    format_default ECMAScript规则替换字符串
    format_sed POSIX sed规则替换字符串
    format_no_copy 不输出输入序列中未匹配的部分
    format_first_only 只替换子表达式的第一次出现

    四、随机数

    • 新标准之前,C和C++都依赖一个简单的C库函数rand来生成随机数,且只符合均匀分布。
    • 新标准:随机数引擎随机数分布类,定义在 random 头文件中。
    • C++程序应该使用default_random_engine类和恰当的分布类对象。

    1. 随机数引擎和分布

    • 随机数引擎操作:
    操作 解释
    Engine e 默认构造函数;使用该引擎类型默认的种子
    Engine e(s) 使用整型值s作为种子
    e.seed(s) 使用种子s重置引擎的状态
    e.min(),e.max() 此引擎可生成的最小值和最大值
    Engine::result_type 此引擎生成的unsigned整型类型
    e.discard(u) 将引擎推进u步;u的类型为unsigned long long
    • 例子:
    // 初始化分布类型
    uniform_int_distribution<unsigned> u(0, 9);
    // 初始化引擎
    default_random_engine e;
    // 随机生成0-9的无符号整数
    cout << u(e) << endl;
    
    • 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static的,否则,每次调用函数都会生成相同的序列。
    • 如果程序作为一个自动过程的一部分反复运行,将time的返回在作为种子的方式就无效了;它可能多次使用的都是相同的种子。

    2. 其他随机数分布

    • 分布类型的操作:
    操作 解释
    Dist d 默认构造函数;使d准备好被使用。其他构造函数依赖于Dist的类型;分布类型的构造函数是explicit的。
    d(e) 用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列;e是一个随机数引擎对象。
    d.min(),d.max() 返回d(e)能生成的最小值和最大值。
    d.reset() 重建d的状态,是的随后对d的使用不依赖于d已经生成的值。
    • 由于引擎返回相同的随机数序列,所以我们必须在循环外声明引擎对象。否则每步循环都会创建一个新引擎,从而每步循环都会生成相同的值。类似的,分布对象也要保持状态,因此也应该在循环外定义。

    五、IO库再探

    1. 格式化输入输出

    • 使用操纵符改变格式状态
    • 控制布尔值的格式:cout << boolalpha << true << endl;
    • 指定整型的进制:cout << dec << 20 << endl;
    • 定义在iostream中的操纵符: 其中 * 代表默认的流状态
    操纵符 解释
    boolalpha truefalse输出为字符串
    * noboolalpha truefalse输出为1,0
    showbase 对整型值输出表示进制的前缀
    * noshowbase 不生成表示进制的前缀
    showpoint 对浮点值总是显示小数点
    * noshowpoint 只有当浮点值包含小数部分时才显示小数点
    showpos 对非负数显示+
    * noshowpos 对非负数不显示+
    uppercase 在十六进制中打印0X,在科学计数法中打印E
    * nouppercase 在十六进制中打印0x,在科学计数法中打印e
    * dec 整型值显示为十进制
    hex 整型值显示为十六进制
    oct 整型值显示为八进制
    left 在值的右侧添加填充字符
    right 在值的左侧添加填充字符
    internal 在符号和值之间添加填充字符
    fixed 浮点值显示为定点十进制
    scientific 浮点值显示为科学计数法
    hexfloat 浮点值显示为十六进制(C++11)
    defaultfloat 充值浮点数格式为十进制(C++11)
    unitbuf 每次输出操作后都刷新缓冲区
    * nounitbuf 恢复正常的缓冲区刷新方式
    * skipws 输入运算符跳过空白符
    noskipws 输入运算符不跳过空白符
    flush 刷新ostream缓冲区
    ends 插入空字符,然后刷新ostream缓冲区
    endl 插入换行,然后刷新ostream缓冲区

    2. 未格式化的输入/输出操作

    • 单字节底层IO操作:
    操作 解释
    is.get(ch) istream is读取下一个字节存入字符ch中。返回is
    os.put(ch) 将字符ch输出到ostream os。返回os
    is.get() is的下一个字节作为int返回。
    is.putback(ch) 将字符ch放回is。返回is
    is.unget() is向后移动一个字节。返回is
    is.peek() 将下一个字节作为int返回,但不从流中删除它。
    • 多字节底层IO操作:
    操作 解释
    is.get(sink, size, delim) is中读取最多size个字节,并保存在字符数组中,字符数组的起始地址由sink给出。读取过程直到遇到字符delim或读取了size个字节或遇到文件尾时停止。如果遇到了delim,则将其留在输入流中,不读取出来存入sink
    is.getline(sink, size, delim) 与接收三个参数的get版本类似,但会读取并丢弃delim
    is.read(sink, size) 读取最多size个字节,存入字符数组sink中。返回is
    is.gcount() 返回上一个未格式化读取从is读取的字节数。
    os.write(source, size) 将字符数组source中的size个字节写入os。返回os
    is.ignore(size, delim) 读取并忽略最多size个字符,包括delim。与其他未格式化函数不同,ignore有默认参数:size默认值是1,delim的默认值为文件尾。

    3. 流随机访问

    • istreamostream类型通常不支持随机访问。
    • 通过将标记seek到一个给定位置来重定位它。
    • tell告诉我们标记的当前位置。
    操作 解释
    tellg(),tellp 返回一个输入流中(tellg)或输出流中(tellp)标记的当前位置。
    seekg(pos)seekp(pos) 在一个输入流或输出流中将标记重定位到给定的绝对地址。pos通常是一个当前teelgtellp返回的值。
    seekp(off, from)seekg(off, from) 在一个输入流或输出流中将标记定位到from之前或之后off个字符,from可以是下列值之一:beg,偏移量相对于流开始位置;cur,偏移量相对于流当前位置;end,偏移量相对于流结尾位置。
  • 相关阅读:
    Python程序执行时的不同电脑路径不同问题
    Python写的计算器程序(主要目的在于熟悉下正则表达式)
    占位符
    selenium自动化测试浏览器驱动安装(属于转载文章)
    python的pip升级问题
    索引
    视图
    事务
    引擎
    约束
  • 原文地址:https://www.cnblogs.com/parzulpan/p/13470729.html
Copyright © 2020-2023  润新知