粗谈自定义类型:结构体,枚举,联合(共用体)

本文详细介绍了C语言中的结构体、枚举和联合的概念及使用。结构体包括类型声明、自引用、变量定义与初始化、内存对齐以及位段的实现。枚举涉及定义和使用规则,而联合则展示了其与结构体的区别,成员共享内存空间。这些内容对于理解C语言的数据类型和内存管理至关重要。
摘要由CSDN通过智能技术生成

一,结构体
1,结构体的类型的声明
2,结构体的自引用
3,结构体变量的定义和初始化
4,结构体内存对齐
5,结构体实现位段(位段的填充和可移植性)
二,枚举
1,枚举的定义
2,枚举的使用
三,联合

正文开始

一,结构体

1,结构体的类型的声明
结构体是由一批数据组合而成的一种新的数据类型。组成结构型数据的每个数据称为结构型数据的“成员”。
结构体的定义如下所示,struct为结构体关键字,tag为结构体的标志,member-list为结构体成员列表,其必须列出其所有成员;variable-list为此结构体声明的变量。

//struct为结构体关键字,tag为结构体的标志
struct tag{
		member-list variable-liat;
		//member-list为结构体成员列表,variable-list为此结构体声明的变量
}

例如:

//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
//匿名结构体类型 
struct {
 
    int a;
 
    char b;
 
    double c;
 
} s1;
//同上声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE{
 
    int a;
 
    char b;
 
    double c;
 
};
 
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3; 
//也可以用typedef创建新类型
typedef struct{
    int a;
    char b;
    double c; 
} Simple2;
//可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
 	在上面的声明中,第一个和第二声明被编译器当作两个完全不同的类型,
 即使他们的成员列表是一样的,如果令t3=&s1,则是非法的。

注意:两个结构体类型内部成员哪怕完全一致也是两种类型
2,结构体的自引用
结构体作为一种类型,起成员可以是各种基本类型,当然也包括结构体这种类型。当一个结构体中想引用自身的结构时,是可以的,不过要注意用法。

struct  A {
 
    int a;
 
    int b;
 
    A c;
 
}pp;

这里A还没有定义完还不能使用

为什么错误呢,这个大家应该可以想象,第三个成员c是个A类型,c的第三个成员也是个A类型,那么会一直有pp.c.c.c.c.c.c.c……,此结构体的大小没有结束,那么肯定是错误的,编译的时候肯定通不过。那么怎么能是引用自身呢,这就要看指针的功能了。


 
struct A{
 
    int a;
 
    int b;
 
    A *c;
 
}pp;

在这里插入图片描述

这样使用指针指向一个自身的结构体,那么第三个元素是个指针,占用一个地址空间的大小,那么pp的大小就是一个int,一个int,再一个指针的大小。c的具体内容需要在实际用的时候去赋值。这样就达到了自身引用的效果。而操作系统中使用的链表的实现也就是这样来的。
3,结构体变量的定义和初始化
结构体的初始化方式有两种,可以在定义的时候或定义之后对结构体变量进行初始化。
一般情况下我们都是在定义的时候对它进行初始化,因为那样比较方便。如果定义之后再进行初始化,那就只能一个一个成员进行赋值,就同数组一样。
下面先介绍如何在定义的时候进行初始化。在定义结构体变量时对其进行初始化,只要用大括号“{}”括起来,然后按结构体类型声明时各项的顺序进行初始化即可。各项之间用逗号分隔。如果结构体类型中的成员也是一个结构体类型,则要使用若干个“{}”一级一级地找到成员,然后对其进行初始化。

# include <stdio.h>
struct AGE
{
    int year;
    int month;
    int day;
};
struct STUDENT
{
    char name[20];
    int num;
    struct AGE birthday;
    float score;
};
int main(void)
{
    struct STUDENT student1 = {"小明", 1207041, {1989, 3, 29}, 100};
    return 0;
}

注意,同字符、字符数组的初始化一样,如果是字符那么就用单引号括起来,如果是字符串就用双引号括起来

第二种方式是定义后再初始化,我们将上面的程序改一下即可:

# include <stdio.h>
# include <string.h>
struct AGE
{
    int year;
    int month;
    int day;
};
struct STUDENT
{
    char name[20];  //姓名
    int num;  //学号
    struct AGE birthday;  /*用struct AGE结构体类型定义结构体变量birthday, 即生日*/
    float score;  //分数
};
int main(void)
{
    struct STUDENT student1;  /*用struct STUDENT结构体类型定义结构体变量student1*/
    strcpy(student1.name, "小明");  //不能写成&student1
    student1.num = 1207041;
    student1.birthday.year = 1989;
    student1.birthday.month = 3;
    student1.birthday.day = 29;
    student1.score = 100;
    printf("name : %s\n", student1.name);  //不能写成&student1
    printf("num : %d\n", student1.num);
    printf("birthday : %d-%d-%d\n", student1.birthday.year, student1.birthday.month, student1.birthday.day);
    printf("score : %.1f\n", student1.score);
    return 0;
}

4,结构体内存对齐
结构体的大小不是结构体元素单纯相加就行的,因为我们主流的计算机使用的都是32bit字长的CPU,对这类型的CPU取4个字节的数要比取一个字节要高效,也更方便。所以在结构体中每个成员的首地址都是4的整数倍的话,取数据元素时就会相对更高效,这就是内存对齐的由来。每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
规则:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

在这里插入图片描述
在这里插入图片描述
这里只是简单介绍了一下,详细介绍请点击这里
5,结构体实现位段(位段的填充和可移植性)
位段:信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,例如,“真”或“假”用0或1表示,只需1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。
位段的声明和结构是类似的,有两个不同:
1,位段的成员必须是int unsigned int或signed int
2,位段的成员名之后有一个冒号和数字

struct CHAR
{
    unsigned int ch   : 8;    //8位
    unsigned int font : 6;    //6位
    unsigned int size : 18;   //18位
};
struct CHAR ch1;

以下程序展示了一个结构体的声明:

struct CHAR2
{
    unsigned char ch;    //8位
    unsigned char font;  //8位
    unsigned int  size;  //32位
};
struct CHAR2 ch2;

第一个声明取自一段文本格式化程序,应用了位段声明。它可以处理256个不同的字符(8位),64种不同字体(6位),以及最多262,144个单位的长度(18位)。这样,在ch1这个字段对象中,一共才占据了32位的空间。而第二个程序利用结构体进行声明,可以看出,处理相同的数据,CHAR2类型占用了48位空间,如果考虑边界对齐并把要求最严格的int类型最先声明进行优化,那么CHAR2类型则要占据64位的空间。
位段的内存分配

  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;
//空间是如何开辟的

在这里插入图片描述
位段的应用:
在这里插入图片描述

二,枚举

1,枚举的定义
在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。 是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
枚举的定义:

enum Num
{
    x1=5,
    x2,
    x3,
    x4
};
enum Num x=x3;
此时, 枚举变量x实际上是7
  1. 枚举中每个成员(标识符)结束符是"," 不是";", 最后一个成员可省略","。
  2. 初始化时可以赋负数, 以后的标识符仍依次加1。
  3. 枚举变量只能取枚举说明结构中的某个标识符常量。
    2,枚举的使用
    枚举类型在使用中有以下规定:
    1.枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。例如对枚举weekday的元素再作以下赋值: sun=5;mon=2;sun=mon; 都是错误的。
  4. 枚举元素本身由系统定义了一个表示序号的数值,从0 开始顺序定义为0,1,2…。如在weekday中,sun值为0,mon值为1, …,sat值为6。
main(){
    enum weekday
        { sun,mon,tue,wed,thu,fri,sat } a,b,c;
    a=sun;
    b=mon;
    c=tue;
    printf("%d,%d,%d",a,b,c);
}
  1. 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如: a=sun;b=mon; 是正确的。而: a=0;b=1; 是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换,如: a=(enum weekday)2;其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于: a=tue; 还应该说明的是枚举元素不是字符常量也不是字符串常量, 使用时不要加单、双引号。
main(){
    enum body
        { a,b,c,d };
    int month[31];
    int j=a;
    int i;
    for(i=1;i<=30;i++){
        month[i]=j;
        j++;
        if (j>d) j=a;
    }
    for(i=1;i<=30;i++){
        switch(month[i])
        {
            case a:printf(" %2d %c\t",i,'a'); break;
            case b:printf(" %2d %c\t",i,'b'); break;
            case c:printf(" %2d %c\t",i,'c'); break;
            case d:printf(" %2d %c\t",i,'d'); break;
            default:break;
        }
    }
    printf("\n");
}

三,联合

“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在“联合”中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。如下面介绍的“单位”变量,如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符型(教研室)。要么赋予整型值,要么赋予字符型,不能把两者同时赋予它。联合类型的定义和联合变量的说明:一个联合类型必须经过定义之后,才能把变量说明为该联合类型。
定义一个联合类型的一般形式为:
union 联合名
{
成员表
};
成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名
成员名的命名应符合标识符的规定。
例如:

union perdata
{
int Class;
char Office;
};

定义了一个名为perdata的联合类型,它含有两个成员,一个为整型,成员名为Class;另一个为字符,字符名为Office。联合定义之后,即可进行联合变量说明,被说明为perdata类型的变量,可以存放整型量Class或存放字符型的变量Office。
联合变量的说明和结构变量的说明方式相同,也有三种形式。即先定义,再说明;定义同时说明和直接说明。
以perdata类型为例,说明如下:

union perdata
{
int Class;
char Office;
};
union perdata a,b;

或者可同时说明为:

union perdata
{
int Class;
char Office;
}a,b;

或直接说明为:

union
{
int Class;
char Office;
}a,b;

经说明后的a,b变量均为perdata类型。a,b变量的长度应等于 perdata 的成员类型中最长的长度,即等于
Class的长度,共4个字节。从图中可见,a,b变量如赋予整型值时,只使用了4个字节,而赋予字符时,可用1个字节。
联合大小的计算:
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
比如:

union Un1
{
 char c[5];
 int i;
};
union Un2
{
 short c[7];
 int i;
};
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

自首的小偷

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

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

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

打赏作者

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

抵扣说明:

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

余额充值