C++基础之输入输出流(I/O)


前言

个人学习笔记


一、C++输入输出机制

       C++的I/O发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作;如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。就C++程序而言,I/O操作可以简单地看作是从程序移进或移出字节,程序只需要关心是否正确地输出了字节数据,以及是否正确地输入了要读取字节数据,特定I/O设备的细节对程序员是隐藏的。

类名作用头文件
ios_base抽象基类,管理格式化标志和输入/输出异常iostream
ios抽象基类,管理任意流缓冲iostream
istream通用输入流iostream
ostream通用输出流iostream
iostream通用输入输出流iostream
ifstream文件输入流fstream
ofstream文件输出流fstream
fstream文件输入输出流fstream
istringstream字符串输入流sstream
ostringstream字符串输出流sstream
stringstream字符串输入输出流sstream

相关类图如下:

       在Ubuntu中,进行输入输出时,如果要终止的话可以用ctrl+c/ctrl+z/ctrl+d,虽然它们的效果差不多,但实际操作却不同。

操作作用
ctrl+c是强制中断程序的执行,进程已经终止。
ctrl+z将任务中止(暂停的意思),但是此任务并没有结束,他仍然在进程中他只是维持挂起的状态,用户可以使用fg/bg操作继续前台或后台的任务,fg命令重新启动前台被中断的任务,bg命令把被中断的任务放在后台执行。(例如:当你vim一个文件时,如果需要执行别的shell操作,但是又不打算关闭vim编辑器,因为你得保存退出,因此你可以简单的按下ctrl+z,shell会将vim进程挂起,当你结束了那个shell操作之后,可以用fg命令继续使用vim编辑文件。
ctrl+d不是发送信号,而是表示一个特殊的二进制值,表示 EOF,EOF是一个计算机术语,为End Of File的缩写,通常在文本的最后存在此字符表示文件结束,在使用while进行输入的时候,可以使用这个操作,结束当前的输入,安全退出,所以一般推荐这个操作。(在shell中,ctrl+d表示退出当前shell)

       流的状态有4种,这四种状态都定义在类ios_base中,以枚举类型作为其数据成员存在,他们名称与值分别是:badbit(1)、failbit(4)、eofbit(2)、goodbit(0)。C++标准库还定义了一组成员函数来查询或操作这些状态。如果需要重置流的状态可以使用void clear(std::ios_base::iostate state = std::ios_base::goodbit),clear的无参版本会复位所有错误标志位(重置流的状态)。

  • 1、badbit:表示发生系统级的错误,如不可恢复的读写错误。通常情况下一旦badbit被置位,流就 无法再使用了;对应函数为bool bad() const,若流的badbit置位,则返回true,否则返回false。
  • 2、failbit:表示发生可恢复的错误,如期望读取一个数值,却读出一个字符等错误。这种问题通常是可以修改的,流还可以继续使用;对应函数为bool fail() const,若流的failbit或badbit置位,则返回true,否则返回false。
  • 3、eofbit:当文件达到结束位置时eofbit和failbit会被置位;对应函数为bool eof() const,若流的eofbit置位,则返回true。
  • 4、goodbit:表明流未发生错误(如果badbit、failbit和eofbit任何一个被置位,则检查流状态的条件会失。);对应函数为bool good() const,若流处于有效状态,则返回true。

二、C++标准IO

标准输入流
       对系统指定的标准设备的输入和输出,即从键盘输入数据,输出到显示器屏幕,这种输入输出称为标准的输入输出,简称标准 I/O 。
相关代码如下:

#include <iostream>
#include <string>
#include <limits>
using std::cout;
using std::endl;
using std::cin;
using std::cerr;
using std::string;
void printStreamStatus()
{
    cout << "cin.badbit = " << cin.bad() << endl;
    cout << "cin.failbit = " << cin.fail() << endl;
    cout << "cin.eofbit = " << cin.eof() << endl;
    cout << "cin.goodbit = " << cin.good() << endl;
}
void test()
{
    int number = 0;
    printStreamStatus();
    cin >> number;

    printStreamStatus();
    cin.clear();//重置流的状态
    /* cout << std::numeric_limits<std::streamsize>::max() << endl; */
    cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');//清空缓存区
    printStreamStatus();
    cout << "number = " << number << endl;

    string line;
    cin >> line;
    cout << "line = " << line << endl;
}
void test2()
{
    int number = 0;
    cout << "please input number:" << endl;
    while(cin >> number, !cin.eof())
    {
        //1、ctrl+c是强制中断程序的执行,进程已经终止
        //2、ctrl+z的是将任务中止(暂停的意思),但是此任务并没有结束,
        //他仍然在进程中他只是维持挂起的状态,用户可以使用fg/bg操作
        //继续前台或后台的任务,fg命令重新启动前台被中断的任务,bg命令
        //把被中断的任务放在后台执行.
        //3、ctrl+d 不是发送信号,而是表示一个特殊的二进制值,表示 EOF。
        if(cin.bad())
        {
            cerr << "the stream is bad!" << endl;
            return;//返回函数的返回值
                   //exit返回当前的进程,这里只能用return返回
        }
        else if(cin.fail())
        {
            cin.clear();//不能省略,否则流的状态不会更正,也就无法往下输入
            cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');//清空缓存区
            cout << "please input valid int number:" << endl;
        }
        else
        {
            cout << "number = " << number << endl;
        }
    }
}
int main()
{
    /* test(); */
    test2();
    return 0;
}

标准输出流
       ostream类定义了3个全局输出流对象,即cout,cerr,clog,平常用的最多的就是cout,即标准输出。cout将数据输出到终端,它与标准C输出stdout关联。cerr是标准错误流(非缓冲),clog也是标准错误流(带缓冲)。cerr与clog都是标准错误流,区别在于cerr不经过缓冲区,直接向终端输出信息,而clog中的信息是存放在缓冲区的,缓冲区满后或遇到endl,向终端输出。注意:在C语言中,标准输入、标准输出和标准错误分别用0,1,2文件描述符代表

缓冲区
       缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区输出缓冲区。比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。因此缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU时间过长,解放出CPU,使其能够高效率 工作。缓冲区向上连接了程序的输入输出请求,向下连接了真实的I/O操作。作为中间层,必然需要分别处理好与上下两层之间的接口,以及要处理好上下两层之间的协作。
       缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。

  • 1、全缓冲:在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
  • 2、行缓冲:在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
  • 3、不带缓冲:也就是不进行缓冲,标准出错情况std::cerr是典型代表,这使得出错信息可以尽快地显示出来。

输出缓冲区
       输出缓冲区的内容并写入到实际的输出设备或者文件。以下几种情况会导致输出缓冲区内容被刷新:

  • 1、程序正常结束(有一个收尾操作就是清空缓冲区);
  • 2、缓冲区满(包含正常情况和异常情况);
  • 3、使用操纵符显式地刷新输出缓冲区,如:endl(用来完成换行,并刷新缓冲区)、flush(用来直接刷新缓冲区的)、ends(在输入后加上一个空字符,但是没有刷新缓冲区,这个需要注意,很多书上说可以刷新缓冲区,但经过实际测试,ends输出’\0’在windows下当空格,在linux下当空什么都不输出,没有刷新功能);
  • 4、使用unitbuf(在每次执行完写操作后都刷新输出缓冲区)操纵符设置流的内部状态;
  • 5、输出流与输入流相关联,此时在读输入流时将刷新其关联的输出流的输出缓冲区。
#include <iostream>
#include <unistd.h>
using std::cout;
using std::endl;
using std::flush;
using std::ends;
//输出缓冲区的5种方式
void test()
{
    for(int idx = 0; idx < 1024; ++idx)
    {
        cout << 'a' ;
    }
    /* cout << 'b' ;//ubuntu缓冲区正好是1024个字节 */
    /* cout << "123" << endl;//endl有刷新缓冲区并且换行的特点 */
    /* cout << "123" << flush;//可以刷新缓冲区,但是不能换行 */
    cout << "123" << ends;//没有刷新缓冲区,不能换行
    //ends输出'\0'在windows下当空格,在linux下当空什么都不输出 
    sleep(5);
}
int main()
{
    test();
    return 0;
}

三、C++文件IO

       以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件I/O。其中文件流是以外存文件为输入输出对象的数据流。文件输入流是从外存文件流向内存的数据,文件输出流是从内存流向外存文件的数据。每一个文件流都有一个内存缓冲区与之对应。文件流本身不是文件,而只是以文件为输入输出对象的流。若要对磁盘文件输入输出,就必须通过文件流来实现。C++对文件进行操作的流类型有三个:ifstream(文件输入流),ofstream(文件输出流),fstream(文件输入输出流),他们的构造函数形式都很类似。文件也有4种流的状态,哪个状态出问题了,该状态对应的函数就会返回1。

文件模式
       根据不同的情况,对文件的读写操作,可以采用不同的文件打开模式,文件模式一共有6种,被定义在名为openmode的枚举类型里。

  • 1、in:输入,文件将允许做读操作;如果文件不存在,打开失败。
  • 2、out:输出,文件将允许做写操作;如果文件不存在,则直接创建一个。
  • 3、app:追加,写入的位置在文件的末尾。
  • 4、ate:追加,读入的位置在文件的末尾
  • 5、trunc:截断,如果打开的文件存在,其内容将被丢弃,其大小被截断为零。
  • 6、binary:二进制,读取或写入文件的数据为二进制形式。

代码如下(示例):

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using std::cin;
using std::cout;
using std::endl;
using std::cerr;
using std::ifstream;
using std::ofstream;
using std::fstream;
using std::string;
using std::vector;

void test()
{
    //默认情况下,对于文件输入流而言,当文件不存在的时候,就报错
    //读文件,通过类对象读取文件,下同
    ifstream ifs("test1.txt", std::ios::in);// | std::ios::ate);
    if(!ifs.good())
    {
        cerr << "ifstream is not good" << endl;
        return ;
    }
    cout << "pos = " << ifs.tellg() << endl;
    string line;
    /* while(ifs >> line, !ifs.eof()) */
    while(ifs >> line)//对于文件输入流而言,默认以空格为分隔符,输出时遇到空格就换行
    {
        cout << line << endl;
    }
    
    ifs.close();
}
void test2()
{
	//读文件,通过类对象读取文件,下同
    ifstream ifs("Point1.cc");
    if(!ifs.good())
    {
        cerr << "ifstream is not good" << endl;
        return ;
    }
    string line;
    while(getline(ifs, line))//按行读取,getline是一个全局函数,不是成员函数
    {
        cout << line << endl;
    }
    
    ifs.close();
}
void test3()
{
	// 不同内置类型的转换,会发生截断;对于自定义类型来说就是隐式转换
	// explicit关键字防止隐式转换
	//Point pt3 = 5;//会成为Point pt3 = Point(5, 0),隐式转换,所以需要在构造函数前加explicit,防止隐式转换
	String s3 = "hello";//等价于String s3 = String("hello"),也是隐式转换

	//下面二者等价
	cout << "hello" << endl;
	cout.operator<<(endl);
	
    string filename("FileIO1.cc");//将C风格字符串转为C++风格字符串,使用了隐式转换,如果不想使用的话在string的构造函数前加关键字explicit

	//读文件,通过类对象读取文件,下同
    ifstream ifs(filename);
    if(!ifs.good())
    {
        cerr << "ifstream is not good" << endl;
        return ;
    }
    string line;
    vector<string> vec;//C++语法规则,<>里面写vector存储的类型
    vec.reserve(1024);//可以用这个函数预留空间,如果已经知道文件的大小的话
    while(getline(ifs, line))//按行读取,getline是一个全局函数,不是成员函数
    {
        vec.push_back(line);
        /* cout << line << endl; */
    }
    //遍历读取
    for(int idx = 0; idx != vec.size(); ++idx)
    {
        cout << vec[idx] << endl;
    }
    ifs.close();
}
void test4()
{
	//读文件,通过类对象读取文件,下同
    ifstream ifs("vector.cc");
    if(!ifs.good())
    {
        cerr << "ifstream is not good" << endl;
        return ;
    }

    //对于文件输出流而言,当文件不存在就创建文件
    //当文件存在的时候,先清空文件,然后再进行写操作
    ofstream ofs("test1.txt", std::ios::out | std::ios::app);//追加模式写文件
    if(!ofs.good())
    {
        cerr << "ofstream is bad!" << endl;
        ifs.close();//如果ofs打开失败,就会直接退出,而ifs还没关闭,所以这里需要注意
        return;
    }

	//C语言中有ftell()函数,返回文件指针位置

	//C++中,ofstream.tellp()与ifstream.tellg()函数,返回文件指针当前的位置,
	//追加写ofstream ofs("test1.txt", std::ios::out | std::ios::app);
	//追加读ifstream ifs("test1.txt", std::ios::in | std::ios::ate);


    cout << "pos = " << ofs.tellp() << endl;//获取当前文件指针的位置,读文件时用ifs.tellg()
											//获取当前文件指针的位置,写文件时用ofs.tellp()
    ofs << "hello,world" << endl;
    cout << "pos = " << ofs.tellp() << endl;
    //ofs.tellp();
    /* string line; */
    /* while(getline(ifs, line))//按行读取,getline是一个全局函数,不是成员函数 */
    /* { */
    /*     ofs << line << endl; */
    /* } */
    
    ifs.close();
    ofs.close();
}
void test5()
{
    //对于文件输入输出流而言,当文件不存在的时候就报错
    fstream fs("test1.txt");
    if(!fs.good())
    {
        cerr << "fstream is bad! " << endl;
        return;
    }
    int number = 0;
    for(int idx = 0; idx != 5; ++idx)
    {
        cin >> number;
        fs << number << " ";
    }
    
    //产看文件指针的位置tellg/tellp
    cout << "pos = " << fs.tellg() << endl;//打印当前文件指针的位置


	
	//fseek()为给出的流设置位置数据
	
    //设置文件指针的位置seekg/seekp
    //其中seek中第二个参数可以为beg/end/cur
    //fs.seekg(0, std::ios::beg) 与下一行等价
    fs.seekg(0);//设置当前文件指针的位置
    for(int idx = 0; idx != 5; ++idx)
    {
        fs >> number;
        /* cout << "fs.fail = " << fs.fail() << endl; */
        /* cout << "fs.eof = " << fs.eof() << endl; */
        cout << number << " ";
    }
    cout << endl;
    fs.close();
}
int main()
{
    test5();
    return 0;
}

Vector简介

#include <iostream>
#include <vector>

using std::cout;
using std::endl;
using std::vector;

void printVectorCapacity(const vector<int> &vec)
{
    cout << "vec.size = " << vec.size() << endl;//size()显示当前存储了多少int型数据
    cout << "vec.capacity = " << vec.capacity() << endl;//显示当前vector在没有动态扩容前可以装多少int型数据
}
void test()
{
    //vector的扩容机制:当size() == capacity()的时候,如果还要插入元素
    //,那么就会按照2 * capacity()的大小进行扩容,接着将老的空间中的元素
    //拷贝到新的空,然后将新的元素插入到新空间的后面,最后将老的空间进行回收

	//vector是一个动态数组,
	//可以通过push_back()插入元素,添加到最后
	//在size与capacity相等的时候,再push_back一个元素,vector就会就在新的位置,重新申请,大小是原来的2倍,然后将原来的内容拷贝过来,
    vector<int> vec;
    vec.reserve(5);//如果知道大小的话,可以用这个函数预留空间
    printVectorCapacity(vec);

    cout << endl;
    vec.push_back(1);
    printVectorCapacity(vec);

    cout << endl;
    vec.push_back(2);
    printVectorCapacity(vec);

    cout << endl;
    vec.push_back(3);
    printVectorCapacity(vec);

    cout << endl;
    vec.push_back(4);
    printVectorCapacity(vec);
    
    cout << endl;
    vec.push_back(5);
    printVectorCapacity(vec);
    
    cout << endl;
    vec.push_back(6);
    printVectorCapacity(vec);
    
    cout << endl;
    vec.push_back(7);
    printVectorCapacity(vec);
    
    cout << endl;
    vec.push_back(8);
    printVectorCapacity(vec);
    
    cout << endl;
    vec.push_back(9);
    printVectorCapacity(vec);

	//auto遍历vector
    cout << endl;
    for(auto &elem : vec)
    {
        cout << elem << "  ";
    }
}
int main()
{
    test();
    return 0;
}

四、C++串IO

       对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称串 I/O 。字符串流是以内存中的字符串类对象或者字符数组为输入输出对象的数据流,也即是将数据输出到字符串流对象或者从字符串流对象读入数据,也称之为内存流。C++对字符串进行操作的流类型有三个:istringstream(字符串输入流),ostringstream(字符串输出流),stringstream(字符串输入输出流),他们的构造函数形式都很类似。

代码如下(示例):

#include <iostream>
#include <sstream>
#include <string>
#include <fstream>
using std::cout;
using std::endl;
using std::ostringstream;
using std::istringstream;
using std::stringstream;
using std::string;
using std::ifstream;
string int2string(int number)
{
    ostringstream oss;//ostringstream创建对象oss时,调用了basic_ostringstream(): basic_ostringstream(ios_base::out) {}这个委托构造函数
    oss << number;
    string str1 = oss.str();
    return str1;
}
void test()
{
	//把一个int型数据变成字符串
    string str1 = int2string(10);
    cout << "str1  = " << str1 << endl;
}
void test2()
{
    int number1 = 10;
    int number2 = 20;
    stringstream ss;
    ss << "number1= " << number1 << " ,number2= " << number2 << endl;
    string str1 = ss.str();
    /* cout << str1; */

    string key;
    int value;
    
    /* cout << "hello" << "23333" << endl; */
    while(ss >> key >> value)//遇到空格就换行,所以可以以空格为分界,用key,value分别接收ss中的值
    {
        cout << key << "  " << value << endl;
    }

}
void readConfig(const string &filename)
{
    ifstream ifs(filename);
    if(!ifs.good())
    {
        std::cerr << "ifstream is not good! " << endl;
        return;
    }

    string line;
    string key,value;
    while(getline(ifs, line))
    {   
        istringstream iss(line);

        iss >> key >> value;//遇到空格就换行,所以可以以空格为分界,用key,value分别接收iss中的值
        cout << key << "    " << value << endl;
    }

    ifs.close();
}
void test3()
{
    readConfig("my.conf");//my.conf中第一行:ip 192.168.100.100,第二行:port 22 
}
int main()
{
	//字符串提出来的目的之一就是用来做一些格式转换的
    test3();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值