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偏重于读。

猜你喜欢

转载自www.cnblogs.com/inchbyinch/p/12398454.html