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


前言

对于C语言的学习,我们知道有基本数据类型,指针类型,自定义类型等。今天一起来探讨一下自定义类型。

结构体

结构体的声明

结构的基础知识

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

结构的声明

struct tag
{
    member_list; //成员列表
}variable_list;    //变量列表也称类型名,属于全局变量

注意:程序应尽可能少定义全局变量,在程序中到处引用全局变量,会导致程序难以控制,容易出错。且声明不占用内存

例如描述一个学生

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//姓名
    char telp[11];//电话号
    char id[20];//学号
};//分号不能丢

特殊的声明

特殊声明就是不完全声明又叫匿名结构体类型声明

//匿名结构体类型声明
struct
{
    int a;
    char b;
    float c;
}x;

struct
{
    int a;
    char b;
    float c;
}a[20],*p;

对于上面声明,均省略了结构体标签,而对于匿名结构体类型声明,编译器会把上面两个声明当成完全不同的 两个类型,所以对于p = &x是非法的

结构的成员

结构的成员可以是标量,数组,指针,甚至是其他结构体。

结构体成员的访问

  • 结构体变量访问成员: 结构变量成员通过点操作符(.)访问,点操作符接收两个操作数。
  • 结构体访问指向变量的成员: 有时候我们得到不是一个结构体变量,而是一个指向结构体的指针,就应该用(->)操作符。
struct Stu
{
    char name[20];//名字
    int age;//年龄
}s;

void print(struct Stu* pc)
{
    printf("%s,%d\n",(*pc).name,(*pc).age);
    printf("%s,%d\n", pc->name, pc->age);

}

结构自引用

简而言之就是在结构中包含一个类型为结构本身的成员

typedef struct Node
{
  int data;
  struct Node* next;
}Node;

结构的不完整声明

struct B;

struct A
{
  int a;
  struct B* pb;
};

struct B
{
  int b;
  struct A* pa;
};

结构体变量定义及初始化

有了结构体类型,定义及初始化就很简单了

struct Stu
{
    int x;
    int y;
}p1; //声明类型同时定义变量p1
struct Stu p2;//定义结构体变量p2

struct Stu P3 = { x, y };//定义变量同时初始化

struct Po
{
    char name[20];
    int age;
};
struct Po s = { "zhangsan", 20 };//定义变量同时初始化

结构体内存对齐

目前,我们已经掌握了结构体的基本使用了,现在来开始讨论一个问题:计算结构的大小。当然也是一个热门考点:结构体内存对齐

如何计算?

结构体内存对齐规则:

  • 第一个成员在与结构体变量偏移量为0的地址处;
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数=编译器默认对齐数 与该成员自身大小中的最小值。VS默认值为8,linux中gcc默认值为4.
  • 结构体总大小为最大对齐数的整数倍;
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体的大小就是所有对齐数中最大对齐数的整数倍。

注意:

#pragma pack(n)    //n表示系统默认最大对齐数
...
#pragma pack()     //使用完应立即取消自定义设置

为什么存在内存对齐?

平台原因(移植原因):

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

    性能原因:

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

总的来说:结构体内存对齐就是用空间换取时间的做法,而我们在设计的时候既要满足内存对齐,还要满足节省空间,那么唯一做法:让占用空间小的成员尽可能集中在一起。

//练习一
struct S1
{
  double d;
  //0-7
  char c;
  //8
  int i;
  //11-15
};
printf("%d\n",sizeof(struct S1));        //16

//练习二
struct S2
{
  char a;
  //0
  struct S1 S;
  //8-23
  double f;
  //24-31
};
printf("%d\n",sizeof(struct S2));        //32

附加:

//宏,表示结构体成员变量在内存中偏移量包含于头文件#include <stddef.h>
offsetof(struct S,   A);

宏实现:(size_t)&(((S*)0)->m)

结构体传参

讲解函数栈桢的时候,我们知道函数传参是需要参数压栈的,如果传递的是一个结构体对象,而结构体过大,参数压栈的系统开销就会很大,所以导致系统性能下降。

结论

结构体传参的时候要传结构体指针。

位段

什么是位段?

位段声明与结构体类型相似,有两个不同:

  • 位段成员必须int 、unsigned int 或 signed int。
  • 位段成员名后面有一个冒号和一个数字。

位段内存分配

  • 位段成员可以是int 、unsigned int、signed int、或者char(整形家族)类型。
  • 位段的空间上按四个字节(int)或者一个字节(char)方式开辟
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植性程序因避免使用位段。

eg:

struct A
{
    int a:2;
    int b:5;
    int c:10;
    int d:30;
};
printf("%d",sizeof(struct A)); //8

位段跨平台问题

  • int位段当成有符号还是无符号是不确定的
  • 位段中最大位数目不确定的
  • 位段中成员是从左到右还是从右到左是不确定的
  • 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的还是利用,是不确定的

    总结

    跟结构相比,位段可以达到同样的效果,还节省空间,但是因为存在跨平台问题,所以运用的就不是很多了。

    枚举

枚举顾名思义就是一一列举。
比如:星期、颜色、性别等;

枚举类型定义

enum Color
{
    RED,
    GRENE,
    BLUE
};

以上定义的enum Color 就是枚举类型,{}中内容就是可能取值,也叫枚举常量,默认从零开始一次递增1,当然有时候也可以自定义赋值:

enum Color
{
    RED = 1,
    GREEN = 4,
    BLUE = 8
};

枚举优点

  • 增加代码可读性与可维护性
  • 与#define相比有类型检查,更加严谨
  • 防止命名污染
  • 便于调试
  • 使用方便,一次可定义多个常量

联合(共用体)

联合定义

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

eg

//联合类型声明
union Un
{
    char c;
    int i;
};
//联合变量的定义
union Un un;
//计算变量大小
printf("%d",sizeof(un)); //4

联合特点

联合成员公用一块内存空间,这样一个联合变量大小,至少是最大成员大小。

经典应用

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

int CheckSystem()
{
    union Un
    {
        int a;
        char i;
    }un;
    un.a = 1;
    //返回为1,小端存储
    //返回为0,大端存储
    return nu.i;
}

联合大小的计算

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

eg

union UN1
{
    char arr[5];
    int i;
};
union UN2
{
    short c[7];
    int i;
};
printf("%d",sizeof(union UN1));   //8
printf("%d",sizeof(union UN2));   //16

结构体与联合巧妙使用:

//将long类型ip地址,转换为电分十进制形式
union ip_addr
{
    unsigned long addr;
    struct
    {
        unsigned char c1;
        unsigned char c2;
        unsigned char c3;
        unsigned char c4;
    }ip;
    union ip_addr my_ip;
    my_ip.addr = 176238749;
    printf("%d.%d.%d.%d",my_ip.ip.c4,my_ip.ip.c3,my_ip.ip.c2,my_ip.ip.c1,);

}

结语

通过自定义类型学习,掌握了内存对齐等相关运用,以及如何检验大小端问题。
好好学习,天天编程


部分资料来源于网络,版权属其原著者所有,只供学习交流之用。如有侵犯您的权益,请联系【公众号:码农印象】删除,可在下方评论,亦可邮件至ysluckly.520@qq.com。互动交流时请遵守宽容、换位思考的原则。

×

喜欢就点赞,疼爱就打赏

(function(){ var bp = document.createElement('script'); bp.src = '//push.zhanzhang.baidu.com/push.js'; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();
休闲小游戏