一、结构体的声明
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版的了!