C语言笔记14:结构体、联合体、枚举

文章目录

C语言笔记14:结构体、联合体、枚举

一、结构体的声明

声明格式

c 复制代码
struct tag
{
	member-list;
}variable-list;

声明和初始化示例

c 复制代码
struct Stu
{
	char name[20];
	int age;
	char gender[5];
	char id[20];
}

int main()
{
	struct Stu s0 = {"张飞",20,"男","2016010601"};
	
	struct Stu s1 = {.age = 21,.name = "朱棣",.gender = "男",.id = "2026010602"};
	
	return 0;
}

特殊声明:匿名结构体类型

c 复制代码
struct
{
	int a;
	char b;
	float c;
}x;

struct 
{
	int a;
	char b;
	float c;
}y,*p;

这个赋值合法吗?

c 复制代码
p = &x;

答案是不合法,因为&x类型和p的类型不一样。

结构体自引用

c 复制代码
struct Node
{
	int data;
	struct Node next;
};

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

上面两个自引用的方式中,下面的是正确的

加入typedef的自引用

c 复制代码
typedef struct Node
{
	int data;
	Node* next;
}Node;

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

下面的是正确的。

二、结构体内存对齐

内存对齐规则

每个结构体变量都有一个对齐数 ,这个对齐数的值是编译器默认对齐数和该成员变量大小的较小值 。成员变量对齐到对齐数的整数倍。结构体大小 所有成员变量中最大对齐数的整数倍

练习

c 复制代码
struct S1
{
    char c1;
    int i;
    char c2;
};

struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
	char c1;
	struct S3 s3;
	double d;
};

#include <stdio.h>
int main()
{
	printf("%d\n", sizeof(struct S1));//12
	printf("%d\n", sizeof(struct S4));//32
}

为什么存在内存对齐

内存对齐是拿空间换时间的做法,CPU每次访问的地址都是4字节(32位)或者8字节(64位),并且变量的起始位置也是4或8字节的倍数,结构体内部遵循了内存对齐的规则之后,CPU想取出结构体内的int或者double这种类型的变量,就不会面临一个int横跨两个4字节或者8字节块的情况。就只用进行一次内存读取,提高了效率。

声明结构体最佳实践

让占用空间小的成员尽量集中在一起

c 复制代码
struct s1
{
	char a;
	int b;
	char c;
};

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

修改默认对齐数

c 复制代码
//设置为1
#pragma pack(1)
struct s1
{
	char a;
	int b;
	char c;
};
//取消设置
#pragma pack()

#include <stdio.h>
int main()
{
	printf("%zd\n",sizeof(s1));
	return 0;
}

结果:

三、结构体传参

c 复制代码
struct S
{
	int data[1000];
	int num;
};
struct S s = {{1,2,3,4},1};

#include <stdio.h>

void print1(struct S s)
{
	printf("%d\n",s.num)
}

void print2(struct S* ps)
{
	printf("%d\n",ps->num);
}

int main()
{
	print1(s);
	print2(&s);
	
	return 0;
}

哪个好?

print1是传值,传参压栈就是多个指令push。造成极大开销。print2是传址,开销较小。

四、位段

声明结构体时,在成员变量后面加:数字就实现了位段。位段的类型必须是整型。位段通常用于节省结构体空间。在网络数据报中常用。

c 复制代码
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
#include <stdio.h>
int main()
{
	struct A a = { 1,3,255,1 };
	printf("%zd\n", sizeof(A));

	return 0;
}

&a:0x005DFE1C

1.int 位段被当成有符号数还是⽆符号数是不确定的。

2.位段中最⼤位的数⽬不能确定。(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会 出问题。)

3.位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。

4.当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时,是舍弃 剩余的位还是利⽤,这是不确定的。

在上面代码中,当前平台会利用一个字节的剩余位,字节用完之后会从左向右使用新字节,比如int_c:10;,紫色部分,横跨三个字节,但是字节的拼接顺序应该是最右边的0 + 中间的01111111 + 最左边的1,也就是小端字节序。0011111111:255

位段使用注意事项

内存中每个字节一个地址,但是字节内的比特位是没有地址的,所以&a.a这种写法是错的。想要赋值给位段成员,要先拿一个值接收再传给位段:

c 复制代码
struct A
{
	int _a:2;
	int _b:5;
	int _c:10;
	int _d:30;
};
#include <stdio.h>
int main()
{
	struct A a = {0};
	int input = 0;
	scanf("%d\n",&input);
	a.a = input;
}

五、联合体

只为最大成员分配空间,所有成员这块空间,叫做联合体,也叫共用体。

c 复制代码
union tag
{
	member-list;
}variable-list;

联合体内一个大的成员变量和一个小的成员变量,小成员变量使用联合体的内存时和数组一样,是从低地址到高地址使用的,可以利用这个特点来判断机器的大小端。

联合体的大小

联合体的大小至少是最大的成员变量,为什么说是至少呢?因为联合体也要遵守内存对齐的规则,联合体大小要对齐到最大对齐数的整数倍,当最大对齐数不是成员变量大小的时候,比如编译器默认对齐数是8的时候。联合体大小就要对齐到8的整数倍。

联合体使用案例

c 复制代码
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;
};

六、枚举

和结构体以及联合体不同,声明枚举类型只会声明一些常量而不是变量。

使用枚举常量的优点

  • 可读性强
  • 枚举有类型检查
c 复制代码
//方案1
#define Green 1
#define Red 2
#define Blue 3.0f
int main()
{
	int clr0 = Green;
	int clr1 = Red;
	int clr2 = Blue;

	return 0;
}
c 复制代码
//方案2
enum Color
{
	Green,
	Red,
	Blue = 3.0f;
};
int main()
{
	enum Color clr0 = Green;
	enum Color clr1 = Red;

	return 0;
}

可以看到,在枚举内部定义常量时必须是整型,而#define却可以随意定义。

  • 枚举遵循作用域规则,#define不遵循
    C++类型检查通常比C语言更严格,比如一些C语言可以使用的强制类型转换在C++做不到,同样定义一个枚举类型变量,C语言可以给这个变量赋一个整数,而C++必须赋值枚举类型内部的常量。
    }

    c 复制代码
    //方案2
    enum Color
    {
    	Green,
    	Red,
    	Blue = 3.0f;
    };
    int main()
    {
    	enum Color clr0 = Green;
    	enum Color clr1 = Red;
    
    	return 0;
    }

可以看到,在枚举内部定义常量时必须是整型,而#define却可以随意定义。

  • 枚举遵循作用域规则,#define不遵循
    C++类型检查通常比C语言更严格,比如一些C语言可以使用的强制类型转换在C++做不到,同样定义一个枚举类型变量,C语言可以给这个变量赋一个整数,而C++必须赋值枚举类型内部的常量。
相关推荐
崇山峻岭之间9 小时前
Matlab学习记录30
开发语言·学习·matlab
stillaliveQEJ9 小时前
【JavaEE】Spring IoC(二)
java·开发语言·spring
民乐团扒谱机9 小时前
【微实验】MATLAB 仿真实战:多普勒效应 —— 洒水车音乐的音调变化仿真
开发语言·matlab·多普勒效应·多普勒频移
寻星探路9 小时前
【Python 全栈测开之路】Python 基础语法精讲(一):常量、变量与运算符
java·开发语言·c++·python·http·ai·c#
朔北之忘 Clancy9 小时前
2020 年 6 月青少年软编等考 C 语言一级真题解析
c语言·开发语言·c++·学习·算法·青少年编程·题解
csbysj20209 小时前
组合实体模式
开发语言
一路往蓝-Anbo9 小时前
第五篇:硬件接口的生死劫 —— GPIO 唤醒与测量陷阱
c语言·驱动开发·stm32·单片机·嵌入式硬件
万物皆字节9 小时前
Spring Cloud Gateway 启动流程源码分析
java·开发语言·spring boot
问水っ9 小时前
Qt Creator快速入门 第三版 第16-7章 其他内容
开发语言·qt