自定义类型详解(上)

简介: 自定义类型详解(上)

本章重点

       C语言中除了有char,short,int,long,long long,double,foat这些内置类型,还有结构体,枚举,联合等这些自定义类型。

结构体:结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 结构体实现位段(位段的填充&可移植性)

枚举:枚举类型的定义 枚举的优点 枚举的使用

联合:联合类型的定义 联合的特点 联合大小的计算


一 结构体

1.1 结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

1.2 结构的声明

1. #include <stdio.h>
2. 
3. struct stu//struct是结构体关键字,stu是结构体标签
4. {
5.  //这里面放成员列表
6.  char name[10];
7.  char sex[5];
8.  int age;
9.  int height;
10. }s2,s3,s4;//这里的s2,s3,s4也是结构体变量,是一个全局变量
11. 
12. struct stu s5;//这里的s5是结构体变量,是一个全局变量
13. 
14. int main()
15. {
16.   struct stu s1 = { "xianming", "nan", 18, 43 };//这里的s1就是结构体变量,是一个局部变量
17.   return 0;
18. }

1.3 特殊的声明

匿名结构体类型

1. #include <stdio.h>
2. 
3. struct
4. {
5.  char c;
6.  int a;
7.  double d;
8. }e;//结构体变量必须在这里命名,而且只能使用一次
9. 
10. struct
11. {
12.   char c;
13.   int a;
14.   double d;
15. }* ps;
16. 
17. int main()
18. {
19.   ps = &e;//在这里是不能这样用的,会出现错误,即使结构体成员一模一样也不可以
20. //编译器认为等号两边是不同的结构体类型,所以这种写法是错误的
21.   return 0;
22. }

 编译器会把上面的两个结构体声明当成完全不同的两个类型。所以是非法的

省略结构体标签,编译器也会认为这两个结构体是不同的。(虽然省略结构体标签后,这两个结构体成员是一样的)

1.4 结构的自引用

下面为正确的自引用的方式:

1. struct Node
2. {
3.  int m;
4.  struct Node* next;
5. };
1. #include <stdio.h>
2. 
3. typedef struct Node//重命名这个结构体为Node
4. {
5.  int data;
6.  struct Node* next;
7. }Node;
8. //这种方法尽量不要使用,不建议
9. int main()
10. {
11.   struct Node s2 = { 0 };//这种写法是可以的
12.   Node s1 = { 0 };//这种写法也是可以的
13.   return 0;
14. }

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

定义:

1. struct stu    
2. {
3.  char name[20];
4.  int age;
5.  float score;
6. };
7. int main()
8. {
9.  struct stu s;
10.   return 0;
11. }
1. struct stu    
2. {
3.  char name[20];
4.  int age;
5. float score;
6. }s1, s2;//这里的s1 s2 和s一样,也是结构体变量,(全局的)
7. struct stu s3;//定义一个初始化变量 全局变量
8. int main()
9. {
10.   struct stu s;//s就是结构体变量,struct stu 就是和int char float 一样的类型,但是却是局部的
11.   return 0;
12. }

初始化:

1. struct stu    
2. {
3.  char name[20];
4.  int age;
5.  float score;
6. };
7. 
8. struct stu s2 = { "xiaohong", 20, 97.5f };
9. 
10. int main()
11. {
12.   struct stu s = { "xiaoming", 20, 97.5f };//97.5 后面加f说明是float类型,不加的话,会默认为为double类型
13.   printf("%s %d %f", s.name, s.age, s.score);
14.   return 0;
15. }
1. 结构体嵌套初始化
2. struct Node
3. {
4. int data;
5. struct Point p;
6. struct Node* next;
7. }n1 = {10, {4,5}, NULL}; 
8. 
9. struct Node n2 = {10, {4,5}, NULL};

1.6 结构体内存对齐

这个知识点比较重要

计算结构体的大小,这也是一个特别热门的考点: 结构体内存对齐

代码1展示:

1. #include <stdio.h>
2. struct S1
3. {
4. char c1;
5. int i;
6. char c2;
7. };
8. int main()
9. {
10.   printf("%d\n", sizeof(struct S1));
11.   return 0;
12. }

运行的结果是:12

1. #include <stdio.h>
2. #include <stddef.h>
3. 
4. struct S1
5. {
6. char c1;
7. int i;
8. char c2;
9. };
10. //offsetof(a,b)计算的是返回的是size_t,a代表结构体类型名,b代表结构体成员名,头文件是<stddef.h>
11. //偏移量,第一个位置的偏移量是0.
12. int main()
13. {
14.   printf("%d\n", sizeof(struct S1));
15.   printf("%d\n", offsetof(struct S1, c1));
16.   printf("%d\n", offsetof(struct S1, i));
17.   printf("%d\n", offsetof(struct S1, c2));
18.   return 0;
19. }

打印结果:12 0 4 8

c1是第一个成员,所以是0,第二个成员的对齐数是4,所以从4开始,因为int是4个字节,所以占了4个位置,然后c2的对齐数是1,然后8是1的倍数,所以是8.此时的大小是9(因为还有一个0处位置的大小),因为最终大小是最大对齐数的整数倍,所以是12个字节

对齐规则

1. 第一个成员在与结构体变量偏移量为0的地址处。

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。(比如该成员是int 4,与8相比,选择4,然后因为)        VS中默认对齐数的值为8,linux没有默认对齐数,所以对齐数就是成员自身本身的大小

3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,然后根据嵌套结构体的大小,占据相应的字节数, 结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

注意: 为了可以既要对齐,又要省空间 。可以让占用空间小的成员尽量集中在一起,以防浪费空间

(每一个成员都要确认一个对齐数,从0开始,总大小(注意加上0的大小)为最大对齐数的倍数)

为什么存在内存对齐?

大部分的参考资料都是如是说的:

1. 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特

定类型的数据,否则抛出硬件异常。

2. 性能原因:(32位平台,4个字节4个字节的读)

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访

问。

总体来说:

结构体的内存对齐是拿空间来换取时间的做法。

1.7 修改默认对齐数

#pragma 这个预处理指令,可以改变我们的默认对齐数

1. #include <stdio.h>
2. #pragma pack(2)  //设置默认对齐数为2
3. struct S1
4. {
5.  char c1;
6.  int i;
7.  char c2;
8. };
9. #pragma pack()   //取消设置的默认对齐数,还原为默认
10. 
11. int main()
12. {
13.   printf("%d\n", sizeof(struct S1));
14.   return 0;
15. }

打印结果:8

在不改变的情况下为,12.

结构在对齐方式不合适的时候,我么可以自己更改默认对齐数

1.8 结构体传参

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

1. #include <stdio.h>
2. struct S
3. {
4.  int data[1000];
5.  int num;
6. };
7. struct S s = { {1,2,3,4}, 1000 };
8. void print2(struct S* ps)
9. {
10.   printf("%d\n", ps->num);
11. }
12. int main()
13. {
14.   print2(&s); //传地址
15.   return 0;
16. }

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的

下降。

相关文章
|
6月前
|
存储 Linux C++
自定义类型讲解
自定义类型讲解
81 0
|
5月前
|
存储 编译器 Linux
自定义类型详解(1)
自定义类型详解(1)
41 5
|
5月前
自定义类型详解(2)
自定义类型详解(2)
42 1
|
6月前
|
存储 移动开发 API
其他内置类型
本文介绍了 .NET 中的 Console 类和 Environment 类。Console 类提供了控制台输入输出的功能,如设置背景色和前景色、打印文本、读取行和发出蜂鸣声。而 Environment 类则包含有关全局环境的信息和方法,如当前目录、进程路径、处理器数量、操作系统信息等。另外,文章还提及了 .NET Framework 的 AppDomain(用于表示应用程序域,但在 .NET Core 中功能减弱)和 .NET Core 中新引入的 AppContext 类,用于存储全局数据和开关。
|
6月前
|
编译器 Linux C++
自定义类型详解
自定义类型详解
|
6月前
|
编译器 C++
自定义类型
自定义类型
|
11月前
|
存储 算法 程序员
自定义类型总结
自定义类型总结
70 0
|
存储
自定义类型超详细解答!!!!!(下)
自定义类型超详细解答!!!!!
|
编译器 C++
自定义类型超详细解答!!!!!(上)
自定义类型超详细解答!!!!!
|
存储 程序员 编译器
【C】自定义类型详解
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。