自定义数据类型 - 结构体,位段,枚举,联合体

103 阅读6分钟

「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战

自定义数据类型 - 结构体

C语言自己的数据类型 - 内置类型

声明一个结构体类型

//声明一个结构体类型
//声明一个学生类型,是想通过学生类型来创建学生变量(对象)
//描述学生:属性+名字+电话+性别

struct Stu
{
	char name[20];
    char tele[20];
    char sex[10];
    int age;
};
结构体变量 结构体标签
{
	成员变量;
}变量列表;

创建结构体变量

struct Stu s1;

分为全局变量、局部变量。

匿名结构体类型

没有结构体标签

struct
{
    int a;
    char b;
}x;

匿名结构体指针类型

struct
{
	int a;
    char b;
}* psa;

两个结构体类型虽然成员变量相同,但是是两个不同的结构体。

警告:编译器会把两个声明当成完全不同的两个类型。

结构体的自引用

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

不使用指针:计算结构体的内存大小将会是无限大(无限套娃)

typedef : 类型重命名

声明 -> 定义

结构体变量的定义与初始化

{ }

结构体内存对齐 - 计算结构体大小

栗子

struct S1   
{
	char a;
	char b;
	int c;
}s1;

struct S2
{
	char a;
	int b;
	char c;
}s2;

int main()
{
	printf("%d\n", sizeof(s1));   // 8
	printf("%d\n", sizeof(s2));   // 12
	return 0;
}
  1. 第一个成员在与结构体变量偏移量为0的地址处(第一个变量放置在顶端,该内存空格为对齐数是0的位置)

  2. 其他成员变量要对齐某个数字**(对齐数)整数倍的地址处**

    对齐数 = 编译器默认的一个对齐数 与 该成员 大小的较小值 (VS默认值为8, gcc没有默认对齐数)

  3. 结构体总大小是最大对齐数(每一个成员变量都有有个对齐数)的整数倍

  4. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

从对齐数开始(对齐数从0开始)

顺序往下放

struct S3
{
	double a;
	char c;
	int i;
}s3;                    // 16 = 8 + 1 + 3(浪费)+ 4

为什么需要内存对齐?

空间换取时间。读取方便。

32位平台:32根地址线,即32根数据线 4字节

在设计结构体时,让占用空间小的成员尽量集中在一起。

修改默认对齐数

#pragma 预处理指令

设置默认对齐数位4

#pragma pack(4)

取消设置的默认对齐数

#pragma pack()

偏移量

offsetof可以求偏移量,本质是一个宏,宏的参数可以是数据类型

size_t offsetof( structName, memberName );

头文件

<stddef.h>

结构体传参

值传递 & 地址传递

地址传递 :不管结构体多大,传的地址在32位平台总是4个字节。

如果传递一个结构体对象时,结构体过大,参数压栈的系统开销大。

自定义数据类型 - 位段

结构体实现位段。

位段

1.位段的成员必须是int , unsigned int, signed int或者char

2.位段的成员名后边有一个冒号和一个数字

#include <stdio.h>

struct A
{
	int _a : 2; //a只需要2个比特位
	int _b : 5; //b只需要5个比特位
	int _c : 10; //c只需要10个比特位
	int _d : 30; // ......
};

int main()
{
	struct A a;
	printf("%d\n", sizeof(a)); //8个字节
	return 0;
}

位段的内存分配

位:二进制位 (比特位) 1字节 = 8比特

1.位段成员可以是 int , unsigned int , signed int 或 char(整型家族)

2.位段的空间是按照需要以4个字节(int)或者 1 个字节(char)的方式开辟的。

开辟空间的时候是按照整型开辟的。

与结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台问题。

内存 -> 十六进制

自定义数据类型 - 枚举

//枚举类型
enum Sex
{
	//枚举的可能类型 - 枚举常量
	MALE,
	FEMALE,
	SECRET
};

int main()
{
	enum Sex s = MALE;
	//s = FEMALE;
    printf("%d %d %d", MALE, FEMALE, SECRET); // 0 1 2
	return 0;
}

给常量赋初始值,后面不可再修改

//枚举类型
enum Sex
{
	//枚举的可能类型
	MALE = 2,
	FEMALE = 4,
	SECRET = 8
};

int main()
{
	enum Sex s = MALE;
	//s = FEMALE;
	printf("%d %d %d", MALE, FEMALE, SECRET); //2  4  8
	return 0;
}

相当于 const 字符串常量

C语言的源代码 --- 预编译 ---> 编译 ---> 链接 ---> 可执行程序

​ (可调试阶段)

可以用 #define 定义常量,为什么要用枚举?

1.增加代码可读性和可维护性

2.和#define定义的标识符比较枚举有类型检查,更加严谨

3.防止命名污染(封装)

4.便于调试

5.使用方便,一次可定义多个常量

自定义数据类型 - 联合

联合 - 联合体 - 共用体 : 共用同一块空间。

开辟一块内存空间,大家都从头开始放。

#include <stdio.h>

union Un
{
	char c;
	int i;
};

int main()
{
	union Un u;
	printf("%d\n", sizeof(u)); //4
	return 0;
}

联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少有能力保存最大的那个成员)

成员不能同时存在使用。

大小端

int a = 0x11223344
低地址-----------------------------高地址
.......[][][11][22][33][44][][][][][][][].....大端存储模式
.......[][][44][33][22][11][][][][][][].....小端存储模式

判断平台大小端存储方式?

int a = 1;  0x 00 00 00 01
低地址   -----      高地址
....[][01][00][00][00][][][] ......  小端存储模式  - 1
...[][00][00][00][01][][][] ......  大端存储模式   - 0

代码

#include <stdio.h>

int main()
{
	int a = 1;
	if (*(char*)&a == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

优化 1

#include <stdio.h>

int check_sys()
{
	int a = 1;
	// 1 - 小端
	// 0 - 大端
	return *(char*)&a;
}

int main()
{
	int a = 1;
	int ret = check_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

优化 2 ----- 联合体

联合体大家都从开始放

int check_sys()
{
	union Un
	{
		char c;
		int i;
	}u;
	u.i = 1;
    //返回1 --- 表示小端
    //返回0 --- 表示大端
	return u.c;
}

共同体和结构体内存存储方式不一样。

联合体(共用体)大小的计算

1.联合体的大小至少是最大成员的大小。

2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

数组的对齐数 是 单个元素的对齐数。