I/O指程序输入(Input)和输出(Output)。输入通过一个名为“流”的C++程序构造传给程序,程序输出也通过“流”传给某个输出设备。“流”是我们第一个“对象”例子
流是由字符(或其他类型的数据)构成的“流”。流向程序,称为输入流。流出程序,称为输出流。如cin是连接键盘的输入流,cout是连接屏幕的输出流。
C++的文件操作就是指对文件进行读写的操作,那么 IO 呢?字母 I 就是 Input 的缩写,意为输入,字母 O 就是 Output 的缩写,意为输出。所以文件的 IO 操作就是指文件的输入与输出。输入就是从磁盘上的文件中读取内容到内存中。输出就是将内存中的数据内容输出或者说写入到磁盘的文件中。(也就是说,输入和输出是相对于内存中的数据而言的)
在C++中,流是一种称为“对象”的特殊变量。
文件的类型分为文本文件和二进制文件,文本文件又称为ASCII文件,它的每个字节存放一个ASCII码,代表一个字符。二进制文件则是把内存中的数据,按照其在内存中的存储形式原样写在磁盘上存放。比如一个 short 类型的整数20000,在内存中占用2个字节,而按文本形式输出则占5个字节。因此在以文本形式输出时,一个字节对应一个字符,因而便于字符的输出,缺点则是占用存储空间较多。用二进制形式输出数据,节省了转化时间和存储空间,但不能直接以字符的形式输出。所以,大家可以根据自己的需要选择使用文本文件还是二进制文件存储。如果是输出log文件之类的,那肯定就得用文本形式了,二进制的也看不懂啊,对不对?
欲对文件进行读写操作,首先含入fstream头文件:
#include
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息 |
ifstream | 该数据类型表示输出文件流,用于从文件读取信息 |
ftream | 该数据类型通常表示文件流,同时具有ofstream和ifstream两种功能,可以创建文件,向文件写入信息,从文件读取信息 |
为了开启一个可供输入的文件,我们定义一个ofstream(供输出用的file stream)对象,并将文件名传入
open()函数是fstream、ifstream和ofstream队形的一个成员,下面是open()函数的标准语法:
void open(const char *filename,ios::openmode mode);
open()成员函数的第一参数指定打开的文件的名称和位置,第二个参数定义文件被打开的模式。
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾 |
ios::ate | 文件打开定位到文件末尾 |
ios::in | 打开文件用于读取 |
ios::out | 打开文件用于写入 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即文件长度设为0 |
ios::binary | 二进制方式 |
模式混合使用。例如,如果想要以写入模式打开文件,并希望截断文件,以防文件已存在,可以使用:
ofstream outfile;
outfile.open("seq_data.txt", ios::out | trunc);
当C++程序终止时,它会自动刷新所有的流,释放所分配的内存,并关闭所有打开的文件。程序员应该养成一个好习惯,在程序终止前关闭所有打开的文件。
下面是 close() 函数的标准语法,与open()函数相同,close() 函数也是 fstream、ifstream 和 ofstream 对象的一个成员。
infile.close();
outfile.close();
程序使用的每个输入和输出文件都有两个名称。外部文件名是文件真实名称,但只在open函数中调用一次,该函数将文件链接到一个流。一旦调用了open,就必须将流名称作为文件名使用。
普通输入模式写入文件例子:
#include
#include
using namespace std;
int main()
{
char data[100];
//以输入模式开启seq_data.txt
ofstream outfile;
outfile.open("seq_data.txt");
cout << "Please input:\n";
cin >> data;
// 向文件写入数据
outfile << data <<endl;
// 关闭打开的文件
outfile.close();
return 0;
}
追加模式写入文件例子:
#include
#include
using namespace std;
int main()
{
char data[100];
// 以追加写入模式打开文件
ofstream outfile;
outfile.open("seq_data.txt",ios::app);
cin >> data;
cout <<data;
outfile << data <<endl;
// 关闭打开的文件
outfile.close();
return 0;
}
读取文件例子:
#include
#include
using namespace std;
int main()
{
char data[100];
// 以读模式打开文件
ifstream infile;
infile.open("seq_data.txt");
infile >> data;
// 屏幕上写入数据
cout << data <<endl;
// 关闭打开的文件
infile.close();
return 0;
}
既然文件打开了,但是文件是否打开成功了呢?我们又该如何判断呢?有的会员会说,看看open函数的返回值,返回 false 肯定就表明打开失败了吧?但是open函数的返回值是 void 类型哦,也就是说无返回值,你又该如何判断呢?方法有多种,这里面一一列举给大家,大家喜欢用哪种方式就用哪种:
①、直接 if 判断 fs 对象;
②、用 is_open 方法判断;
③、用 good 方法判断;
④、用 fail 方法判断;
比较常用的还是前两种方法,意思表达明确。建议大家用前两种方法检测文件是否打开成功。
outfile.open("X:\\seq_txt");
if (!outfile)
{
cout << "open file error." << endl;
return 0;
}
outfile.open("X:\\seq_txt");
if (!outfile.is_open())
{
cout << "open file error." << endl;
return 0;
}
outfile.open("X:\\seq_txt");
if (!outfile.good())
{
cout << "open file error." << endl;
return 0;
}
outfile.open("X:\\seq_txt");
if(outfile.fail())
{
cout << "output file opening failed.\n";
return 0;
}
#include
#include
using namespace std;
int main()
{
char data[100];
ofstream outfile; //创建输出流文件
outfile.open("D://seq_data.txt"); //打开文件
cout << "Please input:";
cin >> data; //写入数据
//outfile << data;
outfile.write((char*)&data, sizeof(char)*100); //二进制写入文件,左参数代表内存的起始位置,
//右参数为写入多少个字节
outfile.close();
ifstream infile; //与写入文件方法类似
infile.open("D:\\seq_data.txt");
//infile >> data;
infile.read((char*)&data, sizeof(char) * 100);
cout << data << endl;
return 0;
}
文件的写入:
文件的读取:
#include
#include
using namespace std;
int main()
{
ofstream outfile("d:\\seq_data.txt");
if (!outfile)return 0; //判断文件是否成功打开
outfile << 2000 << endl; //写文件操作<<
outfile << 1.25 << endl;
outfile << "Hello" << endl;
outfile.put('Y'); //写文件操作put,只能写入单个字符
outfile << endl;
int var = 12345;
outfile.write((const char*)&var, sizeof(var)); //写二进制文本文件writer
char sznihao[] = "\r\n你好\r\n";
outfile.write(sznihao, sizeof(sznihao)); //左操作数为操作对象,右操作数为写入的字节数
outfile.close();
ifstream infile;
infile.open("d:\\seq_data.txt"); //文件读取流的创建与文件的打开
char szbuf[100];
//infile.getline(szbuf, 100);
//cout << szbuf << endl;
//memset(szbuf, 0, 100);
//infile.getline(szbuf, 100);
//cout << szbuf << endl;
//memset(szbuf, 0, 100);
//infile.getline(szbuf, 100);
//cout << szbuf << endl;
//
//memset(szbuf, 0, 100);
//infile.getline(szbuf, 100);
//cout << szbuf << endl;
//memset(szbuf, 0, 100); //缓冲区清除,第一个参数为操作对象,第二个参数为赋给操作对象的值,,第三个参数是操作对象的长度
//infile.getline(szbuf, 100); //左参数为操作对象,右操作数为流的大小
//cout << szbuf << endl;
infile.read(szbuf, 100);
cout << szbuf << endl;
//int abc = 0;
//infile >> abc;
//float val1 = 0;
//infile >> val1;
//cout << val1;
//char szbuf[100] = { 0 };
//infile >> szbuf; //读取结果为Hello
//char ch = infile.get(); //读取结果为10\n
//ch = infile.get(); //读取结果为Y,只能读取一个字符
return 0;
}
①、文件读写是否成功?
可以使用 good、bad、fail 来进行判断!
fail() 方法用于判断最后一次读取数据的时候是否遇到了类型不配的情况,若是返回true(如果遇到了EOF,该方法也返回true)
bad() 如果出现意外的问题,如文件受损或硬件故障,最后一次读取数据的时候发生了这样的问题,方法 bad() 将返回true。
good() 该方法在没有发生任何错误的时候返回true。该方法也指出的最后一次读取输入的操作是否成功。
②、读取文件的时候是否已经读到文件末尾?
可以使用 eof 来进行判断!eof() 方法用于判断最后一次读取数据的时候是否遇到EOF,即到达文件末尾,若是则返回true。
代码实例:
#include
#include
using namespace std;
int main()
{
//开始写
ofstream outfile;
outfile.open("d:\\123.txt");
if (!outfile)return 0;
int var = 2000;
outfile << var << endl;
outfile << var + 1 << endl;
outfile.close();
//开始读
ifstream infile;
infile.open("d:\\123.txt");
var = 0;
//第一次读取
infile >> var;
bool read_is = infile.good();
bool end_of_file = infile.eof();
cout << var << "," << "read_is=" << read_is << ",end_of_file=" << end_of_file << endl;
//第二次读取
infile >> var;
read_is = infile.good();
end_of_file = infile.eof();
cout << var << "," << "read_is=" << read_is << ",end_of_file=" << end_of_file << endl;
//第三次读取
infile >> var;
read_is = infile.good();
end_of_file = infile.eof();
cout << var << "," << "read_is=" << read_is << ",end_of_file=" << end_of_file << endl;
return 0;
}
输出结果:
2000, read_is = 1, end_of_file = 0
2001, read_is = 1, end_of_file = 0
2001, read_is = 0, end_of_file = 1
第一次读取正常,没有读到末尾
第二次读取正常,没有读到末尾
第三次读取失败,未读到数据
这里面说的文件指针也可以理解为文件内部记录读取或者写入的当前位置,不然程序如何知道下一次该从文件的什么位置开始读取或者写入呢?
在读写文件的时候,每读取或者写入一个字节,磁盘中的文件指针就会向后移动一个字节。可以通过控制指针的位置,以便在我们需要的位置进行读写文件。
文件流提供以下成员函数来读取或配置文件指针:
tellg() 返回读取文件指针的当前位置
tellp() 返回写入文件指针的当前位置
seekg(指针偏移量) 将读取文件指针移到指定位置
seekg(指针偏移量,参照位置) 将读取文件指针移到指定位置
seekp(指针偏移量) 将写入文件指针移到指定位置
seekp(指针偏移量,参照位置) 将写入文件指针移到指定位置
这些成员函数名和参数,容易混淆,下面就简单解释一下:
备注:以上函数中的最后一个字母不是g就是p,代表什么意思呢?其中,g代表get,表示读取;p代表put,表示写入。
另外,函数参数中的“文件中的位置”和“指针偏移量”为 long整型,以字节为单位。“参照位置”是一个有以下值的枚举:
ios::beg 文件开头计算偏移量
ios::cur 文件当前位置计算偏移量
ios::end 文件结尾计算偏移量
其中,函数seekg(指针偏移量) 和 seekp(指针偏移量),默认从文件开头计算偏移量。
#include
#include
using namespace std;
int main()
{
//开始写
ofstream outfile;
outfile.open("d:\\123.txt");
if (!outfile)return 0;
int var = 2000;
outfile << var << endl;
outfile << var + 1 << endl;
outfile.close();
//开始读
ifstream infile;
infile.open("d:\\123.txt");
if (!infile)return 0;
//文件指针的应用(测量文件的大小)
infile.seekg(0, ios::end); //将文件指针设置到文件末尾
int file_size = infile.tellg(); //返回文件指针的位置
cout << "file_size=" << file_size << endl; //此时文件指针的位置即文件的大小
//var = 0;
第一次读取
//int read_ptr = infile.tellg(); //第一次读取指针位置,0,因为从头开始
//infile >> var;
//bool read_is = infile.good();
//bool end_of_file = infile.eof();
//cout << var << "," << "read_is=" << read_is << ",end_of_file=" << end_of_file << endl;
//read_ptr = infile.tellg(); //第二次读取指针位置,指针位置为4,因为读取了一个数据2000
infile.seekg(read_ptr + 1); //第二次读取后,指针指向CR,+1后指针指向LF,所以读取到的数据是2001
infile.seekg(read_ptr + 2); //+2后指针指向2,所以读取的数据为2001
infile.seekg(read_ptr + 3); //+3后指针指向第一个0,所以读取到001,因为var为int型的数,所以读取到的数据为1
第二次读取
//infile >> var;
//read_is = infile.good();
//end_of_file = infile.eof();
//cout << var << "," << "read_is=" << read_is << ",end_of_file=" << end_of_file << endl;
//read_ptr = infile.tellg(); //第二次读取,指针位置为12,因为2000和2001八个字节,加上两个CR(回车)、LF(换行),
// //加起来12个字节,刚好和文件的大小对应
第三次读取
//infile >> var;
//read_is = infile.good();
//end_of_file = infile.eof();
//cout << var << "," << "read_is=" << read_is << ",end_of_file=" << end_of_file << endl;
//read_ptr = infile.tellg();
return 0;
}
注:
这段代码中文件的二进制形式为:
2000CRLF
2001CRLF
CR为回车,LF为换行,这两种都各占一个字节
代码实现:
#include
#include
using namespace std;
int file_copy()
{
char before_copy_route[20];
char after_copy_route[20];
cout << "\t\t\t\t欢迎使用菜鸟编程——文件复制( ̄▽ ̄)" << endl;
getchar();
cout << "请输入复制前的路径:";
cin >> before_copy_route;
cout << "请输入复制后的路径:";
cin >> after_copy_route;
//将原始文件复制到新文件
//1. 创建输入流读取文件,将数据写入变量,同时判断是否读取到了文件末尾
//2. 将变量中的值写入到新的文件中
ifstream infile; //输入流的创建
infile.open(before_copy_route,ios::binary); //二进制方式打开原始文件
if (!infile)return 0; //判断原始文件是否打开成功
infile.seekg(0, ios::end);
int file_len = (int)infile.tellg();
char* buff = new char[file_len]; //
infile.seekg(0);
infile.read(buff, file_len);
ofstream outfile;
outfile.open(after_copy_route, ios::binary); //二进制打开新文件
if (!outfile)return 0;
outfile.write(buff, file_len);
infile.close();
outfile.close();
delete []buff;
return 1;
}
int main()
{
if (file_copy())
cout << "复制成功";
else
cout << "复制失败";
getchar();
getchar();
return 0;
}
对象:对象是显示世界中实际存在的事物,是构成世界的独立单位,它由数据(描述事物的属性)和作用于数据的操作体现事物的行为)构成一个独立整体。
类:在面对对象的方法中,类是具有相同属性和行为的一组对象的集合,它提供一个抽象的描述,其内部包括属性和行为两个主要的部分。
抽象:抽象是通过特定的实例抽取共同特征以后形成概念 的过程。抽象化主要是为了 使复杂度降低,是面向对象编程思想的 本质。
类中可以定义数据成员和成员函数,数据成员用于描述对象特征,成员函数用于描述对象行为,其中对象成员被称为属性,成员函数被称为方法
对象是一个变量,和它关联的既有函数,也有数据,project(对象)=Data(数据)+Operations(操作)。
和infile对象关联的open函数有别于和outfile对象关联的open函数。一个函数用于打开文件进行输入,一个用于打开文件进行输出。编译器遇到一个名为open名称的函数的调用时,会通过查看圆点之前的对安详名称来判断所指的哈数。与对象关联的函数称为成员函数
如果一个类型的变量是对象(比如ifstream和ofsteam),这个类型称为类。
由于对象的成员函数完全是由它的类(也就是它的类型)决定,所以这些函数称为类的成员函数(也称为对象的成员)
消息就是对象之间通信的机制。对象之间通过消息进行通信,以实现对象之间的动态联系。一个消息应具有接受消息的对象、消息名和返回值类型。
例如:圆A是圆类的对象,当要重新设置半径时, 就需要向它发出一下消息:圆A将半径设置为5.5
对象是关联了函数的变量,这些函数时成员函数。类是一种类型,它的变量就是对象。对象所属的类(即对象的类)决定了对象有哪些成员函数。
类:相当于模板是抽象的;就如"学生"
对象:是具体的实际例子;就如 某个学生 “张三”,project(对象)=Data(数据)+Operations(操作)
数据成员:类的一部分; 就像 “手” 是 "学生"的一部分
圆点被称为圆点操作符,圆点前的对象称为调用对象。调用对象好比函数的一个额外实参(函数可以把调用对象当做实参一样进行修改),调用对象具有重要的意义,因为调用对象决定了函数名的含义。编译器根据调用对象的类型判断函数名的意义。
封装
封装就是将对象的属性与方法结合为一体,对外部屏蔽其内部细节。
对象具有封装性地条件:
继承
继承是子类(派生类)自动地共享父类(基类)中定义的属性和方法的机制。通过不同程度的抽象原则,可以得到一般的类——父类,较为特殊的类——子类,子类继承父类的属性和方法。
单继承:一个子类仅有一个父类的继承。
多重继承:一个子类具有多个父类的继承。
多态性
多态性是指在具有继承关系的类层次结构中,可以定义相同的属性和方法,但不同层次的类可以按照自己的需求来实现这个行为。简而言之,就是同一消息被不同层次的对象接收,可以产生不同的行为。
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
#include
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
return 0;
}
对对程序输出的布局进行调整称为对输出进行格式化
在C++中,可以用一些命令来控制格式。这些命令可指定大量格式细节。
例如:
cout.seft(ios::fixed);
cout.seft(ios::showpoint);
cout.precision(2);
这段代码可以控制小数位数为两位。
outfile.seft(ios::fixed);
outfile.seft(ios::showpoint);
outfile.precision(2);
每个输出流都有名为precision的成员函数.程序一旦为outfile流执行了precision调用,从此以后向那个流输出带小数的人哈数字时,都是保留两位有效数字。
precision函数调用只对调用指定的流生效。如果还有一个名为outfile2的输出流,outfile.precision()不能影响到outfile2流的输出。
setf是set flag(设置标志的缩写)。标志(flag)作为setf的实参,该标志就会以特定的方式去输出写入那个流。
ios::fixed标志导致流采用定点计数法,一旦采用该标志,输出到那个流中的所有浮点数就会采用日常生活中采用的常规方法来写入,而不是e计数法。
ios::showpoint标志规定流总是在浮点数类型中包含小数点,如果准备输出2.0,就肯定是2.0,而不是2。
标志 | 含义 |
---|---|
ios::fixed | 如果设置这个标志,就不用e计数法写浮点数(使用该标志会自动取消ios::scientific标志) |
ios::scientific | 设置该标志会用e计数法写浮点数(使用该标志自动取消ios::fixed标志) |
ios::showpoint | 设置该标志,始终为浮点数显示小数点和尾随的0,如果没有设置,则一个数字在小数点后全是0 |
ios::showpos | 设置该标志,正整数前面会输出一个正号 |
ios::right | 设置该标志,会调用width函数指定域宽,并指定右对齐(自动取消ios::left) |
ios::left | 设置该标志,会调用width函数指定域宽,并指定左对齐(自动取消ios::right) |
任何的标志都可以使用unsetf函数取消,例如:
cout.unsetf(ios::showpos);
操纵元是以非传统方式调用的函数。调用操纵元之后,本身会调用另外一个成员函数。操纵元位于操作符<<之后。例如endl。
讨论两个新的操纵元setw和setprecision。
使用操纵元需带#include 头文件
使用setw操纵元发送到输出流,随即会调用width()成员函数,用不同的域宽输出数字。
cout << "Start" << setw(4) << 10;
使用setprecision操纵元会调用成员函数precision(),指定小数的位数。
cout.setf(ios::fixed);
cout.setf(ios::showpoint);
cout << setprecision(2) << 10.3 <<endl;
// 输出:10.30
使用操纵元setprecision时,设置会一直生效,直到再次调用setprecision或precision
流可作为函数实参使用,但是形参必须传引用,流参数不能传值。
例如:
#include
#include
using namespace std;
void f(ofstream& outfile, ifstream& infile);
// 前条件:oufile和infile流已用open函数连接到文件
int main()
{
ofstream outfile;
ifstream infile;
outfile.open("abc.txt");
infile.open("abc.txt");
f(outfile,infile);
outfile.close();
infile.close();
return 0;
}
void f(ofstream& outfile, ifstream& infile)
{
char data[100];
cin >> data;
outfile << data <<endl;
char a[100];
infile >> a;
cout << a << "X" << endl;
}