标准c语言10

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)
# 缺点:依赖规则无法描述完整
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值