C++课程学习笔记第七周(上):输入输出
前言:本文主要是根据MOOC网北大课程——《程序设计与算法(三):C++面向对象程序设计》内容整理归纳而来,整理的课程大纲详见 https://www.cnblogs.com/inchbyinch/p/12398921.html
本文介绍了常用的输入输出流、流操纵算子、文件读写,以及缓冲与缓存的区别。
1 输入输出流
1.1 基本IO类相关
输入输出相关类:
标准流对象:cin, cout, cerr, clog
判断输入流结束:if(cin>>x), if(cin.get(..))等(估计本质上是调用了cin.good(),windows屏幕中输入流结束符为单独一行ctrl+z)
istream类成员函数:
- istream & cin.operator>>();
- istream & cin.get()等;
- istream & getline(char * buf, int bufSize)等;
- bool eof(); 判断输入流是否结束
- int peek(); 返回下一个字符,但不从流中去掉.
- istream & putback(char c); 将字符ch放回输入流
- istream & ignore(int nCount = 1, int delim = EOF);
输出输入重定向:
- freopen("test.txt", "w", stdout); //将标准输出重定向到 test.txt文件
- freopen("t.txt", "r", stdin); //cin被改为从 t.txt中读取数据
1.2 cin的详细用法:
https://blog.csdn.net/bravedence/article/details/77282039
cin读取数据方式常用三种:cin>> , cin.get() , cin.getline() .
- 在屏幕上输入数据后,需要敲一个回车才能把数据送到缓冲区中,回车符会被替换成'\n',作为一个字符存入缓冲区中;
- cin读取数据也是从缓冲区中获取数据,缓冲区为空时,cin的成员函数会阻塞等待数据的到来,一旦缓冲区中有数据,就触发cin的成员函数去读取数据。
- cin>> 读取数据时,会跳过并清除开头的空白字符,继续读取下一个字符;若读取成功,后面未读取成功的字符(包括换行符等空白字符)会残留在缓冲区中,cin不做处理。(推测:cin>>读取字符数组时会在末尾自动加上'\0',因为能正确cout)
- cin.get常用的有四种,前两种读取一个字符,后两种可读取一行;开头不会跳过空白字符,读取一行字符时遇到'\n'停止,但'\n'仍残留在缓冲区中,不做处理。(故推荐用getline读取一行)
- cin.getline不会跳过开头的空白字符,持续读取直至将'\n'读入char数组,并将最后一个字符'\n'换成'\0'。(此时缓冲区中已经没有这个'\n'了。)
- 若cin.get或cin.getline读入字符个数达到限定值,则读入失败,cin.fail()为1,(虽然此次已经读入,但下次cin不可用,)可用if(cin.getline(..))判断是否读入成功,可用cin.clear()重置。
- cin.getline之后可以用cin.gcount()来查看成功读取的字符个数。
- 注意cin.getline()和getline()不同,前者读进char数组中,后者读进string中。
cin的条件状态函数:https://www.cnblogs.com/wangduo/p/5940884.html
- 每一个IO对象都会维护一组条件状态属性,用来表示对象当前的条件状态,包括eofbit、failbit、badbit、goodbit等;
- 获取、设置条件状态的函数有:s.eof()、s.fail()、s.bad()、s.good()、s.clear()、s.rdstate()等;
- 最简单常用的判断方法是 if(cin>>c),if(cin.getline(..))
cin清空输入缓冲区:
- 为了避免上一次操作在缓冲区中残留数据导致下一次的输入受影响,可以手动清空缓冲区;
- cin.ignore(numeric_limits < std::streamsize > ::max(), '\n')清除缓冲区的当前行(包括换行符),不指定delim时默认清除整个cin缓冲区.
2 流操纵算子
- 使用流操纵算子需要包含头文件iomanip
- 整数流的基数:流操纵算子dec,oct,hex,setbase
- 控制浮点数精度precision,setprecision
- 设置浮点数小数点位置固定与非固定setiosflags(ios::fixed),resetiosflags(ios::fixed)
- 设置域宽setw, width(宽度设置有效性是一次性的),以及宽度不足的填充方式setfill()
- 也可以自定义流操纵算子
3 文件读写
文件打开后一定要及时关闭,读取文件之前,要把该文件的写入流关闭,否则出错。
3.1 创建文件
ifstream //文件读取流
ofstream //文件写入流
fstream //文件读写流
//创建流对象时直接关联文件
ofstream fout("clients.dat", ios::out|ios::binary);
//先创建对象,再打开文件
ofstream fout;
fout.open("clients.dat", ios::out|ios::binary);
//判断打开是否成功
if(!fout){
cout << “File open error!”<<endl;
}
//第一个参数:(Windows下)文件的绝对路径和相对路径
"c:\\tmp\\mydir\\some.txt" //绝对路径
"\\tmp\\mydir\\some.txt" //当前盘符的根目录下的tmp\dir\some.txt
"tmp\\mydir\\some.txt" //当前文件夹的tmp子文件夹里面的…
"..\\tmp\\mydir\\some.txt" //当前文件夹的父文件夹下面的tmp子文件夹里面的…
//第二个参数:文件打开方式
ios::in //只读
ios::out //只写
ios::app //追加模式,从文件末尾开始写,防止丢失文件中原来就有的内容
ios::ate //打开一个文件时,将位置移动到文件尾
ios::binary //二进制模式
ios::nocreate //打开一个文件时,如果文件不存在,不创建文件
ios::noreplace // 打开一个文件时,如果文件不存在,创建该文件
ios::trunc // 打开一个文件,然后清空内容
3.2 文件的读写指针
相关函数:
- f.seekp 定位写指针
- f.seekg 定位读指针
- f.tellp 获取写指针的位置
- f.tellg 获取读指针的位置
文件指针位置在c++中的用法:
- ios::beg // 文件头
- ios::end // 文件尾
- ios::cur // 当前位置
//文件的读写指针
ofstream fout("a1.out", ios::app); //以添加方式打开
long location = fout.tellp(); //取得写指针的位置
location = 10;
fout.seekp(location); // 将写指针移动到第10个字节处
fout.seekp(location, ios::beg); //从头数location
fout.seekp(location, ios::cur); //从当前位置数location
fout.seekp(location, ios::end); //从尾部数location,location可以为负值
ifstream fin(“a1.in”,ios::ate); //打开文件,定位文件指针到文件尾
long location = fin.tellg(); //取得读指针的位置
fin.seekg(10); // 将读指针移动到第10个字节处
3.3 字符文件读写
因为文件流也是流,所以流的成员函数和流操作算子也同样适用于文件流。
//写一个程序,将文件 in.txt 里面的整数排序后,输出到out.txt
//原in.txt的内容为:1 234 9 45 6 879
//执行后out.txt内容为:1 6 9 45 234 879
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
vector<int> v;
ifstream srcFile("in.txt", ios::in);
ofstream destFile("out.txt", ios::out);
int x;
while(srcFile >> x)
v.push_back(x);
sort(v.begin(),v.end());
for(int i=0; i<v.size(); i++)
destFile << v[i] << " ";
destFile.close(); //文件要及时关闭
srcFile.close();
return 0;
}
3.4 二进制文件读写
- 读取二进制文件用 istream& read(char* s, long n);
- 写入二进制文件用 istream& write(const char* s, long n);
- 前者为ifstream和fstream的成员函数,将文件内容读入地址s处的内存中;后者为ofstream和fstream的成员函数,将内存s处的内容写入文件中;读/写指针均随之移动。
- 读写指针实际是一个指针,在fstream对象打开的文件中,尽量显式地说明指针的位置。
//示例1:从键盘输入几个学生的姓名的成绩,并以二进制文件形式保存、读取、修改
#include <iostream>
#include <cstring>
#include <fstream>
using namespace std;
struct Student {
char name[20];
int score;
};
int main(){
Student s;
//创建写入流文件,写入数据
ofstream outFile( "students.dat", ios::out|ios::binary);
if(!outFile){
cerr << "error" << endl;
return 0;
}
cout << "输入学生信息,格式示例:Jane 68" << endl;
while(cin >> s.name >> s.score)
outFile.write((char*)&s, sizeof(s));
outFile.close(); //需要及时关闭
//创建读取流文件,读取数据
ifstream inFile("students.dat", ios::in|ios::binary );
if(!inFile){
cerr << "error" << endl;
return 0;
}
cout << endl;
while(inFile.read((char* )&s, sizeof(s))){
int readedBytes = inFile.gcount(); //看刚才读了多少字节
cout << s.name << " " << s.score << endl;
}
inFile.close();
//将students.dat文件的第三个学生名字改成Mike
fstream ioFile("students.dat", ios::in|ios::out|ios::binary);
if(!ioFile){
cout << "error";
return 0;
}
ioFile.seekp(2 * sizeof(s), ios::beg); //定位写指针到第三个记录
ioFile.write("Mike", strlen("Mike")+1);
ioFile.seekg(0, ios::beg); //定位读指针到开头,需要显式地说明位置
cout << endl;
while(ioFile.read((char* )&s, sizeof(s)))
cout << s.name << " " << s.score << endl;
ioFile.close();
return 0;
}
//示例2:文件拷贝程序mycopy,将src.dat拷贝到dest.dat
//用法示例:mycopy src.dat dest.dat
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char * argv[]){
if(argc != 3){
cout << "File name missing!" << endl;
return 0;
}
ifstream inFile(argv[1], ios::binary|ios::in); //打开文件用于读
if(!inFile ){
cout << "Source file open error." << endl;
return 0;
}
ofstream outFile(argv[2], ios::binary|ios::out); //打开文件用于写
if(!outFile){
cout << "New file open error." << endl;
inFile.close(); //打开的文件一定要关闭
return 0;
}
char c;
while(inFile.get(c)) //每次读取一个字符
outFile.put(c); //每次写入一个字符
outFile.close();
inFile.close();
return 0;
}
3.5 二进制文件和文本文件的区别
不同系统下换行符的表示不同:
- Linux,Unix下的换行符号:‘\n’(ASCII码: 0x0a)
- Windows 下的换行符号:‘\r\n’(ASCII码: 0x0d0a) endl 就是 '\n'
- Mac OS下的换行符号:‘\r’(ASCII码:0x0d)
导致不同系统下二进制文件和文本文件的区别:
- Linux, Mac OS文本文件在Windows记事本中打开时不换行。
- Unix/Linux下打开文件,用不用 ios::binary 没区别。
- Windows下打开文件,如果不用 ios::binary,则读取文件时,所有的 '\r\n’会被当做一个字符'\n'处理,即少读了一个字符'\r';写入文件时,写入单独的'\n'时,系统自动在前面加一个'\r',即多写了一个'\r'。
4 其余相关:缓冲区和缓存
https://www.cnblogs.com/mlgjb/p/7991903.html
缓冲区(buffer)
- 缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来存储数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
- 缓冲区的三种类型:全缓冲、行缓冲和不带缓冲。
- 缓冲区的刷新:当缓冲区满或关闭文件时自动刷新,还可以显式刷新(endl、flush、ends)。
缓存(cache):
- 缓存是个很大的概念,有CPU的缓存、浏览器的缓存等。
- CPU的Cache是用来解决CPU与内存之间速度不匹配的问题,避免内存与辅助内存频繁存取数据,提高系统的执行效率。
缓冲区和缓存的区别
- Buffer的核心作用是用来缓冲,缓和冲击。比如你每秒要写100次硬盘,对系统冲击很大,浪费了大量时间在忙着处理开始写和结束写这两件事。用个buffer暂存起来,变成每10秒写一次硬盘,对系统的冲击就很小。一方面提高效率,一方面减少对硬盘的访问。
- Cache的核心作用是加快取用的速度。比如你一个很复杂的计算做完了,下次还要用结果,就把结果放手边一个好拿的地方存着,下次不用再算了。加快了数据取用的速度。
- 简单来说就是buffer偏重于写,而cache偏重于读。