Loading

《C++ Primer》笔记 第17章 标准库特殊设施

tuple类型

tuple支持的操作 解释
tuple<T1, T2, ..., Tn> t; t是一个tuple,成员数为n,第i个成员的类型为Ti。所有成员都进行值初始化
tuple<T1, T2, ..., Tn> t(v1, v2, ..., vn); t是一个tuple,成员类型为T1...Tn,每个成员用对应的初始值vi进行初始化。此构造函数是explicit
make_tuple(v1, v2, ..., vn) 返回一个用给定初始值初始化的tuple。tuple的类型从初始值的类型推断
t1==t2 or t1!=t2 当两个tuple具有相同数量的成员且成员对应相等时,两个tuple相等。这两个操作使用成员的==运算符来完成。一旦发现某对成员不等,接下来的成员就不用比较了
t1 relop t2 tuple的关系运算符使用字典序。两个tuple必须具有相同数量的成员。使用<运算符比较t1的成员和t2中的对应成员
get<i>(t) 返回t的第i个数据成员的引用;如果t是一个左值,结果是一个左值引用;否则,结果是一个右值引用。tuple的所有成员都是public的
tuple_size<tupleType>::value 一个类模板可以通过一个tuple类型来初始化。它有一个名为value的public constexpr static数据成员,类型为size_t,表示给的那个tuple类型中成员的数量
tuple_element<i, tupleType>::type 一个类模板,可以通过一个整型常量和一个tuple类型来初始化。它有一个名为type的public成员,表示给定tuple类型中指定成员的类型。
  1. 我们可以将tuple看作一个“快速而随意”的数据结构。

  2. tuple的构造函数是explicit的

    tuple<size_t, size_t, size_t> threeD = {1, 2, 3}; // 错误
    tuple<size_t, size_t, size_t> threeD{1, 2, 3}; // 正确
    
  3. tuple的关系和相等运算符的行为类似容器的对应操作。这些运算符逐对比较左侧tuple和右侧tuple的成员。只有两个tuple具有相同数量的成员时,我们才可以比较它们。而且,为了使用tuple的相等或不等运算符,对每对成员使用==运算符必须都是合法的;为了使用关系运算符,对每对成员使用<必须都是合法的。

    tuple<string, string> duo("1", "2");
    tuple<size_t, size_t> twoD(1, 2);
    bool b = (duo == twoD); // 错误:不能比较size_t和string
    tuple<size_t, size_t, size_t> threeD(1, 2, 3);
    b = (twoD < threeD); // 错误,成员数量不同
    tuple<size_t, size_t> origin(0, 0);
    b = (origin < twoD); // 正确,b为true
    
  4. 由于tuple定义了<和==运算符,我们可以将tuple序列传递给算法,并且可以在无序容器中将tuple作为关键字类型。

bitset类型

  1. bitset类能够处理超过最长整数类型大小的位集合。
初始化bitset的方法 解释
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); b是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的。在C++11中增加了位0和1指定其他字符的功能。
  1. string的下标编号习惯与bitset恰好相反:string中下标最大的字符(最右字符)用来初始化bitset中的低位(下标为0的二进制位)。当你用一个string初始化一个bitset时,要记住这个差别。

    string str("1111111000000011001101");
    bitset<32> bitvec5(str, 5, 4); // 从str[5]开始的四个二进制位,1100
    bitset<32> bitvec6(str, str.size()-4); // 使用最后四个字符
    
bitset操作 解释
b.any() b中是否存在置位的二进制位
b.all() b中所有位都置位了吗
b.none() b中不存在置位的二进制位吗
b.count() b中置位的位数
b.size() 一个constexpr函数,返回b中的位数
b.test(pos) 若pos位置的位是置位的,则返回true,否则返回false
b.set(pos, v) or b.set() 将位置pos处的位设置为bool值v。v默认为true。如果未传递实参,则将b中所有位置位
b.reset(pos) or b.reset() 将位置pos处的位复位或将b中所有位复位
b.flip(pos) or b.flip() 改变位置pos处的位的状态或改变b中每一位的状态
b[pos] 访问b中位置pos处的位;如果b是const的,则当该位置位时b[pos]返回一个bool值true,否则返回false
b.to_ulong() or b.to_ullong() 返回一个unsigned long或一个unsigned long long值,其位模式与b相同。如果b中位模式不能放入指定的结果类型,则抛出一个overflow_error异常
b.to_string(zero, one) 返回一个string,表示b中的位模式。zero和one的默认值分别为0和1,用来表示b中的0和1
os << b or is >> b 将b中二进制位打印为字符1或0,打印到流os从is读取字符存入b。当下一个字符不是1或0时,或是已经读入b.size()个位时,读取过程停止
  1. const版本的下标运算符在指定位置位时返回true,否则返回false。非const版本返回bitset定义的一个特殊类型,它允许我们操纵指定位的值:

    bitvec[0] = 0; // 将第一位复位
    bitvec[31] = bitvec[0]; // 将最后一个设置为与第一位一样
    bitvec[0].flip(); // 翻转第一位
    ~bitvec[0]; // 等价操作,也是翻转第一位(但是bitvec[0]实际并没有改变)
    bool b = bitvec[0]; // 将bitvec[0]的值转换为bool类型
    
  2. 只有当bitset的大小小于等于对应的to_ulong和to_ullong的大小时,我们才能使用这两个操作。如果bitset中的值不能放入给定类型中,则这两个操作会抛出一个overflow_error异常。

    unsigned long ulong = bitvec3.to_ulong();
    cout << "ulong = " << ulong << endl;
    

正则表达式

正则表达式库组件 解释
regex 表示有一个正则表达式的类
regex_match 将一个字符序列与一个正则表达式匹配
regex_search 寻找第一个与正则表达式匹配的子序列
regex_replace 使用给定格式替换一个正则表达式
sregex_iterator 迭代器适配器,调用regex_search来遍历一个string中所有匹配的子串
smatch 容器类,保存在string中搜索的结果
ssub_match string中匹配的子表达式的结果
regex_search和regex_match的参数 解释
—— 注意:这些操作返回bool值,指出是否找到匹配
(seq, m, r, mft) or (seq, r, mft) 在字符序列seq中查找regex对象r中的正则表达式。seq可以是一个string、表示范围的一对迭代器以及一个指向空字符结尾的字符数组的指针。m是一个match对象,用来保存匹配结果的相关细节。m和seq必须具有兼容的类型。mft是一个可选的regex_constants::match_flag_type值。
regex(和wregex)选项 解释
regex r(re) or regex r(re, f) re表示一个正则表达式,它可以是一个string、一个表示字符范围的迭代器对、一个指向空字符结尾的字符数组的指针、一个字符指针和一个计数器或是一个花括号包围的字符列表。f是指出对象如何处理的标志。f通过下面列出的值来设置。如果未指定f,其默认值为ECMAscript
r1 = re 将r1中的正则表达式替换为re。re表示一个正则表达式,它可以是另一个regex对象、一个string、一个指向空字符结尾的字符数组的指针或是一个花括号包围的字符列表
r1.assign(re, f) 与使用赋值运算符(=)效果相同;可选的标志f也与regex的构造函数中对应的参数含义相同
r.mark_count() r中子表达式的数目
f.flags() 返回r的标志集
—— 注:构造函数和赋值操作可能抛出类型为regex_error的异常
定义regex时指定的标志 解释
—— 定义在regex和regex_constants::syntax_option_type中
icase 在匹配过程中忽略大小写
nosubs 不保存匹配的子表达式
optimize 执行速度优先与构造速度
EMCAScript 使用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)) // filename要和results类型一致
		cout << results.str() << endl; // 打印匹配结果
  1. 需要意识到的非常重要的一点是,一个正则表达式的语法是否正确是在运行时解析的。

  2. 如果我们编写的正则表达式存在错误,则在运行时标准库会抛出一个类型为regex_error的异常。类似标准异常类型,regex_error有一个what操作来描述发生了什么错误。regex_error还有一个名为code的成员,用来返回某个错误类型对应的数值编码。

正则表达式错误类型 解释
—— 定义在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 栈空间不足,无法处理匹配
  1. 正则表达式的编译是一个非常慢的操作,如果你在一个循环中使用正则表达式,应该在循环外创建它,而不是在每步迭代时都编译它。

  2. smatch表示string类型的输入序列;cmatch表示字符数组序列;wsmatch表示宽字符串(wstring)输入;而wcmatch表示宽字符数组。

  3. 我们使用的RE库类型必须与输入序列类型匹配,例如:

regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$", regex::icase);
smatch results; // 将匹配string输入序列,而不是char*
if (regex_search("myfile.cc", results, r)) // 错误:输入为char*
    cout << results.str() << endl; // 打印匹配结果
正则表达式类库 解释
如果输入序列类型 则使用正则表达式类
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操作 解释
—— 这些操作也适用于cregex_iterator、wsregex_iterator和wcregex_iterator
sregex_iterator it(b, e, r); 一个sregex_iterator,遍历迭代器b和e表示的string。它调用sregex_search(b, e, r)将it定位到输入中第一个匹配的位置
sregex_iterator end; sregex_iterator的尾后迭代器
*it or it-> 根据最后一个调用regex_search的结果,返回一个smatch对象的引用或一个指向smatch对象的指针
++it or it++ 从输入序列当前匹配位置开始调用regex_search。前置版本返回递增后迭代器;后置版本返回旧值
it1 == it2 or it1 != it2 如果两个sregex_iterator都是尾后迭代器,则它们相等;两个非尾后迭代器是从相同的输入序列和regex对象构造,则它们相等。
// 查找前一个字符不是c的字符串ei
string pattern("[^c]ei");
// 我们想要包含pattern的单词的全部内容
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern, regex::icase); // 在进行匹配时将忽略大小写
// 它将反复调用regex_search来寻找文件中的所有匹配
for (sregex_iterator it(file.begin(), file.end(), r), end_it; it != end_it; ++it)
    cout << it->str() << endl; // 匹配的单词
smatch操作 解释
—— 这些操作也适用于cmatch、wsmatch、wcmatch和对应的csub_match、wssub_match和wcsub_match
m.ready() 如果已经通过调用regex_search或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对象,表示当前匹配之后的部分
—— 在接受一个索引的操作中,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() or m.cbegin(), m.cend() 表示m中sub_match元素范围的迭代器。与往常一样,cbegin()和cend()返回const_iterator
// 显示出匹配单词的上下文
// 循环头与之前一样
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) // 前缀的最后一部分
        << " >>> " << it->str() << " <<<\n" // 匹配的单词
        << it->suffix().str().substr(0, 40) // 后缀的第一部分
        << endl;
}
  1. 正则表达式中的模式通常包含一个或多个子表达式。正则表达式语法通常用括号表示子表达式。第一个子匹配位置为0,表示整个模式对应的匹配,随后是每个子表达式对应的匹配。
string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
regex r(phone); // regex对象,用于查找我们的模式
smatch m;
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;
    }
}
子匹配操作 解释
—— 注意:这些操作是用于ssub_match、csub_match、wssub_match、wcsub_match。
matched 一个public bool数据成员,指出此ssub_match是否匹配了
first or second public数据成员,指向匹配序列首元素和尾后位置的迭代器。如果未匹配,则first和second是相等的
length() 匹配的大小。如果matched为false,则返回0
str() 返回一个包含输入中匹配部分的string。如果matched为false,则返回空string
s = ssub 将ssub_match对象ssub转化为string对象s。等价于s=ssub.str()。转换运算符不是explicit的
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();
}
正则表达式替换操作 解释
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
匹配标志 解释
—— 定义在regex_constants::match_flag_type中
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 只替换子表达式的第一次出现
  1. 默认情况下,regex_replace输出整个输入序列。未与正则表达式匹配的部分会原样输出;匹配的部分按格式字符串指定的格式输出。我们可以通过在regex_replace调用中指定format_no_copy来改变这种默认行为。

    int main()
    {
    	string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})";
        regex r(phone); // 寻找模式所用的regex对象
        smatch m;
        string s;
        string fmt = "$2.$5.$7"; // 将号码格式改为ddd.ddd.dddd
        // 从输入文件中读取每条记录
        while (getline(cin, s))
            cout << regex_replace(s, r, fmt, format_no_copy) << endl;
       	return 0;
    }
    

随机数

  1. 定义在头文件random中。
随机数库的组成 解释
引擎 类型,生成随机unsigned整数序列
分布 类型,使用引擎返回服从特定概率分布的随机数
  1. C++程序不应该使用库函数rand,而应使用default_random_engine类和恰当的分布类对象
随机数引擎操作 解释
Engine e; 默认构造函数;使用该引擎类型默认的种子
Engine e(s); 使用整数值s作为种子
e.seed(s) 使用种子s重置引擎的状态
e.min() or e.max() 此引擎可生成的最小值和最大值
Engine::result_type 此引擎生成的unsigned整型类型
e.discard(u) 将引擎推进u步;u的类型为unsigned long long
  1. 我们传递的是引擎本身,而不是它生成的下一个值,原因是某些分布可能需要调用引擎多次才能得到一个值。

  2. 当我们说随机数发生器时,是指分布对象和引擎对象的组合。

    // 返回一个vector,包含100个均匀分布的随机数
    vector<unsigned> good_randVec()
    {
    	// 由于我们希望引擎和分布对象保持状态,因此应该将它们
    	// 定义为static的,从而每次调用都生成新的数
        static default_random_engine e;
        static uniform_int_distribution<unsigned> u(0, 9); // 包含边界值0和9
        vector<unsigned> ret;
        for (size_t i = 0; i < 100; ++i)
            ret.push_back(u(e));
        return ret;
    }
    
  3. 一个给定的随机数发生器一直会生成相同的随机数序列。一个函数如果定义了局部的随机数发生器,应该将其(包括引擎和分布对象)定义为static的。否则,每次调用函数都会生成相同的序列。

  4. 选择一个好的种子最常用的方法是调用系统函数time。这个函数定义在头文件ctime中,它返回从一个特定时刻到当前经过了多少秒。函数time接受单个指针参数,它指向用于写入时间的数据结构。如果此指针为空,则函数简单地返回时间:

    default_random_engine e1(time(0)); // 稍微随机些的种子
    

    由于time返回以秒计的时间,因此这种方式只适用于生成种子的间隔为秒级或更长的应用。

  5. 如果程序作为一个自动过程的一部分反复运行,将time的返回值作为种子的方式就无效了;它可能多次使用的都是相同的种子。

分布类型的操作 解释
Dist d; 默认构造函数;使d准备好被使用。其他构造函数依赖于Dist的类型。分布类型的构造函数是explicit的。
d(e) 使用相同的e连续调用d的话,会根据d的分布式类型生成一个随机数序列;e是一个随机数引擎对象
d.min() or d.max() 返回d(e)能生成的最小值和最大值
d.reset() 重建d的状态,使得随后对d的使用不依赖于d已经生成的值
  1. 每个分布模板都有一个默认模板实参。由于分布类型只有一个模板实参,因此当我们希望使用默认随机数类型时要记得在模板名之后使用空尖括号:
// 空<>表示我们希望使用默认结果类型
uniform_real_distribution<> u(0, 1); // 默认生成double值
  1. 由于引擎返回相同的随机数序列,所以我们必须在循环外声明引擎对象。否则,每步循环都会创建一个新引擎,从而每步循环都会生成相同的值。类似的,分布对象也要保持状态,因此也应该在循环外定义。

IO库再探

  1. 当操纵符改变流的格式状态时,通常改变后的状态对所有后续IO都生效。

  2. 操纵符hex、oct和dex只影响整型运算对象,浮点值的表示形式不受影响。

  3. precision成员或使用setprecision操纵符来改变精度。precision成员是重载的。一个版本接受一个int值,将精度设置为此值,并返回旧精度值。另一个版本不接受参数,返回当前精度值。setprecision操纵符接受一个参数,用来设置精度。

定义在iostream中的操纵符 解释
boolalpha boolaalpha将true和false输出为字符串
noboolalpha 将true和false输为1,0
showbase 对整数值输出表示进制的前缀
noshowbase 不生成表示进制的前缀
showpoint 对浮点值总是显示小数
noshowpoint 只有当浮点值包含小数部分时才显示小数点
showpos 对非负数显示+
noshwpos 对非负数不显示+
uppercase 对十六进制值中打印0X,在科学计数法中打印E
nouppercase 对十六进制值中打印0x,在科学计数法中打印e
dec 整型值显示为十进制
hex 整型值显示为十六进制
oct 整型值显示为八进制
left 在值的右侧添加填充字符
right 在值的左侧添加填充字符
internal 在符号和值之间添加填充字符
setw() 指定下个数字或字符串的最小空间
setfill() 允许指定下个字符代替默认的空格来补白输出
fixed 浮点值显示为定点十进制
scientific 浮点值显示为科学计数法
hexfloat 浮点数显示为十六进制(C++11新特性)
defaultfloat 重置浮点数格式为是进制(C++11新特性)
unitbuf 每次输出操作后都刷新缓冲区
nounitbuf 恢复正常的缓冲区刷新方式
skipws 输入运算符跳过空白符
noskips 输入运算符不跳过空白符
flush 刷新ostream缓冲区
ends 插入空字符,然后刷新ostream缓冲区
endl 插入换行,然后刷新ostream缓冲区
cout.precision() 返回当前打印精度
cout.precision(int) 设置打印精度
setprecision(int) 设置打印精度
  1. 除非你需要控制浮点数的表示形式(如,按列打印数据或打印表示金额或百分比的数据),否则由标准库选择计数法是最好的方式。

  2. 输出补白:

    • setw指定下一个数字或字符串值的最小空间

    • left表示左对齐输出

    • right表示右对齐输出,右对齐是默认格式

    • internal控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满所有中间空间。

    • setfill允许指定一个字符替代默认的空格来补白输出。

  3. setw类似endl,不改变输出流的内部状态,它只决定下一个输出的大小。

定义在iomanip中的操纵符 解释
setfill(ch) 用ch填充空白
setprecision(n) 将浮点精度设置为n
setw(w) 读或写值的宽度为w个字符
setbase(b) 将整数输出为b进制
  1. 操纵符noskipws会令输入运算符读取空白符,而不是跳过它们。为了恢复默认行为,我们可以使用skipws操纵符。
单字节底层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:它退回从流中读取的最后一个值。但它接受一个参数,此参数必须与最后读取的值相同。
  1. 头文件cstdio定义了一个名为EOF的const,我们可以用它来检测从get返回的值是否是文件尾。
多字节底层IO操作 解释
is.get(sink, size, delim) 从is中读取最多size字节,并保存在字符数组中,字符数组的起始地址由sink给出。读取过程直至遇到字符delim或读取了size个字节或遇到文件尾时停止。如果遇到了delim,则将其留在输入流中,不读取出来存入sink
is.getline(sink, size, delim) 与接收三个参数的get版本类似,但会读取并丢弃delim。上述两个函数无论哪个都不会将分隔符保存在sink中。
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默认值为文件尾
  1. 一个常见的错误是本想从流中删除分隔符,但却忘了做。

  2. 如果在调用gcount之前调用了peek、unget或putback,则gcount的返回值为0

seek和tell函数 解释
—— g版本表示我们正在“获得”(读取)数据,而p版本表示我们正在“放置”(写入)数据:
tellg() 或 tellp() 返回一个输入流中(tellg)或输出流中(tellp)标记的当前位置
seekg(pos) 或 seekp(pos) 在一个输入流或输出流中将标记重定位到给定的绝对地址。pos通常是前一个tellg或tellp返回的值
seekp(off, from) 或 seekg(off, from) 在一个输入流或输出流中将标记定位到from之前或之后off个字符,from可以是下列值之一:
beg,偏移量相对于流开始位置
cur,偏移量相对于流当前位置
end,偏移量相对于流结尾位置
  1. 即使标准库进行了区分“放置”和“获得”版本,但它在一个流中只维护单一的标记——并不存在独立的读标记和写标记。

  2. 由于只有单一的标记,因此只要我们在读写操作间切换,就必须进行seek操作来重定位标记。

posted @ 2021-07-30 22:20  橘崽崽啊  阅读(72)  评论(0编辑  收藏  举报