【c++笔记十三】c++中的输入、输出和文件操作

201526 周五 晴

       很快到星期五了,感觉这一个星期都是在复习,这两周过后我觉得c++的基本知识应该掌握的差不多了,可以追求一点更高层次的东西了。

       今天讲一讲c++中的输入、输出和文件操作,差不多c++基本语法就结束了。可能以后的笔记中不会再强调基本的语法知识。

——————————————分割线——————————————

       其实在c语言中,我们就已经学习了基本的输入输出和文件操作,像什么printfscanffopenfclose之类的。其实c++IO和文件都和c差不多,只不过吧c中的这些都封装到类中去了。我们先看一张图:

 技术分享

       这张图就是所有与c++相关的IO和文件的操作了。我们一起来简单的认识一下他们:

     1)标准的输入输出:

              istream ostream  --> #include <iostream>

              标准对象有cin cout

     2)操作字符串的流:

              istringstream ostringstream  --> #include <sstream>

     (3)操作文件的流:

              ifstream  ofstream  fstream    --> #include <fstream>

       图中外圈的七个流(类),是我们c++中会经常用到的。现在我为大家一一介绍一下他们:

 

一.标准的输入输出

1.标准的输入输出对象

       C++iostream文件中自动创建了8个流对象但我们一般用到其中4个:cincoutcerrclog

       还有四个是用于宽字符流的:wcinwcoutwcerrwclog。(知道即可)

       cincout代表标准的输入输出流,cerrclog代表标准错误流。他们之间是有区别的,cout是带有缓冲的并且可以重定向,而cerrclog是没有缓冲且不可重定向的。我们先看代码再解释什么是重定向:

#include <iostream>
using namespace std;
int main()
{
    cout<<"hello";
    clog<<" world";
    cerr<<"test";
    return 0;
}

        这是一个简单的io小程序,分别用cout clog cerr在屏幕上输出几个单词:

 技术分享

        但是,如果我们用到重定向的话,输出的结果就不是这样了。我们先把编译后生成的.exe(可执行文件)复制一份放到c盘的根目录下,以管理员身份运行命令提示符(cmd)。操作如图:

 技术分享

       先切换到根目录(cd /),再运行01io.exe(上面那段代码的可执行文件) > a.txt 。这“>”就是一个输出重定向符,将01io.exe的输出结果输出到a.txt这个文件中去。(一开始输出都是输出在屏幕上的,但是现在我将输出的目标改为文件,所以叫重定向(重新设定输出的方向))。我们发现屏幕上只输出了“ worldtest”,cout输出  的“hello”不见了。我们再打开a.txt这个文件看看里面的内容:

 技术分享

       我们发现a.txt文件中只有“hello”。这就说明了只有cout能够重定向,cerrclog是不具备重定向的。所以用cerrclog的好处就是无论什么情况下我都能输出任何信息(一般是错误信息)。cin类似cout,这里就不再重复了。

 

2.其他IO方法

       除了上面要讲的四个对象之外,还有给大家补充几点常用IO类的方法(成员函数)。

     (1)getput

               get是得到一个字符,put是输出一个字符。我们看看c++帮助手册里面关于它们的介绍:

 技术分享

 技术分享

        get方法是用在输入流中的(input stream),put方法是用在输出流中(out stream)。put使用方法很简单,就是把一个字符写到输出流中而已。而get我们一般用它的前两个方法:

        A.int get();

           该方法是读入一个字符,并且返回这个字符的整数值(ACSII码)。

#include <iostream>
using namespace std;
int main()
{
    int c;
    while(1){
        c = cin.get();
        cout<<"c="<<c<<endl;
        cout.put(c);
        cout<<endl;
    }
    return 0;
}
技术分享

       我们用get获取了“0aA”这三个字符的ACSII并用cout输出,我们又用put输出了这个三个整数的字符形式。并且我们可以发现一个很奇怪的现象,每两次输入之间空了好几行,为什么呢?

       因为,get把我们回车也输入进去了。所以我们可以看到,“回车”的ACSII码是10put输出的是回车,cout<<endl又回了一次车,所以每两次输入间空了两行。

       如果get读取到的字符是EOFend of file,文件结束符),就会自动结束。


       B.istream& get( char& ch );

          这个get方法读入一个字符的引用,返回的却是一个输入流。当get遇到EOF的时候,返回的istream变为0。例子可以参考c++帮助手册的那个例子。

 

(2)getline

       这种方法是用来读取一行数据的。

 技术分享

        A.istream& getline( char* buffer, streamsize num );

           这种方式的getline将一行字符串放在buffer中,读取num-1个字符或遇到换行或EOF时结束。如:

#include <iostream>
using namespace std;
int main()
{
    char str[20];
    cin.getline(str,10);
    cout<<str<<endl;
    return 0;
}
技术分享

        规定读入10个字符,实际只读入9个字符(还有一个‘\0’)。

        B.istream& getline( char* buffer, streamsize num, char delim );

           这种方法和上面的方法类似,但是输入了num-1个字符或遇到delim字符或遇到EOF时就终止输入,就算换行也不会影响它的输入。如:

#include <iostream>
using namespace std;
int main()
{
    char str[20];
    cout<<"请输入:";
    cin.getline(str,10,'$');
    cout<<"输入的字符串是:"<<str<<endl;
    return 0;
}
技术分享

       我们可以看见,中间我们换了一行可是输入还在继续,直到读到’$’符号的时候,getline才结束,且不会把‘$’字符读进去。

       但是有一点一定要特别注意:如果我们输入的字符串长度超过num-1,就会发生流对象错误,拒绝IO访问。所以遇到流对象发生错误的时候我们要有所处理:

       首先,我们需要纠正流(复位),用到.clear()方法。接着,我们还需要清理缓冲区,用到.ignore()方法。特别需要注意ignore的用法:

istream& ignore( streamsize num=1, int delim=EOF )

       我们先来看看这种流对象错误,并且是不是真正的拒绝了IO访问。

#include <iostream>
using namespace std;
int main()
{
    char str[20];
    cout<<"cin="<<cin<<endl;
    cin.getline(str,10);
    cout<<str<<endl;
    cout<<"cin="<<cin<<endl;
    int num=100;
    cin>>num;
    cout<<"num="<<num<<endl;
    return 0;
}

技术分享

        cin既然是一个对象,它重载了>>运算符,我们就可以输出cin的值看看。我们发现一开始cin的值正常的,可是一旦我们getline的字符串超出了10个字符,cin流就发生错误,值变为了0。并且cin>>num操作也没有进行(拒绝了输入操作),直接输出num的值(num值没有输入,因此不会改变)。

        我们再来更正这个程序:

#include <iostream>
using namespace std;
int main()
{
    char str[20];
    cout<<"cin="<<cin<<endl;
    cin.getline(str,10);
    cout<<str<<endl;
    cout<<"cin="<<cin<<endl;
    if(!cin){
        cin.clear();
        cin.ignore(100,'\n');
    }
    cout<<"cin="<<cin<<endl;
    int num=100;
    cin>>num;
    cout<<"num="<<num<<endl;
    return 0;
}
技术分享

        在getline超出10个字符后cin的值变为0,我们通过纠正cin流并清除输入缓冲区,cin的值又变为正常的值了,最后输入num的值使num值发生改变。


二.字符串的IO

        C中你可能用过sprintfsscanf这样的格式化字符串操作(不懂的自己百度学习哦)。我们可以一起写个程序回顾一下这两个函数:

#include <stdio.h>
int main()
{
    char str[100];
    char name[5]="zm";
    int age=20;
    double height=1.75;
    sprintf(str,"%s:%d:%lf",name,age,height);
    printf("%s\n",str);
 
    int num;
    sscanf("12345","%d",&num);
    printf("%d\n",num);
    return 0;
}
技术分享

       在c++中对应的两个类分别是istringstream,ostringstream。因为c++中使用string代替字符数组,所以一般字符串类型都是在操作stringostringstream是将数据写入字符串,istringstream是把数据从字符串中读出

       看代码就很快学会了:

#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
class Date{
    int year;
    int month;
    int day;
public:
    Date(int year=0,int month=0,int day=0)
        :year(year),month(month),day(day){}
    friend ostream& operator<<(ostream&,const Date&);
};
ostream& operator<<(ostream& os,const Date& date){
    return os<<setfill('0')<<date.year<<"-"
        <<setw(2)<<date.month<<"-"<<setw(2)<<date.day;
}
int main()
{
    string name="zm";
    int age=20;
    double height=1.75;
    ostringstream ostr1;
    ostr1<<name<<":"<<age<<":"<<height;
    string str = ostr1.str();
    cout<<str<<endl;
 
    ostringstream ostr2;
    ostr2<<Date(2015,2,6);
    str=ostr2.str();
    cout<<str<<endl;
 
    istringstream istr("zhc 21 1.76");
    istr>>name>>age>>height;
    cout<<"姓名:"<<name<<endl;
    cout<<"年龄:"<<age<<endl;
    cout<<"身高:"<<height<<endl;
    return 0;
}
技术分享 

       ostringstream类的ostr1用来拼接name(string),age(int),height(double),像普通流对象一样的使用<<运算符(注意观察和sprintf的区别)。.str()方法,是将stringstream对象转换为string字符串。ostr2对象是用来是输出类Date的,我们在Date类中重载了运算符>>,所以ostringstream对象用起来一样没有问题。

       最后我们定义了istringstream类对象istr,用来从字符串“zhc 21 1.76”读出nameageheight(注意和sscanf的区别,<<会自动识别类型)。

 

三.文件操作

       C++中我们使用ifstream创建文件读取流(读文件),用ofstream创建文件输出流(写文件):

                ifstream( const char *filename, openmode mode );

                ofstream( const char *filename, openmode mode );

        第二个参数mode,是指打开文件的模式。基本的打开模式如下图:

 技术分享

     我们使用write方法写文件,read方法读文件:

                istream& read( char* buffer, streamsize num );

                ostream& write( const char* buffer, streamsize num );

        我们经常还会用到:int gcount(); 获取实际读取的字节数。

        文件操作基本和c类似,基本知识还不是很熟悉的自己学哦(百度),我只是讲讲c++中怎么操作而已。

        我们一起来做一道题,把一个结构体整体为单位写入文件中,再从文件中读取出来:

#include <iostream>
#include <fstream>
using namespace std;
struct Date{
    int year;
    int month;
    int day;
    Date(int year=0,int month=0,int day=0)
        :year(year),month(month),day(day){}
    void show(){
        cout<<year<<"-"<<month<<"-"<<day<<endl;
    }
};
int main()
{
    ofstream ofs("a.txt");
    Date date1(2015,2,6);
    ofs.write((const char*)&date1,sizeof(Date));
    ofs.close();
 
    ifstream ifs("a.txt");
    Date date2;
    ifs.read((char*)&date2,sizeof(Date));
    ifs.close();
    date2.show();
    return 0;
}

技术分享

       我们可以看见,我们先把结构体强制类型转换为const char*后写入文件中(按字节写入),最后再用char* 方式从文件中读出来(按字节读)。大家一定要体会,用类的方法去操作文件,原来使用的什么wirte、open都是文件流的成员函数。

 

       最后,我们再综上所有的知识点做一道题目:现在有一个配置文件(如下图),从文件中读出各参数的值。

 技术分享

        我们一起来看代码:

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    string name;
    int price;
    string cpu;
    double size;
    ifstream ifs("xiaomi.txt");
    char temp[20];
    ifs.getline(temp,20,'=');
    ifs>>name;
    ifs.getline(temp,20,'=');
    ifs>>price;
    ifs.getline(temp,20,'=');
    ifs>>cpu;
    ifs.getline(temp,20,'=');
    ifs>>size;
    cout<<name<<endl;
    cout<<"价格:"<<price<<"元"<<endl;
    cout<<"CPU:"<<cpu<<endl;
cout<<"屏幕大小:"<<size<<endl;
ifs.close();
    return 0;
}
技术分享

       运用>>运算符自动识别类型的特性,我们很轻松读取出来了所有值。注意本程序中getline的用法,遇到”=”停止。

 

————————————————结束语———————————————————

       C++中的IO和文件操作,就是用类的思想,把所有以前c中使用的各种函数全部转换为成员函数。大家一定要体会,先建立IO流和文件流再操作的思想。

       总结一下:我们主要是讲了c++中基本IO、字符串IO和文件操作的方式,体验和c中这些操作的区别。学会用面向对象(类)的思维去体会这些操作。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。