首页 > 其他分享 >第十七章 标准库特殊设施

第十七章 标准库特殊设施

时间:2023-02-19 23:34:13浏览次数:29  
标签:第十七章 std 特殊 设施 tuple regex using include string

第十七章 标准库特殊设施

tuple类型

  • tuple是类似pair的模板,每个成员类型都可以不同,但tuple可以有任意数量的成员。
  • 但每个确定的tuple类型的成员数目是固定的。
  • 我们可以将tuple看做一个“快速而随意”的数据结构。

tuple支持的操作

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

定义和初始化tuple

定义和初始化示例:

  • tuple<size_t, size_t, size_t> threeD;
  • tuple<size_t, size_t, size_t> threeD{1,2,3};
  • auto item = make_tuple("0-999-78345-X", 3, 2.00);

访问tuple成员:

  • auto book = get<0>(item);
  • get<2>(item) *= 0.8;

练习17.1

定义一个保存三个int值的 tuple,并将其成员分别初始化为10、20和30。

解:

auto t = tuple<int, int, int>{10, 20, 30};

练习17.2

定义一个 tuple,保存一个 string、一个vector<string> 和一个 pair<string, int>

解:

auto t = tuple<string, vector<string>, pair<string, int> >

练习17.3

重写12.3节中的 TextQuery 程序,使用 tuple 代替 QueryResult 类。你认为哪种设计更好?为什么?

解:

程序略。

我认为tuple更方便。

使用tuple返回多个值

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

练习17.4

编写并测试你自己版本的 findBook 函数。

解:

#include <iostream>
#include <tuple>
#include <string>
#include <vector>
#include <algorithm>
#include <utility>
#include <numeric>

#include "ex_17_4_SalesData.h"

using namespace std;

// matches有三个成员:1.一个书店的索引。2.指向书店中元素的迭代器。3.指向书店中元素的迭代器。
typedef tuple<vector<Sales_data>::size_type,
              vector<Sales_data>::const_iterator,
              vector<Sales_data>::const_iterator>
    matches;

// files保存每家书店的销售记录
// findBook返回一个vector,每家销售了给定书籍的书店在其中都有一项
vector<matches> findBook(const vector<vector<Sales_data>> &files,
                         const string &book)
{
    vector<matches> ret; //初始化为空vector
    // 对每家书店,查找给定书籍匹配的记录范围
    for (auto it = files.cbegin; it != files.cend(); ++it)
    {
        // 查找具有相同ISBN的Sales_data范围,found是一个迭代器pair
        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.second));
    }
    return ret; //如果未找到匹配记录,ret为空
}

void reportResults(istream &in, ostream &os,
                       const vector<vector<Sales_data> > &files){
    string s;  //要查找的书
    while (in >> s){
        auto trans = findBook(files, s);
        if (trans.empty()){
            cout << s << " not found in any stores" << endl;
            continue;  // 获得下一本要查找的书
        }
        for (const auto &store : trans)  // 对每家销售了给定书籍的书店
            // get<n>返回store中tuple的指定的成员
            os << "store " << get<0>(store) << " sales: "
               << accumulate(get<1>(store), get<2>(store), Sales_data(s))
               << endl;
    }
}

int main(){
    return 0;
}

练习17.5

重写 findBook,令其返回一个 pair,包含一个索引和一个迭代器pair。

解:

typedef std::pair<std::vector<Sales_data>::size_type,
                  std::pair<std::vector<Sales_data>::const_iterator,
                            std::vector<Sales_data>::const_iterator>>
                                                                      matches_pair;

std::vector<matches_pair>
findBook_pair(const std::vector<std::vector<Sales_data> > &files,
              const std::string &book)
{
    std::vector<matches_pair> ret;
    for(auto it = files.cbegin(); it != files.cend(); ++it)
    {
        auto found = std::equal_range(it->cbegin(), it->cend(), book, compareIsbn);
        if(found.first != found.second)
            ret.push_back(std::make_pair(it - files.cbegin(),
                                         std::make_pair(found.first, found.second)));
    }
    return ret;
}

练习17.6

重写 findBook,不使用tuplepair

解:

struct matches_struct
{
    std::vector<Sales_data>::size_type st;
    std::vector<Sales_data>::const_iterator first;
    std::vector<Sales_data>::const_iterator last;
    matches_struct(std::vector<Sales_data>::size_type s,
                   std::vector<Sales_data>::const_iterator f,
                   std::vector<Sales_data>::const_iterator l) : st(s), first(f), last(l) { }
} ;

std::vector<matches_struct>
findBook_struct(const std::vector<std::vector<Sales_data> > &files,
                const std::string &book)
{
    std::vector<matches_struct> ret;
    for(auto it = files.cbegin(); it != files.cend(); ++it)
    {
        auto found = std::equal_range(it->cbegin(), it->cend(), book, compareIsbn);
        if(found.first != found.second)
            ret.push_back(matches_struct(it - files.cbegin(), found.first, found.second));
    }
    return ret;
}

练习17.7

解释你更倾向于哪个版本的findBook,为什么。

解:

使用tuple的版本。很明显更加灵活方便。

练习17.8

在本节最后一段代码中,如果我们将Sales_data()作为第三个参数传递给accumulate,会发生什么?

解:

结果是0,以为Sales_data是默认初始化的。

bitset类型

  • 处理二进制位的有序集;
  • bitset也是类模板,但尖括号中输入的是bitset的长度而不是元素类型,因为元素类型是固定的,都是一个二进制位。

初始化bitset的方法:

操作 解释
bitset<n> b; bn位;每一位均是0.此构造函数是一个constexpr
bitset<n> b(u); bunsigned long longu的低n位的拷贝。如果n大于unsigned long long的大小,则b中超出unsigned long long的高位被置为0。此构造函数是一个constexpr
bitset<n> b(s, pos, m, zero, one); bstring s从位置pos开始m个字符的拷贝。s只能包含字符zeroone:如果s包含任何其他字符,构造函数会抛出invalid_argument异常。字符在b中分别保存为zeroonepos默认为0,m默认为string::nposzero默认为'0',one默认为'1'。
bitset<n> b(cp, pos, m, zero, one); 和上一个构造函数相同,但从cp指向的字符数组中拷贝字符。如果未提供m,则cp必须指向一个C风格字符串。如果提供了m,则从cp开始必须至少有mzeroone字符。

初始化案例;

  • bitset<13> bitvec1(0xbeef);
  • bitset<32> bitvec4("1100");

练习17.9

解释下列每个bitset 对象所包含的位模式:

(a) bitset<64> bitvec(32);
// 0000000000000000000000000000000000000000000000000000000000100000
(b) bitset<32> bv(1010101);
// 00000000000011110110100110110101
(c) string bstr; cin >> bstr; bitset<8> bv(bstr);
// 根据输入的str转换成bitset

bitset操作:

操作 解释
b.any() b中是否存在1。
b.all() b中都是1。
b.none() b中是否没有1。
b.count() b中1的个数。
b.size()
b.test(pos) pos下标是否是1
b.set(pos) pos置1
b.set() 所有都置1
b.reset(pos) 将位置pos处的位复位
b.reset() b中所有位复位
b.flip(pos) 将位置pos处的位取反
b.flip() b中所有位取反
b[pos] 访问b中位置pos处的位;如果bconst的,则当该位置位时,返回true;否则返回false
b.to_ulong() 返回一个unsigned long值,其位模式和b相同。如果b中位模式不能放入指定的结果类型,则抛出一个overflow_error异常。
b.to_ullong() 类似上面,返回一个unsigned long long值。
b.to_string(zero, one) 返回一个string,表示b中位模式。zeroone默认为0和1。
os << b b中二进制位打印为字符10,打印到流os
is >> b is读取字符存入b。当下一个字符不是1或0时,或是已经读入b.size()个位时,读取过程停止。

练习17.10

使用序列1、2、3、5、8、13、21初始化一个bitset,将这些位置置位。对另一个bitset进行默认初始化,并编写一小段程序将其恰当的位置位。

解:

#include <iostream>
#include <bitset>
#include <vector>

int main()
{
    std::vector<int> v = { 1, 2, 3, 5, 8, 13, 21 };
    std::bitset<32> bset;

    for (auto i : v)    bset.set(i);

    std::bitset<32> bset2;
    for (unsigned i = 0; i != 32; ++i)
        bset2[i] = bset[i];

    std::cout <<bset <<std::endl;
    std::cout <<bset2<<std::endl;
}

练习17.11

定义一个数据结构,包含一个整型对象,记录一个包含10个问题的真/假测验的解答。如果测验包含100道题,你需要对数据结构做出什么改变(如果需要的话)?

解:

#include <iostream>
#include <bitset>
#include <utility>
#include <string>
#include <iostream>

//class Quiz
template<std::size_t N>
class Quiz
{
public:
    //constructors
    Quiz() = default;
    Quiz(std::string& s) :bitquiz(s){ }

    //generate grade
    template<std::size_t M>
    friend std::size_t grade(Quiz<M> const&, Quiz<M> const&);

    //print
    template<std::size_t M>
    friend std::ostream& operator<<(std::ostream&, Quiz<M> const&);

    //update bitset
    void update(std::pair<std::size_t, bool>);
private:
    std::bitset<N> bitquiz;
};
#endif

template<std::size_t N>
void Quiz<N>::update(std::pair<std::size_t, bool> pair)
{
    bitquiz.set(pair.first, pair.second);
}

template<std::size_t M>
std::ostream& operator<<(std::ostream& os, Quiz<M> const& quiz)
{
    os << quiz.bitquiz;
    return os;
}

template<std::size_t M>
std::size_t grade(Quiz<M> const& corAns, Quiz<M> const& stuAns)
{
    auto result = stuAns.bitquiz ^ corAns.bitquiz;
    result.flip();
    return result.count();
}


int main()
{
    //Ex17_11
    std::string s = "1010101";
    Quiz<10> quiz(s);
    std::cout << quiz << std::endl;

    //EX17_12
    quiz.update(std::make_pair(1, true));
    std::cout << quiz << std::endl;

    //Ex17_13
    std::string answer = "10011";
    std::string stu_answer = "11001";
    Quiz<5> ans(answer), stu_ans(stu_answer);
    std::cout << grade(ans, stu_ans) << std::endl;

    return 0;
}

练习17.12

使用前一题中的数据结构,编写一个函数,它接受一个问题编号和一个表示真/假解答的值,函数根据这两个参数更新测验的解答。

解:

参考17.11。

练习17.13

编写一个整型对象,包含真/假测验的正确答案。使用它来为前两题中的数据结构生成测验成绩。

解:

参考17.11。

正则表达式

  • 正则表达式(reqular expression)是一种描述字符序列的方法,是一种很强大的工具。

正则表达式库组件:

组件 解释
regex 表示一个正则表达式的类
regex_match 将一个字符序列与一个正则表达式匹配
regex_search 寻找第一个与正则表达式匹配的子序列
regex_replace 使用给定格式替换一个正则表达式
sregex_iterator 迭代器适配器,调用regex_searcg来遍历一个string中所有匹配的子串
smatch 容器类,保存在string中搜索的结果
ssub_match string中匹配的子表达式的结果

regex_matchregex_search的参数:

操作 解释
(seq, m, r, mft) 在字符序列seq中查找regex对象r中的正则表达式。seq可以是一个string、标识范围的一对迭代器、一个指向空字符结尾的字符数组的指针。
(seq, r, mft) m是一个match对象,用来保存匹配结果的相关细节。mseq必须具有兼容的类型。mft是一个可选的regex_constants::match_flag_type值。
  • 这些操作会返回bool值,指出是否找到匹配。

使用正则表达式库

  • regex使用的正则表达式语言是ECMAScript,模式[[::alpha::]]匹配任意字母。
  • 由于反斜线是C++中的特殊字符,在模式中每次出现\的地方,必须用一个额外的反斜线\\告知C++我们需要一个反斜线字符。
  • 简单案例:
    • string pattern("[^c]ei"); pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*" 查找不在字符c之后的字符串ei
    • regex r(pattern); 构造一个用于查找模式的regex
    • smatch results; 定义一个对象保存搜索结果
    • string test_str = "receipt freind theif receive";
    • if (regex_search(test_str, results, r)) cout << results.str() << endl; 如有匹配子串,打印匹配的单词。

regex(和wregex)选项:

操作 解释
regex r(re) regex r(re, f) re表示一个正则表达式,它可以是一个string、一对表示字符范围的迭代器、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器、一个花括号包围的字符列表。f是指出对象如何处理的标志。f通过下面列出来的值来设置。如果未指定f,其默认值为ECMAScript
r1 = re r1中的正则表达式替换Wierere表示一个正则表达式,它可以是另一个regex对象、一个string、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表。
r1.assign(re, f) 和使用赋值运算符(=)的效果相同:可选的标志f也和regex的构造函数中对应的参数含义相同。
r.mark_count() r中子表达式的数目
r.flags() 返回r的标志集

定义regex时指定的标志:

操作 解释
icase 在匹配过程中忽略大小写
nosubs 不保存匹配的子表达式
optimize 执行速度优先于构造速度
ECMAScript 使用ECMA-262指定的语法
basic 使用POSIX基本的正则表达式语法
extended 使用POSIX扩展的正则表达式语法
awk 使用POSIX版本的awk语言的语法
grep 使用POSIX版本的grep的语法
egrep 使用POSIX版本的egrep的语法
  • 可以将正则表达式本身看做是一种简单程序语言设计的程序。在运行时,当一个regex对象被初始化或被赋予新模式时,才被“编译”。
  • 如果编写的正则表达式存在错误,会在运行时抛出一个regex_error的异常。
  • 避免创建不必要的正则表达式。构建一个regex对象可能比较耗时。

练习17.14

编写几个正则表达式,分别触发不同错误。运行你的程序,观察编译器对每个错误的输出。

解:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

#include <string>
using std::string;

#include <regex>
using std::regex;
using std::regex_error;

int main()
{
    // for ex17.14
    // error_brack
    try{
        regex r("[[:alnum:]+\\.(cpp|cxx|cc)$", regex::icase);
    }
    catch(regex_error e)
    {
        cout << e.what() << " code: " << e.code() << endl;
    }

    // for ex17.15
    regex r("[[:alpha:]]*[^c]ei[[:alpha:]]*", regex::icase);
    string s;
    cout << "Please input a word! Input 'q' to quit!" << endl;
    while(cin >> s && s != "q")
    {
        if(std::regex_match(s, r))
            cout << "Input word " << s << " is okay!" << endl;
        else
            cout << "Input word " << s << " is not okay!" <<endl;

        cout << "Please input a word! Input 'q' to quit!" << endl;
    }

    cout << endl;

    // for ex17.16
    r.assign("[^c]ei", regex::icase);
    cout << "Please input a word! Input 'q' to quit!" << endl;
    while(cin >> s && s != "q")
    {
        if(std::regex_match(s, r))
            cout << "Input word " << s << " is okay!" << endl;
        else
            cout << "Input word " << s << " is not okay!" <<endl;

        cout << "Please input a word! Input 'q' to quit!" << endl;
    }

    return 0;
}

练习17.15

编写程序,使用模式查找违反“i在e之前,除非在c之后”规则的单词。你的程序应该提示用户输入一个单词,然后指出此单词是否符号要求。用一些违反和未违反规则的单词测试你的程序。

解:

参考17.14。

练习17.16

如果前一题程序中的regex对象用"[^c]ei"进行初始化,将会发生什么?用此模式测试你的程序,检查你的答案是否正确。

解:

参考17.14。

匹配与regex迭代器类型

sregex_iterator操作(用来获得所有匹配):

操作 解释
sregex_iterator it(b, e, r); 一个sregex_iterator,遍历迭代器be表示的string。它调用sregex_search(b, e, r)it定位到输入中第一个匹配的位置。
sregex_iterator end; sregex_iterator的尾后迭代器
*itit-> 根据最后一个调用regex_search的结果,返回一个smatch对象的引用或一个指向smatch对象的指针。
++itit++ 从输入序列当前匹配位置开始调用regex_search。前置版本返回递增后迭代器;后置版本返回旧值。
it1 == it2 如果两个sregex_iterator都是尾后迭代器,则它们相等。两个非尾后迭代器是从相同的输入序列和regex对象构造,则它们相等。

示例:

// 将字符串file中所有匹配模式r的子串输出
for (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it){
    cout << it ->str() << endl;
}

smatch操作:

操作 解释
m.ready() 如果已经通过调用regex_searchregex_match设置了m,则返回true;否则返回false。如果ready返回false,则对m进行操作是未定义的。
m.size() 如果匹配失败,则返回0,;否则返回最近一次匹配的正则表达式中子表达式的数目。
m.empty() 等价于m.size() == 0
m.prefix() 一个ssub_match对象,标识当前匹配之前的序列
m.suffix() 一个ssub_match对象,标识当前匹配之后的部分
m.format(...)
m.length(n) n个匹配的子表达式的大小
m.position(n) n个子表达式距离序列开始的长度
m.str(n) n个子表达式匹配的string
m[n] 对应第n个子表达式的ssub_match对象
m.begin(), m.end() 表示mssub_match元素范围的迭代器。
m.cbegin(), m.cend() 常量迭代器

练习17.17

更新你的程序,令它查找输入序列中所有违反"ei"语法规则的单词。

解:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

#include <string>
using std::string;

#include <regex>
using std::regex;
using std::sregex_iterator;

int main()
{
	string s;
	cout << "Please input a sequence of words:" << endl;
	getline(cin, s);
	cout << endl;
	cout << "Word(s) that violiate the \"ei\" grammar rule:" << endl;
	string pattern("[^c]ei");
	pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
	regex r(pattern, regex::icase);
	for (sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it)
		cout << it->str() << endl;

	return 0;
}

练习17.18

修改你的程序,忽略包含“ei`但并非拼写错误的单词,如“albeit”和“neighbor”。

解:

参考17.17。

使用子表达式

  • 正则表达式语法通常用括号表示子表达式。
  • 子表达式的索引从1开始。
  • fmt中用$后跟子表达式的索引号来标识一个特定的子表达式。

示例:

if (regex_search(filename, results, r))
    cout << results.str(1) << endl;  // .str(1)获取第一个子表达式匹配结果

ssub_match子匹配操作:

操作 解释
matched 一个public bool数据成员,指出ssub_match是否匹配了
firstsecond public数据成员,指向匹配序列首元素和尾后位置的迭代器。如果未匹配,则firstsecond是相等的。
length() 匹配的大小,如果matchedfalse,则返回0。
str() 返回一个包含输入中匹配部分的string。如果matchedfalse,则返回空string
s = ssub ssub_match对象ssub转化为string对象s。等价于s=ssub.str(),转换运算符不是explicit的。

练习17.19

为什么可以不先检查m[4]是否匹配了就直接调用m[4].str()

解:

如果不匹配,则m[4].str()返回空字符串。

练习17.20

编写你自己版本的验证电话号码的程序。

解:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

#include <string>
using std::string;

#include <regex>
using std::regex;
using std::sregex_iterator;
using std::smatch;

bool valid(const smatch& m);

int main()
{
	string phone = "(\\()?(\\d{ 3 })(\\))?([-. ])?(\\d{ 3 })([-. ]?)(\\d{ 4 })";
	regex r(phone);
	smatch m;
	string s;
	bool valid_record;
	// read each record from the input file
	while (getline(cin, s))
	{
		valid_record = false;
		// for each matching phone number
		for (sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it)
		{
			valid_record = true;
			// check whether the number's formatting is valid
			if (valid(*it))
				cout << "valid phone number: " << it->str() << endl;
			else
				cout << "invalid phone number: " << it->str() << endl;
		}

		if (!valid_record)
			cout << "invalid record!" << endl;
	}
	return 0;
}

bool valid(const smatch& m)
{
	// if there is an open parenthesis before the area code
	if (m[1].matched)
		// the area code must be followed by a close parenthesis
		// and followed immediately by the rest of the number or a space
		return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
	else
		// then there can't be a close after the area code
		// the delimiters between the other two components must match
		return !m[3].matched && m[4].str() == m[6].str();
}

练习17.21

使用本节定义的valid 函数重写8.3.2节中的电话号码程序。

解:

#include <iostream>
using std::cerr;
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;

#include <fstream>
using std::ifstream;
using std::ofstream;

#include <sstream>
using std::istringstream;
using std::ostringstream;

#include <string>
using std::string;

#include <vector>
using std::vector;

#include <regex>
using std::regex;
using std::sregex_iterator;
using std::smatch;

struct PersonInfo
{
    string name;
    vector<string> phones;
};

bool valid(const smatch& m);
bool read_record(istream& is, vector<PersonInfo>& people);
void format_record(ostream& os, const vector<PersonInfo>& people);

// fake function that makes the program compile
string format(const string &num) { return num; }

int main()
{
    vector<PersonInfo> people;

    string filename;
    cout << "Please input a record file name: ";
    cin >> filename;
    cout << endl;
    ifstream fin(filename);

    if (read_record(fin, people))
    {
        ofstream fout("data\\result.txt", ofstream::trunc);
        format_record(fout, people);
    }
    else
    {
        cout << "Fail to open file " << filename << endl;
    }

    return 0;
}

bool valid(const smatch& m)
{
    // if there is an open parenthesis before the area code
    if (m[1].matched)
        // the area code must be followed by a close parenthesis
        // and followed immediately by the rest of the number or a space
        return m[3].matched && (m[4].matched == 0 || m[4].str() == " ");
    else
        // then there can't be a close after the area code
        // the delimiters between the other two components must match
        return !m[3].matched && m[4].str() == m[6].str();
}

bool read_record(istream& is, vector<PersonInfo>& people)
{
    if (is)
    {
        string line, word; // will hold a line and word from input, respectively
                           // read the input a line at a time until cin hits end-of-file (or another error)
        while (getline(is, line))
        {
            PersonInfo info; // create an object to hold this record's data
            istringstream record(line); // bind record to the line we just read
            record >> info.name; // read the name
            while (record >> word) // read the phone numbers
                info.phones.push_back(word); // and store them
            people.push_back(info); // append this record to people
        }
        return true;
    }
    else
        return false;
}

void format_record(ostream& os, const vector<PersonInfo>& people)
{
    string phone = "(\\()?(\\d{ 3 })(\\))?([-. ])?(\\d{ 3 })([-. ]?)(\\d{ 4 })";
    regex r(phone);
    smatch m;

    for (const auto &entry : people)
    {
        // for each entry in people
        ostringstream formatted, badNums; // objects created on each loop
        for (const auto &nums : entry.phones)
        {
            for (sregex_iterator it(nums.begin(), nums.end(), r), end_it; it != end_it; ++it)
            {
                // for each number
                // check whether the number's formatting is valid
                if (!valid(*it))
                    // string in badNums
                    badNums << " " << nums;
                else
                    // "writes" to formatted's string
                    formatted << " " << format(nums);
            }
        }

        if (badNums.str().empty()) // there were no bad numbers
            os << entry.name << " " // print the name
            << formatted.str() << endl; // and reformatted numbers
        else // otherwise, print the name and bad numbers
            cerr << "input error: " << entry.name
            << " invalid number(s) " << badNums.str() << endl;
    }
}

练习17.22

重写你的电话号码程序,使之允许在号码的三个部分之间放置任意多个空白符。

解:

参考17.21。

练习17.23

编写查找邮政编码的正则表达式。一个美国邮政编码可以由五位或九位数字组成。前五位数字和后四位数字之间可以用一个短横线分隔。

解:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

#include<string>
using std::string;

#include <regex>
using std::regex;
using std::sregex_iterator;
using std::smatch;

bool valid(const smatch& m);

int main()
{
	string zipcode =
		"(\\d{5})([-])?(\\d{4})?\\b";
	regex r(zipcode);
	smatch m;
	string s;
	
	while (getline(cin, s))
	{

		//! for each matching zipcode number
		for (sregex_iterator it(s.begin(), s.end(), r), end_it;
			it != end_it; ++it)
		{
			//! check whether the number's formatting is valid
			if (valid(*it))
				cout << "valid zipcode number: " << it->str() << endl;
			else
				cout << "invalid zipcode number: " << s << endl;
		}

	}
	return 0;
}

bool valid(const smatch& m)
{
	
	if ((m[2].matched)&&(!m[3].matched))
		return false;
	else
		return true;
}

使用regex_replace

正则表达式替换操作:

操作 解释
m.format(dest, fmt, mft), m.format(fmt, mft) 使用格式字符串fmt生成格式化输出,匹配在m中,可选的match_flag_type标志在mft中。第一个版本写入迭代器dest指向的目的为止,并接受fmt参数,可以是一个string,也可以是一个指向空字符结尾的字符数组的指针。mft的默认值是format_default
rege_replace(dest, seq, r, fmt, mft)regex_replace(seq, r, fmt, mft) 遍历seq,用regex_search查找与regex对象r相匹配的子串,使用格式字符串fmt和可选的match_flag_type标志来生成输出。mft的默认值是match_default

示例:

string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ]?)(\\d{4})"
string fmt = "$2.$5.$7";  // 将号码格式改为ddd.ddd.dddd
regex r(phone);  // 用来寻找模式的regex对象
string number = "(908) 555-1800";
cout << regex_replace(number, r, fmt) << endl;

匹配标志:

操作 解释
match_default 等价于format_default
match_not_bol 不将首字符作为行首处理
match_not_eol 不将尾字符作为行尾处理
match_not_bow 不将首字符作为单词首处理
match_not_eow 不将尾字符作为单词尾处理
match_any 如果存在多于一个匹配,则可以返回任意一个匹配
match_not_null 不匹配任何空序列
match_continuous 匹配必须从输入的首字符开始
match_prev_avail 输入序列包含第一个匹配之前的内容
format_default ECMAScript规则替换字符串
format_sed POSIX sed规则替换字符串
format_no_copy 不输出输入序列中未匹配的部分
format_first_only 只替换子表达式的第一次出现

练习17.24

编写你自己版本的重拍电话号码格式的程序。

解:

#include <iostream>
#include <regex>
#include <string>

using namespace std;


string pattern = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
string format = "$2.$5.$7";
regex r(pattern);
string s;

int main()
{
    while(getline(cin,s))
    {
        cout<<regex_replace(s,r,format)<<endl;
    }

    return 0;
}

练习17.25

重写你的电话号码程序,使之只输出每个人的第一个电话号码。

解:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

string pattern = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
string fmt = "$2.$5.$7";
regex r(pattern);
string s;

int main()
{
    while(getline(cin,s))
    {
        smatch result;
        regex_search(s,result,r);
        if(!result.empty())
        {
        cout<<result.prefix()<<result.format(fmt)<<endl;
        }
        else
        {
            cout<<"Sorry, No match."<<endl;
        }
    }

    return 0;
}

练习17.26

重写你的电话号码程序,使之对多于一个电话号码的人只输出第二个和后续号码。

解:

练习17.27

编写程序,将九位数字邮政编码的格式转换为 ddddd-dddd

解:

#include <iostream>
#include <regex>
#include <string>

using namespace std;

string pattern = "(\\d{5})([.- ])?(\\d{4})";
string fmt = "$1-$3";

regex r(pattern);
string s;


int main()
{
    while(getline(cin,s))
    {
        smatch result;
        regex_search(s,result, r);

        if(!result.empty())
        {
            cout<<result.format(fmt)<<endl;
        }
        else
        {
            cout<<"Sorry, No match."<<endl;
        }

    }
    return 0;
}

随机数

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

示例:

// 初始化分布类型
uniform_int_distribution<unsigned> u(0, 9);
// 初始化引擎
default_random_engine e;
// 随机生成0-9的无符号整数
cout << u(e) << endl;

设置随机数发生器种子

  • 种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。
  • 种子可以使用系统函数time(0)

练习17.28

编写函数,每次调用生成并返回一个均匀分布的随机unsigned int

解:

#include <iostream>
#include <random>
#include<string>

// default version
unsigned random_gen();
// with seed spicified
unsigned random_gen(unsigned seed);
// with seed and range spicified
unsigned random_gen(unsigned seed, unsigned min, unsigned max);
int main()
{
    std::string temp;
    while(std::cin >> temp)
    std::cout << std::hex << random_gen(19, 1, 10) << std::endl;
    return 0;
}

unsigned random_gen()
{
    static std::default_random_engine e;
    static std::uniform_int_distribution<unsigned> ud;
    return ud(e);
}

unsigned random_gen(unsigned seed)
{
    static std::default_random_engine e(seed);
    static std::uniform_int_distribution<unsigned> ud;
    return ud(e);
}

unsigned random_gen(unsigned seed, unsigned min, unsigned max)
{
    static std::default_random_engine e(seed);
    static std::uniform_int_distribution<unsigned> ud(min, max);
    return ud(e);
}

练习17.29

修改上一题中编写的函数,允许用户提供一个种子作为可选参数。

解:

参考17.28。

练习17.30

再次修改你的程序,此次增加两个参数,表示函数允许返回的最小值和最大值。

解:

参考17.28。

其他随机数分布

分布类型的操作:

操作 解释
Dist d; 默认够赞函数;使d准备好被使用。其他构造函数依赖于Dist的类型;分布类型的构造函数是explicit的。
d(e) 用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列;e是一个随机数引擎对象。
d.min(),d.max() 返回d(e)能生成的最小值和最大值。
d.reset() 重建d的状态,是的随后对d的使用不依赖于d已经生成的值。

练习17.31

对于本节中的游戏程序,如果在do循环内定义be,会发生什么?

解:

由于引擎返回相同的随机数序列,因此眉不循环都会创建新的引擎,眉不循环都会生成相同的值。

练习17.32

如果我们在循环内定义resp,会发生什么?

解:

会报错,while条件中用到了resp

练习17.33

修改11.3.6节中的单词转换程序,允许对一个给定单词有多种转换方式,每次随机选择一种进行实际转换。

解:

#include <iostream>
using std::cout;
using std::endl;

#include <fstream>
using std::ifstream;

#include <string>
using std::string;

#include <vector>
using std::vector;

#include <random>
using std::default_random_engine;
using std::uniform_int_distribution;

#include <ctime>
using std::time;

#include <algorithm>
using std::sort;
using std::find_if;

#include <utility>
using std::pair;


int main() {
	typedef pair<string, string> ps;
	ifstream i("d.txt");
	vector<ps> dict;
	string str1, str2;
	// read wirds from dictionary
	while (i >> str1 >> str2) {
		dict.emplace_back(str1, str2);
	}
	i.close();
	// sort words in vector
	sort(dict.begin(), dict.end(), [](const ps &_ps1, const ps &_ps2){ return _ps1.first < _ps2.first; });
	i.open("i.txt");
	default_random_engine e(unsigned int(time(0)));
	// read words from text
	while (i >> str1) {
	  // find word in dictionary
		vector<ps>::const_iterator it = find_if(dict.cbegin(), dict.cend(),
		  [&str1](const ps &_ps){ return _ps.first == str1; });
		// if word doesn't exist in dictionary
		if (it == dict.cend()) {
		  // write it itself
			cout << str1 << ' ';
		}
		else {
		  // get random meaning of word 
			uniform_int_distribution<unsigned> u (0, find_if(dict.cbegin(), dict.cend(),
			 [&str1](const ps &_ps){ return _ps.first > str1; }) - it - 1);
			// write random meaning
			cout << (it + u(e))->second << ' ';
		}
	}

	return 0;
}

IO库再探

格式化输入与输出

  • 使用操纵符改变格式状态。
  • 控制布尔值的格式: cout << boolalpha << true << endl;
  • 指定整型的进制:cout << dec << 20 << endl;

定义在iostream中的操纵符:

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

其中*表示默认的流状态。

练习17.34

编写一个程序,展示如何使用表17.17和表17.18中的每个操作符。

解:

练习17.35

修改第670页中的程序,打印2的平方根,但这次打印十六进制数字的大写形式。

解:

#include <iostream>
#include<iomanip>
#include <math.h>
using namespace std;

int main()
{
	cout <<"default format: " << 100 * sqrt(2.0) << '\n'
		<< "scientific: " << scientific << 100 * sqrt(2.0) << '\n'
		<< "fixed decimal: " << fixed << 100 * sqrt(2.0) << '\n'
		<< "hexidecimal: " << uppercase << hexfloat << 100 * sqrt(2.0) << '\n'
		<< "use defaults: " << defaultfloat << 100 * sqrt(2.0)
		<< "\n\n";
}

//17.36
//Modify the program from the previous exercise to print the various floating-point values so that they line up in a column.
#include <iostream>
#include<iomanip>
#include <math.h>
using namespace std;

int main()
{
	cout <<left<<setw(15) << "default format:" <<setw(25)<< right<< 100 * sqrt(2.0) << '\n'
	<< left << setw(15) << "scientific:" << scientific << setw(25) << right << 100 * sqrt(2.0) << '\n'
	<< left << setw(15) << "fixed decimal:" << setw(25) << fixed << right << 100 * sqrt(2.0) << '\n'
	<< left << setw(15) << "hexidecimal:" << setw(25) << uppercase << hexfloat << right << 100 * sqrt(2.0) << '\n'
	<< left << setw(15) << "use defaults:" << setw(25) << defaultfloat << right << 100 * sqrt(2.0)
	<< "\n\n";
}

练习17.36

修改上一题中的程序,打印不同的浮点数,使它们排成一列。

解:

参考17.36。

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

单字节低层IO操作:

操作 解释
is.get(ch) istream is读取下一个字节存入字符cn中。返回is
os.put(ch) 将字符ch输出到ostream os。返回os
is.get() is的下一个字节作为int返回
is.putback(ch) 将字符ch放回is。返回is
is.unget() is向后移动一个字节。返回is
is.peek() 将下一个字节作为int返回,但不从流中删除它。

多字节低层IO操作:

操作 解释
is.get(sink, size, delim) is中读取最多size个字节,并保存在字符数组中,字符数组的起始地址由sink给出。读取过程直到遇到字符delim或读取了size个字节或遇到文件尾时停止。如果遇到了delim,则将其留在输入流中,不读取出来存入sink
is.getline(sink, size, delim) 与接收三个参数的get版本类似,但会读取并丢弃delim
is.read(sink, size) 读取最多size个字节,存入字符数组sink中。返回is
is.gcount() 返回上一个未格式化读取从is读取的字节数
os.write(source, size) 将字符数组source中的size个字节写入os。返回os
is.ignore(size, delim) 读取并忽略最多size个字符,包括delim。与其他未格式化函数不同,ignore有默认参数:size默认值是1,delim的默认值为文件尾。
  • 注意:一般情况下,主张使用标准库提供的高层抽象,低层函数容易出错。

练习17.37

用未格式化版本的getline 逐行读取一个文件。测试你的程序,给定一个文件,既包含空行又包含长度超过你传递给geiline的字符数组大小的行。

解:

//17.37
//Use the unformatted version of getline to read a file a line at a time.
//Test your program by giving it a file that contains empty lines as well as lines that are
//longer than the character array that you pass to getline.

#include <iostream>
#include <fstream>
#include <iomanip>

using namespace std;

//int main () {
//  ifstream myfile("F:\\Git\\Cpp-Primer\\ch17\\17_37_38\\test.txt");
//  if (myfile) cout << 1 << endl;
//  char sink [250];
//
//  while(myfile.getline(sink,250))
//  {
//    cout << sink << endl;
//  }
//  return 0;
//}

//17.38
//Extend your program from the previous exercise to print each word you read onto its own line.

//#include <iostream>
//#include <fstream>
//#include <iomanip>
//
//using namespace std;
//
//int main () {
//  ifstream myfile ("F:\\Git\\Cpp-Primer\\ch17\\17_37_38\\test.txt");
//  char sink [250];
//
//  while(myfile.getline(sink,250,' '))
//  {
//    cout << sink << endl;
//  }
//  return 0;
//}


int main()
{
	std::cout << "Standard Output!\n";
	std::cerr << "Standard Error!\n";
	std::clog << "Standard Log??\n";
}

练习17.38

扩展上一题中你的程序,将读入的每个单词打印到它所在的行。

解:

参考17.37。

流随机访问

  • 只适用于fstreamsstream
  • 通过将标记seek到一个给定位置来重定位它。
  • tell告诉我们标记的当前位置。
操作 解释
tellg()tellp 返回一个输入流中(tellg)或输出流中(tellp)标记的当前位置。
seekg(pos)seekp(pos) 在一个输入流或输出流中将标记重定位到给定的绝对地址。pos通常是一个当前teelgtellp返回的值。
seekp(off, from)seekg(off, from) 在一个输入流或输出流中将标记定位到from之前或之后off个字符,from可以是下列值之一:beg,偏移量相对于流开始位置;cur,偏移量相对于流当前位置;end,偏移量相对于流结尾位置。

练习17.39

对本节给出的 seek程序,编写你自己的版本。

解:

标签:第十七章,std,特殊,设施,tuple,regex,using,include,string
From: https://www.cnblogs.com/Epiephany/p/17135933.html

相关文章

  • C#两个特殊的集合类StringCollection与StringDictionary
    1、前言    为啥要写StringCollection与StringDictionary这两个集合呢?这两个集合都可以存储字符串的数据结构,都是非泛型的可以存储任何类型的数据,都是使用数组存储元......
  • C++特殊成员
    参考书籍:C++PrimerEssentialC++编译器:gcc/g++C++特殊成员const成员const修饰的数据成员初始化必须采用初始化参数列表不能被修改构造函数必须要初始化常数据成员cons......
  • m基于GA遗传优化的三维工程施工设施布局算法matlab仿真,显示二维和三维布局优化效果
    1.算法描述GA把问题的解表示成“染色体”,在算法中也即是以二进制编码的串。并且,在执行遗传算法之前,给出一群“染色体”,也即是假设解。然后,把这些假设解置于问题的“环境”......
  • 【C++复习】运算符重载中的特殊运算符
    无法被重载类属关系运算符 .成员指针运算符 .*作用域分辨符 ::三目运算符 ?:只能通过成员函数重载赋值运算符=方括号[]圆括号()指向结构体成员运算符->......
  • m基于GA遗传优化的三维工程施工设施布局算法matlab仿真,显示二维和三维布局优化效果
    1.算法描述       GA把问题的解表示成“染色体”,在算法中也即是以二进制编码的串。并且,在执行遗传算法之前,给出一群“染色体”,也即是假设解。然后,把这些假设解置于......
  • Mybatis08 - 特殊SQL
    模糊查询,不确定结果数量使用List<实体类>publicinterfaceSpecialSQLMapper{//通过用户名模糊查询用户信息List<User>getUserByLike(@Param("mohu")Stri......
  • python 特殊方法
    ##跟运算符无关的特殊方法###1.实例的创建和销毁__init__解释:构造方法,可调用超类的构造器。这是实例级别的方法,发生在实例创建之后,用于初始化实例,给实例添加一些属性和......
  • 【Oculus Interaction SDK】(八)特殊的 UI(曲面效果 & 手指点击)
    前言这篇文章是【OculusInteractionSDK】系列的一部分,如果发现有对不上的对方,可以回去翻看我之前发布的文章,或在评论区留言。如果文章的内容已经不适用于新版本了,也可以......
  • 【转载】go.sum中特殊hash如何计算
    Golang为了依赖的安全考虑,在go.mod的基础上引入了go.sum,go.sum文件的作用主要是记录项目依赖的hash值,防止被人修改。在分析具体项目的go.sum文件后可以发现go.sum中不仅......
  • Java的特殊类型
    对Java中一些特殊类型,抽象类,接口,包装类,枚举,注解的简要介绍Author:MsuenbDate:2023-02-11抽象类当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同......