当前位置: 首页 > article >正文

C语言——结构体,位段,枚举和联合

目录

前言

结构体

1含义

2语法

3匿名结构体

4结构体自引用

5结构体的定义与初始化

6内存对齐

7修改对齐数

8结构体传参

位段

1含义

2位段的内存分配

​编辑3位段的问题 

4位段的应用

枚举

1含义

2定义

3枚举优点

4枚举使用

联合

1含义

2定义

3特点

4计算

通讯录


前言

C语言的结构体知识学好了的话对后面学习C++类对象就简单很多了

结构体

1含义

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

2语法

struct tag//类型名
{
    member-list;//成员变量
}variable-list;//可在此时定义出结构体类型的变量 它是全局变量

假如我们用结构体来描述一个学生:

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}S1;//分号不能丢

3匿名结构体

也就是结构体不完全声明

//匿名结构体类型
struct
{
	int a;
	char b;
	float c;
}x;

匿名结构体省略了tag(结构体类型名),而且它只能用一次(声明后立即定义,不能声明后定义)

能否用指针来接收匿名结构体变量?

不能,编译器会认为这个两个类型(vs2019编都编不过,vs2022能编过!)

4结构体自引用

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

int main()
{
	printf("%d", sizeof(struct Node));
	return 0;
}

上面这种引用行吗?

编译器认为:你在内部定义了同一个结构体成员,那这个成员变量里又包含了同一个结构体成员...这个大小是无穷大的

所以正确的结构体自引用

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

这种在链表中用的最多

其它的自引用定义:

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

这种写法是错误的:结构体typedef之前内部并不知道

//正确写法
typedef struct Node
{
	int data;
	struct Node* next;
}N;

5结构体的定义与初始化

struct Poin
{
	int x;
	int y;
}p1 = {1,2};//可以在这里初始化

struct stu
{
	int date;
	struct Poin p;
}s1;//全局变量

int main()
{
	struct stu s2 = { 100,{20,30} };//局部变量 按顺序初始化
	//struct stu s2 = { .p={20,30},.date=100 };//指定成员初始化
	printf("%d %d %d\n", s2.date, s2.p.x, s2.p.y);
	return 0;
}

6内存对齐

计算结构体的大小:这个非常重要的一个内容!!!

来看下面的一段代码: 

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

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

int main()
{
	printf("s1的大小:%d\n", sizeof(struct s1));
	printf("s2的大小:%d", sizeof(struct s2));
	return 0;
}

int是4字节,char是1字节,两个int和一个char加起来不是6字节吗?

不同的结构体放着相同的变量,只是顺序不同,为什么大小不一样?

介绍一个宏:offsetof,用来查看结构体变量相对于内存的偏移位置

按照打印出来的内存偏移位置进行安排成员变量的放置

一共9个字节就能结束了,偏偏还要在浪费3个字节,前面也浪费3个字节,为什么?                    这时我们来看看内存对齐的规则:

1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到对齐数整数倍的地址处。

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值
在VS中默认的值为8 而gcc是没有默认对齐数的
3. 结构体总大小为最大对齐数(每个成员变量计算出来的对齐数的最大值)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处

结构体嵌套的结构体大小:

#include<stdio.h>

struct S3//总大小:16
{
	double d;//8 8 -> 8
	char c;  //1 8 -> 1
	int i;   //4 8 -> 4
};
struct S4
{
	char c1;     //1  8 -> 1
	struct S3 s3;//16 8 -> 8
	double d;    //8  8 -> 8
};
int main()
{
	printf("%d\n", sizeof(struct S4));
	return 0;
}

那为什么存在内存对齐? 

1. 平台原因(移植原因):
可能硬件平台只能在某些地址处取某些特定类型的数据(否则硬件中断);
2. 性能原因:
为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

这本质上就是以空间换时间来提高效率!

那在设计结构体时:既要省空间,又要提高效率:

尽量让字节小的成员变量放在一起

#include<stdio.h>

struct s1
{
	char c1;
	int i;
	char c2;
};

struct s2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d %d", sizeof(struct s1), sizeof(struct s2));
	return 0;
}

7修改对齐数

如果对应VS的默认对齐数不满意(最好不修改!),可以自己设定对齐数

#pragma pack(1)
//在#pragma的范围内的对齐数是1
struct s3
{
	char c1;
	int i;
	char c2;
};
#pragma()

struct s4
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("s3=%d s4=%d", sizeof(struct s2), sizeof(struct s3));
	return 0;
}

8结构体传参

struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };

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;
}

关于结构体传参,尽量使用传址传参

因为一个结构体大小可能很大,如果传值传参会加大系统资源的开销,导致性能的下降;而使用传址传参时数据不希望进行修改,要把const加上

位段

1含义

位段是一种特殊的自定义类型;

对空间的利用精打细算

a 位段的成员必须是 int、char(整形家族);
b 位段的成员名后边有一个冒号和一个数字

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

与结构体相比大小方面有什么不同吗?

struct A//如果是结构体
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

struct B
{
	int _a;
	int _b;
	int _c;
	int _d;
};

int main()
{
	printf("A=%d B=%d", sizeof(struct A),sizeof(struct B));
	return 0;
}

答案:位段比结构体大小要小,更节省空间了!那这是怎么实现的呢?

2位段的内存分配

位段后面的数字代表要分配多少个bit空间给该成员;

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

#include<stdio.h>

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

3位段的问题 

1. int 位段被当成有符号数还是无符号数
2. 位段中最大位的数目

(16位机器最大16,32位机器最大32,如果写成27,在16位机器会出问题)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配

(在VS时从右向左)
4. 开辟的空间无法容纳下个位段时,是舍弃后另外开辟空间还是利用

(在VS时舍弃剩余的位)

4位段的应用

应用于各种协议(信息传到下一层时需要用到)

枚举

1含义

枚举就是一一列举;在现实生活中把一个星期进行枚举:星期一,星期二,星期三...                    那么枚举怎么用代码来表示呢?

2定义

enum Day//星期
{
	Mon = 1,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

enum Day是一个枚举类型

枚举括号内的内容都是常量

枚举默认从0开始(与数组一样);但你也可以自己修改(Mon = 1)

3枚举优点

以上对于星期的枚举:我们也可以用#define Mon 1,#define Tues 2来表示,那是不是就说枚举就可以被替换了?

a 使用枚举方便调试(而#define在调试前就被替换成常量);

b 枚举有类型检查,更严谨写;

b 使用枚举能增加代码的可读性与可维护性;

c 枚举内定义变量不存在命名冲突(而#define可能有命名污染问题)

4枚举使用

enum Color//颜色
{
    RED=1,
    GREEN=2,
    BLUE=4
};
int main()
{
    enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
    return 0;
}

联合

1含义

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,这些成员公用同一块空间(所以联合也叫共用体)。

2定义

#include<stdio.h>
//联合类型的声明
union Un
{
	char c;
	int i;
};
int main()
{
	union Un un;
	printf("%d\n", sizeof(un));
	return 0;
}

3特点

a 由于联合类型共有内存空间,所以比其它的结构体而言节省空间

b 成员共有内存空间,所以定义变量时成员不能同时出现(后一个会改变前一个的数据)

使用场景1:判断大小端(之前通过地址方式访问判断)

#include<stdio.h>
int sys_call()
{
	//匿名联合
	union
	{
		char c;
		int i;
	}Un;
	Un.i = 1;
	// 01 00 00 00 还是 00 00 00 01
	//(c与i公用1字节的内存空间)
	return Un.c;
}

int main()
{
	if (sys_call())
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

使用场景2:设计一个礼物清单 

struct git_list
{
	double price;
	char name[20];
	char author[20];
	int page;
	char form[20];
	double size;
	char flavour[20];
	char materials[20];
};

//利用联合体设计清晰且省空间
struct git_list
{
	double price;
	char name[20];

	union
	{
		char author[20];
		int page;
	}book;

	union
	{
		char form[20];
		double size;
	}mug;

	union
	{
		char flavour[20];
		char materials[20];
	}cooike;
};

4计算

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

所以:联合大小 != 最大成员大小

#include<stdio.h>

union Un1
{
	char c[5];
	int i;
};

int main()
{
	printf("%d\n", sizeof(union Un1));
	return 0;
}

你可能会因为大小是5(char c[5]一共是5字节,比i 4字节大),但实际上:

最大是5没错,但它不是最大对齐数(max(char,int) = 4)的整数倍,进行要进行对齐

通讯录

实现一个通讯录,具备以下功能:

1增加联系人(名字,年龄,性别,电话,地址);

2删除联系人的信息;

3查找联系人的信息;

4更改联系人的信息;

5打印通讯录;

6对通讯录进行排序;

//Contact.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

#define Max_Size 100
#define Max_NameSize 20
#define Max_SexSize 5
#define Max_CallSize 20
#define Max_AddrSize 30

enum
{
	ext,
	add,
	del,
	check,
	modify,
	print,
	sort,
};

typedef struct Mess
{
	char name[Max_NameSize];
	int age;
	char sex[Max_SexSize];
	char call[Max_CallSize];
	char addr[Max_AddrSize];
}Mess;

typedef struct Contact
{
	int sz;
	Mess message[Max_Size];
}Contact;

void Add(Contact* con);
void Del(Contact* con);
void Check(const Contact* con);
void Modify(Contact* con);
void Print(const Contact* con);
void Sort(Contact* con);

//Contact.c
#include"contact.h"

void Add(Contact* con)
{
	assert(con);
	if (con->sz == Max_Size)
	{
		printf("Contact Is Full\n");
		return;
	}

	printf("Input Name:");
	gets(con->message[con->sz].name);
	//scanf("%s", &con->message[con->sz].name);
    printf("Input Age:");
	scanf("%d", &con->message[con->sz].age);//如果输入的是非法字符串?
	getchar();//清空缓冲区的'\n'
	printf("Input Sex:");
	gets(con->message[con->sz].sex);
	//scanf("%s", &con->message[con->sz].sex);
	printf("Input Call:");
	gets(con->message[con->sz].call);
	//scanf("%s", &con->message[con->sz].call);
	printf("Input Addr:");
	gets(con->message[con->sz].addr);
	//scanf("%s", &con->message[con->sz].addr);
	con->sz++;

	printf("Add Finish\n");
}

void Print(const Contact* con)
{
	assert(con);
	if (con->sz == 0)
	{
		printf("Contact Is Empty\n");
		return;
	}
	printf("%-20s %-5s %-5s %-11s %-30s\n", "name", "age", "sex", "call", "addr");
	for (int i = 0; i < con->sz; i++)
	{
		printf("%-20s %-5d %-5s %-11s %-30s\n",
			con->message[i].name, con->message[i].age, 
			con->message[i].sex,con->message[i].call,
			con->message[i].addr);
	}
}

static int Find(const Contact* con,char Name[])
{
	for (int i = 0; i < con->sz; i++)
	{
		if (strcmp(con->message[i].name,Name)==0)//用strcmp比
		{
			printf("Find Sucess\n");
			return i;
		}
	}
	printf("Find Error\n");
	return -1;
}

void Del(Contact* con)
{
	assert(con);
	char InputName[Max_NameSize];
	printf("Input Del Name:");
	gets(InputName);
	int DelPos = Find(con, InputName);
	if (DelPos == -1) return;
	for (int i = DelPos; i < con->sz - 1; i++)
	{
		con->message[i] = con->message[i + 1];//后一个往前一个覆盖
	}
	con->sz--;
	printf("Del Sucess\n");

}

void Check(const Contact* con)
{
	assert(con);
	printf("Input Check Name:");
	char Check_Name[Max_NameSize];
	gets(Check_Name);
	int pos = Find(con, Check_Name);
	if (pos == -1) return;
	printf("Check Result:\n");
	printf("%-20s %-5s %-5s %-11s %-30s\n", "name", "age", "sex", "call", "addr");
	printf("%-20s %-5d %-5s %-11s %-30s\n",
		con->message[pos].name, con->message[pos].age,
		con->message[pos].sex, con->message[pos].call,
		con->message[pos].addr);
}

void Modify(Contact* con)
{
	assert(con);
	char Modify_Name[Max_NameSize];
	printf("Input Modify Name:");
	gets(Modify_Name);
	int pos = Find(con, Modify_Name);
	if (pos == -1) return;

	printf("Modify Name:");
	gets(con->message[pos].name);
	printf("Input Modify Age:");
	scanf("%d", &con->message[pos].age);
	getchar();
	printf("Input Modify Sex:");
	gets(con->message[pos].sex);
	printf("Input Modify call:");
	gets(con->message[pos].call);
	printf("Input Modify addr:");
	gets(con->message[pos].addr);
	printf("Modify Sucess\n");
}

int cmp(const void* a, const void* b)
{
	return ((*((Contact*)a)).message->age - (*((Contact*)b)).message->age);
}

void Sort(Contact* con)
{
	qsort(con, con->sz, sizeof(con->message[0]), cmp);
	printf("排序完成\n");
}

//test.c
#include"contact.h"

void memu()
{
	printf("********************************\n");
	printf("*****   0.exit    1.add    *****\n");
	printf("*****   2.del     3.check  *****\n");
	printf("*****   4.modify  5.print  *****\n");
	printf("*****         6sort        *****\n");
}

int main()
{
	Contact con;
	//对con空间进行清理
	memset(&con, 0, sizeof(con));
	int input;
	do
	{
		memu();
		printf("Please Select:");
		//防止读到字符串时发生死循环
		input = 123456;//防止下一次输入的是字符后input还是上一次的值
		scanf("%d", &input);
		getchar();//输入字符串时:清空缓冲区的'\n'

		switch (input)
		{
		case ext:
			printf("Process Exit");
			break;
		case add:
			Add(&con);
			break;
		case del:
			Del(&con);
			break;
		case check:
			Check(&con);
			break;
		case modify: 
			Modify(&con);
			break;
		case print:
			Print(&con);
			break;
		case sort:
			Sort(&con);
			break;
		default:
			printf("Select Error,Please Try Again\n");
			break;
		}
	}while(input);

	return 0;
}

但上面的通讯录无法做到:

通讯录满了无法再存信息(学习动态内存管理后解决);

关闭程序时通讯录信息还存在(学习完文件操作后解决);

以上便是全部内容,有问题欢迎在评论区指正,感谢观看!


http://www.kler.cn/a/474253.html

相关文章:

  • 网络基础1 http1.0 1.1 http/2的演进史
  • MFC读写文件实例
  • 51单片机——定时器中断(重点)
  • Ubuntu上安装Apache Spark
  • 闲谭SpringBoot--ShardingSphere分库分表探究
  • 33.3K 的Freqtrade:开启加密货币自动化交易之旅
  • failed to resolve sdk 的解决方法
  • 华为设备的监控和管理
  • 基于Spring Boot的车辆违章信息管理系统(LW+源码+讲解)
  • 开源AI智能名片商城小程序在个人品牌建设中的应用与“展温度”策略融合深度探索
  • 【线性代数】通俗理解特征向量与特征值
  • 【Logstash03】企业级日志分析系统ELK之Logstash 过滤 Filter 插件
  • 9 异常
  • PyTorch快速入门教程【小土堆】之完整模型验证套路
  • 网络安全系列 之 协议安全
  • ros2-4.2 用python实现人脸识别
  • 服务器证书不受信任是什么问题?
  • 有关Redis的相关概述
  • Linux(CentOS7)安装JDK和Maven
  • Unity导入特效,混合模式无效问题
  • 使用XMLHttpRequest进行AJAX请求的详解
  • 使用 uniapp 开发微信小程序遇到的坑
  • 毕业项目推荐:基于yolov8/yolov5/yolo11的动物检测识别系统(python+卷积神经网络)
  • Mac M2基于MySQL 8.4.3搭建(伪)主从集群
  • 【Go】:全面掌握 Sentinel — 构建高可用微服务的流量控制、熔断、降级与系统防护体系
  • [Unity] 【图形渲染】Unity Shader光照基础2-标准光照模型