C语言文件读写
文件分类:
二进制文件:把数据的补码直接写入文件,这种文件叫二进制文件。
优点:读写和写入时不需要进行转换,所以读写速度快,数据安全性高。
缺点:不能使用文本编译器打开,无法阅读。
文本文件:把数据转换成字符串写入文件,也就是把字符的二进制写入文件,这种文件叫文本文件。
优点:能被文本编辑器打开,人类能看的懂,能够看出数据是否出错。
缺点:读写时需要转换,读写速度慢,数据有被修改的风险。
int num = 12306; char ch = 'A'; 二进制文件中的内容: 00000000 00000000 00110000 00010010 文本文件中存储的内容: 先把num的值转换成字符串"12306" 再把字符串中的每个字符的ASCII值的二进制写入文本文件 00110001 00110010 00110011 00110000 00110110 总结:二进制文件的大小是确定的,文本文件会根据内容而变化
打开/关闭文件:
FILE *fopen(const char *path, const char *mode); 功能:打开文件 path:文件的路径 mode:文件的打开模式 返回值:文件结构指针,是后续操作文件的凭证,失败则返回NULL。 int fclose(FILE *stream); 功能:关闭文件 返回值:成功返回0,失败返回-1。 注意:不能重复关闭,否则会出现double free错误,为了避免出现这种错误,在文件关闭及时把文件指针赋值为NULL,及时关闭文件可以把缓冲区中的数据写入到文件中。
文件的打开模式:
"r" 以只读方式打开文本文件,如果文件不存在,或文件没有读权限则打开失败。 "r+" 在"r"的基础上增加了写权限。 "w" 以只写方式打开文本文件,如果文件不存在则创建,如果文件存在则清空文件的内容,如果文件存在但没有写权限,则打开失败。 "w+" 在"w"的基础上增加了读权限。 "a" 以只写方式打开文本文件,如果文件不存在则创建,如果文件存在则新写入的内容追加到文件末尾,如果文件存在但没有写权限,则打开失败。 "a+" 在"a"的基础上增加了读权限。 如果要操作二进制文件,则在以上模式的基础上增加b。
文件的文本打开方式和二进制打开方式的区别:
在 UNIX/Linux 平台中,用文本方式或二进制方式打开文件没有任何区别。
在 UNIX/Linux 平台中,文本文件以\n
(ASCII 码为 0x0a)作为换行符号;而在 Windows 平台中,文本文件以连在一起的\r\n
(\r
的 ASCII 码是 0x0d)作为换行符号。
在 Windows 平台中,如果以文本方式打开文件,当读取文件时,系统会将文件中所有的\r\n
转换成一个字符\n
,如果文件中有连续的两个字节是 0x0d0a,则系统会丢弃前面的 0x0d 这个字节,只读入 0x0a。当写入文件时,系统会将\n
转换成\r\n
写入。
也就是说,如果要写入的内容中有字节为 0x0a,则在写入该字节前,系统会自动先写入一个 0x0d。因此,如果用文本方式打开二进制文件进行读写,读写的内容就可能和文件的内容有出入。
因此,用二进制方式打开文件总是最保险的。
文本文件的读写:
int fprintf(FILE *stream, const char *format, ...); 功能:把若干个变量以文本格式写入到指定的文件中 stream:要写入的文件凭证,必须是fopen的返回值。 format:占位符+转义字符+提示信息 ...:若干个变量 返回值:写入字符的数量 int fscanf(FILE *stream, const char *format, ...); 功能:从文件中读取数据 stream:要读取的文件 format:占位符 ...:若干个变量的地址 返回值:成功读取的变量个数
二进制文件的读写:
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); 功能:把一块内存当作数组,然后数组中的内容以二进制格式写入到文件中 ptr:数组首地址 size:数组元素的字节数 nmemb:数组的长度 stream:要写入的文件 返回值:实际写入的次数 size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:把二进制文件中的内容读取的数组中 ptr:要存储数据的数组首地址 size:数组元素的字节数 nmemb:数组的容量 返回值:成功读取的次数 注意:以二进制格式读写文件时,最好加上mode最好包含b。
#include <stdio.h> #include <stdlib.h> #define ARR_LEN 15 int main(int argc,const char* argv[]) { int arr[ARR_LEN]; for(int i=0; i<ARR_LEN; i++) { arr[i] = rand() % 100; } FILE* fwp = fopen("arr.bin","wb"); if(NULL == fwp) { perror("fopen"); return -1; } int ret = fwrite(arr,sizeof(arr[0]),ARR_LEN,fwp); printf("成功写入%d个元素\n",ret); fclose(fwp); fwp = NULL; } #include <stdio.h> #include <stdlib.h> #define ARR_LEN 15 int main(int argc,const char* argv[]) { FILE* frp = fopen("arr.bin","rb"); if(NULL == frp) { perror("fopen"); return -1; } int* p = malloc(sizeof(p[0])*ARR_LEN); if(NULL == p) { printf("p is NULL\n"); return 0; } int ret = fread(p,sizeof(p[0]),ARR_LEN,frp); printf("ret=%d\n",ret); for(int i=0; i<ARR_LEN; i++) { printf("%d ",p[i]); } fclose(frp); frp = NULL; }
注意:
如果以fwrite/fread读写的字符数组,那么我们操作的依然是文本文件。
#include <stdio.h> #include <stdlib.h> #define ARR_LEN 15 int main(int argc,const char* argv[]) { char arr[ARR_LEN]; for(int i=0; i<ARR_LEN; i++) { arr[i] = rand() % 26 + 65; } FILE* fwp = fopen("arr.bin","wb"); if(NULL == fwp) { perror("fopen"); return -1; } int ret = fwrite(arr,sizeof(arr[0]),ARR_LEN,fwp); printf("成功写入%d个元素\n",ret); fclose(fwp); fwp = NULL; }
int sprintf(char *str, const char *format, ...); 功能:把若干个变量转换成字符串输出到str数组中 int sscanf(const char *str, const char *format, ...); 功能:从字符串读取若干个变量
#include <stdio.h> #include <string.h> typedef struct Data { char ch; short sh; int i; double d; }Data; int main(int argc,const char* argv[]) { Data d = {88,1888,99922233,4.443322}; char buf[256] = {}; // 把数据转换成字符串 sprintf(buf,"%hhd %hd %d %lf",d.ch,d.sh,d.i,d.d); FILE* fwp = fopen("data.bin","wb"); if(NULL == fwp) { perror("fopen"); return -1; } int ret = fwrite(buf,sizeof(buf[0]),strlen(buf),fwp); printf("ret=%d\n",ret); fclose(fwp); fwp = NULL; } #include <stdio.h> #include <string.h> typedef struct Data { char ch; short sh; int i; double d; }Data; int main(int argc,const char* argv[]) { FILE* frp = fopen("data.bin","rb"); if(NULL == frp) { perror("fopen"); return -1; } Data d = {}; char buf[256] = {}; int ret = fread(buf,sizeof(buf[0]),sizeof(buf),frp); printf("ret=%d\n",ret); sscanf(buf,"%hhd %hd %d %lf",&d.ch,&d.sh,&d.i,&d.d); printf("%hhd %hd %d %lf\n",d.ch,d.sh,d.i,d.d); fclose(frp); frp = NULL; }
文件位置指针:
文件位置指针它指向文件中即将要读取的数据,以"r"、"r+"方式打开文件,文件位置指针指向文件的开头,以"a"、"a+"方式打开文件,文件位置指针指向文件的末尾。(但是读取可以在任意位置进行)
读取数据时会从 文件位置指针指向 的地方开始读取,写入数据时也会写入到文件位置指针所指向的地址,并且它会随着读写操作自动移动。
注意:fprintf/fwrite写入数据后立即读取,之所以会失败,是因为文件位置指针指向着文件的末尾。
void rewind(FILE *stream); 功能:把文件的位置指针调整到文件的开头。 long ftell(FILE *stream); 功能:返回文件位置指针指向了文件中的第几个字节 int fseek(FILE *stream, long offset, int whence); 功能:设置文件的位置指针 stream:要设置的文件 offset:偏移值(正负整数) whence:基础位置 SEEK_SET 文件开头 SEEK_CUR 当前位置 SEEK_END 文件末尾 whence+offset就是文件指针最终设置的位置。 返回值:成功返回0,失败返回-1。
文件操作时的局限性:
文件的内容是连续存储在磁盘上的,所以就导致需要进行以下操作:
向文件中插入数据:
1、文件位置指针调整到要插入的位置。
2、把后续的数据整体向后拷贝n(要插入的数据字节数)个字节。
3、文件位置指针调整到要插入的位置,写入数据。
从文件中删除数据:
1、文件位置指针调整到要删除的数据末尾。
2、把后续的数据整体向前拷贝nn(要删除的数据字节数)个字节。
3、修改文件的大小。
总结:所以,在程序运行时,建议在一开始就把文件中的数据全部加载到内存中,程序在运行期间只针对这个数据内存进行增、删、改、查等操作,在程序结束之前,再把数据从内存写入到文件中
文件管理:
int remove(const char *pathname); 功能:删除文件 int rename(const char *oldpath, const char *newpath); 功能:重命名文件 int truncate(const char *path, off_t length); 功能:把文件的内容设置为length字节数 char *tmpnam(char *name); 功能:生成一个与当前文件系统不重名的文件名。 int access(const char *pathname, int mode); 功能:检查文件的权限 mode: R_OK 读权限 W_OK 写权限 X_OK 执行权限 F_OK 文件是否存在 返回值: 检查的权限如果存在则返回0,不存在则返回-1。
多文件编程:
当程序的业务逻辑越来越复杂,代码量越来越多,就需要多人组成团队协同开发,那么就必须把任务拆分成若干个文件。
一般的拆分方案:
1、main.c 只当作程序的入口,不实现业务逻辑代码。
2、用于实现程序具体的业务逻辑代码,按照功能拆分成若干个模块
模块名.h 用说明.c文件中有哪些函数、全局变量,也就是函数声明、全局变量声明。
模块名.c 具体的函数实现,全局变量定义。
注意:不需要被其它.c使用的函数、全局变量,可以不在.h文件中声明,另外为了防止命名冲突、调用,定义时可以用static修饰。
3、项目中常用的、通用的工具,宏函数、函数
tools.h
tools.c
4、只用于类型设计type.h
结构体、联合、枚举、宏常量、宏函数
头文件中可以写什么:
由于头文件可能会被若干个.c文件包含,那么每包含一次,.c文件中就会有一份头文件的内容,所以头文件中的内容必须可重复,因此我们只适合在头文件中实现以下内容:
1、头文件卫士
2、#include 语句
3、宏常量、宏函数
4、全局变量的声明(变量的声明可以有多份,但定义只能有一份)。
5、函数声明
6、结构、联合、枚举复合的类型设计
7、类型重定义
头文件包含可能出现的问题:
注意:头文件卫士只能解决重复包含的问题,但无法解决相互包含、递归包含的问题。
互相包含:
a.h #include "b.h"
b.h #include "a.h"
递归包含:
a.h #include "b.h"
b.h #include "c.h"
c.h #include "a.h"
解决这种问题的文件,再设计一个.h文件,把他们共用的内容,实现在新的.h文件中,被他们共同包含即可。
多文件编译过程:
1、gcc xxx.h 检查头文件是否有语法错误,如果没有语法错误,会生成xxx.h.gch,检查完毕后该文件要立即删除。
2、gcc -c xxx.c 把.c文件编译成二进制目标文件
3、gcc *.o 把所有目标文件合并成可执行文件,也可以使用-o 设置可执行文件的名字。
Makefile脚本
C/C++代码变成可执行程序的过程
# 1、预处理 把程序员所编写的.c结尾的代码,编译.i结尾的预处理文件 gcc -E hello.c -o hello.i # 2、编译 把预处理文件翻译成.s结尾的汇编文件 gcc -S hello.i -> hello.s # 3、汇编 把.s结尾的汇编文件编译成.o结尾的目标文件 gcc -c hello.s -> hello.o # 4、链接 把若干个.o结尾的目标文件合并成可执行文件 gcc a.o b.o c.o ... -o hello
什么是Makefile脚本
Makefile脚本集合了程序的编译指令的文件,make是一个命令工具,当执行make命令时,它会自动读取Makefile中的编译指令并执行,会自动完成整个项目的自动化编译工作。
为什么需要Makefile脚本:
项目中如何有很多.c文件,它们的编译指令会有很多,需要的编译时间比较长,依赖关系非常复杂。
当项目中的.h文件被修改时、.c文件,我们无法人为的分辨出哪些文件需要重新编译,只能全部重新编译一下,但这项操作非常耗时。
使用Makefile脚本编译项目的好处
1、节约时间 2、记录文件之间依赖关系 3、自动化执行编译过程
Makefile脚本的原理
Makefile脚本的原理就是基于文件最后修改时间,被依赖文件的最后修改时间晚于目标文件,该文件就需要重新编译。
tools.o 依赖 tools.c tools.h
Makefile脚本的格式
由若干个编译目标组成,它类似C语言中的函数,就是若干个编译指令组成的编译模块,默认只执行排在第一个的编译目标,也叫入口目标。
target ... : prerequisites ... command ... ... target:编译目标,它如果不存在,或者早于它依赖的文件,那么就执行该下面的编译指令。 prerequisites:被依赖的编译目标、文件,相当于C语言虽的函数调用。 command:编译指令
ums:tools.o after_login.o before_login.o main.o gcc tools.o after_login.o before_login.o main.o -o ums tools.o:tools.c tools.h gcc -c tools.c after_login.o:after_login.c after_login.h tools.c tools.h gcc -c after_login.c before_login.o:before_login.c before_login.h after_login.c after_login.h tools.c tools.h gcc -c before_login.c main.o:main.c before_login.c before_login.h gcc -c main.c
在Makefile脚本中可以使用变量
变量名=value 定义变量并给变量赋值初值
$(变量名) 使用变量名,获取出变量的值
常用的变量名: CC=编译器 STD=语法标准 FLAG=检查标准,-Wall,-Werror TARGE=最终可执行文件的名字 OBJECT=所有的目标文件名
负责清理的编译目标
该编译目标一般负责删除目标文件、头文件的编译结果、可执行文件。 一般它不会被依赖,也就是不会执行,而是当需要时,在命令行通过make 目标名,手动执行。 什么时候需要它执行: 1、刚修改的内容,并没有发生变化,有可能是依赖有问题,代码并没有重新编译,执行它就可以删除所有目标文件、可执行文件,重新编译。 2、更换了执行平台后,那么之前编译出的目标文件就全部不能再继续使用。 3、项目最终上线时,会把所有的编译结果删除,重新编译。
clean: rm -rf $(OBJECT) $(TARGE) rm -rf .h.gch
通用的Makefile脚本
CC=gcc STD=-std=gnu99 FLAG=-Wall -Werror TARGE=ums OBJECT=tools.o after_login.o before_login.o main.o $(TARGE):$(OBJECT) $(CC) $(OBJECT) -o $(TARGE) %.o:%.c $(CC) $(STD) $(FLAG) -c $< clean: rm -rf *.gch rm -rf $(TARGE) rm -rf $(OBJECT) # 缺点:依赖规则无法描述完整