zoukankan      html  css  js  c++  java
  • C++ Primer学习笔记


    本章介绍4个标准库设施:tuple, bitset, 随机数生成, 正则表达式。还有IO库具有一些特殊目的的部分。

    17.1 tuple类型

    头文件:tuple
    tuple 元组,类似于pair的模板。联系是pair的成员类型都可以不一样,区别是pair恰好有2个成员,而tuple可以有任意数量成员( >= 0)。

    17.1.1 定义和初始化tuple

    需要指出每个tuple类型,可以用tuple默认构造函数,但无法使用拷贝构造函数。

    // 构造tuple
    tuple<size_t, size_t, size_t> threeD; // 3个成员都进行值初始化,设置为0
    tuple<string, vector<double>, int, list<int>> someVal("constants", {3.14, 2.718}, 42, {0,1,2,3,4,5}); // 调用tuple的构造函数,对成员进行直接初始化
        
    // 由于tuple的构造函数的explicit的,因此必须使用直接初始化语法,而不能进行拷贝赋值
    tuple<size_t, size_t, size_t> threeD = {1,2,3}; // 错误
    tuple<size_t, size_t, size_t> threeD{1,2,3};    // 正确
    
    // make_tuple构造tuple
    auto item = make_tuple("12345678", 3, 20.00);
    

    17.1.2 访问tuple成员

    pair是用first, second进行访问。而tuple由于成员数不是固定的,可以利用标准库函数模板get进行访问。

    // 读
    auto t = get<0>(threeD); // 返回threeD的第1个成员
    auto val_4 = get<2>(someVal); // 返回someVal的第3个成员
    // 写
    get<2>(item) *= 0.8; // item的第3个成员 * 0.8
    

    问题:如何获取一个实例化的tuple准确的类型细节信息?
    使用2个辅助类模板tupe_size::value和tuple_element::type,来查询tuple成员的数量和类型:

    typedef decltype(item) trans; // trans是item的类型
    // 返回trans的类型对象中成员的数量
    size_t sz = tuple_size<trans>::value; // 数量是3
    
    // cnt与item第二个成员类型相同
    tuple_element<1, trans>::type cnt = get<1>(item); // cnt 是一个int
    <=> auto cnt = get<1>(item);
    

    关系和相等运算符
    成员类型、个数完全相同的两个tuple对象,才能进行比较;否则,就是错误行为。

    注意:tuple定义了< 和 == 运算符,可以将tuple序列传递给算法,而且可以在无序容器中将tuple作为关键字类型。

    tuple<string, string> duo("1", "2");
    tuple<size_t, size_t> twoD(1,2);
    
    bool b = (duo == twoD); // 错误,不能比较string和size_t -- 成员类型不同
    
    tuple<size_t, size_t, size_t> threeD(1,2,3); 
    b = (twoD < threeD); // 错误,成员数量不同
    
    tuple<size_t, size_t> origin(0, 0);
    b = (origin < twoD); // 正确:b为true
    

    17.1.2 使用tuple返回多个值

    tuple有什么用途?
    一个很重要的用途就是作为函数返回值,一次返回多个值。我们知道,函数是无法返回一个数组的,pair和tuple就成了一次返回多个类型不同的值的很好的方式。

    17.2 bitset类型

    头文件:bitset
    bitset类专门用于处理二进制位集合,还能处理超过最长整型类型大小的位集合(超过64bit)。

    17.2.1 定义和初始化bitset

    bitset类是一个类模板,类似于array类,有固定大小。定义一个bitset时,通过模板参数指明它包含多少个二进制位。

    bitset<32> bitvec(1u); // 32bit; 对应二进制数:0b 00..0 1 (0b表示是二进制数,数的前面31个0,最后一个1)
    

    bitset的初始化方法

    bitset<n> b; // b有n位,每一位均为0。该构造函数是一个constexpr
    bitset<n> b(u);// b是unsigned long long值u的低n位的拷贝
    bitset<n> b(s, pos, m, zero, one); // b是string s从位置pos开始m个字符的拷贝。s只能包含字符zero或one;如果s包含任何其他字符,构造函数抛出invalid_argument异常。
                                       // 字符在b中分别保存为zeor和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字符
    

    用unsigned值初始化bitset
    用unsigned值初始化bitset,值会转化unsigned long long类型,并被当做位模式来处理。bitset中二进制位将是此模式的一个副本。

    bitset值初始化,自动转换原则:bitset使用最少bit位,尽量确保信息不丢失;超过64bit的部分的高位会清0

    示例

    // 0xbeef = 0b 1011 1110 1110 1111 (实际占用2byte, 共16bit)
    bitset<13> bitvec1(0xbeef); // 值超过存储位数的高位部分截断
    bitset<20> bitvec2(0xbeef); // 超过值大小的位填充0
    bitset<32> bitvec3(0xbeef); // 超过值大小的位填充0
    bitset<128> bitvec4(~0ull); // 超过64bit的高位清0
    
    cout << bitvec1 << endl; // 1111011101111
    cout << bitvec2 << endl; // 00001011111011101111
    cout << bitvec3 << endl; // 00000000000000001011111011101111
    
    cout << bitvec4 << endl; // 00000000000000000000000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111111111111111,32个0,32个1
    

    从一个string初始化bitset
    二进制string或C字符串,初始化bitset 。
    注意下标:string对应字符串,就是bitset二进制形式。

    // 用整个字符串
    bitset<32> bitvec5 ("1100");  // bitvec5 = 0b0..0 1100 (28个前导0) = 0xC
    cout << bitvec5 << endl; 
    
    string str("1111111000000011001101");
    
    bitset<32> bitvec6(str, 5, 4);  // 用从str[5]开始的4个字符,作为二进制位,1100
    bitset<32> bitvec7(str, str.size()-4); // 用从str[str.size()-4]开始的4个字符,也就是最后4个字符,作为二进制位,1101
    

    17.2.2 bitset操作

    bitset类支持位运算符,含义等同作用于unsigned。另外,bitset操作定义多种检测和设置1个或多个二进制位的方法。

    // 位检测操作
    b.any();     // b中是否存在置位的二进制位
    b.all();     // b中所有位都置位了吗?
    b.none();    // b中不存在置位的二进制位吗?
    b.count();   // b中置位的位数
    b.size();    // 一个constexpr函数,返回b中的(所有)位数
    b.test(pos); // 若pos位置的位是置位的,则返回true;否则,返回false
    
    // 置位、清除操作
    b.set(pos, v);   // 将pos处的位设置为v。v默认true
    b.set();         // 将b所有位置位
    
    b.clear(pos);    // 清除pos处的位
    b.clear();       // 清除b所有位
    
    b.flip(pos);     // 改变位置pos处的位状态,或改变b中每一位的状态
    b.flip();        // 求b所有位的反
    
    // 访问位
    b[pos];          // <=> b.test(pos)
    
    // 转换
    b.to_ulong();    // 返回一个unsigned long,如果b中位模式不能放入指定结果类型,抛出overflow_error异常
    b.to_ullong();   // 返回一个unsigned long long值,如果b中位模式不能放入指定结果类型,抛出overflow_error异常
    
    b.to_string(zero, one); // 返回b对应的二进制字符串,zero、one默认为0和1
    
    // 输入输出
    os << b;         // 将b中二进制打印为字符1或0,打印到流os
    is >> b;         // 从is读取字符存入b。当下一个字符不是1或0时,或者已经读入b.size()个位数时,读取停
    

    示例

    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,总共只有1个bit位置位
    size_t sz = bitvec.size();        // 返回32,总共的位数
    bitvec.flip();                    // 翻转bitvec中的所有位
    bitvec.reset();                   // 清除所有位
    bitvec.set();                     // 将所有位置位
    
    // bitset单个位访问及操作
    bitvec[0] = 0;                    // 复位第一位
    bitvec[31] = bitvec[0];           // 将最后一位设置为与第一位一样
    bitvec[0].flip();                 // 翻转第一位
    ~bitvec[0];                       // 取出第一位,然后翻转(注意不会影响原bitset对象)
    bool b = bitvec[0];               // 将bitvec[0]转换为bool类型
    
    // bitset提取值
    unsigned long ulong = bitvec3.to_ulong();
    cout << "ulong = " << ulong << endl;
    
    // bitset的IO运算
    bitset<16> bits;
    cin >> bins;
    cout << "bits: " << bits << endl; // 打印刚刚读取的内容
    

    17.3 正则表达式

    17.4 随机数

    新标准出现前,C/C++依赖C库函数rand生成随机数,生成均匀分布的伪随机整数,范围:0~和系统相关最大值(>=32767)。
    rand的问题:有许多程序需要不同范围的随机数,有些应用需要随机浮点数,有些需要非均匀分布的数。为解决这些问题而转换rand生成的随机数的范围、类型、分布时,常常会引入非随机性。

    通过一组协作的类来解决这些问题:随机数引擎类(random-number engines)和随机数分布类(random-number distribution)

    注意:C++不应该使用rand,而应该使用default_random_engine类和恰当的分别类对象。

    17.4.1 随机数引擎和分布

    头文件:random

    随机数引擎是函数对象类,定义了一个调用运算符。运算符不接受参数,返回一个随机unsigned整数。
    标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器指定其中一个作为default_random_engine类型。
    随机数引擎操作

    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
    

    使用示例

    default_random_enginee e; // 随机数引擎对象
    
    // 读取此引擎可生成的最小值和最大值
    auto d1 = e.min();
    auto d2 = e.max();
    
    cout << "min = " << hex << d1; 
    cout << ", max = " << hex << d2 << endl;
    
    // 生成10个随机数
    for (size_t i = 0; i < 10; ++i) {
      // 通过e()调用对象来生成下一个随机数
      count << e() << " ";
    }
    

    打印结果

    min = 1, max = 7ffffffe
    41a7 10d63af1 60b7acd9 3ab50c2a 4431b782 1c06dac8 6058ed8 56e509fe 56f32f43 77a4044d 
    

    存在问题:可以看到,随机数引擎的输出值范围很大,是不能直接使用的。

    分布类型和引擎
    使用分布类型对象解决不能得到指定范围的数的问题。

    注意:随机数发生器,通常指分布对象和引擎对象的组合。

    uniform_int_distribution<unsigned> u(0, 9); // 生成[0, 9]均匀分布的随机数
    default_random_engine e;
    
    for (size_t i = 0; i < 10; ++i) {
        // 将u作为随机数源
        // 每个调用返回在指定范围内并服从均匀分布的值
        cout << u(e) << " "; // 注意传递给分布对象的是引擎对象本身,如果传递e(),会出现编译错误
    }
    cout << endl;
    

    运行结果:

    0 1 7 4 5 2 0 6 6 9 
    

    比较随机数引擎和rand函数
    default_random_engine对象输出类似random输出,不过随机数引擎生成的unsigned在一个系统定义的范围内,而rand生成的数字0~RAND_MAX之间。

    引擎生成一个数值序列
    一个给定的随机数发生器一直会发生相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static的。否则,每次调用函数都会生成相同的序列。

    示例:

    // 这是一个错误的随机数序列生成方法
    // 每次调用该函数都会生成相同的100个数
    vector<unsigned> bad_randVec()
    {
      default_random_engin e;
      uniform_int_distribution<unsigned> u(0, 9);
      vector<unsigned> ret;
    
      for (size_t i = 0; i < 100; ++i)
        ret.push_back(u(e));
      return ret;
    }
    
    
    vector<unsigned> v1(bad_randVec());
    vector<unsgined> v2(bad_randVec());
    
    cout << ((v1 == v2) ? "equal" : "not equal") << endl; // 打印"equal"
    
    
    // 正确的随机数序列生成方法
    vector<unsigned> good_randVec()
    {
      static default_random_engine e; // 注意这里的static
      static uniform_int_distribution<unsigned> u(0, 9); // 注意这里的static
      
      vector<unsigned> ret;
      for (size_t i = 0; i < 100; ++i)
        ret.push_back(u(e));
      return ret;
    }
    

    设置随机数发生种子
    随机数发生器如果使用相同(默认或指定)的种子,会生成相同的随机数序列。可以通过指定不同的种子,产生不同的随机数序列。

    default_random_engine e1; // 使用默认种子
    default_random_engine_e2(2147483646); // 使用指定种子
    
    default_random_engine = e3; // 使用默认种子
    e3.seed(32767); // 设置新种子
    default_random_engine e4(32767); // 使用指定种子,与e3新设置种子一样
    
    for (size_t i = 0; i != 100; ++i) {
      // e1 e2生成相同随机数序列,因为使用的种子相同
      if (e1() == e2()) 
        cout << "unseed match at iterator: " << i << endl;
      // e3 e4生成不同的随机数序列,因为使用的种子不同
      if (e3() == e4())
        cout << "seeded differs at iterator: " << i << endl;
    }
    

    17.4.2 其他随机数分布

    随机数引擎(default_random_engine)生成的unsigned数,范围内的每个数被生成的概率都是相同的。而APP常需要不同类型、不同分布的随机数。标准库通过定义不同随机数分布对象来满足这两方面的要求。

    生成随机数实数
    生成[0, 1]的随机浮点数,可以使用uniform_real_distribution类型对象,用法与uniform_int_distribution类似。

    default_random_engine e; // 生成无符号随机整数
    
    // 0~1(包含)的均匀分布
    uniform_real_distribution<double> u(0, 1);
    for (size_t i = 0; i < 10; ++i) 
      cout << u(e) << " ";
    

    使用分布的默认结果类型
    每个分布模板都有一个默认的模板实参。

    // 空<>表示希望使用默认结果类型
    uniform_real_distribution<> u(0,1); // 默认生成double值
    
    uniform_int_distribution<> u(0,100); // 默认生成int值
    

    生成非均匀分布的随机数
    normal_distribution 正态分布

    default_random_engine e;
    normal_distribution<> n(4, 1.5);
    
    vector<unsigned> vals(9); // 9个元素均为0
    for (size_t i = 0; i != 200; ++i) {
      unsigned v = lround(n(e)); // 生成的随机数,舍入到最接近的整数
    
      // 统计每个在范围内的数,出现的次数
      if (v < vals.size()) 
        ++vals[v];
    }
    
    for (size_t j = 0; j != vals.size(); ++j)
      cout << j << ": " << string(vals[j], '*') << endl;
    

    bernouli_distribution类
    bernouli_distribution是一个普通类,不接受模板参数。此分布总返回一个bool值,返回true概率是一个常数,默认0.5

    由于引擎返回相同的随机数序列,所以必须在循环外声明引擎对象。否则,每步循环都会创建一个新引擎,从而每步都会生成相同的值。分布对象也是类似,需要保持状态。

    string resp;
    default_random_engine e; // e应保持状态,所有必须在循环外定义 
    bernouli_distribution b; // 默认是50/50的机会
    
    do {
      bool first = b(e);  // 如果为true,则程序先行
      cout << (first ? "We go first" : "You get to go first") << endl; 
      
      // 传递谁先行的指示,进行游戏
      cout << ((play(first) ? "sorry, you lost" : "congrats, you won") << endl;
      cout << "play gain? Enter 'yes' or 'no'" << endl;
    } while(cin >> resp && resp[0] == 'y);
    

    17.5 IO库再探

    格式化字符串同C,略

    控制bool类型及整型打印格式

    // 控制bool格式
    cout << true << endl;               // 输出1
    cout << boolalpha << true << endl;  // 输出true
    
    // 指定整型的进制,注意没有二进制控制方式。要输出二进制串,可以用bitset
    cout << "default: " << 20 << " " << 1024 << endl;         // default: 20 1024
    cout << "octal: " << oct << 20 << " " << 1024 << endl;    // octal: 24 2000
    cout << "hex: " << hex << 20 << " " << 1024 << endl;      // hex: 14 400
    cout << "demical: " << dec << 20 << " " << 1024 << endl;  // demical: 20 1024
    
    cout << showbase;   // 当打印整型值时,显式进制(前缀)
    cout << "default: " << 20 << " " << 1024 << endl;            // default: 20 1024
    cout << "in octal: " << oct << 20 << " " << 1024 << endl;    // in octal: 024 02000
    cout << "in hex: " << hex << 20 << " " << 1024 << endl;      // in hex: 0x14 0x400
    cout << "in demical: " << dec << 20 << " " << 1024 << endl;  // in demical: 20 1024
    cout << noshowbase; // 恢复流状态
    
    // 进制前缀字母打印大写
    cout << uppercase << showbase << hex
    << "printed in hexadecimal: " << 20 << " " << 1024           // printed in hexadecimal: 0X14 0X400
    << nouppercase << noshowbase << dec << endl;
    

    控制浮点数格式
    可以控制浮点数输出三种格式:

    1. 以多高精度(多少个数字)打印浮点数;
    2. 数值是打印为十六进制、定点十进制还是科学计数法形式;
    3. 对于没有小数部分的浮点值,是否打印小数点;

    默认情况下,浮点数按6位数字精度打印;如过没有小数,则不打印小数点;根据浮点数的值选择打印成定点十进制或科学记数法形式。标准库会选择一种可读性更好的格式:非常大和非常小的值,打印位科学计数法,其他值打印为定点十进制形式。

    这里只讲指定打印精度的方法,其他略。

    指定打印精度
    通过IO对象的precision成员,或使用setprecision来改变精度。
    precision成员是重载的,一个版本接受一个int值,用于设置精度值,并返回旧的精度值。另一个版本是不接受参数,返回当前精度值。

    注意:所谓精度,指的是有效数字的个数。

    头文件:iomanip

    示例

    // count.precision 返回当前精度值
    cout << "Precision: " << cout.precision()
         << ", Value: " << sqrt(2.0) << endl;
    // count.precision(12)将打印精度设置为12位数字
    cout.precision(12);
    cout << "Precision: " << cout.precision()
         << ", Value: " << sqrt(2.0) << endl;
    // 另一种设置精度方法是设置setprecision操纵符
    cout << setprecision(3);
    cout << "Precision: " << cout.precision()
         << ", Value: " << sqrt(2.0) << endl; 
    

    17.5.2 未格式化的输入/输出操作

    17.5.3 流随机访问

    各种流类型通常都支持对流中数据的随机访问,可以重定位流,使之跳过一些数据,首先读取最后一行,然后读取第一行,一次类推。
    标准库提供了一对函数来定位到流中给定的位置:seek 定位,tell 告诉我们当前位置。

    使用seek/tell是否有意义,取决于流绑定到哪个设备。在大多数系统中,绑定到cin/cout/cerr和clog的流,不支持随机访问。

    注意:istream, ostream类型不支持随机访问,本节内容只适用于fstream, sstream类型。

    seek和tell函数
    seek 通过到一个给定位置来重定位;
    tell 标记当前位置;

    标准库定义了2个版本:g版本用于输入流,p版本用于输出流。
    逻辑上,只能对istream和派生自istream的ifstream和istringstream使用g版本。一个iostream、fstream、stringstream既能读又能写关联的流,这些类型对象,既能使用g版本,又能使用p版本。

    注意:虽然seek和tell有“放置”和“获得”2个版本,但是流只维护一个标记,不存在写标记和读标记之分。这也就是说,程序可能需要在读写操作间进行切换。
    如,输入流ifstream只能使用g版本,输出流ostringstream只能使用p版本。

    tellg();  // 返回一个输入流中标记的当前位置
    tellp();  // 返回一个输出流中标记的当前位置
    
    seekg(pos); // 在一个输入流中将标记重定位到给定的绝对地址。pos通常是前一个tellg返回的值
    seekp(pos); // 在一个输出流中将标记重定位到给定的绝对地址。pos通常是前一个tellp返回的值
    
    seekg(off, from); // 在一个输入流中,将标记定位到from之前或之后off个字符。
    seekp(off, from); // 在一个输出流中,将标记定位到from之前或之后off个字符。
    // from可以是下列值之一
    // beg, 偏移量相对于流开始位置
    // cur,偏移量相对于流当前位置
    // end,偏移量相对于流结尾位置
    

    读写同一个文件
    示例,给定一个要读取的文件,要实现:在此文件的末尾写入新的一行,这一行包含文件中每行的相对起始位置。

    #if 1 // 用于创建,并输入文件初始内容
    fstream fs("copyOut", fstream::out); // 只读模式打开文件
    if (fs) {
        cout << "Success to open file." << endl;
        fs << "abcd" << endl;
        fs << "efg" << endl;
        fs << "hi" << endl;
        fs << "j" << endl;
    }
    else cout << "fail to open file." << endl;
    fs.close();
    #endif
    
    fstream inOut("copyOut",
                  fstream::ate | fstream::in | fstream::out ); // 以ate模式打开, 一开始旧定位到文件尾
    if (!inOut) {
        cerr << "Unable to open file!" << endl;
        return EXIT_FAILURE;
    }
    
    auto end_mark = inOut.tellg(); // 记住原文件尾位置
    inOut.seekg(0, fstream::beg);  // 重定位到文件开始
    size_t cnt = 0; // 字节累加器
    string line; // 保存输入中的每行
    
    while (inOut && inOut.tellg() != end_mark && getline(inOut, line)) {
        cnt += line.size() + 1;
        auto mark = inOut.tellg();
        inOut.seekp(0, fstream::end);
        inOut << cnt;
    
        // 如果不是最后一行,打印一个分隔符
        if (mark != end_mark) inOut << " ";
        inOut.seekg(mark); // 恢复读位置
    }
    
    inOut.seekp(0, fstream::end); // 定位到文件尾部
    inOut << "
    ";  // 文件尾输出一个换行符
    

    运行结果:

    abcd
    efg
    hi
    j
    5 9 12 14
    
  • 相关阅读:
    02 小白新一天
    集合排序
    匿名内部类-Lambda表达式
    设计模式之适配器设计
    设计模式之代理设计
    设计模式之工厂设计
    依赖倒转原则
    多态及练习题
    在一个类中调用另外一个类
    对象的三大特性之封装
  • 原文地址:https://www.cnblogs.com/fortunely/p/14567790.html
Copyright © 2011-2022 走看看