当前位置: 首页> 财经> 访谈 > C语言自定义类型(结构体,枚举,联合):

C语言自定义类型(结构体,枚举,联合):

时间:2025/8/23 0:48:48来源:https://blog.csdn.net/2302_79606537/article/details/139372988 浏览次数:0次

大家好久不见,今天我们来学习一下C语言中的自定义类型:

C语言的自定义类型包括:结构体,枚举和联合,接下来大家跟我来一起认识一下这三种类型。

目录

1. 结构体

1.1.1 结构体类型的声明

1.1.2 结构的特殊声明

1.1.3 结构体的自引用

1.2. 结构体的内存对齐

1.2.1 对⻬规则

1.2.2 为什么有内存对齐

1.2.3 修改对齐数 

1.3 结构体实现位段

1.3.1 什么是位段

1.3.2 位段的内存分配 

1.3.3 位段的问题

2. 联合体

2.1.1 联合体类型的声明

2.2 联合体的特点

2.2.1 联合体大小的计算 

2.3 联合体的使用案例

 3. 枚举类型

3.1 枚举类型的声明

2.2 枚举类型的优点 


1. 结构体

结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
1.1.1 结构体类型的声明
struct tag
{
    member- list ;
}variable- list ;

 例如我们要写一个学生的结构体,包含学生的年龄,性别,姓名和学号,

struct Stu
{
     char name[ 20 ]; // 名字
     int age; // 年龄
     char sex[ 5 ]; // 性别
     char id[ 20 ]; // 学号
}; // 分号不能丢
1.1.2 结构的特殊声明

在声明结构的时候,可以不完全的声明。

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

 当创建无命名的结构体时,我们称该结构体为匿名结构体,那这种结构体有没有什么问题呢?

p=*x; 

当执行p=*x代码时,编译器会将这个结构体认成是两种不同的类型,但实际上这是同一个结构体。

所以对于匿名结构体来说,应该将所有该结构体变量在声明时创建。 

1.1.3 结构体的自引用
struct Node
{
     int data;
     struct Node next ;
};

 那 么将结构体作为它自身的一个成员 这样行不行得通呢?

如果这样的话,在计算结构体类型的大小时就会像套娃一样无穷的计算下去。所以是不可行的。

那么如果我们想让结构体作为成员,应该怎么来声明呢?

我们知道,指针的大小只与机器本身有关,如果我们使用指向结构体的指针作为成员,不就解决这个问题了吗?

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

 那如果我们在定义结构体时对结构体进行了typedef重命名,在指针部分可以使用重命名的名字吗?

答案是不行的,因为结构是在成员创建之后才进行的重命名,所以要用原名。

typedef struct
{
    int data;
    Node* next;    //错误
}Node;
 
typedef struct
{
    int data;
    struct Node* next;     //正确
}Node;
1.2. 结构体的内存对齐
我们已经掌握了结构体的基本使⽤了。现在我们深⼊讨论⼀个问题:计算结构体的⼤⼩。
结构体是一种类型,所以可以通过sizeof()来计算结构体的大小。那么问题来了,结构体的大小应该怎么计算呢?
我们接下来来学习一下结构体的内存对齐规则。
1.2.1 对⻬规则
⾸先得掌握结构体的对⻬规则:
1. 结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处。
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。

- VS 中默认的值为 8
- Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
对其数与机器也有关,一般在32位机器下是4,64位机器下是8。
我们来通过图来详细了解一下:
struct S1
{
    char c1;
    int i;
    char c2;
};
比如对于上面的结构体S1,

 因为结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处。所以结构体中第一个char类型占内存大小为1,位置从0到1,而对于int来说,它的对齐数为4,位置应该从4到8,对于第二个char类型来说,它的对其数为1,所以可以从位置为8的位置紧接着,位置为8到9。所有的成员类型都花完了,在计算结构体大小时,还要考虑最大对其数,在该结构体中为4,所以结构体的大小为4的整数倍,对与该结构体来说,大小应该为12。

1.2.2 为什么有内存对齐
1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
所以我们在创建结构体时应该 让占⽤空间⼩的成员尽量集中在⼀起,对于上文的结构体S1,可以写成
struct S1
{
    char c1;
     char c2;
    int i;
};

此时结构体的大小为8,节省了空间。

1.2.3 修改对齐数 
#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
# include <stdio.h>
# pragma pack(1) // 设置默认对⻬数为 1
struct S
{
    char c1;
    int i;
    char c2;
};
# pragma pack() // 取消设置的对⻬数,还原为默认
int main ()
{
    printf ( "%d\n" , sizeof ( struct S));
    return 0 ;
}
1.3 结构体实现位段
1.3.1 什么是位段
位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int unsigned int signed int ,在C99中位段成员的类型也可以选择其他类型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
例如下面是一个位段:
struct A
{
    int _a: 2 ;
    int _b: 5 ;
    int _c: 10 ;
    int _d: 30 ;
};

位段与结构体十分相似,那么位段的大小又是多少呢?下面我们来学习一下位段的内存分配。

1.3.2 位段的内存分配 
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
我们现在创建一个位段:
struct S
{
    char a: 3 ;
    char b: 4 ;
    char c: 5 ;
    char d: 4 ;
};
struct S s = { 0 };
s.a = 10 ;
s.b = 12 ;
s.c = 3 ;
s.d = 4 ;

在位段声明时冒号后带的数字为该成员的空间大小,而数据是在创建变量后才能赋值。

 

这就是S位段的内存大小,当一个字节内剩余空间大于下一个成员的大小时,下一个成员将会存放到这个字节中。

1.3.3 位段的问题
1. 位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
4.  位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊放在⼀个变量中,然后赋值给位段的成员。

2. 联合体

2.1.1 联合体类型的声明
像结构体⼀样,联合体也是由⼀个或者多个成员构成,这些成员可以不同的类型。
但是编译器只为最⼤的成员分配⾜够的内存空间。联合体的特点是所有成员共⽤同⼀块内存空间。所以联合体也叫:共⽤体。
给联合体其中⼀个成员赋值,其他成员的值也跟着变化。
union Un
{
    char c;
    int i;
};
int main ()
{
// 联合变量的定义
    union Un un = { 0 };
// 计算连个变量的⼤⼩
    printf ( "%d\n" , sizeof (un));
    return 0 ;
}

 结算的结果是4,那为什么是这个结果?我们继续往下看。

2.2 联合体的特点
联合的成员是共⽤同⼀块内存空间的,这样⼀个联合变量的⼤⼩,⾄少是最⼤成员的⼤⼩(因为联合⾄少得有能⼒保存最⼤的那个成员)。
对于结构体和联合体:
struct S{
    char c;
int i;
};
 
union A{
char c;
int i;
};
 

在联合体中,成员公用一块空间,所以改变一个成员的值时,其他成员的值也会跟着改变。

2.2.1 联合体大小的计算 

根据上文,我们可以知道:联合体的大小至少是最大成员的大小,而当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候,就要对⻬到最⼤对⻬数的整数倍。

2.3 联合体的使用案例

联合体的使用可以节省空间,

⽐如,我们要搞⼀个活动,要上线⼀个礼品兑换单,礼品兑换单中有三种商品:图书、杯⼦、衬衫。每⼀种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。
图书:书名、作者、⻚数
杯⼦:设计
衬衫:设计、可选颜⾊、可选尺⼨
那我们不耐⼼思考,直接写出⼀下结构:

 

struct gift_list      //不使用联合体
{
    //公共属性
    int stock_number;//库存量
    double price; //定价
    int item_type;//商品类型

    //特殊属性
    char title[20];//书名
    char author[20];//作者
    int num_pages;//⻚数

    char design[30];//设计
    int colors;//颜⾊
    int sizes;//尺⼨
};


struct gift_list      //使用联合体
{
    int stock_number;//库存量
    double price; //定价
    int item_type;//商品类型

    union {
        struct
        {
            char title[20];//书名
            char author[20];//作者
            int num_pages;//⻚数
        }book;
        struct
        {
            char design[30];//设计
        }mug;
        struct
        {
            char design[30];//设计
            int colors;//颜⾊
            int sizes;//尺⼨
        }shirt;
    }item;
};

 3. 枚举类型

3.1 枚举类型的声明
枚举顾名思义就是⼀⼀列举。
把可能的取值⼀⼀列举。
⽐如我们现实⽣活中:
⼀周的星期⼀到星期⽇是有限的7天,可以⼀⼀列举
性别有:男、⼥、保密,也可以⼀⼀列举
⽉份有12个⽉,也可以⼀⼀列举
三原⾊,也是可以意义列举
这些数据的表⽰就可以使⽤枚举了。

enum Day//星期
{
    Mon,
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun
};

enum Sex//性别
{
    MALE,
    FEMALE,
    SECRET
};
enum Color//颜⾊
{
    RED,
    GREEN,
    BLUE
};

 

以上定义的 enum Day enum Sex enum Color 都是枚举类型。
{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,依次递增1,当然在声明枚举类型的时候也可以赋初值。

enum Color//颜⾊
{
    RED = 2,
    GREEN = 4,
    BLUE = 8
}; 

2.2 枚举类型的优点 
我们可以使⽤ #define 定义常量,为什么⾮要使⽤枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符⽐较枚举有类型检查,更加严谨。
3. 便于调试,预处理阶段会删除 #define 定义的符号
4. 使⽤⽅便,⼀次可以定义多个常量
5. 枚举常量是遵循作⽤域规则的,枚举声明在函数内,只能在函数内使⽤

 

那么我们今天的学习就到这里啦,我们下次再见。

关键字:C语言自定义类型(结构体,枚举,联合):

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: