栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

笔记21-3 (C语言进阶 通讯录)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

笔记21-3 (C语言进阶 通讯录)

目录

注

要求

思路

前期的准备

1.设置类型存储通讯录内的数据

2.规划流程

实现

创建通讯录

初始化通讯录部分 

用户选择部分

ADDContact函数部分(增加联系人)

PRINTContact函数部分(打印联系人信息)

FindByName函数部分(通过名字查找对象)

DELContact函数部分(删除联系人的信息)

SEARCHContact函数部分(查找指定联系人)

MODIFYContact函数部分(修改指定联系人)

SORTContact函数部分(排序联系人信息)

优化

1.如果出现重名的联系人该怎么办?

2.把数据移动到堆上

源代码

test.c

contact.h

contact.c


注

本笔记参考B站up鹏哥C语言


要求

1. 通讯录能够存放1000个人的信息

        每个人的信息:

        名字+年龄+性别+电话+地址

2. 增加人的信息

3. 删除指定人的信息

4. 修改指定人的信息

5. 查找指定人的信息

6. 排序通讯录的信息

思路

前期的准备

1.设置类型存储通讯录内的数据

在 contact.h 内部(此处代码仅供参考)

#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30

//类型的定义
typedef struct PeoInfo
{
	char name[MAX_NAME];
	char sex[MAX_SEX];
	int age;
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
}PeoInfo;

通过定义常量的方式方便后期的调试。

typedef 定义函数,方便之后使用。

2.规划流程

实现

创建通讯录

需要:

  1. 创建通讯录本体
  2. 需要知道通讯录中当前总共有几个元素(设置一个变量sz负责计数)
PeoInfo data[MAX];
int sz = 0;

但是在封装函数时会发现,如果使用sz计数,在传参时每次都要传入两个参数。这时候就会思考是否可以合并 PeoInfo 和 sz 这两个部分?

所以再封装一个结构体(MAX已提前定义):

//通讯录
typedef struct Contact
{
	PeoInfo data[MAX];//存储添加进来的人的信息
	int sz;//记录当前通讯录中有效信息的个数
}Contact;

此时也改变 test.c 内的结构体

Contact内部

初始化通讯录部分 

认为,刚刚创建好的通讯录内部应该没有数据,所以通讯录内部的 结构体PeoInfo 和 整型sz 应该被初始化。

首先在 test.c 内部写入函数 InitContact

InitContact(&con);
//选择传入 con 的地址,可以改动 con 内部的内容

再在 contact.h 内部声明函数

void InitContact(Contact* pc);

最后在 contact.c 内部首先该函数(需要包含头文件 contact.h )

memset() - 内存设置函数,可以设置一块连续的内存。把指定空间的每个字节的内容初始化成指定的值(以字节为单位)。

void InitContact(Contact* pc)
{
	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));
}
//这种写法适用于较为复杂的初始化。

为什么可以直接使用 sizeof 计算字节个数?

分析:指针 pc->data 找到的是 data 这个数组的数组名,相当于将数组名单独放入 sizeof 内部,此时计算的是data这个数组的总大小。

用户选择部分

通过枚举类型的使用方便分支语句的选择,逻辑清晰

enum Option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SORT,
	PRI
};

可对应switch语句(用户选择)

switch (input)
{
    case ADD://增加信息
	break;
    case DEL://删除信息
	break;
    case SEARCH://查找信息
	break;
    case MODIFY://修改信息
	break;
    case SORT://排序
	break;
    case PRINT://打印信息
	break;
    case EXIT://退出
        break;
    default:
        break;
}

ADDContact函数部分(增加联系人)

写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)

分析:

  • 当通讯录内没有信息时, sz = 0 ,此时把联系人的信息放入 data[0] 内部;
  • 如果有一个联系人,则 sz = 1 ,此时把联系人的信息放入 data[1] 内部;
  • ……

由此可以发现新增加的联系人的信息应该被放入 data[sz] 的内部(放入后 sz++)。

另一方面,当通讯录满了之后就不可以放入数据了。

void ADDContact(Contact* pc)
{
	//处理通讯录满了的情况
	if (pc->sz == MAX)
	{
		printf("通讯录已满,无法添加n");
		return;
	}

	//增加一个人的信息
	printf("请输入名字:");
	scanf("%s", pc->data[pc->sz].name);//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");
}

PRINTContact函数部分(打印联系人信息)

首先在 test.c 内部写入函数 PRINTCotact

PRINTContact(&con);//传入地址

这里选择传入地址,不是为了更改 con 内部的内容,而是因为结构体传地址效率会更高。

声明函数(contact.h) 

void PRINTContact(const Contact* pc);

因为并改变 con 内容,所以使用 const 修饰变量。

实现函数(contact.c)

要求

  1. 先打印标题
    //先设计好要打印的标题格式(左对齐,长度不够的部分会用空格替代)
    printf("%-20st%-5st%-5st%-12st%-20sn", "名字", "年龄", "性别", "电话", "地址");
  2. 再打印数据
void PRINTContact(const Contact* pc)
{
	int i = 0;
	//打印标题
	printf("%-20st%-5st%-5st%-12st%-20sn", "名字", "年龄", "性别", "电话", "地址");
	//打印数据
	for ( i = 0; i < pc->sz; i++)
	{
		printf("%-20st%-5dt%-5st%-12st%-20sn",
			pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele, 
			pc->data[i].addr);
	}
}

FindByName函数部分(通过名字查找对象)

DELContact函数、SEARCHContact函数 和 MODIFYContact函数 都需要查找对象,所以统一使用函数FindByName进行查找。

需要:

  • 如果找到 - 返回查找到的对象的下标;
  • 如果没找到 - 返回 -1 。

该函数在 contact.c 内部被使用

int pos = FindByName(pc, name);//传入通讯录和要查找的对象的名字

所以也在 contact.c 内部创建:

static int FindByName(Contact* pc, char name[])//此处name是字符串
{
	int i = 0;
	for ( i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)//找到了
			return i;
	}
	return -1;//找不到
}

依旧是遍历通过通讯录的方式找到目标。同时,因为其他源文件内不需要使用该函数,所以加上 static 保护函数。

DELContact函数部分(删除联系人的信息)

写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)

 分析:

  1. 通讯录内部的信息为空时,不用删除。
  2. 要删除对象,首先对象需要存在,所以先要查找。接下来:

如果对象不存在,不用删除;

如果对象存在,执行删除操作。

要删除对象,只需要把要删除的对象后面的所有对象往前移动一个元素就可以了。如:

void DELContact(Contact* pc)
{
	char name[MAX_NAME];

	//检测通讯录是否为空
	if (pc->sz == 0)
	{
		printf("通讯录为空,无需删除n");
		return;
	}

	printf("请输入要删除联系人的名字:");
	scanf("%s", name);

	//1.查找删除的对象
	//判断:对象是否存在
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要删除的联系人不存在n");
		return;
	}

	//2.对象存在,执行删除操作
	int i = 0;
	for (i = pos; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功n");
}

为什么此处的 for (i = pos; i < pc->sz - 1; i++) 内部是 sz - 1 呢?

但是当 i + 1 取到5时,访问的是这个数组的第6个元素,删除对象后元素都往前移动一个元素,本就无法访问下标为 i + 1 的那个元素。

SEARCHContact函数部分(查找指定联系人)

写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)

大部分代码和前面有重复,这里直接上代码

void SEARCHContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要查找的联系人的名字:");
	scanf("%s", name);

	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在n");
		return;
	}

	else
	{
		printf("%-20st%-5dt%-5st%-12st%-20sn",
			pc->data[pos].name,
			pc->data[pos].age,
			pc->data[pos].sex,
			pc->data[pos].tele,
			pc->data[pos].addr);
                        //打印部分和之前实现的PRINTContact相类似,但是没有循环
	}
}

MODIFYContact函数部分(修改指定联系人)

修改信息需要传输地址,在 test.c 中

MODIFYContact(&con);//传入地址

声明函数  contact.h

void MODIFYContact(Contact* pc);//与之前的函数类似

实现函数  contact.c

void MODIFYContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	printf("请输入要修改的联系人的名字:");
	scanf("%s", name);

	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在n");
		return;
	}

	else
	{
		printf("请输入名字:");
		scanf("%s", pc->data[pos].name);//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函数部分(排序联系人信息)

写入函数(test.c)→ 声明函数(contact.h) → 实现函数(contact.c)

复习 strcmp函数 的返回值

int strcmp ( const char * str1, const char * str2 );
  • 当 str1 指向的字符串的对应字符的ASCII值 < str2 指向的字符串的时,返回一个小于0的值;
  • 当 str1 指向的字符串和 str2 指向的字符串一致时,返回 0 ;
  • 当 str1 指向的字符串的对应字符的ASCII值 > str2 指向的字符串的时,返回一个大于0的值。

首先是检测通讯录是否为空。

其次,通过冒泡排序把姓名下ASCII值较小的联系人排到前面。

void SORTContact(Contact* pc)
{
	if (pc->data[0].name == 0)
	{
		printf("通讯录为空,无法排序n");
		return;
	}

	int i = 0;
	int j = 0;
	for (i = 0; i < pc->sz; i++)
	{
		for (j = 0; j < pc->sz - 1; j++)
		{
			PeoInfo tmp[] = { 0 };
			if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
			{
				tmp[0] = pc->data[j];
				pc->data[j] = pc->data[j + 1];
				pc->data[j + 1] = tmp[0];
			}
		}
	}
	printf("排序成功n");
}

优化

1.如果出现重名的联系人该怎么办?

一种解决方法:

多列出一列行号,通过行号来区分不同的联系人。会需要处理重名的联系人的函数只有DELContact函数和MODIFYContact函数,这里只举例DELContact函数。

  • 如果需要删除多个联系人,就需要循环了,因为进入时就需要执行一次,所以使用了do循环。
  • 按照上面的想法,就需要多打印一行行号,这时会发现需求的功能和SEARCHContact函数类似,所以直接改造SEARCHContact函数。
  • 因为之前定义的变量pos刚好可以指向行号,所以使用该变量书写行号。

这就是第一个问题,如何改变变量pos?

pos = FindByName(pc, name, pos);
//……
pos++;

这里从函数FindByName入手,因为这样可以筛出每一个同名的联系人。

FindByName函数只改变了循环条件

for (; i < pc->sz; i++)

接下来就是在SEARCHContact函数内部增加一行打印行号即可。

  • 而DELContact函数需要的就是按照用户输入的行号来删除联系人信息。

这里还有一个问题:如果用户只要求删除一个联系人,这种情况要怎么办?

上代码:

do
{		
//……


    pos = FindByName(pc, name, 0);
    if (pos == -1)//检测是否还有要求的联系人信息存在
    {
	printf("修改完毕n");
	return;
    }

    printf("是否继续修改(Y/N):");
    scanf("%c", &ch);
    getchar();
    printf("退出成功n");
} while (ch != 'N');

代码分为两部分:

1.检测联系人信息是否依然存在;

2.询问客户是否依旧修改。

2.把数据移动到堆上

为什么要把数据移动到堆上呢?

  • 因为可能一次性开辟了过大的空间,这些空间可能无法被用完:一开始用户可能只有10个联系人,之后才会慢慢增加,可能最后增加到100个联系人就是极限了。这种情况下空间就被浪费了。
  • 而另一边,可能1000个位置不够用,这怎么办?

这就需要动态内存开辟:当需要的时候再增加空间,使得使用更加高效。

源代码

test.c
#define _CRT_SECURE_NO_WARNINGS

//1. 通讯录能够存放1000个人的信息
//		每个人的信息:
//		名字 + 年龄 + 性别 + 电话 + 地址
//2. 增加人的信息
//3. 删除指定人的信息
//4. 修改指定人的信息
//5. 查找指定人的信息
//6. 排序通讯录的信息

#include
#include"contact.h"

void menu()
{
	printf("************************************n");
	printf("*****    1. add      2. del    *****n");
	printf("*****    3. search   4. modify *****n");
	printf("*****    5. sort     6. print  *****n");
	printf("*****    0. exit               *****n");
	printf("************************************n");
}

enum Option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SORT,
	PRINT
};

int main()
{
	int input = 0;
	char name[MAX_NAME] = { 0 };

	//创建通讯录
	Contact con;

	//初始化通讯录的函数
	InitContact(&con);//传入 con 的地址

	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);

		switch (input)
		{
		case ADD://增加信息
			ADDContact(&con);//传入地址
			break;
		case DEL://删除信息
			DELContact(&con);
			break;

		case SEARCH://查找信息
			printf("请输入要查找的联系人的名字:");
			scanf("%s", name);
			SEARCHContact(&con, name);
			break;

		case MODIFY://修改信息
			printf("请输入要修改的联系人的名字:");
			scanf("%s", name);
			MODIFYContact(&con, name);
			break;

		case SORT://排序
			SORTContact(&con);
			break;
		case PRINT://打印信息
			PRINTContact(&con);
			break;
		case EXIT://退出
			printf("退出通讯录n");
			break;
		default:
			printf("选择错误,请重新选择n");
			break;
		}

	} while (input);
	return 0;
}

contact.h
#include
#include

#define MAX_NAME 20
#define MAX_SEX 10
#define MAX_TELE 12
#define MAX_ADDR 30

#define MAX 1000

//类型的定义
typedef struct PeoInfo
{
	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;//记录当前通讯录中有效信息的个数
}Contact;

//初始化通讯录
void InitContact(Contact* pc);

//增加联系人
void ADDContact(Contact* pc);

//打印联系人信息
void PRINTContact(const Contact* pc);

//删除联系人的信息
void DELContact(Contact* pc);

//查找指定联系人
void SEARCHContact(Contact* pc, char name[]);

//修改指定联系人信息
void MODIFYContact(Contact* pc, char name[]);

//排序联系人信息
void SORTContact(Contact* pc);

contact.c
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"

//初始化
void InitContact(Contact* pc)
{
	pc->sz = 0;

	//memset() - 内存设置函数,可以设置一块连续的内存
	memset(pc->data, 0, sizeof(pc->data));
}

//增加信息
void ADDContact(Contact* pc)
{
	//处理通讯录满了的情况
	if (pc->sz == MAX)
	{
		printf("通讯录已满,无法添加n");
		return;
	}

	//增加一个人的信息
	printf("请输入名字:");
	scanf("%s", pc->data[pc->sz].name);//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");
}

//打印信息
void PRINTContact(const Contact* pc)
{
	int i = 0;
	//打印标题
	printf("%st%-20st%-5st%-5st%-12st%-20sn", "行号", "名字", "年龄", "性别", "电话", "地址");
	//打印数据
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20st%-5dt%-5st%-12st%-20sn",
			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[], int i)
{
	for (; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)//找到了
			return i;
	}
	return -1;//找不到
}

//删除信息
void DELContact(Contact* pc)
{
	char name[MAX_NAME];
	int pos = 0;
	char ch = 0;

	//检测通讯录是否为空
	if (pc->sz == 0)
	{
		printf("通讯录为空,无需删除n");
		return;
	}

	printf("请输入要删除联系人的名字:");
	scanf("%s", name);
	getchar();

	//1.查找删除的对象
	//判断:对象是否存在
	pos = FindByName(pc, name, pos);
	if (pos == -1)
	{
		printf("要删除的联系人不存在n");
		return;
	}

	//2.对象存在,执行删除操作
	do
	{
		SEARCHContact(pc, name);
		printf("请输入需要删除的联系人所在的行号:");
		scanf("%d", &pos);
		getchar();

		int i = 0;
		for (i = pos; i < pc->sz - 1; i++)
		{
			pc->data[i] = pc->data[i + 1];
		}
		pc->sz--;
		printf("删除成功n");

		pos = FindByName(pc, name, 0);
		if (pos == -1)
		{
			printf("删除完毕n");
			return;
		}

		printf("是否继续删除(Y/N):");
		scanf("%c", &ch);
		getchar();
	} while (ch != 'N');

	printf("退出成功n");
}

//查找指定联系人
void SEARCHContact(Contact* pc, char name[])
{
	int pos = 0;
	pos = FindByName(pc, name, pos);

	if (pos == -1)
	{
		printf("要查找的人不存在n");
		return;
	}

	else
	{
		printf("%st%-20st%-5st%-5st%-12st%-20sn", "行号", "名字", "年龄", "性别", "电话", "地址");

		while (pc->data[pos].age)
		{
			pos = FindByName(pc, name, pos);
			if (pos != -1)
			{
				printf("%dt%-20st%-5dt%-5st%-12st%-20sn",
					pos,
					pc->data[pos].name,
					pc->data[pos].age,
					pc->data[pos].sex,
					pc->data[pos].tele,
					pc->data[pos].addr);

				pos++;
			}
		}
	}
}

//修改指定联系人
void MODIFYContact(Contact* pc, char name[])
{
	int pos = 0;
	char ch = 0;

	pos = FindByName(pc, name, pos);
	if (pos == -1)
	{
		printf("要查找的人不存在n");
		return;
	}

	do
	{
		SEARCHContact(pc, name);
		printf("请输入需要修改的联系人所在的行号:");
		scanf("%d", &pos);
		getchar();

		printf("请输入名字:");
		scanf("%s", pc->data[pos].name);//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");

		pos = FindByName(pc, name, 0);
		if (pos == -1)
		{
			printf("修改完毕n");
			return;
		}

		printf("是否继续修改(Y/N):");
		scanf("%c", &ch);
		getchar();
		printf("退出成功n");
	} while (ch != 'N');

	printf("退出成功n");
}



//排序信息
void SORTContact(Contact* pc)
{
	if (pc->data[0].name == 0)
	{
		printf("通讯录为空,无法排序n");
		return;
	}

	int i = 0;
	int j = 0;
	for (i = 0; i < pc->sz; i++)
	{
		for (j = 0; j < pc->sz - 1; j++)
		{
			PeoInfo tmp[] = { 0 };
			if (strcmp(pc->data[j].name, pc->data[j + 1].name) > 0)
			{
				tmp[0] = pc->data[j];
				pc->data[j] = pc->data[j + 1];
				pc->data[j + 1] = tmp[0];
			}
		}
	}
	printf("排序成功n");
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/928558.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号