C++11(16):标准库特殊设施

tuple是类型pair的模板。不同tuple类型的成员类型也不同,但一个tuple可以有任意数量的成员。每个确定的tuple类型的成员数目是固定的。
当我们希望将一些数据组合成单一对象,但有不想麻烦地定义一个新数据结构来表示时,tuple是非常有用的。(“快速而随意”的数据结构)
tuple类型及其伴随类型和函数都定义在tuple头文件中。
tuple<T1, T2, ..., Tn> t;     t是一个tuple,成员数为n,第i个成员类型为Ti。所有成员都进行值初始化。
tuple<T1, T2, ..., Tn> t(v1, v2, ..., vn);     此构造函数时explicit的。
make_tuple(v1, v2, ..., vn)     返回给定初始值初始化的tuple,并进行类型推断。
==      !=    关系运算符
get<i> (t)   返回t的第i个数据成员的引用;如果t是一个左值,结果是一个左值引用;否则,是右值引用。tuple的所有成员都是public的,i是一个整型常量。
tuple_size<tupleType>::value    一个类模板,可以通过一个tuple类型类初始化。它有一个名为value的public constexpr static 数据成员,类型为size_t,表示给定tuple类型中成员的数量。
teple_element<i,tupleType>::type   一个类模板,可以通过一个整型常量和一个tuple类型来初始化。它有一个名为type的public成员,表示给定tuple类型中指定成员的类型。

如果不知道一个tuple的准确的类型细节,可以用两个辅助类模板来查询tuple的成员的数量和类型:
typedef decltype(item) trans;
size_t sz = tuple_size<trans>::value;
tuple_element<1, trans> :: type cnt = get<1>(item);

为了使用关系运算符,对没对成员使用<必须都是合法的。
由于tuple定义了<和==运算符, 我们可以将tuple序列传递给算法,并且可以再无心容器中将tuple作为关键字类型。

tuple常用的以个用途是从一个函数返回多个值。
//matches有三个成员:一家书店的索引和两个指向书店vector中元素的迭代器
typedef tuple<vector<Sales_data>::size_type, vector<Sales_data>::const_iterator, vector<Sales_data>::cosnt_iterator> matches;
vector<matches> findBook(const vector<vector<Sales_data>> &files, const string &book)
{
    vector<matches> ret;
    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.second));
    }
    return 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 tuto & stre : trans)
            os << "stre " << get<0>(store) << " sales: "
                << accumulate(get<1>(store>, get<2>(store), Sales_data(s))
                <<endl;
    }
}

标准库还定义了bitset类, 使得为运算的使用更为容易, 并且能够处理超级最长整型类型大小的位集合。bitset类定义在头文件bitset中。
类似于数组。变化从0开始的二进制位被称为低位,左后结束的二进制位被称为高位。
bitset<n> b;    b有n为;每一位均为0. 此构造函数时一个constexpr
bitset<n> b(u);   b是unsigned long long值u的低n为的拷贝。如果n大于unsigned long long的大小,则b中超出unsigned long long的高位被设置为0 。此构造函数时以个constexpr。初始值中多出的高位被丢弃。
bitset<n> b(s, pos, m, zero, one);     是string s从位置pos开始m个字符的拷贝。s只能包含字符zero和one;如果s包含任何其他字符,构造函数抛出invalid_argument 异常。字符在b中分别保存为zero和one。pos默认为0, m默认为string::npos, zero默认为'0' ,one默认为 '1'
bitset<n> b(cp, pos, m, zero, one); 与一个构造函数相同, 但从cp指向的字符数组中拷贝字符。如果未提供m,则cp必须指向一个C风格字符串串。如果提供了m,则从cp开始必须至少有m个zero或one字符
注意:接受一个string或一个字符指针的构造函数时explicit的。在新标准中增加了为0和1指定其他字符的功能。

对于string的下标编号习惯与bitset加好相反:string中下标最大的字符(最右字符)用来初始化bitset中的低位(下标为0的二进制位)。当你用一个string初始化一个bitset时,要记住这个差别。

bitset还支持位运算符。这些运算符用于bitset对象的含义与内置运算符用于unsigned运算对象相同。

b.any( )        b中时否存在置位(即等于1)的二进制位。
b.all( )          b中所有的位都置位了吗
b.none( )      b中不存在置位的二进制位吗
b.count( )    b中置位的位数
b.size( )        一个constexper函数,返回b中的位数
b.test(pos)    若pos位置的是位置的,则返回true,否则返回false
b.set(pos, v)    将位置pos处的位设置为bool值v。v默认为true。如果未传递实参,则将b中所有位置位
b.set( )
b.reset(pos)        将pos处的位复位或将b中所有位复位
b.reset( )
b.flip(pos)        改变位置pos处的位的状态或改变b中每一位的状态
b.flip( )
b[pos]        访问b中位置pos处的位;如果b是const,则当该位置位是b[pos]返回一个bool值true,否则返回false;非const版本返回bitset定义的一个特殊类型,它允许我们曹总指定位的值
b.to_ulong( )    返回一个unsigned long或一个unsigned long long值,其位模式雨b相同。如果b中位模式不能放入指定的结果类型,则抛出一个overflow_error异常
b.to_ullong( )
b.to_string(zero, one) 返回一个string,表示b中的位模式。zero和one的默认分别是0、1,用来表示b中的0、1
os<<b       将b中二进制打印为字符1或0,打印到os
is>>b          从is读取字符存入b。当下一个字符不是1或0是,或是已经读入b.size( )个位时,读取过程停止

正则表达式是一种描述字符列的方法,是一种极其强大的技术工具。RE库定义在头文件regex中,它包含多个组件:
regex                表示有一个正则表达式的类
regex_match     将一个字符序列与一个正则表达式匹配,如果输入序列与表达式匹配则regex_match函数返回true
regex_search    寻找第一个与正则表达式匹配的子序列   如果输入序列中一个子串与表达式匹配,则regex_search 函数返回true
regex_replace    使用给定格式替换一个正则表达式
sregex_iterator    迭代器适配器,调用regex_search来遍历一个string中所匹配的字串
smatch                容器类,保存在string中搜索的结果
ssub_match        string中匹配的子表达式的结果

regex、search 和regex_match的参数:
注:这些操作返回bool值,指出是否找到匹配
(seq, m, r, mft)               (seq, r, mft)
在字符seq中查找regex对象r中的正则表达式。seq可以是一个string、表示范围的一对迭代器以及一个指向空字符结尾的字符数组的指针
m是一个match对象,用来保存匹配结果的相关细节。m和seq必须具有兼容的类型。
mft是一个可选的regex_constants::match_flag_type值。他们会影响匹配过程

//查找违法众所周知的拼写规则“i除非在c之后,否则必须在e之前“的单词
//查找不在字符c之后的字符串ei
string pattren("[^c]ei");
//我们必须包含pattern的整个单词
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern);    //构造一个用于查找模式的regex
smatch    results;    //定义一个对象保存搜索结果
//定义一个string保存于模式匹配和不匹配的文本
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指出我们想要陪陪这种字符后接ei的字符串。此模式包含了三个字符。为了与整个单词匹配,我们还需要一个正则表达式与这个三个字母模式之前和之后的字母匹配。
这个正则表达式包含零个或多个字母后接我们的三字母的模式,然后在接零个或多个额外的字母。默认情况下,regex使用的正则表达式语言是ECMAScript。在ECMAScript中,模式[[:alpha:]]匹配任意字母,符号+和*分别表示我们希望“一个或多个”或“零个或多个”匹配。因此[[::alpha::]]*将匹配零个或多个字母。

当我们定义一个regex或是对一个regex调用assign为其赋予新值时,可以知道那个一些标志来影响regex如何操作。可行标志控制regex对象的处理过程。对于这6个标志,我们必须设置其中之一,且只能设置一个。默认情况下,ECMAScript标志被这只,从而regex会使用ECMA-262规范,这也是Web浏览器所使用的正则表达式语言。
regex(和wregex)选项:
regex r(re)     regex r(re,f)   re表示一个正则表达式,它可以是一个string、一个表示字符范围的迭代器对、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器或是一个花括号包围的字符列表。f是指出对象如何处理的标志。f通过下面列出的值来设置。如果为指定f,其默认值为ECMAScript
r1 = re            将r1中的正则表达式替换为re。re表示一个正则表达式,它可以是另一个regex对象、一个string、一个指向空字符结尾的字符数组的指针或是一个或括号保卫的字符列表。
r1.assign(re,f)        与使用复制运算符(=)效果相同
r.flags( )                    返回r的标志集
注:构造函数和赋值操作可能抛出类型为regex_error的异常
定义regex时指定的标志:(定义在regex和regex_constants::syntax_option_type中)
icase            在匹配过程中忽略大小写
nosubs         不保存匹配的子表达式
optimize      执行速度优先于构造速度
ECMAScript        使用ECMA-262指定的语法
basic                    使用POSIX基本的正则表达式语法
extended            使用POSIX扩展的正则表达式语法
awk                      使用POSIX版本的awk语言的语法
grep                    使用POSIX版本的grep的语法
egrep                    使用POSIX版本的egrep的语法
前三个标志允许我们指定正则表达式处理过程中与语言无关的方面。
例子,识别特定扩展名的文件名。大多数操作系统都是按大小写无关的方式来识别扩展名的。
//一个或多个字母或数字字符后接一个'.'再接“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;
此包大师将匹配这样的字符串: 一个或多个字母或数字后接一个句点在接三个文件扩展名之一。这样,此正字表达式将会匹配指定的文件扩展名而不理会大小写。
和c++一样,正则表达式语言通常也有特殊字符。字符点(.)通常匹配任意字符。与c++一样,我们可以再字符之前放置一个反斜线来去掉其特殊含义。由于反斜线也是c++中的一个特殊字符,我们在字符串字面常量中必须连续使用两个反斜线来告诉c++我们想要一个普通反斜线字符。因此,为了表示与句点字符匹配的正则表达式,必须写成\\.

正则表达式本身看作用用一种简单程序设计语言编写的“程序”(非C++)。正则表达式是在运行时,当一个regex对象被初始化或被赋予一个新模式是,才被“编译”的。如果编写的正则表达式存在错误,则在运行时标准库会抛出一个类型为regex_error的异常。类似标准异常类型,regex有一个what操作来描述发生了什么错误。regex_error还有一个名为code的成员,用来返回某个错误类型对应的数值编码。code返回的值是由具体实现定义的。RE库能抛出的标准错误下所示。
正则表达式错误类型:(定义在regex和regex_constants::error_type中)
error_collate                无效的元素校对请求
error_ctype                    无效的字符类
error_escape                无效的转义字符或无效的尾置转义
error_backref                无效的向后引用
error_brack                    不匹配的方括号 ([ ])
error_paren                    不匹配的小括号
error_brace                    不匹配的花括号
error_badbrace             {}中无效的范围
error_range                    无效的字符范围(如[z-a])
error_space                    内存不足,无法处理此正则表达式
error_badrepeat            重复字符(*、?、+或{)之前没有有效的正则表达式
error_complexity            要求的匹配过于复杂
error_stack                    栈空间不足,无法处理匹配
避免创建不必要的正则表达式:一个正则表达式所表示的“程序”是在运行时而非编译时编译的。正则表达式的编译是一个非常慢的操作,特别是在使用了扩展的正则表达式语法或是复杂的正则表达式时。因此,构造一个regex对象以及想一个已存在的regex赋予一个新的正则表达式可能是非常耗时的。为了最小化这种开销,你应该努力避免创建很多不必要regex。特别是,如果你在一个循环中使用正则表达式,应该在循环外创建它,而不是在每步迭代时就编译它。

我们可以搜索多种类型输入序列。重点在于我们使用RE库类型必须与输入序列类型匹配。输入可以是普通char数据或wchar_t数据,字符可以保存在标准库string中或是char数组中(或是宽字符版本,wstring或wchar_t数组中)。RE为这些不同的输入序列类型都定义了对应的类型。
下面给出了RE库类型与输入序列类型的对应关系。
如果输入序列类型                    则使用正则表达式类
string                                       regex、smatch, ssub_match, sregex_iterator
const char*                              regex, cmatch, csub_match, cregex_iterator
wstring                                    wregex, wsmatch, wssub_match, wsregex_iterator
const wchar_t*                        wregex, wcmatch, wcsub_match, wcregex_iterator

我们可以使用sregex_iterator来获得所有匹配。regex迭代器是一种迭代器适配器,被绑定到一个输入序列和一个regex对象上。每种不同输入序列类型都有对应的特殊regex迭代器类型。
sregex_iterator操作,这些操作也适用于cregex_iterator、wsregex_iterator、wcregex_iterator
sregex_iterator   it(b, e, r);     一个sregex_iterator, 遍历迭代器b和e表示的string。
sregex_iterator end;              sregex_iterator的尾后迭代器
*it      it->      根据最后一个调用regex_search的结果,返回一个smatch对象的引用或一个指向smatch对象的指针
++it  it++      从输入序列当前匹配位置开始调用regex_search。
it1 == it2  !=   如果两sregex_iterator都是尾后迭代器,则它们相等,两个非尾后迭代器是从相同的输入序列和regex对象构造,则它们相等

当我们将一个sregex_iterator绑定到一个string和一个regex对象时,迭代器自动定位到给定string中第一个匹配位置。当我们解引用时会得到一个对应最近一次搜索结果的smatch对象。当我们递增迭代器是,它调用regex_search在输入序列string中查找下一个匹配。
例,这个版本的程序使用与前一个版本一样的pattern,但会使用一个sregex_iterator来进行搜索
string pattern("[^c]ei");
pattern = '[[:alpha:]] + parrern + "[[:alpha:]]*";
regex r(parrern, regex::icase);
for(sregex_iterator it(file.begin( ), file.end( ), r), end_it; it != end_it; ++it)
    cout << it->str( ) << endl;
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个字符
    cout << it -> prefix( ).str( ).substr(pos)
            << "\n\t\t>>>"<<it ->str( ) << " <<<\n"
            <<it -> suffix( ).str( ).substr(0, 40)
            <<endl;
}

smatch操作:( 这些操作也使用于 cmatch、wsmatch和对应的csub_match、wssub_match和wcsub_match)
m.ready( )        如果已经通过调用regex_serach或regex_match设置了m,则返回true; 否则返回false。如果ready返回false,则对m进行操作是未定义的
m.size ( )          如果匹配失败,则返回0; 否则返回最近一次匹配的正则表达式中子表达式的数目
m.empty( )       若m.size( )为0,则返回true
m.prefix( )        一个ssub_match对象,表示当前匹配之前的序列
m.suffix( )        一个ssub_match对象,表示当前匹配之后的部分
m.format(...)    参见正则表达式的替代操作
在接受一个索引的操作中,n的默认值为0且必须小于m.size( )
第一个子匹配(索引为0)表示整个匹配。
m.length(n)        第n个匹配的子表达式的大小
m.position(n)      第n个子表达式距序列开始的距离
m.str(n)                第n个子表达式匹配的string
m[n]                    对应第n个子表达式匹配的ssub_match对象
m.begin( ),m.end( ),m.cbegin( ), m.cend( )     表示m中sub_match元素范围的迭代器。与往常一样,cbegin和cend返回const_iterator

正则表达式中的模式通常包含一个或多个子表达式。一个字表达式是模式的一部分,本身也具有意义。正则表达式语法通常用括号表示字表达式。每当我们用括号分组多个可行选项时,同时也就声明了这些选项形成子表达式。如:
regex r("([[:alnum:]]+)\\.(cpp|cxx|cc)$",regex::icase);
我们也可以:
if(regex_search(filename, results, r))
    cout << results.str(1) <<endl;     ///打印第一个子表达式

子表达式的一个常见用途是验证必须匹配特定格式的数据。(美国电话号码有十位,包含一个区号和一个七位的本地号码。区号通常放在括号里,但是并不是必须的。剩余七位数字可用一个短横线、一个点或是一个空格分隔,但也可以完全不用分隔符)分两步实现:首先,我们将用一个正则表达式找到可能是电话号码的序列,然后再调用一个函数来完成数据验证。
ECMAScript正则表达式语言的一些特性:
    \{d}表示单个数字而\{d}{n}表示一个n个数字的序列。(如,\{d}{3}匹配三个数组的序列。)
    在方括号中的字符集合表示匹配这些字符中任意一个。(如,[-. ]匹配一个段横线或一个点或一个空格。注意,点在括号中没有特殊含义)
    后接'?'的组件是可选的。(如,\{d}{3}[-. ]?\{d}{4}匹配这样的序列:开始是3个数字,后接一个可选的短横线或点或空格,然后是四个数字。此模式可以匹配555-0132或555.0132或555 0132或5550132。)
    类似c++,ECMAScript使用反斜线表示一个字符本身而不是其特殊含义。由于我们的模式包含括号,而括号是ECMAScript中的特殊字符,因此我们必须用\(和\)来表示括号是我们模式的一部分而不是特殊字符。
因为c++中反斜杠是特殊字符,因此在模式中每次出现\的地方,我们必须都用一个额外的反斜杠来告知c++我们需要一个反斜杠。因此,我们用\\{d}{3}来表示正则表达式\{d}{3}
string phone = "{\\()?(\\d{3})(\\))?([-. ]?)(\\d{3})([-. ])?(\\d{4});
regex r(phone);
smatch m;
string s;
while(getline(cin,s))
{
    for(sregex_iterator it(s.begin(),send(),r),end_it; it !=end_it; ++it)
        if (valid(*it))
            cout<<"valid: "<<it ->str( ) <<endl;
        else
            cout<<"not valid: " << it->str( ) <<endl;
}
对于上述,需要记住的重要一点是,我们的pattern有七个子表达式。与往常以样,每个smatch对象包含八个ssub_match元素。位置[0]的元素表示整个匹配;其他为对应每个子表达式。
子匹配操作:(这些操作适用于ssub_match、csub_match、wssub_match、wcsub_match)
matched     一个public   bool数据成员,指出此ssub_match是否匹配了
first  和  second       public数据成员,指向匹配序列首元素和尾后位置的迭代器。如果为匹配,则first和second是相等的。
length( )         匹配的大小。如果matched为false,则返回0
str( )                返回一个包含输入中匹配部分的string。如果matched为false,则返回空string
s = ssub          将sub_match对象ssub转化为string对象s。等价于s=ssub.str( )。转换运算符不是explicit的。

bool vaild(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( );
}

当我们希望在输入序列中查找并替换一个正则表达式时,可以调用regex_replace。
m.format(dest, fmt, mft)               m.format(fmt, mft)     使用格式字符串fmt生成格式化输出,匹配在m中,可选的match_flag_type标志在mft中。第一个版本写入迭代器dest指向的目的位置并接受fmt参数,可以是一个string,也可以是表示字符数组中范围的一对指针。第二个版本返回一个string,保存输出,并接受fmt参数,可以是一个string,也可以是一个指向空字符结尾的字符数组的指针。mft的默认值为format_default
regex_replace(dest,seq,r,fmt,mft)              regex_replace(seq,r,fmt,mft)
遍历seq,用regex_search查找与regex对象r匹配的字串。使用格式字符串fmt和可选和可选的match_flag_type标志生成输出。第一个版本将输出写入到迭代器dest指定的位置,并接受一对迭代器seq表示范围。第二个版本返回一个string,保存输出,且seq即可以是一个string也可以是一个指向空字符结尾的字符数组的指针。在所有情况下,fmt即可以是一个string也可以是一个指向空字符结尾的字符数组的指针,且mft的默认值为match_default
替换字符串由于我们想要的字符组合与匹配的字串对应的子表达式而组成。我们用一个符号$后跟子表达式的索引来表示一个特定的子表达式
int main( )
{
    string phone  = "{\\()?(\\d{3})(\\))?([-. ]?)(\\d{3})([-. ])?(\\d{4});
    regex r(phone);
    smtch m;
    string s;
    string fmt = "$2.$5.$7";    ///将号码格式改为ddd.ddd.dddd
    while ( getline(cin,s))
        cout<<regex_replace(s, r, fmt) << endl;
    return 0;
}
就像标准库定义标志来指导如果处理正则表达式一样,标准库还定义了用来在替换过程中控制匹配或格式的标志。这些标志可以传给函数regex_search或regex_match或是类smtch的format成员。匹配的格式化标志的类型为match_flag_type。定义在regex_constants的命名空间中。regex_constants定义在std中
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                   只替换子表达式的第一次出现

在新标准之前,c和c++都依赖于一个简单的c库函数rand来生成随机数。此函数生成均匀分布的伪随机数,每个随机数的范围在0和以个系统相关的最大值(至少为32767)之间。在设置范围,特别是要生成浮点数时,常常会引入非随机性。
定义在头文件中random中的随机数库通过一组协作的类来解决这些问题:随机数引擎可随机数分布类。
引擎,生成随机unsigned整数序列
分布,使用引擎返回付出特定概率分布的随机数。
c++程序不应该使用库函数rand,而应该使用default_random_engine类和恰当的分布类对象。

随机数引擎是函数对象类,它们定义了一个调用运算符,该运算符不接受参数并返回一个随机unsigned整数
标准库定义了多个随机数引擎类,区别在于性能和随机性质量不同。每个编译器都会指定其中一个作为default_random_engine类型。此类型一般具有做常用的特性。随机数引擎操作:
Engine e;          默认构造函数:使用该引擎类型默认的种子
Engine e(s);      使用整型值s作为种子
s.send(s)           使用种子s重置引擎的状态
e.min()    e.max( )          此引擎可生成的最小值和最大值
Engine::result_type         此引擎生成的unsigned整型类型
e.discard(u)                     将引擎推进u步;u的类型为unsigned long long
原始的随机数范围通常与我们需要的不符,而正确转换随机数的范围是及其困难的。
为了得到一个指定范围内的数,我们使用一个分布类型的对象:
//生成0到9之间(包含)均匀分布的随机数
uniform_int_distribution<unsigned> u(0, 9);
default_random_engine e;
for(size_t i=0; i<10; ++i)
    cout<<u(e)<<" ";  ///    将u作为随机数源,每个调用返回在指定范围内并服从平均分布的值;
类似引擎类型,分布类型也是一个函数对象类。分布类型定义一个调用运算符,它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的分布。
注意我们传递给分布对象的是引擎对象本身,即u(e).如果是u(e())会导致编译错误,原因是某些分布可能需要调用引擎多次才能得到一个值。
当我们说随机数发生器时,是指分布对象和引擎对象的组合。

随机数发生器有一个特性经常会使新手迷惑:即使生成的数看起来是随机的,但对一个给定的发生器,每次运行程序它都会返回相同的数组序列。序列不变这一事实在调试时非常有用,但另一方面,使用时机数发生器的程序也必须考虑这一特性。
当我们编写以个vector时正确的方法是将引擎和关联分布对象定义为static的
vector<unsigned> good_randVec( )
{
    static default_random_engine e;
    static 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;
}
如果不是static的那么两个连续的调用会产生一样的随机数。

随机数发生器会生成相同的随机数序列这一特性在调试中很有用,但是一旦调试完了我们可以通过提供一个种子生成不同的随机数序列。
为引擎设置种子有两种方式:在创建引擎对象时提供种子,或者调用引擎的seed成员:
default_random_engine e1;//使用默认种子
default_random_engine e2(2147483646);   ///使用给定的种子值
//e3和e4将生成相同的序列,因为他们使用了相同的种子
default_random_engine e3;
e3.seed(32767);
default_random_engine e4(32767);
for(size_t i = 0; i != 100; ++i)
{
    if(e1( ) == e2( ))
          cout<<"unseeded match at iteration: "<< i <<endl;
    if(e3( ) != e4( ))
          cout <<" seeded differs at iteration: "<< i <<endl;
}
选择一个好种子是非常困难的,一般都是使用ctime头文件中,time;
    default_random_engine(time(0));

程序常需要一个随机浮点数的源。特别是,程序经常需要0到1之间的随机数。常用但不正确的是用rand( )的结果除以RAND_MAX,即,系统定义的rand可以生成的最大随机数的上界。这样做不正确的原因是随机数的精度通常低于随机浮点数,这样,有一些浮点值就永远不会被生成了。
在新标准中,我们是用uniform_real_distribution类型的对象,病人标准库来处理从随机数到浮点数的映射。
default_random_engine e;
uniform_real_distribution<double> u(0,1);
for(size_t i=0; i<10; ++i)
    cout<<u(e)<<" ";
分布类型操作:
Dist d;      默认构造函数:是d做好被使用。还有别的构造函数;分布类型的构造函数时explicit的;
d(e)          用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列:e是一个随机数引擎对象;
d.min( )     d.max( )    返回d(e)的最小值和最大值
d.reset( )    重建d的状态,使得随后对d的使用不依赖于d已经生成的值。

使用分布的默认结果类型:
uniform_real_distribution<> u(1,0); //默认生成double值

新标准库的另一个优势是可以生成非均匀分布的随机数。有20中分布类型。
default_random_engine e;
normal_distribution<> n(4,1.5);   //均值4,标准差1.5   正态分布。
vector<unisigned> vals(9);          //9个元素都为零
for(size_t i =0; i != 200; ++i)
{
        unisigned v = lround(n(e))   //舍入到最接近的整数
        if( v < vales.size( ))                 //如果结果在范围内
            ++vals[v];                           //统计每个数出现了多少次
}
for (size_t j = 0; j != vals.size( ); ++j)                          //得到一个正态分布图
    cout<< j << ": "<<string(vals[j]),'*')<<endl;

注意有一个分布不接受模板参数,即bernoulli_distribution,因为它是一个普通类,不是模板类。此分布中时返回一个bool值。返回true的概率是一个常数,默认是0.5;

string resp;
default_random_engine e;
bernoulli_distribution b;    // 可以指定分会true的概率
do{
    bool first = b(e);
    cout << (first ? true "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');

endl输出一个换行符并刷新缓冲区
标准库定义了一组操纵符类修改流的格式状态。一个操纵符是一个函数或是一个对象,会影响流的状态,并能用作输入或输出运算符的运算对象。类似输入和输出运算符,操纵符也返回它所处理的流对象,因此我们可以再一条语句中组合操纵符和数据。
操纵符用于两大类输出控制:控制数值的输出形式以及控制补白的数量和位置。大多数改变格式状态的操纵符都是设置/复原成对的;一个操纵符用来将格式状态设置为一个新值,而另一个用来将其复原,恢复为正常的默认格式。
当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。这一特性会给我们带来方便。但是还是要注意将不在需要特殊格式时尽快将流恢复到默认状态。

控制bool格式,一旦cout“写入”了boolalpha,我们就改变了cout打印bool值的方式。后续打印bool值的操作都会是true或false而非1或0
bool bool_val = get_status( );
cout<<boolalpha         //设置cout的内部状态
<<bool_val
<<noboolalpha;            ///将内部状态恢复为默认格式
本例中我们改变了bool值的格式,但只对bool_val的输出游戏。一旦完成此值的打印,我们立即将流恢复到初始状态。

默认情况下,整型的输入输出使用的是十进制。我们可以使用操纵符hex、oct和dec将其改为十六进制、八进制或是改回十进制。
cout << "default: " << 20 << " " << 1024 << endl;
cout<< " octal: "<< oct << 20 << " " <<1024 << endl;
cout<<"hex: " << hex << 20 << " " << 1024 << endl;
cout << "decimal: "<< dec <<20 << " " << 1024 <<endl;
注意,类似boolalpha,这些操纵符一会改变格式状态。 他们会影响下一个和随后所有的整型输出, 直至另一个操纵符有改变了格式为止。
这三个操纵符只影响整型运算对象,浮点值的表示形式不受影响。

如果需要打印八进制或十六进制值,应该使用showbase操纵符。当流使用showbase操纵符时,会在输出结果中显示进制,它遵循与整型常量中指定进制相同的规范。
我们可以使用showbase修改前一个程序:
cout << showbase; // 当打印整型值时显示进制
cout << "default: " << 20 << " " << 1024 << endl;
cout<< " octal: "<< oct << 20 << " " <<1024 << endl;
cout<<"hex: " << hex << 20 << " " << 1024 << endl;
cout << "decimal: "<< dec <<20 << " " << 1024 <<endl;
cout<<noshowbase; //恢复流状态
默认情况下,十六进制值会以小写打印,前到字符也是小写的x。我们可以通过使用uppercase操纵符来输出大写的X并将十六进制数字a-f以大写输出:
cout<< uppercase << showbase <<hex
        <<"printed in hexadecimal: "<<20<< " "<<1024
         << nouppercase << noshowbase <<dec <<endl;
我们使用了操纵符nouppercase、noshowbase和dec来重置流的状态。

我们可以控制浮点数输出三种格式:
    以多高精度(多少个数字)打印浮点值
    数值是打印为十六进制、定点十进制还是科学计数法形式
    对于没有小数部分的浮点值是否打印小数点
默认情况下,浮点值按六位数字精度打印;如果浮点值没有小数部分,则不打印小数点;根据浮点数的值选择打印成定点十进制或科学计数法形式。标准库会选择一种可读性更好的格式:非常大和非常小的值打印为科学计数法形式,其他值打印为定点十进制形式。
默认情况下,精度会控制打印的数字的总数。打印时,浮点值按当前精度舍入而非截断。因此如果精度为三位数字,则打印为3.14.
我们可以通过调用IO对象的precision成员或使用setprecision操纵符来改变精度。precision成员是重载的。一个版本接受int值,将精度设置为此值,并返回旧精度值。另一个版本一接受参数,返回当前精度值。setprecision操纵符接受一个参数,用来设置精度。
操纵符setprecision和其他接受参数的操纵符都定义在头文件iomanip中。
//cout.precision返回当前精度值
cout << "Precision: "<< cout.precision( )
         << ", Value: "   <<sqrt(2.0)<<endl;    sqrt  在cmath中
//cout.precision(12)将打印精度设置为12为数字
cout.precision(12);
cout << "Precision: " << cout.precision( )
         << ", Value: " << sqrt(2.0) <<endl;
//另外一种设置精度的方法是使用setprecision操纵符
cout << setprecision(3) ;
cout << "Precision: " <<cout.precision( )
         <<", Value: "   <<sqrt(2.0) <<endl;

定义在iostream中的操纵符(*表示默认流状态)
    boolalpha             将true和false输出为字符串
*   noboolalpha        将true和false输出为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)
    defaulthexfloat       重置浮点数格式为十进制(c++11)
    unitbuf                    每次输出操作后都刷新缓冲区
*   nounitbuf                恢复正常的缓冲区刷新方式
*   skipws                      输入运算符跳过空白符
    nosikpws                  输入运算符不跳过空白符
    flush                         刷新ostream缓冲区
    ends                         插入空字符,然后刷新ostream缓冲区
    endl                          插入换行, 然后刷新ostream缓冲区
一般标准库选择的浮点数标记法是最好的方式,除非你要按列打印就或打印表示金额或百分比的数据

当按列打印数据时,我们常常需要非常精细地控制数据格式。标准库提供了一些操纵符帮助我们完成所需的控制:
    setw 指定下一个数字或字符串值的最小空间
    left 表示左对齐输出。
    right表示右对齐输出,右对齐是默认格式
    internal控制负数的符号的位置,它对齐符号,右对齐值,用空格填满所有中间空间
    setfill允许指定一个字符代替默认的空格来补白输出

setw类型endl,不改变输出流的内部状态。它决定下一个输出的大小。
int i = -16;
double d = 3.14159;
// 补白第一列,使用输出中最新12个位置
cout<< "i: " << setw(12) << i <<"next col" << '\n'
        <<"d: "<< sew(12) << d << "next col" << '\n';
//补白第一列,左对齐所有列
cout <<left
        << "i: " << setw(12) << i <<"next col" << '\n'
         << "d: "<<setw(12) <<d<< "next col" << '\n'
         <<right; //恢复正常对齐
cout << right
         <<"i: "<< setw(12) << i << "next col" << '\n'
          <<"d: "<< setw(12) << d <<"next col"<<'\n'
//补白第一列,但补在域的内部
cout <<internal
         << "i: " << setw(12) << i << "next col" <<'\n'
         <<"d: "<<setw(12)  << i << "next col" <<'\n'
补白第一类,用#作为补白字符
cout << setfill('#')
         <<"i: " << setw(12) << i << "next col" <<'\n'
         <<"d: " <<setw(12) <<d <<"next col" << '\n'
         << setfill(' '); //恢复正常的补白字符

定义在iomanip中的操纵符
setfill(ch)                   用ch填充空白
setprecision(n)           将浮点精度设置为n
setw(w)                        读或写值的宽度为w个字符
setbase(b)                    将整数输出为b进制

输入格式:默认情况下会忽略空白符,但我们可以使用noskipws(不忽略)和skipws操纵符进行切换。

输入和输出运算符(<<和>>)根据读取或写入的数据类型来格式化他们。输入运算符忽略空白符,输出运算符应用补白、精度等规则
标准库还提供了一组低层操作,支持未格式化的IO。这些操作允许我们将一个流当作一个无解释的字节序列来处理。
下面几个未格式化的操作每次一个字节地处理流。它们会读取而不是忽略空白符。
单字节低层IO操作:
is.get(ch)                从istream is读取下一个字节存入字符ch中。返回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返回,但不从流中删除它

有时候我们需要读取一个字符才能知道还未准备好处理它。在这种情况下,我们希望将字符放回流中。标准库提供了三种方法退回字符,它们有着细微的差别:
    peek返回输入流中下一个字符的副本,但不会将它从流中删除,peek返回的值仍然流在流中。
    unget使得输入流向后移动,从而最后读取的值又回到流中。即使我们不知道最后从流中读取什么值,仍然可以调用unget
    putback是更特殊的版本的unget:它退回从流中读取的最后一个值,但它接受一个参数,此参数必须与最后读取的值相同。
一般情况下,在读取下一个值之前,标准库保证我们可以退回最多一个值。即,标准库不保证在中间不进行读取操作的情况下能连续调用putback或unget

函数peek和无参的get版本都以int类型从输入流返回一个字符。这些函数返回一个int的原因是:可以返回文件尾标记。可能char更合理,但是我们是用char范围中的每个值来表示一个真实字符,因此,取值范围中没有额外的值可以用来表示文件尾。
返回int的函数将他们要返回的字符先转换为unsigned char,然后再将结果提升到int。因此,即使字符集中有字符映射到负值,这些操作返回的int也是正值。而标准库使用负值表示文件尾,这样就可以保证与任何合法字符的值都不同。头文件cstdlib定义了一个名为EOF的const,我们可以用它来检测从get返回值是否是文件尾。对我们来说重要的是用一个int来保存这些函数返回的值
int ch;//使用一个int,而不是char来保存get( )的返回值
//循环读取并输出输入中的所有数据
while (( ch = cin.get( ) != EOF)
        cout.put(ch);

一些未格式化IO操作一次处理大块数据。如果速度是要考虑的重点问题的话,这些操作很重要,但类似其他底层操作,这些操作也容易出错,特别,这些操作要求我们自己分配并管理用来保存和提取数据的字符数组
多字节底层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.gconut( )                                  返回上一个未格式化读取操作从is读取的字节数
os.write(source, size)                   将字符数组source中的size个字节写入os。返回os
is.ignore(size, delim)                  读取并忽略最多size个字符,包括delim。与其他未格式化函数不同。ignore默认参数:size的默认值为1, delim的默认值为文件尾。

get和getline函数接受相同的参数,他们的行为类似但不相同。在两个函数中,sink都是一个char数组,用来保存数据。两个函数都是一直读取数据,直至已经读取了size-1个字符,遇到了文件尾,遇到了分隔符。
两个函数的差别是处理分隔符的方式:get将分隔符留作istream中的下一个字符,而getline则读取并丢弃分隔符。无论哪个函数都不会将分隔符保存在sink中
一个常见的错误是本想从流中删除分隔符,但却忘了做。

某些操作从输入读取未知个数的字节。我们可以调用gcount来确定最后一个未格式化输入操作读取了多少个字符。应该在任何后续未格式化输入操作之前调用gcount。特别是,将字符退回流的单字符操作也属于未格式化输入操作。如果在调用gcount之前调用了peek、unget或putback,则gcount的返回值为0。

各种流类型通常都支持对六中数据的随机访问。我们可以重定位流,使之跳过一些数据,首先读取最后一行,然后读取第一行,依次类推
随机访问IO本质上是依赖于系统的。为了理解如何使用这些特性,你必须查询系统文档。
虽然标准库为所有流类型都定义了seek和tell函数,但他们是否会做有意义的事情依赖于流绑定到哪个设备。大多数系统中,绑定到cin、cout,cerr和clog的流不支持随机访问,毕竟,当我们向cout直接输出数据时,类似向回跳十个位置这种操作是没有意义的。对这些流我们可以调用seek和tell函数,但在运行时会出错,将流置于一个无效状态。
由于istream和ostream类型通常不支持随机访问,所以接下来的内容只适用于fstream和sstream
为了支持随机访问,IO类型维护一个标记来确定下一个读写操作要在哪里进行。它们还提供了两个函数:一个函数通过标记seek到一个给定位置来重定位它;另一个函数tell我们标记的当前位置。标准库定义了两对seek和tell函数。一对用于输入流,另一对用于输出流。
  tellg( )     tellp( )      返回一个输入流中(tellg)或输出六中(tellp)标记的当前位置
seekg(pos)    seekp(pos)        在一个输入流或输出流中将标记重定位到给定的绝对地址。pos通常是前一个tellg或tellp返回的值
seekp(off, from)    seekg(off, from)       在一个输入流或输出流中将标记定位到from之前或之后off个字符,from可以是:beg,偏移量相对于流开始位置;cur,偏移量相对于流当前位置;end,偏移量相对于流结尾位置

从逻辑上讲,我们只能对istream和派生自istream的类型ifstream和istringstream使用g版本,同样只能对ostream和派生自ostream的类型ofstream和ostringstream使用p版本。一个iostream、fstream和stringstream两个版本都可以用。

在标准库中即使对seek和tell进行了区分,但它在一个流中只维护单一的标记,并不存在独立的读标记和写标记。
对于以个只读或只写的流,我们可以对这些流只使用g或p版本。
fstream和stringstream类型可以读写同一个流。这些类型中,有单一的缓冲区用于保存读写的数据,同样,标记页只有一个,表示缓冲区中的当前位置。标准库将g和p版本的读写位置都映射到这个单一的标记。
对于只有单一的标记,因此只要我们在读写操作间切换,就必须进行seek操作来重新定位标记。
seek有两个版本:一个移动到文件中的“绝对”地址;另一个移动到一个给定位置的知道那个偏移量
函数tellg和tellp返回一个pos_type值,表示流的当前位置。tell函数通常用来记住一个位置,以便稍后再定位回来:
//记住当前写位置
ostringstream writeStr;     //输出stringstream
ostringstream::pos_type mark = writeStr.tellp( );
//..
if(cancelEntry)
    //回到刚才记住的位置
    writeStr.seekp(mark);

例,我们的程序将朱行读取文件。对每一行,我们将递增计数器,将刚刚读取的一行的长度加到计数器上,则此计数器即为下一行的其实地址
int main( )
{
    fstream inOut("copyOut", fstream::ate | fstream::in | fstream::out);
    if(!inOut)
    {
        cerr << "Uable to open file!"<<endl;
        return EXIT_FAILURE;
    }
    auto end_mark = inOut.tellg( );
    inOut.seekg(0,fstream::beg);
    size_t cnt = 0;
    string line;
    while (inOut && inOut.tellg( ) != end_mark && getline(inOut.line))
    {
        cnt += line.size( ) + 1;
        auto mark = inOut.tellg( );
        inOut.seekp(0, fstream::end);
        inOut << cnt;
        if(mark != end_mark) inOut << " ";
        inOut.seekg(mark); 
    } 
    inOut.seekp(0, fstream::end);
    inOut << "\n"
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值