C++中的IO库提供了用于输入输出的各种流,除此之外还提供了管理流和缓冲区等功能。IO库结构如下图所示:
相同颜色的类位于同一个头文件中
streambuf
的指针;istream
和ostream
,提供了输入输出方法;istream
,提供了对文件进行输入的方法;ostream
,提供了对文件进行输出的方法;iostream
,提供了对文件进行输入输出的方法;istream
,对字符串进行操作的输入流类;ostream
,对字符串进行操作的输出流类;iostream
,对字符串进行操作的输入输出流类;C++中为我们提供了四个标准IO对象:
实际上应有8个对象,针对宽字符
wchar_t
,以上4个对象都有对应的处理wchar_t
的对象。
cout,clog,cerr默认都会输出到显示器,但对输出重定向后,只会影响cout输出内容,clog和cerr不受影响。
Linux系统中,运算符
2>
可以重定向错误输出。
使用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;
}
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;
}
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;
}
cout
进行输出时,并非直接输出到显示器,而是先输出到缓冲区,再由缓冲区刷新到显示器。ostream
类提供了两个控制符可以进行强制刷新缓冲区:
flush
:刷新缓冲区;endl
:刷新缓冲区,并且插入一个换行符号;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;
}
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:
#
*********#
#
*/
c++中浮点数默认精度为6,但不打印末尾的0,可通过成员函数precision()
设置:
streamsize precision() const;//get
streamsize precision (streamsize prec);//set
与width()
不同,新的精度将一直有效。
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
头文件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
*/
C++中用于对文件进行IO操作的类位于头文件fstream
中,对文件进行读取操作时,需要ifstream
对象,对文件进行写入操作时,需要ofstream
对象。
从IO结构图来看,ifstream
和ofstream
分别继承于istream
和ostream
,因此文件IO的大部分操作和标准IO类似,但却更为复杂,这些复杂主要体现在对文件的操作:
因此,C++中提供了多个文件打开模式,用来描述文件如何关联到ifstream
和ofstream
。
这些模式作为ios_base
的数据成员,如下:
常量 | 含义 |
---|---|
ios_base::app | 追加到文件尾(append) |
ios_base::ate | 打开文件,并流位置标记移到文件尾(at end) |
ios_base::binary | 二进制文件,非文本文件 |
ios_base::in | 打开文件,允许流进行输入操作,即只读 |
ios_base::out | 打开文件,允许流进行输出操作,即只写 |
ios_base::trunc | 如果文件存在,则打开文件时进行清零 |
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;
}
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;
}
除了对文本文件进行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指针,当读取后,可能出现错误。