【C语言】自定义类型详解(结构体+枚举+联合)
作者简介:大家好我是狂暴于涛侠本侠
🦸个人主页:狂暴于涛侠
这一次给大家带来c语言关于结构体的讲解
⛽1. 结构体
🌊1. 1结构体类型的声明
🚢结构体的声明
//结构体写法
struct Book
{
char name[20];
int price;
char id[12];
}b4,b5,b6;//b4,b5,b6是全局的 不放直接加;号也是可以的
int main()
{
//b1,b2,b3是局部变量
struct Book b1;
struct Book b2;
struct Book b3;
return 0;
}
🚢特殊的声明
匿名结构体只能用开始创建的变量,因为接下来无法创建了
//匿名结构体类型 下面的两个结构在声明的时候省略掉了结构体标签。
struct //省略结构体标签。
{
char c;
int i;
char ch;
double d;
} s;
struct //省略结构体标签。
{
char c;
int i;
char ch;
double d;
}* ps; //意思是创建了一个匿名结构体的指针,用这个指针创建了一个ps变量 *是跟着结构体的
int main()
{
ps = &s;//err //这样写法报错,虽然说两个结构体大小都一样,成员也都一样
//但是对于编译器来说这就是两个不同的结构体
//会把它当做两个完全不同的类型
return 0;
}
🌊1.2结构的自引用
在结构中包含一个类型为该结构本身的成员是否可以呢?
struct A
{
int i;
char c;
};
struct B
{
char c;
struct A sa;
double d;
}; //完全可以
struct N
{
int d;
struct N n;
//这种可以么?
//很显然是不可以的这种循环无限
};
//那是不是调用自己不可以了?
//并不是,结构体引用自己变量不可以但是引用自己的指针确可以
struct M
{
int d;
struct M* m;
};
struct //这样写法同样错 不知道结构体标签 struct M* m; 怎么执行
{
int d;
struct M* m;
};
//那这种呢?
typedef struct //我直接重命名结构体为Node行么?
//不行,程序是一步一步进行的,当进行到 struct Node* next;
//并不知道Node是什么,根本没有到命名完成的步骤
{
int data;
Node* next;
}Node; //命名完成
可以这么写
typedef struct Node
{
int data;
struct Node* next;
}Node;
这是一种链表数据结构,每个结构体分两个内存其中一个位数据域,另一个为指针域
数据域储存着数据,指针域存着下一个位置的指针
🌊1.3结构体变量的定义和初始化
#include<stdio.h>
struct S
{
char c;
int i;
}s1, s2;
struct B
{
double d;
struct S s;
char c;
};
int main()
{
struct S s3 = {'x', 20};
struct S s4 = {'a', 10} ,s5 = { 'v', 15 } ;
struct B sb = { 3.14, {'w', 100},'q' };
//.
//->
printf("%lf %c %d %c\n", sb.d, sb.s.c, sb.s.i, sb.c);
return 0;
}
🌊1.4结构体内存对齐
struct S
{
int i;//4
char c;//1
};
struct S2
{
char c1;//1
int i;//4
char c2;//1
};
//结构体内存对齐
int main()
{
struct S s = {0};
printf("%d\n", sizeof(s));//8
//为什么是8和12
struct S2 s2 = { 0 };
printf("%d\n", sizeof(s2));//12
return 0;
}
首先我们得掌握结构体的对齐规则:
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
/例如:
struct S1
{
char c1;
int i;
char c2;
}; //12
struct S2
{
char c1;
char c2;
int i; //8
};
🚢修改默认对齐数
#pragma pack(2)//以下以2为默认对其数
struct S
{
char c1;//0
int i;//
char c2;//
};
#pragma pack()//以下默认对齐数恢复正常
#pragma pack(1)//以下以1为默认对其数
struct A
{
char c1;//1 1 1
int i;//4 1 1
char c2;//1 1 1
};
#pragma pack()//以下默认对齐数恢复正常
int main()
{
printf("%d\n", sizeof(struct S));//8
printf("%d\n", sizeof(struct A));//6
return 0;
}
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
🚢小练习
//关于offsetof
#include <stddef.h>//需要引头文件
#include <stdio.h>
struct S
{
char c1;
int i;
char c2;
};
int main()
{ // 返回的偏移量
printf("%d\n", offsetof(struct S, c1));//0
printf("%d\n", offsetof(struct S, i));//4
printf("%d\n", offsetof(struct S, c2));//8
return 0;
}
🌊1.5结构体传参
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); //传地址 传递地址就4个字节,效率更快而且可以修改原变量
return 0;
}
结论:使用址传递
🌊1.6 结构体实现位段(位段的填充&可移植性)
🚢什么是位段
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
比如:
//性别 - 3
//00 男
//01 女
//10 保密
//11
//
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\n", sizeof(struct A));//输出8 为什么呢?
return 0;
}
🚢位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct A
{
//因为是int所以开始开辟4个字节 - 32bit
int _a : 2; a只用了2个bit //_a 成员占2个bit位
int _b : 5; b只用了5个bit //_b 成员占5个bit位
int _c : 10; c只用了10个bit //_c 成员占10个bit位
//还剩与15bit 但是d一次就要30个位置
//直接新建4个字节 - 32bit 不用上面的剩余的15个字节因为是vs编译器,别的编译器不一定
int _d : 30; //_d 成员占30个bit位
//int _e : 34;这种写法不允许因为int最大32个字节
};
//16位 - int - 2byte - 16 bit
//32位 - int - 4byte - 32 bit
int main()
{
printf("%d\n", sizeof(struct A));//8 4个字节32个bit位
return 0;
}
//一个例子
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;
};
//空间是如何开辟的?```
🚢位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
🚢位段的应用
⛽2.枚举
枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
🌊2.1枚举类型的定义
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
以上定义的 enum Day , enum Sex , enum Color 都是枚举类型。
{}中的内容是枚举类型的可能取值,也叫 枚举常量 。
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
例如:
enum Color//颜色
{
RED=1,
GREEN=2,
BLUE=4
};
🌊2.2枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
枚举的优点:
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
🌊2.3枚举的使用
//枚举类型就是一种类型 枚举变量
//整形就是整形
void menu()
{
printf("*****************************\n");
printf("**** 1. add 2. sub *****\n");
printf("**** 3. mul 4. div *****\n");
printf("**** 0. exit *****\n");
printf("*****************************\n");
}
enum Option
{
EXIT,//0
ADD,//1
SUB,//2
MUL,//3
DIV//4
};
int main()
{
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
break;
case SUB:
break;
case MUL:
break;
case DIV:
break;
case EXIT:
break;
default:
break;
}
} while (input);
return 0;
}
⛽3.联合体(共用体)
🌊3.1联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
比如:
🌊3.2联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)。
🚢联合小练习
判断当前计算机的大小端存储
//方法一
int check_sys()
{
int a = 1;
if ((*(char*)&a) == 1)
{
return 1;//小端
}
else
{
return 0;//大端
}
}
//方法二
int check_sys()
{
union U
{
char c;
int i;
}u;
u.i = 1;
return u.c;
//返回1 就是小端
//返回0 就是大端
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
🌊3.3联合大小的计算
运算法则
联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un
{
char a[5];//1 5
int i;//4 4的整数倍比5大取8
};
union Un1
{
short s[5];// 2 10
int a;//4 4的整数倍比10大取12
};
int main()
{
union Un u;
union Un1 u1;
printf("%d\n", sizeof(u));//8
printf("%d\n", sizeof(u1));//12
return 0;
}
⛽4.练习实现通讯录
创建三个文件
test.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
void menu()
{
printf("********************************\n");
printf("****** 1. add 2. del ******\n");
printf("****** 3. search 4. modify*****\n");
printf("****** 5. sort 6. print *****\n");
printf("****** 0. exit *****\n");
printf("********************************\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
int main()
{
int input = 0;
//创建通讯录
Contact con;//通讯录
//初始化通讯录
//给data申请一块连续的空间再堆区上
//sz=0
//capacity 初始化为当前的最大的容量
InitContact(&con);
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
//增加人的信息
AddContact(&con);
break;
case DEL:
//删除
DelContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
ModifyContact(&con);
break;
case SORT:
//自己完善
break;
case PRINT:
PrintContact(&con);
break;
case EXIT:
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
contact.h文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
#define MAX 1000
//类型的定义
typedef struct PeoInfo
{
char name[MAX_NAME];
char sex[MAX_SEX];
int age;
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
//通讯录-静态版本
typedef struct Contact
{
PeoInfo data[MAX];//存放添加进来的人的信息
int sz;//记录的是当前通讯录中有效信息的个数 默认从0开始
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人
void AddContact(Contact* pc);
//打印联系人信息
void PrintContact(const Contact* pc);
//删除联系人的信息
void DelContact(Contact* pc);
//查找指定联系人
void SearchContact(Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
contact.c文件
#include "contact.h"
#define _CRT_SECURE_NO_WARNINGS 1
//静态版本的
void InitContact(Contact* pc)
{
pc->sz = 0;
//memset(); - 内存设置
memset(pc->data, 0, sizeof(pc->data)); //pc->data 是数组名计算的是整个数组
}
// 静态版本的-增加联系人
void AddContact(Contact* pc)
{
if (pc->sz == MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
//增加一个人的信息
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name); //将pc放到data数组下标为sz的位置处.name处
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age));//除了年龄都是数组,数组就是首元素地址!所以除了年龄不用取地址
printf("请输入性别:>"); //age是变量 创建的时候没有让age是数组
scanf("%s", pc->data[pc->sz].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("增加成功\n");
}
void PrintContact(const Contact* pc)
{
int i = 0;
//打印标题 %s-20\t -代表左对齐 20代表20个字符位置\t不足用空格代替
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
//打印数据
for (i = 0; i < pc->sz; i++)
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
static int FindByName(Contact* pc, char name[])
{
int i = 0;
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i;
}
}
return -1;//找不到
}
void DelContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
if (pc->sz == 0)
{
printf("通讯录为空,无需删除\n");
return;
}
printf("请输入要删除人的名字:>");
scanf("%s", name);
//1. 查找要删除的人
//有/没有
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要删除的人不存在\n");
return;
}
//2. 删除
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功\n");
}
void SearchContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入要查找人的名字:>");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要查找的人不存在\n");
return;
}
else
{
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
//打印数据
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-20s\n",
pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr);
}
}
void ModifyContact(Contact* pc)
{
char name[MAX_NAME] = { 0 };
printf("请输入要修改人的名字:>");
scanf("%s", name);
int pos = FindByName(pc, name);
if (pos == -1)
{
printf("要修改的人不存在\n");
return;
}
else
{
printf("请输入名字:>");
scanf("%s", pc->data[pos].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pos].age));
printf("请输入性别:>");
scanf("%s", pc->data[pos].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pos].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pos].addr);
printf("修改成功\n");
}
}
测试题:
你能算出下面代码的运行结果么?
#define A 2
#define B 3
#define MAX_SIZE A+B
struct _Record_Struct
{
unsigned char Env_Alarm_ID : 4;
unsigned char Para1 : 2;
unsigned char state;
unsigned char avail : 1;
}*Env_Alarm_Record;
int main()
{
int sz = (sizeof(struct _Record_Struct) * MAX_SIZE);
printf("%d\n", sz);
return 0;
}
参考:
struct _Record_Struct
{
unsigned char Env_Alarm_ID : 4;//1 - 8bit 用了4个bit位
unsigned char Para1 : 2;//用了剩余4个中的两个bit位
unsigned char state;//1 开辟了一个新的字节
unsigned char avail : 1;//1 再次开辟新字节8个bit用了1个
}*Env_Alarm_Record;
代码运行结果为9
你能算出下面代码的运行结果么?
int main()
{
unsigned char puc[4];
struct tagPIM
{
unsigned char ucPim1;
unsigned char ucData0 : 1;
unsigned char ucData1 : 2;
unsigned char ucData2 : 3;
}*pstPimData;
pstPimData = (struct tagPIM*)puc;
memset(puc, 0, 4);
pstPimData->ucPim1 = 2;
pstPimData->ucData0 = 3;
pstPimData->ucData1 = 4;
pstPimData->ucData2 = 5;
printf("%02x %02x %02x %02x\n", puc[0], puc[1], puc[2], puc[3]);
return 0;
}
解释:
你能算出下面代码的运行结果么?(小端存储)
int main()
{
union
{
short k;
char i[2];
}*s, a;
s = &a;
s->i[0] = 0x39;
s->i[1] = 0x38;
printf("%x\n", a.k);
return 0;
}
因篇幅问题不能全部显示,请点此查看更多更全内容