笔记目录
目录
前言
本文主要讲解关于流类的一些知识,同时以这些知识为基础引入类的继承等性质的介绍。
一、格式化输出
格式化输出通过添加流操纵符来实现,这种操纵符大部分的作用是持久的,这意味着它们的作用会一直有效除非它们被明确地改变,某些则是短暂的,只影响下一个输出的数据值,输出流操纵符主要包含以下几种,带有参数的操纵符需要依赖头文件<iomanip>,其他在头文件<iostream>中即可运行。
endl | 表示行结束 |
setw(n) | (作用短暂)将下一个输出字段的宽度设置为n,不够用空格填充 |
setprecision(digits) | (作用持久)将输出流的精度设置为digits,精度的说明要结合输出模式的设置,如果设置为fixed或scientific模式,精度指小数点后的位数,否则为有效数字的位数并且不考虑数字在什么地方 |
setfill(ch) | (作用持久)常与setw一起使用,为流设置填充字符。 |
left | (作用持久)设置左对齐 |
right | (作用持久)设置右对齐 |
fixed | (作用持久)指定浮点数输出完整呈现。 |
scentific | (作用持久)指定浮点数以科学技术发输出 |
showpoint noshowpoint | (作用持久)showpoint强制要求加上小数点,可以使用noshowpoint进行恢复 |
showpos noshowpos | (作用持久)控制正数前面是否应该有一个正号 |
uppercase nouppercase | (作用持久)控制作为数据转换的一部分所产生的的任意字母的大小写,比如科学计数法中的E,默认为小写字母 |
boolalpha noboolalpha | (作用持久)控制布尔值的出现形式,一般以数值形式出现,使用boolalpha输出true/false形式。 |
可以举个例子,比如想要输出两列数,这两列数纵向对齐,可以采用以下的代码:
#include <iostream>
#include <iomanip>
#include <cmath>
...
cout << right << setw(2) << i
<< setw(2) << j << endl;
二、格式化输入
对于格式化输入介绍两个流操纵符,它们在<iostream>中即可使用:
skipws noskipws | 控制>>在读取第一个值之前的空格是否忽略空白字符,默认忽略,作用持久。 |
ws | 从输入流中读取字符,直到它实用空白字符,这个流操纵符的作用是跳过输入中的任何空白字符、制表符。 |
使用ws的举例如下,这样输入“schubert China"之后,country变量也可以读入”China".
int main()
{
string name,country;
cout << "Enter your full name:";
cin >> name >> ws >> country;
cout << "Hello ," << name << "!" << endl;
cout << country <<endl;
return 0;
}
三、数据文件
1.使用文件流
C++流库中若干类形成一个层次结构,文件流依赖于<fstream>库提供的两个类:ifstream和ofstream。和学习其它语言一样,对于文件的使用而言,读取文件的流程操作是十分重要的,在C++中,读或者写一个文件需要以下步骤
- 声明一个指向某个文件的流变量,处理文件的程序通常为每一个活动文件声明一个流变量。
#include <fstream> void main(){ ifstream infile; ofstream outfile; return ;}
- 打开文件,需要使用open方法,但是open只支持字符串字面值(C风格),所以如果是string类的文件名,需要使用c_str()方法转换。
infile.open(filename.c_str());
- 传输数据,使用的方法包括输入流以及输出流的方法,之后介绍。
- 关闭文件,使用close方法
infile.close();
2.单个字符的输入/输出
从本小节开始介绍传输数据的具体方法实现。首先是文件字符的输入,所有输入流都支持以下方法:
stream>>variable | 将格式化数据读入到一个变量中。 |
stream.get(var) | 将下一个字符读入到字符变量var中,var是引用参数。返回值是流本身,如果没有更多字符,设置fail标志。 |
stream.get() | 返回流的下一个字符。返回值是一个整数,它可识别以常量EOF表示文件结尾的字符。 |
stream.unget() | 复制流的内部指针以便最后读取的一个字符能再次被下一个get调用读取,也就是将大部分最近的字符推回到输入流中,这些字符在下一个get函数中还可以调用。 |
getline(stream,str) | 将stream中下一行读入到字符串变量str中。getline函数返回流,简化了对文件结尾的测试 |
读取一个文件的所有字符的一般模式如下:
int ch;
while((ch=infile.get())!=EOF){
Perform some operation on the character
}
之所以用get方法返回一个int变量,而不是char是因为字符会使程序难以检测到输入文件的结尾,由于char类型值只有256种可能的字符代码,一个数据文件可能包含这些值中的任何一个,不存在一个值可以被作为表示文件结束条件的信号量,定义get返回一个整数意味着函数可以返回合法字符代码范围之外的一个值来表示文件结束的条件,该值即为EOF。单个字符的读入可以使用ifstream.get(ch)方法,其他输入方法之后介绍。
对于字符的输出,所有输出流支持以下方法:
stream<<expression | 将格式化数据写入到一个输出流 |
stream.put(ch) | 将字符ch写入到输出流 |
当然这里的输出流并非写入文件之中,而是将内容输出到控制台上。
while(infile.get(ch)){
outfile.put(ch)
}
3.面向行的输入/输出
这里主要用到的是getline流函数,它和<iostream>中的getline函数并不一样(是它的重载),实例如下
string line;
while (getline(infile.line)){
cout << line <<endl;}
4.格式化输入/输出
格式化的输入输出主要依靠>>以及<<实现,通过infile>>variable即可实现将文件中的数据逐字输入变量,但是如果数据没有以正确的方式格式化,程序在没有给出错误发生的提示就会退出,故障通常会有两个:到达文件结尾,该处已没有更多的数据可供读取;第二是试图从文件中读取不能转化为整数的数据。可以读取数据后添加下面的代码,告知用户数据错误是否发生:
if(!infile.eof()){
error("Data error in file");
}
实际上,关于所有流类,还有以下常用的方法:
stream.fail() | 如果流处于失效状态,返回true |
stream.eof() | 如果流位于文件的结尾,返回true,用于判断故障提示是否是由于到达文件的结尾引起的 |
stream.clear() | 重置流的状态位,当一个故障发生后,无论何时需要重新使用一个流,都必须调用这个函数 |
if(stream) | 判断流是否有效,就大部分情况而言,这个测试和调用if(!stream.fail())的效果相同 |
5.字符串流
很多情况下需要将字符串转换为整数,虽然atoi函数可以将字符串转换为一个整数,但是由于函数是在<string>类库之前出现的,他要求使用C字符串,C字符串使用起来更不方便,所以我们还有将string字符串转换为整数的需求,因此,如果有一种方法可以使用相同的代码从字符串中读取整数,那么stringToInteger函数的实现就会成为可能,而字符串流类istringstream提供了这个功能,提供字符串流依赖于<sstream>库。标程如下,需要注意的是,ws的使用让输入的数据后面必须出现空白字符,否则就会激发error函数。
#include <string>
#include <sstream>
#include <iostream>
int stringtoInteger(string str) {
istringstream stream(str);
int value;
stream >> value>>ws;//第一个>>自动跳过出现在值前面的空白字符,ws则会读取任何出现在值后面的空白字符
if (stream.fail() || !stream.eof()) {
error("stringToInteger: Illegal integer format");
}
return value;
}
如果是字符串转向整数的装换,可以使用ostringstream类:
#include <string>
#include <sstream>
#include <iostream>
int integerToString(int n) {
ostringstream stream;
stream << n;
return stream.str(); //str函数复制了内部字符串的值,使它可以返回给调用者
}
6.一个用于控制台输入的更棒策略
面临一个需求:输入一个整数,这个输入需要检查数据的有效性,比如输入1t,程序会要求重新输入,标程如下:
int getInteger(string prompt) {
int value;
string line;
while (true) {
cout << prompt;
getline(cin, line);
istringstream stream(line);
stream >> value >> ws;
if (!stream.fail() && stream.eof())break;
cout << "Illegal integer format.Try again:" << endl;
}
return value;
}
四、类层次
1.流类层次
类是C++有别于C的一个重要的特色,它提供了封装和继承,将数据表示和与其相关的操作组合成一个一致的整体,并且每一个类自动获取上面一个层次的类的特征,尽管相对于python,C++趋向较少使用继承,但是仍然是一个面向对象模式区别于早期程序模型的特征。
对于之前我们的介绍,接触到了文件流和字符串流,它们各自包含自己的输入输出流,它们也是有层次关系的。这个层次的顶端是类ios,它代表一个最通用的流类型,可以被用于任何一种输入/输出,它允许的方法如表5所示。然后该层次被分为两类(istream和ostream),也即输入流和输出流,它们允许的方法分别为表3和表4所示,同时这两类还继承了ios的方法,也就是说这两类的对象使用方法更多,但是作为平衡,适用对象的范围减小了。自然而然的,ifstream和istringstream就作为了istream的子类,ostream是ofstream和ostringstream共同的父类,子类继承了父类中所有的方法。cout,cin属于输出输入流,但是不属于文件流和字符串流。cerr属于一种标准输出流。
2.在流层次中选择正确的层次
有以下实例:
void copyStream(istream & is, ostream & os) {
char ch;
while (is.get(ch))
{
os.put(ch);
}
}
在这个实例中,我们直接采用了输入输出流类作为输入参数的数据类型,这使得cout流也可以作为函数的参数,函数调用如下:
copyStream(infile, cout);
但是,如果函数中的参数变量选择ofstream,以上调用就不能运行,因为cout不是文件输出流,所以正如上文中说的那样,子类对象使用方法更多,但是作为平衡,适用对象的范围减小了,作为程序设计者根据需要使用正确的类层次才是正确的做法。
总结
本文主要介绍一些关于流类的知识,主要包含使用C++进行各种数据的读写与输出方法。