fopen/fclose
fopen
在操作文件之前要用fopen命令打开文件,操作完毕要用fclose命令关闭文件。
FILE *fopen(const char *path, const char *mode);
path
文件的路径名,可以是相对路径也可以是绝对路径。
mode
文件打开方式,mode参数是一个字符串,由rwab+五个字符组合而成。r表示读;w表示写;a表示追加;b表示二进制模式,不写b则表示文本模式;+表示读写。
Hello\r\nWorld\r\n
- Windows系统下,以文本模式打开:读"\r\n",会被转换成"\n"。写"\n",会被转换成"\r\n";以二进制模式打开则不会做这种转换。
- UNIX系统下,不管以文本模式还是二进制模式打开,数据都不会转换。
返回值
如果文件打开成功,返回一个指向FILE结构体的文件指针。
如果文件打开失败,返回NULL。
fclose
把文件指针传给fclose可以关闭它所标识的文件。
int fclose(FILE *fp);
fp
是指向FILE结构体的指针,用于指示打开的文件。
返回值
成功时返回 0,失败时返回EOF。
stdin/stdout/stderr
程序启动时会自动打开终端设备,并且用3个FILE*指针stdin、stdout和stderr指向这个设备,这三个文件指针是Iibc中定义的全局变量,在stdio.h中声明,用户程序可以直接使用这三个文件指针。
stdin
用途
用于从标准输入设备读取数据,通常是从键盘输入。
类型
FILE*指针,指向一个输入流。
函数
主要通过scanf、fget、fread等函数进行数据读取。
stdout
用途
用于向标准输出设备写入数据,通常是显示到屏幕上。
类型
FILE*指针,指向一个输出流。
函数
主要通过printf、puts、fwrite等函数进行数据输出。
stderr
用途
用于输出错误信息,通常是显示到屏幕上,与标准输出流分开。
类型
FILE*指针,指向一个错误流。
函数
主要通过fprintf、perror等函数进行错误信息输出。
errno与perror/strerror函数
errno
errno在头文件errno.h中声明,是一个整型变量,所有错误码都是正整数。如果在程序中打印错误信息时直接打印errno的值,不能很直观地看出是什么错误。比较好的办法是用perror将errno解释成字符串再打印。
perror
perror根据当前errno的值,查找对应的错误信息字符串,并将其添加到输出中。
void perror(const char *s);
s
用户提供的自定义字符串,作为错误信息的前缀。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
FILE *fp = fopen("without", "r");
if (fp == NULL) {
perror("Open file without");
printf("errno: %d\n", errno);
exit(1);
}
return 0;
}
strerror
strerror函数用于根据错误码返回对应的错误信息字符串,声明在string.h头文件中。
char *strerror(int errnum);
errnum
表示错误码的整数值,通常这个值是由errno变量提供的。
返回值
返回一个指向静态内存区域的指针,该区域包含错误码errnum所对应的错误信息字符串。需要注意的是,返回的字符串是静态的,因此每次调用strerror都会覆盖之前的内容。
以字节为单位的I/O函数
fgetc
从指定的文件中读一个字节。系统对于每个打开的文件都记录着当前读写位置,当文件打开时,读写位置在文件开头,每调用一次fgetc,读写位置向后移动一个字节,因此可以连续多次调用fgetc函数依次读取多个字节。
int fgetc(FILE *stream);
stream
指向FILE结构的指针,表示要从中读取字符的文件流。
返回值
成功时,返回读到的字符(int类型)。
失败时,返回EOF,表示遇到文件末尾或发生错误。
getchar
从标准输入流读一个字节,调用getchar()相当于调用fgetc(stdin)。
int getchar(void);
返回值
成功时,返回读到的字符(int类型)。
失败时,返回EOF,表示遇到文件末尾或发生错误。
fputc
fputc函数向指定的文件中写一个字节。每调用一次fputc,读写位置向后移动一个字节,因此可以连续多次调用fputc函数依次写入多个字节。如果文件是以追加方式打开的,则每次调用fputc总是先把读写位置移到文件末尾然后把要写入的字节追加到后面。
int fputc(int c, FILE *stream);
c
要写入的字符(int类型)。
stream
指向FILE结构的指针,表示要写入字符的文件流。
返回值
成功时,返回写入的字符(int类型)。
失败时,返回EOF,表示写入错误。
putchar
putchar向标准输出写一个字节,调用putchar(c)相当于调用fputc(c, stdout)。
int putchar(int c);
c
要写入的字符(int类型)。
返回值
成功时,返回写入的字符(int类型)。
失败时,返回EOF,表示写入错误。
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
int ch;
// 打开文件 "file" 以进行读写操作。如果文件不存在,它将被创建。
if ((fp = fopen("file", "w+")) == NULL) {
perror("Open file\n"); // 打开文件失败,打印错误信息
exit(1);
}
// 从标准输入读取字符并写入文件,直到遇到文件结束符(EOF)
while ((ch = getchar()) != EOF)
fputc(ch, fp);
// 将文件指针移动到文件开头,以便重新读取文件内容
rewind(fp);
// 从文件读取字符并输出到标准输出,直到遇到文件结束符(EOF)
while ((ch = fgetc(fp)) != EOF)
putchar(ch);
// 关闭文件
fclose(fp);
return 0;
}
在终端设备输入时,表示文件结束的方法:
在输入过程中,当光标在行首时,按下Ctrl+D(也称为EOF)来标记输入结束。
如果光标不在行首,通常需要连续按Ctrl+D两次,第一次表示当前输入结束,第二次表示输入流结束。
操作读写位置的函数
fseek
用于移动文件指针,可以用于随机访问文件中的任何位置。
int fseek(FILE *stream, long offset, int whence);
stream
指向FILE结构的指针,表示文件流。
offset
从whence指定的位置开始的偏移量(以字节为单位)。offset可正可负,负值表示向前移动,正值表示向后移动。如果向前移动的字节数超过了文件开头则出错返回。如果向后移动的字节数超过了文件末尾,再次写入时将增大文件尺寸,从原来的文件末尾到fseek移动之后的读写位置之间的字节都是0。
whence
在文本模式下使用fseek时,如果需要从文件的特定位置开始读取或写入,最好使用ftell来获取当前指针位置,并以此作为参考点。
在二进制模式下,fseek的使用较为灵活。
表示基准位置的常量:
- SEEK_SET从文件开头移动offset个字节。
- SEEK_CUR从当前读写位置移动offset个字节。
- SEEK_END从文件末尾移动offset个字节。
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
// 打开文件 "file" 以进行读写操作
if ((fp = fopen("file", "r+")) == NULL) {
perror("Error opening file textfile");
exit(1);
}
// 移动文件指针到文件的第10个字节
if (fseek(fp, 10L, SEEK_SET) != 0) {
perror("Error seeking file textfile");
fclose(fp);
exit(1);
}
// 写入字符 'K' 到文件
if (fputc('K', fp) == EOF) {
perror("Error writing to file textfile");
fclose(fp);
exit(1);
}
// 关闭文件
fclose(fp);
return 0;
}
返回值
成功时,返回写入的字符(int类型)。
失败时,返回EOF,表示写入错误。
ftell
获取当前文件指针的位置。
long ftell(FILE *stream);
stream
指向FILE结构的指针,表示文件流。
返回值
成功时,返回当前文件指针的位置(相对于文件开头的偏移量)。
失败时,返回-1L。
rewind
将文件指针重置到文件开头。
void rewind(FILE *stream);
stream
指向FILE结构的指针,表示文件流。
以字符串为单位的I/O函数
fgets
从指定的文件中读一行字符到调用者提供的缓冲区中。
char *fgets(char *s, int size, FILE *stream);
s
缓冲区首地址。
size
缓冲区长度。
stream
指向FILE结构的指针,表示文件流。
返回值
如果fgets读到文件末尾的最后几个字符,不管是不是以'\n'结尾都加上一个'\0'存入缓冲区并返回。下次再调用fgets时读写位置已经到达文件末尾,则返回NULL。
fgets主要用于读取文本文件而不适合处理二进制文件。
fputs
向指定的文件中写入一个字符串。
int fputs(const char *s, FILE *stream);
puts
向标准输出写入一个字符串。
int puts(const char *s);
缓冲区s中保存的是以NULL结尾的字符串。
fputs将该字符串写入文件stream,但并不写入结尾的'\0'。
puts将字符串s写到标准输出(不包括结尾的'\0'),然后自动写一个'\n'到标准输出。
以记录为单位的I/O函数
记录是指一串固定长度的字节,比如一个int、一个结构体或者一个定长数组。
fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr
指向用于存储读取数据的内存块的指针。
size
每个数据项的大小,以字节为单位。
nmemb
要读取的数据项的数量。
stream
指向FILE结构的指针,表示文件流。
返回值
返回实际读取的数据项的数量。
fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr
指向要写入的数据的内存块的指针。
size
每个数据项的大小,以字节为单位。
nmemb
要写入的数据项的数量。
stream
指向FILE结构的指针,表示文件流。
返回值
返回实际写入的数据项的数量。
#include <stdio.h>
#include <stdlib.h>
struct record {
char name[10];
int age;
};
int main()
{
// 初始化一个 record 数组,包含两个结构体
struct record array[2] = {{"Ken", 24}, {"Knuth", 28}};
// 打开文件 recfile 以进行写操作
FILE *fp = fopen("recfile", "w");
if (fp == NULL) {
// 如果文件打开失败,打印错误信息并退出程序
perror("Open file recfile");
exit(1);
}
// 将数组写入文件
size_t written = fwrite(array, sizeof(struct record), 2, fp);
if (written != 2) {
// 如果写入的记录数不等于预期值,打印错误信息
perror("Write error");
exit(1);
}
// 关闭文件
fclose(fp);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
// 定义结构体 record
struct record {
char name[10];
int age;
};
int main()
{
// 定义一个 record 类型的数组,用来存储两个记录
struct record array[2];
// 打开文件 "recfile" 进行读取
FILE *fp = fopen("recfile", "r");
if (fp == NULL) {
perror("Open file recfile");
exit(1);
}
// 从文件中读取两个 record 结构体
fread(array, sizeof(struct record), 2, fp);
printf("Name1: %s\tAge1: %d\n", array[0].name, array[0].age);
printf("Name2: %s\tAge2: %d\n", array[1].name, array[1].age);
// 关闭文件
fclose(fp);
return 0;
}
直接在文件中读写结构体的程序是不可移植的,因为不同平台的大小端可能不同,结构体的填充方式也可能不同。