C++的输入输出是由库iostream中提供的一组类实现的;
流
C++把输入输出看成字节流,每个字节可以由程序解释成一个字符,或者只是一个二进制位构成的串;
输入流:外围设备流向输入缓冲区,Input对象从输入缓冲区读数据再流向内存;
输出流:内存流向Output对象,Output对象处理后传送给输出缓冲区,再流向外围设备;
通常,输入对象来自类istream,输出对象来自类ostream。
C++在命名空间std下预定义了一些输入对象和输出对象:
语句的解释:cout<<对象1<<对象2;
首先确保对象1和对象2的类中,有定义运算符<<的重载(通常是类中的友元,回顾60.友元),cout<<对象1
返回ostream类的对象(其实还是cout,因为cout作为参数传入,在调用结束后返回cout对象),所以变成cout<<对象2
继续输出。
重定向标准输入输出
如果我们在命令行界面执行可执行文件,只要在命令行提示符后面输入可执行文件名,程序中的输入部分都是从键盘读,输出都是显示在显示器。
我们可以在命令行执行可执行文件时,重定向输入:
可执行文件名 < 文本文件名 #从文本文件读数据
重定向输出:
可执行文件名 > 文本文件名 #将数据输出到文本文件
同时重定向输入和输出,从文本文件1读数据,将处理后的数据输出到文本文件2:
可执行文件名 < 文本文件名1 > 文本文件名2
假设有:
int x=-1;
cout<<x;
编译器执行整型类的operator<<函数,从x的地址开始取4个字节,将其转换成字符串”-1”,发送到显示器
operator<<函数的返回值是ostream&,所以允许拼接输出,比如:
cout<<x<<"abc";
ostream类的成员函数:put和write
put:输出一个字符
函数原型ostream& put(char),返回ostream类的对象本身
cout.put('a'); //输出字符a
cout.put('a').put('b'); //先输出字符a,再输出字符b,cout.put('a')返回对象cout
write函数:输出指定字符数的字符串
const char* tmp="abcdefg";
cout.write(tmp,5); //输出abcde
C++提供了用于执行格式化 输入 或 输出 的流操作算子(本质上是<<运算符的参数,是函数对象)和成员函数(ostream类的成员函数)
设置整型数的基数
输入输出流中的整型数默认为十进制表示,若要设置其他数制,可以用流操作算子(使用任何带参数的流操作算子,都必须包含头文件iomanip):
基数值只有被显式更改时才会变化,否则一直沿用原有的基数
示例
设置浮点数的精度
浮点数的精度指实数的有效位数,设置方法:
setprecision(位数)
precision(位数)
一旦设置了精度,将影响所有输出的浮点数精度,直到下一个设置精度的操作出现为止
setw(宽度)
width(宽度)
比如a=123, b=456
,输出cout<为
123456
,但是如果cout<
\n\n123\n\n456
cin是istream类的对象,>>运算符的返回值还是cin,>>以空白字符(空格,回车,TAB)作为输入流(输入缓冲区)中的分隔符,或者在读缓冲区时,遇到与接收变量类型不符合的数据时,本次>>输入结束(即使用类型不符合的数据作为分隔符)。
利用cin>>时,会自动丢弃空白字符;使用类型不符合的数据作为分隔符时,不丢弃该数据;
输入错误的处理
如果输入数据不能转换成接收变量的类型,或者输入了EOF,cin>>变量
表达式返回0,变量的值也变成0;
输入错误的分类
cin.eof()
:读到EOF;cin.bad()
:IO失败或者无法诊断的错误;cin.fail()
:没能读到预期类型的数据;
遇到错误时,程序不会继续读输入缓冲区,我们可以调用cin.clear()
清除错误,让程序可以从输入缓冲区读数据。示例如下:
其他输入方法
ch=cin.get()
:读入一个字符,可以是任意字符,包括空白字符和EOFcin.getline(字符数组名,数组规模)
cin.get(字符数组名,数组规模)
cin.getline(字符数组名,数组规模)
和cin.get(字符数组名,数组规模)
以回车字符或达到数组规模结束输入,区别:getline将回车的换行符丢弃,get会将换行符留在缓冲区放在下一次输入的最开始位置。
在早期C语言没有getline时候,只能使用get,但是get对于读入回车的处理会让人们对字符文本的逻辑容易出错,为了让get每次都只输入一行,并让回车不放在下一次输入的行中,我们使用无参数的cin.get()
,cin.get()
读入任意一个字符,包含回车。
文件读写需要我们定义流对象,并与文件相关联;
文件流类(从标准输入输出流类派生,被定义在头文件fstream中):
ifstream:输入文件流
ofstream:输出文件流
fstream:输入输出文件流
文件流对象关联文件(两种方式):
1.使用open成员函数:
ofstream outfile;
outfile.open(“file_name”);
2.在定义对象时,向构造函数传递参数(参数为文件名):
ifstream infile(“file_name”);
标准输入输出流对象在控制台和内存之间交流,文件流对象在文件和内存之间交流:
infile>>x; //将infile关联的文件内容读到变量x
outfile<<”123”<<3+5; //将字符串”123”和int数值8保存到outfile关联的文件
切断文件流对象与文件的关联:
使用close成员函数:infile.close();
简单文件访问示例:
上述示例会在当前工程目录下创建名为pythag的文件,并将内容写入,我们也可以用记事本查看文件中的内容。
首先回顾关联文件与文件流对象的两种方式:
1.open成员函数
2.流的构造函数(向构造函数传递参数)
文件打开时可以指定打开方式:
文件流对象.open(文件名, 打开模式);
文件流类 文件流对象(文件名, 打开模式);
常用打开模式如下:
打开方式组合:以添加方式打开二进制文件
ofstream outfile(“file1”, ios_base::app | ios_base::binary);
关于文本文件
文件中的信息是合法字符,用<<向文件写数据时,数据被转换成字符串,用>>从文件读数据时,字符串被转换成接收变量的类型。
关于二进制文件
存储的数值是机器表示,比如将一个double类型数值写入二进制文件,文件存储的是64位表示。
对比:将整型-1写入文件
文本文件:占用2个字符,分别是’-’和’1’
二进制文件:在文件中占4字节,是-1的补码表示
检查文件打开是否成功
1.检查文件流对象:打开文件失败时,流对象返回false;
2.fail成员函数:打开文件失败时,fail函数返回true;
3.is_open成员函数:文件打开成功后,is_open函数返回true。
对于文本文件,只要文件与文件流对象建立关联(也就是打开了文件),文件的访问方式和控制台一样,只是换了一种流对象而已。
但对于二进制文件的访问,我们人是看不懂二进制文件的,但是二进制文件相对于文本文件有一个优点:数据在文件中占用的空间与数据类型有关,与具体的值无关,这便于随机访问。
假设我们在文本文件中写入一组整数,我们要获取第1000个整数的值,我们只能从文本文件中一个一个读,直到读到第1000个整数才能获得值。但对于二进制文件,由于整型都是只占有4字节,我们可以不用读前999个数,我们可以直接锁定到第1000个数的地址,然后取值。(这就是随机访问)
二进制文件读写
对于二进制文件的读写,我们不能使用>>和<<(因为>>和<<会自动在内存表示与人能看懂的字符串之间进行转换),而二进制文件要求:我们直接将内存表示写到二进制文件,或者直接将二进制文件读到内存中。
写文件:
write(char*, 长度);
//类型转为char,因为char占1个字节,方便计数信息长度;
当我们要把内存的信息写到文件,我们要告诉机器两件事情:1.这块信息在机器中的地址是多少,2.这块信息的长度是多少。
读文件:
read(char*, 长度);
//char* 用于指明将文件中的数据读到机器中的char*所指位置,并且有多大长度的信息要读入内存。
判断读文件结束:
eof()
或read
函数的返回值
在读文本文件时候,如果用>>去读,操作结果返回fin,如果fin.eof()为true可以判断读文件结束。对于二进制文件的读,也是一样的,也是看输入文件流对象的eof()。
读二进制文件判断结束也可以看read函数的返回值(读到文件结束,read返回false)。
比如:
int x=5;
fout.write((char*)&x, sizeof(int));
int y;
fin.read((char*)&y, sizeof(int));
二进制文件读写示例(把一组结构体类型信息写到一个二进制文件中,便于随机访问)
提前说明一个内容:在代码的最后几行里看到
并且发现分别打印了两行:
这是因为在读或写二进制文件时,有一个指针,当文件流对象关联文件后,指针默认指向文件开始位置,当读入或写入一定长度信息后,指针停留在 文件起始位置+该长度偏移 的位置。下次对文件的操作就从指针当前位置开始执行。
我们可以手动设置读写文件的位置
设置读文件的位置:seekg(偏移量, 起始位置); //操作位置=起始位置+偏移量
设置写文件的位置:seekp(偏移量, 起始位置);
对于参数 起始位置
:
以102.二进制文件访问中的例子继续演示
首先显示原文件的内容:
修改某一条记录:由于是二进制文件,我们可以很容易去定位
显示修改后的文件新内容:
执行结果如下: