标准IO与文件IO的区别 IO概念 1> IO:顾名思义就是输入输出,程序与外部设备进行信息交换的过程称为IO操作
2> 最先接触的IO:#include<stdio.h>标准的缓冲 输入输出头文件
IO的分类 1> 标准IO :使用系统提供的库函数实现
2> 文件IO :基于系统调用完成,每进行一次系统调用,进程会从用户空间 向内核空间 进行一次切换,当用户空间 与内核空间 进行切换时,进程就会进入一次挂起状态,从而导致进程执行效率低
3> 文件IO与标准IO的区别 :标准IO相比于文件IO而言,提供了缓冲区 ,用户可以将数据先放入缓冲区中,等到缓冲区时机到了后,统一进行一次系统调用,将数据刷入内核空间
标准IO 实现原理
FILE结构体 1> FILE结构体是系统提供的用于描述一个文件全部信息的结构体
2> FILE结构体的原型
1 2 3 4 5 6 struct FILE { char * _IO_buf_base; char * _IO_buf_end; int _fileno; };
3> 特殊的FILE指针,这三个指针,全部都是针对于终端文件而言 的,当程序启动后,系统默认打开的三个特殊文件指针
stderr: 标准出错指针
stdin: 标准输入指针
stdout: 标准输出指针
打开文件:fopen 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 FILE *fopen(const char *path, const char *mode); 功能:打开一个文件,并返回当前文件的文件指针 参数1:表示文件路径,是一个字符串 参数2:打开文件的方式,也是一个字符串,字符串必须以以下的字符开头 r 以只读的形式打开文件,文件光标定位在开头部分 r+ 以读写的形式打开文件,文件光标定位在开头部分 w 以只写的形式打开文件,如果文件不存在,就创建文件,如果文件存在,就清空,光标定位在开头 w+ 以读写的形式打开文件,如果文件不存在,就创建文件,如果文件存在,就清空,光标定位在开头 a 以追加(结尾写)的形式打开文件,如果文件不存在就创建文件,光标定位在文件末尾 a+ 以读写的形式打开文件,如果文件不存在就创建文件,如果文件存在,读取操作光标定位在开头,写操作光标定位在结尾 r Open text file for reading. The stream is positioned at the beginning of the file. r+ Open for reading and writing. The stream is positioned at the beginning of the file. w Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file. w+ Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file. a Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file. a+ Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file. 返回值:成功返回打开的文件指针,失败返回NULL并置位错误码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include "stdio.h" int main (int argc, const char *argv[]) { FILE *fp = NULL ; fp = fopen ("./file.txt" , "w" ); if (fp == NULL ) { printf ("fopen error\n" ); return -1 ; } printf ("fopen success\n" ); fclose (fp); return 0 ; }
关闭文件:fclose 1 2 3 4 5 #include <stdio.h> int fclose(FILE *fp); 功能:关闭指定的文件 参数:要关闭的文件指针(由fopen返回的结果) 返回值:成功执行返回0,失败返回EOF并置位错误码
错误码 1> 概念 :当内核提供的函数出错后,内核空间会向用户空间反馈一个错误信息 ,由于错误信息比较多也比较复杂,系统就给每种不同的错误信息起了一个编号,用一个整数表示,这个整数就是错误码
2> 错误码都是大于或等于0的数字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #define EPERM 1 操作受限 #define ENOENT 2 没有当前文件或目录 #define ESRCH 3 没有进程 #define EINTR 4 中断系统调用 #define EIO 5 #define ENXIO 6 #define E2BIG 7 #define ENOEXEC 8 #define EBADF 9 #define ECHILD 10 #define EAGAIN 11 #define ENOMEM 12 #define EACCES 13 #define EFAULT 14 #define ENOTBLK 15 #define EBUSY 16 #define EEXIST 17 #define EXDEV 18 #define ENODEV 19 #define ENOTDIR 20 #define EISDIR 21 #define EINVAL 22 #define ENFILE 23 #define EMFILE 24 #define ENOTTY 25 #define ETXTBSY 26 #define EFBIG 27 #define ENOSPC 28 #define ESPIPE 29
3> 关于错误码的处理函数(strerror、perror )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <errno.h> int errno; #include <string.h> char *strerror (int errnum) ; 功能:将错误码转换为错误信息描述 参数:错误码 返回值:错误信息字符串描述 #include <stdio.h> void perror (const char *s) ; 功能:输出当前错误码对应的错误信息 参数:提示符号,会原样打印出来,并且会在提示数据后面加上冒号,并输出完后,自动换行 返回值:无
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include "stdio.h" #include <errno.h> #include <string.h> int main (int argc, const char *argv[]) { FILE *fp = NULL ; fp = fopen ("./file.txt" , "r" ); if (fp == NULL ) { perror ("fopen error" ); return -1 ; } printf ("fopen success\n" ); fclose (fp); return 0 ; }
单字符读写:fputc/fgetc 原型:
1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> int fgetc (FILE *stream) ;功能:从指定文件中读取一个字符数据,并以无符号整数的形式返回 参数:文件指针 返回值:成功返回读取的字符对应的无符号整数,失败返回EOF并置位错误码 #include <stdio.h> int fputc (int c, FILE *stream) ;功能:将指定的字符c写入到stream指向的文件中 参数1 :要写入的字符对应的无符号整数 参数2 :文件指针 返回值:成功返回写入字符对的无符号整数,失败返回EOF并置位错误码
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./file.txt" , "w" )) == NULL ) { perror ("fopen error" ); return -1 ; } fputc ('H' , fp); fputc ('e' , fp); fputc ('l' , fp); fputc ('l' , fp); fputc ('o' , fp); fclose (fp); if ((fp = fopen ("./file.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } char ch = 0 ; while (1 ) { ch = fgetc (fp); if (ch == EOF) { break ; } printf ("%c " , ch); } fclose (fp); return 0 ; }
拷贝文件案例 使用fgetc和fputc完成两个文件的拷贝工作,实现指令cp的功能: cp srcfile destfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <stdio.h> int main (int argc, const char *argv[]) { if (argc != 3 ) { printf ("input file error\n" ); printf ("usage:./a.out srcfile destfile\n" ); return -1 ; } FILE *srcfp = NULL ; FILE *destfp = NULL ; if ((srcfp = fopen (argv[1 ], "r" )) ==NULL ) { perror ("srcfile open error" ); return -1 ; } if ((destfp = fopen (argv[2 ], "w" )) ==NULL ) { perror ("destfile open error" ); return -1 ; } char ch = 0 ; while (1 ) { ch = fgetc (srcfp); if (ch == EOF) { break ; } fputc (ch, destfp); } fclose (srcfp); fclose (destfp); printf ("拷贝成功\n" ); return 0 ; }
字符串读写:fgets/fputs 原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 int fputs (const char *s, FILE *stream) ;功能:将指定的字符串,写入到指定的文件中 参数1 :要被写入的字符串 参数2 :文件指针 返回值:成功返回本次写入字符的个数,失败返回EOF char *fgets (char *s, int size, FILE *stream) ;功能:从stream指向的文件中最多读取size-1 个字符到s容器中,遇到回车或文件结束,会结束一次读取,并且会将回车放入容器,最后自动加上一个字符串结束标识'\0' (之所以读取size-1 是因为要留位置给结束符) 参数1 :字符数组容器起始地址 参数2 :要读取的字符个数,最多读取size-1 个字符 参数3 :文件指针 返回值:成功返回容器s的起始地址,失败返回NULL
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> #include <iostream> #include <string.h> int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./file.txt" , "w" )) == NULL ) { perror ("fopen eror" ); return -1 ; } char wbuf[128 ] = "" ; while (1 ) { std::cin >> wbuf; fputs (wbuf, fp); fputc ('\n' , fp); if (strcmp (wbuf, "quit" ) == 0 ) { break ; } } fclose (fp); if ((fp = fopen ("./file.txt" , "r" )) == NULL ) { perror ("fopen eror" ); return -1 ; } char rbuf[128 ] = "" ; while (1 ) { char *res = fgets (rbuf, sizeof (rbuf), fp); if (res == NULL ) { break ; } printf ("rbuf = %s\n" , rbuf); } fclose (fp); return 0 ; }
拷贝文件案例 使用fgets和fputs完成两个文件的拷贝
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <stdio.h> #include <string.h> using namespace std;int main (int argc, const char *argv[]) { if (argc != 3 ) { printf ("inut file error\n" ); printf ("usage:./a.out srcfile destfile\n" ); return -1 ; } FILE *srcfp = NULL ; FILE *destfp = NULL ; if ((srcfp = fopen (argv[1 ], "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } if ((destfp = fopen (argv[2 ], "w" )) == NULL ) { perror ("fopen error" ); return -1 ; } char buf[128 ] = "" ; while (1 ) { char *ptr = fgets (buf, sizeof (buf), srcfp); if (ptr == NULL ) { break ; } fputs (buf, destfp); } fclose (srcfp); fclose (destfp); printf ("拷贝成功\n" ); return 0 ; }
标准IO的缓冲区问题 缓冲区分类及大小
行缓存 :和终端文件 相关的缓冲区叫做行缓存,行缓冲区的大小为1024字节 ,对应的文件指针:stdin、stdout
全缓存 :和外界文件 相关的缓冲区叫做全缓存,全缓冲区的大小为4096字节 ,对应的文件指针:fp
不缓存 :和标准出错 相关的缓冲区叫不缓存,不缓存的大小为0字节 ,对应的文件指针:stderr
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { printf ("行缓存的大小为:%d\n" , stdout->_IO_buf_end - stdout->_IO_buf_base); printf ("行缓存的大小为:%d\n" , stdout->_IO_buf_end - stdout->_IO_buf_base); printf ("行缓存的大小为:%d\n" , stdin->_IO_buf_end - stdin->_IO_buf_base); int num = 0 ; cin >> num; printf ("行缓存的大小为:%d\n" , stdin->_IO_buf_end - stdin->_IO_buf_base); FILE *fp = NULL ; if ((fp = fopen ("./aa.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } printf ("全缓存的大小为:%d\n" , fp->_IO_buf_end - fp->_IO_buf_base); fgetc (fp); printf ("全缓存的大小为:%d\n" , fp->_IO_buf_end - fp->_IO_buf_base); fclose (fp); printf ("不缓存的大小为:%d\n" , stderr->_IO_buf_end - stderr->_IO_buf_base); perror ("error" ); printf ("不缓存的大小为:%d\n" , stderr->_IO_buf_end - stderr->_IO_buf_base); return 0 ; }
结果图:
缓冲区刷新时机/fflush函数 缓冲区刷新函数:fflush
原型:
1 2 3 4 5 #include <stdio.h> int fflush (FILE *stream) ;功能:刷新给定的文件指针对应的缓冲区 参数:文件指针 返回值:成功返回0 ,失败返回EOF并置位错误码
行缓存刷新时机
触发刷新(输出)的条件
具体说明
遇到换行符 \n
最核心规则:只要输出内容里有 \n,不管缓冲区有没有满,都会立即把缓冲区内容刷到屏幕 / 终端。
缓冲区被填满
备用规则:如果一直不输出 \n,当写入的内容达到缓冲区容量(通常为 1024 字节)时,缓冲区会自动刷新。
主动调用刷新函数
手动控制:调用 fflush(stdout) 可以强制刷新,不管有没有 \n、缓冲区满没满。
程序正常结束
程序执行到 return 0 / exit() 时,会自动刷新所有缓冲区,确保数据全部输出。
输入输出切换时
当从输出(如 printf)切换到输入(如 scanf、getchar)时,为了让用户看到提示,行缓冲区会自动刷新。
关闭行缓存对应的文件指针
调用 fclose(stdout) 或程序结束时,会先刷新缓冲区,再关闭文件指针,防止数据丢失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { for (int i=0 ; i<1025 ; i++) { printf ("A" ); } while (1 ); return 0 ; }
全缓存刷新时机
触发刷新(输出)的条件
具体说明
缓冲区被填满
当写入的数据量达到缓冲区容量(通常为 4096 字节)后,再写入数据时,缓冲区会自动刷新。
主动调用刷新函数
调用 fflush(fp) 可以强制刷新缓冲区,与缓冲区是否已满无关。
程序正常结束
程序执行到 return 0 / exit() 时,会自动刷新所有缓冲区,确保数据全部写入文件。
输入输出切换时
当对同一个文件指针进行读写切换时(如先 fputs 输出,后 fgetc 输入),会自动刷新缓冲区。
关闭文件指针时
调用 fclose(fp) 关闭文件时,会先刷新缓冲区,再关闭文件指针,防止数据丢失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./aa.txt" , "r+" )) == NULL ) { perror ("fopen error" ); return -1 ; } for (int i=0 ; i<4097 ; i++) { fputc ('A' , fp); } while (1 ); return 0 ; }
全缓冲与行缓冲对比
特性
全缓冲(Full Buffer)
行缓冲(Line Buffer)
适用场景
磁盘文件 IO(fopen)
终端 IO(stdout/stdin)
对换行符 \n 的响应
完全忽略,不会触发刷新
核心触发条件,遇到 \n 立即刷新
输入输出切换
对同一文件指针读写切换时,会刷新
从输出切换到输入时,会刷新
关闭文件指针
会刷新
主要用于终端,一般不涉及关闭文件指针
主动刷新
fflush(fp)
fflush(stdout)
程序结束
自动刷新
自动刷新
缓冲区满
自动刷新
自动刷新(备用规则)
不缓存直接刷新 对于不缓存的刷新时机,只要放入数据,立马进行刷新
1 2 3 4 5 6 7 8 9 10 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { fputs ("A" , stderr); while (1 ); return 0 ; }
格式化读写:fprintf/fscanf 原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 int fprintf (FILE *stream, const char *format, ...) ;功能:向指定的文件中输出一个格式串 参数1 :文件指针 参数2 :格式串,可以包含格式控制符,%d(整数)、%s (字符串)、%f (小数)、%lf (小数) 参数3 :可变参数,输出项列表,参数个数由参数2 中的格式控制符的个数决定 返回值:成功返回输出的字符个数,失败返回一个负数 int fscanf (FILE *stream, const char *format, ...) ;功能:从指定的文件中以指定的格式读取数据,放入程序中 参数1 :文件指针 参数2 :格式串,可以包含格式控制符,%d(整数)、%s (字符串)、%f (小数)、%lf (小数) 参数3 :可变参数,输入项地址列表,参数个数由参数2 中的格式控制符的个数决定 返回值:成功返回读入的项数,失败返回EOF并置位错误码
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { fprintf (stdout, "%d %lf %s\n" , 520 , 3.14 , "I Love Xingqiu" ); int num = 0 ; fscanf (stdin, "%d" , &num); printf ("num = %d\n" , num); FILE *fp = NULL ; if ((fp = fopen ("./usr.txt" , "w" )) == NULL ) { perror ("fopen error" ); return -1 ; } fprintf (fp, "%s %d" , "admin" , 123456 ); fclose (fp); if ((fp = fopen ("./usr.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } char usrName[20 ] = "" ; int pwd = 0 ; fscanf (fp, "%s %d" , usrName, &pwd); 个整数放入程序中的 fclose (fp); printf ("usrName = %s, pwd = %d\n" , usrName, pwd); return 0 ; }
案例:
使用fprintf和fscanf完成注册和登录功能,要求做个小菜单,三个功能,功能1是注册,用户输入账号和密码后,将其存放到外部文件中;功能2是登录,用户输入登录账号和密码后,如果跟文件中匹配,则提示登陆成功,如果全部不匹配,则提示登录失败;功能0,表示退出。菜单可以循环调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 #include <iostream> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace std;int do_register () { char reg_name[20 ] = "" ; char reg_pwd[20 ] = "" ; printf ("请输入注册账户:" ); fgets (reg_name, sizeof (reg_name), stdin); reg_name[strlen (reg_name)-1 ] = '\0' ; printf ("请输入注册密码:" ); fgets (reg_pwd, sizeof (reg_pwd), stdin); reg_pwd[strlen (reg_pwd)-1 ] = '\0' ; FILE *wfp = NULL ; if ((wfp = fopen ("./usr.txt" , "a" )) == NULL ) { perror ("fopen error" ); return -1 ; } fprintf (wfp, "%s %s\n" , reg_name, reg_pwd); fclose (wfp); printf ("注册成功\n" ); return 0 ; } int do_login () { char log_name[20 ] = "" ; char log_pwd[20 ] = "" ; char name[20 ] = "" ; char pwd[20 ] = "" ; printf ("请输入登录账户:" ); fgets (log_name, sizeof (log_name), stdin); log_name[strlen (log_name)-1 ] = '\0' ; printf ("请输入登录密码:" ); fgets (log_pwd, sizeof (log_pwd), stdin); log_pwd[strlen (log_pwd)-1 ] = '\0' ; FILE *rfp = NULL ; if ((rfp = fopen ("./usr.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } while (1 ) { int res = fscanf (rfp, "%s %s" , name, pwd); if (res == EOF) { break ; } if (strcmp (name, log_name)==0 && strcmp (pwd, log_pwd)==0 ) { printf ("登录成功\n" ); fclose (rfp); return 1 ; } } fclose (rfp); printf ("登录失败, 请重新登录\n" ); return 0 ; } int main (int argc, const char *argv[]) { int menu = 0 ; while (1 ) { system ("clear" ); printf ("\t\t======XXX 登录界面=========\n" ); printf ("\t\t======1、注册==========\n" ); printf ("\t\t======2、登录==========\n" ); printf ("\t\t======0、退出==========\n" ); printf ("请输入功能选项:" ); scanf ("%d" , &menu); getchar (); switch (menu) { case 1 : { do_register (); } break ; case 2 : { int res = do_login (); if (res == 1 ) { printf ("登录成功后的界面\n" ); } } break ; case 0 :exit (EXIT_SUCCESS); default :printf ("您输入的功能有误,请重新输入!!!!!\n" ); } printf ("请输入任意键按回车清屏\n" ); while (getchar () != '\n' ); } return 0 ; }
格式串转字符串:sprintf/snprintf printf是向终端打印一个格式串,fprintf是向外部文件中打印一个格式串。有时候,想要将多个不同数据类型的数据,组成一个字符串放入字符数组中,此时就可以使用sprintf或snprintf
原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int sprintf (char *str, const char *format, ...) ;功能:将指定的格式串转换为字符串,放入字符数组中 参数1 :字符数组的起始地址 参数2 :格式串,可以包含多个格式控制符 参数3 :可变参数 返回值:成功返回转换的字符个数,失败返回EOF 对于上述函数而言,用一个小的容器去存储一个大的转换后的字符时,会出现指针越界的段错误,为了安全起见,引入了snprintf int snprintf (char *str, size_t size, const char *format, ...) ;功能:将格式串中最多size-1 个字符转换为字符串,存放到字符数组中 参数1 :字符数组的起始地址 参数2 :要转换的字符个数,最多为size-1 参数3 :格式串,可以包含多个格式控制符 参数4 :可变参数 返回值:如果转换的字符小于size时,返回值就是成功转换字符的个数,如果大于size则只转换 size-1 个字符,返回值就是size,失败返回EOF
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { char buf[10 ] = "" ; snprintf (buf, sizeof (buf), "%d %s %lf" , 1001 , "zhangsanfeng" , 99.5 ); printf ("buf = %s\n" , buf); return 0 ; }
模块化读写:fread/fwrite 原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> size_t fread (void *ptr, size_t size, size_t nmemb, FILE *stream) ;功能:从stream指向的文件中,读取nmemb项数据,每一项的大小为size,将整个结果放入ptr指 向的容器中 参数1 :容器指针,是一个void *类型,表示可以存储任意类型的数据 参数2 :要读取数据每一项的大小 参数3 :要读取数据的项数 参数4 :文件指针 返回值:成功返回nmemb,就是成功读取的项数,失败返回小于项数的值,或者是0 size_t fwrite (const void *ptr, size_t size, size_t nmemb, FILE *stream) ;功能:向stream指向的文件中写入nmemb项数据,每一项的大小为size,数据的起始地址为ptr 参数1 :要写入数据的起始地址 参数2 :每一项的大小 参数3 :要写入的总项数 参数4 :文件指针 返回值:成功返回写入的项数,失败返回小于项数的值,或者是0
字符串的读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <iostream> #include <stdio.h> #include <string.h> using namespace std;int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./test.txt" , "w" )) == NULL ) { perror ("fopen error" ); return -1 ; } char wbuf[128 ] = "" ; while (1 ) { printf ("请输入>>>" ); fgets (wbuf, sizeof (wbuf), stdin); if (strcmp (wbuf, "quit\n" ) == 0 ) { break ; } fwrite (wbuf, strlen (wbuf), 1 , fp); fflush (fp); printf ("录入成功\n" ); } fclose (fp); if ((fp = fopen ("./test.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } char rbuf[10 ] = "" ; int res = fread (rbuf, 1 , sizeof (rbuf), fp); fwrite (rbuf, 1 , sizeof (rbuf), stdout); fclose (fp); return 0 ; }
整数的读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <iostream> #include <stdio.h> #include <string.h> using namespace std;int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./test.txt" , "w" )) == NULL ) { perror ("fopen error" ); return -1 ; } int num = 16 ; fwrite (&num, sizeof (num), 1 , fp); fclose (fp); if ((fp = fopen ("./test.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } int key = 0 ; fread (&key, sizeof (key), 1 , fp); fclose (fp); printf ("key = %d\n" , key); return 0 ; }
结构体数据的读写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <iostream> #include <stdio.h> #include <string.h> using namespace std;class Stu { public : char name[20 ]; int age; double score; }; int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./test.txt" , "w" )) == NULL ) { perror ("fopen error" ); return -1 ; } Stu s[3 ] = {{"张三" , 18 , 98 }, {"李四" , 20 , 88 }, {"王五" , 16 , 95 }}; fwrite (s, sizeof (Stu), 3 , fp); fclose (fp); if ((fp = fopen ("./test.txt" , "r" )) == NULL ) { perror ("fopen error" ); return -1 ; } Stu temp; fread (&temp, sizeof (Stu), 1 , fp); printf ("name:%s, age:%d, score:%.2lf\n" , temp.name, temp.age, temp.score); fclose (fp); return 0 ; }
文件光标操作:fseek/ftell/rewind 原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int fseek (FILE *stream, long offset, int whence) ;功能:移动文件光标位置,将光标从指定位置处进行前后偏移 参数1 :文件指针 参数2 :偏移量 >0 :表示从指定位置向后偏移n个字节 <0 :表示从指定位置向前偏移n个字节 =0 :在指定位置处不偏移 参数3 :偏移的起始位置 SEEK_SET:文件起始位置 SEEK_CUR:文件指针当前位置 SEEK_END:文件结束位置 返回值:成功返回0 ,失败返回-1 并置位错误码 long ftell (FILE *stream);功能:获取文件指针当前的偏移量(字节数) 参数:文件指针 返回值:成功返回文件指针所在的位置,失败返回-1 并置位错误码 eg: fseek (fp, 0 , SEEK_END); ftell (fp); void rewind (FILE *stream) ;功能:将文件光标定位在开头:fseek (fp, 0 , SEEK_SET); 参数:文件指针 返回值:无
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <stdio.h> #include <string.h> using namespace std;class Stu { public : char name[20 ]; int age; double score; }; int main (int argc, const char *argv[]) { FILE *fp = NULL ; if ((fp = fopen ("./test.txt" , "w+" )) == NULL ) { perror ("fopen error" ); return -1 ; } Stu s[3 ] = {{"张三" , 18 , 98 }, {"李四" , 20 , 88 }, {"王五" , 16 , 95 }}; fwrite (s, sizeof (Stu), 3 , fp); printf ("此时文件的大小为:%ld\n" , ftell (fp)); fseek (fp, -sizeof (Stu) * 2 , SEEK_CUR); Stu temp; fread (&temp, sizeof (Stu), 1 , fp); printf ("name:%s, age:%d, score:%.2lf\n" , temp.name, temp.age, temp.score); fclose (fp); return 0 ; }
文件IO 通过系统调用(内核提供的函数)实现,只要使用的文件IO接口,那么进程就会从 用户空间 向内核空间 进行一次切换。标准IO的实现中,也是在内部调用了文件IO操作。该操作效率较低 ,因为没有缓冲区的概念 。文件IO常用的操作:open、close、read、write、lseek
文件描述符(File Descriptor,FD) 1> 文件描述符(FILE结构体里有提到)的本质是一个大于等于0的整数 ,在使用open函数打开文件时,就会产生一个用于操作文件的句柄 ,。文件描述符是操作系统给打开的文件 / 设备分配的「整数编号」,程序通过这个编号来间接操作文件 / 设备,而非直接操作硬件。
2> 在一个进程中,能够打开的文件描述符是有限制的 ,一般是1024个即[0,1023],可以通过指令 ulimit -a进行查看,如果要更改这个限制,可以通过指令 ulimit -n 数字,进行更改
3> 文件描述符的使用原则一般是最小未分配原则
4> 特殊的文件描述符:0、1、2 ,这三个文件描述符在一个进程启动时就默认被打开了 ,分别表示标准输入、标准输出、标准错误
5> 文件描述符不是只针对磁盘文件,而是操作系统统一的 IO 抽象.这也是 Unix/Linux 「一切皆文件」思想的核心 ——所有 IO 操作都通过文件描述符统一处理
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> #include <stdio.h> using namespace std;int main (int argc, const char *argv[]) { printf ("stdin->_fileno = %d\n" , stdin->_fileno); printf ("stdout->_fileno = %d\n" , stdout->_fileno); printf ("stderr->_fileno = %d\n" , stderr->_fileno); return 0 ; }
效果图:
打开文件:open 原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open (const char *pathname, int flags) ;int open (const char *pathname, int flags, mode_t mode) ;功能:打开或创建一个文件,并返回该文件的文件描述符,返回文件描述符的规则是最小未分配原则 参数1 :要打开的文件文件路径 参数2 :打开模式 以下三种方式必须选其一:O_RDONLY (只读)、O_WRONLY(只写)、O_RDWR(读写) 以下的打开方式可以有零个或多个,跟上述的方式一起用位或运算连接到一起 O_CREAT:用于创建文件,如果文件不存在,则创建文件,如果文件存在,则打开文件,如果flag中包含了该模式,则函数的第三个参数必须要加上 O_APPEND:以追加的形式打开文件,光标定位在结尾 O_TRUNC:清空文件 O_EXCL:常跟O_CREAT一起使用,确保本次要创建一个新文件,如果文件已经存在,则open函数报错 eg: "w" : O_WRONLY|O_CREAT|O_TRUNC "w+" : O_RDWR|O_CREAT|O_TRUNC "r" : O_RDONLY "r+" : O_RDWRx "a" : O_WRONLY|O_CREAT|O_APPEND "a+" : O_RDWR|O_CREAT|O_APPEND 参数3 : 如果参数2 中的flag中有O_CREAT时,表示创建新文件,参数3 就必须给定,表示新创建的文件的权限 如果当前参数给定了创建的文件权限,最终的结果也不一定是参数3 的值,系统会用你给定的参数3 的值,与系统的umask取反的值进行位与运算后,才是最终创建文件的权限(mode & ~umask) 当前终端的umask的值,可以通过指令 umask 来查看,一般默认为为 0022 ,表示当前进程所在的组中对该文件没有写权限,其他用户对该文件也没有写权限 当前终端的umask的值是可以更改的,通过指令:umask 数字进行更改,这种方式只对当前终端有效 普通文件的权限一般为:0644 ,表示当前用户没有可执行权限,当前组中其他用户和其他组中的用户都只有读权限 目录文件的权限一般为:0755 ,表示当前用户具有可读、可写、可执行,当前组中其他用户和其他组中的用户都没有可写权限 注意:如果不给权限,那么当前创建的权限会是一个随机值 返回值:成功返回打开文件的文件描述符,失败返回-1 并置位错误码
用 O_BINARY 打开文本文件,不会损坏文件,只是改变了系统对「换行符」的处理规则:
打开方式
对文本文件的影响
示例(写入 \n)
不加 O_BINARY(文本模式)
Windows 自动把 \n(0x0A)转换成 \r\n(0x0D+0x0A),适配 Windows 记事本的换行规则
写入 \n → 实际存储 \r\n
加 O_BINARY(二进制模式)
禁用换行符转换,写入什么字节,就存储什么字节,完全原样保存
写入 \n → 实际存储 \n
文件权限 四位数字的格式是:[特殊权限位][所有者权限][所属组权限][其他人权限]
第 1 位:特殊权限位(取值 0-7,对应 SUID/SGID/ 粘滞位的组合)
第 2 位:所有者(u)基础权限(rwx,取值 0-7)
第 3 位:所属组(g)基础权限(rwx,取值 0-7)
第 4 位:其他人(o)基础权限(rwx,取值 0-7)
特殊权限位:
数字
二进制
对应特殊权限
含义
0
000
无
无特殊权限(最常见,所以第一位是 0)
1
001
粘滞位(Sticky)
仅目录所有者可删除目录内文件(如 /tmp)
2
010
SGID
执行文件时继承所属组权限;目录下新建文件继承目录所属组
4
100
SUID
执行文件时继承文件所有者权限(如 passwd 命令)
3
011
SGID + 粘滞位
组合权限
5
101
SUID + 粘滞位
组合权限
6
110
SUID + SGID
组合权限
7
111
三者都有
组合权限
「所有者 / 所属组 / 其他人」基础权限:
权限符号
二进制
八进制数字
含义
—
000
0
无权限
–x
001
1
仅执行
-w-
010
2
仅写入
-wx
011
3
写入 + 执行
r–
100
4
仅读取
r-x
101
5
读取 + 执行
rw-
110
6
读取 + 写入
rwx
111
7
读 + 写 + 执行(全权限)
文件关闭:close 原型 :
1 2 3 4 5 #include <unistd.h> int close (int fd) ;功能:关闭文件描述符对应的文件 参数:文件描述符 返回值:成功返回0 ,失败返回-1 并置位错误码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <myhead.h> int main (int argc, const char *argv[]) { int fd = -1 ; if ((fd = open ("./tt.txt" , O_WRONLY|O_CREAT, 0644 )) == -1 ) { perror ("open error" ); return -1 ; } printf ("open success fd = %d\n" , fd); close (fd); return 0 ; }
读写操作:read、write 原型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <unistd.h> ssize_t read (int fd, void *buf, size_t count) ;功能:从fd文件描述符引用的文件中读取count的字符放入buf对应的容器中 参数1 :已经打开文件对应的文件描述符 参数2 :容器的起始地址 参数3 :要读取的字符个数 返回值:成功返回读取的字符个数,这个个数可能会小于count的值,失败返回-1 并置位错误码 #include <unistd.h> ssize_t write (int fd, const void *buf, size_t count) ;功能:将buf容器中的count个数据,写入到fd引用的文件中 参数1 :已经打开的文件的文件描述符 参数2 :要写入的数据的起始地址 参数3 :要写入数据的个数 返回值:成功返回写入的字符个数,这个个数可能小于count,失败返回-1 并置位错误码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <myhead.h> int main (int argc, const char *argv[]) { int fd = -1 ; if ((fd = open ("./tt.txt" , O_WRONLY|O_CREAT, 0644 )) == -1 ) { perror ("open error" ); return -1 ; } printf ("open success fd = %d\n" , fd); char wbuf[128 ] = "hello world" ; write (fd, wbuf, strlen (wbuf)); close (fd); if ((fd = open ("./tt.txt" , O_RDONLY)) == -1 ) { perror ("open error" ); return -1 ; } printf ("open success fd = %d\n" , fd); char rbuf[5 ] = "" ; int res = read (fd, rbuf, sizeof (rbuf)); write (1 , rbuf, res); close (fd); return 0 ; }
关于光标的操作:lseek 原型 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <sys/types.h> #include <unistd.h> off_t lseek (int fd, off_t offset, int whence) ;功能:移动光标的位置,并返回光标现在所在的位置 参数1 :文件描述符 参数2 :偏移量 >0 :表示从指定位置向后偏移n个字节 <0 :表示从指定位置向前偏移n个字节 =0 :在指定位置处不偏移 参数3 :偏移的起始位置 SEEK_SET:文件起始位置 SEEK_CUR:文件指针当前位置 SEEK_END:文件结束位置 返回值:光标现在所在的位置 注意: lseek = fseek+ftell
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <myhead.h> int main (int argc, const char *argv[]) { int fd = -1 ; if ((fd = open ("./tt.txt" , O_RDWR|O_CREAT|O_TRUNC, 0644 )) == -1 ) { perror ("open error" ); return -1 ; } printf ("open success fd = %d\n" , fd); char wbuf[128 ] = "hello world" ; write (fd, wbuf, strlen (wbuf)); lseek (fd, -5 , SEEK_END); char rbuf[5 ] = "" ; int res = read (fd, rbuf, sizeof (rbuf)); write (1 , rbuf, res); close (fd); return 0 ; }
改图案例 将一个bmp格式的图片信息读取出来,并将该图片的相关内容(颜色)进行更改
关于bmp图像格式可以参考:https://upimg.baike.so.com/doc/5386615-5623077.html
如何将windows下的文件,复制到linux系统的Centos下
1 2 3 1、在windows下调出cmd窗口 2、输入指令: scp 要拷贝的文件路径 linux下的用户名@ip地址:linux下存放的地址路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <myhead.h> int main (int argc, const char *argv[]) { int fd = -1 ; if ((fd = open ("./wukong.bmp" , O_RDWR)) == -1 ) { perror ("open error" ); return -1 ; } printf ("文件大小为:%ld\n" , lseek (fd, 0 , SEEK_END)); int pic_size = 0 ; lseek (fd, 2 , SEEK_SET); read (fd, &pic_size, 4 ); printf ("pic_size = %d\n" , pic_size); lseek (fd, 54 , SEEK_SET); unsigned char color[3 ] = {0 , 0 , 255 }; for (int i=0 ; i<100 ; i++) { for (int j=0 ; j<684 ; j++) { write (fd, color, sizeof (color)); } } close (fd); return 0 ; }
拷贝案例 使用文件IO来完成两个文件的拷贝,可以完成两个图像的拷贝工作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #define BUF_SIZE 4096 int main (int argc, const char *argv[]) { int input_fd = -1 ; if ((input_fd = open ("cat.bmp" , O_RDONLY | O_BINARY)) == -1 ) { perror ("open input file failed" ); return -1 ; } int output_fd = -1 ; if ((output_fd = open ("cat_copy2.bmp" , O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0644 )) == -1 ) { perror ("open output file error" ); close (input_fd); return -1 ; } char buf[BUF_SIZE] = "" ; int read_bytes = 0 ; while ((read_bytes = read (input_fd, buf, sizeof (buf))) > 0 ) { int write_len = 0 ; if ((write_len = write (output_fd, buf, read_bytes)) == -1 || write_len != read_bytes) { perror ("write error" ); break ; } } if (read_bytes == -1 ) { perror ("read input file error" ); return -1 ; } close (input_fd); close (output_fd); printf ("拷贝成功!" ); return 0 ; }
场景
关键差异
对 BMP 的影响
视觉结果
都用 O_BINARY
无任何数据转换 / 截断
完整保留二进制结构
和原图一致 ✅
仅 output 文本模式
写入时插入 0x0D,增加字节数
像素偏移、图像撕裂
整体偏移 📏
input 文本模式
读取时遇到 0x1A 提前终止
数据截断,丢失上部内容
大面积全透明/黑屏
补充操作建议
安装ctags:sudo yum install ctags
进入 usr/incldue 目录(或其他需要查询的目录)
执行sudo ctags -R指令,会在当前目录生成一个名为 tags 的文件,里面记录了代码中所有「函数、变量、类、宏」的定义位置(文件名 + 行号)。
追寻相关内容:vi/vim -t 内容
编辑器中快速跳转(Vim 中最常用):
生成 tags 文件后,在 Vim 中打开代码,光标移到函数名上,按 Ctrl + ] 直接跳转到定义处;
按 Ctrl + t 跳回原来的位置;
输入 :tag 函数名 直接定位到指定函数的定义。
vsc中自定义代码片段设置
在vs code中使用组合键ctrl+sift+p调出控制面板
输入user snippets
输入自定义片段文件名称打开自定义文件
输入程序内容
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 "Simple C++ Program" : { "scope" : "cpp" , "prefix" : "main" , "body" : [ "#include <iostream>" , "using namespace std;" , "" , "int main(int argc, char* argv[])" , "{" , "" , "\treturn 0;" , "}" ] , "description" : "A simple C++ program with a main function that returns 0." }
建立myhead.h(Linux)
创建一个头文件,例如 myhead.h
将能用到的所有头文件都放入该文件中
将myhead.h文件,移动到根目录下的usr目录中的include目录下sudo mv myhead.h /usr/include
常用的头文件
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <iostream> #include <string> #include <iomanip> #include <stdio.h> #include <math.h> #include <string.h> #include <stdlib.h>
重新设置用户的自定义片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 "Simple C++ Program" : { "scope" : "cpp" , "prefix" : "main" , "body" : [ "#include <myhead.h>" , "using namespace std;" , "" , "int main(int argc, char* argv[])" , "{" , "" , "\treturn 0;" , "}" ] , "description" : "A simple C++ program with a main function that returns 0." }