C标准I/O库
打开/关闭文件
fopen函数
fopen函数的原型为
FILE *fopen (const char *__restrict __filename,const char *__restrict __modes);
作用是打开一个文件并返回其文件指针。
参数1:const char *__restrict __filename:表示打开的文件的路径,可以写相对路径/绝对路径
参数2:const char *__restrict __modes:表示打开文件的方式,有读/写/追加写等
(1)"r": 只读模式 没有文件打开失败
(2)"w": 只写模式 若存在文件则会清空文件,不存在文件则创建新文件
(3)"a": 只追加写模式 不会覆盖原有内容 新内容写到末尾,如果文件不存在则创建
(4)"r+": 读写模式 文件必须存在 写入是从头一个一个覆盖
(5)"w+": 读写模式 可读取,写入,同样会清空文件内容,不存在则创建新文件
(6)"a+": 读写追加模式 可读取,写入从文件末尾开始,如果文件不存在则创建
#include <stdio.h>
int main()
{
char name[] = "io.txt";
if(fopen(name,"a+"))//表示以追加写的形式写入,同时可读
{
printf("打开文件成功\n");
}
else
perror("打开文件失败");
return 0;
}
fclose函数
fclose函数原型为
int fclose (FILE *__stream);
作用是关闭文件
参数为需要关闭的文件的指针,成功关闭返回0,失败返回EOF
#include <stdio.h>
int main()
{
char name[] = "io.txt";
FILE *file;
if(file = fopen(name,"a+"))//表示以追加写的形式写入,同时可读
printf("打开文件成功\n");
else
perror("打开文件失败");
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
return 0;
}
向文件中写入数据
fputc函数
原型:int fputc (int __c, FILE *__stream);
作用是向__stream指向的文件中写入一个字符
int __c:要写入的字符,按ASCII码写入
FILE *__stream:要写入的文件
成功写入则返回写入的字符值,失败返回EOF
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"w+");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
//定义一个tmp接收写入的字符
char tmp = fputc('j',file);
printf("%c\n",tmp);
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
return 0;
}
fputs函数
原型:int fputs (const char *__restrict __s, FILE *__restrict __stream)
向文件中写入字符串
const char *__restrict __s:待写入的字符串缓冲区
FILE *__restrict __stream:写入的目标文件
成功写入返回非负整数,写入失败返回EOF
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"w+");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
char str[] = "hello world";
int result;
result = fputs(str,file);
if(result < 0)
perror("写入文件失败");
else
printf("写入文件成功\n");
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
return 0;
}
fprintf函数
原型fprintf (FILE *__restrict __stream, const char *__restrict __fmt, ...)
参数1:FILE *__restrict __stream: 要写入的文件,写在哪里取决于访问模式 (w和a的区别)
char *__restrict __fmt: 格式化字符串
...: 变长参数列表
return: 成功返回正整数(写入字符总数不包含换行符) 失败返回EOF
和最常使用的printf函数的区别就是输入参数多了一个文件指针
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"w+");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
char str[] = "hello world";
int result;
result = fprintf(file,"%s",str);
if(result < 0)
perror("写入文件失败");
else
{
//写入个数应该为11,包括空格但是不包括换行符
printf("写入文件成功,写入个数为%d\n",result);
}
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
return 0;
}
fwrite函数
函数原形size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
const void *ptr : 指针指向要写入数据的内存首地址 ;
size_t size : 要写入数据的 基本单元 的字节大小 , 单位字节;
size_t nmemb : 要写入数据的 基本单元 的个数 ;
FILE *stream : 文件指针 ;
返回值说明 : size_t 返回值返回的是实际写出到文件的 基本单元 个数 ;
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"w+");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
char str[] = "how are you";
int result;
result = fwrite(str,1,11,file);
if(result < 0)
perror("写入文件失败");
else
{
//写入个数应该为11,包括空格但是不包括换行符
printf("写入文件成功,写入个数为%d\n",result);
}
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
return 0;
}
从文件中读取数据
fgetc函数
原型:int fgetc (FILE *__stream)
FILE *__stream: 需要读取的文件
return: 读取的一个字节 到文件结尾或出错返回EOF
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"r");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
char result;
result = fgetc(file);
if(result < 0)
perror("读取失败");
else
//之前在io.txt中写入了"how are you",所以应该读到h
printf("读取到的字符是%c\n",result);
result = fgetc(file);
if(result < 0)
perror("读取失败");
else
//此处应该读到o
printf("读取到的字符是%c\n",result);
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
return 0;
}
fgets函数
char *fgets(char *restrict str, int size, FILE *restrict stream));
从指定文件中读取字符串
char *restrict str:接收读取的字符串的缓冲区
int size: 能够接收数据的长度,包括结束符
FILE *restrict stream:指定的文件指针
#include <stdio.h>
#include <memory.h>
#include <malloc.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"r");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
char *result = NULL;
char *str = NULL;
str = malloc(20);
result = fgets(str,6,file);
if(result == NULL)
perror("读取失败");
else
//之前在io.txt中写入了"how are you",此处应该读取到"how a",注意结束符也算在内
printf("读取到的字符串%s\n",str);
result = fgets(str,5,file);
if(result == NULL)
perror("读取失败");
else
//此处应该读取到"re y"
printf("读取到的字符串%s\n",str);
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
free(str);
return 0;
}
fscanf函数
int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...)
FILE *__restrict __stream: 读取的文件
char *__restrict __format: 读取的匹配表达式
...: 变长参数列表 用于接收匹配的数据
return: 成功返回参数的个数 失败返回0 报错或结束返回EOF
和常用的scanf函数相似,多了一个文件指针
fread函数
原型:size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
void *buffer 参数 : 将文件中的二进制数据读取到该缓冲区中 ;
size_t size 参数 : 读取的 基本单元 字节大小 , 单位是字节 , 一般是 buffer 缓冲的单位大小 ;
如果 buffer 缓冲区是 char 数组 , 则该参数的值是 sizeof(char) ;
如果 buffer 缓冲区是 int 数组 , 则该参数的值是 sizeof(int) ;
size_t count 参数 : 读取的 基本单元 个数 ;
FILE *stream 参数 : 文件指针 ;
size_t 返回值 : 实际从文件中读取的 基本单元 个数 ; 读取的字节数是 基本单元数 * 基本单元字节大小 ;
#include <stdio.h>
#include <memory.h>
#include <malloc.h>
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
FILE *file = NULL;
file = fopen(name,"r");
if(file == NULL)
perror("打开文件失败");
else
printf("打开文件成功\n");
char *str;
str = malloc(20);
int result = 0;
result = fread(str,1,5,file);
if(result < 0)
perror("读取数据失败");
else
//应该读取到how a
printf("%s %d\n",str,result);
if(fclose(file) >= 0)
printf("关闭文件成功\n");
else
perror("关闭文件失败");
free(str);
return 0;
}
系统调用
文件描述符的概念
在Linux系统中,当我们打开或创建一个文件(或套接字)时,操作系统会提供一个文件描述符(File Descriptor,FD),这是一个非负整数,我们可以通过它来进行读写等操作。
然而,文件描述符本身只是操作系统为应用程序操作底层资源(如文件、套接字等)所提供的一个引用或“句柄”。每个文件描述符都关联到内核一个struct file类型的结构体数据,也就是说当应用程序操控文件时,是根据文件的编号进行操作,具体的底层资源的改变是通过改变该结构体的内容实现的,不过这部分逻辑并不需要应用层实现
常见的系统调用
open()函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags,
mode_t mode);
作用是打开一个文件,并返回该文件的文件描述符
参数一是文件所在的路径,注意相对路径和绝对路径的区别
参数二包括
(1) O_RDONLY: 以只读方式打开文件
(2) O_WRONLY: 以只写方式打开文件
(3) O_RDWR: 以读写方式打开文件
(4) O_CREAT: 如果文件不存在,则创建一个新文件
(5) O_APPEND: 将所有写入操作追加到文件的末尾
(6) O_TRUNC: 如果文件存在并且以写入模式打开,则截断文件长度为0 还有其他标志,如O_EXCL(当与O_CREAT一起使用时,只有当文件不存在时才创建新文件)、O_SYNC(同步I/O)、O_NONBLOCK(非阻塞I/O)等 ,如果需要设置多个模式,可以进行或操作
参数三为可选参数,仅在参数二设置了O_CREAT标志且文件不存在时生效,用于指定新文件的权限,由三位八进制数字组成,如0666,0代表八进制,666代表八进制的666。分别代表了所有者,同组用户,其他用于的读写执行权限
返回值为该文件的文件描述符
read()函数
作用是从文件中读取数据
ssize_t read (int __fd, void *__buf, size_t __nbytes);
参数一是文件描述符,参数二为接收数据的地址,参数三为读取数据的个数,单位字节
返回值为实际读取的个数,单位字节
write()函数
作用是向文件中写入数据
ssize_t write (int __fd, const void *__buf, size_t __n);
参数一位文件描述符,参数二为待写入数据的缓冲区地址,参数三为写入数据的个数,单位字节
返回值为实际写入的个数,单位字节
文件读写指针
在C语言中,文件读写操作依赖于文件指针(也称为文件位置标记),文件指针记录当前读写操作的位置,每次读写操作后自动向后移动。对文件的操作比如写入、读取数据,其操作位置是由读写指针决定的,读写操作都是从读写指针指向的地址开始,比如write函数,write每写入一个字节的数据就会将读写指针往前移一个字节,read函数同理。所以当调用write函数之后若未调整读写指针的位置,则read函数读取的数据是错误的,因为read函数会从读写指针指向的地址开始,此时指向的是文件末尾。
write,read函数会使读写指针顺序后移,C语言也提供了调整读写指针的方法,lseek和fseek
fseek原型是
int fseek(FILE *stream, long int offset, int whence)
参数一是文件指针,参数二为相对于whence的偏移量
参数三表示开始添加偏移 offset 的位置。它一般指定为SEEK_SET(文件开头),SEEK_CUR(文件指针的当前位置),SEEK_END(文件末尾)三个其中之一
成功返回0,错误返回非零值
lseek原型是
off_t lseek(int fd, off_t offset, int whence);
参数一是文件描述符,参数二为相对于whence的偏移量
参数三表示开始添加偏移 offset 的位置。它一般指定为SEEK_SET(文件开头),SEEK_CUR(文件指针的当前位置),SEEK_END(文件末尾)三个其中之一
fseek和lseek实现的功能相同,fseek对文件指针进行操作,lseek则是对文件描述符进行操作
close函数
int close (int __fd);
作用是关闭一个文件描述符,这会使该文件描述符在当前进程内不可用,且引用计数-1,当该文件的引用计数降为0时才会真正关闭这个文件,例如不同进程打开了同一个文件,就会在所有进程都关闭对应的文件描述符之后才会真正关闭这个文件。
_exit()函数
void _exit(int status);
父进程可以接收到子进程的退出状态码,0代表正常退出
_exit()是由POSIX标准定义的系统调用,用于立即终止一个进程,定义在unistd.h中。这个调用确保进程立即退出,不执行任何清理操作。
_exit()在子进程终止时特别有用,这可以防止子进程的终止影响到父进程(比如,防止子进程意外地刷新了父进程未写入的输出缓冲区)。
_exit()通常在程序遇到严重错误需要立即退出时使用,避免了清理操作浪费的时间
exit()函数
void exit(int status);
这个是C标准库提供的关闭进程的函数,定义在stdlib
该函数在退出前会执行清理操作,包括
调用所有通过atexit()注册的终止处理函数(自定义)
刷新所有标准I/O缓冲区(刷写缓存到文件)
关闭所有打开的标准I/O流(比如通过fopen打开的文件)
_exit和exit的使用场景区别
通常在父进程中使用exit(),以确保程序在退出前能执行清理操作,如关闭文件和刷新输出。
在子进程中,特别是在fork()之后立即调用了一个执行操作(如exec())但执行失败时,推荐使用_exit()或_Exit()来确保子进程的快速、干净地退出,避免执行标准的清理操作,这些操作可能会与父进程发生冲突或不必要的重复。
综合案例
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define handle_error(cmd,result) \
if(result < 0) \
{ \
perror(cmd); \
exit(EXIT_FAILURE); \
} \
int main(int argc, char const *argv[])
{
char name[] = "io.txt";
char write_buf[] = "hello csdn";
char *read_buf;
read_buf = malloc(20);
int fd,tmp;
//打开io.txt文件,打开方式为可读写
fd = open(name,O_RDWR);
handle_error("open",fd);
tmp = write(fd,(void *)write_buf,sizeof(write_buf));
handle_error("write",tmp);
printf("实际写入的数据个数:%d\n",tmp);
lseek(fd,0,SEEK_SET);
tmp = read(fd,(void *)read_buf,20);
handle_error("read",tmp);
printf("读取到的数据:%s 个数%d\n",read_buf,tmp);
tmp = close(fd);
handle_error("close",tmp);
free(read_buf);
printf("程序还未结束\n");
//exit退出当前进程
exit(EXIT_SUCCESS);
//进程已经退出,所以这句话不会打印出来
printf("程序已经结束\n");
return 0;
}
最终的运行结果为
实际写入的数据个数:11
读取到的数据:hello csdn 个数11
程序还未结束