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
对象t1
和t2
,并使用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;
}
在这个示例中,t1
和t2
的比较是逐元素进行的,首先比较第一个元素,如果第一个元素相等,再比较第二个元素,依此类推。
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;
}
重点与难点分析
重点:
- 定义和初始化
tuple
:理解如何定义和初始化tuple
对象,掌握std::make_tuple
的使用方法。 - 访问和修改
tuple
元素:掌握使用std::get
和std::tie
访问和修改tuple
元素的方法。 - 使用
tuple
返回多个值:理解如何通过tuple
返回多个值,并使用std::tie
或结构化绑定解包这些值。 tuple
的比较操作:了解tuple
的比较规则,理解逐元素比较的原理。- 结构化绑定:熟悉C++17引入的结构化绑定语法,掌握如何用结构化绑定从
tuple
中解包元素。
难点:
- 元素访问和修改:正确使用
std::get
函数访问和修改tuple
元素,避免类型错误和越界访问。 - 比较操作:理解
tuple
的逐元素比较规则,正确实现tuple
的比较操作。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 了解了
tuple
类型的定义和初始化方法,掌握了使用std::make_tuple
创建tuple
对象的技巧。 - 学会了使用
std::get
和std::tie
访问和修改tuple
元素,以及使用结构化绑定解包tuple
。 - 理解了
tuple
的比较操作规则,能够正确实现tuple
的相等比较和大小比较。 - 掌握了使用
tuple
返回多个值的方法,能够通过std::tie
或结构化绑定解包返回的tuple
。 - 学会了使用
std::apply
函数将tuple
展开为函数参数,从而灵活地操作tuple
中的数据。
提高建议:
- 多练习
tuple
的使用:通过编写更多的tuple
相关代码,熟悉tuple
的定义、初始化、访问和修改操作,掌握结构化绑定和比较操作的技巧。 - 深入理解比较规则:通过阅读相关文档和书籍,深入理解
tuple
的比较规则,避免在实际使用中出现比较错误。 - 优化代码设计:在实际项目中,合理使用
tuple
存储和处理异构数据,提高代码的可读性和可维护性。 - 结合其他标准库组件:学习和掌握
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
的各个位,或者使用test
、set
、reset
和flip
等成员函数。
示例代码
#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;
}
在这个示例中,我们使用数组下标运算符和test
、set
、reset
、flip
成员函数访问和修改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;
}
在这个示例中,我们使用bitset
的size
、count
、set
、reset
成员函数进行操作。
重点与难点分析
重点:
- 定义和初始化
bitset
:理解如何使用整数、字符串和默认值来定义和初始化bitset
对象。 - 访问和修改
bitset
元素:掌握使用数组下标运算符和test
、set
、reset
、flip
等成员函数访问和修改bitset
元素的方法。 bitset
的位运算:了解bitset
的按位与、按位或、按位异或等位运算操作。bitset
的其它操作:熟悉bitset
的size
、count
、set
、reset
等成员函数的使用。
难点:
- 位运算操作:正确使用
bitset
的按位与、按位或、按位异或操作,避免运算错误。 - 访问和修改元素:使用数组下标运算符和
test
、set
、reset
、flip
等成员函数时,注意位的索引范围和值的合法性。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习17.7:定义一个
bitset
,使用成员函数set
、reset
和flip
修改其中的位,并打印修改后的结果。
-
- 示例代码:
#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;
}
总结与提高
本节总结:
- 了解了
bitset
类型的定义和初始化方法,掌握了使用整数、字符串和默认值初始化bitset
对象的技巧。 - 学会了使用数组下标运算符和
test
、set
、reset
、flip
等成员函数访问和修改bitset
元素。 - 理解了
bitset
的位运算操作,包括按位与、按位或、按位异或等。 - 熟悉了
bitset
的其他操作,如获取位数、全部置位、全部复位等。
提高建议:
- 多练习
bitset
的使用:通过编写更多的bitset
相关代码,熟悉bitset
的定义、初始化、访问和修改操作,掌握位运算和其他操作的技巧。 - 深入理解位运算:通过阅读相关文档和书籍,深入理解
bitset
的位运算规则,避免在实际使用中出现运算错误。 - 优化代码设计:在实际项目中,合理使用
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_search
、std::regex_match
和std::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::smatch
和std::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]
匹配任意小写字母
重点与难点分析
重点:
- 定义和使用正则表达式:理解如何使用
std::regex
类定义正则表达式,并在字符串中搜索匹配项。 - 正则表达式匹配:掌握
std::regex_search
、std::regex_match
和std::regex_replace
的用法。 - 捕获组和迭代器:了解如何使用捕获组提取匹配到的子串,以及使用迭代器遍历所有匹配项。
- 常用正则表达式模式:熟悉常用的正则表达式模式,掌握它们在实际应用中的使用方法。
难点:
- 复杂的正则表达式模式:编写和调试复杂的正则表达式模式可能比较困难,需要反复测试和调整。
- 捕获组的使用:正确使用捕获组提取子串,确保提取到正确的匹配内容。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习17.10:编写一个程序,使用正则表达式提取并打印字符串中的所有电子邮件地址。
-
- 示例代码:
#include <iostream>
#include <regex>
int main() {
std::regex pattern("[\\w.-]+@[\\w.-]+\\.\\w+");
std::string test_str = "Contact us at [email protected] or [email protected]";
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;
}
总结与提高
本节总结:
- 了解了如何使用
std::regex
定义和使用正则表达式,掌握了基本的正则表达式匹配和替换方法。 - 学会了使用捕获组和迭代器提取匹配到的子串,以及遍历所有匹配项的方法。
- 熟悉了常用的正则表达式模式,能够编写和使用正则表达式解决实际问题。
提高建议:
- 多练习正则表达式的使用:通过编写更多的正则表达式相关代码,熟悉各种模式的定义和使用,掌握复杂模式的编写技巧。
- 深入理解捕获组和迭代器:通过阅读相关文档和书籍,深入理解捕获组和迭代器的用法,确保正确提取匹配内容。
- 优化代码设计:在实际项目中,合理使用正则表达式进行文本操作,提高代码的效率和可维护性。
17.4 随机数
C++11标准库引入了一个强大且灵活的随机数生成库,可以生成各种随机数序列。与传统的rand
函数相比,新的随机数库提供了更多的功能和更高的随机性。
17.4.1 随机数引擎
随机数引擎是随机数生成的核心。C++标准库提供了多种随机数引擎,如std::default_random_engine
、std::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的正态分布随机数。
重点与难点分析
重点:
- 随机数引擎:理解如何使用随机数引擎生成随机数,并掌握不同随机数引擎的特点。
- 随机数分布:了解各种随机数分布的使用方法,掌握生成不同类型随机数的技巧。
- 种子和播种:理解种子的作用,掌握如何使用种子生成不同的随机数序列。
- 生成浮点数和其他分布:学会生成浮点数随机数,并了解其他常用的随机数分布。
难点:
- 随机数的随机性:理解不同随机数引擎和分布的随机性特点,选择合适的引擎和分布生成所需的随机数。
- 种子的选择:正确选择和使用种子,确保生成的随机数序列满足需求。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 了解了随机数引擎的定义和使用方法,掌握了不同随机数引擎的特点。
- 学会了使用各种随机数分布生成不同类型的随机数,掌握了生成整数和浮点数随机数的技巧。
- 理解了种子的作用,掌握了使用种子生成不同随机数序列的方法。
- 熟悉了其他常用的随机数分布,如正态分布、泊松分布等,能够根据需要选择合适的分布生成随机数。
提高建议:
- 多练习随机数生成:通过编写更多的随机数相关代码,熟悉随机数引擎和分布的使用方法,掌握生成不同类型随机数的技巧。
- 深入理解随机性:通过阅读相关文档和书籍,深入理解不同随机数引擎和分布的随机性特点,选择合适的引擎和分布生成所需的随机数。
- 优化代码设计:在实际项目中,合理使用随机数生成库,提高代码的效率和可维护性。
17.5 IO库的再探讨
C++标准库的输入输出库(IO库)提供了丰富的功能和灵活的接口,用于处理文件、字符串和控制台的输入输出操作。在这一节中,我们将深入探讨IO库的一些高级特性和使用技巧。
17.5.1 格式化输入与输出
格式化输入与输出是控制数据表示方式的关键。C++提供了多种控制输出格式的方法。
控制布尔值的格式
默认情况下,布尔值会以0
或1
的形式输出。可以使用std::boolalpha
和std::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::dec
、std::oct
和std::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::showbase
和std::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::fixed
、std::scientific
和std::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::fixed
和std::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::showpoint
和std::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::skipws
和std::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 未格式化的输入/输出操作
未格式化的输入/输出操作提供了更底层的控制,用于处理单字节和多字节的输入输出操作。
单字节操作
可以使用get
和put
成员函数进行单字节输入输出操作。
#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;
}
多字节操作
可以使用read
和write
函数进行多字节输入输出操作。
#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 流随机访问
流随机访问允许我们在文件中跳转到特定位置进行读写操作。可以使用seek
和tell
函数实现这一功能。
seek和tell函数
seekg
和tellg
用于输入流的定位和获取位置,seekp
和tellp
用于输出流。
#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;
}
访问标记
可以使用tellg
和tellp
获取当前的读写位置。
#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;
}
重点与难点分析
重点:
- 格式化输入与输出:掌握如何使用各种操作符控制输出格式,包括布尔值、整型值的进制、浮点数的格式和精度等。
- 未格式化的输入/输出操作:了解如何使用单字节和多字节操作进行未格式化的输入输出操作。
- 流随机访问:熟悉如何使用
seek
和tell
函数实现流的随机访问,掌握读写同一个文件的技巧。
难点:
- 复杂的格式化控制:正确使用各种格式化控制操作符,避免输出格式错误。
- 流的随机访问:理解流的标记管理和同步问题,确保在文件中正确定位和读写数据。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 掌握了格式化输入与输出的控制方法,能够灵活地控制布尔值、整型值和浮点数的输出格式。
- 学会了未格式化的输入/输出操作,熟悉了单字节和多字节操作的使用方法。
- 了解了流的随机访问技术,掌握了
seek
和tell
函数的使用方法,能够在文件中进行随机读写操作。
提高建议:
- 多练习文件和字符串流操作:通过编写更多的文件和字符串流相关代码,熟悉各种IO操作的使用方法,掌握复杂的文件和字符串处理技巧。
- 深入理解IO状态检测:通过阅读相关文档和书籍,深入理解流对象的状态检测机制,确保在实际项目中能够正确处理IO操作中的各种错误。
- 优化代码设计:在实际项目中,合理使用IO库,提高代码的效率和可维护性,确保输入输出操作的可靠性。
标签:std,cout,17,tuple,int,C++,导学,随机数,include From: https://blog.csdn.net/iShare_Carlos/article/details/140190931本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。