1、C的文件操作
1.1 不带缓冲I/O
不带缓冲的I/O每次调用都会触发内核中的一个系统调用。这类I/O操作是围绕一个文件的文件描述符来进行的。此类文件操作常用的函数如下表,这些函数及其所用的一些符号在io.h和fcntl.h中定义,在使用时要加入相应的头文件。
函数原型 | 功能介绍 |
int open(const char *path, int access [, unsigned mode]); | 打开一个文件并返回它的文件描述符,如果失败,将返回一个小于0的值。参数path是要打开的文件名,access是打开的模式,mode是可选项。access可取的值如下: O_RDONLY :只读方式 O_WRONLY :只写方式 O_RDWR :读/写方式 O_APPEND :追加方式 O_CREAT :如果文件不存在就创建 O_EXCL 和O_CREAT连用,如果文件存在返回错误 O_BINARY :二进制方式 对于多个要求,可以用"|"运算符来连接 |
int close(int handle); | 关闭一个文件描述符。 |
long lseek(int handle, long offset, int fromwhere); | 定位到指定的位置,参数offset是移动的量,fromwhere是移动的基准位置,SEEK_SET:文件首部;SEEK_CUR:文件当前位置;SEEK_END:文件尾。 |
int read(int handle, void *buf, unsigned len); | 从文件读取一块,参数buf保存读出的数据,len是读取的字节。函数返回实际读出的字节。 |
int write(int handle, void *buf, unsigned len); | 写一块数据到文件中,参数的含义同read(),返回实际写入的字节。 |
int eof(int handle); | 测试文件是否结束,是返回1,否则返回0; |
long filelength(int handle); | 返回文件长度 |
int rename(const char *oldname, const char *newname); | 重命名文件,参数oldname是旧文件名,newname是新文件名。成功返回0 |
int chsize(int handle, long size); | 改变文件长度,参数size表示文件新的长度,成功返回0,否则返回-1,如果指定的长度小于文件长度,则文件被截短;如果指定的长度大于文件长度,则在文件后面补'/0'。 |
1.2 标准I/O库
这种方式的文件操作有一个重要的结构流——FILE,FILE在stdio.h中定义如下:
typedef struct {
int level; /* fill/empty level of buffer */
unsigned flags; /* File status flags */
char fd; /* File descriptor */
unsigned char hold; /* Ungetc char if no buffer */
int bsize; /* Buffer size */
unsigned char _FAR *buffer; /* Data transfer buffer */
unsigned char _FAR *curp; /* Current active pointer */
unsigned istemp; /* Temporary file indicator */
short token; /* Used for validity checking */
} FILE; /* This is the FILE object */
标准I/O库的函数如下:
函数原型 | 功能介绍 |
FILE *fopen(const char *filename,const char *mode); | fopen实现三个功能:为使用而打开一个流、把一个文件和此流相连接、给此流返回一个FILE指针 。参数filename指向要打开的文件名,mode表示打开状态的字符串,其取值如下表: "r" :以只读方式打开文件 |
int fclose(FILE *fp); | 关闭用fopen()打开的文件,如果成功,返回0,失败返回EOF。 |
int fputc(int c, FILE *stream); | 向流写一个字符,成功返回这个字符,失败返回EOF。 |
int fputc(FILE *stream); | 从流中读一个字符,成功返回这个字符,失败返回EOF。 |
int fseek(FILE *stream, long offset, int whence); | 定位到流中指定的位置,如果成功返回0,参数offset是移动的字符数,whence是移动的基准,取值是: SEEK_SET 0 文件开头 |
int fputs(const char *s, FILE *stream); | 写一个字符串到流中 |
char *fgets(char *s, int n, FILE *stream); | 从流中读取n-1个字符,除非读完一行,参数s是来接收字符串,如果成功则返回s的指针,否则返回NULL。 |
int fprintf(FILE *stream, const char *format[, argument, ...]); | 按格式输入到流,其用法和printf()相同,不过不是写到控制台,而是写到流。 |
int fscanf(FILE *stream, const char *format[, address, ...]); | 从流中按格式读取,其用法和scanf()相同,不过不是从控制台读取,而是从流读取。 |
int feof(FILE *stream); | 检测是否已到文件尾,是返回真,否则返回0。 |
int ferror(FILE *stream); | 返回流最近的错误代码 |
void rewind(FILE *stream); | 把当前的读写位置回到文件开始。 |
int remove(const char *filename); | 删除文件,参数就是要删除的文件名,成功返回0。 |
size_t fread(void *ptr, size_t size, size_t n, FILE *stream); | 从流中读指定个数的字符,参数ptr是保存读取的数据,void*的指针可用任何类型的指针来替换,如char*、int *等等来替换;size是每块的字节数;n是读取的块数,如果成功,返回实际读取的块数(不是字节数),本函数一般用于二进制模式打开的文件中。 |
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream); | 向流中写指定的数据,参数ptr是要写入的数据指针,void*的指针可用任何类型的指针来替换,如char*、int *等等来替换;size是每块的字节数;n是要写的块数,如果成功,返回实际写入的块数(不是字节数),本函数一般用于二进制模式打开的文件中。 |
FILE *tmpfile(void); | 生成一个临时文件,以"w+b"的模式打开,并返回这个临时流的指针,如果失败返回NULL。在程序结束时,这个文件会被自动删除。 |
char *tmpnam(char *s); | 生成一个唯一的文件名,其实tmpfile()就调用了此函数,参数s用来保存得到的文件名,并返回这个指针,如果失败,返回NULL。 |
1.3 不带缓冲I/O和标准I/O的区别
这里使用两个对应的函数进行比较:
- ssize_t write(int filedes, const void *buff, size_t nbytes);
- size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);
上面的buff和ptr都是指应用程序自己使用的buffer,实际上当需要对文件进行写操作时,都会先写到内核所设的缓冲存储器。如果该缓存未满,则并不将其排入输出队列,直到缓存写满或者内核再次需要重新使用此缓存时才将其排入磁盘I/O输入队列,再进行实际的I/O操作,也就是此时才把数据真正写到磁盘,这种技术叫延迟写。
如果我们直接用不带缓冲/O对内核的缓冲区进行读写,会产生许多管理不善而造成的麻烦(如一次性写入过多,或多次系统调用导致的效率低下)。标准I/O为我们解决了这些问题,它处理很多细节,如缓冲区分配,以优化长度执行I/O等,更便于我们使用。由于标准I/O在系统调用的上一层多加了一个缓冲区,也因此引入了流的概念,在UNIX/Linux下表示为FILE*(并不限于 UNIX/Linux,ANSI C都有FILE的概念),FILE实际上包含了为管理流所需要的所有信息:实际I/O的文件描述符,指向流缓存的指针(标准I/O缓存,由malloc分 配,又称为用户态进程空间的缓存,区别于内核所设的缓存),缓存长度,当前在缓存中的字节数,出错标志等。因此可知,不带缓存的I/O对文件描述符操作,带缓存的标准I/O是针对流的。标准I/O对每个I/O流自动进行缓存管理(标准I/O函数通常调用malloc来分配缓存)。它提供了三种类型的缓存:
- 全缓存:当填满标准I/O缓存后才执行I/O操作。磁盘上的文件通常是全缓存的。
- 行缓存:当输入输出遇到新行符或缓存满时,才由标准I/O库执行实际I/O操作。stdin、stdout通常是行缓存的。
- 无缓存:相当于read、write了。stderr通常是无缓存的,因为它必须尽快输出。
一般而言,由系统选择缓存的长度,并自动分配。标准I/O库在关闭流的时候自动释放缓存。另外,也可以使用函数fflush()将流所有未写的数据送入(刷新)到内核(内核缓冲区),fsync()将所有内核缓冲区的数据写到文件(磁盘)。
在标准I/O库中也有引入缓存管理而带来的缺点--效率问题。例如当使用每次一行函数fgets和fputs时,通常需要复制两次数据:
- 一次是在内核和标准I/O缓存之间(当调用read和write时);
- 第二次是在标准I/O缓存(通常系统分配和管理)和用户程序中的行缓存(fgets的参数就需要一个用户行缓存指针之间。
2、C++的文件操作
在C++中,有一个stream这个类,所有的I/O都以这个"流"类为基础的,包括我们要认识的文件I/O,stream这个类有两个重要的运算符:
- 插入器(<<) :向流输出数据。比如说系统有一个默认的标准输出流(cout),一般情况下就是指的显示器,所以,cout<<"Write Stdout"<<'/n';就表示把字符串"Write Stdout"和换行字符('/n')输出到标准输出流。
- 析取器(>>) :从流中输入数据。比如说系统有一个默认的标准输入流(cin),一般情况下就是指的键盘,所以,cin>>x;就表示从标准输入流中读取一个指定类型(即变量x的类型)的数据。
在C++中,对文件的操作是通过stream的子类fstream(file stream)来实现的,所以,要用这种方式操作文件,就必须加入头文件fstream.h。
接下来我们介绍fstream类中的成员函数:
成员函数原型 | 函数功能介绍 |
void open(const char* filename,int mode,int access); | 打开文件,filename是要打开的文件名,mode是要打开文件的方式,access是打开文件的属性 。打开文件的方式在类ios(是所有流式I/O类的基类)中定义,常用的值如下: ios::app:以追加的方式打开文件 打开文件的属性取值是: 0:普通文件,打开访问 |
close(); | 打开的文件使用完成后一定要关闭,fstream提供了成员函数close()来完成此操作。 |
ofstream &put(char ch); | 读写文件分为文本文件和二进制文件的读取,对于文本文件的读取比较简单,用插入器和析取器就可以了。而对于二进制文件,put()函数向流写入一个字符。 |
ifstream &get(char &ch); | 从流中读取一个字符,结果保存在引用ch中,如果到文件尾,返回空字符。 |
int read(unsigned char *buf,int num); | 读二进制数据块 |
int write(const unsigned char *buf,int num); | 写二进制数据块 |
getline(char *str, streamsize num); | 读取一行到一个字符数组中,str为C字符数组首地址,num为需要读取的字符个数,而不是字节个数。如果单行超过了缓冲,则循环会结束。用getline的时候,一定要保证缓冲区够大,能够容纳各种可能的数据行。推荐使用全局函数getline(istream&, string&)代替成员函数getline。 |
int eof(); | 检测是否到达文件尾,如果到达文件尾返回非0值,否则返回0。 |
istream &seekg(streamoff offset,seek_dir origin); | 文件定位 ,设置读位置 |
ostream &seekp(streamoff offset,seek_dir origin); | 文件定位 ,设置写位置 |