结构体——简单通讯录实现
1、结构体知识回顾
1.1、结构体的声明
struct tag
{
menmber-list;
}variable-list;
描述一个学生:
struct Stu
{
char name[20];
int age;
char sex[5];
char id[20];
}; // 分号不要忘记
1.2、结构体变量的创建和初始化
#include<stdio.h>
struct Stu
{
char name[20]; // 姓名
int age; // 年龄
char sex[15]; // 性别
char id[20]; // 学号
};
int main()
{
// 按结构体成员顺序初始化
struct Stu s ={"李四", 20, "男", "1088200066"};
printf("姓名: %s\n", s.name);
printf("年龄: %s\n", s.age);
printf("性别: %s\n", s.sex);
printf("学号: %s\n", s.id);
// 按指定的顺序初始化
struct Stu s ={.age = 18, .name = "李四", .sex = "男", .id = "1088200066"};
printf("姓名: %s\n", s.name);
printf("年龄: %s\n", s.age);
printf("性别: %s\n", s.sex);
printf("学号: %s\n", s.id);
return 0;
}
1.3、结构特殊声明
声明结构的时候可以不完全声明。
比如:结构体在声明的时候省略结构体标签
# include<stdio.h>
int main()
{
// 定义一个匿名结构体
struct
{
int x;
int y;
}point1, point2; // point1和point2是该结构体的两个实例
// point1和point2赋值
point1.x = 10;
point1.y = 20;
point2.x = 10;
point2.y = 20;
// 输出
printf("point1:(%d %d)\n", point1.x, point2.y);
printf("point2:(%d %d)\n", point1.x, point2.y);
return 0;
}
定义匿名结构体:
- 在
struct
关键字后没有结构体命名(tag),而是直接定义了结构体成员(x 和 y). - 这种结构体只能在定义他的作用域内实现。(匿名结构体的类型没有全局或外部可见性,只有在定义它的地方才能操作该结构体的实例)
优点:
- 简洁性: 在某些情况下,使用匿名结构体可以使代码更加简洁,尤其是在只需要使用一次的情况下。
- 局部性: 匿名结构体的作用域限制在定义他的地方,避免了命名冲突。
1.4、结构的自引用
在结构体中不能包含一个类型为该结构体本身的成员,因为当结构体中再包含一个同类型的结构体变量,会使得结构体变量的大小变得无穷大,这是不合理的。但是可以包含该结构体成员的地址。
struct Node
{
int data;
struct Node* next;
};
在结构体⾃引⽤使⽤的过程中,夹杂了 typedef
对匿名结构体类型重命名,也容易引⼊问题.
问题代码:
typedef struct
{
int data;
Node* next;
}Node;
分析原因: 原理上上述代码执行完成后才完成对匿名结构体类型的重命名,但是在重命名过程中便引入了Node, 这便引起了先有鸡还是先有蛋的问题,显然是不对的。
解决方案:定义结构体不要使用匿名结构体
typedef struct Node
{
int data;
struct Node* next;
};
2、 结构体内存对齐
2.1 对齐规则
- 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
- 其他成员变量要对齐到对齐数 的整数倍的地址处。
- 对齐数 = 编译器默认的对齐数和成员变量大小的较小值。
- VS中默认的对齐数是8。
- Linux中gcc没有默认的对齐数,对齐数是成员变量的自身大小。
- 结构体总大小为最大对齐数 (结构体总每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
- 如果嵌套了结构体的话,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的(含嵌套结构体成员的对齐数)的整数倍。
2.2、修改默认对齐数
#pragma
这个预处理指令,可以改变编译器的默认对齐数。
# include<stdio.h>
#pragma pack(1) // 设置默认对齐数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack() // 回复默认对齐数
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
3、结构体传参
结构体传参最好传地址
#include<stdio,h>
struct S
{
int data[100];
int num;
}; // 不要忘记;
// 结构体传参
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;
}
4、简单通讯录的实现
要求:
实现一个通讯录
人的信息:
名字+年龄+性别+电话+地址
存放100个人的信息、增加联系人、删除指定的练习人、查找练习人、修改联系人、排序、显示连续人
为了后期维护和 查看的方便我们将该项目写成多文件的形式。
test1.c
main函数主程序
contact.c
各种函数的实现
contact.h
头文件和函数声明
test1.c
#include<stdio.h>
void menu()
{
printf("******************************************\n");
printf("******* 1.add 2.del ******\n");
printf("******* 3.search 4.modify ******\n");
printf("******* 5.show 6.sort ******\n");
printf("******* 0.exit ******\n");
printf("******************************************\n");
printf("******************************************\n");
}
int main()
{
int input = 0;
do
{
menu(); // 打印菜单
printf("请选择:>");
scanf("%d", &input);
switch(input)
{
case 1:
break;
case 2:
break:
case 3:
break:
case 4:
break;
case 5:
break:
case 6:
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
}while(input);
return 0;
}
首先,为实现多次输入我们想到利用 do....while
循环, 为了根据输入实现相应的功能,利用 switch
。根据多任务,我们打印的菜单如图所示。
哦,对了,怎么能少了用结构体存放人的信息呢,我们着手子在 contact.h
中声明一个结构体类型
typedef struct PeoInfo
{
char name[20]; // 姓名
int age; // 年龄
char sex[10]; // 性别
char tele[12]; // 电话
char addr[30] // 地址
}PeoInfo;
可以存放人员信息的结构类型已经被我们声明好了,接下来我们要创建一个通讯录,同样创建通讯录我们放在 contact.h
中。
// 创建通讯录
typedef struct Contact
{
PeoInfo data[100]; // 存放人的信息
int count; // 当前通讯录的实际的人数
}Contact;
用结构体实习那对人的信息的存放,然后创建一个通讯录,紧接着我们要对通讯录进行初始化,这是必须的。
我们设计一个函数来进行通讯录的初始化,同样在 contact.h
中声明函数, 在 contact.c
中实习那函数的功能。
contact.h
// 初始化通讯录
void InitContact(Contact* pc);
contact.c
void InitContact(Contact* pc)
{
pc->count = 0;
memset(pc->data,0,sizeof(pc->data)); // memset 函数将data数组所在内存全部u初始化为0;
} // 头文件<string.h>
因为牵扯到指针,为了避免野指针的出现,我们在之后的函数中,凡是牵扯到阴蒂指针进来的,都要进行assert断言, 头文件<assert.h>.
是为避免头文件的重复声明,我们将所有头文件都放在 `contact.h` 中,在 `test.1` 和 `contact.c` 中仅声明 `#include"contact.h"` 即可。
同时。我么们在结构体中定义name的大小是20 ,后期我么们想要维护是不方便了的,因此我们采用宏定义的方法,在 contact.h
中,我么们定义如下:
#define MAX 100
#define MAX_NAME 20;
#define MAX_SEX 10;
#define MAX_TELE 12
#define MAX_ADDR 30
经过上述操作后,各文件的状态如下:
test1.c
#include"contact.h"
void menu()
{
printf("******************************************\n");
printf("******* 1.add 2.del ******\n");
printf("******* 3.search 4.modify ******\n");
printf("******* 5.show 6.sort ******\n");
printf("******* 0.exit ******\n");
printf("******************************************\n");
printf("******************************************\n");
}
int main()
{
int input = 0;
Contact con; // 创建一个通讯录
// 初始化通讯录
InitContact(&con);
do
{
menu(); // 打印菜单
printf("请选择:>");
scanf("%d", &input);
switch(input)
{
case 1:
break;
case 2:
break:
case 3:
break:
case 4:
break;
case 5:
break:
case 6:
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
}while(input);
return 0;
}
contact.c
# include"contact.h"
// 初始化通讯录
void InitContact(Contact* pc)
{
assert(pc);
pc->count = 0;
memset(pc->data,0,sizeof(pc->data)); // memset 函数将data数组所在内存全部u初始化为0;
} // 头文件<string.h>
contact.h
# include<stdio.h>
# include<assert.h>
# include<string.h>
#define MAX 100
#define MAX_NAME 20;
#define MAX_SEX 10;
#define MAX_TELE 12
#define MAX_ADDR 30
typedef struct PeoInfo
{
char name[20]; // 姓名
int age; // 年龄
char sex[10]; // 性别
char tele[12]; // 电话
char addr[30] // 地址
}PeoInfo;
// 创建通讯录
typedef struct Contact
{
PeoInfo data[100]; // 存放人的信息
int count; // 当前通讯录的实际的人数
}Contact;
// 初始化通讯录
void InitContact(Contact* pc);
创建和初始化通讯录的工作都已经完成了,之后,我们开始编写相关的功能函数。
首先 增加联系人函数。AddContact()
// 增加联系人
void AddContact(Contact* pc); // 函数声明
// 怎加联系人函数
void AddContact(Contact* pc)
{
assert(pc);
if(pc->count == MAX)
{
printf("通讯录已满,无法继续添加\n");
return;
}
printf("请输入名字:>");
scanf("%s", pc->data[pc->count].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->count].age));
printf("请输入性别:>");
scanf("%s", pc->data[pc->count].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->count].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->count].addr);
pc->count++;
printf("增加成功\n");
}
显示通讯录函数: ShowContact()
//显示通讯录
void ShowContact(Contact* pc); // 函数声明
// 显示通讯录函数
void ShowContact(Contact* pc)
{
assert(pc);
printf("%-15s\t%-15s\t-15s\t-15s\t-15s\t-15s\n", "姓名:","年龄:", "性别:", "电话", "地址:");
int i = 0;
for(i = 0; i < pc->count; i++)
{
printf("%-15s\t%-15d\t%-15s\t%-15s\t%-15s\n", pc->data[pc->count].name,
pc->data[pc->count].age,
pc->data[pc->count].sex,
pc->data[pc->count].tele,
pc->data[pc->count].addr,);
}
}
删除联系人函数: DeleContact()
// 删除联系人函数
void DeleContact(Contact* pc);
int FindByNmae(Contact* pc, char name[])
{
assert(pc);
int i = 0;
for(i = 0; i < pc->count; i++)
{
if(strcmp(pc->data[i].name, name))
{
return i; // 返回下标
}
}
return -1;
}
void DeleContact(Contact* pc)
{
assert(pc);
int i = 0;
char name[MAX_name] = {0};
if(pc->count == 0)
{
printf("通讯录为空,没有联系人可以删除\n");
return;
}
printf("请输入要删除人的名字:>");
scanf("%s", name);
// 要删除首先需要查找
// 查找
int pos = FindByName(pc,name); // 没有找到返回-1, 找到的话返回下标
if(-1 == pos)
{
printf("你要删除的联系人通讯录中不存在。\n");
return;
}
// 删除
for(i = pos, i < pc->count-1; i++) // 为了防止越界,count - 1
{
pc->data[i] = pc->count[i+1]; // 从后向前覆盖
}
pc->count--;
printf("删除成功,\n");
}
搜索联系人函数: SearchContact()
// 搜索联系人函数
void SearchContact(Contact* pc);
void SearchContact(Contact* pc)
{
assert(pc);
int i = 0;
char name[MAX_name] = {0};
if(pc->count == 0)
{
printf("通讯录为空,没有联系人可以查找\n");
return;
}
printf("请输入查找人的名字:>");
scanf("%s", name);
// 查找
int pos = FindByName(pc,name); // 没有找到返回-1, 找到的话返回下标
if(-1 == pos)
{
printf("你要查找的联系人通讯录中不存在。\n");
return;
}
// 打印
printf("%-15s\t%-15s\t-15s\t-15s\t-15s\t-15s\n", "姓名:","年龄:", "性别:", "电话", "地址:");
int i = 0;
printf("%-15s\t%-15d\t%-15s\t%-15s\t%-15s\n", pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].sex,
pc->data[pos].tele,
pc->data[pos].addr,);
}
修改联系人函数: MoifyContact()
//修改联系人函数
void ModifyContact(Contact* pc);
// 修改联系人函数
void ModifyContact(Contact* pc)
{
assert(pc);
// 想要修改首先需要先找到
int i = 0;
char name[MAX_name] = {0};
if(pc->count == 0)
{
printf("通讯录为空,没有联系人可以查找\n");
return;
}
printf("请输入查找人的名字:>");
scanf("%s", name);
// 查找
int pos = FindByName(pc,name); // 没有找到返回-1, 找到的话返回下标
if(-1 == pos)
{
printf("你要查找的联系人通讯录中不存在。\n");
return;
}
// 找到后再重新打印
printf("请重新输入想要修改的联系人信息:>\n");
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");
}
排序函数: SortContact()
// 排序函数
void SortContact(Contact* pc);
int cmp_peo_by_name(const void* e1, const void* e2)
{
return strcmp((PeoInfo*)e1->name, (PeoInfo*)e2->name);
}
void SortContact(Contact* pc)
{
assert(pc);
// 使用qsort函数进行排序
qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_By_name);
printf("排序成功\n");
}
到这里,所有的函数已经完成
个文件现在的状态是:
test1.c
#include"contact.h"
void menu()
{
printf("******************************************\n");
printf("******* 1.add 2.del ******\n");
printf("******* 3.search 4.modify ******\n");
printf("******* 5.show 6.sort ******\n");
printf("******* 0.exit ******\n");
printf("******************************************\n");
printf("******************************************\n");
}
int main()
{
int input = 0;
Contact con; //创建一个通讯录
// 初始化通讯录(模块化设置)
InitContact(&con);
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
AddContact(&con);
break;
case 2:
DeleContact(&con);
break;
case 3:
SearchContact(&con);
break;
case 4:
ModifyContact(&con);
break;
case 5:
ShowContact(&con);
break;
case 6:
SortContact(&con);
break;
case 0:
printf("退出通讯录\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
contact.c
#include"contact.h"
// 初始化通讯录
void InitContact(Contact* pc)
{
assert(pc);
pc->count = 0;
memset(pc->data, 0, sizeof(pc->data));
}
// 增加联系人函数
void AddContact(Contact* pc)
{
assert(pc);
if (pc->count == MAX)
{
printf("通讯录已满,无法添加\n");
return;
}
printf("请输入名字:>");
scanf("%s", pc->data[pc->count].name);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->count].age));
printf("请输入性别:>");
scanf("%s", pc->data[pc->count].sex);
printf("请输入电话:>");
scanf("%s", pc->data[pc->count].tele);
printf("请输入地址:>");
scanf("%s", pc->data[pc->count].addr);
pc->count++;
printf("增加成功\n");
}
// 显示通讯录的讯息
void ShowContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->count; i++)
{
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\n", pc->data[i].name,
pc->data[i].age,
pc->data[i].sex,
pc->data[i].tele,
pc->data[i].addr);
}
}
// 查找函数(我们在头文件中欸有暴露出去)
int FindByName(Contact* pc, char name[])
{
assert(pc);
int i = 0;
for (i = 0; i < pc->count; i++)
{
if (0 == strcmp(pc->data[i].name, name))
{
return i;
}
}
return -1;
}
// 删除指定的连续人
void DeleContact(Contact* pc)
{
assert(pc);
int i = 0;
char name[MAX_NAME] = { 0 };
if (pc->count == 0)
{
printf("通讯录为空,没有讯息可以删除\n");
}
printf("请输入要删除的人的名字:>");
scanf("%s", name);
// 要先删除首先需要查找
// 1、查找
int pos = FindByName(pc, name); // 找到的话返回下标,找不到的话返回-1
if (-1 == pos)
{
printf("要删除的人不存在\n");
return;
}
// 2、删除
for (i = pos; i < pc->count-1; i++) // count-1是为了防止数组越界
{
pc->data[i] = pc->data[i + 1]; // 将后面的元素往前覆盖
}
pc->count--;
printf("删除成功\n");
}
// 目录中的查找函数
void SearchContact(const Contact* pc)
{
assert(pc);
int i = 0;
char name[MAX_NAME] = { 0 };
printf("请输入查找的人的名字:>");
scanf("%s", name);
// 1、查找
int pos = FindByName(pc, name); // 找到的话返回下标,找不到的话返回-1
if (-1 == pos)
{
printf("要查找的人不存在\n");
return;
}
// 2、打印
printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
printf("%-20s\t%-5d\t%-5s\t%-12s\t%-30s\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)
{
assert(pc);
int i = 0;
char name[MAX_NAME] = { 0 };
printf("请输入修改的人的名字:>");
scanf("%s", name);
// 1、查找
int pos = FindByName(pc, name); // 找到的话返回下标,找不到的话返回-1
if (-1 == pos)
{
printf("要修改的人不存在\n");
return;
}
// 2、修改
printf("请对要修改的人的信息重新录入:>\n");
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");
}
// qsort函数中需要的函数
int cmp_peo_by_name(const void* e1, const void* e2)
{
return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
// 目录中的排序函数(按照名字排序)
void SortContact(Contact* pc)
{
assert(pc);
qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_by_name);
printf("排序成功\n");
}
contact.h
#pragma once
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30
// 类型的声明
typedef struct PeoInfo
{
char name[MAX_NAME];
int age;
char sex[MAX_SEX];
char tele[MAX_TELE];
char addr[MAX_ADDR];
}PeoInfo;
// 创建通讯录
typedef struct Contact
{
PeoInfo data[MAX]; // 存放人的讯息
int count; // 记录当前通讯录中实际的个数
}Contact;
// 初始化通讯录
void InitContact(Contact* pc);
// 增加讯息
void AddContact(Contact* pc);
// 显示通讯录的讯息
void ShowContact(const Contact* pc);
// 删除指定的联系人
void DeleContact(Contact* pc);
// 查找函数
void SearchContact(const Contact* pc);
// 修改指定的联系人
void ModifyContact(Contact* pc);
// 排序(按照名字排序)
void SortContact(Contact* pc);