自定义类型:结构体,枚举,联合 重点整合!!

知识点:

结构体类型的创建
结构体初始化
结构体内存对齐
结构体实现位段(位段的填充&可移植性)
枚举+联合

结构体类型的创建和初始化

结构体的声明+结构体的自引用+结构体变量的定义和初始化

结构的基础知识

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

结构体的声明

struct Stu
{
     char name[20];
     int age;
     char sex[5];
     char id[20];
};//分号不能丢

特殊的声明
在声明结构体的时候,可以不完全的声明
比如 :

//匿名结构体类型
//声明时可省略掉结构体标签(tag)
struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}a[20],*p;//可直接声明对象

But 在上面代码的基础上,下面的代码合法吗?

p=&x;
//warning:编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。

结构体的自引用
Tip:结构体内部不能存和自己同类型的变量(struct node next 与struct node ,因为无法计算结构体的大小,正确的方法应该包含一个同类型的指针

//正确的自引用方式
struct Node
{
    int data;
    struct Node* next;
};
//结构体重命名的正确操作
typedf struct Node
{
    int data;
    struct Node*next;
}Node;
//Error Writing:
typede struct
{
     int data;
     Node*next;
}Node;

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

struct Point      //类型声明
{
      int x;
      int y;
}p1;                  //声明类型的同时定义变量p1  
struct Point p2;  //定义结构体变量p2
struct Point p3 = {x,y}; //初始化:定义变量的同时赋初值。

struct Node
{
      int data;
      struct Point p;
      struct Node*next;
}n1={10,{4,5},NULL};//结构体嵌套初始化
struct Node n2={20,{5,6},NULL};//结构体嵌套初始化

结构体传参,传的是结构体的地址,用指针接收。如果传递一个结构体对象时,参数压栈的系统开销(时间及空间)比较大,会导致性能的下降。

结构体内存对齐

考点一:结构体的对齐规则

1.第一个成员永远都放在结构体起始位置的0偏移处。
2.从第二个成员开始,每个成员都放在某个对齐数的整数倍的偏移处。
(某个对齐数:成员自身的大小与默认对齐数的较小值)
默认对齐数:Vs : 8;linux gcc : 4
3.结构体的总大小必须是所有成员的对齐数中最大对齐数的整数倍。
4.嵌套的结构体要对齐到自身最大对齐数的整数倍处。结构体的总大小是所有对齐数(包含嵌套结构的)中最大对齐数的整数倍。

struct S3
{
      double d; //0-7
      char c; //8
      int i;//12-15
};
struct S4
{
     char c1;//0   1-7浪费
     struct S3  s3;//8-23
     double d; //24-31
};
printf("%d\n",sizeof(struct S3));//16Byte
printf("%d\n",sizeof(struct S4));//32Byte

ps:#include<stddef.h> offsetof 宏定义(用于计算一个成员在其起始位置的偏移量)

考点二:为什么存在内存对齐

平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常。
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。因为为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总的来说:结构体内存对齐是拿空间换时间的做法。
因此,我们在设计结构体时,应尽可能使占用内存小的成员集中在一起,避免过多的浪费空间。

结构体实现位段(位段的填充&可移植性)

位段与结构类似,但有两个不同:

位段的成员必须是int ,unsigned int ,signed int 或者是char (属于整型家族)类型。
位段的成员名后有一个冒号和一个数字。

//结构体实现位段
struct S
{
   char a:3;  //开辟一个字节
   char b:4;
   char c:5; //不够,再开辟一个字节
   char d:4; //不够,再开辟一个字节
};
int main()
{
   struct Ss={0};
   printf("%d",sizeof(struct S)); //3
   s.a=10; //0101-->010
   s.b=12; //1100-->1100
   s.c=3;  //0011-->00011
   s.d=4;  //0100
   return 0;
}

结构体s在Vs环境下的储存形式

位段的内存分配特点:
1.都属于整型家族。
2.位段的空间上是按照需要以4个字节或1个字节的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

//eg:
struct A    //A是一个位段类型
{
     int _a:2; //开辟4个字节--32个比特位
     int_b:5;
     int_c:10; //剩5个比特位
     int_d:30;  //单位为比特位  //再开辟4个字节--32个比特位
};
printf("%d\n",sizeof(struct A));  //8 Byte

位段的跨平台问题:
1.int 位段被当成有符号数还是无符号数是不确定的。
2.位段中最大位的数目不能确定。(16位机器最大为16,32位机器为32,写成27,在16位机器上有问题)
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用是不确定的。

综上面情况来看,位段和结构相比,可以达到同样的效果,但是可以很好的节省空间,但有跨平台的问题存在。

枚举与联合

枚举 — 一一列举

enum Day
{
//可能的取值都是有限的,默认从0开始,依次递增1,当然在定义的时候也可赋初值。
  Mon,
  Tues,
  Wed,
  Thur,
  Fri,
  Sat,
  Sun
  };
  int main()
  {
      enum Day day = Mon;
      //(枚举的使用)只能拿枚举常量给枚举变量赋值,才不会出现类型差异。
      day = Tues;
      return 0;
   }

枚举相比 define 定义常量,有以下优点:

增加代码的可读性和可维护性。
和 #define 定义的标识符相比有类型的检查,更加严谨。
为防止命名污染(封装)//用 define 时暴露在全局中,且易名字重复,冲突。
便于调试。
使用方便,一次可以定义多个常量。

联合—共用体

联合的定义

一种特殊的自定义类型,此类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间

联合的特征

union Un //联合类型的声明
{
  char i;
  int c;
  double d;
};
int main()
{
  union Un un;//联合变量的定义
  printf("%d\n",sizeof(un));// 8 Byte 公用内存空间,联合变量的大小是最大成员的大小。
  return 0;
}
//在上述代码的基础上增加以下代码
printf("%p\n",&(un.i));
printf("%p\n",&(un.c)); // 根据联合的特征,最终地址打印会显示同一块地址

联合大小的计算

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

PS:联合的应用
面试题:判断当前计算机的大小端存储

小端字节性存储:把数字的低位字节内容存到低地址处。
大端字节性存储:把数据的低位字节保存到高地址处。

int check_sys1()
{
   union Un;
   {
      char c;
      int i;
    } u;
    u.i=1;// 返回1小端,返回0大端
    return u.c;
 }
 int check_sys2()
 {
   int a=1;// 返回1小端,返回0大端
   if(1==*(char*)&a)
   {
      return 1;
   }
   else
   {
      return 0;
    }
   }
 int main()
 {
    int ret==check_sys1();
 //   int ret2==check_sys2();
    if(ret==1)
    {
      printf("小端\n");
    }
    else   printf("大端\n");
    return 0;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值