结构体
定义方式
结构体,相当于是自定义的一种数据类型,C语言中结构体里面可以包含成员变量,但是不能包含函数,结构体的大小一般大于各成员变量大小之和。struct 结构体名 {成员变量类型 成员变量名;成员变量类型 成员变量名......};定义结构体时末尾的分号不能忘记。一般定义结构体时,不为结构体的成员变量赋初值。
当用结构体定义了一个变量后,这个变量就是一个集合,集合里面的成员就是变量的组成部分。所以结构体数组就类似于一个表格,行就是结构体数组的每一个元素,列就是每一个结构体的成员变量。
定义变量方式1
struct JieGouTi a={,,,,};直接初始化结构体变量。
定义变量方式2
结构体定义好了之后,struct JieGouTi a 或者定义结构体的时候就定义变量 struct{,,,}a;a.成员变量=xx;间接为结构体变量赋值。注意:结构体变量一经定义,就不能直接整体赋值了,比如a={,,,,};会报错,修改成员变量值时只能.或者->修改。
结构体大小与成员变量地址
结构体的大小遵循2个原则:
1、结构体成员变量地址对于结构体首地址的偏移量为该成员变量类型大小的整数倍(0偏移量为任何类型大小的整数倍)。
2、结构体的大小为最大成员变量基本类型的最小公倍数。
计算机遵循如此原则可能会浪费空间,但是能提高访问效率。
注意:
如果结构体中含有成员结构体,成员结构体里面的成员变量地址遵循的原则1和原则2,既要满足第一层结构体也要满足第二层结构体。
如果结构体中含有数组,整个数组不需要遵循原则2,但是需要遵循原则1,即数组首地址对于结构体首地址的偏移量为数组元素类型大小的整数倍。
指定对齐小于最大的成员变量的大小,则按照指定对齐。指定对齐大于最大的成员变量的大小,则按照最大的成员变量为准。
各种情况的代码如下:
#include<stdio.h>
typedef struct{
char a; //地址0
char b; //地址1
int c; //因为首地址需要为类型整数倍,地址为4 总大小1+1+2+4=8
}s1;
typedef struct{
char a; //地址0
int b; //地址4
char c; //地址8,因为总大小为成员基本数据类型(除数组和结构体)的最小公倍数,所以总大小为1+3+4+1+3=12
}s2;
typedef struct{
int a; //地址0
char b; //地址4
char c; //地址5,因为下一个成员为int类型,所以大小为5+3=8;
int d[12]; //地址8
char e; //地址56,因为下一个成员为double类型,所以大小为56+1+7=64
double f; //地址64
int g; //地址72,因为大小为最小公倍数,所以总大小为72+4+4=80
}s3;
typedef struct{
int a; //地址0
char b; //地址4
char c; //地址5 因为struct里有double,所以前面的大小必须是8的倍数,所以先空2个 大小为4+1+1+2=8;
struct{
char e[12];
char f;
double g;
//总大小24
}d;
int h; //地址32,因为满足所有成员变量(包括结构体里面的)的最小公倍数s,所以大小为8的倍数32+4+4=40
}s4;
typedef struct{
char a; //地址0
int b; //地址4
char c; //地址8,因为下一个成员变量是联合体,空间为double的空间,所以大小为8+1+7=16
union e{
int f;
double g; //地址16 所以总大小为16+8=24
};
}s5;
#pragma pack(4) //指定对齐小于最大的成员变量的大小,则按照指定对齐。
typedef struct{
char a; //地址0
int b; //地址4
char c; //地址8,因为指定对齐为4,所以不需要考虑下一个成员变量double 大小为8+4=12;
double d;// 地址为12
int e; //地址为20 所以总大小24
//如果不指定对齐为4,那么总大小为32
}s6;
#pragma pack(16) //指定对齐大于最大的成员变量的大小,则按照最大的成员变量为准。
typedef struct{
char a; //地址0
int b; //地址4
char c; //地址8,因为下一个成员变量double 大小为8+1+7=16;
double d;// 地址为16
int e; //地址为24 所以总大小24+4+4=32
}s7;
int main(){
s3 dd;
s4 ee;
printf("s1大小%d\n",sizeof(s1));
printf("s2大小%d\n",sizeof(s2));
printf("s3大小%d\n",sizeof(s3));
printf("s3首地址%p,s3 b的地址%p,s3 c的地址%p,s3 d的地址%p,s3 e的地址%p,s3 f的地址%p,s3 g的地址%p\n",&dd.a,&dd.b,&dd.c,&dd.d,&dd.e,&dd.f,&dd.g);
printf("s4大小%d s4结构体大小%d\n",sizeof(s4),sizeof(ee.d));
printf("s4首地址%p,s4 b的地址%p,s4 c的地址%p,s4 e的地址%p,s4 f的地址%p,s4 g的地址%p,s4 h的地址%p\n",&ee.a,&ee.b,&ee.c,&ee.d.e,&ee.d.f,&ee.d.g,&ee.h);
printf("s5大小%d\n",sizeof(s5));
printf("s6大小%d\n",sizeof(s6));
printf("s6大小%d\n",sizeof(s7));
}
访问方式
结构体变量通过.运算符访问成员变量。结构体指针变量通过->运算符访问成员变量,因为指针变量取内容也可以访问数据。所以 struct JieGouTi *p;p->成员变量等价于(*p).成员变量。
注意:scanf为结构体变量赋值的时候,非指针变量记得&;
联合体
定义方式
联合体是几种不同类型的成员变量共享同一内存空间,即联合体各成员变量的地址都是同一个地址。联合体内存空间大小等于最大长度成员变量的大小。每当为联合体的成员变量赋值后,该内存空间的值都会被覆盖为最新的赋值。联合体变量也可以作为结构体的成员变量。
定义方式union 联合体名 {成员变量;成员变量.....};
应用场景
用一个表格填写学校人员的数据,包括老师和学生,老师和学生都需要包含他们的姓名,工号,性别,职业。而且如果是老师还要包含他的教授科目,如果是学生还需要包含他的班级。所以我们可以制作一个结构体数组,结构体包含的成员变量有姓名,工号,性别,职业,额外信息(科目or班级),这个额外信息就可以用到联合体。不同的结构体变量,为这个额外信息赋的值不同。
代码:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct{
char name[13];
char number[20];
char sex[5];
char profession[5];
union {
int class[3];
char kemu[5];
}message;
}Person;
void getMessage(Person **p,int* len){
printf("请输入职工人数\n");
scanf("%d",len);
*p=(Person *)calloc(*len,sizeof(Person));
for(int i=0;i<*len;i++){
printf("请输入人员姓名\n");
scanf("%s",(*p)[i].name);
printf("请输入人员职业,教师or学生\n");
scanf("%s",(*p)[i].profession);
if(!strcmp((*p)[i].profession,"教师\0")){
printf("请输入教师性别,男性为男,女性为女\n");
scanf("%s",(*p)[i].sex);
printf("请输入教师工号\n");
scanf("%s",(*p)[i].number);
printf("请输入教师教授科目\n");
scanf("%s",(*p)[i].message.kemu);
}
else if(!strcmp((*p)[i].profession,"学生\0")){
printf("请输入学生性别,男性为男,女性为女\n");
scanf("%s",(*p)[i].sex);
printf("请输入学生工号\n");
scanf("%s",(*p)[i].number);
printf("请输入学生班级\n");
scanf("%s",(*p)[i].message.class);
}
else {
exit(-1);
}
}
}
void getResult(Person *per,int len){
printf("\n姓名\t职业\t性别\t班级or科目\t\n");
for(int i=0;i<len;i++){
if(!strcmp(per[i].profession,"教师\0"))
printf("%s\t%s\t%s\t%s\t\n",per[i].name,per[i].profession,per[i].sex,per[i].message.kemu);
else
printf("%s\t%s\t%s\t%s\t\n",per[i].name,per[i].profession,per[i].sex,per[i].message.class);
}
}
int main(){
Person *per;
int len;
getMessage(&per,&len);
getResult(per,len);
}
枚举类型
定义方式
enum用于一个变量只有几种可能值的情况,定义方式enum 枚举名:基础类型{标识符,标识符,标识符......};系统会默认为第一个标识符赋值0,后面的标识符的值依次+1;也可以定义时候为标识符赋值,但是值如果超出基础类型表示的范围时,会报错。不写基础类型标识符类型默认int,若写基础类型(byte,short,int,ushort,uint,long,u long ),标识符类型就是该基础类型。
枚举类型的变量赋值时候,赋的值只能是枚举类型中已有的标识符。
typedef关键字
typedef用于给已有的类型起别名,起了别名之后,就可以用别名定义变量了,对于类型名很长的类型typedef很方便。比如链表数据结构typedef struct node{int data;struct node* pnext}Node,*Pnext;就可以用Node来定义链表的节点,Pnext来定义链表节点的指针,很方便。
在单片机开发中,对于寄存器有8位,16位,32位。光用int类型来配置寄存器,对于8位,16位的寄存器会有浪费。如果想用对应大小的数据类型的变量来为配置寄存器,那么就可以
typedef unsigned char u_int8;typedef unsigned short u_int16;typedef unsigned int u_int32;
分别表示8位大小无符号整数的数据类型,16位大小无符号整数的数据类型,32位大小无符号整数的数据类型。
作业案例:投票系统
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct XuanMin{
char name[13];
int ticket;
};
void choose(struct XuanMin *xm,int len){
printf("票数从高到低排序结果为\n");
struct XuanMin tmp;
for(int i=0;i<len-1;i++){
for(int j=i+1;j<len;j++){
if((xm+j)->ticket>(xm+i)->ticket){ //(xm+j)->ticket>(xm+i)->ticket这种是通过结构体指针直接访问结构体的成员变量
//等于xm[j].ticket>xm[i].ticket这种是通过结构体变量名访问结构体的成员变量
//也等于*(xm+j).ticket>*(xm+i).ticket这种是通过结构体指针取内容访问结构体的成员变量
tmp=*(xm+j);
*(xm+j)=*(xm+i);
*(xm+i)=tmp;
}
}
}
}
struct XuanMin* getXm(struct XuanMin* qiquan,int *len){
printf("请输入有几个选民\n");
scanf("%d",len);
struct XuanMin *xm=(struct XuanMin *)calloc(*len,sizeof(struct XuanMin));
for(int i=0;i<*len;i++){
printf("请输入%d个选民的名字\n",i+1);
scanf("%s",(xm+i)->name);
if(strlen((xm+i)->name)>12){
printf("名字过长,不合法\n");
return NULL;
}
(xm+i)->ticket=0;
}
strcpy(qiquan->name,"弃权\0");
qiquan->ticket=0;
return xm;
}
void doVote(struct XuanMin *xm,struct XuanMin *qiquan,int len){
printf("请输入投票人数\n");
int num;
scanf("%d",&num);
printf("下面开始投票,弃权的话,填写弃权\n");
for(int i=0,count=1;;i++){
if(count>num)break;
int mark=0;
char tmpname[13];
memset(tmpname,'\0',sizeof(tmpname));
printf("第%d人: 请写上你要投的人的名字\n",count);
scanf("%s",tmpname);
if(strlen(tmpname)>12){
printf("名字过长,不合法\n");
exit(-1);
}
for(int j=0;j<len;j++){
if(!strcmp((xm+j)->name,tmpname)){
(xm+j)->ticket++;
mark=1;
}
}
if(!strcmp(tmpname,qiquan->name)){
qiquan->ticket++;
mark=1;
}
if(!mark){
printf("候选人中没有此人,请重新投\n");
continue;
}
count++;
}
}
void getResult(struct XuanMin *xm,struct XuanMin qiquan,int len){
for(int i=0;i<len;i++){
printf("%s:\t票数:%d\n",(xm+i)->name,(xm+i)->ticket);
}
printf("%s:\t票数:%d\n",qiquan.name,qiquan.ticket);
if(xm->ticket==(xm+1)->ticket){
printf("%s和%s票数平票,重投\n",xm->name,(xm+1)->name);
}
else printf("恭喜%s当选\n",xm->name);
}
int main(){
struct XuanMin* xm;
struct XuanMin qiquan;
int len =0;
xm=getXm(&qiquan,&len); if(!xm)return -1;
doVote(xm,&qiquan,len);
choose(xm,len);
getResult(xm,qiquan,len);
return 0;
}