2015年2月6日
周五 晴
很快到星期五了,感觉这一个星期都是在复习,这两周过后我觉得c++的基本知识应该掌握的差不多了,可以追求一点更高层次的东西了。
今天讲一讲c++中的输入、输出和文件操作,差不多c++基本语法就结束了。可能以后的笔记中不会再强调基本的语法知识。
——————————————分割线——————————————
其实在c语言中,我们就已经学习了基本的输入输出和文件操作,像什么printf、scanf、fopen、fclose之类的。其实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个:cin、cout、cerr、clog
还有四个是用于宽字符流的:wcin、wcout、wcerr、wclog。(知道即可)
cin和cout代表标准的输入输出流,cerr和clog代表标准错误流。他们之间是有区别的,cout是带有缓冲的并且可以重定向,而cerr和clog是没有缓冲且不可重定向的。我们先看代码再解释什么是重定向:
#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能够重定向,cerr和clog是不具备重定向的。所以用cerr和clog的好处就是无论什么情况下我都能输出任何信息(一般是错误信息)。cin类似cout,这里就不再重复了。
2.其他IO类方法
除了上面要讲的四个对象之外,还有给大家补充几点常用IO类的方法(成员函数)。
(1)get和put
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获取了“0、a、A”这三个字符的ACSII码并用cout输出,我们又用put输出了这个三个整数的字符形式。并且我们可以发现一个很奇怪的现象,每两次输入之间空了好几行,为什么呢?
因为,get把我们回车也输入进去了。所以我们可以看到,“回车”的ACSII码是10,put输出的是回车,cout<<endl又回了一次车,所以每两次输入间空了两行。
如果get读取到的字符是EOF(end 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中你可能用过sprintf和sscanf这样的格式化字符串操作(不懂的自己百度学习哦)。我们可以一起写个程序回顾一下这两个函数:
#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代替字符数组,所以一般字符串类型都是在操作string。ostringstream是将数据写入字符串,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”读出name,age,height(注意和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中这些操作的区别。学会用面向对象(类)的思维去体会这些操作。