目录
一、结构体
1、结构体类型的声明
(1)结构体的基础知识
(2)结构体的声明
(3)结构体变量的创建
(4)特殊的声明
2、结构的自引用
3、结构体变量的定义和初始化
(1)定义变量
(2)变量初始化
4、结构体内存对齐
(1)为什么存在内存对齐
(2)结构体的对齐规则
(3)如何做到满足对齐又节省空间
(4)修改默认对齐数
(5)例题:计算结构体变量偏移的宏
5、结构体传参
6、结构体实现位段
(1)什么是位段
(2)位段的内存分配
(3)位段的跨平台问题
(4)位段的应用
二、枚举
1、枚举类型的定义
2、枚举的优点
3、枚举的使用
三、联合
1、联合类型的定义
2、联合的特点
3、联合大小的计算
四、练习:实现通讯录
一、结构体
1、结构体类型的声明
(1)结构体的基础知识
●结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
●数组是一组相同类型的元素的集合。
(2)结构体的声明
struct tag { ??? member_list; }variable-list;
例:
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
(3)结构体变量的创建
//1
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}s1,s2,s3; //是全局的
//2
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
};
int main()
{
struct Stu s4; //是局部的
struct Stu s5;
struct Stu s6;
return 0;
}
(4)特殊的声明
在声明结构的时候,可以不完全的声明。
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20],*p;
2、结构的自引用
自引用是指在结构中包含一个类型为该结构本身的成员。
//错例1
struct Node
{
int data;
struct Node next;
};
//正确的引用方式
struct Node
{
int data;
struct Node* next;//不是包含同类型的结构体变量,而是包含同类型的结构体指针
};
//错例2
typedef struct
{
int data;
Node* next;
}Node;
//解决方案
typedef struct Node
{
int data;
Node* next;
}Node;
3、结构体变量的定义和初始化
(1)定义变量
//法一:声明类型的同时定义变量p1
struct Point
{
int x;
int y;
}p1;
//法二:定义结构体变量p2
struct Point p2;
(2)变量初始化
//法一:定义变量p3的同时赋初值
struct Point p3={x,y};
//法二:声明类型的同时,定义变量p4并赋初值
struct Point
{
int x;
int y;
}p4={4,5};
//法三:结构体嵌套初始化
struct Node
{
int x;
int y;
struct Node* next;
};
struct Node p5={2,6,{4,5}};
4、结构体内存对齐
例:
struct S1
{
int i1; //4
char c1;//1
};
struct S2
{
char c2;//1
int i2; //4
char c3;//1
};
struct S3
{
double d1;//8
char c4; //1
int i3; //4
};
int main()
{
struct S1 s1={0};
printf("%d\n",sizeof(s1));//8
struct S2 s2={0};
printf("%d\n",sizeof(s2));//12
struct S3 s3={0};
printf("%d\n",sizeof(s3));//32
return 0;
}
(1)为什么存在内存对齐
●平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
●性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
(2)结构体的对齐规则
●第一个成员在与结构体变量偏移量为0的地址处
●其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
? 对齐数=编译器默认的一个对齐数与该成员大小的较小值
●VS中默认的值为8,linux没有默认对齐数
●如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
?总结 :
结构体的内存对齐是拿空间来换取时间的做法。
(3)如何做到满足对齐又节省空间
让占用空间小的成员尽量集中在一起
?例:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
(4)修改默认对齐数
结构在对齐方式不合适的时候,可以自己更改默认对齐数。
方式:预处理指令#pragma
?例:
#include<stdio.h>
#pragma pack(2) //设置默认对齐数为2
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认
(5)例题:计算结构体变量偏移的宏
宏:offsetof(type,member)
//offsetof实现
#include<stdef.h>
struct S
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n",offsetof(struct S,c1));
printf("%d\n",offsetof(struct S,i));
printf("%d\n",offsetof(struct S,c2));
return 0;
}
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);
return 0;
}
首选print2函数,因为函数传参的时候,参数需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销会比较大,所以会导致性能的下降。
总结:传地址更好,功能上一样,效率更高。
6、结构体实现位段
(1)什么是位段
位段的声明和结构是类似的,有两个不同:
●位段的成员必须是int、unsigned int或signed int
●位段的成员名后边有一个冒号和一个数字
?例:
struct A
{
int _a:2; //_a成员占2个bit位
int _b:5; //_b成员占5个bit位
int _c:10;//_c成员占10个bit位
int _d:30;//_d成员占30个bit位
};
int main()
{
printf("%d\n",sizeof(struct A)); //8
return 0;
}
(2)位段的内存分配
●位段的成员可以是int、unsigned int、signed int或者是char类型
●位段的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
●位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
?例:
struct S
{
char a:3;
char b:3;
char c:3;
char d:3;
}
int main()
{
struct S s={0};
s.a=10;
s.b=12;
s.c=3;
s.d=4;
return 0;
}
(3)位段的跨平台问题
●int位段被当成有符号数还是无符号数是不确定的
●位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器上会出问题)
●位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
●当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:跟结构体相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
(4)位段的应用
二、枚举
1、枚举类型的定义
●{}中的内容是枚举类型可能的取值,也叫枚举常量。
●这些可能取值都是有指的,默认从0开始,一次递增1,当然在定义时也可以赋初值。
?例:
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex
{
MALE,
FEMALE,
SECRET
}
int main()
{
printf("%d\n",MALE); //0
printf("%d\n",FEMALE);//1
printf("%d\n",SECRET);//2
return 0;
}
2、枚举的优点
●增加代码的可读性和可维护性
●和#define定义的标识符比较,枚举有类型检查,更加严谨
●防止了命名污染
●便于调试
●使用方便,一次可以定义多个常量
3、枚举的使用
例:
//在前面实现过的计算其中,可以利用枚举进行优化
enum Option
{
EXIT,
ADD,
SUB,
MUL,
DIV
}
//可将主函数中的case 1改成case EXIT......
//这样代码更易读
三、联合
1、联合类型的定义
联合定义变量也包含一系列的成员,特征是这些成员共用同一块空间。所以联合也叫共用体。
//联合类型的声明
union Un
{
char c;
int i;
}
int main()
{
//联合变量的定义
union Un u;
//计算联合变量的大小
printf("%d\n",sizeof(u));
//查看联合变量的成员地址
printf("%d\n",&u); //0113F888
printf("%d\n",&(u.c));//0113F888
printf("%d\n",&(u.i));//0113F888
}
2、联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
?例:判断当前计算机的大小端存储
小端:低字节存放在低地址 大端:低字节存放在高地址
//1
int main()
{
int a=1;//00000001
if((*(char*)&a)==1)//取一个字节的内容
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
//2
int check_sys()
{
union U
{
char c;
int i;
}u;
u.i=1;
return u.c;//返回1是小端,返回0是大端
}
3、联合大小的计算
●联合的大小至少是最大成员的大小
●当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
?例:
union Un1
{
char c[5];//5
int i;//4
};
printf("%d\n",sizeof(union Un1));
四、练习:实现通讯录
要求:
1.通讯录能够存放1000个人的信息,每个人的信息:名字+年龄+性别+电话+地址
2.能够对信息进行增、删、改、查、排序
//contact.h
//包含类型定义、函数声明
#include<string.h>
#include<stdio.h>
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
#define MAX 1000
typedef struct PeoIofo
{
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; //通讯录中当前有sz个元素
}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"
void InitContact(Contact* pc)
{
pc->sz=0;
memset(pc->data,0,sizeof(pc->data));//内存设置函数
}
void AddContact(Contact* pc)
{
if(pc->sz==MAX)
{
printf("通讯录已满,无法添加!\n");
return ;
}
printf("请输入名字:\n");
scanf("%s",pc->data[pc->sz].name);
printf("请输入年龄:\n");
scanf("%s",pc->data[pc->sz].age);
printf("请输入性别:\n");
scanf("%s",pc->data[pc->sz].sex);
printf("请输入地址:\n");
scanf("%s",pc->data[pc->sz].tele);
printf("请输入地址:\n");
scanf("%s",pc->data[pc->sz].addr);
pc->sz++;
printf("增加成功!\n");
}
void PrintContact(const Contact* pc)
{
printf("%-12s\t%-5d\t%-5s\t%-12s\t%-12s\n","名字","年龄","性别","电话","地址");
for(int 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[])
{
for(int 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];
if(pc->sz==0)
{
printf("通讯录为空,无需删除!\n");
return ;
}
printf("请输入要删除的人的名字:\n");
scanf("%s",&name);
//要先查找
//有就删除,没有就报错
int pos=FindByName(pc,name);
if(pos==-1)
{
printf("要删除的人不存在!\n");
return ;
}
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];
printf("请输入要查找人的名字:\n");
scanf("%s",&name);
int pos=FindByName(pc,name);
if(pos==-1)
{
printf("要删除的人不存在!\n");
return ;
}
else
{
printf("%-12s\t%-5d\t%-5s\t%-12s\t%-12s\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];
printf("请输入要修改人的名字:\n");
scanf("%s",&name);
int pos=FindByName(pc,name);
if(pos==-1)
{
printf("要修改的人不存在!\n");
return ;
}
else
{
printf("请输入名字:\n");
scanf("%s",pc->data[pos].name);
printf("请输入年龄:\n");
scanf("%s",pc->data[pos].age);
printf("请输入性别:\n");
scanf("%s",pc->data[pos].sex);
printf("请输入地址:\n");
scanf("%s",pc->data[pos].tele);
printf("请输入地址:\n");
scanf("%s",pc->data[pos].addr);
printf("修改成功!\n");
}
}
//test.c
//测试通讯录的模块
#include"contact.h"
void menu()
{
printf("*******************************");
printf("**** 1.add 2.del *****");
printf("**** 3.search 4.modify *****");
printf("**** 5.sort 6.print *****");
printf("**** 0.exit *****");
printf("*******************************");
}
enum Options
{
EXIT,
ADD,
DEL,
SERACH,
MODIFY,
SORT,
PRINT,
}
int main()
{
int input=0;
Contact con;
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 MIDIFY:
ModifyContact(&con);
break;
case SORT:
SortContact();
break;
case PRINT:
PrintContact(&con);
break;
case EXIT:
printf("退出通讯录!\n");
break;
default:
printf("选择错误,请重新选择!\n");
break;
}
}while(input);
return 0;
}
|