浅谈自定义类型(枚举+结构体+联合)

内置类型:C语言自己的数据类型

自定义类型:枚举+结构体+联合

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

struct tag { 

    member-list;
}variable-list;//变量列表

//声明一个结构体类型

//声明一个学生类型,是想通过学生类型来创建学生对象或变量

//描述学生的属性:名字,电话,性别,年龄

struct stu//结构体标签

{

//结构体的成员变量

char name[20];//名字

char tele[12];//电话

char sex[10];//性别

int age;

}s4,s5,s6;//全局变量

struct stu s3;//全局变量

int main()

{

//创建结构体变量

struct stu s1;

struct stu s2;

return 0;

}

特殊的结构体声明类型

//匿名结构体类型 

struct

{

    int a;
    char b;
    float c;

}x; 

struct { 

    int a;
    char b;
    float c;

}a[20], *p; 

//在上面代码的基础上,下面的代码合法吗? p = &x; 

警告: 

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

匿名结构体类型用一次之后就不能使用了

struct 

{

int a;

char c;

}sa;//匿名结构体

struct 

{

int a;

char c;

}*psa;//匿名结构体指针

int main()

{

return 0;

}

结构体的自引用

错误

struct Node

{

int data;

struct Node n;

//结构体的定义不可以包含自己本身

};

typedef struct Node//将struct Node定义为Node,两种方式都可以写

{

int data;//4

struct Node* next;//4/8

}Node;

存放数据的地方叫数据域

存放指针的地方叫指针域

int main()

{

return 0;

}

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

struct t

{

double weight;

short age;

}

struct s

{

char c;

struct t st;

int a;

double d;

char arr[20];

}

int main()

{

struct s s={'c',100,3.14,"hello bit"};//结构体的初始化

printf("%c %d %lf %s\n",s.c,s.a,s.d,s.arr)//将结构体成员全部打印

struct s s={'c',{55.6,30},100,3.14,"hello bit"};

printf("%lf\n",s.st.weight);//结构体成员中可以套用结构体成员,用两个. .来访问

return 0;

}

结构体内存对齐

struct s1

{

char c1;

int a;//a 4/8=4找一个地址位对齐数的倍数

char c2;//1/8=1

}

struct s2

{

char c1;

char c2;

int a;

}

int main()

{

struct s1 s1={0};//第一个是0,剩下的全部都初始化为0

printf("%d\n",sizeof(s1));//12

struct s2 s2={0};

printf("%d\n",sizeof(s2));//8

return 0;

}

偏移量:中间差了多少个距离

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

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

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 

VS中默认的值为8 

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

4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整

    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

gcc编译器没有默认对齐数,成员的大小就是对齐数

//练习3 

struct S3 { 

    double d;
    char c;
    int i;

};
printf("%d\n", sizeof(struct S3));
输出结果为16

//练习4-结构体嵌套问题

struct S4

{

    char c1;
    struct S3 s3;
    double d;

};
printf("%d\n", sizeof(struct S4));

输出的大小为32

为什么存在内存对齐? 

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

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

  不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
 定类型的数据,否则抛出硬件异常。

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访 问。 

总体来说:

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

struct s

{

char c;//1

//7

int a;//8

};

struct s s;

//32位机器

32根地址线

32根数据线

右图中如果四个字节四个字节读取,上面要读取两次,下面只需要读取一次。

那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

 让占用空间小的成员尽量集中在一起。

修改默认对齐数

之前我们见过了#pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。 

#pragma pack(4)//设置默认对齐数为4

struct s

{

char c;//1

//3

int a;//8

};

struct s s;

#pragma pack(0)

//取消设置的默认对齐数

int main()

{

struct s s;

printf("%d\n",sizeof(s));

return 0;

}

百度笔试题: 

写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明

考察: offsetof 宏的实现 

#include <stddef.h>

struct s

{
       char c;//0偏移量

       int I;//4

      double d;//8
}l

int main()

{

offsetof();//计算结构体成员相对于结构体起始位置的偏移量

printff("%d\n",offsetof(struct s,c));

printff("%d\n",offsetof(struct s,i));

printff("%d\n",offsetof(struct s,d));

return 0;

}

结构体传参

struct s

{

int a;

char c;

double d;

};

void init(struct s*tmp)

{

tmp->a=100;

tmp->c='w';

tmp->d=3.14;

}

//传值

print1(struct s tmp)

{

printf("%d %c %lf\n",tmp.a,tmp.c,tmp.d);

}

//传址

void print2(const struct s *ps)

{

printf("%d %c %lf\n",ps->a,ps->c,ps->d);

}

int main()

{

struct s s;

init(&s);

print1(s);//如果s比较大就会把系统性能压榨比较大

print2(&s);//传递指针就不会特别大地压榨系统性能

return 0;

}

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

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

结论: 

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

每一次函数调用都要在内存的栈区上开辟一块空间

参数压栈

栈是一种数据结构

位段

位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是 int、unsigned int 或signed int 。//只要是整型就可以,位段的成员一般是相同的类型。

 2.位段的成员名后边有一个冒号和一个数字。

struct A { 

    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;

};

//位段式的结构体

#include<stdio.h>

//

//位段-二进制位

//

struct s

{

int a:2;//此处代表a只需要占2个比特位

int b:5;//此处代表b只需要占5个比特位

int c:10;//此处代表c只需要占10个比特位

int d:30;//此处代表d只需要占30个比特位

};

//开辟空间的方式为整型,一次开辟一个整型空间4个字节,32个比特位(int位段后面的数字不能大于32,否则在一个位段里面放不下)。

还剩15个比特位,不能够将d放下,将这块空间舍弃,重新开辟一块整型空间

c占了10个比特位

b占了5个比特位

a占了2个比特位

d在重新开辟的整型空间中占了30个比特位

所以一共需要2个整型空间,一共8个字节。

//位段存在就是为了节省空间,如果此处不适用位段就要使用4个整型空间,一共16个字节

int main()

{

struct s s;

printf("%d\n",sizeof(s));//输出结果为8(个字节)

return 0;

}

2.2 位段的内存分配 

1. 位段的成员可以是 int signed int 或者是 char (属于整形家族)类型 

2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

//一个例子 

struct S { 

    char a:3;
    char b:4;
    char c:5;
    char d:4;

};

假设该位段从右向左使用

0

1

0

0

0

1

0

0

0

0

1

1

0

1

0

0

转换为16进制

//22

//03

//04

int main()

{

struct S s = {0};//a,b,c,d中存放的全部都是0
s.a = 10;//1010但只能存下3个比特位的空间,只能将010的值存入
s.b = 20;//10100但只能存下4个比特位的空间,智能加工0100的值存入
s.c = 3;//011,但要放5个比特位,前面用0补齐
s.d = 4;//100,但要放4个比特位,前面补0

return 0;

}

//空间是如何开辟的? 

2.3 位段的跨平台问题 

1. int 位段被当成有符号数还是无符号数是不确定的。(最高位不知道是不是符号位)

2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

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

总结: 

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

2.4 位段的应用 

3. 枚举 

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。 

性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举 

3.1 枚举类型的定义 

enum Day//星期 

{ //枚举的可能取值

    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun

}; //花括号后面的";"不能省略

enum Sex//性别 

{

    MALE,
    FEMALE,
    SECRET

};

enum Color//颜色 

{

    RED,
    GREEN,
    BLUE

};

int main()

{

enum sex s =male;

enum color c =blue;//对枚举类型变量的赋值只能由枚举中的元素来赋值。

printf("%d %d %d\n",red, green,blue);//输出结果为0,1,2说明在枚举类型中的变量名称是从0开始赋值的。

return 0;

}

enum Sex//性别 

{

    MALE=2,//可以对变量赋初始值,如果不赋值,不赋值的元素会顺着前一个赋值的元素往后顺延一个。
    FEMALE=4,
    SECRET=8

};

以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。 {}中的内容是枚举类型的可能取值,也叫 枚举常量 。 

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。 例如: 

enum Color//颜色 { 

    RED=1,
    GREEN=2,
    BLUE=4

};

3.2 枚举的优点 

我们可以使用#define 定义常量,为什么非要使用枚举? 枚举的优点: 

1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装)//防止命名冲突

4. 便于调试

5. 使用方便,一次可以定义多个常量 

枚举的每个元素的大小全部都是四个字节

c语言的源代码--->预编译--->编辑---->链接---->可执行程序

预编译会把注释全部删掉,并把预处理的部分完成

4. 联合(共用体) 

4.1 联合类型的定义 

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

//联合类型的声明 union Un

{

char c; 

int i; }; 

//联合变量的定义

union Un un; //计算连个变量的大小 

//联合-联合体-共用体

union un

{

char c;

int I;

}

int main()

{

union un u;

printf("%d\n",sizeof(u));//大小为4

printf("%p\n",&u);

printf("%p\n",&(u.c))//在同一时刻,i和c是不可以同时使用的

printf("%p\n",&(u.i));//发现u,c,i的存放的起始地址地址都是相同的,所以占用的是同样的一块空间,所以他们被叫做联合体的同时也被称为共用体。

return 0;

}

4.2 联合的特点 

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。

判断当前计算机的大小端存储

int main()

{

int a=0x11223344;

//低地址--------->高地址

//11223344 大端存储模式 也叫大端字节序存储模式

//44332211 小端存储模式

//讨论一个数据存放在内存中的字节顺序,称为大小端字节顺序。

return 0;

}

取出1的第一个字节查看是大端还是小端

check_sys()

{

int a=1;

//返回1表示小端

//返回0表示大端

return *(char*)&a;

}

int main()

{

int a=1;

int ret =check_sys()

if(1==*(char*)&a)

{

printf("小端\n");

}

else

{

printf("大端\n");

}

}

用联合体的方式解决

int check_sys()

{

union un

{

char c;

int i;

}u;

u.i=1;

return u.c;//u.c所查找的就是第一个字节

}

4.3 联合大小的计算 

联合的大小至少是最大成员的大小。
  当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union un

{

int a;//4对齐数是4

char arr[5];//5数组的对齐数是1,

}//联合体的大小为8

int main()

{

union un u;

printf("%d\n",sizeof(u));

return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桜キャンドル淵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值