一.前言引入.
我们知道在C语言中有内置类型,如:整型,浮点型等。但是只有这些内置类 型还是不够的,假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要名字、年龄、学号、⾝⾼、体重等;描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问 题,增加了结构体,共用体等⾃定义的数据类型,让程序员可以⾃⼰创造适合的类型。今天我们来讲讲结构体。
二.结构体介绍.
 
 定义:结构体是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体。 
 
 
结构体的声明:
struct tag
{
 member-list;
}variable-list;例如:描述一个学生
struct Student
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[15];//学号
}s1;//s1为全局变量
int main()
{
	struct Student s2;//局部变量
	return 0;
}结构体特殊声明:
 
  在声明结构的时候,可以不完全的声明 
 
 
  
 //在声明结构的时候,可以不完全的声明,即匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}s1;请判断下面代码是否正确:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
//在声明结构的时候,可以不完全的声明,即匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}s1;
struct
{
	int a;
	char b;
	float c;
}s2[20], * p;
int main()
{
	p = &s1;
	return 0;
} 
  警告:  
 
 
  
  编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。  
 
 
  
  匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次 
 
 
  
 结构体变量的定义:
struct Point
{
	int x; int y;
}s1;//结构体变量的定义
struct Point s2;//结构体变量的定义
int main()
{
	struct Point s3;//结构体变量的定义
	return 0;
}结构体变量的初始化:
struct Stu
{
	char name[20];
	int age;
};
int main()
{
	struct Stu s1 = { "zhangsan",18 };///结构体变量初始化	
	return 0;
}镶嵌初始化:
struct Point
{
	int x;
	int y;
};
struct Stu
{
	char name[20];
	int age;
	struct Point s2;
};
int main()
{
	struct Stu s1 = { "zhangsan",18,{2,3} };///结构体变量镶嵌初始化	
	return 0;
}三.结构成员访问操作符.
3.1.结构体成员的直接访问
使⽤⽅式:结构体变量.成员名
 
  结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数. 
 
 
 struct Point
{
	int x;
	int y;
}s1 = {3,5};
int main()
{
	struct Point s2 = { 2,4 };
	printf("%d %d\n", s1.x, s1.y);
	printf("%d %d\n", s2.x, s2.y);
	return 0;
}3.2.结构体成员的间接访问
 
  使⽤⽅式:结构体指针->成员名  
 
 
 有时候我们得到的不是⼀个结构体变量,⽽是得到了⼀个指向结构体的指针.
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct Point
{
	int x;
	int y;
}s1 = { 3,5 };
int main()
{
	struct Point s2 = { 2,3 };
	struct Point* pi1 = &s1;
	struct Point* pi2 = &s2;
	printf("%d %d\n", pi1->x, pi1->y);
	printf("%d %d\n", pi2->x, pi2->y);
	pi1->x = 10;
	pi1->y = 20;
	printf("%d %d\n", pi1->x, pi1->y);
	return 0;
}综合使用结构体用例:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
struct Stu
{
	char name[10];
	int age;
};
void Print(struct Stu s2)
{
	printf("%s %d\n", s2.name, s2.age);
}
void Set(struct Stu* s3)
{
	s3->age = 15;
	strcpy(s3->name, "李四");
}
int main()
{
	struct Stu s1 = { "zhangsan",18 };
	Print(s1);
	Set(&s1);
	Print(s1);
	return 0;
}结果:

四.结构体的⾃引⽤.
五.结构体的内存对齐.
计算结构体的⼤⼩
例如:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
struct s1
{
	char a;
	int b;
	char c;
};
struct s2
{
	char a;
	char b;
	int c;
};
int main()
{
	printf("%zd\n", sizeof(struct s1));
	printf("%zd\n", sizeof(struct s2));
	return 0;
}如果我给你一份这样的代码,请问你认为结果如何呢?两者一样吗?
答案如下:

和你想的一样吗?
如果不一样,不妨来看看我的解读:
 
  结构体的对⻬规则:  
 
 
  
  1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处  
 
 
  
  2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。  
   VS 中默认的值为 8 ,Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩  
 
 
  
  3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的  
 
 
  
  整数倍。  
 
 
  
  4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构  
 
 
  
  体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍 
 
 
  
 例题1:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct s1
{
	//1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
	char a;//对齐偏移量为0位置处,char一个字节
	//其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
	//对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。  
	//对齐数VS 中默认的值为 8 
	char b;//对齐偏移量为1位置处,char一个字节,即 1 8 1(第一个为成员变量的对齐数,第二个为VS对齐数,第三个为该行对齐数)
	int c;//对齐偏移量为4位置处,int四个字节,即 4 8 4
};	
int main()
{
	//结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍,即1 1 4 8 ->8
	printf("%zd\n", sizeof(struct s1));//结果为8
	return 0;
}结果为:

例题二:
struct s2
{
	//1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处
	char a;//对齐偏移量为0位置处,char一个字节
	//其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
	//对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。  
	//对齐数VS 中默认的值为 8 
	int b;//对齐偏移量为4位置处,四个字节,int: 4 8 4(第一个为成员变量的对齐数,第二个为VS对齐数,第三个为该行对齐数)
	char c;//对齐偏移量为9位置处,char 1个字节;1 8 1
	
};
int main()
{
	//结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍,即1 4 1  ->4的整数倍为12(原来的数据已经达到偏移量为9了)
	printf("%zd\n", sizeof(struct s2));//结果为12
	return 0;
}结果:

例题三:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct s3
{
	//下面简写
	//前三个为成员大小 VS大小 该行大小,偏移量的位置
	double d;//8 8 8,0-7
	char c;//1 8 1,8
	int i;//4 8 4.12-15
};
int main()
{
	printf("%zd\n", sizeof(struct s3));//结果8 1 4->8 15->16
	return 0;
}结果:

例题四:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
练习4-结构体嵌套问题
struct s3
{
	//下面简写
	//前三个为成员大小 VS大小 该行大小,偏移量的位置
	double d;
	char c;
	int i;
};
struct s4
{
	char c1;//1 8 1,0
	//4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍
	struct s3 a;//16 8 8,8-23
	double d;//8 8 8,24-31
};
int main()
{
	printf("%zd\n", sizeof(struct s4));//1 16 8->16 31->32
	return 0;
}
 
  你是否想过这样一个问题,为什么存在内存对⻬呢? 
 
 
  
  平台原因 (移植原因):  
 
 
  
  不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。  
 
 
  
  性能原因:  
 
 
  
  数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。  
 
 
  
  总体来说:结构体的内存对⻬是拿空间来换取时间的做法。  
 
 
  
  那在设计结构体的时候,我们既要满⾜对⻬,⼜要节省空间, 
 
 
  
  重点:如何做到: 让占⽤空间⼩的成员尽量集中在⼀起!!! 
 
 
  
  重点:如何做到: 让占⽤空间⼩的成员尽量集中在⼀起!!! 
 
 
  
  重点:如何做到: 让占⽤空间⼩的成员尽量集中在⼀起!!! 
 
 
 说三遍!
相信你现在一定会开头的那题了!
那么我们可以自己设置对齐数吗?答案是当然可以啦!
#pragma 这个预处理指令,可以改变编译器的默认对⻬数#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#pragma pack(1)
练习4-结构体嵌套问题
struct s3
{
	//下面简写
	//前三个为成员大小 VS大小 该行大小,偏移量的位置
	double d;
	char c;
	int i;
};
struct s4
{
	char c1;//1 8 1,0
	//4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍
	struct s3 a;//16 8 8,8-23
	double d;//8 8 8,24-31
};
int main()
{
	printf("%zd\n", sizeof(struct s4));//1 16 8->16 31->32
	return 0;
}修改之后结果为:

#pragma pack()//取消设置的对⻬数,还原为默认这样,当结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对齐数。
六.结构体传参和对比
1.传数据
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct S
{
	int data[1000];
	int num;
};
void Print(struct S s1)
{
	printf("%d\n", s1.num);
}
int main()
{
	struct S s1 = { {1,2,3,4}, 1000 };
	Print(s1);
	return 0;
}结果:

2.传地址
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct S
{
	int data[1000];
	int num;
};
void Print(struct S* s1)
{
	printf("%d\n", s1->num);
}
int main()
{
	struct S s1 = { {1,2,3,4}, 1000 };
	Print(&s1);
	return 0;
}结果:

对比两者,你认为哪个好呢?
 
  函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。  
 
 
  
  如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下降。  
 
 
  
  结论:  
 
 
  
  结构体传参的时候,要传结构体的地址。 
 
 
  
  
 最后,学习进步!!!



![[UnityWebGL]修改webgl启动模板](https://img-blog.csdnimg.cn/direct/d4dee4bbb0484e2b8acacb6017e9eff4.png)

![[c++]——string类____详细初步了解string类的运用](https://img-blog.csdnimg.cn/direct/8df3f50ed94f420bbccac32d59528a18.png)













