C语言核心知识点Day05

1.结构体嵌套二级指针

	当创建结构体变量后,如果其中的成员为指针类型的话,就要将此变量
开辟至堆空间,是因为在创建结构体后,程序在运行结束之后,对于结构体
变量所占的内存空间就已经分配好了,分配的大小为4字节(也就是一个指针
的)大小,如果不重新在堆空间中开辟内存的话,就会导致溢出,因此,一
般情况下,看到使用指针类型的数据的时候,应该在堆空间中存储。
	同样的,当存在多级指针的时候,也就相应的要开辟多块存储空间,在
这个时候,记住先释放内层的堆空间,再释放外层的堆空间。
	小技巧:浏览代码看malloc数和free数是否相等,如果相等的话一般都是
没问题的。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

struct Teacher
{
	char* name;
	char** student;
};

void test01()
{
	struct Teacher** t1 = malloc(sizeof(struct Teacher* ) * 3);
	//开辟空间并进行赋值操作
	for (int i = 0; i < 3; i++)
	{
		t1[i] = malloc(sizeof(struct Teacher));
		t1[i]->name = malloc(sizeof(char) * 64);//给每个教师的名字开辟堆空间
		sprintf(t1[i]->name, "teacher_%d", i + 1);
		t1[i]->student = malloc(sizeof(char*) * 5);//每个老师可以带5个学生
		for (int j = 0; j < 5; j++)
		{
			t1[i]->student[j] = malloc(sizeof(char) * 64);
			sprintf(t1[i]->student[j], "teacher_%d_name_%d", i + 1, j + 1);
		}
	}
	//打印输出结果
	for (int i = 0; i < 3; i++)
	{
		printf("teacher_name = %s\n", t1[i]->name);
		for (int j = 0; j < 5; j++)
		{
			printf("studennt_name = %s\t", t1[i]->student[j]);
		}
		printf("\n");
	}
	//释放空间
	for (int i = 0; i < 3; i++)
	{
		if (NULL != t1[i]->name)
		{
			free(t1[i]->name);
			t1[i]->name = NULL;
		}
		for (int j = 0; j < 5; j++)
		{
			if (t1[i]->student[j] != NULL)
			{
				free(t1[i]->student[j]);
				t1[i]->student[j] = NULL;
			}
		}
		if (NULL != t1[i])
		{
			free(t1[i]);
			t1[i] = NULL;
		}
	}

	if (NULL != t1)
	{
		free(t1);
		t1 = NULL;
	}
}

int main()
{
	test01();
	return 0;
}

2.结构体成员偏移量

计算结构体中的成员偏移量的时候有两种办法:
	1.通过计算不同数据类型所占的字节大小累计后得出目标成员的偏移量。
	2.通过头文件<stddef.h>中的offsetof函数来进行计算
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stddef.h>

struct Teacher
{
	char a;
	int b;
};

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 = {'c',22};
	struct Teacher* p = &t1;
	printf("结构体中的b的值:%d\n",*(int* )((char* )p + offsetof(struct Teacher, b)));
	printf("结构体中的b的值:%d\n", *((int*)p + 1));

}
//结构体
struct teacher2
{
	char a;
	int b;
	struct Teacher c;
};
void test03()
{
	struct teacher2 t2= { 'a',10,'b',20 };
	int offset1 = offsetof(struct teacher2, c);
	int offset2 = offsetof(struct Teacher, b);

	//方式一:
	printf("属性C中的值为:%d\n", *(int*)((char*)&t2 + offset1 + offset2));
	//方式二:
	printf("属性C中的值为:%d\n", ((struct Teacher*)((char*)&t2 + offset1))->b);
}

int main()
{
	//test01();
	//test02();
	test03();
	return 0;
}

3.结构体字节对齐

	在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中
所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。

	从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是
事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这
就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这
就是内存对齐。

	举一个很简单的例子,例如我们在内存中存储数字,假如是按照我们预期
的顺序存储的方式进行存储,存储一个char 和一个int型的数据,假设计算机
在读取的时候是4字节读取,那么就会先读到char类型的数据,然后读取int型
数据的前三位,这是一次读取,然后再读取下一块内容,读取int型数据的最
后一位数据,最后如果要得到这个数据,计算机还要进行拼接操作,将之前
所读取到的三个字节和第二次读取到的一个字节拼接起来,才可以得到完整
的数字,而通过字节对齐的方式进行存储,虽然在空间上浪费,但是在时间
上却大大提高了效率,因此,这是一种牺牲空间换取时间的存储方式,提高
了存取数据的效率。

	对齐的原则为以下几点:
		1.第一个属性开始,从零开始计算偏移量
		2.第二个属性要放在,该属性大小与对齐模数比,二者较小的那个值
	的整数倍当所有属性计算完毕后,整体做二次偏移,将上有计算的结果,
	扩充到这个结构体中最大的数据类型的整数倍上
		也就是说,其实对齐就是以结构体中最大的数据类型为基准,其余的
	变量的存储都是以这个最大的数据类型的空间为准,能存储的下就存储,
	存储不下就开辟一块新的最大的数据类型的空间进行存储。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#pragma pack(show)//对齐模数 8

//内存对齐原则
//第一个属性开始,从零开始计算偏移量
//第二个属性要放在 该属性大小与对齐模数比,二者较小的那个值的整数倍
//当所有属性计算完毕后,整体做二次偏移,将上有计算的结果,扩充到这个结构体中最大的数据类型的整数倍上

typedef struct _STUDENT
{
	int a;
	char b;
	double c;
	float d;
}Student;

void test01()
{
	printf("student结构体的大小为:%d\n", sizeof(Student));
}

//结构体嵌套结构体的时候
typedef struct _STUDENT2
{
	char a;//8
	Student b;//24
	double c;//40
}Student2;

void test02()
{
	printf("student2结构体的大小为:%d\n", sizeof(Student2));
}

int main()
{
	test01();
	test02();

	return 0;
}

4.文件

4.1 流的概念
	流是一个动态的概念,可以将一个字节形象地比喻成一滴水,字节在
设备、文件和程序之间的传输就是流,类似于水在管道中的传输,可以看
出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。C语言
中,I/O操作可以简单地看作是从程序移进或移出字节,这种搬运的过程
便称为流(stream)。

4.2 文件指针	
	我们知道,文件是由操作系统管理的单元。当我们想操作一个文件的
时候,让操作系统帮我们打开文件,操作系统把我们指定要打开文件的信
息保存起来,并且返回给我们一个指针指向文件的信息。文件指针也可以
理解为代指打开的文件。这个指针的类型为FILE类型。该类型定义在stdio.h
头文件中。通过文件指针,我们就可以对文件进行各种操作。
	在VS中,文件指针的结构体如下所示:
struct _iobuf { 
        char  *_ptr;         //文件输入的下一个位置 
        int   _cnt;          //剩余多少字符未被读取
        char  *_base;        //指基础位置(应该是文件的其始位置) 
        int   _flag;         //文件标志 
        int   _file;         //文件的有效性验证 
        int   _charbuf;      //检查缓冲区状况,如果无缓冲区则不读取 
        int   _bufsiz;       //文件的大小 
        char  *_tmpfname;    //临时文件名 
}; 
typedef struct _iobuf FILE;
4.3 文件缓冲区
	在文件读取的过程中,其实都是通过文件缓冲区,来建立文件和操作
系统或者是操作系统和文件之间的互相传输内容,在开始读取的过程中,
都是先将读取的内容传输到流中,当流中内存占满时才进行下一步的输入或者
输出的操作这个过程既可以提高读取的速度,同时也可以提升我们硬盘的寿
命,因为每一次的读取过程都会减少硬盘的寿命,这样可以大大提升硬盘的
寿命。
	文件操作完成后,如果程序没有结束,必须要用fclose()函数进行关闭,
这是因为对打开的文件进行写入时,若文件缓冲区的空间未被写入的内容填
满,这些内容不会写到打开的文件中。只有对打开的文件进行关闭操作时,
停留在文件缓冲区的内容才能写到该文件中去,从而使文件完整。再者一旦
关闭了文件,该文件对应的FILE结构将被释放,从而使关闭的文件得到保护
,因为这时对该文件的存取操作将不会进行。文件的关闭也意味着释放了该
文件的缓冲区。
4.4 文件的读写
	4.4.1 文件读写的几种不同方式:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//1、字符的读写回顾 fgetc(), fputc()
void test01()
{
	//写文件
	FILE * f_write = fopen("./test1.txt", "w");

	if (f_write == NULL)
	{
		return;
	}


	char buf[] = "hello world";

	for (int i = 0; i < strlen(buf);i++)
	{
		fputc(buf[i], f_write);
	}

	fclose(f_write);

	//读文件
	FILE * f_read = fopen("./test1.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);
}



//2、按行进行读写
void test02()
{
	//写文件
	FILE * f_write = fopen("./test2.txt", "w+");

	if (f_write == NULL)
	{
		return;
	}

	char * buf[]=
	{
		"锄禾日当午\n",
		"汗滴禾下土\n",
		"谁知盘中餐\n",
		"粒粒皆辛苦\n"
	};

	for (int i = 0; i < 4;i++)
	{
		fputs(buf[i], f_write);
	}

	fclose(f_write);


	//读文件
	FILE * f_read = fopen("./test2.txt", "r");
	if (f_read == NULL)
	{
		return;
	}
	while ( !feof(f_read))
	{
		char temp[1024] = { 0 };

		fgets(temp, 1024, f_read);

		printf("%s", temp);

	}

	fclose(f_read);

}


//3、按块进行读写

struct Hero
{
	char name[64];
	int age;
};
void test03()
{
	//写文件
	FILE * f_wirte = fopen("./test3.txt", "wb"); 
	if (f_wirte == NULL)
	{
		return;
	}

	struct Hero heros[] = 
	{
		{ "孙悟空", 999 },
		{ "亚瑟", 20 },
		{ "曹操", 80 },
		{ "鲁班", 5 },
	};

	for (int i = 0; i < 4;i++)
	{
		//参数1 数据地址   参数2  块大小   参数3  块个数   参数4  文件指针
		fwrite(&heros[i], sizeof(struct Hero), 1, f_wirte);
	}

	fclose(f_wirte);


	//读文件
	FILE * f_read = fopen("./test3.txt", "rb");

	if (f_read == NULL)
	{
		return;
	}

	struct Hero temp[4];
	fread(&temp, sizeof(struct Hero), 4, f_read);
	for (int i = 0; i < 4;i++)
	{
		printf("姓名: %s  年龄:%d\n", temp[i].name, temp[i].age);
	}

	fclose(f_read);

}

//4、格式化读写回顾
void test04()
{
	
	//写文件
	FILE *f_write = fopen("./test4.txt", "w");
	if (f_write == NULL)
	{
		return;
	}

	fprintf(f_write, "hello world %s", "abcd");

	fclose(f_write);


	//读文件
	FILE * f_read = fopen("./test4.txt", "r");
	if (f_read == NULL)
	{
		return;
	}
	char temp[1204] = { 0 };
	while (! feof(f_read ))
	{
		fscanf(f_read, "%s", temp);
		printf("%s\n", temp);
	}

	fclose(f_read);
}

void test05()
{
	//写文件
	FILE * f_wirte = fopen("./test5.txt", "wb");
	if (f_wirte == NULL)
	{
		return;
	}

	struct Hero heros[] =
	{
		{ "孙悟空", 999 },
		{ "亚瑟", 20 },
		{ "曹操", 80 },
		{ "鲁班", 5 },
	};

	for (int i = 0; i < 4; i++)
	{
		//参数1 数据地址   参数2  块大小   参数3  块个数   参数4  文件指针
		fwrite(&heros[i], sizeof(struct Hero), 1, f_wirte);
	}

	fclose(f_wirte);


	//读文件
	FILE * f_read = fopen("./test51.txt", "rb");
	if (f_read == NULL)
	{
		//error 宏
		//printf("文件加载失败\n");
		perror("文件加载失败"); //用户提示信息 + 系统提示信息

		return;
	}
	struct Hero tempHero;
	//移动光标
	// 参数1  文件指针   参数2 偏移大小    参数3  起始位置   
	// SEEK_SET 从开始  SEEK_END 从结尾   SEEK_CUR 从当前位置
	//fseek(f_read, sizeof(struct Hero) * 2, SEEK_SET);

	fseek(f_read, - (long)sizeof(struct Hero) * 2, SEEK_END);

	rewind(f_read); //将文件光标置首

	fread(&tempHero, sizeof(struct Hero), 1, f_read);

	printf(" 姓名: %s , 年龄: %d\n", tempHero.name, tempHero.age);


	fclose(f_read);
}


int main(){
	//test01();
	//test02();
	//test03();
	//test04();
	test05();


	system("pause");
	return EXIT_SUCCESS;
4.4.2 文件读写的注意事项
	1.在进行单个字符(例如:getc、putc等函数的调用的时候)为什么不在
读取单个字符的时候使用feof来进行判断是否到文件末尾呢?
	是因为fetc会使光标移动至下一个位置,因此会移动到EOF位置,并将
EOF打印出来后再进行判断。

	解决办法如下所示的if代码
void test01()
{
	//读文件
	FILE* fp1 = fopen("./test01.txt", "r+");

	if (NULL == fp1)
		return;

	char ch;

	//while ((ch = fgetc(fp1))!=EOF)
	//{
	//	printf("%c", ch);
	//}
	while(!feof(fp1))
	{
		//为什么不在读取单个字符的时候使用feof判断呢
		//是因为fetc会使光标移动至下一个位置,因此会移动到EOF位置,并将EOF打印出来后再进行判断
		//解决办法如下所示的if代码
		ch = fgetc(fp1);
		if (ch == EOF)
			break;
		printf("%c", ch);
	}

	fclose(fp1);
}
	2.如果结构体中的有一个变量是指针类型,要将数据创建在堆区;也
就是说不要将这个指针变量存储在文件中。

4.5 配置文件
	两种版本,一种是自己写的一种是网课上的,对比差距在代码易读性以及
对于结构体的熟练适应程度上有很大的差距 。
	1.自己写的版本
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int getFilelines()
{
	FILE* fp = fopen("test07.txt", "r");
	if (fp == NULL)
		return;

	int lines = 0;
	char buffer[1024] = { 0 };
	while (!feof(fp))
	{
		memset(buffer, 0, 1024);
		fgets(buffer, 1024, fp);
		if (buffer[0] != '#' && strstr(buffer, ":") != 0)
			lines++;
	}
	return lines;
}

void getFilevalue(char** p)
{
	FILE* fp = fopen("test07.txt", "r");
	if (fp == NULL)
		return;

	char buffer[1024] = { 0 };
	int index = 0;
	while (!feof(fp))
	{
		memset(buffer, 0, 1024);
		fgets(buffer, 1024, fp);
		if (buffer[0] != '#' && strstr(buffer,":") != 0)
		{
			p[index] = malloc(sizeof(char) * 64);
			memset(p[index], 0, 64);
			for (int i = 0; i < strlen(buffer); i++)
			{
				p[index][i] = buffer[i];
			}
			index++;
		}
	}
}

void findfileValue(char** p, int a)
{
	printf("请输入您要查询的值:\n");
	char inputArr[64] = { 0 };
	scanf("%s", inputArr);

	//根据键值来匹配相应的内容
	int i = 0;
	while (i < a)
	{
		if (strncmp(p[i], inputArr, strlen(inputArr)) == 0)
		{
			strtok(p[i], ":");
			printf("%s ", strtok(NULL, ":"));
			break;
		}
		++i;
	}
	if (i == a)
		printf("无输入指定内容!");
}

void freeSpace(char** p,int a)
{
	for (int i = 0; i < a; i++)
	{
		if (p[i] != NULL)
		{
			free(p[i]);
			p[i] = NULL;
		}
	}
	free(p);
	p = NULL;
}

void test01()
{
	//获取行数
	int a = getFilelines();

	//将内容保存到数组中
	char** p = malloc(sizeof(char*) * a);
	getFilevalue(p);

	//用户查询
	findfileValue(p,a);
	
	//释放开辟的空间
	freeSpace(p, a);
}

int main()
{	
	test01();
	return 0;
}
	2.别人的版本
#pragma  once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct ConfigInfo
{
	char key[64]; //索引值
	char value[64]; //实值
};


//获取有效行数
int getFileLine( char * fileName );

//判断当前行是否有效
int isValidLine(char * str);

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

//根据索引值 获取 实值 
char * getInfoByKey(char * key, struct ConfigInfo * configInfo, int line);

//释放信息
void freeSpace(struct ConfigInfo * configInfo);
struct Person
{
	char a;
	int b;
};

void test01()
{
	char * filePath = "./config.txt";

	int line = getFileLine(filePath);

	printf("文件的有效行数为:%d\n", line);

	struct ConfigInfo * pArray = NULL;

	parseFile(filePath, line, &pArray);

	//测试 根据key 访问value
	printf("heroId = %s\n", getInfoByKey("heroId", pArray, line));
	printf("heroName = %s\n", getInfoByKey("heroName", pArray, line));
	printf("heroAtk = %s\n", getInfoByKey("heroAtk", pArray, line));
	printf("heroDef = %s\n", getInfoByKey("heroDef", pArray, line));
	printf("heroInfo = %s\n", getInfoByKey("heroInfo", pArray, line));


	//释放内存
	freeSpace(pArray);
	pArray = NULL;

int main(){

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

//获取有效行数
int getFileLine(char * fileName)
{
	FILE * file = fopen(fileName, "r");
	if (file == NULL)
	{
		return -1 ;
	}

	char buf[1024] = { 0 };
	int lines = 0;
	while (fgets(buf, 1024, file) != NULL)
	{
		//如果是有效行 才统计
		if (isValidLine(buf))
		{
			lines++;
		}
		
	}
	fclose(file);

	return lines;
}

//判断当前行是否有效
int isValidLine(char * str)
{
	if (str[0] == ' ' || str[0] == '\0' ||  strchr(str,':') == NULL)
	{
		return 0; //无效数据 都返回假
	}
	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");

	char buf[1024] = { 0 };
	int index = 0;
	while ( fgets(buf,1024,file ) != NULL)
	{
		//解析数据  有效数据才解析
		// heroName:aaaa\n
		if (isValidLine(buf))
		{
			memset(info[index].key, 0, 64);
			memset(info[index].value, 0, 64);

			char * pos = strchr(buf, ':'); //pos代表冒号所在位置

			strncpy(info[index].key, buf, pos - buf); //将key截取到 结构体中 最后-1的原因是不需要截取换行符
			strncpy(info[index].value, pos + 1, strlen(pos + 1) - 1);

			/*printf("key =  %s\n", info[index].key);
			printf("value =  %s", info[index].value);*/
			index++;

		}

		memset(buf, 0, 1024);

	}


	*configInfo = info;
}


//根据索引值 获取 实值 
char * getInfoByKey(char * key, struct ConfigInfo * configInfo, int line)
{
	for (int i = 0; i < line;i++)
	{
		if ( strcmp (key, configInfo[i].key  ) == 0)
		{
			return configInfo[i].value;
		}
	}
	return NULL;
}


//释放信息
void freeSpace(struct ConfigInfo * configInfo)
{
	if (configInfo != NULL)
	{
		free(configInfo);
		configInfo = NULL;
	}

}
4.6 简单的加密解密文件
	简单的加密的过程就是将文件中的内容转化为单个字符,然后对单个字符
进行变化的过程,例如进行逻辑运算或者是加减运算,从而转换文件的具体
内容,达到加密的效果。
	简单的解密的过程则于上述恰好相反,对于加密过程中的加减运算一
般是进行逆运算,对于逻辑运算则是通过左移或者是右移的方式进行还原,
下面自己写的一个简单的加密解密过程:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void passwdFile()
{
	FILE* fp = fopen("passwdfile.txt", "r");
	FILE* tempfp = fopen("temppasswdfile.txt", "w");
	if (fp == NULL || tempfp == NULL)
	{
		perror("文件打开失败!");
		return;
	}

	char buffer[1024] = { 0 };
	while (!feof(fp))
	{
		memset(buffer, 0, 1024);
		fgets(buffer, 1024, fp);
		for (int i = 0; i < strlen(buffer); ++i)
		{
			buffer[i] += 1;
			buffer[i] ^= buffer[i];
		}

		fputs(buffer, tempfp);
	}

	fclose(fp);
	fclose(tempfp);
	
	remove("passwdfile.txt");
	rename("temppasswdfile.txt", "passwdfile.txt");
}

void openpasswdFile()
{
	FILE* fp = fopen("passwdfile.txt", "r");
	FILE* tempfp = fopen("temppasswdfile.txt", "w");

	char buffer[1024] = { 0 };
	while (!feof(fp))
	{
		memset(buffer, 0, 1024);
		fgets(buffer, 1024, fp);
		for (int i = 0; i < strlen(buffer); ++i)
		{
			buffer[i] -= 1;
		}
		fputs(buffer, tempfp);
	}

	fclose(fp);
	fclose(tempfp);


	remove("passwdfile.txt");
	rename("temppasswdfile.txt", "passwdfile.txt");
}

//文件加密(关于逻辑运算的)
// 将#(35)转为 short,即将字符对应的ASCII码转换成short型
// 0000 0000 0010 0011   << 4(左移)
// 0000 0010 0011 0000   
// 1000 0000 0000 0000    |(或运算,最高位置1,也就是符号位取负)
// 1000 0010 0011 0000  +(低位加任意 0000 ~ 1111 随机数,用rand())
// 1000 0010 0011 1010


//解密 
// 1000 0010 0011 1010  <<1 //左移一位
// 000  0010 0011 10100  >> 5//右移五位
// 0000 0000  0010 0011


void test01()
{
	//加秘文件
	passwdFile();
	//解密文件
	openpasswdFile();
}

int main()
{
	test01();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值