马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
目次
媒介
布局体
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;
- }
复制代码 但上面的通讯录无法做到:
通讯录满了无法再存信息(学习动态内存管理后办理);
关闭步调时通讯录信息还存在(学习完文件利用后办理);
以上便是全部内容,有标题接待在批评区指正,感谢观看!
|