一、结构体的声明
1.1 结构体的定义
结构体是一些值的集合,这些值被称为成员变量。结构的每个成员可以是不同的类型
1.2 结构体的声明
这里以描述一个学生为例:
struct stu
{char name[10];//名字int age;//年龄char id[20];//学号char sex[5];//性别
};
1.3 结构体自引用
各位请思考:如果结构体里面需要用到他自己本身,此时的代码我们应该如何完成呢
可以这样弄吗?
struct stu
{char name[10];struct stu s2;
};
如果可以的话,那么sizeof(struct stu)的大小是不是就是无限的了?
因此如果我们想实现结构体的自引用的话可以用指针(一个指针的大小是固定4/8个字节的):
struct stu
{char name[10];struct stu* s2;
};
1.4 结构体的初始化
其实很简单的聪明的你一定一看就会
struct stu
{char name[20];int age;char sex[5];char id[20];
};//结构体定义int main()
{struct stu s1 = { "fox", 12,"nan", "123456" };//结构体初始化return 0;
}
也可以在定义的时候就初始化
struct stu
{char name[20];int age;char sex[5];char id[20];
}s1 = { "fox", 12, "nan", "123456" };//结构体定义及初始化
还有结构体嵌套的初始化
struct node
{int data;struct stu s1;
}n1 = {10, { "fox", 12, "nan", "123456" } };//结构体嵌套初始化
1.5 结构体内存对齐
我们已经掌握了结构体的基本使用了。现在我们深入讨论一个问题:计算结构体的大小。
这也是一个热门考点,我们举例说明:
struct s1
{char c1;char c2;int i;
};struct s2
{char c1;int i;char c2;
};int main()
{printf("%d\n", sizeof(struct s1));printf("%d\n", sizeof(struct s2));return 0;
}
上面的代码会输出什么?
同样都是装了两个char和一个int,为啥因为顺序不同他们的大小也不一样了呢?
就是因为结构体内存对齐了
那如何计算结构体的内存大小就需要清楚他的对齐规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
图有点丑~
1.6 修改默认对起数
可以通过#pragma pack()来修改默认对齐数
#pragma pack(8)//设置默认对齐数为8
struct S1
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{char c1;int i;char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{//输出的结果是什么?printf("%d\n", sizeof(struct S1));printf("%d\n", sizeof(struct S2));return 0;
}
二、通讯录的实现
和前面几期的一样,我们用test.c来测试程序,context.c和context.h,用于实现程序功能
2.1 大体思路
实现通讯录,我们最少要有增、删、查、改、显示所有人的信息,排序,格式化
通讯录中,我们需要有名字,年龄,性别,电话号,地址
这里需要存人的信息就可以用结构体,假设我们的通讯录容量为100个人,那么我们就可以用一个大小为100的数组来管理我们的通讯录
就可以先把程序的大体运行思路(菜单)和结构体完成
void menu()
{printf("****************************************\n");printf("****** 1. add 2. del ********\n");printf("****** 3. search 4. modify ********\n");printf("****** 5. show 6.sort ********\n");printf("****** 7. cln 0. exit ********\n");printf("****************************************\n");}int main()
{int input = 0;//创建通讯录context con;//初始化通讯录init_context(&con);do{menu();printf("请选择:");scanf("%d", &input);switch (input){case ADD:add_context(&con);break;case DEL:del_context(&con);break;case SEARCH:serch_context(&con);break;case MODIFY:modify_contest(&con);break;case 5: show_context(&con);break;case SORT:sort_context(&con);break;case CLN:cln_context(&con);break;case 0 :printf("退出通讯录");break;default:printf("输入错误,请重新输入\n");break;}} while (input);return 0;
}
头文件也可以先声明好每个函数,接下来再来实现他们
#define _CRT_SECURE_NO_WARNINGS 1;#include <stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>#define MAX 100
#define NAME_MAX 20
#define SEX_MAX 5
#define TELE_MAX 12
#define ADDR_MAX 15//声明选项
enum option
{EXIT,ADD,DEL,SEARCH,MODIFY,SHOW,SORT,CLN,
};typedef struct peo_info
{char name[NAME_MAX];int age;char sex[SEX_MAX];char tele[TELE_MAX];char addr[ADDR_MAX];
}peo_info;typedef struct context
{peo_info data[MAX];//存放人的信息int sz;//存放当前存的人的个数
}context;//初始化通讯录
void init_context(context* pc);//增加联系人
void add_context(context* pc);//显示通讯录
void show_context(context* pc);//删除联系人
void del_context(context* pc);//查找联系人
void serch_context(context* pc);//修改联系人
void modify_contest(context* pc);//排序
void sort_context(context* pc);//清空通讯录
void cln_context(context* pc);
接下来就是一步一步把通讯录的所有功能实现了
2.2 初始化通讯录
初始化通讯录可以用memset函数,这样可以很方便的把我们的context全部初始化为0
//初始化通讯录
void init_context(context* pc)
{pc->sz = 0;memset(pc->data, 0, sizeof(pc->data));
}
2.3 增加联系人
其实就是结构体的输入和输出,注意检查pc指针是否为空指针
//增加联系人
void add_context(context* pc)
{assert(pc);if (MAX == pc->sz){printf("通讯录已满,无法存放\n");return;}//增加一个人的信息printf("请输入名字");scanf("%s", pc->data[pc->sz].name);printf("请输入年龄");scanf("%d", &pc->data[pc->sz].age);printf("请输入性别");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");
}
2.4 显示通讯录
我们先把显示功能搞定,以便观察我们的代码是否正确
//显示通讯录
void show_context(context* pc)
{assert(pc);int i = 0;printf("%-10s\t%-4s\t%-4s\t%-13s\t%-10s\n", "姓名", "年龄", "性别", "电话", "地址");for (i = 0; i < pc->sz; i++){printf("%-10s\t%-4d\t%-4s\t%-13s\t%-10s\n", pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].tele,pc->data[i].addr);}
}
2.5 查找函数
后面的很多功能都需要先查找到这个人,再进行一些列的操作,因此我们先写一个查找函数(人名查找),也可以运用函数指针的知识实现人名查找、电话查找、地点查找……
int find_by_name(context* pc, char name[])
{int i = 0;for (i = 0; i < pc->sz; i++){int ret = strcmp(pc->data[i].name, name);if (0 == ret){return i;}}return -1;
}
2.5 删除联系人
就是先调佣查找函数找到用户想删除的那个人,再进行删除操作就可以
void del_context(context* pc)
{assert(pc);char name[NAME_MAX] = { 0 };if (0 == pc->sz){printf("通讯录为空,无法删除\n");return;}printf("请输入要删除的人的名字:");scanf("%s", name);int ret = find_by_name(pc, name);if (-1 == ret){printf("要删除的人不存在\n");return;}//删除int i = 0;for (i = ret; i < pc->sz - 1; i++){pc->data[i] = pc->data[i + 1];}pc->sz--;printf("删除成功\n");
}
2.6 查找联系人
同样也是先调用查找函数找到那个人,再把这个人的信息打印出来就行了
//查找联系人
void serch_context(context* pc)
{assert(pc);char name[NAME_MAX] = { 0 };printf("请输入要查找的人的名字:");scanf("%s", name);int pos = find_by_name(pc, name);if (-1 == pos){printf("要查找的人不存在\n");return;}else{printf("%-10s\t%-4s\t%-4s\t%-13s\t%-10s\n", "姓名", "年龄", "性别", "电话", "地址");printf("%-10s\t%-4d\t%-4s\t%-13s\t%-10s\n", pc->data[pos].name,pc->data[pos].age,pc->data[pos].sex,pc->data[pos].tele,pc->data[pos].addr);}
}
2.7 修改联系人
先用查找函数找到那个人,再进行修改操作
//修改联系人
void modify_contest(context* pc)
{printf("请输入要修改的人的名字:");char name[NAME_MAX] = { 0 };scanf("%s", name);//找人int pos = find_by_name(pc, name);if (-1 == pos){printf("要修改的人不存在\n");return;}//修改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");
}
2.8 排序通讯录
这里我们调用库函数qsort,不熟悉qsort的朋友可以看我前几期的内容(C语言指针plus版-CSDN博客),在写上两个比大小的函数传给qsort就行
//按年龄排序
int cmp_by_age(const void* p1, const void* p2)
{return (((context*)p1)->data->age) - (((context*)p2)->data->age);
}//按名字排序
int cmp_by_name(const void* p1, const void* p2)
{return strcmp((((context*)p1)->data->name), (((context*)p2)->data->name));
}//排序通讯录
void sort_context(context* pc)
{assert(pc);if (0 == pc->sz){printf("通讯录为空,无法排序\n");return;}int input = 0;printf("0. 按年龄排序\n");printf("1. 按名字排序\n");printf("请选择:");scanf("%d", &input);if (input){printf("按名字排序\n");qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_name);printf("排序成功\n");return;}printf("按年龄排序\n");qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp_by_age);printf("排序成功\n");
}
2.9 清空通讯录
其实就是再初始化一下通讯录就行了,直接调用初始化通讯录的函数就行了
//清空联系人
void cln_context(context* pc)
{assert(pc);int input = 0;printf("确定要清空通讯录吗?\n");printf("1. YES\n");printf("0. NO\n");scanf("%d", &input);if (input){printf("清空成功\n");init_context(pc);return;}return;
}
这样你就得到的一个一般般的通讯录了,下期我们弄个plus版的来~
需要各位好心人多点点赞、评评论、这样通讯录就可以变成plusplus版的了!