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

第十六天(结构体初学)

现在我要描述一个人,这个人有很多的信息,人也有很多个
因此我就需要建立多个数组,
去操作这些数组就比较麻烦
因此我们希望有一个什么东西去描述这个人的多重属性
每次描述人的时候描述的这些属性可能不一样,因此这个类型
肯定是程序员自建类型
来根据程序员的需求来建立这个类型
c语言里面允许这么做 -- 结构体

结构体:多重单一属性,或着多结构组合在一起形成的这么一个结构

c语言语法:struct建立一个结构
struct 结构体的名字
{
成员变量类型1 成员变量名1;
成员变量类型2 成员变量名2;
成员变量类型3 成员变量名3;
成员变量类型4 成员变量名4;
......
};

eg:
struct Student
{
char *name;
int age;
char addr[1024];
char sex;
};    

成员的引用:
如果这个结构定义出来变量是一个普通的变量
那么结构成员用 . 去引用
如果这个结构定义出来变量是一个指针
那么结构成员用 -> 去引用

结构体变量的定义:
struct Student 变量名; ->栈/data开辟内存空间
struct Student * 指针变量名;//在栈上面开了一个指针变量
指针变量名 = malloc(sizeof(struct Student));

    eg:
struct Student pl;//栈开辟一个变量叫pl 类型是struct Studen
//使用pl的成员
pl.name = malloc(1024);
strcpy(pl.name , "penglei");
pl.age = 20;
//(&pl) ->age
.....

        struct Student * pl;//栈开辟一个变量叫pl 类型是struct Studen *,还是一个野指针
//使用pl的成员
pl = malloc(sizeof(struct Student));
pl ->name = malloc(1024);
strcpy(pl ->name , "penglei");
pl ->age = 20;
//(*pl).age = 20;
.....

练习:
1 以上面的Student为基础,从键盘录入学生的信息
录入完毕保存到Student变量里面去
然后打印出来
Student变量和指针都弄一遍

    2 以上面的Student为基础,我现在要维护多个学生,怎么办?
把 不连续结构体变量在堆上面的内存空间分布.png 和 在堆上面开结构体数组.png
这两个图片上面的结构用代码实现


结构体的内存空间分布
1 连续存储,前面的成员的放在前面,后面成员放在后面
struct Student
{
char *name;
int age;
char addr[1024];
char sex;//'m'  'w'
};
name放在age前面,age放在addr的前面,addr放在sex的前面
struct Student pl;
底层值有如下特性:&pl == &pl.name
typeof(&pl) -> struct Student *
typeof(&pl.name) -> char **

struct Student *p = &pl;//没问题
p = (struct Student *)&pl.name;

    2  不允许本结构里面套本结构的变量,但是可以有本结构的指针存在
struct Student
{
int age;
struct Student st;//不允许这么套  
};
struct Student
{
int age;
struct Student * st;//我们经常这么干 我这么做是为了指向一个本结构的变量
//这个是链表专题
};

    3 结构体的大小等于所有的成员的字节数的和
struct Student
{
成员1;
成员2;
...
成员n;
};
struct Student pl;
sizeof(pl) = sizeof(pl.成员1) + sizeof(pl.成员2) + ..... + sizeof(pl.成员n)
原理是如上
但是它必须符合4字节对齐(有可能是类型对齐)


结构体的成员对齐有两种方式
1.  4字节对齐 -- 每一个成员的地址都应该是4的整数倍
如果是同类型,或者某一个类型能将字节更少的类型包含,那么他们会合在一起形成一个数组
只会包到4个字节,如果超了就不包了
struct node
{
char a;
int b;
};
sizeof(struct node) == 8
struct node1
{
char a;
char a1;//a a1看成一个数组 并且不到4字节,因此包了
int b;
};
sizeof(struct node1) == 8
struct node2
{
char a;
char a1;
short d;//d将a a1给包了  没有超过4字节,因此一起捆在一起了
int b;
};
sizeof(struct node2) == 8
struct node3
{
char a;
int b;
char a1;
};
sizeof(struct node3) == 12

    2 类型对齐 --- 结构里面最长的那个基本类型是多少字节,那么就按这个字节数来进行对齐
struct node1
{
char a;
short c;
char d;
};
sizeof(struct node1) == 6
struct node2
{
char a;
int c;
char d;
};
sizeof(struct node1) == 12
struct node3
{
char a;
long c;
char d;
};
sizeof(struct node1) == 24

成员的位置是会影响内存大小的,为了节约内存空间,因此成员的顺序就是程序员应该考虑的问题了
相同类型尽量放在一起,小字节的放在大字节的前面

    对齐晚上可以多去试一下


结构体初始化问题,结构体初始化一般我们写代码很少见,一般都是先定义
然后逐成员进行赋值
struct Student
{
char *name;
int age;
char addr[1024];
char sex;//'m'  'w'
};
1 依次初始化
struct Student pl = {
NULL,//给name
18,//给age
"王八屯",//初始化addr数组
'm'//给sex
};

    2 非顺序初始化
struct Student pl = {
.addr = "王八屯",//初始化addr数组
.age = 18,//给age
.sex = 'm',//给sex 
.name = NULL//给name
};

    3 结构体数组的初始化,数组元素顺序初始化
struct Student pl[2] = {
{//pl[0]
NULL,//给name
18,//给age
"王八屯",//初始化addr数组
'm'//给sex
},
{//pl[1]
NULL,//给name
20,//给age
"长沙",//初始化addr数组
'w'//给sex
}
};
3 结构体数组的初始化,数组元素非顺序初始化
struct Student pl[2] = {
[1] = {//pl[1]
NULL,//给name
18,//给age
"王八屯",//初始化addr数组
'm'//给sex
},
[0] = {//pl[0]
NULL,//给name
20,//给age
"长沙",//初始化addr数组
'w'//给sex
}
};

    

作业 
1 以上面的Student为基础,我现在要维护多个学生,怎么办?
把 不连续结构体变量在堆上面的内存空间分布.png 和 在堆上面开结构体数组.png
这两个图片上面的结构用代码实现

// 1 以上面的Student为基础,我现在要维护多个学生,怎么办?
//         把 不连续结构体变量在堆上面的内存空间分布.png 和 在堆上面开结构体数组.png
//             这两个图片上面的结构用代码实现
#include<stdio.h>
#include<stdlib.h>//用struct
#include<string.h>//用strcpystruct Student
{char *name;int age;char addr[1024];char sex;
};
int main()
{if(0){//维护多名学生struct Student  pl2[3];for(int i = 0;i < 3;i++){pl2[i].name = (char *)malloc(1024);scanf("%s",pl2[i].name);scanf("%d",&(pl2[i].age));scanf("%s",pl2[i].addr);while((pl2[i].sex = getchar()) == '\n' || pl2[i].sex == ' ' || pl2[i].sex == '\t');}for(int i = 0;i < 3;i++){printf("%s  %d  %s  %c\n",pl2[i].name,pl2[i].age,pl2[i].addr,pl2[i].sex);}//释放堆空间for(int i = 0;i < 3;i++){free(pl2[i].name);}}if(0){//请用结构体指针 将上面的这个结构体数组再弄一遍 也就是将结构体数组开到堆上面去struct Student  *pl3;pl3 = (struct Student *)calloc(3,sizeof(struct Student));for(int i = 0;i < 3;i++){pl3[i].name = (char *)malloc(1024);printf("请输入第%d个学生的名字:",i+1);scanf("%s",pl3[i].name);printf("请输入第%d个学生的年龄:",i+1);scanf("%d",&(pl3[i].age));printf("请输入第%d个学生的地址:",i+1);scanf("%s",pl3[i].addr);printf("请输入第%d个学生的性别:",i+1);while((pl3[i].sex = getchar()) == '\n' || pl3[i].sex == ' ' || pl3[i].sex == '\t');}for(int i = 0;i < 3;i++){printf("%s  %d  %s  %c\n",pl3[i].name,pl3[i].age,pl3[i].addr,pl3[i].sex);}//释放堆空间for(int i = 0;i < 3;i++){free(pl3[i].name);}free(pl3);}if(1){//开辟内存空间struct Student *pl4[3];for(int i = 0;i < 3;i++){   //每个内存空间单独分配内存空间pl4[i] = (struct Student *)calloc(1,sizeof(struct Student));//为第一个名字开辟内存空间pl4[i] ->name = (char *)malloc(1024);printf("请输入第%d个学生的名字:",i+1);scanf("%s",pl4[i] ->name);printf("请输入第%d个学生的年龄:",i+1);scanf("%d",&(pl4[i] ->age));printf("请输入第%d个学生的地址:",i+1);scanf("%s",pl4[i] ->addr);printf("请输入第%d个学生的性别:",i+1);while((pl4[i] ->sex = getchar()) == '\n' || pl4[i] ->sex == ' ' || pl4[i] ->sex == '\t');}for(int i = 0;i < 3;i++){printf("%s  %d  %s  %c\n",pl4[i] ->name,pl4[i] ->age,pl4[i]->addr,pl4[i] ->sex);}//释放堆空间for(int i = 0;i < 3;i++){free(pl4[i] ->name);free(pl4[i]);//!!!需要加上这句,开始我只释放了 name 字段,但没有释放 pl4[i] 本身分配的结构体内存。}}return 0;
}

    2 你要描述一个人,需要描述名字和生日
struct Birthday
{
int y;
int m;
int d;
};
struct Person
{
char * name;
struct Birthday bir;
//struct Birthday * bir;
};
你需要至少维护三个人,信息从键盘给入

// 2 你要描述一个人,需要描述名字和生日
//         struct Birthday
//         {
//             int y;
//             int m;
//             int d;
//         };
//         struct Person
//         {
//             char * name;
//             struct Birthday bir;
//             //struct Birthday * bir;
//         };
//         你需要至少维护三个人,信息从键盘给入
#include <stdio.h>
#include <stdlib.h>
struct Birthday
{int y;int m;int d;
};//Birthday在栈空间
struct Person
{char * name;struct Birthday bir;
};//Birthday在堆空间
struct Person1
{char * name;struct Birthday * bir;
};
int main()
{//方法一;人名记录采用堆空间多部分记录法,年龄在栈空间if(0){struct Person *pl[3];for(int i = 0;i < 3;i++){pl[i] = (struct Person *)calloc(1,sizeof(struct Person));pl[i] ->name =(char *)calloc(1,200);printf("请输入第%d个人的名字:",i + 1);scanf("%s",pl[i] ->name);printf("请输入第%d个人的出生日期:",i + 1);scanf("%d%d%d",&pl[i] ->bir.y,&pl[i] ->bir.m,&pl[i] ->bir.d);}//打印信息for(int i = 0;i < 3;i++){printf("第%d个人的名字是:%s\n",i + 1,pl[i] ->name);printf("第%d个人的出生日期是:%d-%d-%d\n",i + 1,pl[i] ->bir.y,pl[i] ->bir.m,pl[i] ->bir.d);}for(int i = 0;i < 3;i++){//先释放名字free(pl[i] ->name);//再释放结构体free(pl[i]);}}//方法二:人名记录采用堆空间多部分记录法,年龄在堆空间if(1){struct Person1 *pl[3];for(int i;i < 3;i++){ //给结构体分配空间pl[i] = (struct Person1 *)calloc(1,sizeof(struct Person1));//!!!注意拼写错误问题//给没开辟内存空间的名字在堆空间开辟内存空间pl[i] ->name = (char *)calloc(200,sizeof(char));//输入nmaeprintf("请输入第%d个人名:",i + 1);scanf("%s",pl[i] ->name);//输入年龄printf("请输入第%d个人的出生日期:",i + 1);//先为bir申请内存J空间pl[i] ->bir = (struct Birthday *)calloc(1,sizeof(struct Birthday));scanf("%d %d %d",&pl[i] ->bir ->y,&pl[i] ->bir ->m,&pl[i] ->bir ->d);}for(int i = 0;i < 3;i++){printf("第%d个人的名字是:%s\n",i + 1,pl[i] ->name);printf("他的出生日期是:%d-%d-%d\n",pl[i] ->bir ->y,pl[i] ->bir ->m,pl[i] ->bir ->d);}//先释放结构体里面的内存,再释放结构体for(int i = 0;i < 3;i++){//先释放bir申请的内存free(pl[i] ->bir);//再释放name申请的内存free(pl[i] ->name);//再释放结构体free(pl[i]);}}
}

    3 将结构体对齐多试一些代码

#include <stdio.h>struct Student
{char a;long c;char d;
}pl1;//直接定义两个变量pl1 pl2 这两是全局的
struct Student1
{char a;int c;char d;
}pl2;
struct Student2
{char a;int f;long c;char d;
}pl3;int main()
{printf("1111 %ld\n",sizeof(pl1));printf("2222 %ld\n",sizeof(pl2));printf("3333 %ld\n",sizeof(pl3));return 0;
}

    4 指针里面还有一个题目的,就是协议的那个,有时间继续完成

// 你一次接收有一个数据包,数据包有固定的开头
//         协议规定:
//             第一个字节是固定包头0x55//             第二个字节为有效数据的字节数,也就是有效数据有多少字节,这里就写多少//             从第三个字节开始是有效数据
//                 前面4个字节保存的是大气压强值,
//                 在后面两个字节保存的是温度值,
//                 再后面两个字节保存的是湿度值
//                 eg:
//                     你的数据的8个字节如下
//                         0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08
//                         p = 0x01(是p的最高字节) 0x02(是p的次高字节) 0x03(是p的次低字节) 0x04(是p的最低字节) 
//                         T = 0x05(是T的最高字节) 0x06(是T的最低字节)
//                         Hum = 0x07(是Hum的最高字节) 0x08(是Hum的最低字节)//             有效数据字节之后是校验位,校验方式为前面的所有字节的异或值(这种是常用的)
//                 还有一种是更加常用的(前面的所有字节加的结果保留一个字节)
//                 现在的校验方式是异或值
//                     chrck = 0x5a ^ 0x08 ^ 0x01 ^ 0x02^0x03^0x04^0x05^0x06^0x07^0x08
//         现有一个数组保存有n行值,每一行为一个数据包,请你写代码判断这些数据包是不是有效的
//             数据包有效则解析出有效的数据  大气压强、温度、湿度
//             无效则打印数据无效:并给出问题原因
//                 如:头部出错,数据字节数出错,校验出错.....   
//         char buf[5][20] = {
//             {0x5a,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x50},
//             {0x55,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x54},
//             {0x55,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x55},
//             {0x55,0x09,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x10},
//             {0x55,0x08,0x03,0x02,0x01,0x00,0x05,0x15,0x04,0x56,0x09}
//         };
#include<stdio.h>
char buf[5][20] = {{0x5a,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x50},{0x55,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x54},{0x55,0x08,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x55},{0x55,0x09,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x10},{0x55,0x08,0x03,0x02,0x01,0x00,0x05,0x15,0x04,0x56,0x09}};
//数据包检测函数,pacekt为数据包首地址,p为大气压强,t为温度,hum为湿度
int Packet_check(unsigned char *packet,unsigned int *p,int *t,int *hum)//!!!p为四个字节
{
//检验数据包包头是否合法if(packet[0] != 0x55){printf("数据包无效,包头错误\n");return -1;}
//检验数据包字节长度是否合法unsigned char len = packet[1];if(len != 0X08){printf("数据包无效,字节长度错误\n");return -1;}
//校验数据包数据是否合法(异或校验)unsigned char check = 0;for(int i = 0; i < len +1; i++)//!!!这里是从包头开始算起{check ^= packet[i];//异或累加}if (len + 2 > sizeof(buf[0])) {printf("数据包长度不足,校验失败\n");return -1;if (check != packet[len+1]){printf("数据包无效,数据校验错误\n");return -1;}}
//解析数据printf("数据包有效,数据解析如下:\n");//使用按位或更符合拼接数字的逻辑,不容易出错*p = (packet[2] << 24)|(packet[3] << 16)|(packet[4] << 8)|packet[5];//大气压强*t = (packet[6] << 8 | packet[7]);//温度*hum = (packet[8] << 8 | packet[9]);//湿度return 0;}
int main()
{for(int i = 0; i < 5; i++){unsigned int p = 0;int t = 0, hum = 0;printf("第%d个数据包:\n",i+1);if(Packet_check(buf[i],&p,&t,&hum) == 0){printf("大气压强:%d Pa\n温度:%d ℃\n湿度:%d\n",p,t,hum);}else{printf("数据包无效!\n");}}}


http://www.lryc.cn/news/595919.html

相关文章:

  • Sa-Token大师:第四章 - 企业级架构与源码实战
  • Events
  • Linux部署.net Core 环境
  • 虚幻 5 与 3D 软件的协作:实时渲染,所见所得
  • linux-日志服务
  • 同步本地文件到服务器上的Docker容器
  • 跨维智能:全新一代人形机器人 DexForce W1 Pro
  • 大模型后训练——DPO实践
  • Mosaic数据增强介绍
  • 使用ubuntu:20.04和ubuntu:jammy构建secretflow环境
  • android模拟器手机打开本地网页
  • Tailwind CSS快速上手 Tailwind CSS的安装、配置、使用
  • J2EE模式---拦截过滤器模式
  • Vite:下一代前端构建工具的革命
  • C语言---VSCODE的C语言环境搭建
  • RISC-V基金会Datacenter SIG月会圆满举办,探讨RAS、PMU性能分析实践和经验
  • vs2017 c++ 使用sqlite3数据库
  • 末日期权的双买和单买策略区别是什么?
  • 双向链表详解及实现
  • C++_Hello算法_队列
  • 基于Java+MySQL实现(Web)文件共享管理系统(仿照百度文库)
  • 188粉福
  • Spring快速整合Mybatis
  • 技术与情感交织的一生 (十)
  • nodejs:告别全局安装,npx 命令详解及其与 npm 的区别
  • 从零开始学CTF(第二十五期)
  • Gitlab-CI实现组件自动推送
  • n8n - 为技术团队提供安全的自动化工作流
  • 基于Kubernetes的微服务CI/CD:Jenkins Pipeline全流程实践
  • 知识库搭建之Meilisearch‘s 搜索引擎 测评-东方仙盟测评师