Linux标准IO(二)-打开、读写、定位文件
1.打开文件fopen
在前面所介绍的文件 I/O 中,使用 open()系统调用打开或创建文件,而在标准 I/O 中,我们将使用库函数fopen()打开或创建文件,fopen()函数原型如下所示:
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
使用该函数需要包含头文件 stdio.h。
函数参数和返回值含义如下:
path:参数 path 指向文件路径,可以是绝对路径、也可以是相对路径。
mode:参数 mode 指定了对该文件的读写权限,是一个字符串,稍后介绍。
返回值:调用成功返回一个指向 FILE 类型对象的指针(FILE *),该指针与打开或创建的文件相关联,后续的标准 I/O 操作将围绕 FILE 指针进行。如果失败则返回 NULL,并设置 errno 以指示错误原因。
参数 mode 字符串类型,可取值为如下值之一:
新建文件的权限
由 fopen()函数原型可知,fopen()只有两个参数 path 和 mode,不同于 open()系统调用,它并没有任何一个参数来指定新建文件的权限。当参数 mode 取值为"w"、"w+"、"a"、"a+"之一时,如果参数 path 指定的文件不存在,则会创建该文件,那么新的文件的权限是如何确定的呢?
虽然调用 fopen()函数新建文件时无法手动指定文件的权限,但却有一个默认值:
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)
使用示例
使用只读方式打开文件:
fopen(path, "r");
使用可读、可写方式打开文件:
fopen(path, "r+");
使用只写方式打开文件,并将文件长度截断为 0,如果文件不存在则创建该文件:
fopen(path, "w");
fclose()关闭文件
调用 fclose()库函数可以关闭一个由 fopen()打开的文件,其函数原型如下所示:
#include <stdio.h>
int fclose(FILE *stream);
参数 stream 为 FILE 类型指针,调用成功返回 0;失败将返回 EOF(也就是-1),并且会设置 errno 来指示错误原因。
2.读文件和写文件
当使用 fopen()库函数打开文件之后,接着我们便可以使用 fread()和 fwrite()库函数对文件进行读、写操作了,函数原型如下所示:
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
库函数 fread()用于读取文件数据,其参数和返回值含义如下:
ptr:fread()将读取到的数据存放在参数 ptr 指向的缓冲区中;
size:fread()从文件读取 nmemb 个数据项,每一个数据项的大小为 size 个字节,所以总共读取的数据大小为 nmemb * size 个字节。
nmemb:参数 nmemb 指定了读取数据项的个数。
stream:FILE 指针。
返回值:调用成功时返回读取到的数据项的数目(数据项数目并不等于实际读取的字节数,除非参数size 等于 1);如果发生错误或到达文件末尾,则 fread()返回的值将小于参数 nmemb,那么到底发生了错误还是到达了文件末尾,fread()不能区分文件结尾和错误,究竟是哪一种情况,此时可以使用 ferror()或 feof()函数来判断。
库函数 fwrite()用于将数据写入到文件中,其参数和返回值含义如下:
ptr:将参数 ptr 指向的缓冲区中的数据写入到文件中。
size:参数 size 指定了每个数据项的字节大小,与 fread()函数的 size 参数意义相同。
nmemb:参数 nmemb 指定了写入的数据项个数,与 fread()函数的 nmemb 参数意义相同。
stream:FILE 指针。
返回值:调用成功时返回写入的数据项的数目(数据项数目并不等于实际写入的字节数,除非参数 size等于 1);如果发生错误,则 fwrite()返回的值将小于参数 nmemb(或者等于 0)。
由此可知,库函数 fread()、fwrite()中指定读取或写入数据大小的方式与系统调用 read()、write()不同,前者通过 nmemb(数据项个数)*size(每个数据项的大小)的方式来指定数据大小,而后者则直接通过一个 size 参数指定数据大小。
譬如要将一个 struct mystr 结构体数据写入到文件中,可按如下方式写入:
fwrite(buf, sizeof(struct mystr), 1, file);
当然也可以按如下方式写:
fwrite(buf, 1, sizeof(struct mystr), file);
使用示例
结合使用本小节与上小节所学内容,我们来编写一个简单地示例代码,使用标准 I/O 方式对文件进行读写操作。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char buf[] = "Hello World!\n";
FILE *fp = NULL;
/* 打开文件 */
if (NULL == (fp = fopen("./test_file", "w"))) {
perror("fopen error");
exit(-1);
}
printf("文件打开成功!\n");
/* 写入数据 */
if (sizeof(buf) >
fwrite(buf, 1, sizeof(buf), fp)) {
printf("fwrite error\n");
fclose(fp);
exit(-1);
}
printf("数据写入成功!\n");
/* 关闭文件 */
fclose(fp);
exit(0);
}
首先使用 fopen()函数将当前目录下的 test_file 文件打开,调用 fopen()时 mode 参数设置为"w",表示以只写的方式打开文件,并将文件的长度截断为 0,如果指定文件不存在则创建该文件。打开文件之后调用fwrite()函数将"Hello World!"字符串数据写入到文件中。
写入完成之后,调用 fclose()函数关闭文件,退出程序。
示例代码标准 I/O 之 fread()读文件
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char buf[50] = {0};
FILE *fp = NULL;
int size;
/* 打开文件 */
if (NULL == (fp = fopen("./test_file", "r"))) {
perror("fopen error");
exit(-1);
}
printf("文件打开成功!\n");
/* 读取数据 */
if (12 > (size = fread(buf, 1, 12, fp))) {
if (ferror(fp)) { //使用 ferror 判断是否是发生错误
printf("fread error\n");
fclose(fp);
exit(-1);
}
/* 如果未发生错误则意味着已经到达了文件末尾 */
}
printf("成功读取%d 个字节数据: %s\n", size, buf);
/* 关闭文件 */
fclose(fp);
exit(0);
}
首先同样使用 fopen()打开当前目录下的 test_file 文件得到 FILE 指针,调用 fopen()时其参数 mode 设置为"r",表示以只读方式打开文件。
接着使用 fread()函数从文件中读取 12 * 1=12 个字节的数据,将读取到的数据存放在 buf 中,当读取到的字节数小于指定字节数时,表示发生了错误或者已经到达了文件末尾,程序中调用了库函数 ferror()来判断是不是发生了错误,该函数将会在 4.7 小节中介绍。如果未发生错误,那么就意味着已经达到了文件末尾,其实也就说明了在调用 fread()读文件时对应的读写位置到文件末尾之间的字节数小于指定的字节数。
最后调用 printf()打印结果,编译测试:
3.fseek定位
库函数 fseek()的作用类似于学习的系统调用 lseek(),用于设置文件读写位置偏移量,lseek()用于文件 I/O,而库函数 fseek()则用于标准 I/O,其函数原型如下所示:
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
函数参数和返回值含义如下:
stream:FILE 指针。
offset:与 lseek()函数的 offset 参数意义相同。
whence:与 lseek()函数的 whence 参数意义相同。
返回值:成功返回 0;发生错误将返回-1,并且会设置 errno 以指示错误原因;与 lseek()函数的返回值意义不同,这里要注意!
调用库函数 fread()、fwrite()读写文件时,文件的读写位置偏移量会自动递增,使用 fseek()可手动设置文件当前的读写位置偏移量。
譬如将文件的读写位置移动到文件开头处:
fseek(file, 0, SEEK_SET);
将文件的读写位置移动到文件末尾:
fseek(file, 0, SEEK_END);
将文件的读写位置移动到 100 个字节偏移量处:
fseek(file, 100, SEEK_SET);
使用示例
示例代码 使用 fseek()调整文件读写位置
//示例代码 使用 fseek()调整文件读写位置
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = NULL;
char rd_buf[100] = {0};
char wr_buf[] = "自学\n";
int ret;
/* 打开文件 */
if (NULL == (fp = fopen("./test_file", "w+"))) {
perror("fopen error");
exit(-1);
}
printf("文件打开成功!\n");
/* 写文件 */
if (sizeof(wr_buf) >
fwrite(wr_buf, 1, sizeof(wr_buf), fp)) {
printf("fwrite error\n");
fclose(fp);
exit(-1);
}
printf("数据写入成功!\n");
/* 将读写位置移动到文件头部 */
if (0 > fseek(fp, 0, SEEK_SET)) {
perror("fseek error");
fclose(fp);
exit(-1);
}
/* 读文件 */
if (sizeof(wr_buf) >
(ret = fread(rd_buf, 1, sizeof(wr_buf), fp))) {
printf("fread error\n");
fclose(fp);
exit(-1);
}
printf("成功读取%d 个字节数据: %s\n", ret, rd_buf);
/* 关闭文件 */
fclose(fp);
exit(0);
}
程序中首先调用 fopen()打开当前目录下的 test_file 文件,参数 mode 设置为"w+";接着调用 fwrite()将wr_buf 缓冲区中的字符串数据写入到文件中;由于调用了fwrite(),所以此时的读写位置已经发生了改变,不再是文件头部,所以程序中调用了 fseek()将读写位置移动到了文件头,接着调用 fread()从文件头部开始读取刚写入的数据,读取成功之后打印出信息。
ftell()函数
库函数 ftell()可用于获取文件当前的读写位置偏移量,其函数原型如下所示:
#include <stdio.h>
long ftell(FILE *stream);
参数 stream 指向对应的文件,函数调用成功将返回当前读写位置偏移量;调用失败将返回-1,并会设置errno 以指示错误原因。
我们可以通过 fseek()和 ftell()来计算出文件的大小,示例代码如下所示:
示例代码 fseek()和 ftell()函数获取文件大小
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = NULL;
int ret;
/* 打开文件 */
if (NULL == (fp = fopen("./testApp.c", "r"))) {
perror("fopen error");
exit(-1);
}
printf("文件打开成功!\n");
/* 将读写位置移动到文件末尾 */
if (0 > fseek(fp, 0, SEEK_END)) {
perror("fseek error");
fclose(fp);
exit(-1);
}
/* 获取当前位置偏移量 */
if (0 > (ret = ftell(fp))) {
perror("ftell error");
fclose(fp);
exit(-1);
}
printf("文件大小: %d 个字节\n", ret);
/* 关闭文件 */
fclose(fp);
exit(0);
}
首先打开当前目录下的 testApp.c 文件,将文件的读写位置移动到文件末尾,然后再获取当前的位置偏移量,也就得到了整个文件的大小。
运行测试:
从上图可知,程序计算出的文件大小与 ls 命令查看到的文件大小是一致的。