Day15 内容梳理:
目录
Chapter 10 文件操作
10.2 文件加密、解密
(1)文件加密
先在D盘手动创建了名为"解密1"的文件,写入了“这是一句话。”
int main(){
// 文件的加密
FILE* fp1 = fopen("D:/解密1.txt", "r");
FILE* fp2 = fopen("D:/加密1.txt", "w");
char ch;
while ((ch = fgetc(fp1)) != 0 ) { //让fp1指针不断指向下一个字符,并判断是否指向了文件终止符0,是则终止while
//加密
ch++;
fputc(ch, fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
结果如下,左侧是原始文件,右侧是加密后的文件
(2)文件解密
继续用上面通过ch++加密过的文件,这次解密文件只需要while循环中让ch--。
int main() {
// 文件的解密
FILE* fp1 = fopen("D:/加密1.txt", "r");
FILE* fp2 = fopen("D:/解密2.txt", "w");
char ch;
while ((ch = fgetc(fp1)) != 0) { //让fp1指针不断指向下一个字符,并判断是否指向了文件终止符0,是则终止while
//解密
ch--;
fputc(ch, fp2);
}
fclose(fp1);
fclose(fp2);
return 0;
}
结果如下,从“解密1”到“加密1”,再到“解密2”,完成了转换。
10.3 文件读取、写入
(1)逐字符读写文件
读取时:
int main() {
// (1)字符读取
FILE* fp = fopen("D:/abc.txt", "r");
//malloc()是stdlib库的函数,需要在代码最开头加#include<stdlib.h>
char* p = (char*)malloc(sizeof(char) * 5); //用malloc函数给指针分配一定存储空间
memset(p, 0, 5); //是string库的函数,需要在代码最开头加#include<string.h>
fgets(p, 5, fp);
printf("%s\n", p); //打印指针指向的内容
memset(p, 0, 5);
fgets(p, 5, fp);
printf("%s\n", p);
memset(p, 0, 5);
fgets(p, 5, fp);
printf("%s\n", p);
free(p); //释放p指针所占用的空间资源
return 0;
}
判断是否到文件结尾的函数:feof(文件指针)
适用范围:文本文件和二进制文件都可
使用方式:没有到文件结尾则返回0,到文件结尾则返回非零值,因此这么用while( ! feof(fp) ){}
写入时:
在字符串中遇到\n不会终止读写,而是换行,但是遇到\0则会终止读写。所以下面分情况探讨了这三种。不过无论是哪种情况,代码整体没有改动。(只有字符串中发生了\n或\0等改动)
情况1:文本没有\n也没有\0的情况:
原文件: 写入后的文件:
情况2:文本有\n的情况,会换行
原文件: 写入后的文件:
情况3:文本有\0的情况,会只写入\0以前的内容
原文件: 写入后的文件:
(2)逐行读写文件
虽说是逐行,但实际是每次读取指针p大小的内容。比如p大小为2,就一次读2个数据;大小为10就一次读取10个数据。
读取文件:
写入文件:
注意需要导入string库(代码为#include<string.h>)
int main() {
// 以行读取的形式将内容写入文本文件
FILE* fp = fopen("D:/code.txt", "w");
char* p = (char*)malloc(sizeof(char) * 1024);
while (1) { //循环条件设为1,持续读取输入的文本
memset(p, 0, 1024);
scanf("%[^\n]",p); //①:吞噬输入字符串中的\n
getchar(); //追加\n换行
//判断是否停止输入
if (!strcmp(p, "comm=exit")) break; //②
strcat(p, "\n"); //追加\n换行
fputs(p, fp); //将除了comm=exit以外的内容都存入文本文件
}
free(p);
fclose(fp);
return 0;
}
需要特别注意的有两点。
①代码scanf("%[^\n]",p);的作用:
是吞噬输入字符串中的\n,也就是不把回车以文本的形式(\n)存入文本文件中。但是考虑到我们仍需要回车来分割字段,所以加了strcat(p,”\n”);来追加回车。
②if( !strcmp(p, “comm=exit”))中加了个“!”:
因为strcmp()的作用是接收两个参数并比较,返回值为0则代表两个值相同。但放回代码看也就是if(0),无法进入条件,所以在strcmp()前加了“!”符号,相当于如果符合条件,就是if(1)。
运行代码后,直接在弹出的调试控制台中输入文本。输入什么都行,我这里输入的是hello world的代码。
得到:
(3)逐数据块读写文件fread()、fwrite()
可以把所有的结构类型都以数据块的形式读取、写入文件
①读写数字
int main() {
//数据体:写入数字
FILE* fp = fopen("D:/数据块读写1.txt", "w");
if (!fp) return -1; //如果文件打开失败,返回-1
//(1)存入数字
int a = 1234;
fwrite(&a, sizeof(int), 1, fp); //存储格式:地址、元素大小、元素个数、文件指针
fclose(fp);
return 0;
}
写入的时候,因为是转成二进制后以二进制的编码格式显示在文本文件中,所以看不到具体数据。
不过点开文件的属性可以看到,占用4个字节,也就是一个int类型数据的大小
读取的时候,因为操作的是二进制文件,所以读取符号是”rb”而非”r”。
int main() {
//数据体:读取数字
FILE* fp = fopen("D:/数据块读写1.txt", "rb"); //在windows中,读取二进制文件是rb而非r
if (!fp) return -1; //如果文件打开失败,返回-1
// 读取数字
int num;
fread(&num, sizeof(int), 1, fp); //存储格式:地址、元素大小、元素个数、文件指针
printf("%d\n", num); //打印值看看
fclose(fp);
return 0;
}
打印结果如下:
②读写数组
代码都和上面相似。
int main() {
//数据体:写入数组
FILE* fp = fopen("D:/数据块读写2.txt", "w");
if (!fp) return -1; //如果文件打开失败,返回-1
//(2)存入数组
int arr[] = { 1,2,3,4,5,6,7,8,9 };
fwrite(arr, sizeof(int), 9, fp); //存储格式:地址、元素大小、元素个数、文件指针
fclose(fp);
return 0;
}
写入的结果:
代码和读取结果如下图:
int main() {
//数据体:读取数组
FILE* fp = fopen("D:/数据块读写2.txt", "rb"); //在windows中,读取二进制文件是rb而非r
if (!fp) return -1; //如果文件打开失败,返回-1
// 读取数组
int arr[9] = {0}; //把元素都先初始化为0
fread(arr, sizeof(int), 10, fp); //存储格式:地址、元素大小、元素个数、文件指针
for (int i = 0; i < 9; i++) {
printf("%d ", arr[i]); //打印值
}
fclose(fp);
return 0;
}
③读写结构体
typedef struct student {
char name[20];
int age;
int score;
char addr[50];
}stu;
int main() {
//数据体:写入结构体
stu ss[3] = {
{"张三",16,90,"单元楼A"},
{"李四",17,95,"单元楼B"},
{"王五",15,80,"单元楼C"}
};
FILE* fp = fopen("D:/数据块读写3.txt", "wb");
if (!fp) return -1; //如果文件打开失败,返回-1
//(3)存入结构体
for (int i = 0; i < 3; i++) {
//需要区分的是,sizeof(stu)是单个结构体元素的大小,而sizeof(student)则是整个结构体的大小
fwrite(&ss[i], sizeof(stu), 1, fp); //一次写入一条结构体元素。
}
fclose(fp);
return 0;
}
写入结果如下,能显示部分中文,而大部分数据都处于乱码或不可见状态:
读取的代码和结果如下:
10.4 技能应用(文件写入+随机数)
随机生成四则运算题目。
需要用到:文件写入、随机数种子、枚举类(枚举出加减乘除的符号)
分两步进行,一步是随机生成题目,另一步是运算出这些随机式的答案。
第一步,生成四则运算题目
srand()是stdlib库的,time是time库的,需要在代码的最开头写:
#include<stdlib.h>
#include<time.h>
enum operators {
add, subtract, multiply, divide //没规定各自代表的数字,默认从0开始,0、1、2、3分别代表四个符号
};
int main() {
//随机生成四则运算式子
srand( (int)time(NULL) ); //随机数种子,是stdlib库的
FILE* fp = fopen("D:/四则运算.txt", "w");
char* p = (char*)malloc( sizeof(char)* 20 );
int num1, num2;
char opt;
for (int i = 0; i < 10; i++) {
//随机生成从1到10的数
num1 = rand()% 10 + 1;
num2 = rand()% 10 + 1;
switch (rand() % 4) { //随机生成一个数,除以4后取余,得到enum类中加减乘除符号对应的值
case add: opt = '+';
break;
case subtract: opt = '-';
break;
case multiply: opt = '*';
break;
case divide: opt = '/';
break;
}
memset(p, 0, 20);
// sprintf()是格式化好字符串内容
sprintf(p, "%d%c%d=\n", num1, opt, num2); //生成格式:数字1 符号 数字2 =
fputs(p, fp); //把生成的内容写入文本中
}
free(p);
fclose(fp);
return 0;
}
这里用sprintf()而非printf(),是因为sprintf()可以把内容按格式排列。后续计算四则运算结果所用到的sscanf()也是同理。
结果如下,每运行一次代码,文本内容都会被随机生成的新内容覆盖。
第二步,读取式子并在另一个文本文件中生成式子+答案
首先利用while循环和判断是否到文本末尾的feof()来逐行读取,再利用sscanf()按一定格式取得num1、符号、num2.通过switch判断符号是哪个之后,在对应的case中执行运算,并把结果存储进另一个文件中。
结果如下: