《C++ Primer》导学系列:第 17 章 - 标准库特殊设施

17.1 tuple类型

C++11引入的tuple类型是一个可以包含多个不同类型元素的固定大小容器。tuple类似于pair,但其可以容纳多个元素,不限于两个。这使得tuple非常适合用来返回多个值的函数或者需要存储异构数据的场景。

17.1.1 定义和初始化tuple

定义和初始化tuple非常简单,可以使用std::make_tuple来创建一个tuple对象。

示例代码
#include <iostream>
#include <tuple>

int main() {
    // 定义并初始化tuple
    std::tuple<int, double, std::string> t1(10, 3.14, "Hello");
    auto t2 = std::make_tuple(20, 2.718, "World");

    std::cout << "t1: " << std::get<0>(t1) << ", " << std::get<1>(t1) << ", " << std::get<2>(t1) << std::endl;
    std::cout << "t2: " << std::get<0>(t2) << ", " << std::get<1>(t2) << ", " << std::get<2>(t2) << std::endl;

    return 0;
}

在这个示例中,我们定义了两个tuple对象t1t2,并使用std::get函数来访问其中的元素。

17.1.2 访问tuple元素

访问tuple元素有两种常见方法:使用std::get和使用std::tie。前者用于获取单个元素,后者用于解包tuple中的多个元素。

使用std::get
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t(10, 3.14, "Hello");

    // 使用std::get访问元素
    int n = std::get<0>(t);
    double d = std::get<1>(t);
    std::string s = std::get<2>(t);

    std::cout << "n: " << n << ", d: " << d << ", s: " << s << std::endl;

    return 0;
}
使用std::tie
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t(10, 3.14, "Hello");

    // 使用std::tie解包tuple
    int n;
    double d;
    std::string s;
    std::tie(n, d, s) = t;

    std::cout << "n: " << n << ", d: " << d << ", s: " << s << std::endl;

    return 0;
}

17.1.3 修改tuple元素

tuple元素可以通过std::get函数进行修改。由于std::get返回的是元素的引用,因此可以直接对其进行赋值操作。

示例代码
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t(10, 3.14, "Hello");

    // 修改tuple元素
    std::get<0>(t) = 20;
    std::get<1>(t) = 2.718;
    std::get<2>(t) = "World";

    std::cout << "t: " << std::get<0>(t) << ", " << std::get<1>(t) << ", " << std::get<2>(t) << std::endl;

    return 0;
}

17.1.4 tuple的比较操作

tuple支持比较操作,包括相等比较和大小比较。这些比较操作是逐元素进行的,直到找到第一个不相等的元素。

示例代码
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t1(10, 3.14, "Hello");
    std::tuple<int, double, std::string> t2(20, 2.718, "World");

    if (t1 < t2) {
        std::cout << "t1 is less than t2" << std::endl;
    } else {
        std::cout << "t1 is not less than t2" << std::endl;
    }

    return 0;
}

在这个示例中,t1t2的比较是逐元素进行的,首先比较第一个元素,如果第一个元素相等,再比较第二个元素,依此类推。

17.1.4 使用tuple返回多个值

tuple在函数返回多个值时非常有用。通过将多个返回值打包到一个tuple中,我们可以轻松地从函数返回多个值,并使用std::tie或结构化绑定来解包这些值。

示例代码

以下是一个使用tuple返回多个值的示例:

#include <iostream>
#include <tuple>

// 定义一个返回多个值的函数
std::tuple<int, double, std::string> getValues() {
    return std::make_tuple(42, 3.14, "Hello");
}

int main() {
    // 使用std::tie解包返回的tuple
    int n;
    double d;
    std::string s;
    std::tie(n, d, s) = getValues();

    std::cout << "n: " << n << ", d: " << d << ", s: " << s << std::endl;

    return 0;
}

在这个示例中,getValues 函数返回一个包含三个不同类型值的tuple,在main函数中使用std::tie解包这些返回值。

17.1.5 tuple和结构化绑定

C++17引入了结构化绑定语法,使得从tuple中解包多个元素变得更加简单和直观。

示例代码
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t(10, 3.14, "Hello");

    // 使用结构化绑定解包tuple
    auto [n, d, s] = t;

    std::cout << "n: " << n << ", d: " << d << ", s: " << s << std::endl;

    return 0;
}

重点与难点分析

重点

  1. 定义和初始化tuple:理解如何定义和初始化tuple对象,掌握std::make_tuple的使用方法。
  2. 访问和修改tuple元素:掌握使用std::getstd::tie访问和修改tuple元素的方法。
  3. 使用tuple返回多个值:理解如何通过tuple返回多个值,并使用std::tie或结构化绑定解包这些值。
  4. tuple的比较操作:了解tuple的比较规则,理解逐元素比较的原理。
  5. 结构化绑定:熟悉C++17引入的结构化绑定语法,掌握如何用结构化绑定从tuple中解包元素。

难点

  1. 元素访问和修改:正确使用std::get函数访问和修改tuple元素,避免类型错误和越界访问。
  2. 比较操作:理解tuple的逐元素比较规则,正确实现tuple的比较操作。

练习题解析

  1. 练习17.1:定义一个tuple,包含一个整数、一个双精度浮点数和一个字符串。使用std::get访问并修改这些元素。
    • 示例代码
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t(10, 3.14, "Hello");

    // 访问元素
    int n = std::get<0>(t);
    double d = std::get<1>(t);
    std::string s = std::get<2>(t);

    std::cout << "Before modification: " << n << ", " << d << ", " << s << std::endl;

    // 修改元素
    std::get<0>(t) = 20;
    std::get<1>(t) = 2.718;
    std::get<2>(t) = "World";

    std::cout << "After modification: " << std::get<0>(t) << ", " << std::get<1>(t) << ", " << std::get<2>(t) << std::endl;

    return 0;
}
  1. 练习17.2:定义两个包含相同类型元素的tuple,比较它们的大小。
    • 示例代码
#include <iostream>
#include <tuple>

int main() {
    std::tuple<int, double, std::string> t1(10, 3.14, "Hello");
    std::tuple<int, double, std::string> t2(20, 2.718, "World");

    if (t1 < t2) {
        std::cout << "t1 is less than t2" << std::endl;
    } else {
        std::cout << "t1 is not less than t2" << std::endl;
    }

    return 0;
}
  1. 练习17.3:编写一个函数,使用tuple返回多个值,并在主程序中使用结构化绑定解包这些值。
    • 示例代码
#include <iostream>
#include <tuple>

// 定义一个返回多个值的函数
std::tuple<int, double, std::string> getValues() {
    return std::make_tuple(42, 3.14, "Hello");
}

int main() {
    // 使用结构化绑定解包返回的tuple
    auto [n, d, s] = getValues();

    std::cout << "n: " << n << ", d: " << d << ", s: " << s << std::endl;

    return 0;
}
  1. 练习17.4:编写一个函数,接受一个tuple作为参数,并打印其内容。使用std::apply函数将tuple展开为函数参数。
    • 示例代码
#include <iostream>
#include <tuple>
#include <utility>

// 打印tuple内容的函数
void printTuple(int n, double d, const std::string& s) {
    std::cout << "n: " << n << ", d: " << d << ", s: " << s << std::endl;
}

int main() {
    std::tuple<int, double, std::string> t = std::make_tuple(42, 3.14, "Hello");

    // 使用std::apply将tuple展开为函数参数
    std::apply(printTuple, t);

    return 0;
}

总结与提高

本节总结

  1. 了解了tuple类型的定义和初始化方法,掌握了使用std::make_tuple创建tuple对象的技巧。
  2. 学会了使用std::getstd::tie访问和修改tuple元素,以及使用结构化绑定解包tuple
  3. 理解了tuple的比较操作规则,能够正确实现tuple的相等比较和大小比较。
  4. 掌握了使用tuple返回多个值的方法,能够通过std::tie或结构化绑定解包返回的tuple
  5. 学会了使用std::apply函数将tuple展开为函数参数,从而灵活地操作tuple中的数据。

提高建议

  1. 多练习tuple的使用:通过编写更多的tuple相关代码,熟悉tuple的定义、初始化、访问和修改操作,掌握结构化绑定和比较操作的技巧。
  2. 深入理解比较规则:通过阅读相关文档和书籍,深入理解tuple的比较规则,避免在实际使用中出现比较错误。
  3. 优化代码设计:在实际项目中,合理使用tuple存储和处理异构数据,提高代码的可读性和可维护性。
  4. 结合其他标准库组件:学习和掌握std::apply等标准库组件,与tuple结合使用,进一步提高代码的灵活性和功能性。

17.2 bitset类型

C++标准库提供了bitset类型,用于处理固定大小的二进制位序列。bitset类型提供了位操作的丰富接口,方便我们进行位操作和位运算。

17.2.1 定义和初始化bitset

定义和初始化bitset可以使用不同的方法,包括使用整数、字符串和位序列。

示例代码
#include <iostream>
#include <bitset>

int main() {
    // 使用整数初始化bitset
    std::bitset<8> b1(42);    // 42的二进制表示:00101010
    std::cout << "b1: " << b1 << std::endl;

    // 使用字符串初始化bitset
    std::bitset<8> b2("101010");    // 二进制表示:00101010
    std::cout << "b2: " << b2 << std::endl;

    // 默认初始化
    std::bitset<8> b3;    // 默认值:00000000
    std::cout << "b3: " << b3 << std::endl;

    return 0;
}

在这个示例中,我们定义了三个bitset对象,分别使用整数、字符串和默认值进行初始化。

17.2.2 访问bitset元素

可以使用数组下标运算符访问和修改bitset的各个位,或者使用testsetresetflip等成员函数。

示例代码
#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> b(42);    // 42的二进制表示:00101010

    // 使用数组下标运算符访问和修改位
    std::cout << "b[0]: " << b[0] << std::endl;    // 输出:b[0]: 0
    b[0] = 1;
    std::cout << "b: " << b << std::endl;          // 输出:b: 00101011

    // 使用成员函数访问和修改位
    std::cout << "b.test(1): " << b.test(1) << std::endl;  // 输出:b.test(1): 1
    b.set(1, 0);
    std::cout << "b: " << b << std::endl;          // 输出:b: 00101001
    b.reset(2);
    std::cout << "b: " << b << std::endl;          // 输出:b: 00100001
    b.flip(3);
    std::cout << "b: " << b << std::endl;          // 输出:b: 00110001

    return 0;
}

在这个示例中,我们使用数组下标运算符和testsetresetflip成员函数访问和修改bitset的各个位。

17.2.3 bitset的位运算

bitset提供了丰富的位运算操作,包括按位与(&)、按位或(|)、按位异或(^)等。

示例代码
#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> b1(42);    // 42的二进制表示:00101010
    std::bitset<8> b2(15);    // 15的二进制表示:00001111

    // 按位与
    std::bitset<8> b_and = b1 & b2;
    std::cout << "b1 & b2: " << b_and << std::endl;  // 输出:b1 & b2: 00001010

    // 按位或
    std::bitset<8> b_or = b1 | b2;
    std::cout << "b1 | b2: " << b_or << std::endl;   // 输出:b1 | b2: 00101111

    // 按位异或
    std::bitset<8> b_xor = b1 ^ b2;
    std::cout << "b1 ^ b2: " << b_xor << std::endl;  // 输出:b1 ^ b2: 00100101

    return 0;
}

在这个示例中,我们使用bitset的按位与、按位或、按位异或操作来进行位运算。

17.2.4 bitset的其它操作

除了常见的位运算,bitset还提供了一些其它操作,如获取位数、全部置位、全部复位等。

示例代码
#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> b(42);    // 42的二进制表示:00101010

    // 获取位数
    std::cout << "b.size(): " << b.size() << std::endl;       // 输出:b.size(): 8
    std::cout << "b.count(): " << b.count() << std::endl;     // 输出:b.count(): 3

    // 全部置位
    b.set();
    std::cout << "b: " << b << std::endl;                    // 输出:b: 11111111

    // 全部复位
    b.reset();
    std::cout << "b: " << b << std::endl;                    // 输出:b: 00000000

    return 0;
}

在这个示例中,我们使用bitsetsizecountsetreset成员函数进行操作。

重点与难点分析

重点

  1. 定义和初始化bitset:理解如何使用整数、字符串和默认值来定义和初始化bitset对象。
  2. 访问和修改bitset元素:掌握使用数组下标运算符和testsetresetflip等成员函数访问和修改bitset元素的方法。
  3. bitset的位运算:了解bitset的按位与、按位或、按位异或等位运算操作。
  4. bitset的其它操作:熟悉bitsetsizecountsetreset等成员函数的使用。

难点

  1. 位运算操作:正确使用bitset的按位与、按位或、按位异或操作,避免运算错误。
  2. 访问和修改元素:使用数组下标运算符和testsetresetflip等成员函数时,注意位的索引范围和值的合法性。

练习题解析

  1. 练习17.5:定义一个bitset,初始化为整数值42,打印其二进制表示,并依次将所有位翻转,然后将所有位复位。
    • 示例代码
#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> b(42);    // 42的二进制表示:00101010
    std::cout << "Initial bitset: " << b << std::endl;

    // 翻转所有位
    b.flip();
    std::cout << "After flipping: " << b << std::endl;  // 输出:After flipping: 11010101

    // 复位所有位
    b.reset();
    std::cout << "After resetting: " << b << std::endl; // 输出:After resetting: 00000000

    return 0;
}
  1. 练习17.6:定义两个bitset,分别初始化为整数值42和15,进行按位与、按位或和按位异或操作,并打印结果。
    • 示例代码
#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> b1(42);    // 42的二进制表示:00101010
    std::bitset<8> b2(15);    // 15的二进制表示:00001111

    // 按位与
    std::bitset<8> b_and = b1 & b2;
    std::cout << "b1 & b2: " << b_and << std::endl;  // 输出:b1 & b2: 00001010

    // 按位或
    std::bitset<8> b_or = b1 | b2;
    std::cout << "b1 | b2: " << b_or << std::endl;   // 输出:b1 | b2: 00101111

    // 按位异或
    std::bitset<8> b_xor = b1 ^ b2;
    std::cout << "b1 ^ b2: " << b_xor << std::endl;  // 输出:b1 ^ b2: 00100101

    return 0;
}
  1. 练习17.7:定义一个bitset,使用成员函数setresetflip修改其中的位,并打印修改后的结果。
    • 示例代码
#include <iostream>
#include <bitset>

int main() {
    std::bitset<8> b(42);    // 42的二进制表示:00101010

    // 使用成员函数修改位
    b.set(0);
    std::cout << "After set(0): " << b << std::endl;    // 输出:After set(0): 00101011

    b.reset(1);
    std::cout << "After reset(1): " << b << std::endl;  // 输出:After reset(1): 00101001

    b.flip(2);
    std::cout << "After flip(2): " << b << std::endl;   // 输出:After flip(2): 00100001

    return 0;
}

总结与提高

本节总结

  1. 了解了bitset类型的定义和初始化方法,掌握了使用整数、字符串和默认值初始化bitset对象的技巧。
  2. 学会了使用数组下标运算符和testsetresetflip等成员函数访问和修改bitset元素。
  3. 理解了bitset的位运算操作,包括按位与、按位或、按位异或等。
  4. 熟悉了bitset的其他操作,如获取位数、全部置位、全部复位等。

提高建议

  1. 多练习bitset的使用:通过编写更多的bitset相关代码,熟悉bitset的定义、初始化、访问和修改操作,掌握位运算和其他操作的技巧。
  2. 深入理解位运算:通过阅读相关文档和书籍,深入理解bitset的位运算规则,避免在实际使用中出现运算错误。
  3. 优化代码设计:在实际项目中,合理使用bitset进行位操作,提高代码的效率和可维护性。

17.3 正则表达式

正则表达式(Regular Expressions)是一种描述字符模式的工具,用于搜索、匹配和操作文本。C++11标准库引入了正则表达式库,使得在C++中使用正则表达式变得方便和高效。

17.3.1 定义和使用正则表达式

在C++中,std::regex类用于表示正则表达式。可以使用构造函数来定义正则表达式模式。

示例代码
#include <iostream>
#include <regex>

int main() {
    // 定义正则表达式
    std::regex pattern("\\d+");
    
    // 测试字符串
    std::string test_str = "The number is 12345";
    
    // 检查是否匹配
    if (std::regex_search(test_str, pattern)) {
        std::cout << "Found a match!" << std::endl;
    } else {
        std::cout << "No match found." << std::endl;
    }

    return 0;
}

在这个示例中,我们定义了一个匹配数字的正则表达式,并使用std::regex_search函数在字符串中搜索匹配项。

17.3.2 正则表达式匹配

C++提供了几种匹配正则表达式的方法,包括std::regex_searchstd::regex_matchstd::regex_replace

std::regex_search

std::regex_search用于在目标字符串中搜索正则表达式的匹配项。它返回一个布尔值,指示是否找到了匹配项。

#include <iostream>
#include <regex>

int main() {
    std::regex pattern("\\d+");
    std::string test_str = "The number is 12345";

    if (std::regex_search(test_str, pattern)) {
        std::cout << "Found a match!" << std::endl;
    } else {
        std::cout << "No match found." << std::endl;
    }

    return 0;
}
std::regex_match

std::regex_match用于检查整个字符串是否完全匹配正则表达式模式。它同样返回一个布尔值。

#include <iostream>
#include <regex>

int main() {
    std::regex pattern("\\d+");
    std::string test_str = "12345";

    if (std::regex_match(test_str, pattern)) {
        std::cout << "The entire string is a match!" << std::endl;
    } else {
        std::cout << "The entire string does not match." << std::endl;
    }

    return 0;
}
std::regex_replace

std::regex_replace用于将目标字符串中匹配正则表达式的部分替换为指定的字符串。

#include <iostream>
#include <regex>

int main() {
    std::regex pattern("\\d+");
    std::string test_str = "The number is 12345";
    
    // 替换匹配项
    std::string replaced_str = std::regex_replace(test_str, pattern, "number");
    std::cout << "Replaced string: " << replaced_str << std::endl;  // 输出:The number is number

    return 0;
}

17.3.3 捕获组和迭代器

正则表达式支持捕获组,允许我们提取匹配到的子串。可以使用std::smatchstd::regex_iterator来获取捕获组。

示例代码
#include <iostream>
#include <regex>

int main() {
    std::regex pattern("(\\d+)");
    std::string test_str = "Numbers: 123, 456, 789";

    std::smatch matches;
    if (std::regex_search(test_str, matches, pattern)) {
        std::cout << "Found matches:" << std::endl;
        for (size_t i = 0; i < matches.size(); ++i) {
            std::cout << "Match " << i << ": " << matches[i] << std::endl;
        }
    }

    return 0;
}

17.3.4 常用正则表达式模式

正则表达式的模式使用特定的语法,以下是一些常用的正则表达式模式:

  • . 匹配任意单个字符
  • * 匹配前面的子表达式零次或多次
  • + 匹配前面的子表达式一次或多次
  • ? 匹配前面的子表达式零次或一次
  • \\d 匹配一个数字字符
  • \\w 匹配一个字母或数字字符
  • [] 定义字符类,例如[a-z]匹配任意小写字母

重点与难点分析

重点

  1. 定义和使用正则表达式:理解如何使用std::regex类定义正则表达式,并在字符串中搜索匹配项。
  2. 正则表达式匹配:掌握std::regex_searchstd::regex_matchstd::regex_replace的用法。
  3. 捕获组和迭代器:了解如何使用捕获组提取匹配到的子串,以及使用迭代器遍历所有匹配项。
  4. 常用正则表达式模式:熟悉常用的正则表达式模式,掌握它们在实际应用中的使用方法。

难点

  1. 复杂的正则表达式模式:编写和调试复杂的正则表达式模式可能比较困难,需要反复测试和调整。
  2. 捕获组的使用:正确使用捕获组提取子串,确保提取到正确的匹配内容。

练习题解析

  1. 练习17.8:编写一个程序,使用正则表达式搜索并打印字符串中的所有数字。
    • 示例代码
#include <iostream>
#include <regex>

int main() {
    std::regex pattern("\\d+");
    std::string test_str = "Numbers: 123, 456, 789";

    auto words_begin = std::sregex_iterator(test_str.begin(), test_str.end(), pattern);
    auto words_end = std::sregex_iterator();

    std::cout << "Found numbers:" << std::endl;
    for (auto it = words_begin; it != words_end; ++it) {
        std::cout << it->str() << std::endl;
    }

    return 0;
}
  1. 练习17.9:编写一个程序,使用正则表达式匹配并替换字符串中的所有单词,将其替换为"word"
    • 示例代码
#include <iostream>
#include <regex>

int main() {
    std::regex pattern("\\w+");
    std::string test_str = "This is a test string.";

    std::string replaced_str = std::regex_replace(test_str, pattern, "word");
    std::cout << "Replaced string: " << replaced_str << std::endl;  // 输出:word word word word word.

    return 0;
}
  1. 练习17.10:编写一个程序,使用正则表达式提取并打印字符串中的所有电子邮件地址。
    • 示例代码
#include <iostream>
#include <regex>

int main() {
    std::regex pattern("[\\w.-]+@[\\w.-]+\\.\\w+");
    std::string test_str = "Contact us at support@example.com or sales@example.com";

    auto words_begin = std::sregex_iterator(test_str.begin(), test_str.end(), pattern);
    auto words_end = std::sregex_iterator();

    std::cout << "Found email addresses:" << std::endl;
    for (auto it = words_begin; it != words_end; ++it) {
        std::cout << it->str() << std::endl;
    }

    return 0;
}

总结与提高

本节总结

  1. 了解了如何使用std::regex定义和使用正则表达式,掌握了基本的正则表达式匹配和替换方法。
  2. 学会了使用捕获组和迭代器提取匹配到的子串,以及遍历所有匹配项的方法。
  3. 熟悉了常用的正则表达式模式,能够编写和使用正则表达式解决实际问题。

提高建议

  1. 多练习正则表达式的使用:通过编写更多的正则表达式相关代码,熟悉各种模式的定义和使用,掌握复杂模式的编写技巧。
  2. 深入理解捕获组和迭代器:通过阅读相关文档和书籍,深入理解捕获组和迭代器的用法,确保正确提取匹配内容。
  3. 优化代码设计:在实际项目中,合理使用正则表达式进行文本操作,提高代码的效率和可维护性。

17.4 随机数

C++11标准库引入了一个强大且灵活的随机数生成库,可以生成各种随机数序列。与传统的rand函数相比,新的随机数库提供了更多的功能和更高的随机性。

17.4.1 随机数引擎

随机数引擎是随机数生成的核心。C++标准库提供了多种随机数引擎,如std::default_random_enginestd::mt19937等。随机数引擎用于生成均匀分布的无偏随机数序列。

示例代码
#include <iostream>
#include <random>

int main() {
    // 创建随机数引擎
    std::default_random_engine engine;
    
    // 生成随机数
    for (int i = 0; i < 5; ++i) {
        std::cout << engine() << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用std::default_random_engine创建一个随机数引擎,并生成了5个随机数。

17.4.2 随机数分布

C++标准库提供了多种随机数分布类型,如均匀分布、正态分布、泊松分布等。通过结合随机数引擎和随机数分布,我们可以生成不同类型的随机数序列。

示例代码
#include <iostream>
#include <random>

int main() {
    // 创建随机数引擎和均匀分布
    std::default_random_engine engine;
    std::uniform_int_distribution<int> dist(1, 100);
    
    // 生成随机数
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用std::uniform_int_distribution生成1到100之间的均匀分布随机数。

17.4.3 种子和播种

为了生成不同的随机数序列,可以使用种子来初始化随机数引擎。相同的种子将生成相同的随机数序列。

示例代码
#include <iostream>
#include <random>

int main() {
    // 使用相同的种子初始化随机数引擎
    std::default_random_engine engine1(42);
    std::default_random_engine engine2(42);
    
    // 生成随机数
    std::uniform_int_distribution<int> dist(1, 100);
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine1) << " ";
    }
    std::cout << std::endl;

    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine2) << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用相同的种子初始化了两个随机数引擎,并生成了相同的随机数序列。

17.4.4 生成浮点数

除了生成整数,随机数库还可以生成浮点数。可以使用std::uniform_real_distribution生成均匀分布的浮点数。

示例代码
#include <iostream>
#include <random>

int main() {
    // 创建随机数引擎和均匀分布
    std::default_random_engine engine;
    std::uniform_real_distribution<double> dist(0.0, 1.0);
    
    // 生成随机数
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用std::uniform_real_distribution生成0到1之间的均匀分布浮点数。

17.4.5 其他随机数分布

C++标准库还提供了其他类型的随机数分布,如正态分布、泊松分布等。可以根据需要选择合适的分布类型生成随机数。

示例代码
#include <iostream>
#include <random>

int main() {
    // 创建随机数引擎和正态分布
    std::default_random_engine engine;
    std::normal_distribution<double> dist(0.0, 1.0);
    
    // 生成随机数
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

在这个示例中,我们使用std::normal_distribution生成均值为0,标准差为1的正态分布随机数。

重点与难点分析

重点

  1. 随机数引擎:理解如何使用随机数引擎生成随机数,并掌握不同随机数引擎的特点。
  2. 随机数分布:了解各种随机数分布的使用方法,掌握生成不同类型随机数的技巧。
  3. 种子和播种:理解种子的作用,掌握如何使用种子生成不同的随机数序列。
  4. 生成浮点数和其他分布:学会生成浮点数随机数,并了解其他常用的随机数分布。

难点

  1. 随机数的随机性:理解不同随机数引擎和分布的随机性特点,选择合适的引擎和分布生成所需的随机数。
  2. 种子的选择:正确选择和使用种子,确保生成的随机数序列满足需求。

练习题解析

  1. 练习17.11:编写一个程序,使用std::uniform_int_distribution生成1到100之间的随机整数,并打印这些整数。
    • 示例代码
#include <iostream>
#include <random>

int main() {
    std::default_random_engine engine;
    std::uniform_int_distribution<int> dist(1, 100);
    
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}
  1. 练习17.12:编写一个程序,使用std::uniform_real_distribution生成0到1之间的随机浮点数,并打印这些浮点数。
    • 示例代码
#include <iostream>
#include <random>

int main() {
    std::default_random_engine engine;
    std::uniform_real_distribution<double> dist(0.0, 1.0);
    
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}
  1. 练习17.13:编写一个程序,使用std::normal_distribution生成均值为0,标准差为1的正态分布随机数,并打印这些随机数。
    • 示例代码
#include <iostream>
#include <random>

int main() {
    std::default_random_engine engine;
    std::normal_distribution<double> dist(0.0, 1.0);
    
    for (int i = 0; i < 5; ++i) {
        std::cout << dist(engine) << " ";
    }
    std::cout << std::endl;

    return 0;
}

总结与提高

本节总结

  1. 了解了随机数引擎的定义和使用方法,掌握了不同随机数引擎的特点。
  2. 学会了使用各种随机数分布生成不同类型的随机数,掌握了生成整数和浮点数随机数的技巧。
  3. 理解了种子的作用,掌握了使用种子生成不同随机数序列的方法。
  4. 熟悉了其他常用的随机数分布,如正态分布、泊松分布等,能够根据需要选择合适的分布生成随机数。

提高建议

  1. 多练习随机数生成:通过编写更多的随机数相关代码,熟悉随机数引擎和分布的使用方法,掌握生成不同类型随机数的技巧。
  2. 深入理解随机性:通过阅读相关文档和书籍,深入理解不同随机数引擎和分布的随机性特点,选择合适的引擎和分布生成所需的随机数。
  3. 优化代码设计:在实际项目中,合理使用随机数生成库,提高代码的效率和可维护性。

17.5 IO库的再探讨

C++标准库的输入输出库(IO库)提供了丰富的功能和灵活的接口,用于处理文件、字符串和控制台的输入输出操作。在这一节中,我们将深入探讨IO库的一些高级特性和使用技巧。

17.5.1 格式化输入与输出

格式化输入与输出是控制数据表示方式的关键。C++提供了多种控制输出格式的方法。

控制布尔值的格式

默认情况下,布尔值会以01的形式输出。可以使用std::boolalphastd::noboolalpha控制布尔值的输出格式。

#include <iostream>

int main() {
    bool flag = true;

    std::cout << "Default: " << flag << std::endl;
    std::cout << std::boolalpha << "Boolalpha: " << flag << std::endl;
    std::cout << std::noboolalpha << "Noboolalpha: " << flag << std::endl;

    return 0;
}
指定整型值的进制

可以使用std::decstd::octstd::hex控制整型值的输出进制。

#include <iostream>

int main() {
    int number = 42;

    std::cout << "Decimal: " << std::dec << number << std::endl;
    std::cout << "Octal: " << std::oct << number << std::endl;
    std::cout << "Hexadecimal: " << std::hex << number << std::endl;

    return 0;
}
在输出中指出进制

可以使用std::showbasestd::noshowbase控制输出中是否显示进制前缀。

#include <iostream>

int main() {
    int number = 42;

    std::cout << std::showbase;
    std::cout << "Decimal: " << std::dec << number << std::endl;
    std::cout << "Octal: " << std::oct << number << std::endl;
    std::cout << "Hexadecimal: " << std::hex << number << std::endl;
    std::cout << std::noshowbase;

    return 0;
}
控制浮点数格式

可以使用std::fixedstd::scientificstd::defaultfloat控制浮点数的输出格式。

#include <iostream>
#include <iomanip>

int main() {
    double pi = 3.141592653589793;

    std::cout << "Default: " << pi << std::endl;
    std::cout << std::fixed << "Fixed: " << pi << std::endl;
    std::cout << std::scientific << "Scientific: " << pi << std::endl;
    std::cout << std::defaultfloat << "Defaultfloat: " << pi << std::endl;

    return 0;
}
指定打印精度

可以使用std::setprecision指定浮点数的打印精度。

#include <iostream>
#include <iomanip>

int main() {
    double pi = 3.141592653589793;

    std::cout << std::fixed << std::setprecision(2) << "Precision 2: " << pi << std::endl;
    std::cout << std::setprecision(5) << "Precision 5: " << pi << std::endl;

    return 0;
}
指定浮点数计数法

可以使用std::fixedstd::scientific指定浮点数的计数法。

#include <iostream>
#include <iomanip>

int main() {
    double pi = 3.141592653589793;

    std::cout << std::fixed << "Fixed: " << pi << std::endl;
    std::cout << std::scientific << "Scientific: " << pi << std::endl;

    return 0;
}
打印小数点

可以使用std::showpointstd::noshowpoint控制是否显示小数点。

#include <iostream>

int main() {
    double value = 42.0;

    std::cout << "Default: " << value << std::endl;
    std::cout << std::showpoint << "Showpoint: " << value << std::endl;
    std::cout << std::noshowpoint;

    return 0;
}
控制输入格式

可以使用std::skipwsstd::noskipws控制输入时是否跳过空白字符。

#include <iostream>

int main() {
    char ch1, ch2;

    std::cout << "Enter two characters: ";
    std::cin >> std::skipws >> ch1 >> ch2;  // 默认跳过空白字符
    std::cout << "You entered: " << ch1 << " and " << ch2 << std::endl;

    std::cin >> std::noskipws >> ch1 >> ch2;  // 不跳过空白字符
    std::cout << "You entered: " << ch1 << " and " << ch2 << std::endl;

    return 0;
}

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

未格式化的输入/输出操作提供了更底层的控制,用于处理单字节和多字节的输入输出操作。

单字节操作

可以使用getput成员函数进行单字节输入输出操作。

#include <iostream>

int main() {
    char ch;

    std::cout << "Enter a character: ";
    std::cin.get(ch);
    std::cout.put(ch).put('\n');

    return 0;
}
将字符放回输入流

可以使用unget函数将读取的字符放回输入流。

#include <iostream>

int main() {
    char ch;

    std::cout << "Enter a character: ";
    std::cin.get(ch);
    std::cout << "You entered: " << ch << std::endl;

    std::cin.unget();
    std::cin.get(ch);
    std::cout << "After unget: " << ch << std::endl;

    return 0;
}
多字节操作

可以使用readwrite函数进行多字节输入输出操作。

#include <iostream>

int main() {
    char buffer[10];

    std::cout << "Enter a string: ";
    std::cin.read(buffer, 10);
    std::cout.write(buffer, 10).put('\n');

    return 0;
}

17.5.3 流随机访问

流随机访问允许我们在文件中跳转到特定位置进行读写操作。可以使用seektell函数实现这一功能。

seek和tell函数

seekgtellg用于输入流的定位和获取位置,seekptellp用于输出流。

#include <iostream>
#include <fstream>

int main() {
    std::ofstream ofs("example.txt");
    ofs << "Hello, World!";
    ofs.close();

    std::ifstream ifs("example.txt");
    ifs.seekg(7);
    char buffer[6];
    ifs.read(buffer, 5);
    buffer[5] = '\0';
    std::cout << buffer << std::endl;  // 输出:World

    return 0;
}
只有一个标记

大多数流只有一个标记,用于跟踪读写位置。

#include <iostream>
#include <fstream>

int main() {
    std::fstream fs("example.txt", std::ios::in | std::ios::out);
    fs << "12345";
    fs.seekg(0);

    char ch;
    fs.get(ch);
    std::cout << ch << std::endl;  // 输出:1

    fs.seekp(1);
    fs.put('X');
    fs.seekg(0);
    fs.get(ch);
    std::cout << ch << std::endl;  // 输出:1

    return 0;
}
重定位标记

可以使用相对位置和绝对位置进行标记重定位。

#include <iostream>
#include <fstream>

int main() {
    std::fstream fs("example.txt", std::ios::in | std::ios::out);
    fs << "12345";
    fs.seekg(0);

    fs.seekg(2, std::ios::cur);
    char ch;
    fs.get(ch);
    std::cout << ch << std::endl;  // 输出:3

    fs.seekp(-2, std::ios::end);
    fs.put('Y');
    fs.seekg(0, std::ios::beg);
    std::string content;
    std::getline(fs, content);
    std::cout << content << std::endl;  // 输出:123Y5

    return 0;
}
访问标记

可以使用tellgtellp获取当前的读写位置。

#include <iostream>
#include <fstream>

int main

() {
    std::ifstream ifs("example.txt");
    ifs.seekg(0, std::ios::end);
    std::streampos length = ifs.tellg();
    ifs.seekg(0, std::ios::beg);
    std::cout << "File length: " << length << " bytes" << std::endl;

    return 0;
}
读写同一个文件

可以同时打开文件进行读写操作,但要注意流的同步问题。

#include <iostream>
#include <fstream>

int main() {
    std::fstream fs("example.txt", std::ios::in | std::ios::out);
    fs << "12345";
    fs.seekg(0);

    char ch;
    fs.get(ch);
    std::cout << ch << std::endl;  // 输出:1

    fs.seekp(2);
    fs.put('Z');
    fs.seekg(0);
    std::string content;
    std::getline(fs, content);
    std::cout << content << std::endl;  // 输出:12Z45

    return 0;
}

重点与难点分析

重点

  1. 格式化输入与输出:掌握如何使用各种操作符控制输出格式,包括布尔值、整型值的进制、浮点数的格式和精度等。
  2. 未格式化的输入/输出操作:了解如何使用单字节和多字节操作进行未格式化的输入输出操作。
  3. 流随机访问:熟悉如何使用seektell函数实现流的随机访问,掌握读写同一个文件的技巧。

难点

  1. 复杂的格式化控制:正确使用各种格式化控制操作符,避免输出格式错误。
  2. 流的随机访问:理解流的标记管理和同步问题,确保在文件中正确定位和读写数据。

练习题解析

  1. 练习17.14:编写一个程序,使用ofstream向文件写入多行文本,然后使用ifstream从文件读取这些文本并打印到控制台。
    • 示例代码
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ofstream ofs("example.txt");
    if (!ofs) {
        std::cerr << "Failed to open file for writing." << std::endl;
        return 1;
    }

    // 写入多行文本
    ofs << "Line 1" << std::endl;
    ofs << "Line 2" << std::endl;
    ofs << "Line 3" << std::endl;
    ofs.close();

    std::ifstream ifs("example.txt");
    if (!ifs) {
        std::cerr << "Failed to open file for reading." << std::endl;
        return 1;
    }

    std::string line;
    while (std::getline(ifs, line)) {
        std::cout << line << std::endl;
    }
    ifs.close();

    return 0;
}
  1. 练习17.15:编写一个程序,使用stringstream对字符串进行格式化处理,并将处理后的字符串打印到控制台。
    • 示例代码
#include <iostream>
#include <sstream>
#include <string>

int main() {
    std::stringstream ss;
    ss << "Formatted number: " << 42 << ", text: " << "Hello";

    std::string result;
    std::getline(ss, result);

    std::cout << result << std::endl;  // 输出:Formatted number: 42, text: Hello

    return 0;
}
  1. 练习17.16:编写一个程序,演示如何使用流对象的状态检测成员函数来处理输入输出操作中的错误。
    • 示例代码
#include <iostream>
#include <fstream>

int main() {
    std::ifstream ifs("nonexistent.txt");
    if (!ifs) {
        std::cerr << "Failed to open file." << std::endl;
        return 1;
    }

    int value;
    ifs >> value;

    if (ifs.eof()) {
        std::cout << "End of file reached." << std::endl;
    } else if (ifs.fail()) {
        std::cerr << "Input failure (formatting or extraction error)." << std::endl;
    } else if (ifs.bad()) {
        std::cerr << "Non-recoverable error." << std::endl;
    }

    ifs.close();
    return 0;
}

总结与提高

本节总结

  1. 掌握了格式化输入与输出的控制方法,能够灵活地控制布尔值、整型值和浮点数的输出格式。
  2. 学会了未格式化的输入/输出操作,熟悉了单字节和多字节操作的使用方法。
  3. 了解了流的随机访问技术,掌握了seektell函数的使用方法,能够在文件中进行随机读写操作。

提高建议

  1. 多练习文件和字符串流操作:通过编写更多的文件和字符串流相关代码,熟悉各种IO操作的使用方法,掌握复杂的文件和字符串处理技巧。
  2. 深入理解IO状态检测:通过阅读相关文档和书籍,深入理解流对象的状态检测机制,确保在实际项目中能够正确处理IO操作中的各种错误。
  3. 优化代码设计:在实际项目中,合理使用IO库,提高代码的效率和可维护性,确保输入输出操作的可靠性。

本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“AI与编程之窗”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。 

  • 43
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI与编程之窗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值