文件分类:
int num = 2048;
二进制文件:把数据的补码直接写入文件,这种文件叫二进制文件。
优点:读入和写入时不需要进行转换,所以读写速度快。
缺点:不能使用文本编辑器打开,无法阅读。
0000 0000 0000 0000 0000 1000 0000 0000
文本文件:把数据转换成字符串写入文件,这种文件叫文本文件,能被文本编辑器打开,人类能看得懂。
优点:能被文本编辑器打开,人类能看得懂,能够看出数据是否出错。
缺点:读写时需要转换,读写速度慢,数据有被修改的风险。
“2048”
'2' '0' '4' '8'
50 48 52 56
00110010 -50
00110000 -48
00110100 -52
00111000 -56
总结:二进制文件的大小是确定的,文本文件会根据数据的内容而变化,大小不确定。
打开/关闭文件:
FILE *fopen(const char *char,const char *mode);
功能:打开文件
path:文件的路径
mode:文件的打开模式
返回值:文件结构指针,是后续操作文件的凭证,失败的话会返回NULL
文件打开的模式:
"r" 以只读方式打开文本文件,如果文件不存在,或文件没有读权限则打开失败。
"w" 以只写方式打开文本文件,如果文件不存在则创建,如果文件存在则清空文件的内容,如果文件存在但没有写权限,则打开失败。
"a" 以只写方式打开文本文件,如果文件不存在则创建,如果文件存在则新写入的内容追加到文件末尾,如果文件存在但没有写权限,则打开失败。
"r+" 在"r"的基础上增加了写权限。
"w+" 在"w"的基础上增加了读权限。
"a+" 在"a"的基础上增加了读权限。
如果要操作二进制文件,则在以模式的基础上增加b。
以文本方式打开文件时,如果写入要的字符是 '\n' 时,系统会写 '\n' 和 '\r' ,两个字符,在windows系统中以 '\n' + '\r' 表示一个换行,在读取数据时,遇到 '\n' '\r' 只会读取一个 '\n'。
Unix系统里,每行结尾只有“<换行>”,即"\n";Windows系统里面,每行结尾是“<回车><换行>”,即“\r\n”;
如:在windows系统下,写入数字0x0A时,会写入0x0A0D,在linux和unix系统下加不加b没有区别。
注意:如果要操作二进制文件,则在以上模式的基础上增加b。
int fclose(FILE *stream);
功能:关闭文件
返回值:成功返回0,失败返回-1
fclose(file);
file = NULL;
注意:不能重复关闭,否则会出现double free的错误,为了防止出现野指针,文件关闭后最后把文件赋值为空NULL。及时关闭文件可以把缓冲区中的数据写入到文件中。
文本文件的读写:
int fprintf(FILE *stream, const char *format, ...);
功能:把若干个变量以文本格式写入到指定的文件中
stream:要写入的文件
format:占位符+转义字符+提示信息 例如:”%d\n"
...:若干个变量
返回值:写入字符的数量
int fscanf(FILE *stream, const char *format, ...);
功能:从文件中读取数据
stream:要读取的文件
format:占位符
...:若干个变量的地址
返回值:成功读取的变量个数
二进制文件的读写:
注意:以二进制格式读写文件时,最好加上mode,最好包含b。
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:把一块内存处作数组,然后数组中的内容以二进制给是写入到文件中
ptr:数组的首地址
size:一个元素的字节数
nmemb:数组的长度
返回值:实际写入的次数
size_t fread (void *ptr, size_t size, size_t nmemb, FILE *stream) ;
功能:把二进制文件中的内容读取到数组中
ptr:要存储数据的数组的首地址
size:数组元素的字节数
nmemb:数组的容量
返回值:成功读取的次数
注意:如果以fwrite/fread读写的字符串,那么我们操作的依然是文本文件。
练习1:设计一个学生结构,定义结构变量并初始化,把结构变量以文本格式写入到stu.txt文件中。
student.h文件:
#ifndef STUDENT_H
#define STUDENT_H
typedef struct Student
{
char name[20];
char sex;
float score;
int id;
}Student;
#endif//STUDENT_H
main.h文件
#include <stdio.h>
#include "student.h"
int main(int argc,const char* argv[])
{
Student stu = {"hehe",'m',98,10010};
FILE* wfp = fopen("stu.txt","w");
if(NULL == wfp)
{
printf("文件打开失败!");
return 0;
}
int ret=fprintf(wfp,"%s %c %g %d\n",stu.name,stu.sex,stu.score,stu.id);
printf("写入了%d个字符\n",ret);
fclose(wfp);
wfp = NULL;
return 0;
}
练习2:从stu.txt文件中,读取结构变量的值。
student.h文件:
#ifndef STUDENT_H
#define STUDENT_H
typedef struct Student
{
char name[20];
char sex;
float score;
int id;
}Student;
#endif//STUDENT_H
main.h文件:
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
int main(int argc,const char* argv[])
{
Student* stup = malloc(sizeof(Student));
FILE* rfp = fopen("stu.txt","r");
if(NULL == rfp)
{
printf("打开文件失败!\n");
return 0;
}
int ret = fscanf(rfp,"%s %c %g %d",stup->name,&stup->sex,&stup->score,&stup->id);
printf("%s %c %g %d",stup->name,stup->sex,stup->score,stup->id);
printf("成功读取了%d个变量\n",ret);
fclose(rfp);
rfp = NULL;
return 0;
}
文件位置指针:
文件位置指针它记录着读写文件时位置,读取数据时从文件位置指针处开始读取,写入数据时也会写入到文件位置指针所指向的位置,并且它会随着读写操作自动移动。
以“r”、“r+”方式打开文件,文件位置指针指向文件的开头。
以“a”、“a+”方式打开文件,文件位置指针指向文件的末尾。
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。
练习3:实现计算文件字节数的函数。
size_t file_size(const char* path);
#include <stdio.h>
#include <assert.h>
size_t file_size(const char* path)
{
assert(NULL != path);
FILE* rfp = fopen(path,"r");
if(NULL == rfp)
{
return -1;
}
fseek(rfp,0,SEEK_END);
size_t size = ftell(rfp);
fclose(rfp);
return size;
}
int main(int argc,const char* argv[])
{
printf("%d\n",file_size("stu.txt"));
return 0;
}
文件操作时的局限性:
文件的内容是连续存储在磁盘上的,所以就导致以下操作变得困难:
向文件中插入数据:
1、文件位置指针调整到要插入的位置
2、把后续的数据整体向后拷贝n个(要插入的字节数)字节
3、文件位置指针调整到要插入的位置,写入数据
从文件中删除数据:
1、文件位置指针调整到要删除的数据末尾
2、把后续的数据整体向前拷贝n个(要删除的字节数)字节
3、截取文件的长度/修改文件的长度、大小
所以:程序运行时,先把数据从文件中加载到内存,程序在运行期间只针对内存进行增、删、改、查,程序结束时再把数据从内存写入到文件。
文件管理:
int remove(const char *pathname);
功能:删除文件
int rename(const char *oldpath,const char *newpath);
功能:重命名文件
int access(const char* pathname,int mode);
功能:生成一个与当前文件系统不重名的文件名
int truncate(const char *path,off_t length);
功能:把文件的内容设置为length字节数
mode:(可以用或的方式添加多条mode)
R_OK 读权限
W_OK 写权限
X_OK 执行权限
F_OK 文件是否存在
返回值:
检查的权限
char *tmpnam(char *name);
功能:生成一个与当前文件不重名的文件名
练习2:实现一个文件拷贝的函数
int filecopy(char* dest_path,char* src_path);
练习3:实现一个文件移动的函数
int filemove(char* dest_path,char* src_path);
结构序列化:
把结构体变量转化成字符串的过程叫序列化,反序列化就是把字符串转化成结构变量的过程。
为什么把结构变量转化成字符串:
1、网络通信时传输的是大端序列,而个人计算机使用的是小端序列,为了避免传输时的各成员的大小端转换,所以把结构转换成字符串传递更方便。
2、在使用SQL语言操作数据时,SQL是以字符串形式操作数据库的。
方法一:使用sprintf/scanf对简单的结构变量进行序列化和反序列化。
sprintf(arr,"提示信息+占位符",结构.成员变量);
sscanf(str,"提示信息+占位符",&结构.成员变量);
注意:该方法不适合有复杂嵌套关系的结构变量。
方法二:把结构变量转换成JSON格式的字符串,了解cJSON库的使用即可
阅读开源项目的步骤:
1、阅读README文件
版权信息介绍
使用方法介绍
编译方法介绍
原理、示例、局限性
依赖的工具、库
使用时的注意事项
2、编译项目,执行测试程序,阅读测试代码
3、查看示例代码,学习如何使用
4、查看源码,进行学习
5、照葫芦画瓢
main函数参数:
完整的main函数格式:
int main(int argc,const char* argv[ ])
我们在命令执行程序时,可以附加一些参数,这些参数会以字符串形式传递给main函数。
argc:字符串个数,也就是指针数组的长度。
argv:指针数组,里面存储着每个字符串的首地址。
for(int I=0; i<argc; i++)
{
printf("%s\n",argv[i]);
}
./a.out 执行程序时,argc的值是1
练习4:实现一个带覆盖检测的mymv命令。
if(3 != argc)
{
puts("Use:./mymv src dest");
return 0;
}
filemove(argv[2],argv[1]);
数据加密:
对称加密:
明文 经过加密处理 ------> 密文
密文 经过加密处理 ------> 明文
最简单的加密:
A ^ B = C
C ^ B = A
非对称加密:
明文经过加密处理能得到独一无二的密文,而密文无法还原出明文。
用于用户登录以及验证文件的完整性。
如:MD5算法