Day21.C提高5
一、结构体嵌套二级指针
/*当创建结构体变量后,如果其中的成员为指针类型的话,就要将此变量开辟至堆空间,是因为在创建结构体后,程序在运行结束之后,对于结构体变量所占的内存空间就已经分配好了,分配的大小为4字节(也就是一个指针的)大小,如果不重新在堆空间中开辟内存的话,就会导致溢出,因此,一般情况下,看到使用指针类型的数据的时候,应该在堆空间中存储。*/
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Teacher
{
char* name;
char** student;
};
void test101()
{
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_Stu_%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("\t%s_Stu_%d\n", t1[i]->name,j+1);
}
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()
{
test101();
return 0;
}
二、结构体偏移量
//通过地址加减偏移量,来打印相应的值
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stddef.h>
struct A
{
char a1;
int a2;
};
void test201()
{
struct A a = { "b",20 };
//C 库宏 offsetof(type, member-designator) 会生成一个类型为 size_t 的整型常量,它是一个结构成员相对于结构开头的字节偏移量。
printf("A.a2:%d\n", *(int*)((char*)&a + offsetof(struct A, a2)));
printf("A.a2:%d\n", *((int*)&a + 1));
}
struct C
{
int a;
double b;
};
struct B
{
char a;
double b;
struct C c;
};
void test202()
{
struct B b = { 'a',20,30,3.14 };
int off1 = offsetof(struct B, c);
int off2 = offsetof(struct C, b);
printf("b.c.b:%f\n", *(double*)(((char*)&b + off1) + off2));
}
int main(void)
{
//test201();
test202();
system("pause");
return EXIT_SUCCESS;
}
三、结构体内存字节对齐
在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中
所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。
从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是
事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这
就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这
就是内存对齐。
举一个很简单的例子,例如我们在内存中存储数字,假如是按照我们预期
的顺序存储的方式进行存储,存储一个char 和一个int型的数据,假设计算机
在读取的时候是4字节读取,那么就会先读到char类型的数据,然后读取int型
数据的前三位,这是一次读取,然后再读取下一块内容,读取int型数据的最
后一位数据,最后如果要得到这个数据,计算机还要进行拼接操作,将之前
所读取到的三个字节和第二次读取到的一个字节拼接起来,才可以得到完整
的数字,而通过字节对齐的方式进行存储,虽然在空间上浪费,但是在时间
上却大大提高了效率,因此,这是一种牺牲空间换取时间的存储方式,提高
了存取数据的效率。
对齐的原则为以下几点:
1.第一个属性开始,从零开始计算偏移量
2.第二个属性要放在,该属性大小与对齐模数(#pargama pack(n))相比,二者较小的那个值的整数倍。
3.当所有属性计算完毕后,整体做二次偏移,将上有计算的结果(结构体整体的大小),扩充到 min(#pargama pack(n),这个结构体中最大的数据类型的大小)的整数倍上。
也就是说,其实对齐就是以结构体中最大的数据类型为基准,其余的变量的存储都是以这个最大的数据类型的空间为准,能存储的下就存储,存储不下就开辟一块新的最大的数据类型的空间进行存储。
四、文件
流的概念
流是一个动态的概念,可以将一个字节形象地比喻成一滴水,字节在设备、文件和程序之间的传输就是流,类似于水在管道中的传输,可以看出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。C语言中,I/O操作可以简单地看作是从程序移进或移出字节,这种搬运的过程便称为流(stream)。
文件指针
文件是由操作系统管理的单元。当我们想操作一个文件的时候,让操作系统帮我们打开文件,操作系统把我们指定要打开文件的信息保存起来,并且返回给我们一个指针指向文件的信息。文件指针也可以理解为代指打开的文件。这个指针的类型为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;
文件读写的注意事项
1.在进行单个字符(例如:getc、putc等函数的调用的时候)为什么不在读取单个字符的时候使用feof来进行判断是否到文件末尾呢?
编程过程中遇到此类问题,是feof的滞后性
解决办法如下所示的if代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test301()
{
FILE* f = fopen("./test.txt", "r");
if (NULL == f)
{
printf("打开文件失败!!");
return;
}
char ch;
while (!feof(f))
{
ch = fgetc(f);
//解决feof的滞后性
if (feof(f))
break;
printf("%c", ch);
}
//关闭文件
fclose(f);
f = NULL;
}
int main(void)
{
test301();
system("pause");
return EXIT_SUCCESS;
}
文件操作复习:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Hero
{
char name[64];
int age;
};
void test302()
{
//写文件
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);
}
void test303()
{
//写文件
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("./test5.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, sizeof(struct Hero) * 3, SEEK_SET);
rewind(f_read); //将文件光标置首
fread(&tempHero, sizeof(struct Hero), 1, f_read);
printf(" 姓名: %s , 年龄: %d\n", tempHero.name, tempHero.age);
fclose(f_read);
}
int main(void)
{
//test302();
test303();
system("pause");
return EXIT_SUCCESS;
}
五、配置文件读写(分文件编程)
配置文件读写.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"ConfigFile.h"
void test()
{
char** fileData = NULL;
int lines = 0;
struct ConfigInfo* info = NULL;
//加载配置文件
loadFile_ConfigFile("./config.ini", &fileData, &lines);
//解析配置文件
parseFile_ConfigFile(fileData, lines, &info);
char buf[64] = { 0 };
while (1)
{
printf("请输入要查询的配置信息(输入“comm=exit”退出):");
scanf("%s", buf);
if (strncmp(buf, "comm=exit",9) == 0)
{
break;
}
printf("%s:%s\n", buf, getInfo_ConfigFile(buf, info, lines));
getchar();
}
//释放配置文件信息
destroInfo_ConfigFile(info);
}
int main(void)
{
test();
system("pause");
return EXIT_SUCCESS;
}
ConfigFile.c
#include"ConfigFile.h"
//获取文件有效行数
int getLines_ConfigFile(FILE* file)
{
char buf[1024] = { 0 };
int lines = 0;
while (fgets(buf, 1024, file) != NULL)
{
if (!isValid_ConfigFile(buf))
{
continue;
}
memset(buf, 0, 1024);
++lines;
}
//把文件光标重置到文件开头
fseek(file, 0, SEEK_SET);
return lines;
}
//加载配置文件
void loadFile_ConfigFile(const char* filePath, char*** fileData, int* line)
{
FILE* file = fopen(filePath, "r");
if (!file)
{
printf("打开文件失败");
return;
}
int lines = getLines_ConfigFile(file);
//给每行数据开辟内存
char** temp = malloc(sizeof(char*) * lines);
char buf[1024] = { 0 };
int index = 0;
while (fgets(buf, 1024, file) != NULL)
{
//如果返回false,表示该行为无效行
if (!isValid_ConfigFile(buf))
{
continue;
}
temp[index] = malloc(strlen(buf) + 1);
strcpy(temp[index], buf);
++index;
memset(buf, 0, 1024);
}
fclose(file);
*fileData = temp;
*line = lines;
}
//解析配置文件
void parseFile_ConfigFile(char** fileData, int lines, struct ConfigInfo** info)
{
struct ConfigInfo* myinfo = malloc(sizeof(struct ConfigInfo) * lines);
memset(myinfo, 0, sizeof(struct ConfigInfo) * lines);
for (int i = 0; i < lines; ++i)
{
char* pos = strchr(fileData[i], ':');
strncpy(myinfo[i].key, fileData[i], pos - fileData[i]);
strncpy(myinfo[i].val, pos + 1, strlen(pos + 1) - 1);
//printf("key:%s val:%s\n", myinfo[i].key, myinfo[i].val);
}
//释放文件信息
for (int i = 0; i < lines; ++i)
{
if (fileData == NULL)
break;
if (fileData[i] != NULL)
{
free(fileData[i]);
fileData[i] = NULL;
}
}
free(fileData);
fileData = NULL;
*info = myinfo;
}
//获得指令配置信息
char* getInfo_ConfigFile(const char* key, struct ConfigInfo* info,int line)
{
for (int i = 0; i < line; ++i)
{
if (strcmp(key, info[i].key) == 0)
{
return info[i].val;
}
}
return NULL;
}
//释放配置文件信息
void destroInfo_ConfigFile(struct ConfigInfo* info)
{
if (NULL == info)
{
return;
}
free(info);
info = NULL;
}
//判断当前行是否有效
int isValid_ConfigFile(const char* buf)
{
if (buf[0] == '#' || buf[0] == '\n' || strchr(buf, ':') == NULL)
{
return 0;
}
return 1;
}
ConfigFile.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct ConfigInfo
{
char key[64];
char val[128];
};
//目的为了在C++中能够调用C写的函数
#ifdef __cplusplus
extern "C" {
#endif
//获取文件有效行数
int getLines_ConfigFile(FILE* file);
//加载配置文件
void loadFile_ConfigFile(const char* filePath, char*** fileData, int* line);
//解析配置文件
void parseFile_ConfigFile(char** fileData, int lines, struct ConfigInfo** info);
//获得指令配置信息
char* getInfo_ConfigFile(const char* key, struct ConfigInfo* info, int line);
//释放配置文件信息
void destroInfo_ConfigFile(struct ConfigInfo* info);
//判断当前行是否有效
int isValid_ConfigFile(const char* buf);
#ifdef __cplusplus
}
#endif