Bootstrap

C语言有关结构体的知识(后有通讯录的实现)

一、结构体的声明

        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版的了!

;