自定义类型详解
Posted 正义的伙伴啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义类型详解相关的知识,希望对你有一定的参考价值。
自定义类型种类:
- 结构体
- 枚举
- 联合
文章目录
结构体
结构体的定义
数组——一些类型相同的值的集合
结构体——一些值的集合(可以是不同的类型),这些值被称为成员变量。
结构体的声明
struct tag //结构体类型名
{
number - list; //成员列表
};
- 成员列表由自己定义,且不用初始化
- 结构体可以嵌套另一个结构体,如
struct c
{
char a;
int a;
}d;
struct p
{
struct c d;
char arr[20];
int a;
}a,b;
结构体类型变量的定义
1.先声明结构体类型,再定义该类型变量:
形式:
struct 结构体类型名
{
成员表列
};
struct 结构体类型名 变量名表列;
结构体变量初始化如图
#include<stdio.h>
struct c
{
char a;
int a;
};
struct c a = { 'w',5 }; //1
int main()
{
struct c b{'a',6 }; //2
return 0;
}
注意:
1 定义的是全局变量,而2是局部变量
2.在声明的同时定义变量:
形式:
struct 结构体类型名
{
成员表列
}变量名表列;
结构体变量初始化
#include<stdio.h>
struct c
{
char a;
int a;
}b;
int main()
{
b = { 'w' , 5 };
b.a = 'w';
return 0;
}
3.不指定类型名直接定义结构体类型变量:
形式:
struct
{
成员表列
}变量名表列;
实际上就是省略了结构体的标签(tag)
结构体变量初始化
#include<stdio.h>
struct
{
char b;
int a;
}n={'w',5};
int main()
{
n = { 'a',5 };
printf("%d", n.a);
return 0;
}
实际上这个结构体只能定义一次,原因是在接下的引用的过程中如果出现多个无名结构体,那调用的结构体是哪一个呢?
结构体的自引用
结构体包含一个类型是本身的结构体的类型
struct Node
{
char b;
struct Node next;
};
这种调用实际上是错误的,原因是调用了定义不完整的类型,实际上计算机也无法计算出Node的内存大小!
结构体包含一个指向自身类型的指针
struct Node
{
char b;
struct Node* next;
};
实际上与数据类型有关——链表
而我们知道链表的数据是由 数据域-指针域 组成
而这里的next是指向下一个数据的指针,指针的大小在32位操作平台上是固定的,所以结构体的内存大小也能计算出来。
结构体内存对齐
这个问题的实质是:计算结构体的大小
#include<stdio.h>
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
char c2;
int i;
};
int main()
{
printf("s1的大小位:%d\\n",sizeof(s1));
printf("s2的大小位:%d\\n", sizeof(s2));
return 0;
}
实际上s1和s2只是成员定义的顺序不同,但是导致了结构体内存大小的不同。
结构体的对齐规则1——(不含嵌套结构体)
- 结构体第一个成员放在结构体变量在内存中存储位置的偏移处开始。
- 从第二个成员往后的所有成员,都放在自身对齐数的整数倍的地址处。
对齐数=编译器默认的一个对齐数 与 该成员大小 的较小值
vs中默认对齐数为8 (其他编译器有自己的规则,这里以vs编译器为例)
- 结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
s1的内存分配:
结构体的对齐规则2——(含嵌套结构体)
实际上结构体也是一种数据类型,结构体嵌套结构体我们要搞清楚两点:
- 结构体的对齐数:结构体的对齐数为结构体内所有类型对齐数的最大值,所以要对齐到自己最大对齐数的整数倍处
- 结构体的大小:按照规则1计算
#include<stdio.h>
struct s1
{
char c1;
int i;
char c2;
};
struct s3
{
char a;
struct s1 s;
int b;
};
int main()
{
printf("%d\\n", sizeof(struct s1));
printf("%d\\n", sizeof(struct s3));
return 0;
}
由前面可知:s1的大小为 12——— 最大对齐数为 4(int)
为什么要内存对齐?
1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说: 结构体内存对齐是拿空间换取时间的做法
那再涉及结构体的时候我们可以人为的 将占用空间小的成员尽量集中在一起
例如:
struct s1
{
char c1;
int i;
char c2;
};
struct s2
{
char c1;
char c2;
int i;
};
这两种声明顺序不一样,造成s1和s2的内存不同,分别为:12-----8
修改默认参数
我们可以通过#pragma这个预处理指令
#include<stdio.h>
#pragma pack(2)//把默认对齐数改为2
struct S1
{
char c1;// 对齐数 1
int i;// 对齐数 2
char c2;// 对齐数 1
};
#pragma pack()
#pragma pack(1)//把默认对齐数改为1
struct S2
{
char c1;//对齐数 1
int i;//对齐数 1
char c2;//对齐数 1
};
#pragma pack()
int main()
{
printf("%d\\n", sizeof(struct S1));
printf("%d\\n", sizeof(struct S2));
return 0;
}
结构体传参
struct s
{
int data[1000];
int num;
};
struct s b = { {0},100 };
void print1(struct s b);
void print2(struct s* ps);
int main()
{
print1(b); //传结构体
print2(&b); //传地址
return 0;
}
print1 和 print2 分别是传结构体 和 传地址,我们再写代码时优选print2 因为如果结构体占的内存
很大,print1函数传结构体时需要压栈,会有时间和空间上的系统开销,这样会导致性能下降。传地址可以解决这一问题。
位段
结构体还有一种功能就是实现位段
什么是位段
- 位段的成员必须是int、signed int和unsigned int
- 位段的成员名后面必须有一个冒号 和 一个数字
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
A就是一个位段
位段的内存分配
printf("%d",sizeof(struct A));
- 位段的成员可以是int、 unsigned int、signed int 或 char(属于整型家族)类型
- 位段的空间上按照需要以4个字节(int)或者1个字节(char)的方式来开辟
- 位段涉及很多不确定因素,位段时不跨平台的,注意可移植的程序应该避免使用位段
#include<stdio.h>
struct A
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct A a = { 0 };
int main()
{
a.a = 10;
a.b = 12;
a.c = 3;
a.d = 4;
return 0;
}
我们由位段的声明就可以判断出需要三个字节,但是这三个字节里内存是怎么存放的,我们大胆猜测一下,比特位由高地址向低地址存放,读出来的八进制位应该是0x 62 03 04,在调试中打开内存监视,结果也验证了猜想。但是这vs2019编译器出来的结果,别的环境的结果就不得而知。
位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的
- 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出现问题。
- 位段中的成员在内存中从左向右还是从右向左分配尚未标准定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
枚举类型
枚举顾名思义就是一一举例,把可能的值一一列举
例如
一周有:周一、周二、、、、、
颜色有:蓝、黄、绿、、、、、
enum DAY//日期
{
MON,
TUES,
WED,
THUR,
FRI,
};
enum color//颜色
{
BLUE,
YELLOW,
GREEN
};
以上定义的 enum DAY、enum color都是枚举类型。{}中的内容是枚举类型的可能取值,也叫枚举常量
枚举常量既然是常量肯定是有值的,第一个枚举常量默认是从0开始,依次递增一,当然也可以在定义的时候赋初值:
enum color
{
BLUE=1,
YELLOW=2,
GREEN
};
枚举的注意事项:
枚举是一种数据类型,虽然枚举常量是常量,但是不能用常量给枚举类型赋值:
enum color b = BLUE;//正确,赋值号两边类型相同
b = 5;//不能把整型赋给枚举类型
枚举的优点
和#define相比,我们为什么要用枚举?
- 增加代码的可读性和可维护性
#define是在预编译阶段将代码发生替换,所以在检查代码的时候,不宜与发现错误
- 和#define定义的标识符比较枚举有类型检查,更加严谨
#define定义的标识符是没有类型的
- 防止了命名污染
- 使用方便,一次可以定义多个常量
联合体(共用体)
联合类型的定义
联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间
//联合体类型的声明
union un
{
char c;
int i;
};
//联合变量的定义
union un n;
//计算联合体变量的大小
printf("%d", sizeof(un));
联合体的特点
- 联合体的成员共用同一块内存空间,这样一个联合变量的大小,至少是最大成员的大小
- 因为共用同一块空间,改动一个类型的变量,内存发生改变顾所有变量的值都要改变。
#include<stdio.h>
union un
{
int i;
char c;
};
union un n;
int main()
{
printf("%p\\n", &(n.c));
printf("%p\\n", &(n.i));
n.i = 0x10223344;
n.c = 0x20;
printf("%x\\n", n.i);
}
从代码中可以看出union用的是同一块内存空间,地址都是相同的,所以下面改变n.c的之的时候n.i的值也随之发生改变。我们可以用联合体的特性来实现大小端的判断。
#include<stdio.h>
union un
{
int i;
char c;
};
union un n;
int main()
{
n.i = 1;
if (n.c == 0)
printf("大端存储");
else
printf("小段存储");
}
联合体大小计算
- 联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数(类型的字节数)的整数倍的时候,就要对齐到最大对齐数的整数倍
union un
{
int i;
char c;
};
根据int类型大小为4,char类型大小为1,所以联合体大小至少为4,4又是最大对齐数4的倍数所以联合体的大小为4.
#include<stdio.h>
union i
{
char b[5];
int i;
};
int main()
{
printf("%d", sizeof(union i));
}
这里b[5]的大小为5,i的大小为4顾联合体的最小大小为5,然而最大对齐数是int类型的4,最大成员5不是最大对齐数4的倍数,所以结果为8。
以上是关于自定义类型详解的主要内容,如果未能解决你的问题,请参考以下文章