1).标准库的附加特性,
2).我们使用的标准库的名字,实际上都是使用名字为std的命名空间中的名字。
1).tuple
类似于pair
成员。不同tuple
类型的成员类型也不同,但一个tuple
可以由任意数量的成员。每一个确定的tuple
类型的成员数量也是固定的。
tuple
类型和它的伴随类,函数都定义在头文件tuple
中。1).
{
// 当我们定义一个tuple时,需要指出每一个成员的类型
tuple<size_t, size_t, size_t> threeD;//三个成员都为0
tuple<string, vector<double>, int, list<int>> someval("contents", {1, 3, 4}, 34, {1, 2, 3});
// 构建一个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_pair函数,标准库定义了make_tuple函数。
auto item = make_tuple("isbn", 2, 23.00);
// make_tuple就是一个函数模板
//并且它通过推断实参类型,得到tuple的类型
// tuple
}
2).访问tuple的成员
tuple
的成员都是未命名的,使用名为get
的标准库 函数模板。{
// 使用get
auto book = get<0>(item);//返回item的第一个成员
auto = cnt = get<1>(item);//返回item的第二个成员
// 我们必须指定一个显式模板实参,指定我们要访问第几个成员,
// 必须是一个整型常量表达式,从0开始计数。
// 并且传递一个tuple对象。
// 函数返回的是指定成员的引用。
get<2>(item) *= .8;//打八折
// 如果不知道一个tuple准确的类型细节信息,可以使用两个辅助类模板来查询tuple的成员的数量和类型
typedef decltype(item) trans;//trans 是item的类型
size_t sz = tuple_size<trans>::value;//返回的是tuple的成员的数量
tuple_element<1, trans>::type cnt = get<1>(item);//cnt是一个int类型
// 使用之前需要知道一个tuple对象的类型,
// 然后对两个类模板进行实例化。
// 取它的value和type成员即可。
// 其中,value是一个public static
// type是一个public成员,
// 类似于get,tuple_element的索引下标也是从0开始。
}
3).关系和相等运算符
tuple
的比较运算符,比较左右两个tuple
里的成员,这一点和容器对应;但是只有两个tuple中的成员数量一样时,我们才可以比较它们。并且比较时,我们需要保证对于每一个成员相应的比较运算是有定义的。{
tuple<string, string> dou("1", "2");
tuple<size_t, size_t> twoD(1, 2);
bool b = (dou == twoD);//错误,不能比较size_t和string
tuple<size_t, size_t, size_t> threeD(1, 2, 3);
b = (twoD < twoD);//错误,成员数量不一致
tuple<size_t, size_t> origin(0, 0);
b = (origin < twoD);//正确,b为true
// 由于在tuple中定义了==和<运算,所以可以将tuple传递给排序算法,可以在无序容器中作为关键字类型。
}
1).
{
// 每一个元素代表一家店的销售记录
vector<vector<Sales_data>> files;
// 对于一本书,我们在整个files中搜索出售过这本书的书店,对于每一个加与匹配的销售记录的书店,我们将创建一个tuple
// 来保存这加书店的索引和两个迭代器。
// 索引指出书店在files中的位置,迭代器标记给定书籍在此书店vector的起止位置。
// 返回tuple的函数
typedef tuple<vector<Sales_data>::size_type, vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator
> matches;
// findBook返回一个vector,每一个销售了给定书籍的店都有一项
vector<matches>
findBook(const vector<vector<Sales_data>> &files,
const string &book) {
vector<matches> ret;//初始化为空的vector
// const迭代器,是底层的const
for (auto it = files.cbegin(); it != files.cend(); ++it) {
auto found = equal_range(it->cbegin(), it->cend(),
book, compareIsbn);
if (found.first != found.second) //此书店销售了给定的书籍
ret.push_back(make_tuple(it - files.cbegin(), found.first, found.secod);
}
return ret;
}
// 默认情况下,equal_range是使用<进行比较,我们使用compareIsbn可调用对象。
}
3).对返回的结果进行处理
{
void reportResult(istream &is, ostream &os, const vector<vector<Sales_data>> &files) {
string s;//需要查找的书籍
while (is >> s) {
auto trans = findBook(files, s);
if (trans.empty()) {
cont << s << "not found in ant stores" << endl;
continue;
}
for (const auto &store : trans) {
os << "store: " << getr<0>(store) << "sales: " << accumulate(get<1>(store), get<2>(store), Sales_data(s)) << endl;
}
}
}
}
1).bitset
类可以处理超过最长整型类型大小的位集合。而且位运算也变得更加肉容易。bitset
类定义在头文件bitset
中。
1).bitset
,是一个类模板,它类似array
类,具有固定的大小。当我们定义一个bitset
时,需要声明它包含多少个二进制位。
{
bitset<32> bitvec(1U);//32位,最低位为1,其他位为0;
// 这句语句定义bitset为一个包含32位
// 的bitset。
// 就像vector中的包含未命名元素一样
// bitset包含的二进制位也是命名的。我们使用位置来进行访问
// 二进制的位也是从0开始编号的
// 因此,编号是从0-31。
// 编号从0开始的二进制位被称为低位
// 编号到31号结束的二进制位被称为高位。
}
2).初始化方式表见p641表格。
unsigned
值初始化bitset
{
// 当我们使用一个整型值来初始化bitset时,此值被转换为unsigned long long类型,并且被当作位模式来处理
// bitset的二进制位将是此模式的一个副本
// 如果bitset的大小等于一个unsigned long long 中的二进制位数,则剩余的高位被置为零
// 如果bitset的大小小于一个unsigned long long中的二进制位数。
// 则只是用给定值中的低位,超出的部分就被丢弃。
bitset<13> bitvec1(0xbeef);
bitset<20> bitvec2(0xbeff);
bitset<128> bitvec3(~0ULL);//0-63位为1,其余的为0;
// 在64位的及其中,long long 0ULL是64个bit。
}
string
来初始化bitset
{
// 我们可以使用一个string或者一个字符数组指针来初始化bitset
// 两种情况下,字符都直接表示位模式
// 当我们使用字符串表示数时,字符串中下标最小的字符对应最高位
bitset<32> bitvec4("1100");//表示的二进制位为,0011,其余的位置是0
// 这是因为如果string中包含的字符数比bitset少,则bitset的高位置为0
// 使用字串来初始化一个bitset
string str("10010010010010010101");
bitset<32> bitvec5(str, 5, 4);//从str[5]开始的4位二进制位,1100。
// 结果同上
bitset<32> bitvec6(str, str.size() - 4);//使用最后4个字符
// 初始化和以上的方式一样。
// 低位赋值给高位,
// 是平移不是翻转。
}
练习,
invalid_argument
的异常。1).有多种检测或者设置位的方法。也支持位运算,并且含义也是与我们将这些运算符作用于unsigned
的含义是相同的。。
{
// count, size, all, none,等不接受参数,返回的是整个bitset的状态。
// set, reset, flip, 改变bitset的状态。
// 改变bitset的状态的成员函数都是重载的
// 对于每一个函数,不接受参数的版本是对整个集合执行给定的操作,
// 接受一个位置参数的版本则对指定位置进行操作
bitset<32> bitvec(1U);//低位为1,其他为0
bool is_set = bitvec.any();//返回true,因为有一位置位
boll 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.flip();//翻转所有的位
bitvec.reset();//所有位复位
bitvec.set();//所有位置位
// 置位就是等于1。
// size操作时一个constexpr操作,允许我们使用在任何要求常量表达式的地方。
// 可以接受参数的重载版本
bitvec.flip(0);//翻转第一位
bitvec.set(bitvec.size() - 1);//将最后一位置位
b.set(0, 0);//复位最后一位
b.reset(i);//复位第i位
b.test(0);//返回false,因为第一位时复位的。
// 下标运算对const属性进行了重载
// const版本在指定位置位时返回true,
// 否则返回false
// 非const版本,返回bitset定义的一个特殊类型,允许我们操作指定位的值
b[0] = 0;//将第一位复位
b[31] = b[0];
b[0].flip();//反转第一位
~b[0];//翻转第一位
bool a = b[0];//转为bool值。
}
2).提取bitset
的值
{
// to_ulong, to_ullong操作都返回一个值,保存了于bitset对象相同的位模式
//只有当bitset的大小等于对应的大小时,才能使用这两个操作
unsigned long ulong = bitvec3.to_ulong();
// 如果bitset中的值不能放入给定的类型时,两个两个操作会抛出一个overflow_error的异常。
}
3).bitset
的IO运算符
{
// 输入运算从一个输入流中读取字符,保存在一个临时的string对象中。
// 直到读取的字符数达到对应的bitset大小时或者遇到不是1或者0的字符时,或者遇到文件尾或者遇到错误时,读取程序才会结束
// 随即用临时的string对象来初始化bitset。
// 此时的规则就和用string来初始化bitset是一样的
bitset<16> bits;
cin >> bits;
cout << "bits: " << bits << endl; //这里输出的是string,也就是刚刚读入的string
}
4).使用bitset
{
// 实现评分程序
bool status;
unsigned long quizA = 0;//此值将被当作位集合来使用
quizA |= 1UL << 27; //指出第27个学生通过了测验
status = quizA & (1UL << 27);//检查第二十七位学生是否通过了测验
quizA &= ~(1UL << 27);//第二十七位学生没有通过测验
// 使用标准库bitset进行等价的操作
bitset<30> quizB;
quizB.set(27);
status = quizB[27];
quizB.reset(27);
}
练习,
1).重点介绍如何使用。RE库(正则表达式库)定义在头文件regex
中。包含的组件见p645。
2).regex
可以做一些什么。
regex
类表示一个正则表达式。除了赋值和初始化之外,还支持一些其他的操作。见p647。regex_match,regex_search
确定一个给定的字符序列与一个给定的regex
是否匹配。如果整个输入序列和表达式匹配,则regex_match
返回true
,如果输入序列中的一个字串与表达式匹配,则regex_search
函数返回true
。3).p646列出了,regex
的函数的参数。这些函数都是返回bool
,。而且被重载了。其中一个版本接受类型为smatch
的附加参数。如果匹配成功,这些函数将成功匹配的相关信息保存在给定的smatch
对象中。
1).从简单的例子开始。
{
// 查找违反拼写规则“i除非在c之后,否则必须在e之前”的单词。
// 查找不在字符c之后的字符串ei
string pattern("[^c]ei");
// 我们需要包含pattern的整个单词
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern);//构造一个用于查找模式的regex
smatch results;//定义一个对象保存搜索的结果
string test_str = "receipt freind theif receive";
// 用r在test_str中查找与pattern匹配的子串
if (regex_search(test_str, results, r)) //如果有匹配的子串
cout << results.str() << endl;//打印匹配的单词
//[^c]表示匹配任意非c的字符,
// [^c]ei,表示三个字符,第一个不是c后面两个是ei
// 如果我们想要得到完整的单词,
// 由于regex使用的正则表达式语言是ECMAScript。在ECMAScript中,模式[[:alpha:]]表示任意的字母
// 符号+表示1个或者多个字符
// 符号*表示0个或者多个字符。
// 我们将正则表达式存入在string中后,
// 用来初始化一个名字为r的regex对象。
// 如果regex_search函数匹配到字串,就会返回true。使用smatch对象results中的str成员来打印,模式匹配的部分。
// 由于函数regex_search在输入序列中只要找到一个匹配的字串就会返回,停止查找,所以
// 输出结果就是freind
}
2).指定regex
对象的选项见表格p647
regex
或者对一个regex
使用assign
赋新值的时候,可以指定一些标志来影响regex
如何操作。这些标志控制regex
对象的处理过程。详见p647。ECMAScript
标志被设置。从而,regex
会使用ECMA-262规范。这也是很多Web
浏览器所使用的正则表达式语言。{
// 使用icase标志查找具有特定扩展名称的文件
// 大多数系统都是以大小写无关的方式来识别扩展名的
// 例如c++程序的扩展名可以是cc, Cc, cC, CC等。效果是一样的
// 一个或者多个字母或者数字后面加上一个.
// 再接上cpp或者cxx或者cc
regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$", regex::icase);
smatch results;
string filename;
while (cin >> filename) {
if (regex_search(filename, results, r)) {
cout << results.str() << endl;//打印匹配的结果。
}
}
// 此时正则表达式会匹配指定文件的扩展名而不会理会大小写
// 在正则表达式中,.表示匹配任意字符
// 在其前面加上一个\去掉其特殊含义
// 由于反斜杠也是一个特殊字符,所以
// 得到一个.需要这样做\\.
}
3).我们可以将正则表达式本身看作是,用一种简单程序设计语言编写的“程序”。这种语言不是由c++编译器解释的。正则表达式是在运行时,当一个regex
对象被初始化或者被赋予一个新模式时,才被“编译”的。与任何其他程序设计语言一样,我们用这种语言编写的正则表达式也可能会有错误。需要意识到的是,一个正则表达式的语法是否正确是在运行时才解析的。
regex_error
的异常。regex_error
有一个what
操作描述发生了什么错误;还有一个名为code
的成员,返回某一个错误类型对应的数值编码;它返回的值是由具体实现定义的。RE库能抛出的标准错误,见表17.7(p649){
try {
// 漏掉一个方括号的错误
regex r("[[:alnum:]+\\.(cpp|cxx|cc)&", regex::icase);
} catch (regex_error e) {
cout << e.what() << "\ncode :" << e.code << endl;
}
// 生成结果就是,
regex_error(error_brack)
The expression contained mismatched [ and ].
code: 4
// 我们的编译器定义了code成员,返回的是表17.7的错误类型的编号,
// 一样的编号从0开始
// 例如以上的错误error_brack
// 就是第五个错误类型,编号为4.
// 正则表达式的编译时一个非常慢的操作,特别时你是哟共了扩展的正则表达式语法
// 或者是复杂的正则表达式时
// 因此,构造一个regex对象以及对一个已存在的regex赋予一个新的正则表达式可能是非常耗时间的
// 为了最小化这种开销,避免创建不必要的regex
// 例如,当我们需要在循环里面使用正则表达式时,应该在循环体外面创建它
// 而不是在循环体内创建
}
4).正则表达式类型和输入序列类型
char
数据,也可以是wchar_t
数据string
,也可以是在char
数组中。(宽字符版本,wstring
,wchar_t
数组中。)regex
类保存类型char
的正则表达式;wregex
类保存类型wchar_t
的正则表达式,它的操作和regex
完全相同。唯一的差别是wregex
的初始值必须是哟共wchar_t
而不是char
。smatch
表示string
类型的输入序列;cmatch
表示字符数组的输入序列;wsmatch
表示宽字符串wstring
的输入序列;wcmatch
表示宽字符数组的输入序列。{
// 如果不匹配
regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$", regex::icase);
smatch results; //匹配的是string的序列
if (regex_reach("myfile.cc", results, r));//错误,待匹配的序列是一个const char *
//将上述的smatch 改为cmatch即可。
}
1).我们可以使用sregex_iterator
迭代器类获取所有的匹配。
regex
迭代器是一种迭代器适配器,被绑定到一个输入序列和一个regex
对象上。迭代器的操作见表17.9(p651)sregex_iterator
绑定到一个string
序列和一个regex
对象上时,迭代器自动定位到string
中的第一个匹配的位置。即,sregex_iterator
的构造函数自动对给定的string
和regex
调用regex_search
。当我们解引用迭代器时,会得到一个对应一次所有结果的samtch
对象,当我们递增迭代器时,regex_search
会输入序列string
中查找下一个匹配。2).使用sregex_iterator
。
{
string pattern("[^c]ei");
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase);
for (sregex_iterator it(file.begin(), file.end(), r), end_it;
it != end_it; ++it) {
cout << it->str() << endl;//输出匹配的单词。
}
// 注意end_it是一个空的sregex_iterator;起到尾后迭代器的作用。
// 如果我们还想要匹配结果的上下文信息
// 见表17.10,17.11
// smatch和ssub_match类型允许我们获得匹配的上下文信息。
// 匹配类型,有两个名为prefix和suffix的成员。调用后分别返回表示输入序列中当前匹配之前和之后的部分的ssub_match对象
// 一个ssub_match对象有两个名为str和length的成员,分别 返回 匹配的string和该string的大小
for (sregex_iterator it(file.begin(), file.end(), r), end_it);
it != end_it; ++it) {
auto pos = it->prefix().length(); //前缀的大小
pos = pos > 40 ? pos - 40 : 0; //最多只要40个字符
// 从0开始的位置。
cout << it->prefix().str().substr(pos)
<< "\n\t\t>>> " << it->str() << " <<<\n"
<< it->suffix().str().substr(0, 40)
<< endl;
}
}
1).正则表达式中的模式通常包含一个或者多个子表达式。一个子表达式是模式的一部分,本身也具有意义。正则表达式语法通常用括号表示子表达式。
{
// 模式中点之前的文件名也形成子表达式
regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)$",regex::icase);
// 有两个子表达式,
// 改写程序使之输出语句值打印文件名
if (regex_search(filename, results, r))
cout << results.str(1) << endl; //打印第一个子表达式
// 匹配对象不仅仅提供匹配整体的信息之外,还提供访问 模式中子表达式的能力
// 子匹配是按照位置来匹配的。第一个子匹配的位置为0,表示整个模式对应的匹配;
// 随后是每一个子表达式对应的匹配。
// 默认情况下就是0。
}
2).子表达式用于,数据验证
{
// 美国的电话号码验证
// 首先将用一个正则表达式找到可能是电话号码的序列
// 再调用一个函数完成数据的验证
// 首先了解ECMAScript正则表达式语言的一些特性
//1. \{d}表示单一个数字,\{d}{n}表示一个n个数字的序列(\{d}{3}表示匹配三个数字的序列)
//2. 在方括号中的字符集合表示匹配这些字符中的任意一个。([-. ]匹配一个短横或者一个点
// , 或者一个空格 注,.在方括号里面没有特殊含义)
//3. 后接'?'的组件时可选的。
// \{d}{3}[-. ]?\{d}{4}
//可以匹配444-9088/.0989/ 0989/9069。/表示或者,不是语言特性,笔者偷懒而已。
//4. 类似于c++,ECMAScript使用反斜杠\表示没有特殊含义。
// \(\)才表示括号,而不是特殊的字符
// 由于反斜线是c++的特殊字符,在模式中使用\的地方,我们都必须用一个额外的\来告诉c++,我们使用的是一个\而不是特殊符号
// \\{d}{3},来表示\{d}{3}这一正则表达式
// 验证手机号码,我们需要得到模式的组成部分。
// 因为我们不希望的到只有一个括号的情况
// 为了获得匹配的组成部分,我们需要定义正则表达式时使用子表达式。
"(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})";
// 其中\\d{3}和\\{d}{3}含义相同
// ([-. ]?)和([-. ])?含义也是一样的
string phone = ...;
regex r(phone);
smatch results;
string s;
while (getline(cin, s)) {
for (sregex_iterator it(s.begin(), s.end(), r), end_it;
it != end_it; ++it) {
if (valid(*it))
cout << "valid: " << it->str() << endl;
else
cout << "not valid: " << it->str() << endl;
}
}
// 使用子匹配的操作来编写valid函数
// pattern有7个子表达式
// smatch对象会有8个ssub_match元素
// smatch[0]表示整个匹配的表达式,
// 当调用valid时,我们知道一定有一个完整的匹配。
// 如果子表达式是完整匹配的一部分,则其对应的ssub_match对象的matched成员是true
// 主要检查是否是完整的括号或者是没有括号
bool valid(const smatch &m) {
// 如果区号左边的括号是存在的
if (m[1].matched) {
// 则区号后面必须有一个右括号
// 之后紧跟剩余的号码或者一个空格
return m[3].matched &&
(m[4].matched == 0 || m[4].str() == " ")
else {
// 区号后面的不能有括号
// 另外两个组成部分间的分隔符必须匹配,即使用的分隔符要相同。
return !m[3].matched &&
m[4].str() == m[6].str();
}
}
}
}
练习
(\s)
还是(\s)*
1).用来查找并且替换一个序列的时候。详见p657(表17.12)。它接受一个输入字符序列和一个regex
对象,以及我们想要的输出形式的字符串。
{
// 使用第二个,第五个,第七个子表达式
// 而忽略其他的子表达式
// 我们使用$后跟子表达式的索引号来表示一个特定的子表达式。
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
}
2).只是替换输入序列中的一部分
{
// 可以用在一个很大的文本中
// 处理电话号码的格式修改
int main() {
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\d{3})([-. ])?(\d{4})";
regex r(phone);
string s;
smatch m;
string fmt = "$2.$5.$7";//用来修改格式
while (getline(cin, s)) {
cout << regex_replace(s, r, fmt) << endl;
}
return 0;
}
}
3).用来控制匹配和格式的标志
regex
对象匹配过程的标志一样。替换过程中有相似的控制匹配和格式的控制。(这些都是标准库定义的)。详见表格17.13。regex_search,regex_match或者类smatch的format成员
match_flag_type
。这些值均定义在regex_constants
命名空间中。与bind
的placeholders
,regex_constants
也是在命名空间std
中的命名空间。using std::regex_constants::format_no_copy;
{
// 使用格式标志
// 默认情况下regex_replace会将输入序列全部输出
// 没有匹配的会原样输出,
// 匹配的按格式字符串指定的格式输出
string fmt2 = "$2.$5.$7 ";//电话号码后面放置一个空格符来作为分隔符
cout << regex_replace(s, r, fmt2, format_no_copy) << endl;
// 只输出它所改变的文本
}
1).在新标准之前,c和c++都依赖于一个简单的c库函数rand
。此函数生成均匀分布的伪随机数,范围在0-32767之间(系统相关的最大值)。
random
的随机数库通过一组协作的类来解决这些问题,unsigned
整数序列,范围内的每一个数被生成的概率是相同的。rand
,应该使用default_random_engine
类和恰当的分布类对象。1).随机数 引擎 是一个函数对象类。定义了一个调用运算符函数。
unsigned
整数。{
default_random_engine e;
for (size_t i = 0; i < 10; ++i)
cout << e() << " ";
// 标准库定义了多个引擎类,区别在于性能和随机性质量不同。
// 每一个编译器都会指定其中一个作为default_random_engine类型
// 该类型一般具有最常用的特性。
// 标准库定义的引擎类见p783
// 引擎类的操作见p660
// 我们把以上称为原始随机数
// 因为大多数情况下,以上的输出不能直接使用
// 问题在于范围与我们的所需要的是不符合的,而且转换是困难的
}
2).分布类型和引擎
{
// 使用分布类型对象得到指定范围的随机数
// 生成0-9之间的(包含)均匀分布的随机数
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
for (size_t i = 0; i < 10; ++i) {
cout << u(e) << " ";
}
// uniform_int_distribution类型生成 均匀分布 的unsigned值。
// 当我们定义该类型的对象时。
// 可以提供想要的最大和最小值(包含)
}
{
// 注意我们传递的是一个引擎对象,而不是它的一个随机数值
u(e);//正确
u(e());//编译错误
// 原因在于,某一些分布可能需要调用引擎多次才可以得到一个值
}
3).比较随机数引擎和rand
函数
比较类型 | rand | 引擎对象 |
---|---|---|
生成数的范围 | 在0-RAND_MAX之间 | 它生成的unsigned在一个系统定义的范围内,可以调用该类型对象的min() ,max() 返回值来得到。依赖于系统。 |
4).引擎生成一个数值序列
{
// 对于一个给定的发生器,每一次运行它都会返回相同的数值序列
// 序列不变这一事实可以用来调式
// 使用时也需要注意这一点
vector<unsigned> bad_randVec() {
default_random_engine 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
// 编写此函数的正确方法是,将引擎和关联的分布对象定义为static
// 因为我们希望引擎和分布对象保留状态,因此我们需要把它们定义为static
// 从而每一次调用都生成新的数
// 这样第一次调用生成前100个随机数,
// 第二次调用哦生成接下来的100个数
// 以此类推
}
5).设置随机数发生器种子
seed
。种子就是一个数值, 引擎 利用它从序列中的一个新位置重新开始生成随机数。seed
seed
成员{
default_random_engine e1;//使用默认的种子
default_random_engine e2(234235242);//使用给定的种子值
default_random_engine e3; //使用默认的种子值
e3.seed(32767); //设置一个新的种子值
default_random_engine e4(32767);
//d3和e4将会生成相同的随机数值
// 因为它们的种子是一样的
// 选择一个好的种子是及其困难的。
// 最常用方式就是调用系统函数time
// 该函数定义在头文件ctime中
// 返回从一个特定时刻到当前经过了多少秒
// 函数接受单个指针参数
// 它指向用于写入时间的数据结构
// 指针为空,函数简单地返回时间
default_random_engine e(time(0));
// 由于time是以秒计时的,这种方式生成的种子适合间隔以秒为级别的或更长的应用。
// 如果程序是需要反复进行,time作为种子的方式可能导致多次使用的都是同一个种子
}
1).解决不同分布和不同类型的问题(随机引擎只是生成均匀分布的unsigned
)。标准库定义了不同的随机数分布类来满足这个需求。
2).生成随机实数
rand
获得一个随机浮点数的方法是使用rand()/RAND_MAX
。不正确的原因是随机整数的精度通常低于随机浮点数,这样一些浮点值就永远不会生成。{
// 定义一个uniform_real_distribution 对象
// 让标准库从随机整数到随机浮点数的映射。
// 我们同样可以指定范围。
default_random_engine e;
uniform_real_distribution<double> d(0, 1);
// 使用
u(e);
// 分布类型都是模板。具有单一的模板类型参数,表示分布生成的随机数的类型
// 这些分布类型要么生成整型要么生成浮点类型
// 每一个分布模板都有一个默认模板实参,生成浮点型的分布类型默认生成的是double;
// 生成整型值的分布默认是int。
// 由于分布类型只有一个模板参数,因此当我们希望使用默认随机数类型时,要在后面加上<>
uniform_real_distribution<> u(0, 1);//默认生成的是double
// ------生成非均匀的随机数----------
// 除了可以指定范围,类型,还可以指定分布。
// 20种的分布类型,见p781。
// 生成正态分布的值的序列,并画出值得分布
default_random_engine e;
normal_distribution<> n(4, 1.5);//均值4,标准差1.5
vector<unsigned> vals(9);//均为0
for (size_t i = 0; i != 200; ++i) {
unsigned v = lround(n(e));//舍入到最接近的整数
if (v < val.size())
++vals[v]; //统计出现的次数
}
for (size_t i = 0; i != vals.size(); ++i) {
cout << i << ": " << string(vals[i], "*") << endl;
}
//normal_distribution生成浮点值
// 头文件的cmath中的lround函数。得到最接近的整数
// 以4为均值,表示以4为中心
// 由于是正态分布,我们希望99%的数都在0-8之间。
// 先进行统计次数。
// 打印一个星号组成的图,来表示随机分布
// -----------bernoulli_distribution(伯努利随机分布)
// 该类是一个普通类。此分布总是返回一个bool,它返回true的概率是一个常数,默认是0.5
// 编写谁先行的程序
// 1.可以使用uniform_int_distribution来选择谁先行,范围为0-1即可
//2. 使用bernoulli随机分布。
string resp;
default_random_engine e; //需要保持状态,在函数外围定义。
bernoulli_distribution b; //默认是50/50的概率
// 分布对象也需要保持状态
// bernoulli_distribution b(.55);//表示有.55的概率得到true
do {
bool first = b(e);
cout << (first ? "We go first" : "You get to go first") << endl;
// 传递谁先进行游戏
cout << ((play(first)) ? "sorry you lost" : "congrats, you won") << endl;
cout << "play again ? Enter 'yes' or 'no'" << endl;
} while (cin >> resp && resp[0] == 'y');
// 如果随机数引擎和分布定义在里面
// 每一次得到的随机数一样的,游戏的先行者是固定的
}
1).除了条件状态之外,每一个iostream
对象还维护一个格式状态来控制IO如何格式化的细节。格式状态控制格式化的某一些方面,
2).标准库定义了一组操作符,来修改流的格式状态。见表17.17(p670)、17.8。一个操作符是一个函数或者一个对象。它们可以用作输入或输出运算符的运算对象,也返回流的对像。因此我们可以在一条语句中组合操纵符和数据。
3).操纵符用于两大类输出控制。
4).应用
{
cout << "default bool value: " << true << " " << false
<< "\nalpha bool value: " <<
boolalpha <<
true << " " << false
<< endl;
// 输出结果就是1 0 true false
// 使用了boolalpha来覆盖默认的格式
// 将它复原
cout << boolalpha << true << noboolalpha ;
}
{
// 默认情况下就是十进制
cout << "default:" << 20 << " " << 1024 << endl;
cout << "octal(8):" << otc << 20 << 1024 << endl;
cout << "hex(16):" << hex << 20 << 1024 << endl;
cout << "decimal(10):" << dec << 20 << 1024 << ednl;
// 输出
20 1024
24 2000
14 400
20 1024
// 注意可以进行覆盖。otc之后hex或者dec
// 以上的操纵符只对整型影响,对于浮点值的表示形式没有影响
// --------在输出中指出进制
// 解决我们不知道是几进制的问题
// 显式进制
// 显式的规范和我们在整型常量中指定进制的规范一样
cout << showbase; //打印整型值时显式进制
...
cout << noshowbase;
// 输出
20 1024
024 02000
0x14 0x400
20 1024
// 默认情况下,十六进制值会以小写打印,0x也是小写的,
// 我们可以使用uppercase操作符号来输出大写的X并将十六进制数字也将大写的放hi输出
cout << uppercase << showbase << hex << .....
<< nouppercase << noshowcase << dec << endl;
// 输出
0X14 0X400
}
{
// -----------指定打印精度
// 默认情况下打印的是6位数字
// -----------指定是否打印小数点
// 如果浮点值没有小数部分,不打印小数点
// -----------指定格式(十六进制如何?)
// 根据浮点数的值选择打印成定点十进制或者科学计数法的形式。
// 标准库会选择一种可读性好的格式;非常大或者非常小的值打印为科学计数法的形式,其他打印成十进制定点的形式
// -----------指定打印的精度
// 默认情况下,精度会控制打印的数字总数;打印时,浮点值按当前精度舍入而不是截断
// 例如精度为4,则3.14159将打印为3.142
// 如果精度为3,将打印为3.14
// 使用IO对象的precision成员或者
// setprecision操纵符来改变精度
// precision成员是重载的,一个版本接受int,将精度设为该值,并返回旧精度
// 另一个版本不接受参数,返回当亲啊的精度
// setprecision操纵符接受一个参数,用来设置精度。
// 操纵符setprecision和其他接收参数的操纵符都定义在头文件iomanip中
cout << "Precision: " << cout.precision() << ",value:" << sqrt(2.0) << endl;
// 将精度设置为12
cout.precision(12);
cout << "..." << ....
cout << setprecision(3);
.....
// 输出
6,1.41421
12, 1.41421356237
3, 1.41
// sqrt标准库函数,定义在头文件cmath中。
// sqrt是重载的,分别接受一个float,double,long double参数。返回实参的平法根。
//--------------指定浮点数记数法
// 使用操纵符scientific改变流的状态使用科学计数法
// 使用操纵符fixed改变流的状态使用定点十进制
// 新标准库中,允许使用hexfloat强制浮点数使用十六进制格式
// 新标准中,还提供一种名为defaultfloat的操纵符,它将流恢复到默认状态--根据要打印的值选择计数法
// 这些操纵符也会改变精度的默认含义
// 在执行scientific,fixed或者hexfloat时
// 精度值控制的是小数点后面的数字位数
cout << 100 * sqrt(2.0) << '\n'
<< scientific
<< fixed
<< hexfloat
<< defaultfloat
<< endl;
// 输出
141.421
1.414214e+002
141.421356
0x1.ad7bcp+7
141.421
// 同理我们可以使用uppercase将小写改成大写
//--------------打印小数点
cout << 10.0 << endl;//10
cout << showpoint << 10.0
<< noshowpoint << endl;/10.000000
}
{
// 1、setw指定下一个数字或字符串值得最小空间
// 2、left表示左对齐输出
// 3、right表示右对齐输出,这是默认格式
// 4、internal控制负数的符号的位置,它是左对齐符号,右对齐值,控制填满之间的空间
// 5、setfill允许指定一个字符来代替默认的空客来补白输出
// setw不改变流的内部状态,它只是改变下一个输出的大小。
int i = -16;
double d = 3.14159;
....
cout << internal << left << right ....
cout << sefill('#') << setw(12) << d << endl;
cout << setw(12) << i << endl;
cout << setfill(' ');
// 输出
// 右对齐
#########-16
#####3.14159
// 左对齐
-16#########
3.14159#####
// internal
-#########16
#####3.14159
// 注意,小数点也是一个位
// 操纵符号也是只对特定的类型起作用,对其他类型没有影响
}
{
// 默认情况下输入运算符会忽略空白,制表,换行,换纸,回车符号
char ch;
while (cin >> ch) {
cout << ch;
}
// 输入
a b c
d
// 输出
abcd
// 循环一共执行了四次
// 操纵符noskipws会零输入运算读物空白符,而不是跳过
cin >> noskipws;
while (cin >> ch)
cout << ch;
cin >> skipws;//恢复默认的状态
// 此循环会执行7次。将普通字符和空白字符均读取
// 输出
a b c
d
}
1).到目前为止我们只使用过格式化IO操作。
2).标准库还提供了一组底层操作,支持未格式化IO。这些操作允许我们将一个流当作一个无解释的字节序列来处理。
3).单字节操作
{
char ch;
while (cin.get(ch))
cout,put(ch);
// 该程序保留输入中的空白,输出和输入完全一致,它的执行过程和前一个使用noskipws完全相同
}
4).将字符放回输入流
peek
返回输入流中下一个字符的副本,但是不会将他从流中删除。unget
使得输入流向后移动,从而最后读取的值又回到流中。即使我们不知道最后从流中读取什么值,仍然可以调用unget
putback
,是更加特殊的unget
,它退回流中读取的最后一个值,但它必须接受一个参数,此参数必须和最后读取的值相同。putback
或者unget
。5).从输入操作返回的int值
peek
和无参数的get
版本都以int
从输入流返回一个字符。char
范围中的每一个值来表示一个真实字符,因此,取值范围中没有额外的值可以标志文件尾。unsigned char
,然后再将结果提升到int
。因此,即使字符集中有字符映射到负值,这些操作返回的int也是正值。而标准库使用负值表示文件尾,这样就可以保证与任何合法字符的值都不一样。头文件cstdio
定义了一个名为EOF
的const
,我们可以使用它来确定返回的是否是文件尾,而不必记忆文件尾的实际数值。{
int ch;//使用一个int而不是char来保存get()的返回值
while ((ch = cin.get() != EOF))
cout.put(ch);
// 接受参数的版本,可以自动的检测到EOF
// 使用while检测流的状态
}
6).多字节操作
get
和getline
函数接受相同的参数,它们的行为类似但是不相同。sink
都是一个char
数组,用来保存数据,两个函数都一直读取数据,知道以下条件之一发生。get
将分割符留作istream
的下一个字符getline
则将其读取并且丢弃。sink
中。7).确定读入了多少个字符
gcount
来确定最后一个未格式输入操作读取了多少个字符。gcount
。(明确读取的字符数目)gcount
之前调用了peek,unget,getback
,则gcount
返回值为0。8).底层函数容易出错。
get,peek
的返回值赋予一个char
而不是int
。这样做是错误的,但是编译器不会发现这个错误。具体发生什么错误,取决于机器和输入数据。char
被实现为unsigned
的机器上,while ((ch = cin.get() != EOF))
永远都不会停止循环。char
为signed char
上,会发生什么?有的不能确定循环的行为;有的遇到变量的值越界了,依赖编译器如何处理这种越界;有的恰好可以正常工作,除非遇到和EOF
相等的情况,虽然在普通数据中共这种字符不太可能,但是底层的IO通常用于读取二进制值,二进制值不能直接映射到普通字符和数值。…练习
getline
,暂时略过。1).各种流类型通常都支持对流中数据的随机访问。**我们可以重定位流,使之跳过一些数据,首先读取最后一行,然后读取第一行,以此类推。**标准库提供了一对函数(成员函数。),
seek
定位到流中的给定位置tell
返回我们当前的位置。2).虽然标准库为所有的流类型都定义了seek
和tell
函数,但他们是否会做有意义的事情,依赖于流绑定的设备。绑定到cin,cout,cerr,clog
的流设备不支持随机访问——当我们cout
直接输出数据时,类似向回跳十个位置这样的操作是没有意义的…对这些流我们可以调用seek,tell
函数,但是在运行时会出错,将流置为无效状态。
istream,ostream
类型通常不支持随机访问,以下内容只是用于fstream,和sstream
类型。3).seek,tell
函数,详见表格17.21(p676)
seek,tell
分别用于输入流,输出流。差别在于后缀一个是g(get)读取,输入;一个是p(put)写入,输出。4).标准库虽然区分读写的版本,但是在流中只有单一的标记–不存在独立的读标记和写标记。
fstream,stringstream
。它们有单一的缓冲区,用来保存读写的数据,标记只有一个,表示缓冲区当前的位置。标准库将g和p版本的读写都映射到这个单一的标记。由于只有单一的标记,我们要在读写操作间进行切换时,就必须使用seek
操作来重定位标记。5).重定位标记
{
// 将标记移动到一个固定的位置
seekg(new_position);//将读标记移动到一个指定的pos_type类型的位置上
seekp(new_position);//将写标记移动到指定的pos_type位置上
// 移动到给的那个起止点之前或之后指定的偏移位置
seekg(offset, from);//将读标记移动到距from偏移量为offset的位置
seekp(offset, from);
// from可能值见表17.21(p676)
// new_positon类型为pos_type
// offset类型为off_type
// 以上两个类型都是机器相关的
// 定义在头文件istream和ostream中
// pos_type表示一个文件位置
// off_set的值可以是整的也可以为负
// 通过正负确定是向前还是向后移动
// -----------访问标记
// tell函数返回一个类型为pos_type的值,表示当前的流的位置
// tell函数通常是为了记住一个位置,以便稍后回来改位置
ostringstream writeStr;
ostringstream::pos_type mark = writeStr.tellp();
// ...
if (cancelEntry) {
// 回到刚才的位置
writeStr.seekp(mark);
}
// ---------读写同一个文件
// 给定一个文件,在文件末尾写入新的一行,包含文件中每一行的相对起始位置。
abcd
efg
hi
j
5 9 12 14
// 注意偏移量包括行尾的不可见的换行符
int main() {
// 以读写的方式打开文件并且定位到文件尾
fstream inOut("copyOut", fstream::ate | fstream::in | fstream::out);
if (!inOut) {
cerr << "..." << endl;
return EXIT_FALLURE;//这是一个预处理变量,定义在头文件cstdlib中
}
// inOut以ate的方式打开,因此一开始就是定位在文件的末尾
auto end_mark = inOut.tellg(); //记住 读取 的末尾位置
inOut.seekg(0, fstream::beg);//重定位到文件开始的位置
size_t cnt = 0; //字节数计数器
string line;//保存每一行
// 如果读取没有失败
// 并且还没有 读取 到达文件end_mark
//
while (inOut && inOut.tellg() != end_mark
&& getline(inOut, line)) {
cnt += line.size() + 1;//加1表示换行符
auto mark = inOut.tellg();//记住当前 读取 的位置
intOut.seekp(0, fstream::end); //将 写 的标记移动到文件的末尾
inOut << cnt ;//写入数据
if (mark != end_mark) inOut << " ";//如果不是最后一行,打印一个分割符
inOut.seekg(mark);//恢复到上一次 读 的位置
}
inOut.seekp(0, fstream::end);//定位到文件的末尾
inOut << '\n';//写入换行符号。
return 0;
}
// 绝对位置是没有from的三个特殊值得
// 使用重载的版本得到文件的起始和结束位置
}
1).match
对象是sub_match
对象的容器(反映的是整个正则表达式(0号元素)/子表达式的匹配结果。),而且它还有成员prefix(),suffix()
返回匹配之前和之后的信息。也是一个sub_match
对象。