- 标准输入输出流
- 文件输入输出流
- 字符串输入输出流
标准输入输出流
即标准输入流cin、标准输出流cout。前者指键盘,后者指显示器。
1插入符与提取符
在输入输出流类库中,重载了两种运算符以简化输入输出流的使用,运算符<<常用做输入输出流的插入符,表明“输出到”,例如cout << “Hello”是把字符串“Hello”输出到屏幕上;运算符>>常用做提取符,表明“赋值给”,例如 cin >> i 是把键盘输入的信息赋值给i。
标准输入给不同类型变量赋值的不足示例。
#include <iostream>
using namespace std;
int main() {
int i;
char str[20];
cout << "请输入一个整形数及一个字符串:" ;
cin >> i >> str;
cout << "i=" << i << endl ;
cout << "str=" << str << endl ;
return 0;
}
若在命令行上输入1 how are you?,回车后执行结果为:
请输入一个整形数及一个字符串:1 how are you?
i=1
str=how
实际上在交互过程中,经常需要一次输入一行字符序列,当这些字符安全地存储到缓冲区后再进行扫描和转换工作。在该示例中,并不能把输入的串完全的放到内存中,下面讲到的get系列函数很好的解决了这个问题。
2 get系列函数
常用的有3个函数。
(1)int get() ; 返回输入流一个字符的ASCII值。
(2)istream& get(unsigned char* pszBuf, int nBufLen, char delim=’\n’);
pszBuf: 指向字符缓冲区的指针,用于保存结果 nBufLen: 缓冲区长度 delim: 结束字符,根据结束字符判断何时停止读入操作
(3)istream& getline(unsigned char* pszBuf, int nBufLen, char delim=’\n’); 参数解释同get
第2个get函数及getline函数都是读一行记录,那么它们有什么区别呢?细微而重要的区别在于:当遇到输入流中的界定符(delim,即结束字符)时get()停止执行,但是并不从输入流中提取界定符,直接在字符串缓冲区尾部加结束标志’\0’;函数getline()则相反,它将从输入流中提取界定符,但仍然不会把它存储到结果缓冲区中。
#include <iostream>
using namespace std;
int main() {
char szBuf[60]; //定义输入字符串接收缓冲区
cout << "请输入一个字符串:" ;
int n = cin.get(); //先读1个字符
cin.getline(szBuf, 60); //接着读1行字符,遇到结束符’\0’停止
cout << n << endl;
cout << "The received string is: " << szBuf << endl;
return 0;
}
//输出结果
请输入一个字符串:abcde
97
The received string is: bcde
3 处理流错误
获取状态信息的函数如下:
- int rdstate(); 无参数,返回值既是状态信息特征值。 使用下面函数来检测相应输入/输出状态:
- bool good();若返回值true,一切正常,没有错误发生。
- bool eof(); 若返回值true,表明已到达流的末尾。
- bool fail();若返回值true,表明I/O操作失败,主要原因是非法数据(例如读取数字时遇到字母)。但流可以继续使用。
- bool bad(); 发生了(或许是物理上的)致命性错误,流将不能继续使用。
//判断是否输入整形数。
int main() {
int a;
cout << "请输入一个数据: ";
cin>>a;
cout<<"状态值为: " <<cin.rdstate()<<endl;
if(cin.good())
{
cout<<"输入数据的类型正确,无错误!"<<endl;
}
if(cin.fail())
{
cout<<"输入数据类型错误,非致命错误,可清除输入缓冲区挽回!"<<endl;
}
return 0;
}
//确保输入一个整形数给变量a。
int main() {
int a;
while(1)
{
cin>>a; //赋值给整形数a
if(cin.fail()) //如果输入数据非法,则:
{
cout<<"输入有错!请重新输入"<<endl;
cin.clear();//清空状态标识位
cin.get(); //清空流缓冲区
}
else //如果输入数据合法,则:
{
cout<<a<< endl; //直接在屏幕上输出数据
break; //跳出while循环
}
}
return 0;
}
2 文件输入输出流
C++对文件的读写使用的是ifstream、ofstream和fstream流类,同样是依照“打开-->读写-->关闭”原语进行操作。文件读写所用到的很多常数都在基类ios中被定义出来。ifstream类只用于读取文件数据,ofstream类只用于写入数据到文件,fstream则可用于读取和写入文件的数据。
1 文件打开
ifstream/ofstream/fstream流类都使用构造函数或open函数打开文件,原型如下:
ifstream( const char* szName, int nMode = ios::in, int nProt = filebuf::openprot )
ofstream( const char* szName, int nMode = ios::out, int nProt = filebuf::openprot )
fstream( const char* szName, int nMode, int nProt = filebuf::openprot );
void ifstream::open(const char *filename, int openmode=ios::in)
void ofstream::open(const char *filename, int openmode=ios::out|ios::trunc)
void fstream::open(const char *filename, int openmode=ios::in|ios::out)
均有两个参数,第1个是文件名,第2个是打开方式,打开方式见下述:
- ios::in 以读取方式打开文件
- ios::out 以写入方式打开文件
- ios::app 每次写入数据时,先将文件指针移到文件尾,以追加数据到尾部
- ios::ate 仅初始时将文件指针移到文件尾,仍可在任意位置写入数据
- ios::trunc 写入数据前,先删除文件原有内容(清空文件),当文件不存在时会创建文件
- ios::binary以二进制方式打开文件,不作任何字符转换
2 文件关闭
ifstream/ofstream/fstream流类都通过close函数释放文件资源。 close(); 无参数,一般来说打开文件与关闭文件是成对出现的。
3 文件读写
//读文本文件并显示在屏幕上
int main(){
char szBuf[80];
ifstream in("a.txt");//通过构造函数创建文件读入流
if(!in) return 0; //若文件不存在,返回
while(in.getline(szBuf, 80)) //通过getline函数按行读取内容
{
cout << szBuf << endl; //将读入缓冲区内容输出到屏幕上,判断是否正确
}
in.close();
return 0;
}
//写文本文件:把学生成绩信息保存至文件
struct STUDENT //学生成绩结构体
{
char strName[20]; //姓名
int nGrade; //成绩
};
int main() {
ofstream out;
out.open("d:\\a.txt"); //打开或创建a.txt文本文件
STUDENT st1 ={"张三", 90};//用结构体产生两名学生的成绩信息
STUDENT st2 ={"李四", 80};
out << st1.strName << "\t" << st1.nGrade << endl;//把成绩信息存到文本文件
out << st2.strName << "\t" << st2.nGrade << endl;
out.close();
return 0;
}
可以看出,通过“<<”完成写文本文件是一个较好的方法,这是因为“<<”操作符可把不同类型的数据直接输出。结构体中strName是字符串,nGrade是整形数,但均可直接输出到文件上即可。此外strName与nGrade之间采用“\t”输出是为了数据对齐,而“endl”保证输出“\n”回车符,保证了文本文件的换行,是必须的。
读写二进制文件
主要是通过read()、write()函数完成读写二进制文件功能的,原型如下所示
- ostream& write(const char *, int nSize)
- istream& read(char *, int nSize)
第1个参数表明读写缓冲区的头指针,第二个参数表明读写缓冲区的大小
//写二进制文件:把学生成绩信息保存至文。
struct STUDENT //学生成绩结构体
{
char strName[20]; //姓名
int nGrade; //成绩
};
int main() {
ofstream out;
out.open("d:\\a.txt"); //打开或创建a.txt文本文件
STUDENT st1 ={"张三", 90};//用结构体产生两名学生的成绩信息
STUDENT st2 ={"李四", 80};
out.write((const char *)&st1, sizeof(STUDENT)); //把成绩信息存到二进制 文件
out.write((const char *)&st2, sizeof(STUDENT));
out.close();
return 0;
}
//读二进制文件,并把结果显示在屏幕上。
int main() {
ifstream in;
in.open("d:\\a.txt");
if(!in) return 0;
STUDENT st1;
STUDENT st2;
in.read((char *)&st1, sizeof(STUDENT));
in.read((char *)&st2, sizeof(STUDENT));
cout << st1.strName << "\t" << st1.nGrade << endl;
cout << st2.strName << "\t" << st2.nGrade << endl;
in.close();
return 0;
}
输入输出流缓冲
C++标准库封装了一个缓冲区类streambuf,以供输入输出流对象使用。每个标准C++输入输出流对象都包含一个指向streambuf的指针,用户可以通过调用rdbuf()成员函数获得该指针,从而直接访问底层streambuf对象,可以直接对底层缓冲区进行数据读写,从而跳过上层的格式化输入输出操作。但由于类似的功能均可由上层缓冲区类实现,所以就不再加以论述了。streambuf最精彩的部分在于它重载了operator <<及operator >>。对operator <<来说,它以streambuf指针为参数,实现把streambuf对象中的所有字符输出到输出流出中;对operator >>来说,可把输入流对象中的所有字符输入到streambuf对象中。
//打开一个文件并把文件中的内容送到标准输出中。
int main() {
ifstream fin("d:\\my.txt");
if(fin !=NULL) //判断文件是否存在
{
cout<<fin.rdbuf(); //把文件所有内容输出到屏幕上
}
fin.close();
}
定位输入输出流
流的位置标识有3个:
- ios::beg 流的开始位置
- ios::cur 流的当前位置
- ios::end 流的结束位置 定位函数主要有两个:
istream& seekg(long relativepos, ios::seek_dir dir)
针对输入流。第1个参数是要移动的字符数目,可正可负;第2个参数是移动方向,是ios::begin, ios::cur, ios::end中的一个值。含义是:字符指针相对于移动方向移动了向前或向后移动了多少个字符。
ostream& seekp(long relativepos, ios::seek_dir dir) 针对输出流。参数含义同seekg。
//下面示例先写文件,然后再把文件内容读出显示在屏幕上。
int main() {
fstream in_out;
in_out.open("d:\\my.txt",ios::in|ios::out|ios::trunc);
in_out.write("Hello", 5);
in_out.seekg(0, ios::beg); //读指针移到文件头
cout << in_out.rdbuf();
in_out.close();
return 0;
}
3 字符串输入输出流
标准库定义了三种类型的字符串流。
- stringstream 输入流,提供读string功能
- ostringstream 输出流,提供写string功能
- stringstream 输入输出流,读写string功能
//反解字符串给各变量赋值
#include <iostream>
#include <sstream>
using namespace std;
int main()
{
int n;
float f;
string strHello;
string strText = "1 3.14 hello";
istringstream s(strText);
s >> n;
s >> f;
s >> strHello;
cout << "n= " << n << endl;
cout << "f= " << f << endl;
cout << "strHello= " << strHello << endl;
return 0;
}
//合并不同类型的数据到字符串
int main()
{
cout << "type an int,a float and a string: ";
int i;
float f;
string stuff;
cin >> i >> f;
getline(cin, stuff);
ostringstream os;
os << "integer= " << i << endl;
os << "float= " << f << endl;
os << "string= " << stuff << endl;
string result = os.str();
cout << result << endl;
return 0;
}
综合示例
#include <iostream>
#include <string>
using namespace std;
class Student
{
public:
string strName; //姓名
string strSex; //性别
int nAge; //年龄
};
istream& operator>> (istream& is, Student& s)
{
is >> s.strName >> s.strSex >> s.nAge;
return is;
}
ostream& operator << (ostream& os, Student& s) //Student是普通对象
{
os << s.strName << "\t" << s.strSex << "\t" << s.nAge << "\n";
return os;
}
ostream& operator << (ostream& os, const Student& s)//Student是常对象
{
cout << "const Student输出是:" << endl;
os << s.strName << "\t" << s.strSex << "\t" << s.nAge << "\n";
return os;
}
void f(const Student& s)
{
cout << s;
}
int main(int argc, char *argv[])
{
Student s;
cout << "请输入数据(姓名 性别 年龄):";
cin >> s;
cout << "输入的数据是: ";
cout << s;
f(s);
return 0;
}
对该程序着重理解主程序中cin>>s, cout << s的用法,它们均是对对象的操作。当cin >> s时,其实真正调用的是重载的istream& operator>> (istream& is, Student& s),如何输入是由该函数体来完成的;同理当cout << s时,真正调用的是重载的ostream& operator << (ostream& os, Student& s),如何输出由该函数体来完成;当Student是常对象时,例如函数void f(const Student& s)中cout<<s调用的是ostream& operator << (ostream& os, const Student& s)。那么为什么对基本数据类型就不必重载相应函数呢?那是因为STL中缺省实现了基本数据类型的输入输出函数,我们就不必要再重载了。