[C++] IO总结

C++中的IO库提供了用于输入输出的各种流,除此之外还提供了管理流和缓冲区等功能。IO库结构如下图所示:
[C++] IO总结_第1张图片

相同颜色的类位于同一个头文件中

  • ios_base:表示流的基本特征;
  • ios:继承于ios_base,提供了一个指向streambuf的指针;
  • streambuf:为缓冲区提供了内存,并提供了用于操作缓冲区的方法;
  • istream:继承于ios类,提供了输入方法;
  • ostream:继承于ios类,提供了输出方法;
  • iostream:继承于istreamostream,提供了输入输出方法;
  • ifstream:继承于istream,提供了对文件进行输入的方法;
  • ofstream:继承于ostream,提供了对文件进行输出的方法;
  • fstream:继承于iostream,提供了对文件进行输入输出的方法;
  • istringstream:继承于istream,对字符串进行操作的输入流类;
  • ostringstream:继承于ostream,对字符串进行操作的输出流类;
  • stringstream:继承于iostream,对字符串进行操作的输入输出流类;

1.标准IO

C++中为我们提供了四个标准IO对象:

  • cin:标准输入流对象,默认情况下关联到标准输入设备(键盘)。
  • cout:标准输出流对象,默认情况下关联到标准输出设备(显示器)。
  • clog:表示错误的标准输出流对象,默认情况下关联到标准输出设备(显示器)。
  • cerr: 用于日志记录的标准输出流对象,默认情况下关联到标准输出设备(显示器)。该流未被缓冲,这意味这信息将直接发送给屏幕,而不是等到缓冲区中填满数据或有新的换行符才会发送。

实际上应有8个对象,针对宽字符wchar_t,以上4个对象都有对应的处理wchar_t的对象。

cout,clog,cerr默认都会输出到显示器,但对输出重定向后,只会影响cout输出内容,clog和cerr不受影响。

Linux系统中,运算符2>可以重定向错误输出。

1.1.输入

使用cin进行读取已经用的够多了,常用的读取方法有:

  • operator >>:用于读取满足参数的类型(基本数据类型都可以),读取时将忽略空白符号;
  • int get():读取单个字符,读取到文件尾时,返回EOF;
  • istream& get (char& c):读取单个字符到c,读取到文件尾时,返回false;
  • istream& get (char* s, streamsize n):用于读取C风格字符串,它不会丢弃换行符,换行符将继续留在输入缓冲区中,因此接下来的输入操作首先将读取换行符;
  • istream& getline(char* s, streamsize n ):用于读取C风格字符串,通过换行符来确定行尾,但不保存换行符,而是将换行符用空字符来替换;

示例:

#include 
#include 

int main()
{
        char c;
        std::cout << "please enter a character: ";
        // 读取单个字符
        std::cin.get(c);
        std::cout << "you entered char: " << c << std::endl;
        char firstname[12];
        char lastname[20];
        //清除缓冲区中的Enter
        std::cin.get();
        //读取c风格字符串
        std::cout << "please enter first name: ";
        //get(char*,int)会保留换行符,因此需要清除
        std::cin.get(firstname,12).get();
        std::cout << "please enter last name: ";
        std::cin.getline(lastname,20);
        std::cout << "your name: " << firstname << "." << lastname << std::endl;
        return 0;
}

1.1.1.输入错误时的处理

ios_base中包含一个用来描述流状态的数据成员,由3个标记位组成:

  • ios_base::eofbit:当cin操作到达文件末尾时,设置此标记位;
  • ios_base::failbit:当cin未能读取到预期字符时,设置此标记位;
  • ios_base::badbit:当存在无法诊断的失败破坏流时,设置此标记位;

以上任何一个如果被设置,那么流将对后面的输入/输出关闭,直到位被清除。

clear()方法负责重置标记位。

示例:

#include 

int main()
{
        using namespace std;
        int score;
        for (int i=0;i < 10;i++) {
                cout << "please enter your score: ";
                while(!(cin >> score)){
                        cin.clear(); //clear state bit
                        while(cin.get() != '\n') { //清空输入队列中的错误字符
                                continue;
                        }   
                        cout << "please enter a number: ";
                        continue;
                }
                cout << "your score: " << score << endl;
        }
        return 0;
}

1.2.输出

ostream类中对>>运算符做了重载,用来进行输出。除此之外,还有两个方法:

  • ostream& put (char c):向输出流中插入一个字符;
  • ostream& write (const char* s, streamsize n):将s指向的数组的前n个字符插入到流中;
#include 

int main()
{
        char ch; 
        char firstname[12] ={'\0'};
        std::cout << "Please Enter a char: ";
        (std::cin >> ch).get();
        std::cout << "you enterd char: ";
        //put()输出单个字符
        std::cout.put(ch);
        std::cout << std::endl << "please enter your fistname: ";
        std::cin.getline(firstname,12);
        //write()输出一个字符串
        std::cout.write(firstname,12);
        return 0;
}

1.2.1.刷新缓冲区

cout进行输出时,并非直接输出到显示器,而是先输出到缓冲区,再由缓冲区刷新到显示器。ostream类提供了两个控制符可以进行强制刷新缓冲区:

  • flush:刷新缓冲区;
  • endl:刷新缓冲区,并且插入一个换行符号;

1.2.1.格式化输出

1.修改进制

cout 默认十进制输出,可通过如下方式修改:

  • cout << hex:十六进制输出
  • cout << oct:八进制输出
  • cout << dec:十进制输出(默认)

示例:

#include 

int main()
{
        int i = 12; 
        std::cout << "dec: " << i << std::endl;
        std::cout << std::hex;
        std::cout << "hex: " << i << std::endl;
        std::cout << std::oct;
        std::cout << "oct: " << i << std::endl;
        return 0;
}

2.调整字符宽度、填充字符

ostream类的成员函数width用来设置宽度:

streamsize width() const;//get
streamsize width (streamsize wide);//set

width只影响下一个显示的内容,然后字段宽度恢复为默认值。

示例:

#include 

int main()
{
        char ch = '#';
        std::cout << ch << std::endl;
        std::cout.width(10);//设置宽度为10
        std::cout.fill('*');//填充宽度中为被使用的部分
        std::cout << ch << std::endl;
        std::cout << ch << std::endl;

        return 0;
}
/*
Output:
#
*********#
#
*/

3.修改浮点数精度

c++中浮点数默认精度为6,但不打印末尾的0,可通过成员函数precision()设置:

streamsize precision() const;//get
streamsize precision (streamsize prec);//set

width()不同,新的精度将一直有效。

4.setf()成员函数设置格式和控制符

ios_base中提供了许多的标记可以用来进行格式化,将这些标记作为函数setf()的参数就可以设置对应的格式了.

fmtflags setf (fmtflags fmtfl);//设置新标记,返回原来标记的设置
fmtflags setf (fmtflags fmtfl, fmtflags mask);

比如常用的控制数字和字符的标记:

常量 含义
ios_base::showpoint 显示末尾的小数点
ios_base::boolalpha 输入/输出对应的bool值
ios_base::showbase 输出时使用基数前缀

在使用时可以这样:

int i = 0;
cout.setf(ios_base::showpoint|ios_base::boolalpha);
cout << i << endl;
/*输出: false而不是0*/

使用setf()并不是进行格式化最好的方法,C++还提供了和每个标记对应的控制符,来直接调用setf(),如:

控制符 调用
std::showpoint setf(ios_base::showpoint)
std::boolalpha setf(ios_base::boolalpha)
std::showbase setf(ios_base::showbase)

使用时:

int i = 0;
cout << boolalpha << i << endl;

更多的格式化标记请参考API:std::ios_base::fmtflags

5.iomanip

头文件iomanip中提供了更为方便的方法来进行格式化输出,常用函数有:

  • setbase (int base):设置进制;
  • setfill (char_type c):设置填充字符;
  • setprecision (int n):设置精度;
  • setw (int n):设置宽度;
  • setiosflags(ios_base::fmtflags mask):设置ios_base中提供的标记。

示例:

#include      // std::cin, std::cout
#include       // 

int main ()
{
        double d = 2.3333675;
        char c = '#';
        std::cout << std::setw(10); //width
        std::cout << std::setfill('*'); //fill
        std::cout << c << std::endl;
        std::cout << std::setprecision(3);//precision
        std::cout << d << std::endl;
        std::cout << std::setiosflags(std::ios_base::showpos); 
        std::cout << d << std::endl;
        return 0;
} 
/*
*********#
2.33
+2.33
*/

2.文件IO

C++中用于对文件进行IO操作的类位于头文件fstream中,对文件进行读取操作时,需要ifstream对象,对文件进行写入操作时,需要ofstream对象。

从IO结构图来看,ifstreamofstream分别继承于istreamostream,因此文件IO的大部分操作和标准IO类似,但却更为复杂,这些复杂主要体现在对文件的操作:

  • 1.打开文件方式:只读方式、只写方式、读写方式;
  • 2.写入文件方式:创建新文件、追加到旧文件、覆盖旧

因此,C++中提供了多个文件打开模式,用来描述文件如何关联到ifstreamofstream

2.1.文件打开模式

这些模式作为ios_base的数据成员,如下:

常量 含义
ios_base::app 追加到文件尾(append)
ios_base::ate 打开文件,并流位置标记移到文件尾(at end)
ios_base::binary 二进制文件,非文本文件
ios_base::in 打开文件,允许流进行输入操作,即只读
ios_base::out 打开文件,允许流进行输出操作,即只写
ios_base::trunc 如果文件存在,则打开文件时进行清零

2.1.使用ifstream读取文件

构造方法

ifstream构造方法如下:

ifstream();//默认构造
explicit ifstream (const char* filename, ios_base::openmode mode = ios_base::in);
explicit ifstream (const string& filename, ios_base::openmode mode = ios_base::in);
ifstream (const ifstream&) = delete;//拷贝构造不可用
ifstream (ifstream&& x);//移动构造

成员函数

因为ifstream继承于istream,因此istream中的所有public成员函数,ifstream都是可用的,如<<get(char&)getline()等:

std::ifstream fin("io1.cpp");
char c;
char str[100];
fin >> str;
fin.get(c)
fin.getline(str,100);

此外,ifstream类中添加了用于操作文件的几个成员函数:

  • void open(const char* filename, ios_base::openmode mode = ios_base::in);打开文件,将其与流对象关联,以便对其内容执行输入/输出操作;默认打开模式为只读;
  • void open(const string& filename, ios_base::openmode mode = ios_base::in);
  • bool is_open() const:返回流当前是否与文件关联(是否成功打开文件);
  • void close():关闭流,断开当前关联的文件对象;

示例:

#include 
#include 
#include  // for exit()

const char* file = "file.txt";
int main()
{
        using namespace std;
        ifstream fin;
        fin.open(file);//打开文件,和fin关联,打开模式默认为只读
        if(!fin.is_open()){//如果关联失败,退出程序
                cerr << "Could not open the file: " << file << endl;
                exit(EXIT_FAILURE);
        }
        char ch;
        while((ch = fin.get()) != EOF) {//按字符读取输出
                cout << ch;    
        }   
        fin.close();//关闭流,断开与文件的关联
        cout << "====Read file Done." << endl;
        return 0;
}

2.2.使用ofstream写入文件

构造方法

ofstream构造方法如下:

ofstream();//默认构造
//指定关联文件、打开文件模式
explicit ofstream (const char* filename, ios_base::openmode mode = ios_base::out);
explicit ofstream (const string& filename, ios_base::openmode mode = ios_base::out);
ofstream (const ofstream&) = delete;//拷贝构造不可用
ofstream (ofstream&& x);//移动构造

ofstream类继承与ostream类,因此sstream中的所有public成员函数,ofstream都是可用的,同样地,ofstream类中也提供了和ifstream类相同的用于管理文件的成员函数,常用的有open()is_open()close().

示例:

#include 
#include 
#include 

const char* file = "file.txt";
int main()
{
        using namespace std;

        cout << "Start write to " << file << endl;
        ofstream fout;//ofstream对象
        //打开文件,和fout关联,并制定打开模式
        fout.open(file,ios_base::out|ios_base::app);
        if (!fout.is_open()) {//关联成功
                cerr << "Could not open the file: " << file << endl;
                exit(EXIT_FAILURE);
        }
        cout << "please Enter the content you want to write in " << file << endl;
        char ch;
        //从键盘读取
        while((ch = cin.get()) != '\n') {
                fout << ch;//写入文件
        }
        fout.close();//关闭fout,断开关联
        cout << "Write file Done." << endl;
        return 0;
}

2.3.二进制文件IO

除了对文本文件进行IO操作外,可以对二进制文件进行IO操作。有些数据如字符串等可以以文本格式进行读写,这样做的好处时具有易读性,然而有些数据并不适合以文本形式读写,比如要存储一个类信息,针对于这种数据,最佳方式就是使用二进制模式来读写。二进制文件IO操作时:

  • 打开文件模式需要指定ios_base::binary;
  • 读取文件使用read();
  • 写入文件使用write().

read()函数原型如下:

istream& read (char* s, streamsize n);

表示从流中提取n个字符,并将它们存储在s指向的数组中。

write()函数原型如下:

ostream& write (const char* s, streamsize n);

表示将s指向的数组的前n个字符插入到输出流中。

两者都只复制一个数据块,而不检查其内容。

示例:

#include 
#include 
#include 
#include 

void read_person();
void write_person();

class Person
{
private:
       int age;
       char name[20] = {'\0'};
public: 
       Person(){}
       Person(int age,const char* name):age(age){
               std::strcpy(this->name,name);         
            
       }   
       ~Person(){}
       void show() const { 
               std::cout << "name: " << name << ",age: " << age << std::endl;
       }   
};

int main()
{
       write_person();
       read_person();
       return 0;
}

void read_person() {
       Person p1; 
       std::ifstream infile_b("person_info.bin",std::ios_base::in | std::ios_base::binary);
       if(!infile_b.is_open()) {
               std::cerr << "Could't open the file!" << std::endl;
               exit(EXIT_FAILURE);
       }   
       while(infile_b.read((char*)&p1,sizeof(p1))) {
               p1.show();
       }
       infile_b.close();
       std::cout << "Read Successful " << std::endl;   
}

void write_person() {

       Person p1(21,"Zhangsan");
       Person p2(32,"XiaoWang");
           
       std::ofstream outfile_b("person_info.bin", std::ios_base::out | std::ios_base::app
                | std::ios_base::binary);
       if(outfile_b.is_open()) {
               outfile_b.write((char*)&p1,sizeof(p1));
               outfile_b.write((char*)&p2,sizeof(p2));
       }
       outfile_b.close();
       std::cout << "Write successful. " << std::endl;
}

Person中使用char[]而非string,因为string内部实际上包含一个指向字符串的指针,因此在进行复制时,将得到字符串的存储地址。

需要注意的是,使用二进制格式对类进行文件存取的方式,不适合具有虚函数的类,因此在写入时,会写入隐式vtpr指针,当读取后,可能出现错误。

你可能感兴趣的:(C++)