C++笔记day18 结构体指针 文件读写

183 阅读5分钟

结构体嵌套二级指针练习

有多少个malloc要有多少释放

image.png

示例

struct Teacher 
{
	char* name;
	char** students;
};
void allocateSpace(struct Teacher*** teacher) 
{
	if (teacher ==NULL)
	{
		return;
	}
	//开辟内存
	struct Teacher** ts = malloc(sizeof(struct Teacher*) * 3);
	//给每个老师分配内存
	for (size_t i = 0; i < 3; i++)
	{
		ts[i] = malloc(sizeof(struct Teacher));
		//给老师的姓名分配内存
		ts[i]->name = malloc(sizeof(char) * 64);
		//给老师起名称
		sprintf(ts[i]->name, "Teacher_%d", i + 1);
		//给学生数组分配内存
		ts[i]->students = malloc(sizeof(char*) * 4);
		//给学生的兴明光开辟内存以及赋值
		for (size_t j = 0; j < 4; j++)
		{
			ts[i]->students[j] = malloc(sizeof(char) * 64);
			sprintf(ts[i]->students[j], "%s_Students_%d", ts[i]->name, j + 1);
		}
	}
	*teacher = ts;
}
void printTeacher(struct Teacher** pArray) 
{
	if (pArray == NULL) 
	{
		return;
	}
	for (size_t i = 0; i < 3; i++)
	{
		printf("%s\n", pArray[i]->name);
		for (size_t j = 0; j < 4; j++)
		{
			printf("            %s\n", pArray[i]->students[j]);
		}
	}
}
void freeSpace(struct Teacher** pArray) 
{
	if (pArray == NULL) 
	{
		return;
	}
	for (size_t i = 0; i < 3; i++)
	{
		//先释放老师姓名
		if (pArray[i]->name != NULL) 
		{
			free(pArray[i]->name);
			pArray[i]->name = NULL;
		}
		//释放学生姓名
		for (size_t j = 0; j < 4; j++)
		{
			if (pArray[i]->students[j] != NULL) 
			{
				free(pArray[i]->students[j]);
				pArray[i]->students[j] = NULL;
			}
		}
		//释放学生数组
		if (pArray[i]->students != NULL) 
		{
			free(pArray[i]->students);
			pArray[i]->students = NULL;
		}
		//释放老师
		if (pArray[i] != NULL) 
		{
			free(pArray[i]);
			pArray[i] = NULL;
		}
	}
	//释放老师数组
	if (pArray != NULL) 
	{
		free(pArray);
		pArray = NULL;
	}
}
void test01() 
{
	struct Teacher** pArray = NULL;
	allocateSpace(&pArray);
	printTeacher(pArray);
	freeSpace(pArray);
}
int main(void)
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

结构体偏移量

获取属性偏移  offsetof

1. offsetof

2. (int)&(p->b) - (int)p

通过偏移量 获取内存

结构体嵌套结构体

示例

struct Teacher 
{
	char a;//0~3
	int b;//4~7
};
void test01() 
{
	struct Teacher t1;
	struct Teacher* p = &t1;

	printf("b的属性偏移量为:%d\n", (int)&(p->b) - (int)p);
	printf("b的属性偏移量为:%d\n", offsetof(struct Teacher,b));
}
//通过偏移量 操作内存
void test02() 
{
	struct Teacher t1 = { 'a',10 };
	printf("t1.b =%d\n", *(int*)((char*)&t1 + offsetof(struct Teacher, b)));
	printf("t1.b =%d\n", *(int*)((int*)&t1 +1));//二者等价
}
struct Teacher2 //结构体嵌套结构体
{
	char a;
	int b;
	struct Teacher c;
};
//本质上是
//struct Teacher2 //结构体嵌套结构体
//{
//	char a;
//	int b;
//	char a;
//	int b;
//};
void test03() 
{
	struct Teacher2 t1 = { 'a',10,'b',20 };

	int offset1 = offsetof(struct Teacher2, c);
	int offset2 = offsetof(struct Teacher, b);

	printf("%d\n", *(int*)((char*)&t1 + offset1 + offset2));//二者等价
	printf("%d\n", ((struct Teacher*)((char*)&t1 + offset1))->b);
}
int main(void)
{
	test01();
	test02();
	test03();
	system("pause");
	return EXIT_SUCCESS;
}

内存对齐

image.png

查看对齐模数  #pragma pack(show)

默认对齐模数  8

自定义数据类型 对齐规则

第一个属性开始  从0开始偏移

第二个属性开始  要放在 该类型的大小  与  对齐模数比  取小的值  的整数倍

所有属性都计算完后,再整体做二次偏移,将整体计算的结果 要放在  结构体最大类型 与对齐模数比  取小的值的 整数倍上

结构体嵌套结构体

结构体嵌套结构体时候,子结构体放在该结构体中最大类型 和对齐模数比 的整数倍上即可

示例

//1. 第一个属性开始,从0开始
//2. 第二个属性 要放在该类型的大小 与 对齐模数比 取小的值 的整数倍
#pragma pack(show)//查看当前的对齐模数,只需要重新生成;
#pragma pack(1)//对齐模数是可以改的,可以改成2的n次方
//3. 所有属性都计算完后,再整体做二次偏移,将整体计算的结果 要放在 结构体最大类型 与对齐模数比取小的值得整数倍

typedef struct _STUDENT 
{
	int a;//0-3
	char b;//4-7
	double c;//8-15
	float d;//16-19
} Student;
//结构体嵌套结构体时候,子结构体放在该结构体中最大类型和对齐模数比的整数倍上即可

void test01() 
{
	printf("size of srudent = %d\n", sizeof(Student));
}
typedef struct _STUDENT2
{
	char a;//0-4
	Student b;//8-31
	double c;//32-39
} Student2;
void test02()
{
	printf("size of srudent = %d\n", sizeof(Student2));
}
int main(void)
{
	test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

文件读写回顾

image.png

按照字符读写

写  fputc

读  fgetc

while ( (ch = *fgetc*(f_read)) != *EOF*  )  判断是否到文件尾

示例

// 按照字符读写文件:fgetc(),fputc();
void test01() 
{
//写文件
	FILE * f_write =fopen("./test01.txt", "w+");

	if (f_write == NULL) 
	{
		return;
	}
	char buf[] = "this is first test";
	for (size_t i = 0; i < strlen(buf); i++)
	{
		fputc(buf[i], f_write);
	}
	fclose(f_write);
//读文件
	FILE* f_read = fopen("./test01.txt", "r");
	if (f_read == NULL) 
	{
		return;
	}
	char ch;
	while ((ch = fgetc(f_read)) != EOF)  //EOF End of File
	{
		printf("%c", ch);
	}
	fclose(f_read);
}


int main(void)
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

按行读写

写  fputs

读  fgets

示例

//按照行读写文件
void test02() 
{
	FILE* f_write = fopen("test02.txt", "w");
	if (f_write ==NULL)
	{
		return;
	}
	char* buf[] =
	{
		"锄禾日当午,\n",
		"汗滴禾下土。\n",
		"谁知盘中餐,\n",
		"粒粒皆辛苦。\n"
	};
	for (size_t i = 0; i < 4; i++)
	{
		fputs(buf[i], f_write);
	}
	fclose(f_write);
	//读文件
	FILE* f_read = fopen("test02.txt", "r");
	if (f_read ==NULL)
	{
		return;
	}
	while (!feof(f_read)) 
	{
		char buf[1024] = { 0 };
		fgets(buf, 1024, f_read);
		printf("%s", buf);
	}
	fclose(f_read);
}

按块读写

写  fwrite

参数1 数据地址  参数2  块大小   参数3  块个数   参数4  文件指针

读  fread

示例

//按块读写文件 fread() fwrite()
struct Hero 
{
	char name[64];
	int age;
};
void test03() 
{
	//写文件 wb二进制方式
	FILE* f_w = fopen(".test03.txt", "wb");
	if (!f_w)
	{
		return;
	}
	struct Hero heroes[4] =
	{
		{"Author",18},
	{"Jeff", 23},
	{"Andy", 22},
	{"Bob",15}
	};
	for (size_t i = 0; i < 4; i++)
	{
		//参数1 数据地址;参数2 块大小;参数3:块个数;参数4:文件指针
		fwrite(&heroes[i], sizeof(struct Hero), 1, f_w);
	}
	fclose(f_w);
	//读文件
	FILE* f_r = fopen(".test03.txt", "rb");//read binary
	if (f_r == NULL)
		{
			return;
		}
	struct Hero temp[4];
	fread(&temp, sizeof(struct Hero), 4, f_r);
	for (size_t i = 0; i < 4; i++)
		{
			printf("name %s age:%d\n", temp[i].name, temp[i].age);
		}
	fclose(f_r);
	
}

格式化读写

写  fprintf

读  fscanf

示例

void test04() 
{
	//写文件
	FILE* fw = fopen("test04.txt", "w");
	if (!fw)
	{
		return;
	}
	fprintf(fw, "hello world %d年 %d月 %d日", 2022, 9, 17);
	//关闭文件
	fclose(fw);
	//读文件
	FILE* fr = fopen("test04.txt", "r");
	if (!fr) 
	{
		return;
	}

	char buf[1024] = { 0 };
	while (!feof(fr)) 
	{
		fscanf(fr, "%s", buf);
		printf("%s\n", buf);
	}

	fclose(fr);
}

随机位置读写

fseek()偏移文件光标 ftell获取当前光标位置 rewind()让文件光标返回文件首部

fseek()偏移文件光标( 文件指针, 偏移, 起始位置 ) 
SEEK_SET  从头开始

SEEK_END  从尾开始

SEEK_CUR  从当前位置

rewind  将文件光标置首

error宏  利用perror打印错误提示信息

示例

//按照随机位置读写文件
void test05() 
{
	FILE* f_w = fopen(".test05.txt", "wb");
	if (!f_w)
	{
		return;
	}
	struct Hero heroes[4] =
	{
		{"Author",18},
	{"Jeff", 23},
	{"Andy", 22},
	{"Bob",15}
	};
	for (size_t i = 0; i < 4; i++)
	{
		//参数1 数据地址;参数2 块大小;参数3:块个数;参数4:文件指针
		fwrite(&heroes[i], sizeof(struct Hero), 1, f_w);
	}
	fclose(f_w);

	//读取Andy数据
	FILE* fr = fopen(".test05.txt", "r");
	if (!fr) 
	{
		//error 宏
		//printf("fail open");
		perror("fail to open fr");
		return;
	}

	//创建临时结构体
	struct Hero temp;
	//改变文件光标位置
	fseek(fr, sizeof(struct Hero) * 2,SEEK_SET);
	//sizeof 如果想让它是负数 要强转成long类型 -(long)sizeof(struct Hero)*2
	rewind(fr);//将文件光标置首
	fread(&temp, sizeof(struct Hero), 1, fr);
	printf("name of %s age of %d", temp.name, temp.age);
	fclose(fr);
}

文件读写注意事项

注意事项1:

不要用feof 按照字符方式读文件,原因有滞后性,会读出EOF

注意事项2:

如果属性开辟到堆区,不要存指针到文件中,要将指针指向的内容存放到文件中

配置文件读写案例

1. 文件中按照键值对方式 存放了有效的信息需要解析出来
2. 创建 config.h  和 config.c做配置文件读操作
3. 获取有效信息的行数  getFileLines
4. 判断字符串是否是有效行  int isValidLines(char *str)
5. 解析文件到配置信息数组中
    void parseFile(char * filePath, int lines , struct  ConfigInfo ** configinfo);
6. 通过key获取value值
     char * getInfoByKey(char * key, struct ConfigInfo * configinfo, int len);
7. 释放内存
    void freeConfigInfo(struct ConfigInfo * configinfo);

示例:头文件 config.h

#pragma once	
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <Windows.h>
//配置信息
struct ConfigInfo 
{
	char key[64];
	char value[64];
};
//获取有效行数
int getFileLInes(char *filePath);

//检测当前的行是否是有效的信息
int isVaildLInes(char *str);

//解析文件
void parseFile(char* filePath, int lines, struct ConfigInfo** configinfo);

//根据key获取对应value
char* getInfoByKey(char* key, struct ConfigInfo* configinfo, int len);

//释放内存
void freeConfigInfo(struct ConfigInfo* configinfo);

示例:函数文件 config.c

#include "config.h"

int getFileLines(char* filePath) 
{
	FILE* file = fopen(filePath, "r");
	if (file == NULL) 
	{
		return NULL;
	}
	char buf[1024] = { 0 };
	int lines=0;
	while (fgets(buf, 1024, file) != NULL) 
	{
		
		if (isValidLines(buf))
		{
			lines++;
		}
		memset(buf, 0, 1024);
	}
	return lines;
	fclose(file);
}
int isValidLines(char* str) 
{
	if (strchr(str, ':') == NULL) 
	{
		return 0;//返回假,代表无效行
	}
	else 
	{
		return 1;//返回真
	}
}
//解析文件
void parseFile(char* filePath, int lines, struct ConfigInfo** configinfo)
{
	struct ConfigInfo* info = malloc(sizeof(struct ConfigInfo) * lines);
	if (info == NULL)
	{
		return;
	}
	FILE* file = fopen(filePath, "r");
	if (!file)
	{
		return;
	}
	char buf[1024] = { 0 };
	int index = 0;
	while (fgets(buf, 1024, file) != NULL) 
	{
		if (isValidLines(buf)) 
		{
			//有效信息 才去解析
			//清空key和value数组
			//heroName:aaa;
			memset(info[index].key, 0, 64);
			memset(info[index].value, 0, 64);
			char * position =strchr(buf, ':');//得到相应符号的地址
			strncpy(info[index].key, buf, position - buf);//pos-buf相对位置偏移量
			strncpy(info[index].value, position + 1, strlen(position+1)-1);//第二个参数是起始位置,第三个参数-1 以免引入换行符
			index++;
		}
		memset(buf, 0, 1024);
	}
	*configinfo = info;
}

char* getInfoByKey(char* key, struct ConfigInfo* configinfo, int len) 
{
	for (size_t i = 0; i < len; i++)
	{
		if (strcmp(key, configinfo[i].key) == 0) //代表找到相关信息了
		{
			return configinfo[i].value;
		}
	}
	return NULL;
}
void freeConfigInfo(struct ConfigInfo* configinfo) 
{
	if (configinfo != NULL) 
	{
		free(configinfo);
		configinfo = NULL;
	}
}

示例:主程序

# include "config.h"

int main(void)
{
	char* filePath = "./config.txt";
	
	int len = getFileLines(filePath);
	printf("文件的有效行数为:%d\n", len);
	
	struct ConfigInfo* configInfo =NULL;
	parseFile(filePath, len, &configInfo);

	//测试根据key获取value
	printf("hero id = %s\n", getInfoByKey("heroId", configInfo, len));
	printf("hero Name = %s\n", getInfoByKey("heroName", configInfo, len));
	printf("hero Atk = %s\n", getInfoByKey("heroAtk", configInfo, len));
	printf("hero Def = %s\n", getInfoByKey("heroDef", configInfo, len));
	printf("hero Info = %s\n", getInfoByKey("heroInfo", configInfo, len));
	
	//释放空间
	freeConfigInfo(configInfo);
	configInfo == NULL;

	system("pause");
	return EXIT_SUCCESS;
}

添加文件加密和解密的模块

//文件加密
void codeFile(char* sourceFilePath, char* destFilePath)
{
	FILE* fp1 = fopen(sourceFilePath, "r");
	FILE* fp2 = fopen(destFilePath, "w");

	if (!fp1 || !fp2)
	{
		perror("fopen");
		return;
	}
	char ch;
	while ((ch = fgetc(fp1)) != EOF)
	{
		// # 代表35
		//先转为short类型
		//0000 0000 0010 0011   <<4
		//0000 0010 0011 0000

		short temp = (short)ch;
		temp = temp << 4;
		//0000 0010 0011 0000 |
		//1000 0000 0000 0000
		//1000 0010 0011 0000 +0000~1111随机数 0~15
		temp = temp | 0x8000;

		temp += rand() % 16;

		fprintf(fp2,"%hd\n", temp);
	}
	fclose(fp1);
	fclose(fp2);
}

//解密文件
void decodeFile(char* sourceFilePath, char* destFilePath)
{
	FILE* fp1 = fopen(sourceFilePath, "r");
	FILE* fp2 = fopen(destFilePath, "w");

	if (!fp1 || !fp2)
	{
		perror("fopen");
		return;
	}
	//1000 0010 0011 1010  <<1
	//0000 0100 0111 0100  >>5
	//0000 0000 0010 0011

	short temp;
	while (!feof(fp1)) 
	{

		fscanf(fp1, "%hd", &temp);

		temp = temp << 1;
		temp = temp >> 5;
		char ch = (char)temp;
		fputc(ch, fp2);
	}
	fclose(fp1);
	fclose(fp2);
}
main函数里 可以添加srand((unsigned int)time(NULL));
通过随机数种子来让每次加密后的文件不一样。