自定类型结构体(6000字全解)

自定类型结构体主要包括以下三类:

  1. 结构体
  2. 枚举
  3. 联合

自定类型结构体(6000字全解)_第1张图片

结构

结构是一些值的集合,这些值被称为成员变量。结构的每个成员可以是不同类型的变量。
区别于数组,数组是一组相同类型变量的集合。

结构的声明

自定类型结构体(6000字全解)_第2张图片
以下是两种常规的声明方法

struct student
{
	char name[20];
	int age;
	char sex[5];//“保密”+“\0”最多占5个字节,一个汉字占两个字节
	float score;
}s1,s2,s3;//s1,s2,s3是三个结构体变量--->是全局变量
int main()
{
  struct student s4,s5,s6;//s4,s5,s6是三个结构体变量------->是局部变量
	return 0;
}

特殊声明

还有一种特殊的声明方法----->匿名声明

struct 
{
	char name[20];
	int age;
	char sex[5];
	float score;
}b1,b2;//匿名结构体只能在这里创建变量
int main()
{
	return 0;
}

结构的自引用

在结构体内包含一个类型为该结构本身的成员是否可以呢?
举例:表示链表

struct note
{
  int date;//数据
  struct note n;//下一个节点
}

这个代码并不正确,无法计算sizeof(struct note)
可以把下一个节点改成下一个节点的指针,最后一个节点存为NULL即可
正确的自引用

struct note
{
  int date;//存放数据--->数据域
  struct note* n;//存放下一个节点的地址--->指针域
}
int main()
{
   printf("%d\n",sizeof(struct note));//16
  return 0;
}

注意:自引用不能匿名结构体

struct
{
  int date;//存放数据--->数据域
  struct note* n;//存放下一个节点的地址--->指针域
}//err

这样也不行

typedef struct 
{
  int date;//存放数据--->数据域
  struct note* n;//存放下一个节点的地址--->指针域
}note;//这里在创建note时就要使用note,所以也不行

结构体变量的定义和初始化

struct Point
{
 int x;
 int y;
}p1={1,2};//全局变量,定义变量并初始化
struct Point p3={4,5};//全局变量
int main()
{
struct Point p2;//-->局部变量
  return 0;
}

打印结构体成员默认按照顺序,也可以自己规定乱序,用变量名.

struct stu
{
  char name[15];//名字
  int age;
}int main()
{
   struct stu s  = {"zhangsan",20};
   struct stu s2 = {.age=18,.name="wangwu"};
   printf("%s %d\n",s.name,s.age);
   printf("%s %d\n",s2.name,s2.age);
  return 0;
}

嵌套结构体的打印方式

#include
struct Point
{
	int x;
	int y;
}p1 = {1,2};
struct Node
{
	int data;
	struct Point p;
	struct Node* next;
};

int main()
{
	struct Node n = { 100, {20, 21}, NULL };
	printf("%d x=%d y=%d\n", n.data, n.p.x, n.p.y);

	return 0;
}

结构体内存对齐(难,重要)

现在我们深入讨论一个问题:如何计算结构体的大小?

例子:

struct S1
{
  char c1;
  int i;
  char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};
int main()
{   
  printf("%d\n",sizeof(struct S1));//12  
  printf("%d\n",sizeof(struct S2));//8  
    return 0;
}

为什么成员的顺序会影响结构体的大小呢?
先说明一个宏:
offsetof是一个宏
可以直接使用
可以计算结构体成员相较于起始位置的偏移量

S1如下自定类型结构体(6000字全解)_第3张图片

自定类型结构体(6000字全解)_第4张图片

S2如下
自定类型结构体(6000字全解)_第5张图片

自定类型结构体(6000字全解)_第6张图片

对齐的规则:

自定类型结构体(6000字全解)_第7张图片

struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n",sizeof(struct S3));//16
return 0}
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n",sizeof(struct S4));//32
return 0}

S4在内存中存储如下自定类型结构体(6000字全解)_第8张图片

为什么存在内存对齐?

1.平台原因:不是所有硬件平台都能访问任意地址的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐,原因在于为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问
总体来说:
结构体的内存对齐是拿空间换取时间的做法

在32位机器上,读取数据一次拿取32位的数据
如果不考虑内存对齐的话
读取如下的结构体s中的 i 要读两次才可以读完
自定类型结构体(6000字全解)_第9张图片
考虑内存对齐,读取 i 只需要一次,效率更高
自定类型结构体(6000字全解)_第10张图片

注意:

在设计结构体时,既要满足内存对齐,又要节省空间,就要让占用空间小的成员尽量集中在一起。

修改结构体的默认对齐数

#pragma pack(想要设置的对齐数)

一般不设置奇数,设置2的次方的数

#pragma pack(1)
struct S2
{
 char c1;
 char c2;
 int i;
};//结构体大小6

结构体传参

通常有两种
1.传值
2.传地址
一般选用传地址,这样压栈空间小,效率更高
函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销过大,会导致性能下降。如下函数,首选print2函数。

struct S
{
	int data[1000];
	int num;
};

void print1(struct S t)
{
	printf("%d %d %d %d\n", t.data[0], t.data[1], t.data[2], t.num);
}

void print2(const struct S * ps)
{
	printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);
}

int main() 
{
	struct S s = { {1,2,3}, 100 };
	print1(s);//传值调用
	print2(&s);//传址调用

	return 0;
}

结论:结构体传参的时候,要传结构体的地址。

位段

位段的出现就是为了节省空间
结构体说完就要探讨一下结构体实现位段的能力

位段的声明和结构体的区别

  1. 位段的成员必须是int ,unsigned int ,char(整型家族)
  2. 位段的成员名后边有一个冒号和一个数字。
    位段的位是二进制的位
struct A
{
int _a:2;//表示_a占用2个bit位的空间,而int本来要32个bit位,所以这样可以节约空间
int _b:5;
int _c:10;
int _d:30;
};

位段的内存是怎样分配的?

struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};
int main()
{
printf("%d\n",sizeof(struct A));//占用8个字节,相较于结构体节省了一半的空间!
  return 0;
}

注意:

位段的空间上是按照四个字节(int)或者一个字节(char)的方式来开辟的
位段涉及很多不确定因素,位段是不跨平台的,注重可移植性的程序应该尽量避免使用位段 但是即使有再多的不确定性,我们也可以探究一下在VS上位段是如何使用的

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };

	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	printf("%d\n", sizeof(struct S));
	return 0;
}

假设上述代码存储的的方式是从低位向高位存储
存完a,b如下图,第一个字节内剩余空间不够存c,所以再开辟一个字节,假设第一个字节中剩余的空间不再利用,则c存储在新开辟的字节当中
自定类型结构体(6000字全解)_第11张图片

对于a而言,丢失了数据
a二进制是00001010,只存进了010
存储完如下:
自定类型结构体(6000字全解)_第12张图片
这是我们假设的情况,怎样验证呢?只需要换算成16进制,在内存窗口上观察即可
以四个bit位划分,计算的16进制为620304自定类型结构体(6000字全解)_第13张图片
结果如下,验证成功,说明我们假设成立

自定类型结构体(6000字全解)_第14张图片

位段的跨平台问题

int位段被当做有符号位或者无符号位是不确定的

位段中的最大位的数目是不确定的(16位的机器上最大是16,32位机器上最大是32,写成30,在16位机器上会出问题)

位段中的成员在内存中从左向右分配还是从右向左分配是不确定的

当一个结构中包含两个位段时,第二个位段成员比较大时,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这也是不确定的

总结

跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台问题存在

枚举

定义的变量只有有限个可能
把可能的取值一一列举就是枚举。
比如月份,性别等都可以枚举

枚举类型的定义

例一:

enum sex
{
 man;//枚举常量
 woman;
 secret;
 };
 int main()
 {
 printf("%d",man);//0
 printf("%d",woman);//1
 printf("%d",secret);//2
   return 0;
 }

1.枚举的默认取值是从0开始依次向下递增1的
2.也可以在声明变量的时候给变量赋值
3.枚举变量的大小相当于一个整型,占用4个字节
用枚举常量给变量赋值,才不会出现类型的差异

联合(共用体)

联合类型的定义

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)

//union是联合体的关键字
union Un
{
char c;
int i;
};
int main()
{
union Un un;
printf("%d\n",sizeof(un));//4
return 0;
}

联合体在内存中如何存储的?

自定类型结构体(6000字全解)_第15张图片
可以推测在内存中如下
自定类型结构体(6000字全解)_第16张图片

联合体特点

  1. 联合体的大小至少是最大成员的大小
    2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
    3 联合体的成员在同一时间内只能使用一个
    同时使用会相互影响

联合体大小计算实例

union Un
{
	char c[5];//5
	int i;//4
};
int main() 
{
	printf("%zd\n", sizeof(union Un));//8
//本来是5,对齐为4的倍数,结果是8
	return 0;
}
union Un
{
	short c[7];//14
	int i;//4
};

int main()
{
	printf("%zd\n", sizeof(union Un));//16
	//short类型占2个字节,7个元素就是14个字节,最大对齐数是int,4个字节,结果对齐到16

	return 0;
}

联合体应用——判断大小端

常规方法:利用char*指针访问一个字节判断

int check_sys()
{
	int a = 1;
	if (*(char*)&a == 1)//int*
		return 1;
	else
		return 0;
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

自定类型结构体(6000字全解)_第17张图片
使用联合体的方法

int check_sys()
{
	union
	{
		char c;
		int i;
	}u;

	u.i = 1;
	return u.c;//返回1表示小端,返回0表示大端
}
int main()
{
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

联合体使用场景

自定类型结构体(6000字全解)_第18张图片
像这样不同商品有公共属性,也有特殊属性时,可以考虑联合体
不使用联合体,会创建很多不用的变量,浪费内存空间
自定类型结构体(6000字全解)_第19张图片
改造成联合体可节省空间如下
自定类型结构体(6000字全解)_第20张图片

你可能感兴趣的:(C语言刷题,c语言,笔记,经验分享,其他)