自定义类型详解(结构体+枚举+联合)

自定义类型详解

1. 结构体

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

1.1 结构体的声明

一个标准的结构体的声明如下,例如描述一本书:

struct Book{
    char name[20];    //书名
    char auther[20];  //作者名
    short price;      //价格
}b1; //声明结构体的同时定义变量b1

首先struct Book是结构体标签,表示这是一个关于书的结构体。其次char name[20]、char auther[20]、short price是结构体中的成员变量,最后的b1是定义的一个结构体变量

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

struct Book{
	char name[20];    //书名
	char auther[20];  //作者名
	short price;      //价格
}b1;  //定义变量b  
int main(){
    struct Book b = { "《进击的巨人》", "谏山创", 20 };  //初始化变量b
    return 0;
}

不同的写法:

typedef struct Book{  //typedef-类型重命名
	char name[20];    //书名
	char auther[20];  //作者名
	short price;      //价格
}Book;  //将结构体struct Book重命名为Book  
//注:此处的Book与上一代码中的b不同,b是变量名,Book是类型名
int main(){
    Book b = { "《鬼灭之刃》", "吾峠呼世晴", 20 };  //初始化变量b
    return 0;
}

1.3 匿名结构体类型

 匿名结构体类型在编译器看来都是独立的个体,所以彼此之间的类型也不相同,很容易导致代码无法通过编译,不推荐使用。

struct{
    int a;
    char b;
    short c;
}s;

 

1.4 结构体自引用

struct Node{
    int data;           //数据域
    struct Node* next;  //指针域
};

:在匿名结构体类型不可使用结构体自引用,如下

typedef struct{
    int data;    //数据域
    Node* next;  //指针域
}Node;

以上代码会导致程序编译不通过,这是因为需要使用Node时还没有定义,在结尾才有定义,不可“先上车,再买票”。这也是不推荐使用匿名结构体类型的原因之一。

1.5 结构体嵌套

struct A{    //类型声明
    int i;   //成员变量
    char cha;
    double d;
};
struct B{
    char chb;
    struct A a;   //嵌套结构体struct A
    short sh;
};
struct B b = { 'a', { 2, 'b', 3.14 }, 3 };  //结构体嵌套初始化
printf("%lf\n", b.a.d);  //输出

1.6 结构体内存对齐

首先我们来看一段代码:

#include<stdio.h>
struct S{
	char a;   //1
	int i;	  //4
	short b;  //2
};
int main(){
	struct S s;
	printf("%d\n", sizeof(s));  //打印结构体变量s的大小
	return 0;
}

    这段代码的结果是多少呢?答案是12,这就是结构体内存对齐的效果。结构体中的其他成员变量要对齐到对齐数的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 最大成员大小 的较小值。在VS中编译器默认的对齐数为8,在上述代码中结构体的最大成员是i 为4,所以这里的对齐数就是二者中的较小值,4。在内存中成员a 和成员b 需要与成员i 对齐,所以最终的答案是12。

    这样显然是极为浪费空间的做法,我们可以换一种写法,将成员b 放在成员i 之前,这样就可以使成员a和成员b放在一起,且能与成员i 对齐,这样一来最终打印出的结构体变量s的大小就是8了。

char a;   //1
short b;  //2
int i;	  //4

结构体嵌套情况下如何计算结构体变量大小:

#include<stdio.h>
struct S1{
	char a;  //1
	short b;   //2
	int i;	  //4
};
struct S2{
	char c;     //1
	struct S1 s1;    //8
	short sh;    //2
};
int main(){
	struct S2 s2; 
	printf("%d\n", sizeof(s2));  //打印结构体变量s2的大小
	return 0;
}

    以上代码的结果是16。纵观s1和s2,最大的成员是int i,大小是4,所以此时的对齐数是4。由于是s1嵌套进s2,所以我们从s2开始,成员c 需要与成员i,也就是4对齐。进入s1,成员a 和成员b 都与成员i 对齐。再看s2,成员sh 与成员i 对齐,所以最终的结果是16。

   

1.7 修改默认对齐数

先看一段代码:

#include<stdio.h>
//#pragma pack(4)  //设置默认对齐数为4
struct S{
	short sh; //2
	double d; //8
	int i;	  //4
};
int main(){
	struct S s;
	printf("%d\n", sizeof(s));
	return 0;
}

    通过之前的学习我们很容易得出这段代码的结果为24,这是因为在结构体S中的最大成员为double d,大小为8,与VS编译器默认的对齐数一致,所以对齐数为8。上下两个成员需要与之对齐,所以结果为24。

若是将代码中的#pragma pack(4)的注释去除会发生什么事情?结果变为了16。这是因为我们使用了 #pragma 这个预处理指令,将对齐数设置为4,所以成员short sh 和int i 就不需要与double d 对齐了,而是与4对齐,所以答案变为了16。

1.8 位段

#include<stdio.h>
struct S{
	int a : 2;  //2个比特位
	int b : 5;
	int c : 10;
	int d : 30; //int最大32个比特位,位段不可以超过该数据结构的最大位
};
int main(){
    struct S s.
	printf("%d\n", sizeof(s));  //打印结构体S的大小
	return 0;
}

    由上述代码,struct S就是一个位段结构体类型(注:位段类型的成员必须是unsigned intsigned int 或者 char类型)。使用位段可以使结构体更加节省空间,首先开辟出1个整形的空间,也就是4个字节或者说32个比特位的空间。成员int a占用2个比特位,成员int b占用5个比特位,成员int c占用10个比特位,此时还剩余15个比特位不够成员int d存放,所以再开辟32个比特位来存放成员int d。最终占用了8个字节的空间,打印的内容就是8。那么以上存放了成员的内存空间还有剩余的比特位该怎样使用呢,在C语言的标准中并没有规定,所以编译器的不同可能会导致程序无法运行,也就是位段会导致程序不可移植。那位段在内存中又是如何存储的,如下图先开辟出一个整形的空间,成员从右向左依次存进去,直到存不下,再重新开辟一个整形的空间,直到存完。

 

2. 枚举

2.1 定义

    枚举就是将可能的取值列举出来,例如在生活中,一个星期有七天,可以列举出星期一到星期天七个取值,再比如颜色可以列举出红、黄、蓝、绿、青、蓝、紫等。像此类的的取值就可以使用枚举。

enum Color{    //颜色
	Red,
	Bule,
	Green = 1,  //给常量Green赋初值
	Black,
	White
};
int main(){
	enum Color c = Black;  //定义枚举变量c,并初始化
	return 0;
}

    在以上代码中,enum Color就是枚举类型,而{ }中的可能取值叫做枚举常量。枚举中的常量都是有值的,默认从0开始,依次递增1,也可以在定义的时候赋值,例如以上代码中Red的值就是0,而Bule则是1,Green我们赋值为1,Black就是2,White是3。

2.2 枚举的优点

  1. 增加代码的可读性和可维护性。
  2. 和#defifine定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装) 。
  4. 便于调试。
  5. 使用方便,一次可以定义多个常量。

 

3. 联合

3.1 定义

联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特点是这些成员共用同一块空间。
union Un{    //声明联合类型
    char c;  //定义联合成员
    int i;
};
union Un u;  //定义联合变量
printf("%d\n", sizeof(u.c));  //计算联合成员c的大小
printf("%d\n", sizeof(u.i));  //计算联合成员i的大小
printf("%d\n", sizeof(u));  //计算联合变量u的大小

上述代码中计算联合成员c、i 和变量u的大小应该是多少?答案是1、4、4,前面两个大小毋庸置疑,但是联合变量中的charint类型加起来不应该是5吗?这就涉及到联合的特点了。

3.2 联合的特点

联合的成员共用同一块内存空间,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有足够的空间来保存那个最大的成员)。
 
#include<stdio.h>
union Un{
	char c;
	int i;
};
int main(){
	union Un u;
	printf("%p\n", &u);        //输出联合变量u的地址
	printf("%p\n", &(u.c));    //输出联合成员c的地址
	printf("%p\n", &(u.i));    //输出联合成员i的地址
	return 0;
}

这是上述代码的执行结果,由此可知联合的成员确实是共用一块内存空间的。而在内存中的表现如下:

可见成员c 和成员i 共用了第一个内存空间,所以在同一时间只能用c 或者i 其中的一个,因为改变其中一个的值另一个的值也会发生改变。我们可以通过代码测试一下:

#include<stdio.h>
union Un{
	char c;
	int i;
};
int main(){
	union Un u;
    u.i = 0x11223344;  //给联合成员i赋值十六进制的11223344
	u.c = 0x55;        //给联合成员c赋值十六进制的55
	printf("%x\n", u.i);  //以十六进制方式打印成员i的值
	return 0;
}

    以上代码的执行结果是,这是因为在内存中我们首先为成员i 赋值11223344,那么它在内存中是这样的(如下图左方),随后我们为成员c 赋值55,他就会改变与成员i 共用的当前为44的内存空间为55(如下图右方),最终打印出来的结果就成了11223355。

3.3 联合大小的计算

联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,需要对齐到最大对齐数的整数倍。
#include<stdio.h>
union Un{
	char arr[7];  //7
	int i;    //4
};
int main(){
	union Un u;
	printf("%d\n", sizeof(u));   //打印联合变量u的大小
	return 0;
}

    由以上代码,最终打印的数字是多少?答案是8。这是因为联合的大小至少是最大成员的大小,上述代码中的char数组的大小是7,对齐数是1(char类型大小),而成员i 的大小是4,最大对齐数也是4(int类型大小),此时的最大成员大小是7,最大对齐数是4,最大成员大小不是最大对齐数的整数倍,所以需要对齐到8才可以。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中,结构体是一种自定义数据类型,可以将不同类型的变量组合在一起,形成一个新的数据类型结构体定义的基本形式如下: ```c struct 结构体名 { 数据类型 成员名1; 数据类型 成员名2; // ... }; ``` 其中,结构体名是用户自定义的名称,成员名是结构体中每个成员的名称,数据类型可以是任意C语言的数据类型,包括基本数据类型自定义数据类型结构体变量的定义方式如下: ```c struct 结构体名 变量名; ``` 读取结构体中的成员变量可以通过“.”运算符来实现,例如: ```c #include <stdio.h> struct Person { char name[20]; int age; }; int main() { struct Person p; printf("请输入姓名:"); scanf("%s", p.name); printf("请输入年龄:"); scanf("%d", &p.age); printf("姓名:%s,年龄:%d\n", p.name, p.age); return 0; } ``` 枚举是一种特殊的数据类型,用于定义一组常量。枚举的定义方式如下: ```c enum 枚举名 { 常量名1, 常量名2, // ... }; ``` 其中,枚举名是用户自定义的名称,常量名是枚举中每个常量的名称。枚举常量的值默认是从0开始自动递增的,也可以手动指定值。例如: ```c #include <stdio.h> enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }; int main() { enum Weekday today = Tuesday; printf("今天是星期%d\n", today + 1); return 0; } ``` 联合是一种特殊的数据类型,它的成员变量共享同一块内存空间。联合的定义方式如下: ```c union 联合名 { 数据类型 成员名1; 数据类型 成员名2; // ... }; ``` 其中,联合名是用户自定义的名称,成员名是联合中每个成员的名称,数据类型可以是任意C语言的数据类型,但所有成员的大小不能超过联合的大小。例如: ```c #include <stdio.h> union Number { int i; float f; }; int main() { union Number n; n.i = 123; printf("int: %d, float: %.2f\n", n.i, n.f); n.f = 3.14; printf("int: %d, float: %.2f\n", n.i, n.f); return 0; } ``` 以上就是C语言自定义数据类型中的结构体枚举联合的基本用法和注意事项。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值