一、linux文件操作的概念
1. 文件的概念:
文件在linux系统中,是对系统资源的一个抽象,是对系统资源进行访问的通用接口。linux下的系统资源都可表述为文件。这样做可以对这些资源提供接口,供系统编程接口设计。
2. 文件的类型:
普通文件:存储在系统磁盘上的普通文件,以字节为单位存储。文件系统本身不关心内容,只提供数据存储和访问的通道。文件内容是由应用程序解释的。
目录:目录是一种特殊的文件,可以打开,关闭以及进行相应操作
管道:(pipe)linux中的一种进程间通信机制
设备文件:设备文件没有具体内容,对设备文件的读写操作实际上是与某个设备输入输出操作关联在一起。字符设备文件对应字符设备,块设备文件对应块设备。
符号链接:是一种特殊的文件,内容是指向另一个文件的路径。
Socket:也是进程间通信的机制,与管道不同的是,他们可以在不同的主机上通信,即网络通信。
3. 文件描述符
应用程序中表示被打开的文件的一个整数,通过这个整数可以对文件进行操作。对于内核,都是通过使用这个文件描述符引用文件。
文件的索引节点:(inode)
文件的索引节点(inode)是文件的唯一标识。inode包含文件系统处理文件所需要的全部信息(如访问限,当前文件大小等)
存在两种类型inode:一个是内核inode,保存在内核;另一个是磁盘inode,保存在系统磁盘上。当进程打开一个文件时,保存在系统磁盘上的文件的inode就会被加载至内存,同时系统创建一个内核inode,当内核ionde被修改后,系统会将其同步到系统磁盘上。内核inode记录的是一些更通用的信息,忽略与具体的文件系统相关的一些信息。
4. 文件操作一般过程:
进程打开文件,若成功则返回一个文件描述符,应用程序通过文件描述符对文件操作,操作完成后,应用程序需要关闭文件。
函数有open,read,write,ioctl函数
open函数
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname,int flags);
int open(const char *pathname,int flags,mode_t mode);
参数说明:
pathname:要打开或者创建的文件名称
flags:指定打开的文件操作方式可取值有:
(1)O_RDONLY:只读方式打开
(2)O_WRONLY:只写方式打开
(3)O_RDWR:以读写方式打开
上面的常量必须要指定一个。另外可以用位或的方式添加其他部分标志量.
(1)O_CREAT:如果被打开的文件不存在,则自动创建
(2)O_EXCL:如果O_CREAT标志已经使用,那么当pathname指定的文件已经存在,则open返回失败。
(3)O_TRUNC:如果被打开的文件存在并且是以可写的方式打开,则清空原有的内容。
(4)O_APPEND:新写入的内容被加到原有的内容之后,即打开文件的读写位置被置于文件末尾处。
mode:指定新文件的访问权限
返回值:成功返回文件描述符,错误返回-1,设置errno。
read函数
#include<unsitd.h>
ssize_t read(int fd,void *buf,size_t count);
各个参数及返回值说明:
fd:文件描述符
buf:读取到的数据所存入的区域
count:需要一次读取的字节
返回值:成功则返回读到的字节数,若到文件结尾则返回0,出错返回-1设置errno。
注意:
read函数返回值情况分析:
(1)返回conut值说明成功读取conut个字节的数据。
(2)返回一个大于0小于count的值,原因可能是由一个信号打断了读取过程,或发生错误,或者读取的有效字节数大于0但不足count个,或者在读入count个字节前已经结束。
(3)返回0,说明文件读取结束。
(4)阻塞,说明没有可读的数据,若采取的非阻塞的方式,则立即返回错误。
(5)返回-1且errno为EINTR,表示在读入有效字节前收到一个信号,这种情况可以重新进行读操作。
(6)返回-1且errno值为EAGAIN,说明是在非阻塞的方式下读文件,并且没有可读的数据。
(7)返回-1且errno值为非EINTR或EAGAIN,表示由其他类型错误发生。
write函数
#include<unistd.h>
ssize_t write(int fd,const void *buf,size_t count);
参数说明类比read函数。
注意:
write函数返回值情况分析:
返回值等于count,说明数据全部写入成功。
返回一个大于0小于count的值,说明部分数据没有写入,可能是因为写入过程被信号打断,或者底层的设备暂时没有足够的空间存放所写入的数据。
阻塞,说明暂时不能写入数据,这种情况下如果以非阻塞方式操作文件,那么会立即返回错误。
返回-1且errno的值为EINTR,表示在写入一个有效字节前收到一个信号,应用程序可以再次进行写操作。
返回-1且errno的值为EAGAIN,这说明是在非阻塞方式下写文件但文件暂时不能写
入数据。
返回-1且errno的值为EBADF,表示给定的文件描述符非法,或者文件不是以写方
式打开的。
返回-1且errno的值为EFAULT,表示buf是无效指针。
返回-1且errno的值为EFBIG,表示写入的数据超过了最大的文件尺寸,或者超过了
允许的文件的读写位置。
返回-1且errno的值为EPIPE,说明写入时发生了数据通道断裂的错误,这只能够情
况只在文件是管道或socket的情况下发生。这种情况下,进程还将收到一个SIGPIPE
信号,信号的默认处理程序是使进程退出。
返回-1且errno的值为ENOSPC,说明底层设备没有足够的空间。
ioctl函数
linux系统上,那些不能被抽象为读写的操作都由ioctl操作实现。
对于设备文件来说,ioctl函数常用于修改设备的参数。
#include <sys/ioctl.h>
int ioctl(int fd,int request,...);
各个参数说明:
(1)fd:要操作的文件描述符
(2)request:代表要进行的操作,不同的(设备)文件有不同的定义
(3)可变参数,取决于request参数,通常指向变量或者数据结构的指针。
(4)返回值:成功返回0,部分操作返回非负值,出错则返回-1.
close函数
调用close函数关闭一个打开的文件。
#include <unistd.h>
int close(int fd);
二、C/C++文件操作的概念
1、文件概述
文件是指存储在外部存储器上的数据集合。更准确的来说,文件就是一组相关元素或数据的有序集合,而且每个集合都有一个符号化的指代,称这个符号化的指代为文件名。
2、文件类型
根据文件在外部存储器上的组织形式,文件可以分为ASCII文件和二进制文件。
ASCII文件(文本文件):以字符的方式进行存储,一个字符对应一个ASCII码,而一个ASCII码占用1字节。例如:整数12在内存中占用4个字节;如果按照ASCII码的方式存储,则占用2个字节。ASCII文件可以阅读,可以打印,但是它与内存数据交换时需要转换;
二进制文件:将内存中的数据按照其在内存中的存储形式原样输出并保存在文件中。二进制文件占用空间小,内存数据与磁盘数据交换时无需转换,可以节省外存空间和转换时间。但是二进制文件不可阅读、打印。
在C语言中,扩展名为.c的源文件是由字符构成,而扩展名由.obj目标文件和.exe可执行文件是由二进制符号构成的。其实:txt文件也是一个文本文件。
3、C如何操作文件——文件指针
C语言是通过文件指针变量的操作来实现对文件的具体访问。
文件的指针不是指向一段内存空间,而是指向描述有关这个文件的相关信息的一个文件信息结构体,该结构体定义在studio.h头文件中。当然,也无需了解有关此结构体的详细细节,只需要知道如何使用文件指针就行了。
声明文件指针变量的一般形式为:
FILE* 文件型指针变量名;
其中,FILE应为大写;它实际上是由系统定义的一个结构体,该结构体中包含了文件名、文件使用方式、当前位置等信息。
在stdio.h文件中,FILE的结构体定义为:
typedof atruct
{
int _fd; /* 文件号 */
int _cleft; /* 缓冲区剩下的字符 */
int _mode; /* 文件操作模式 */
char* _nextc; /* 下一个字符的位置 */
char* _buff; /* 文件缓冲区位置 */
}FILE;
4、文件缓冲区
由于文件存储在外存储器上,外存的数据读/写速度相对较慢,所以在对文件进行写/读操作时,系统会在内存中为文件的输入或输出开辟缓冲区。
当对文件进行输出时,系统首先把输出的数据填入为该文件开辟的缓冲区内,每当缓冲区被填满时,就把缓冲区中的内容一次性输出到对应的文件中;
当从某个文件输入数据时,首先将从输入文件中输入一批数据放入到该文件的内存缓冲区中,输入语句将从该缓冲区中依次读取数据;当该缓冲区的数据被读完时,将在从输入文件中输入一批数据到缓冲区。
5、文件的打开与关闭
C语言规定,任何文件在使用之前必须打开,使用之后必须关闭。对文件的操作都是通过标准函数来实现的。
文件的打开——fopen()函数
C语言用fopen()函数打开一个文件,其调用的一般形式为:
文件指针名 = fopen(文件名,文件的使用方式);
该函数可以通过对文件指针名的判断来对文件打开进行判断,如果文件指针名为NULL,则文件打开失败;否则打开成功。
文件的使用方式和含义如下表所示:
文件的使用方式和含义
打开方式 含义 指定文件不存在时 指定文件存在时
r 只读方式打开文本文件 出错 正常打开
w 只写方式打开文本文件 建立新文件 文件原有内容丢失
a 追加方式打开文本文件 建立新文件 在原有内容末尾追加
r+ 读/写方式打开文本文件 出错 正常打开
w+ 读/写方式创建新的文本文件 建立新文件 文件原有内容丢失
a+ 读/追加方式建立新的文本文件 建立新文件 在原有内容末尾追加
rb 只读方式打开二进制文件 出错 正常打开
wb 只写方式打开二进制文件 建立新文件 文件原有内容丢失
ab 追加方式打开二进制文件 建立新文件 在原有内容末尾添加
rb+ 读/写方式打开二进制文件 出错 正常打开
wb+ 读/写方式创建新的二进制文件 建立新文件 文件原有内容丢失
ab+ 读/追加方式创建新的二进制文件 建立新文件 在原有内容末尾追加
高版本的VS编译器可能会认为fopen()函数不安全,会强制要求使用fopen_s()函数来代替。
fopen_s()函数调用的一般形式为:
errno_t err;
err = fopen_s(指向文件指针的指针,文件名,文件的使用方式);
err = fopen_s(&fp,"d:\\1.txt","r");
这个函数的使用有两点注意:
该函数有返回值,如果打开文件成功,函数返回值为0;否则返回值非0;
该函数的第一个参数时指向文件指针的指针,也就是说,需要传递文件指针的地址。
文件的关闭——fclose()函数
在程序中,当对一个文件的操作使用完毕后,应将其关闭,断开文件指针与该文件之间的联系,防止文件遭到其他操作的破坏。C语言用fclose()函数打开一个文件,其调用的一般形式为:
fclose(文件指针);
该函数有返回值,如果关闭文件成功,函数返回值为0;否则返回值非0。
文件的顺序读/写
字符读/写函数fgetc()和fputc()
fgetc()函数的功能是从指定的文件中读取一个字符,其调用的形式为:
字符变量 = fgetc (文件指针);
如果在执行fgetc()函数时遇到文件结束符,函数会返回一个文件结束符标志EOF(-1)。
fputc()函数的功能是把一个字符写入指定的文件中,其一般调用的格式为:
fput(字符,文件指针);
例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE* fp1, *fp2;
errno_t err;
char c;
err = fopen_s(&fp1, "d:\\1.txt", "w");
if (err != 0) {
printf("文件打开失败!\n");
exit(0);
}
c = getchar();
while (c != '\n') {
fputc(c, fp1);
c = getchar();
}
fclose(fp1);
err = fopen_s(&fp2, "d:\\1.txt", "r");
if (err != 0) {
printf("文件打开失败!\n");
exit(0);
}
c = fgetc(fp2);
while (c != EOF) {
printf("%c", c);
c = fgetc(fp2);
}
printf("\n");
fclose(fp2);
return 0;
}
从上面的例程可以看出,文件的顺序读/写函数仅仅是沟通文件和程序之间的函数,如果想要从键盘上输入或者输出到命令行等,还是需要我们之前学到的输入输出的内容
字符串读/写函数fgets()和fputs()
fgets()函数的功能是从指定的文件中读取一个字符串,其调用的形式为:
fgets(字符数组名,n,文件指针);
其中,n是一个正整数,表示从文件中读出的字符串不超过n-1个字符。在读入一个字符串后加上字符串结束标志'\0'。
如果在执行fgets()函数时如果文件内的字符串读取完毕,函数会返回0。
fputs()函数的功能是把一个字符串写入指定的文件中,其一般调用的格式为:
fputs(字符串,文件指针);
其中,字符串可以是字符串常量、字符数组、字符指针变量。
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
FILE* fp1, *fp2;
errno_t err;
char s[30];
err = fopen_s(&fp1,"d:\\1.txt", "w");
if (err != 0) {
printf("文件打开失败!\n");
exit(0);
}
gets(s);
while (strlen(s) > 0) {
fputs(s, fp1);
gets(s);
}
fclose(fp1);
err = fopen_s(&fp2, "d:\\1.txt", "r");
if (err != 0) {
printf("文件打开失败!\n");
exit(0);
}
while (fgets(s, 11, fp2) != 0) {
printf("%s", s);
}
printf("\n");
fclose(fp2);
return 0;
}
数据块读/写函数fread()和fwrite()
读数据块函数fread(),其调用的一般形式为:
fread(buf,size,n,文件指针);
fread()函数的功能是从文件中读取字节长度为size的n个数据,并存放到buf指向的内存地址中去。
函数的返回值为实际读出的数据项个数。比如:
fread(fa,4,5,fp);
其意义是从fp所指向的文件中,每次读4个字节长度(int)送入到fa指向的内存地址中去,连续读5次。也就是说,读5个int类型的数据到fa指向的内存中。
写数据块函数fwrite(),其调用的一般形式为:
fwrite(buf,size,n,文件指针);
fread()函数的功能是将buf中存放的size*n个字节的数据输出到文件指针所指向的文件中去。
函数的返回值为实际写入的数据项个数。
fread()和fwrite()函数一般适用于二进制文件,它们是按数据块的大小来处理输入/输出的。
格式化读/写函数fscanf()和fprintf()
格式化读/写函数与标准的格式输入/输出函数功能相同,只不过它们的读/写对象不是键盘和显示器,而是文件。fscanf()和fprintf()函数只适用于ASCII码文件的读/写。
两个函数的格式如下:
fscanf(文件指针,格式字符串,输入列表);
fprintf(文件指针,格式字符串,输出列表);
fscanf()和fprintf()函数对文件进行读/写,使用方便,容易理解。但由于在输入时需要将ASCII码转换为二进制格式,在输出时又要将二进制格式转换为字符,花费时间较长,所以在内存与磁盘交换数据频繁的时候,最好不要用这两个函数。
文件定位与文件的随机读/写
在C语言中,打开文件时,文件指针指向文件头,即文件的起始位置。在读写文件时,需要从文件头开始,每次读写完一个数据后,文件指针会自动指向下一个数据的位置。但有时不想从文件头开始读取文件,而是读取文件中某个位置的数据。这时,系统提供了定位到某个数据存储位置的函数。
文件头定位函数rewind()
rewind()函数用于把文件指针移动到文件首部,其调用的一般形式为:
rewind(文件指针);
当前读/写位置函数ftell()
ftell()函数用于确定文件指针的当前读/写位置,其调用的一般形式为:
ftell(文件指针);
此函数有返回值,若成功定位,则返回当前位置;否则返回-1。
随机定位函数fseek()
fseek()函数用于将文件指针移动到某个确定的位置,其调用的一般形式为:
fseek(文件指针,位移量,起始点);
此函数有返回值,若成功移动,则返回当前位置;否则返回-1。
其中:位移量指从起始点向前移动的字节数,大多数C版本要求该位移量为long型数据;起始点有三种选择,具体的含义见下表:
起始点取值含义
起始点 表示符号 数字表示
文件首 SEEK_SET 0
当前位置 SEEK_CUR 1
文件尾 SEEK_END 2
例如,将指针位置移动到距离文件开头100字节处:
fseek(fp,100L,0)
注意:fseek()函数一般用于二进制文件,因为文本文件计算位置往往比较混乱,容易发生错误。
文件检测函数
C语言还提供了一些检测函数,用于在文件打开、关闭以及读/写操作过程中对有可能会发生的一些情况进行检测。
文件结束检测函数feof()
feof()函数用于判断文件是否处于文件结束为止,其调用的一般格式为:
feof(文件指针);
该函数有返回值,如果文件结束,函数的返回值为1;否则返回值为0。
读/写文件出错检测函数ferror()
ferror()函数用于检查文件在使用各种读/写函数时是否出错,其调用的一般格式为:
ferror(文件指针);
该函数有返回值,如果没有错误,函数的返回值为0;否则返回值非0。
文件出错标志清除函数clearerr()
clearerr()函数用于清除出错标志,其调用的一般格式为:
clearerr(文件指针);
在ferror()函数值为非0时,在调用此函数后,ferror()函数的值变为0。
三、C++的文件操作
在C++中,有一个stream这个类,所有的I/O都以这个“流”类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:
1、插入器(<<)
向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<''\n'';就表示把字符串"Write Stdout"和换行字符(''\n'')输出到标准输出流。
2、析取器(>>)
从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。
在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。
C++ 通过以下几个类支持文件的输入输出:
- ofstream: 写操作(输出)的文件类 (由ostream引申而来)
- ifstream: 读操作(输入)的文件类(由istream引申而来)
- fstream: 可同时读写操作的文件类 (由iostream引申而来)
打开文件(Open a file)
对这些类的一个对象所做的第一个操作通常就是将它和一个真正的文件联系起来,也就是说打开一个文件。被打开的文件在程序中由一个流对象(stream object)来表示 (这些类的一个实例) ,而对这个流对象所做的任何输入输出操作实际就是对该文件所做的操作。
1. 通过一个流对象调用open函数打开一个文件。
我们使用它的成员函数open():void open (const char * filename, openmode mode);
这里filename 是一个字符串,代表要打开的文件名,mode 是以下标志符的一个组合: ios::in 为输入(读)而打开文件
文件的输入输出是从内存的角度看的:数据载入内存叫做输入,数据从内存到其他地方叫做输出。
- ios::out 文件以输出(写)方式打开
- ios::in 文件以输入(读)方式打开
- ios::ate 初始位置:文件尾
- ios::app 所有输出附加在文件末尾
- ios::trunc 如果文件已存在则先删除该文件
- ios::binary 二进制方式
这些标识符可以被组合使用,中间以”或”操作符(|)间隔。例如,如果我们想要以二进制方式打开文件”example.bin” 来写入一些数据,我们可以通过以下方式调用成员函数open()来实现:
-
ofstream file;
-
file.open ("example.bin", ios::out | ios::app | ios::binary);
ofstream, ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式,这三个类的默认方式各不相同: 类 参数的默认方式
- ofstream ios::out | ios::trunc
- ifstream ios::in
- fstream ios::in | ios::out
只有当函数被调用时没有声明方式参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。
2. 通过 类 对象 构造函数 打开文件。
类 ofstream, ifstream 和 fstream 的 对象 所进行的第一个操作通常都是打开文件,这些类都有一个构造函数可以直接调用open 函数,并拥有同样的参数。这样,我们就可以通过以下方式进行与上面同样的定义对象和打开文件的操作:
-
ofstream file ("example.bin", ios::out | ios::app | ios::binary);
-
例如:以二进制输入方式打开文件c:\config.sys
-
fstream file1;
-
file1.open("c:\\config.sys",ios::binary|ios::in,0);
-
//如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:
-
file1.open("c:\\config.sys");<=>file1.open("c:\\config.sys",ios::in|ios::out,0);
-
//另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:
-
fstream file1("c:\\config.sys");
以上 两种 打开文件的方式都是正确的。
通过调用成员函数is_open()来检查一个文件是否已经被顺利的打开:bool is_open()。它返回一个布尔(bool)值,为真(true)代表文件已经被顺利打开,假( false )则相反。
-
#include <fstream>
-
ofstream //文件写操作 内存写入存储设备
-
ifstream //文件读操作,存储设备读区到内存中
-
fstream //读写操作,对打开的文件可进行读写操作
在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象 进行对文件的读写操作
在fstream类中,有一个成员函数open(),就是用来打开文件的,其原型是:
void open(const char* filename,int mode,int access);
参数:
filename: 要打开的文件名
mode: 要打开文件的方式
access: 打开文件的属性
打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下:
ios::app: 以追加的方式打开文件
ios::ate: 文件打开后定位到文件尾,ios:app就包含有此属性
ios::binary: 以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文
ios::in: 文件以输入(读)方式打开
ios::out: 文件以输出(写)方式打开
ios::nocreate: 不建立文件,所以文件不存在时打开失败
ios::noreplace:不覆盖文件,所以打开文件时如果文件存在失败
ios::trunc: 如果文件存在,把文件长度设为0。即删除该文件。
可以用“或”把以上属性连接起来,如ios::out|ios::b inary
ofstream out;
-
out.open("Hello.txt", ios::in|ios::out|ios::binary) //根据自己需要进行适当的选取
打开文件的属性取值是:
0:普通文件,打开访问
1:只读文件
2:隐含文件
4:系统文件
可以用“或”或者“+”把以上属性连接起来 ,如3或1|2就是以只读和隐含属性打开文件。
例如:以二进制输入方式打开文件c:config.sys
-
fstream file1;
-
file1.open("c:config.sys",ios::binary|ios::in,0);
-
// 如果open函数只有文件名一个参数,则是以读/写普通文件打开,即:
-
// file1.open("c:config.sys");<=>file1.open("c:config.sys",ios::in|ios::out,0);
-
// 另外,fstream还有和open()一样的构造函数,对于上例,在定义的时侯就可以打开文件了:
-
fstream file1("c:config.sys");
-
// fstream有两个子类:ifstream(input file stream)和ofstream(outpu file stream),ifstream默认以输入方式打开文件,而ofstream默认以输出方式打开文件。
-
ifstream file2("c:pdos.def");//以输入方式打开文件
-
ofstream file3("c:x.123");//以输出方式打开文件
所以,在实际应用中,根据需要的不同,选择不同的类来定义:如果想以输入方式打开,就用ifstream来定义;如果想以输出方式打开,就用ofstream来定义;如果想以输入/输出方式来打开,就用fstream来定义。很多程序中,可能会碰到ofstream out("Hello.txt"), ifstream in("..."),fstream foi("...")这样的的使用,并没有显式的去调用open()函数就进行文件的操作,直接调用了其默认的打开方式,因为在stream类的构造函数中调用了open()函数,并拥有同样的构造函数,所以在这里可以直接使用流对象进行文件的操作,默认方式如下:
-
ofstream out("...", ios::out);
-
ifstream in("...", ios::in);
-
fstream foi("...", ios::in|ios::out);
当使用默认方式进行对文件的操作时,你可以使用成员函数is_open()对文件是否打开进行验证
关闭文件(Closing a file)
当文件读写操作完成之后,我们必须将文件关闭以使文件重新变为可访问的。关闭文件需要调用成员函数close(),它负责将缓存中的数据排放出来并关闭文件。
void close (); 这个函数一旦被调用,原先的流对象(stream object)就可以被用来打开其它的文件了,这个文件也就可以重新被其它的进程(process)所有访问了。
为防止流对象被销毁时还联系着打开的文件,析构函数(destructor)将会自动调用关闭函数close。
打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作,如:file1.close();就把file1相连的文件关闭。
读写文件
读写文件分为 文本文件 和 二进制文件 的读取,
对于文本文件的读取比较简单,用插入器和析取器就可以。而对于二进制的读取就要复杂些。
下要就详细的介绍这两种方式:
文本文件(Text mode files)
类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。
一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符
文本文件的读写很简单:用插入器(>>)从文件输入。假设file1是以输入方式打开,file2以输出打开。示例如下:
-
file2<<"I Love You";//向文件写入字符串"I Love You"
-
int i;
-
file1>>i;//从文件输入一个整数值。
这种方式还有一种简单的格式化能力,比如可以指定输出为16进制等等,具体的格式有以下一些
操纵符 功能 输入/输出
dec 格式化为十进制数值数据 输入和输出
endl 输出一个换行符并刷新此流 输出
ends 输出一个空字符 输出
hex 格式化为十六进制数值数据 输入和输出
oct 格式化为八进制数值数据 输入和输出
setpxecision(int p) 设置浮点数的精度位数 输出
比如要把123当作十六进制输出:file1<<hex<<123;要把3.1415926以5位精度输出:file1<<setpxecision(5)<<3.1415926。
类ofstream, ifstream 和fstream 是分别从ostream, istream 和iostream 中引申而来的。这就是为什么 fstream 的对象可以使用其父类的成员来访问数据。
一般来说,我们将使用这些类与同控制台(console)交互同样的成员函数(cin 和 cout)来进行输入输出。如下面的例题所示,我们使用重载的插入操作符<<:
-
// writing on a text file
-
#include <fiostream.h>
-
int main () {
-
ofstream out("out.txt");
-
if (out.is_open())
-
{
-
out << "This is a line.\n";
-
out << "This is another line.\n";
-
out.close();
-
}
-
return 0;
-
}
-
//结果: 在out.txt中写入:
-
This is a line.
-
This is another line
-
从文件中读入数据也可以用与 cin>>的使用同样的方法:
-
// reading a text file
-
#include <iostream.h>
-
#include <fstream.h>
-
#include <stdlib.h>
-
int main () {
-
char buffer[256];
-
ifstream in("test.txt");
-
if (! in.is_open())
-
{ cout << "Error opening file"; exit (1); }
-
while (!in.eof() )
-
{
-
in.getline (buffer,100);
-
cout << buffer << endl;
-
}
-
return 0;
-
}
-
//结果 在屏幕上输出
-
This is a line.
-
This is another line
例子中读入一个文本文件的内容,然后将它打印到屏幕上。注意我们使用了一个新的成员函数叫做eof ,它是ifstream 从类 ios 中继承过来的,当到达文件末尾时返回true 。
二进制文件(Binary files)
在二进制文件中,使用<< 和>>,以及函数(如getline)来操作符输入和输出数据,没有什么实际意义,虽然它们是符合语法的。
文件流包括两个为顺序读写数据特殊设计的成员函数:write 和 read。第一个函数 (write) 是ostream 的一个成员函数,都是被ofstream所继承。而read 是istream 的一个成员函数,被ifstream 所继承。类 fstream 的对象同时拥有这两个函数。它们的原型是:
write ( char * buffer, streamsize size );
read ( char * buffer, streamsize size );
这里 buffer 是一块内存的地址,用来存储或读出数据。参数size 是一个整数值,表示要从缓存(buffer)中读出或写入的字符数。
-
// reading binary file
-
#include <iostream>
-
#include <fstream>
-
using namespace std;
-
int main ()
-
{
-
const char * filename = "example.txt";
-
char * buffer;
-
long size;
-
ifstream file(filename, ios::in|ios::binary|ios::ate);
-
size = file.tellg();
-
file.seekg(0, ios::beg);
-
buffer = new char [size];
-
file.read(buffer, size);
-
file.close();
-
cout <<"the complete file is in a buffer";
-
delete[] buffer;
-
return 0;
-
}
-
//The complete file is in a buffer
①put()
put()函数向流写入一个字符,其原型是ofstream &put(char ch),使用也比较简单,如file1.put(‘c’);就是向流写一个字符’c’。
②get()
get()函数比较灵活,有3种常用的重载形式:
一种就是和put()对应的形式:ifstream &get(char &ch);功能是从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示从文件中读取一个字符,并把读取的字符保存在x中。
另一种重载形式的原型是: int get();这种形式是从流中返回一个字符,如果到达文件尾,返回EOF,如x=file2.get();和上例功能是一样的。
还 有一种形式的原型是:ifstream &get(char *buf,int num,char delim=’n’);这种形式把字符读入由 buf 指向的数组,直到读入了 num 个字符或遇到了由 delim 指定的字符,如果没使用 delim 这个参数,将使用缺省值换行符’n’。例如:
file2.get(str1,127,’A’);//从文件中读取字符到字符串str1,当遇到字符’A’或读取了127个字符时终止。
③读写数据块
要读写二进制数据块,使用成员函数read()和write()成员函数,它们原型如下:
read(unsigned char *buf,int num);
write(const unsigned char *buf,int num);
read() 从文件中读取 num 个字符到 buf 指向的缓存中,如果在还未读入 num 个字符时就到了文件尾,可以用成员函数 int gcount();来取得实际读取的字符数;而 write() 从buf 指向的缓存写 num 个字符到文件中,值得注意的是缓存的类型是 unsigned char *,有时可能需要类型转换。
-
unsigned char str1[]="I Love You";
-
int n[5];
-
ifstream in("xxx.xxx");
-
ofstream out("yyy.yyy");
-
out.write(str1,strlen(str1));//把字符串str1全部写到yyy.yyy中
-
in.read((unsigned char*)n,sizeof(n));//从xxx.xxx中读取指定个整数,注意类型转换
-
in.close();out.close();
状态标志符的验证(Verification of state flags)
除了eof()以外,还有一些验证流的状态的成员函数(所有都返回bool型返回值):
bad():如果在读写过程中出错,返回 true 。例如:当我们要对一个不是打开为写状态的文件进行写入时,或者我们要写入的设备没有剩余空间的时候。
fail():除了与bad() 同样的情况下会返回 true 以外,加上格式错误时也返回true ,例如当想要读入一个整数,而获得了一个字母的时候。
eof():如果读文件到达文件末尾,返回true。
good():这是最通用的:如果调用以上任何一个函数返回true 的话,此函数返回 false 。
要想重置以上成员函数所检查的状态标志,你可以使用成员函数clear(),没有参数。
缓存和同步(Buffers and Synchronization)
当我们对文件流进行操作的时候,它们与一个streambuf 类型的缓存(buffer)联系在一起。这个缓存(buffer)实际是一块内存空间,作为流(stream)和物理文件的媒介。例如,对于一个输出流, 每次成员函数put (写一个单个字符)被调用,这个字符不是直接被写入该输出流所对应的物理文件中的,而是首先被插入到该流的缓存(buffer)中。
当缓存被排放出来(flush)时,它里面的所有数据或者被写入物理媒质中(如果是一个输出流的话),或者简单的被抹掉(如果是一个输入流的话)。这个过程称为同步(synchronization),它会在以下任一情况下发生:
1. 当文件被关闭时: 在文件被关闭之前,所有还没有被完全写出或读取的缓存都将被同步。
2. 当缓存buffer 满时:缓存Buffers 有一定的空间限制。当缓存满时,它会被自动同步。
3. 控制符明确指明:当遇到流中某些特定的控制符时,同步会发生。这些控制符包括:flush 和endl。
4. 明确调用函数sync(): 调用成员函数sync() (无参数)可以引发立即同步。这个函数返回一个int 值,等于-1 表示流没有联系的缓存或操作失败
检测 EOF
成员函数eof()用来检测是否到达文件尾,如果到达文件尾返回非0值,否则返回0。原型是int eof();
if(in.eof())ShowMessage("已经到达文件尾!");
文件定位
和 C的文件操作方式不同的是,C++ I/O系统管理两个与一个文件相联系的指针。一个是读指针,它说明输入操作在文件中的位置;另一个是写指针,它下次写操作的位置。每次执行输入或输出时, 相应的指针自动变化。所以,C++的文件定位分为读位置和写位置的定位,对应的成员函数是 seekg()和 seekp(),seekg()是设置读位置,seekp是设置写位置。它们最通用的形式如下:
-
istream &seekg(streamoff offset,seek_dir origin);
-
ostream &seekp(streamoff offset,seek_dir origin);
streamoff 定义于 iostream.h 中,定义有偏移量 offset 所能取得的最大值,seek_dir 表示移动的基准位置,是一个有以下值的枚举:
-
ios::beg: 文件开头
-
ios::cur: 文件当前位置
-
ios::end: 文件结尾
这两个函数一般用于二进制文件,因为文本文件会因为系统对字符的解释而可能与预想的值不同。
-
file1.seekg(1234,ios::cur);//把文件的读指针从当前位置向后移1234个字节
-
file2.seekp(1234,ios::beg);//把文件的写指针从文件开头向后移1234个字节
获得和设置流指针(get and put stream pointers)
所有输入/输出流对象(i/o streams objects)都有至少一个流指针:
ifstream, 类似istream, 有一个被称为get pointer的指针,指向下一个将被读取的元素。
ofstream, 类似 ostream, 有一个指针 put pointer ,指向写入下一个元素的位置。
fstream, 类似 iostream, 同时继承了get 和 put
我们可以通过使用以下成员函数来读出或配置这些指向流中读写位置的流指针:
tellg() 和 tellp()
这两个成员函数不用传入参数,返回pos_type 类型的值(根据ANSI-C++ 标准) ,就是一个整数,代表当前get 流指针的位置 (用tellg) 或 put 流指针的位置(用tellp).
seekg() 和seekp()
这对函数分别用来改变流指针get 和put的位置。两个函数都被重载为两种不同的原型:
-
seekg ( pos_type position );
-
seekp ( pos_type position );
使用这个原型,流指针被改变为指向从文件开始计算的一个绝对位置。要求传入的参数类型与函数 tellg 和tellp 的返回值类型相同。
-
seekg ( off_type offset, seekdir direction );
-
seekp ( off_type offset, seekdir direction );
使用这个原型可以指定由参数direction决定的一个具体的指针开始计算的一个位移(offset)。它可以是:
ios::beg 从流开始位置计算的位移
ios::cur 从流指针当前位置开始计算的位移
ios::end 从流末尾处开始计算的位移
流指针 get 和 put 的值对文本文件(text file)和二进制文件(binary file)的计算方法都是不同的,因为文本模式的文件中某些特殊字符可能被修改。由于这个原因,建议对以文本文件模式打开的文件总是使用seekg 和 seekp的第一种原型,而且不要对tellg 或 tellp 的返回值进行修改。对二进制文件,你可以任意使用这些函数,应该不会有任何意外的行为产生。
以下例子使用这些函数来获得一个二进制文件的大小:
-
// obtaining file size
-
#include <iostream>
-
#include <fstream>
-
using namespace std;
-
int main ()
-
{
-
const char * filename = "example.txt";
-
long l,m;
-
ifstream file(filename, ios::in|ios::binary);
-
l = file.tellg();
-
file.seekg(0, ios::end);
-
m = file.tellg();
-
file.close();
-
cout <<"size of "<< filename;
-
cout <<" is "<< (m-l)<<" bytes.\n";
-
return 0;
-
}
-
//size of example.txt is 40 bytes.
四、open与fopen的区别
系统调用的文件操作分别直接基于IO无缓存操作以及带有缓存操作;
不带缓存的函数特点是直接对文件(包括设备)进行读写操作;
不带缓存的函数不是ANSI C的组成部分,是POSIX的组成部分
都是基于文件描述符,基于基本的IO控制,不带缓存;
关于不带缓存的情景:
运行系统调用时,Linux必须从用户态切换到内核态,执行相应的请求,然后在返回到用户态;
如果不带缓存会频繁的运行系统调用,会降低程序的效率;
标准I/O操作提供流缓存,目的就是尽可能减少使用read()和wirte()等不带缓存的系统调用;
比如:printf(), scanf();
不带缓存直接进行IO操作 | 基于缓存对文件进行操作 |
---|---|
int open(const char*path, int flags, int perms) | FILE *fopen(const char* path, const char* mode) FILE *fdopen(int fd, const char* mode) FILE *freopen(const char*path, const char*mode, FILE*stream) |
int close(int fd) | int fclose(FILE *stream) |
ssize_t read(int fd, void *buf, size_t count) | size_t fread(void*ptr,size_t size,size_t nmemb,FILE*stream) |
ssize_t write(int fd, void *buf, size_t count) | size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE*stream) |
off_t lseek(int fd, off_t offset, int whence) | int fseek(FILE *stream, long offset, int fromwhere); |
罗列了他们之前的声明,那么该如何选择呢:
使用带缓存操作的特点:
1.存在缓冲,操作外存次数减少,执行速度快效率高
2.fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api
3.fopen是标准c函数。返回文件流而不是linux下文件句柄
4.fopen可移植,open不能
使用直接操作IO:
1.open为系统调用,直接返回文件句柄
2.因为UNIX系统中设备驱动都是文件形式所以推荐使用open进行IO操作
3.设备文件不可以当成流式文件来用,只能用open
标准IO提供了3种类型的带缓冲存储;
1 全缓冲
2 行缓冲
3 无缓冲
自行查找资料了解相关知识点;
一般用fopen打开普通文件,用open打开设备文件