文件IO操作

文件操作

cache:是读的缓冲区,读内容先读到cache中,是读的加速机制

buffer:是写的缓冲区,写内容先写到buff中,是写的加速机制

对一个文件的操作有两种不同的方式,既可以使用由操作系统直接提供的编程接口
(API),即系统调用,也可以使用由标准C库提供的标准IO函数。

系统IO

open

功能 打开一个指定的文件并获得文件描述符,或者创建一个新文件
头文件 <sys/types.h>
<sys/stat.h>
<fcntl.h>
原型 int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数 - pathname:即将要打开的文件路径
- flags
- O_RDONLY:只读方式打开文件
- O_WRONLY:只写方式打开文件
- O_RDWR:读写方式打开文件
- O_CREAT:如果文件不存在,则创建该文件
- O_EXCL:如果使用 O_CREAT 选项且文件存在,则返回错误消息
- O_NOCTTY:如果文件为终端,那么终端不可以作为调用 open() 系统调用的那个进程的控制终端
- O_TRUNC:如文件已经存在,则删除文件中原有数据
- O_APPEND:以追加方式打开文件
- mode:如果文件被新建,指定其权限为 mode(八进制表示法)
返回值 成功:大于等于0的整数(即文件描述符)
失败:-1
备注

** 示例代码**

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
 
 
int main()
{
    int fd;
    
    //以读写的方式创建新文件    
    fd=open("myhello",O_RDWR | O_CREAT,0777);
 
    //以读写的方式创建新文件,并判断是否存在    
    //fd=open("myhello",O_RDWR | O_CREAT | O_EXCL,0777);
 
    //以读写的方式创建新文件,若文件存在则进行截断(清空文件内容)  
    //fd = open("myhello", O_RDWR | O_CREAT | O_TRUNC, 0777);
 
    if (fd < 0)
    {
        printf("can not open file %s\n", argv[1]);
        printf("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("open");
    }
    else
    {
        printf("fd = %d\n", fd);
    }
    close(fd);
    return 0;
}

close

功能 关闭一个打开的文件描述符
头文件 <unistd.h>
原型 int close(int fd);
参数 fd:要关闭的文件描述符
返回值 成功:0
失败:-1
备注 重复关闭一个已经关闭的文件或者尚未打开的文件是安全的

示例代码

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main(int argc, char *argv[])
{
    int fd;
    // 假设 fd 是已经打开的文件描述符
    int ret = close(fd);
    if (ret < 0)
    {
        printf("can not close file descriptor %d\n", fd);
        printf("errno = %d\n", errno);
        printf("err: %s\n", strerror(errno));
        perror("close");
    }
    return 0;
}

read

功能 从指定文件中读取数据
头文件 <unistd.h>
原型 ssize_t read(int fd, void *buf, size_t count);
参数 - fd:从文件 fd 中读取数据
- buf:指向存放读到的数据的缓冲区
- count:想要从文件 fd 中读取的字节数
返回值 成功:实际读到的字节数
失败:-1
备注 实际读到的字节数小于等于 count
  • 如果返回值是0,说明读到文件末尾

write

功能 将数据写入指定的文件
头文件 <unistd.h>
原型 ssize_t write(int fd, const void *buf, size_t count);
参数 - fd:将数据写入到文件 fd
- buf:指向即将要写入的数据
- count:要写入的字节数
返回值 成功:实际写入的字节数
失败:-1
备注 实际写入的字节数小于等于 count

** 示例代码 **

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int main() {
    // 打开文件
    int fd = open("example.txt", O_RDWR | O_CREAT, 0666);
    if (fd < 0) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 写入数据
    const char *message = "Hello, this is a test message.\n";
    ssize_t bytes_written = write(fd, message, strlen(message));
    if (bytes_written < 0) {
        perror("write");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 读取数据
    char buffer[100];
    lseek(fd, 0, SEEK_SET); // 将文件指针移至文件开头
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1);
    if (bytes_read < 0) {
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }
    buffer[bytes_read] = '\0'; // 添加字符串结束符

    // 输出读取的数据
    printf("Data read from file: %s", buffer);

    // 关闭文件
    close(fd);

    return 0;
}

  • 这个程序的作用是打开一个文件,向文件中写入一条消息,然后再读取出来并打印。

dup dup2

功能 复制文件描述符
头文件 <unistd.h>
原型 int dup(int oldfd);
int dup2(int oldfd, int newfd);
参数 - oldfd:要复制的文件描述符
- newfd:指定的新文件描述符
返回值 成功:新的文件描述符
失败:-1
备注

dup是英文单词duplicate的缩写,意味着复制一个已有的文件描述符,dup将会返回一个最小未用的文件描述符作为拷贝;

dup2则可以通过第二个参数来指定描述符,如果这个描述符已经存在,则将会被覆盖。

lseek

功能 调整文件位置偏移量
头文件 <sys/types.h>
<unistd.h>
原型 off_t lseek(int fd, off_t offset, int whence);
参数 - fd:要调整位置偏移量的文件的描述符
- offset:新位置偏移量相对基准点的偏移
- whence:基准点
- SEEK_SET:文件开头处
- SEEK_CUR:当前位置
- SEEK_END:文件末尾处
返回值 成功:新文件位置偏移量
失败:-1
备注 只对普通文件有效,特殊文件是无法调整偏移量的

** 示例代码 **

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    // 打开文件
    int fd = open("example.txt", O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 设置新的位置偏移量
    off_t new_offset = lseek(fd, 0, SEEK_SET);
    if (new_offset == -1) {
        perror("lseek");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    close(fd);

    return 0;
}

ioctl

功能 文件控制
头文件 <sys/ioctl.h>
原型 int ioctl(int fd, int request, ...);
参数 - fd:要控制的文件描述符
- request:针对不同文件的各种控制命令字
- ...:根据不同的命令字而不同
返回值 成功:一般情况下是0,但有些特定的请求将返回非负整数
失败:-1
备注

request是一个由底层驱动提供的命令字, 一些通用的命令字被放置在头文件/usr/include,(不同的系统存放位置也许不同)中,后面的变参也由前面的request命令字决定。

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情况下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

ioctl 函数用于对设备进行控制操作,它是一种通用的设备 I/O 控制接口。通过 ioctl 函数,可以向设备发送控制命令,或者获取设备的状态信息。这些控制命令通常由预定义的常量表示,称为 IOCTL 命令字。

使用 ioctl 函数可以实现很多设备相关的功能,例如:

  • 设置设备参数:如串口的波特率、数据位、停止位等;
  • 控制设备行为:如开启或关闭设备的某些功能;
  • 查询设备状态:如获取设备的错误信息、缓冲区状态等。
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fb.h>


int main(int argc, char const *argv[])
{
	//1.打开LCD
	int lcd_fd = open("/dev/fb0",O_RDWR);


	//2.利用ioctl函数获取LCD硬件参数
	struct fb_var_screeninfo lcd_vinfo;
	ioctl(lcd_fd,FBIOGET_VSCREENINFO,&lcd_vinfo);

	//3.输出LCD的宽、高、色深
	printf("lcd height= %-5d\n", lcd_vinfo.yres); 		//480   
	printf("lcd width= %-5d\n", lcd_vinfo.xres);  		//800
	printf("lcd bpp=%-5d\n",lcd_vinfo.bits_per_pixel);	//32
	
	return 0;
}

  • 通过 ioctl(lcd_fd, FBIOGET_VSCREENINFO, &lcd_vinfo) 调用,将请求发送给 LCD 设备文件描述符 lcd_fd,并将 LCD 设备的硬件参数保存在结构体 lcd_vinfo 中。

fcntl

功能 文件控制
头文件 <unistd.h>
<fcntl.h>
原型 int fcntl(int fd, int cmd, .../*arg*/);
参数 - fd:要控制的文件描述符
- cmd:控制命令字
- .../*arg*/:根据不同的命令字而不同
返回值 成功:根据不同的 cmd,返回值不同
失败:-1
备注

fcntl 函数用于对文件描述符进行各种控制操作,例如设置文件状态标志、获取文件状态标志、复制文件描述符等。其功能包括但不限于:

  • 修改已打开文件的属性,比如设置文件的状态标志(例如设置非阻塞模式)、文件描述符的标志、文件的读写锁等。
  • 复制文件描述符,使两个描述符指向相同的文件。
  • 获取已打开文件的各种属性信息,如获取文件状态标志、获取文件的读写锁信息等。

这些操作可以通过 cmd 参数来指定。cmd 参数决定了 fcntl 函数的具体行为,例如 F_GETFL 用于获取文件状态标志,F_SETFL 用于设置文件状态标志,F_DUPFD 用于复制文件描述符等。

总的来说,fcntl 函数是一个灵活的接口,用于实现对文件描述符的各种控制操作。

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    // 打开文件
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 获取当前文件状态标志
    int flags = fcntl(fd, F_GETFL);
    if (flags == -1) {
        perror("fcntl");
        return 1;
    }

    // 设置文件状态标志为非阻塞模式
    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
        perror("fcntl");
        return 1;
    }

    // 演示读取文件,此时设置了非阻塞模式
    char buffer[1024];
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
    if (bytes_read == -1) {
        perror("read");
    } else {
        printf("Read %zd bytes: %s\n", bytes_read, buffer);
    }

    // 关闭文件
    close(fd);

    return 0;
}

在这个示例中,我们首先使用 open 函数打开一个文件,然后使用 fcntl 函数获取文件的状态标志,接着设置文件的状态标志为非阻塞模式,并最后读取文件。

mmap

功能 将文件或其他对象映射到内存
头文件 <sys/mman.h>
原型 void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数 - addr:指定映射区域的首地址,通常设为 NULL,让内核选择适当的地址
- length:映射区域的长度
- prot:保护映射区域的权限
- PROT_READ:允许读取映射区域的内容
- PROT_WRITE:允许写入映射区域的内容
- PROT_EXEC:允许执行映射区域的内容
- PROT_NONE:拒绝对映射区域的访问
- flags:控制映射区域的属性
- MAP_SHARED:共享映射,对映射区域的修改会影响到文件或其他映射该文件的进程
- MAP_PRIVATE:私有映射,对映射区域的修改不会影响到文件或其他映射该文件的进程
- MAP_ANONYMOUS:创建匿名映射,不与文件关联,常用于创建匿名内存
- fd:要映射的文件描述符,如果映射的是匿名内存,则为 -1
- offset:映射文件的偏移量,通常设为 0
返回值 成功:映射区域的起始地址
失败:MAP_FAILED--->(void*)-1
备注 - 如果映射的是文件,则 fd 参数指定文件描述符,offset 参数指定文件的偏移量;如果映射的是匿名内存,则 fd 参数为 -1offset 参数无效。
- 映射区域的长度必须是页面大小的整数倍,通常使用 sysconf(_SC_PAGE_SIZE) 获取页面大小。
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    // 打开文件
    int fd = open("example.txt", O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 获取文件大小
    struct stat file_stat;
    if (fstat(fd, &file_stat) == -1) {
        perror("fstat");
        close(fd);
        return 1;
    }
    off_t file_size = file_stat.st_size;

    // 映射文件到内存
    void *addr = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (addr == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return 1;
    }

    // 读取映射区域的内容
    printf("Mapped content: %s\n", (char *)addr);

    // 解除内存映射
    if (munmap(addr, file_size) == -1) {
        perror("munmap");
        close(fd);
        return 1;
    }

    // 关闭文件
    close(fd);

    return 0;
}

标准IO

fopen

函数 fopen
功能 打开一个文件
头文件 <stdio.h>
原型 FILE *fopen(const char *path, const char *mode);
参数 - path:即将要打开的文件路径名
- mode
- "r": 以只读方式打开文件,要求文件必须存在。
- "r+": 以读写方式打开文件,要求文件必须存在。
- "w": 以只写方式打开文件,文件如果不存在将会创建新文件,如果存在将会将其内容清空。
- "w+": 以读写方式打开文件,文件如果不存在将会创建新文件,如果存在将会将其内容清空。
- "a": 以只写方式打开文件,文件如果不存在将会创建新文件,且文件位置偏移量被自动定位到文件末尾(即以追加方式写数据)。
- "a+": 以读写方式打开文件,文件如果不存在将会创建新文件,且文件位置偏移量被自动定位到文件末尾(即以追加方式写数据)。
返回值 成功:FILE * ----> 文件指针
失败:NULL
备注 最好要以二进制文件打开rb``wb
使用标准IO的时候,是不可以反复关闭相同的文件,因为释放已经被释放的堆内存,会导致段错误!

思考:fopen函数的返回值是一个指向被打开文件的FILE类型的指针,请问FILE类型是什么?

回答:FILE类型其实是一个结构体数据类型,它包含了标准 I/O 库函数为管理文件所需要的所有信息,比如包括用于实际I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等。头文件stdio.h中有关于FILE类型的相关描述,

fclose

函数 fclose
功能 关闭一个文件
头文件 <stdio.h>
原型 int fclose(FILE *stream);
参数 stream:要关闭的文件流指针
返回值 成功:0
失败:非0
备注 不能对同一个文件重复关闭

image-20240514230440287

  • 使用标准IO函数处理文件的最大特点是,数据将会先存储在一个标准IO 缓冲区中,而后在一定条件下才被一并flush(冲洗,或称刷新)至内核缓冲区,而不是像系统IO那样,数据直接被flush至内核。 注意到,标准IO函数fopen()实质上是系统IO函数open()的封装,他们是一一对应的,每一次fopen()都会导致系统分配一个file{ }结构体和一个FILE{}来保存维护该文件的读写信息,每一次的打开和操作都可以不一样,是相对独立的,因此可以在多线程或者多进程中多次打开同一个文件,再利用文件空洞技术进行多点读写。

  • 默认打开的三个标准文件,标准输出入输出设备在系统IO是默认被打开的,在标准IO也是一样,在程序开始就已经拥有相应的文件指针

设备 文件描述符(int) 文件指针(FILE *) 标准输入设备(键盘)
标准输入设备 0 STDIN_FILENO stdin
标准输出设备 1 STDOUT_FILENO stdout
标准出错设备 2 STDERR_FILENO stderr

每次一个字符的读写标准IO函数接口

fgetc getc getchar

功能 获取指定文件的一个字符
头文件 <stdio.h>
原型 int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
参数 stream:文件指针
返回值 成功读取到的字符
失败:EOF
备注 当返回EOF时,文件stream可能已达末尾,或者遇到错误

fputc putc putchar

功能 将一个字符写入一个指定的文件
头文件 <stdio.h>
原型 int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
参数 c:要写入的字符
stream:写入的文件指针
返回值 成功写入到的字符
失败:EOF
备注

1,fgec()、getc()和getchar()返回值是int,而不是char,原因是因为他们在出错或者读到文件末尾的时候需要返回一个值为-1的EOF标记,而char型数据有可能因 为系统的差异而无法表示负整数。

2,当fgec()、getc()和getchar()返回EOF时,有可能是发生了错误,也有可能是读到了文件末尾,需要用 feof(),ferror()来判断。

feof ferror

功能 判断一个文件是否到达文件末尾
头文件 <stdio.h>
原型 int feof(FILE *stream);
int ferror(FILE *stream);
参数 stream:进行判断的文件指针
返回值 feof:如果文件已达末尾则返回真,否则返回假
ferror:如果文件遇到错误则返回真,否则返回假
备注

每次一行字符的读写标准IO函数接口

fgets gets

功能 从指定文件读取最多一行数据
头文件 <stdio.h>
原型 char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
参数 s:自定义缓冲区指针
size:自定义缓冲区大小
stream:即将被读取数据的文件指针
返回值 成功:自定义缓冲区指针 s
失败:NULL
备注 1. gets() 缺省从文件 stdin 读入数据
2. 当返回 NULL 时,文件 stream 可能已达末尾,或者遇到错误

fputs puts

功能 将数据写入指定的文件
头文件 <stdio.h>
原型 int fputs(const char *s, FILE *stream);
int puts(const char *s);
参数 s:自定义缓冲区指针
stream:即将被写入数据的文件指针
返回值 成功:非负整数
失败:EOF
备注 puts() 缺省将数据写入文件 stdout
  1. fgets()fgetc()一样,当其返回NULL时并不能确定究竟是达到文件末尾还是 碰到错误,需要用feof()/ferror()来进一步判断。
  2. fgets()每次读取至多不超过size个字节的一行,所谓“一行”即数据至多包含一 个换行符’\n’。
  3. gets()是一个已经过时的接口,因为他没有指定自定义缓冲区s的大小,这样很容 易造成缓冲区溢出,导致程序段访问错误。
  4. fgets()fputs()gets()puts()一般成对使用,鉴于gets()的不安全性, 一般建议使用前者。

** 每次读写若干数据块的标准IO函数接口 **

fread fwrite

功能 从指定文件读取若干个数据块
头文件 <stdio.h>
原型 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数 ptr:自定义缓冲区指针
size:数据块大小
nmemb:数据块个数
stream:即将被读取数据的文件指针
返回值 成功:读取的数据块个数,等于 nmemb
失败:读取的数据块个数,小于 nmemb 或等于 0
备注 当返回小于 nmemb 时,文件 stream 可能已达末尾,或者遇到错误
功能 将若干块数据写入指定的文件
头文件 <stdio.h>
原型 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数 ptr:自定义缓冲区指针
size:数据块大小
nmemb:数据块个数
stream:即将被写入数据的文件指针
返回值 成功:写入的数据块个数,等于 nmemb
失败:写入的数据块个数,小于 nmemb 或等于 0
备注
  1. 如果fread()返回值小于nmemb时,则可能已达末尾,或者遇到错误,需要借于feof()/ferror()来加以进一步判断。
  2. 当发生上述第1种情况时,其返回值并不能真正反映其读取或者写入的数据块数, 而只是一个所谓的“截短值”,比如正常读取5个数据块,每个数据块100个字节,在执 行成功的情况下返回值是5,表示读到5个数据块总共500个字节,但是如果只读到499 个数据块,那么返回值就变成4,而如果读到99个字节,那么fread()会返回0。因此当 发生返回值小于nmemb时,需要仔细确定究竟读取了几个字节,而不能直接从返回值确定。

** 获取或设置文件当前位置偏移量 **

fseek ftell rewind

功能 设置指定文件的当前位置偏移量
头文件 <stdio.h>
原型 int fseek(FILE *stream, long offset, int whence);
参数 stream:需要设置位置偏移量的文件指针
offset:新位置偏移量相对基准点的偏移
whence:基准点
- SEEK_SET:文件开头处
- SEEK_CUR:当前位置
- SEEK_END:文件末尾处
返回值 成功:0
失败:-1
备注
功能 获取指定文件的当前位置偏移量
头文件 <stdio.h>
原型 long ftell(FILE *stream);
参数 stream:需要返回当前文件位置偏移量的文件指针
返回值 成功:当前文件位置偏移量
失败:-1
备注
功能 将指定文件的当前位置偏移量设置到文件开头处
头文件 <stdio.h>
原型 void rewind(FILE *stream);
参数 stream:需要设置位置偏移量的文件指针
返回值
备注 该函数的功能是将文件 stream 的位置偏移量置位到文件开头处

1,fseek()的用法基本上跟系统IO的lseek()是一致的。

2,rewind(fp)相等于fseek(fp,0L,SEEK_SE);

** 标准格式化IO函数 **

fprintf printf snprintf sprintf

功能 将格式化数据写入指定的文件或者内存
头文件 <stdio.h>
原型 int fprintf(FILE *restrict stream, const char *restrict format, ...);
int printf(const char *restrict format, ...);
int snprintf(char *restrict s, size_t n, const char *restrict format, ...);
int sprintf(char *restrict s, const char *restrict format, ...);
参数 stream:写入数据的文件指针
format:格式控制串
s:写入数据的自定义缓冲区
n:自定义缓冲区的大小
返回值 成功:成功写入的字节数
失败:-1
备注

fscanf scanf sscanf

功能 从指定的文件或者内存中读取格式化数据
头文件 <stdio.h>
原型 int fscanf(FILE *restrict stream, const char *restrict format, ...);
int scanf(const char *restrict format, ...);
int sscanf(const char *restrict s, const char *restrict format, ...);
参数 stream:读出数据的文件指针
format:格式控制串
s:读出数据的自定义缓冲区
返回值 成功:正确匹配且赋值的数据个数
失败:EOF
备注

1,fprintf()不仅可以像printf()一样向标准输出设备输出信息,也可以向由stream
指定的任何有相应权限的文件写入数据。
2,sprintf()snprintf()都是向一块自定义缓冲区写入数据,不同的是后者第二个参
数提供了这块缓冲区的大小,避免缓冲区溢出,因此应尽量使用后者,放弃使用前者。
3,fscanf()不仅可以像scanf()一样从标准输入设备读入信息,也可以从由stream
指定的任何有相应权限的文件读入数据。
4,sscanf()从一块由s指定的自定义缓冲区中读入数据。
5,最重要的一条:这些函数的读写都是带格式的,这些所谓的格式由下表规定:

格式控制符

格式控制符 含义 范例
%d 有符号十进制整型数 int a = 1; printf("%d", a);
%u 无符号十进制整型数 int a = 1; printf("%u", a);
%o 无符号八进制整型数 int a = 1; printf("%o", a);
%x 无符号十六进制整型数 int a = 1; printf("%x", a);
%c 字符 char a = 'd'; printf("%c", a);
%s 字符串 char *a = "xy"; printf("%s", a);
%f 计数法单精度浮点数 float a = 1.0; printf("%f", a);
%e 科学技术法单精度浮点数 float a = 1.0; printf("%e", a);
%p 指针 int *a; printf("%p", a);
%.5s 取字符串的前5个字符 char *a = "abcdefghijk"; printf("%.5s", a);
%.5f 取单精度浮点数小数点后5位小数 float a = 1.0; printf("%.5f", a);
%5d 位宽至少为5个字符,右对齐 int a = 1; printf("%5d", a);
%-5d 位宽至少为5个字符,左对齐 int a = 1; printf("%-5d", a);
%hd 半个有符号数十进制整型数 short a = 1; printf("%hd", a);
%hhd 半半个有符号数十进制整型数 char a = 1; printf("%hhd", a);
%lf 双精度浮点数 double a = 1.0; printf("%lf", a);
%Le 科学技术法双精度浮点数 double a = 1.0; printf("%Le", a);
%Lf 长双精度浮点数 long double a = 1.0; printf("%Lf", a);
%Le 科学技术法长双精度浮点数 long double a = 1.0; printf("%Le", a);

** 示例代码 **

  • 注意,这一组函数跟之前的标准IO最大的区别是带有格式控制,因此最适用于有格式 的文件处理,假设有一个文件存储了班级学生的姓名、性别、年龄和身高,如下:

image-20240515000814675

很明显这个文件是带有格式的,假如我们需要将这个文件读入程序,再将之打印到屏幕
显示出来,可以使用fscanf()fprintf()来实现:

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<unistd.h>
#include<string.h>
#include<strings.h>
#include<errno.h>
#include<sys/stat.h>
#include<sys/types.h>

#define NAMELEN 20

// 学生结构体
struct student {
    char name[NAMELEN];
    char sex;
    int age;
    float stature;
    struct student *next; // 用以形成链表
};

// 初始化链表
struct student *init_list(void) {
    struct student *head = malloc(sizeof(struct student));
    head->next = NULL;
    return head;
}

// 向链表中添加学生节点
void add_student(struct student *head, struct student *new) {
    struct student *tmp = head;
    while (tmp->next != NULL)
        tmp = tmp->next;
    tmp->next = new;
}

// 显示链表中的所有学生信息
void show_student(struct student *head) {
    struct student *tmp = head->next;
    while (tmp != NULL) {
        fprintf(stdout, "%-5s %c %d %.1f\n",
                tmp->name, tmp->sex, tmp->age, tmp->stature);
        tmp = tmp->next;
    }
}

int main(int argc, char **argv) {
    // 打开文件
    FILE *fp = fopen("format_data", "r");
    if (fp == NULL) {
        perror("Error opening file");
        return errno;
    }
    
    // 初始化链表
    struct student *head = init_list();
    int count = 0;
    // 从文件中读取数据并添加到链表中
    while (1) {
        struct student *new = malloc(sizeof(struct student));
        if (fscanf(fp, "%s %c %d %f", new->name, &(new->sex), &(new->age), &(new->stature)) == EOF) {
            break;
        }
        add_student(head, new);
        count++;
    }
    // 输出添加的学生数
    printf("%d students have been added.\n", count);
    // 显示所有学生信息
    show_student(head);
    // 关闭文件
    fclose(fp);
    return 0;
}

** 文件属性 **

--- 待补齐

posted @   sanjiudemiao  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示