文章可能比较长,如果想仔细了解结构体知识的话请大佬们耐心看完。

一、结构体基本使用

基本规则与使用方法:
1.正常结构体定义时不能赋初值;

struct Person
{
	char name[64];
	//int age = 50;//定义时不能赋初值,只有使用变量时才赋初值。
	int age;
};

2.使用typedef对结构体取别名;

typedef struct Person
{
	char name[64];
	int age;
}MyPerson;//MyPerson为struct Person类型的别名;

void test()
{
	MyPerson p = {"aaa",10};//可以直接用别名使用;
}

3.没有写时typedef是定义了一个结构体变量(与上面对比),可以对其直接使用;

struct Person
{
	char name[64];
	int age;
}MyPerson = {"小米",200};//定义了一个结构体变量,可以直接使用

void test()
{
	printf("姓名:%s 年龄:%d\n",MyPerson.name,MyPerson.age);
}

输出结果:
在这里插入图片描述
4.匿名类型下创建一个结构体变量,只有一个MyPerson3结构体变量可以使用,无法创建新的变量,也可对其直接使用;局限性强,基本不会使用。

struct
{
	char name[64];
	int age;
}MyPerson3 = {"小白",30};

void test()
{
	printf("姓名:%s 年龄:%d\n",MyPerson3.name,MyPerson3.age);
}

输出结果:
在这里插入图片描述
5.在栈区与堆区创建结构体变量;

void test()
{
	//在栈上创建结构体
	struct Person p1 = {"aaa",10};

	//在堆区创建结构体变量
	struct Person* p2 = (struct Person*)malloc(sizeof(struct Person));
	p2->age = 66;
	strcpy(p2->name,"bbb");
}

6.在栈上与堆上创建结构体变量数组;

typedef struct Person
{
	char name[64];
	int age;
}MyPerson;

void PrintArray(struct Person persons[],int len)
{
	for (int i=0; i<len; i++)
	{
		printf("姓名:%s  年龄:%d\n",persons[i].name,persons[i].age);
	}
}

void test()
{
	//在栈上创建结构体变量数组
	struct Person persons[] = 
	{
		{"aaa",10},
		{"bbb",20},
		{"ccc",30},
		{"ddd",40}
	};
	int len = sizeof(persons)/sizeof(struct Person);
	PrintArray(persons,len);

	//在堆区创建结构体变量数组
	struct Person* personArr = (struct Person*)malloc(sizeof(struct Person)*4);
	for (int i=0; i<4; i++)
	{
		sprintf(personArr[i].name,"name_%d",i);
		personArr[i].age = i+18;
	}
	PrintArray(personArr,4);

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

打印结果:输出成功。
在这里插入图片描述

二、结构体常见赋值问题及解决

结构体常见赋值问题以及解决:
1.结构体变量赋值问题:在栈上开辟内存 : 将p2赋值给了p1

struct Person
{
	char name[64];
	int age;
};

void test()
{
	struct Person p1 = {"Tom",20};
	struct Person p2 = {"Jerry",18};

	p1 = p2;//将p2赋值给p1

	printf("p1的姓名:%s p1的年龄:%d\n",p1.name,p1.age);
	printf("p2的姓名:%s p2的年龄:%d\n",p2.name,p2.age);
}

那么系统做了什么操作呢?
系统提供了浅拷贝操作,将p2每个字节拷贝到p1上,拷贝成功。

在这里插入图片描述
输出结果:赋值成功;
在这里插入图片描述
2.结构体变量赋值问题:在堆区开辟内存(浅拷贝出现的问题) :
若将上面代码改为如下代码:

struct Person2
{
	char* name;
	int age;
};

void test2()
{
	struct Person2 p1;
	p1.age = 18;
	p1.name = (char*)malloc(sizeof(char)*64);
	strcpy(p1.name,"Tom");

	struct Person2 p2;
	p2.age = 20;
	p2.name = (char*)malloc(sizeof(char)*128);
	strcpy(p2.name,"Jerry");

	printf("p1的姓名:%s p1的年龄:%d\n",p1.name,p1.age);
	printf("p2的姓名:%s p2的年龄:%d\n",p2.name,p2.age);
}

先打印一下,打印成功。
在这里插入图片描述
此时,我们再将p2赋值给p1。

p1 = p2;//p2赋值给p1

再打印一下:还是没问题的
在这里插入图片描述
但是我们此时是在堆区开辟的内存,需要手动释放(堆区才会出现的问题,栈区不会出现该问题),我们执行如下代码后。

	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	if (p2.name != NULL)
	{
		free(p2.name);
		p2.name = NULL;
	}

程序崩溃!未响应,关了半天才关掉!
在这里插入图片描述
为什么会发生这样的结果呢?我们来研究一下:
在这里插入图片描述
那么如何解决它呢?
C语言里面解决它的方案:手动做一个赋值操作。开辟新的空间,最后释放。
在这里插入图片描述
代码操作:先将p1释放干净,重新手动开辟一块新的空间,手动使用strcpy赋值,最后再释放。

    struct Person2
{
	char* name;
	int age;
};

void test2()
{
	struct Person2 p1;
	p1.age = 18;
	p1.name = (char*)malloc(sizeof(char)*64);
	strcpy(p1.name,"Tom");

	struct Person2 p2;
	p2.age = 20;
	p2.name = (char*)malloc(sizeof(char)*128);
	strcpy(p2.name,"Jerry");

	//p1 = p2;//p2赋值给p1
	//自己提供一个赋值操作
	//先释放原有的内容
	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}
	p1.name = (char*)malloc(strlen(p2.name)+1);
	strcpy(p1.name,p2.name);
	p1.age = p2.age;

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

	if (p1.name != NULL)
	{
		free(p1.name);
		p1.name = NULL;
	}

	if (p2.name != NULL)
	{
		free(p2.name);
		p2.name = NULL;
	}
}

输出一下:打印成功,也没有崩溃,堆区数据也释放干净。
在这里插入图片描述

三、结构体偏移量

我们已知一个结构体:如何计算结构体中某个变量它的偏移量呢?
方式一:口算 如下,我们明白内存对齐的方式,会自动补齐;因此a补齐为0-3,b的偏移量就是4了。

struct Person
{
	char a;//0~3
	int b;//4~7
};

方式二:利用offsetof宏
offsetof 会生成一个类型为 size_t 的整型常量,其功能是一个结构成员相对于结构开头的字节偏移量。

宏声明:offsetof(type, member-designator)
参数:
type:这是一个class类型,其中,member-designator是一个有效的成员指示器。
member-designator:这是一个class类型的成员指示器。
返回值:该宏返回类型为 size_t 的值,表示 type 中成员的偏移量。

代码实例:

struct Person
{
	char a;//0~3
	int b;//4~7
};

void test()
{
	struct Person p1;
	struct Person* p = &p1;
	printf("b的偏移量为:%d\n",offsetof(struct Person,b));
}

可以直接计算出来偏移量:4
在这里插入图片描述
方式三:利用地址计算 : 利用b的地址减去a的地址

printf("b的偏移量为:%d\n",(int)&(p->b)-(int)p);//即b的地址减去a的地址

偏移量也为:4
在这里插入图片描述
实际案例1:通过偏移量获取结构体里面的数据

struct Person
{
	char a;
	int b;
};

void test2()
{
	struct Person p1 = {'a',10};
	printf("p.b = %d\n",*(int*)((char*)&p1 + offsetof(struct Person,b)));
	printf("p.b = %d\n",*(int*)((int*)&p1 + 1));
}

输出结果:
在这里插入图片描述
实际案例2:通过偏移量找到结构体中嵌套结构体的数据 结构体中嵌套结构体本质是将其展开。

struct Person
{
	char a;
	int b;
};

struct Person2
{
	char a;
	int b;
	struct Person c;//相当于将上面的结构体展开
};

void test()
{
	struct Person2 p = {'a',10,'c',20};

	int offset1 = offsetof(struct Person2,c);
	int offset2 = offsetof(struct Person,b);
	printf("方式1访问偏移量为:%d\n",*(int*)((char*)&p+offset1+offset2));//方式1
	printf("方式2访问偏移量为:%d\n",((struct Person*)((char*)&p+offset1))->b);//方式2
}

打印结果:成功找到
在这里插入图片描述

四、内存对齐问题

为什么需要内存对齐?
       内存的最小单元是一个字节,当CPU从内存中读取数据的时候,是一个一个字节读取。但是实际上CPU将内存当成多个块,每次从内存中读取一个块,这个块的大小可能是2、4、8、16等。内存对齐是操作系统为了提高访问内存的策略,操作系统再访问内存的时候,每次读取一定的长度(这个长度是操作系统默认的对齐数,或者默认对齐数的整数倍)。如果没有对齐,为了访问一个变量可能产生二次访问。内存对齐的优势:以空间换时间。
在这里插入图片描述
那么如何进行内存对齐?
1.对于标准数据类型,它的地址只要是它的长度的整数倍。
2.对于非标准数据类型,如结构体,要遵循以下对齐原则:
①从第一个属性开始,偏移为0;
②从第二个属性开始,地址要放在该类型整数倍与对齐模数(系统中默认为8)比取小的值的整数倍上。
③所有的属性都计算完成后,整体在做二次对齐,整体需要放在属性中最大类型与对齐模数比取效地整数倍上。
如何查看对齐模数:输入下列代码,重新生成下;对齐模数可以改为2^n;

#pragma pack(show)//查看对齐模数,输出该代码,不要运行,重新生成以下就好了

可以看到编译器最下面:
在这里插入图片描述
实例1:计算结构体大小

typedef struct _STUDENT
{
	int a;//0-3
	char b;//4-7
	double c;//8-15
	float d;//16-24
}Student;

void test()
{
	printf("sizeof = %d\n",sizeof(Student));
}

输出结果:根据内存对齐为24
在这里插入图片描述
实例2:修改对齐模数为1(相当于没有内存对齐,挨个相加)后计算

#pragma pack(1)
typedef struct _STUDENT
{
	int a;//0-3
	char b;//4
	double c;//5-12
	float d;//13-16
}Student;

void test()
{
	printf("sizeof = %d\n",sizeof(Student));
}

输出结果:修改对齐模数为1,挨个数据类型相加即为结果。
在这里插入图片描述
实例3:结构体嵌套结构体时,只需要看子结构体中最大数据类型就可以了。

typedef struct _STUDENT
{
	int a;
	char b;
	double c;
	float d;//总大小为24
}Student;

typedef struct _STUDENT2
{
	char a;//0-7
	Student b;//8-31
	double c;//32-39
}Student2;

void test()
{
	printf("sizeof = %d\n",sizeof(Student2));
}

输出结果:总大小为40字节
在这里插入图片描述

五、结构体与一级指针嵌套

我们直接看一个例子来使用它:大致在堆区创建结构如下。需求为打印出来这里面所有人的年龄和姓名。
大致流程如下:
①设计一个结构体struct Person{char* name,int age}
②在堆区创建结构体指针数组malloc(sizeof(struct Person*)*3);
③给每个结构体也分配到堆区
④给每个结构体的姓名分配到堆区
⑤打印数组中所有人的信息
⑥释放堆区数据。
在这里插入图片描述
实现代码:

struct Person
{
	char* name;
	int age;
};

void printArray(struct Person **pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		printf("姓名:%s  年龄:%d\n",pArray[i]->name,pArray[i]->age);
	}
}

void freeSpace(struct Person** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		if (pArray[i]->name != NULL)//释放属性
		{
			printf("%s被释放了\n",pArray[i]->name);
			free(pArray[i]->name);
			pArray[i]->name = NULL;
		}

		free(pArray[i]);//释放结构体
		pArray[i] = NULL;
	}
	free(pArray);//释放数组
	pArray = NULL;
}

struct Person** allocateSpace()
{
	struct Person** pArray = (struct Person**)malloc(sizeof(struct Person*)*3);//分配空间
	for (int i=0; i<3; i++)
	{
		//给每个结构体开辟内存
		pArray[i] = (struct Person*)malloc(sizeof(struct Person));

		//给每个结构体的姓名开辟内存
		pArray[i]->name = (char*)malloc(sizeof(char)*64);
		sprintf(pArray[i]->name,"name_%d",i+1);
		pArray[i]->age = i+20;
	}
	return pArray;
}

void test()
{
	struct Person** pArray = NULL;//利用被调函数分配内存

	pArray = allocateSpace();
	printArray(pArray,3);
	freeSpace(pArray,3);
	pArray = NULL;
}

输出结果:成功实现结构体与一级指针嵌套使用。
在这里插入图片描述

六、结构体与二级指针嵌套

我们直接看一个例子来使用它:
在这里插入图片描述
代码实现:

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

void allocateSpace(struct Teacher*** teachers)//分配内存
{
	struct Teacher** pArray = (struct Teacher**)malloc(sizeof(struct Teacher*)*3);

	for (int i=0; i<3; i++)
	{
		//给每个老师分配空间
		pArray[i] = (struct Teacher*)malloc(sizeof(struct Teacher));

		//给每个老师姓名分配空间
		pArray[i]->name = (char*)malloc(sizeof(char)*64);
		sprintf(pArray[i]->name,"Teacher_%d",i+1);

		//给老师带的学生数组分配空间
		pArray[i]->Students = (char**)malloc(sizeof(char*)*4);//学生数组
		//给4个学生再分配内存再赋值
		for (int j=0; j<4; j++)
		{
			pArray[i]->Students[j] = (char*)malloc(sizeof(char)*64);
			sprintf(pArray[i]->Students[j],"%s_Student_%d",pArray[i]->name,j+1);
		}
	}
	*teachers = pArray;
}

void ShowArray(struct Teacher** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		printf("%s\n",pArray[i]->name);
		for (int j=0; j<4; j++)
		{
			printf("     %s\n",pArray[i]->Students[j]);
		}
	}
}

void freeSpace(struct Teacher** pArray,int len)
{
	for (int i=0; i<len; i++)
	{
		if (pArray[i]->name != NULL)//释放老师的姓名
		{
			free(pArray[i]->name);
			pArray[i]->name = NULL;
		}
		
		for (int j=0; j<4; j++)//释放每个学生
		{
			if (pArray[i]->Students[j] != NULL)
			{
				free(pArray[i]->Students[j]);
				pArray[i]->Students[j] = NULL;
			}
		}
		//释放学生数组
		free(pArray[i]->Students);
		pArray[i]->Students = NULL;

		//释放老师
		free(pArray[i]);
		pArray[i] = NULL;
	}
	//释放老师数组
	free(pArray);
	pArray = NULL;
}

void test()
{
	struct Teacher** pArray = NULL;
	allocateSpace(&pArray);//分配内存
	ShowArray(pArray,3);//打印数组
	freeSpace(pArray,3);//释放
	pArray = NULL;
}

打印结果:成功实现结构体与一级指针嵌套使用。
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐