C++以面向对象的观点,把I/O(input/output)抽象为流类。I/O流提供了低级和高级的输入/输出功能。低级I/O是无格式的数据传输,对字节序列不作解释;高级I/O是格式化的数据传输,系统会对字节序列按照数据类型进行解释。
流类库有三个基类streambuf(流缓冲区)类,ios类,iostream_init类(用于流类初始化操作)。
1.ifstream是文件输入流,用于对文件的提取操作
2.istrstream是字符串输入流,用于对字符串的提取操作
3.istream_withassign是重载赋值运算符的输入流类,标准输入流cin属于该类对象
1.ostream是文件输出流
2.ostrstream是字符串输出流
3.ostream_withassign是重载赋值运算符的输出流类,标准输出流cout属于该类对象
istream和ostream派生出iostream类。有三个派生类:fstream,strstream,stdiostream。
函数 | 功能 |
---|---|
read() | istream& read (char* s, streamsize n) 读取流中n个字节并存入s中 |
get() | 从流中提取字符(包括空格) |
get的不同形式
函数 | 功能 |
---|---|
int get() | 从输入流中提取一个字符,并将字符的ASCII作为返回值,遇到文件结束符返回EOF |
istream& get(char& a) | 从指定输入流中提取一个字符,并写入到a中。e.g: char c;c= cin.get()//cin.get(c) |
istream& get(char* a,int n, char d = ‘\n’) | 从流当前字符开始,读取n-1个字符,或遇到指定分隔符d结束,默认换行符。字符串后会添加结束符’\0’(所以是读取n-1个字符) |
istream& getline(char* a,int n,char d =’\n’) | 从流当前字符开始,读取n-1个字符,其他同上,区别在于get()在缓冲区不会丢弃d,getline()会丢弃d。但是都不会存入到a里面。 |
char c[10];
char test;
cin.getline(c, 10, 'e');
cout << c <<endl;
cin.get(test);
cout << test << endl;
char c[10];
char test;
cin.get(c, 10, 'e');
cout << c <<endl;
cin.get(test);
cout << test << endl;
这里我们看出,cin.getline()读取到’e’时,会从缓冲区拿出来,但不放进存储的数组里面;而cin.get()读取到‘e’时,不会从缓冲区拿出来,后面cin>>test,test就能拿到e了。
另外,假如在指定取10个字符(包括结束符,即从缓冲区取9个)的情况下,如果你输入了11个字符,前面用cin.get来获取缓冲区的字符,后面再cin>>test是没有问题的。结果如图:
但如果用cin.getline来获取缓冲区字符,后面cin>>test就不能获取了。
因为超过了字符,cin.getline()会将输入流的failbit设为1,后面的输入流操作就失败了,这时候如果想让后面的cin>>test拿到相应的值,就要加上cin.clear()将输入流的goodbit设为1。
char c[10];
char test;
cin.getline(c, 10, 'e');
cout << c <<endl;
cout <<"failbit的状态:"<< cin.fail() << endl;
cin.clear();
cin.get(test);
cout << test << endl;
函数 | 功能 |
---|---|
ignore | 提取并丢弃流中指定字符 |
peek | 返回流中的下一个字符的ASCII码 |
gcount | 统计最后输入的字符(非标准操作cin>>)个数 |
seekg | 移动输入流指针 |
tellg | 返回输入流中指定位置的指针值 |
char c[10];
char test[10];
cin.get(c, 10, 'e');
cout << c << endl;
cout << cin.gcount() << endl;
cout <<"下一个字符:"<< char(cin.peek()) << endl;
cin.ignore(10, 'e');//忽略十个字符,将其从流中删除。
cin.get(test,10);
cout << test << endl;
cout << cin.gcount() << endl;
函数 | 功能 |
---|---|
put | 无格式,插入一个字节 |
write | 无格式插入一个字符序列 |
flush | 刷新输出流 |
seekp | 移动输出流指针 |
tellp | 返回输出流中指定位置的指针值 |
char c;
cin.get(c);
cout.put(c);
输入a,输出a。其他函数常在进行文件处理时使用。
标识常量 | 值 | 意义 |
---|---|---|
goodbit | 0x00 | 状态正常 |
eofbit | 0x01 | 遇到文件结束符 |
failbit | 0x02 | i/o操作失败,数据未丢失,可恢复 |
badbit | 0x04 | 非法操作,数据丢失不可恢复 |
1.int good() const;
2.int eof() const;
3.int fail() const;
4.int bad()const;
上述函数可以判断对应标识常量的状态。
!cin 在good()返回1时该函数返回0,可用于判断流状态。
int rdstate() const该函数返回状态字,fail()是failbit或者badbit
设置后才返回1
**void clear(int nState = 0)**将流状态设置为对应的状态字,默认参数0,对应ios::goodbit。使用时可使用cin.clear(ios::goodbit)形式。
char c;
cin.get(c);
cout << cin.good() << endl;
cout << cin.eof() << endl;
cout << cin.fail() << endl;
cout << cin.bad() << endl;
标志常量 | 意义 | 输入输出 |
---|---|---|
ios::left | 设置左对齐输出 | O |
ios::right | 设置右对齐输出 | O |
ios::interal | 在符号位或基数指示符后填入字符 | O |
cout.width(5);//设置宽度为5
cout.fill('@');//设置填充字符
cout.setf(ios::left);//设置左对齐
cout << 5 << endl;
cout.setf(ios::right, ios::left);//设置右对齐,取消左对齐
cout.width(5);//重新设置宽度,否则下面是没有设置的
cout << 5 << endl;
标志常量 | 意义 | 输入输出 |
---|---|---|
ios::dec | 转化为十进制形式 | I/O |
ios::oct | 转化为八进制形式 | I/O |
ios::hex | 转化为十六进制形式 | I/O |
ios::showbase | 输出中显示基数指示符(输出十六进制前+0x,八进制前+0) | O |
ios::uppercase | 输出十六进制时一律用大写字母 | O |
ios::showpoint | 显示小数点 | O |
ios::showpos | 正数前加“+”号 | O |
ios::scientific | 科学计数法显示浮点数 | O |
ios::fixed | 定点形式显示浮点数 | O |
ios::basefield 值为dec|oct|hex
ios::adjustfield 值为left|right|internal
ios::floatfield 值为scientific|fixed
函数 | 功能 |
---|---|
long setf(long flag) | 设置参数flag指定的标志位,返回更新前的标志字 |
long setf(long aflag,long bflag) | 将b标志位清0,将a标志位置1 |
long unsetf(long flag) | 将flag标志位清0 |
int width(int a) | 将下一个显示宽度设置为a,默认右对齐,如果a小于数据宽度,则无效。 |
int width() const | 返回当前的输出宽度 |
char fill(char c) | 当设置的数据宽度大于输出的数据宽度,填充字符c,默认空白符 |
char fill() const | 返回当前使用的填充符 |
int precision(int p) | 设置数据显示精度,如果浮点数以定点形式输出,p为小数点后的数字位数;如果以科学记数法输出,p为位数精度位数(包括小数点) |
int precision() const | 返回当前数据的显示精度值 |
int a, b, c;
cin >> a;
cin.setf(ios::oct,ios::basefield);//表明输入时是八进制
cin >> b;
cin.setf(ios::hex,ios::basefield);//表明输入时是十六进制
cin >> c;
cout << "十进制显示:" << endl;
cout << "a = " << a << "\nb = " << b << "\nc =" << c << endl;
cout << "八进制显示:" << endl;
cout.setf(ios::oct, ios::basefield);//表明八进制输出
cout << "a = " << a << "\nb = " << b << "\nc =" << c << endl;
cout << "十六进制显示:" << endl;
cout.setf(ios::hex, ios::basefield);//表明十六进制输出
cout << "a = " << a << "\nb = " << b << "\nc =" << c << endl;
cout << "带符号的十六进制显示:" << endl;
cout.setf(ios::uppercase | ios::showpos | ios::showbase);
cout << "a = " << a << "\nb = " << b << "\nc =" << c << endl;
设置新的显示方式前,要将与之矛盾的标志位清0,否则会出现问题。
double x = 22.0 / 7;
int i;
cout << x << endl;
cout << "output in fixed:\n";
cout.setf(ios::fixed | ios::showpos);//定点输出,显示+号
for (i = 1; i <= 5; i++)
{
cout.precision(i);
cout <<x<< endl;
}
cout << "compare with the last:" << endl;
cout.setf(ios::scientific, ios::showpos);//未将定点输出位置0
for (i = 1; i <= 5; i++)
{
cout.precision(i);
cout << x *1e5<< endl;
}
cout << "output in scientific:\n";
cout.setf(ios::scientific,ios::fixed |ios::showpos);//将定点输出位置0
for (i = 1; i <= 5; i++)
{
cout.precision(i);
cout << x * 1e5 << endl;//x乘10的五次方
}
上面格式控制函数可能略有繁琐,C++在istream和ostream中定义了一批函数,可作为"<<“和”>>"的右操作数,实现格式控制。
1.iostream中的控制符
控制符 | 功能 | 输入/输出 |
---|---|---|
endl | 输出一个换行符,并清空流 | O |
ends | 输出一个字符串结束符(相当于空格) | O |
flush | 清空缓冲区 | O |
dec | 用十进制输入或输出 | I/O |
oct | 用八进制输入或输出 | I/O |
hex | 用十六进制输入或输出 | I/O |
int a,b,c;
cin>>dec>>a;
cin>>oct>>b;
cin>>hex>>c;//下面若不注明,cin>>d则是和最后一次使用控制符一样,十六进制
cout<<dec<<a;
cout<<oct<<b;
cout<<hex<<c;
2.iomanip中的控制符
使用前应#include
控制符 | 功能 | 输入/输出 |
---|---|---|
resetiosflags(ios::flag) | 清除flag指定的标志位 | I/O |
setiosflags(ios::flag) | 设置flag指定的标志位 | I/O |
setbase(int b) | 设定基数8,10,16从而实现各种进制输入输出 | I/O |
setfill(char c) | 设置填充符 | O |
setprecision(int n) | 设置浮点数输出宽度 | O |
setw(int n) | 设置宽度 | O |
int a,b;
double c = 22.0 / 7;
cin >> a>>b;
cout << resetiosflags(ios::basefield) << setiosflags(ios::oct) << a << endl;
cout << a<<' '<<b << endl;
cout << setbase(10) << a <<' '<< b << endl;
cout << setiosflags(ios::right) << setfill('*') << setw(5) << a << endl;
cout << setiosflags(ios::fixed) << setprecision(5) << c<<endl;
C++标准流对象是内存与外部设备的数据传输,C++还可以定义连接内存的流对象——字符串流。
1.strstream、istrstream、ostrstream是ios的派生类,支持C语言的字符串输入、输出。使用这些类要#include
2.stringstream、istringstream、ostringstream类支持从string对象输入/输出数据。使用这些类要#include
#include
#include
#include
using namespace std;
int main()
{
string testStr("Input test 256 * 0.5");//空格注意,避免把数据当字符串提取了
string s1, s2, s3;
double x, y;
istringstream input(testStr);//建立istringstream类对象,与串对象连接
input >> s1 >> s2 >> x >> s3 >> y;
cout << s1 <<' '<< s2 << ' ' << x << s3 << y << '=' << x * y << endl;
}
#include
#include
using namespace std;
int main()
{
const char* testStr = "Input test 256 * 0.5";
char s1[10], s2[10], s3[10];
double x, y;
istrstream input(testStr);//建立istrstream对象
input >> s1 >> s2 >> x >> s3 >> y;
cout << s1 << ' ' << s2 << ' ' << x << s3 << y << '=' << x * y << endl;
}
#include
#include
using namespace std;
int main()
{
ostringstream Output;//建立输出流对象
double x, y;
cout << "Input x :";
cin >> x;
cout << "Input y :";
cin >> y;
Output << x << "*" << y << "=" << x * y << endl;
cout << Output.str();
}
#include
#include
using namespace std;
int main()
{
char buf[80];
ostrstream Output(buf, sizeof(buf));//建立输出流对象
double x, y;
cout << "Input x :";
cin >> x;
cout << "Input y :";
cin >> y;
Output << x << "*" << y << "=" << x * y << '\0';
cout << buf<<endl;
}
文件处理都是按文件指针来操作,进行一次读操作,写操作后,对应的指针位置都会发生变动,下一次的读/写操作就从指针的位置开始操作。
标识常量 | 意义 |
---|---|
ios::cur | 当前流指针位置 |
ios::beg | 流的开始位置 |
ios::end | 流的尾部 |
函数 | 功能 |
---|---|
seekp(long n) | 相当于以文件头部为参考,将指针向后移动n个字节 |
seekp(long n,ios::seek_dir dir) | 以dir为参照,将指针向后移动n个字节 |
seekp(n)相当于seekp(n,ios::beg),fstream类中有seekg和seekp前者为输入流指针(get),后者为输出流指针(put)。两个函数用法类似。
打开文件:(1)流类 对象名;对象名.open(文件名,方式); (2)流类对象名(文件名,方式)
流类:ifstream(只读方式),ofstream(写方式),fstream(可读又可写)
方式
标识常量 | 意义 |
---|---|
ios::in | 读方式打开文件,文件指针在文件头。 |
ios::out | 写方式打开文件,文件不存在则创建,但不能创建目录 |
ios::ate | 打开文件时文件指针指向文件末尾,可通过移动文件指针的方式读写数据,不存在则创建文件 |
ios::app | 追加方式,将向文件中输出的内容追加到文件尾部,不存在则创建文件 |
ios::trunc | 删除文件现有的内容(ios::out的默认操作) |
ios::nocreate | 如果文件不存在,则打开操作失败 |
ios::noreplace | 如果文件存在,则打开操作失败 |
ios::binary | 以二进制代码方式打开,默认为文本方式 |
另外,
ios::in|ios::out 打开文件时不会清空内容,文件指针在文件头。
ios::app|ios::out 打开文件时不会清空内容,文件指针在文件尾部,且移动指针操作无效。
ios::ate|ios::out 打开文件时清空文件内容。
ios::app|ios::in|ios::out 打开文件时不会清空文件内容,文件指针在文件尾部,且移动指针操作无效
文件名可使用绝对路径和相对路径,文件路径使用参照:
https://www.cnblogs.com/vranger/p/3820783.html
对象名.close() 当一个流对象的生存周期结束时,系统会自动关闭文件,但是为了将缓冲区的东西及时写入文件,应手动关闭文件。
文本文件写入可以用插入符,流对象名.get()等等。在想把文本文件里的字符解释为int应该用插入符。如果单纯是解释为字符的话可以用.get()形式等
二进制文件写入用流对象名.write((char *)&s,sizeof(s))。读取内容用流对象名.read((char *)&s,sizeof(s))来读取相应写入的内容。
示例:
文本文件
#include
#include
#include
using namespace std;
class student {
public:
string name;
int age;
student(string c = "", int a=0) :name(c), age(a) {}
};
int main()
{
student s;
int op = 1;
ofstream output("test.txt", ios::app);//建立文件输出流对象
while (op)
{
cout << "姓名:"; cin >> s.name;
cout << "年龄:"; cin >> s.age;
output << s.name << ' ' << s.age<<endl;//分隔符有必要,没有读取文件时会将年龄也放入string中。
cout << "(1/0)?"; cin >> op;
}
output.close();
cout << "读取文件测试" << endl;
ifstream input("test.txt", ios::in);
if (!input)//当文件不存在时!input返回1
cout << "文件不存在" << endl;
else
while (input >> s.name >> s.age)//检测是否到达文件尾
cout << "姓名:" << s.name << ' ' << "年龄:" << s.age << endl;//会跳过分隔符
input.close();
}
#include
#include
#include
using namespace std;
class student {
public:
string name;
int age;
student(string c = "", int a=0) :name(c), age(a) {}
};
int main()
{
student s;
int op = 1;
ofstream output("test.dat", ios::binary|ios::out);
while (op)
{
cout << "姓名:"; cin >> s.name;
cout << "年龄:"; cin >> s.age;
output.write((char*)&s, sizeof(s));//二进制文件写入方式
cout << "(1/0)?"; cin >> op;
}
s.age = 0; s.name = "";
output.write((char*)&s, sizeof(s));//写入空记录,作读文件的结束标准
output.close();
cout << "读取文件测试" << endl;
ifstream input("test.dat", ios::in|ios::binary);
if (!input)//当文件不存在时!input返回1
cout << "文件不存在" << endl;
else
do {
input.read((char*)&s, sizeof(s));
if(s.age != 0)
cout << "姓名:" << s.name << ' ' << "年龄:" << s.age << endl;
} while (s.age!=0);
input.close();
}
二进制文件中假如存入的数据是double类型1.5,而你想要进行修改成2.0,此时只需要移动文件指针就可以了。对于文本文件,因为里面都是字符,想要修改会比较麻烦。
#include
#include
using namespace std;
int main()
{
ofstream output("test.dat", ios::binary | ios::out);
double a[5] = { 1.1,2.2,3.3,4.4,5.5 };
double c;
for (int i = 0; i < 5; i++)
output.write((char*)&a[i], sizeof(double));
output.close();//写文件操作
fstream input("test.dat", ios::binary | ios::in|ios::out);//可读又可写方便修改
cout << "修改数值前" << endl;
for(int i=0;i<5;i++)
{
input.read((char*)&c, sizeof(double));
cout << c << endl;
}//输出修改前的数据
input.seekg(0);//移动读指针在文件头
double d = 6.6;
for (int i = 0; i < 5; i++)
{
input.seekg(i*sizeof(double), ios::beg);
input.read((char*)&c, 8);
if (c == 3.3)
{
input.seekp(i*sizeof(double), ios::beg);//移动写指针的位置在修改地数值前
input.write((char*)&d, 8);//覆写
}
}
input.seekg(0); input.seekp(0);//主要讲读指针放在文件头,再次输出内容
cout << "将3.3替换成6.6:" << endl;
for (int i = 0; i < 5; i++)
{
input.read((char*)&c, sizeof(double));
cout << c << endl;
}
input.close();
}
对于文本文件,每个文件的最后都会有个文件结束符(内存上十六进制是0xFF,十进制为-1),我们通常使用 流对象名.eof()判断是否结束,这时候会有一些问题出现。
如上面文本文件是1空格2空格3但在a是char类型的情况下,用input.eof()作为循环判断条件会出现重复输出的情况。因为input>>a,每次会读取文件一个字符(空格会忽略),已经读取完3的时候,input.eof()还是返回0因为流没有读取到eof。继续执行循环,此时读到EOF,input>>a无效,就输出上一次的值,并且这时候才会将流的eofbit设置为1。(就是eofbit是根据上次读入的值设置)
但在a是int类型的情况下,却没有重复输出。a在文本文件中应该是要读取一个数字,可能是个位数,十位数。假如是十位数就必须读入一个字符后继续读入一个字符看是否还是数字,这样读到空格的时候才会认为这次读入结束。在上例文本中,输出2后,流会读取3,然后继续读入下一个字符,读到EOF认为本次读入结束,这时候EOF已经是被流读取了,eofbit设置为1。这样就不会重复输出。
在同样的文本中,a是char类型,这次输出就没有重复输出了。是因为循环语句中带有input.peek();这条语句在读取完这次内容后,去看看下一个内容,但不改变流指针位置。如第一次输出3的时候也就是第一次读取3后,input.peek();会看3后面是什么,看到EOF不止是当作这次函数的返回值,同时会将eofbit设置为1。这样就不会进入下次循环了。
另外,如果用流对象名作为循环判断标准,结果会不同,一样的文本,a是int类型,这时候还是会重复输出3。流对象作为循环判断标准,当fail()或bad()返回1时,流对象的bool型才是0。当指针读到EOF时,eofbit置1,input仍然是1。这时候再执行一次循环,指针继续读下去,发现不能读下去后,将failbit设置为1,这时才退出循环。
如果想让a是char类型时不会重复输出,可以用input.peek()!=EOF或者input>>a作为判断标准,这时候就不会重复输出了。
这时候,无论a是int类型还是char类型还是会重复输出。都是因为没有读入EOF,a是int类型的时候读到3后面的空格也没有继续读取。这时候就算用peek()作为作为判断条件也是会重复输出,用input>>a就可以了。
感觉判断条件用input>>a无论哪种情况都不会重复输出。
二进制文件里面不是用ASCII码解释的,文件指针指向文件尾ios::end时,不一定能表示最后一个完整记录的结束,如果用eof来判断,会出现些问题。
通常可以加一个意味结束的标志作为文件的结束记录。如上面通过判断age是否等于0来判断文件是否结束。
......
s.age = 0; s.name = "";
output.write((char*)&s, sizeof(s));//写入空记录,作读文件的结束标准
......
do {
input.read((char*)&s, sizeof(s));
if(s.age != 0)
cout << "姓名:" << s.name << ' ' << "年龄:" << s.age << endl;}while(s.age!=0)
......