C升级——自定义类型总结

本文详细介绍了C语言中的自定义类型,包括结构体的声明、定义、初始化,内存对齐问题,位段的使用,枚举的声明、优点及使用,以及联合的声明、特点和内存大小计算。结构体的内存对齐旨在提高效率,位段节省空间但不跨平台,枚举增强代码可读性和类型安全性,联合则是一组成员变量共享内存空间。
摘要由CSDN通过智能技术生成

嗨,小左又来了呢!虽然C语言系统规定了很多数据类型,但是并不能描述完全所有的数据,因此就出现了自定义的一些数据类型。虽说是自定义,但是它也有自己的规则,另外很多时候这些类型都会帮上大忙,所以这篇文章给它们做个总结!欢迎小伙伴们来探讨~

一、结构体

1、声明、定义、初始化

结构体是一个不同类型的值的集合,这些值被称为成员变量。下面看一下结构体声明:

//方法1:
struct tag
{
    memberList;
}variableList;

//方法2:
struct tag
{
    memberList;
};

一般都会使用方法2。因为结构体的声明一般是写在 main 函数之外,若是直接定义一些结构体变量,就额外创建了全局变量,这样不好而且也没必要,具体可以看一下这篇回答:为什么避免使用全局变量?

结构体变量的定义及初始化,这二者可以一起也可以分开,具体视情况而定:

//合在一起
struct tag varibaleName = {memberListValue};

//分开
struct tag varibaleName;//结构体变量定义
varibaleName = {memberListValue};//结构体变量初始化赋值

还有一个特殊情况,若是声明的时候没有 tag (变量名),结构体也是合法的,此时叫它匿名结构体。直接使用是没有任何意义的,所以一般也不会直接使用。使用时都是作为结构体成员变量被嵌套使用的。下面看具体声明:

struct
{
	memberList;
}variableList;//定义的结构体变量只能使用1次,而且必须在定义的时候直接初始化
//例子:
struct
{
	int a;
	int b;
}s1 = { 10, 12 };//无意义
int main()
{
	printf("%d", s1.a);
	return 0;
}

2、结构体的自引用和结构体传参

  • 结构体自引用

    //错误方法,没有终止条件,会无限调用
    struct s
    {
        int a;
        struct s next;
    };
    //正确方法,使用结构体指针,只是完成指向下一个结构体的地址就结束。
    struct s
    {
        int a;
        struct s* next;
    };
    
  • 结构体传参
    这部分在前面的讲解已经很完备,不再赘述。具体可点击查看->《系统梳理3》

3、结构体的内存对齐问题

  • 对齐数
    对 齐 数 = m i n ( 编 译 器 默 认 对 齐 数 , 成 员 变 量 大 小 ) 对齐数 = min(编译器默认对齐数,成员变量大小) =min()
    VS中默认对齐数为8;Linux使用gcc编译器,没有默认对齐数,自身大小就是它的对齐数
  • 结构体内存对齐规则

1、第一个结构体成员变量的地址在结构体变量偏移量为0处
2、其他成员变量要对齐到自身对齐数的整数倍的地址处(若嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍)
3、结构体总大小为最大对齐数的整数倍,此时结构体整体大小就是所有对齐数的整数倍

举例(成员变量注释均为偏移量):

 //例1
 struct S1
 {
 	char c1;//0
 	int i;//4
 	char c2;//8
 };
 //offsetof计算的就是上面成员变量的偏移量,即注释内容。仅以例1为例
 printf("%d\n", offsetof(struct S1,c1));
 printf("%d\n", offsetof(struct S1, i));
 printf("%d\n", offsetof(struct S1, c2));
 
 printf("%d\n", sizeof(struct S1));//12,最大对齐数4
 //练习2
 struct S2
 {
 	char c1;//0
 	char c2;//1
 	int i;//4
 };
 printf("%d\n", sizeof(struct S2));//8,最大对齐数4
 //练习3
 struct S3
 {
 	double d;//0
 	char c;//8
 	int i;//12
 };
 printf("%d\n", sizeof(struct S3));//16,最大对齐数8
 //练习4-结构体嵌套问题
 struct S4
 {
 	char c1;//0
 	struct S3 s3;//8,对齐数8
 	double d;//24
 };
 printf("%d\n", sizeof(struct S4));//32,最大对齐数8

偏移量的计算可以用 offsetof ( ) 宏去实现。下面大概介绍一下这个宏:

头文件:<stddef.h>,声明: " size_t offsetof( structName, memberName ); "
传入结构体名字和要计算的成员变量的名字,返回计算的偏移量起始位置。实例见上例1。

  • 为什么存在内存对齐

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

    结构体的内存对齐是通过空间的浪费换取时间效率的提升,所以在设计结构体的时候,但是为了节省空间,尽量做到让占用内存小的成员集中在一起

  • 修改默认对齐数

    一般修改的设置值都是2的幂次方数。

    #pragma pack(设置值)
    #pragma pack() //取消设置值,还原为默认

4、位段(位段式结构体)

位段的声明与结构体类似,但是有一些不同:

1.成员必须是 char 、int 、 unsigned int 、signed int
2.成员名字后还得加一个冒号和数字,数字表示这个变量占的内存字节位数,具体举一个例子看一下:

	struct A {
		int _a : 2;//第一个是int,开辟4字节,32个比特位,_a占2个比特位
		int _b : 5;//剩余30个比特位,_b占5个比特位
		int _c : 10;//剩余25个比特位,_b占10个比特位
		int _d : 30;//剩余15个比特位,_c不够用,_c是int,所以再开辟4字节
	};
	printf("%d\n", sizeof(struct A));//8
  • 位段的内存分配

    位段的成员只能是 int unsigned int signed int 或者是 char (属于整形家族)类型。 所以它的内存空间是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。位段是不跨平台的(很多没有标准规定),注重可移植性的程序应该避免使用位段。、

    位段目前的缺点——跨平台性差:int 位段被当成有符号数还是无符号数是不确定的;位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成大于16的值,在16位机器会出问题;位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义;当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

  • 位段的总结及应用

    对一般结构体做了优化,达到同样的效果的同时一定程度上节省了空间开销,但是不可跨平台,可移植性差。

    应用:位段描述计算机网络协议,提高传输效率

二、枚举

枚举:把有限的可能取值一一列举。

1、枚举的结构声明

    enum 枚举名    //枚举类型
    {
        该枚举类型的可能取值
    }

对于枚举变量可以赋值也可以不赋。赋值的话,按照赋的值;否则,默认从0开始依次递增。下面看举例:

enum Sex//性别
{
 MALE,
 FEMALE
};
enum Color//颜色
{
 RED = 1,
 GREEN = 2,
 BLUE = 3
};

2、枚举的优点

枚举的作用和#define 定义的常量基本一致,给常量一个“ 别名 ”,那么相较后者它的优势在哪里呢?

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

3、枚举常量的使用

直接看例子:

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;      //有问题!!

关于枚举常量提一点,赋值的时候只能拿枚举常量给枚举变量赋值,才不会出现类型的差异,否则其实是有问题的。问题在C语言其实对数据类型的灵敏度没有那么高,直接像赋值的第二行给它赋一个常量数值或许没问题,但是放到C++就不行了,它会因为类型不兼容,直接报错,所以小伙伴们,编程的时候一定要严谨喔~

三、联合(共同体)

联合也是一种特殊的自定义类型,它的特别之处在于它所包含的一系列成员变量的内存空间是共用的。老规矩,还是先看一下它的声明:

1、联合的声明

	union 联合名
	{
		联合成员变量;
	};

除了关键字变成了 union ,其余部分可以参考结构体,是完全一致的。

2、联合的特点

联合的成员变量是不会同时出现的一组变量值,它们共用同一块内存空间,所以这样一个联合变量的大小至少是最大成员的大小,且它们的地址都是一样的。

所以需要注意,使用了其中一个成员变量就不可以使用其它的了,如果还不理解可以看一下这个例子:

union Un
{
 int i;
 char c;
};
union Un un;
//下面输出的结果是什么?
un.i = 0x11223344;
un.c = 0x00;
//结果是11223300(VS小端存储)
printf("%x\n", un.i);

3、联合的内存大小计算

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

最后再来看两个例子:

	union Un1    //对齐数4
	{
		char c[5];//5*1 = 5
		int i;//4
	};
	union Un2    //对齐数4
	{
		short c[7];//7*2 = 14
		int i;//4
	};
	//下面输出的结果是什么?
	printf("%d\n", sizeof(union Un1));//8
	printf("%d\n", sizeof(union Un2));//16

over~~~😁😁😁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值