[C] 类团队合作:“通讯录管理系统”课程设计案例

*注:如遇安全性错误C4996可添加此行代码于文件头:#pragma warning(disable :4996)。

**注:【文中代码作为笔记,作者已不再维护,仅供参考】

1、本文案例粗糙模拟了一个多人团队的共同项目场景,有多个  源文件  和  头文件  ,代码内容仅供参考,如有需要,请自行根据实际情况更改文件名等相关参数。

2、由于数据存储结构使用了并不合适此案例的   动态内存分配   ,且个人精力有限、部分bug修补难度较大,修补安排将无限期推迟。所以本文代码仅作抛砖引玉,分享经验之用,请谅解。

【重要】文中代码已有部分bug已知如下:

①(恶性)请不要删除某一类的所有联系人,否则一旦再次读取就会崩坏。

弥补:可以修改联系人时顺便改变类型名(原因:结构中用到了数组,改动数据比较麻烦,修补难度大)

其他:如遇问题可关闭程序,打开项目目录,手动修改 或 删除 Linker.txt文件,再重启即可从头再来

项目文件大概构成 

一、案例解决方案代码分享:

相关介绍:头文件的编写(格式)

//头文件应可独立编译,不依赖其他 额外 头文件,但也不能包含无用头文件
//头文件格式如下: 以保证其对应源文件自调用不出错,且使得函数对应关系可查
#ifndef  _头文件名全大写_H_
#define _头文件名全大写_H_

   中间部分写头文件内容

#endif // ! _头文件名全大写_H_

可以在任何可以使用 #if 的地方使用 #ifdef 和 #ifndef 指令。 如果定义了 identifier#ifdefidentifier 语句等效于 #if 1。 如果 identifier 尚未定义或未被 #undef 指令定义,它等效于 #if 0。 这些指令只检查使用 #define 定义的标识符是否存在,而不检查在 C 或 C++ 源代码中声明的标识符。

提供这些指令只是为了实现与该语言的早期版本的兼容性。 首选与 #if 指令一起使用的 defined(identifier) 常量表达式。

#ifndef 指令检查与 #ifdef 检查的条件相反的情况。 如果尚未定义标识符,或者如果它的定义已用 #undef 删除,则条件为 true(非零值)。 否则,条件为 false (0)。

                                                                ————截自Microsoft.Visual Studio 2022帮助文档

1、结构体定义(即head.h内容)(应由所有成员共同讨论并最先确定)

#ifndef _HEAD_H_
#define _HEAD_H_

#include <stdio.h>

//联系人节点
typedef struct linkman {
	char Name[11];//名字最多5个字
	char PhoneNumber[15];//手机号最多14位
	char eMail[21];//邮箱地址最多20位
	linkman* next;//单链表结构指向下一个联系人
}LinkMan;

//类型节点
typedef struct {
	char Name[7];//类型名3个字
	LinkMan* First;//指向第一个联系人
}Class;//结构:类型节点->分别指向各类型下的联系人链表

#endif

2、从文件中读写记录(即iofile.cpp内容)(使记录能在上述结构体构造的链表形式和文件存档间转换)(Linker.txt中内容  格式请参考此处fwriter函数  

#include <stdio.h>
#include <stdlib.h>
#include "iofile.h" //调用自己的头文件

//根据传入数字动态分配空内存并返回其指针
void* MemoryOpenUp(int i)
{
	return ((void*)malloc(i));
}

//从文件读入联系人的数据,并构建联系人链表,返回第一个联系人的地址
LinkMan* fReadLinkMan(FILE** fp, LinkMan* lm)//传入文件指针的地址,保证此文件指针在读取时后移,同时影响到调用函数
{
	for (char c = fgetc(*fp); c != '\n'; c = fgetc(*fp));//从文件读一个字符,不是换行就继续读
	if (fgetc(*fp) == '#') {//如果这行一开头就是“#”号就结束读取
		free(lm);//释放空间
		return NULL;//返回空指针作为上一个联系人的链接内容
	}
	rewind(stdin);
	fscanf_s(*fp, "%10s", &lm->Name, 10);//读10位字符,作为联系人名字,第15位留作\0,中文一个站2位
	fscanf_s(*fp, "%14s", &lm->PhoneNumber, 14);//读14位,作手机号
	fscanf_s(*fp, "%20s", &lm->eMail, 20);///读20位,作邮箱
	lm->next = (LinkMan*)MemoryOpenUp(sizeof(LinkMan));//创建好下一个联系人的空间
	lm->next = fReadLinkMan(fp, lm->next);//下一个联系人的地址用递归接收
	return lm;//返回这个联系人的地址作为上一联系人的链接内容
}

//传入类结点的指针,协调调用联系人链表构建函数,从文件读入数据并构建链表
void fBuildLinkMan(FILE* fp, Class* cla)
{
	for (char c = fgetc(fp); c != '#'; c = fgetc(fp));//从文件读一个字符,不是#就继续读
	fgets(cla->Name, 7, fp);//读7个字符作类型名
	cla->First = (LinkMan*)MemoryOpenUp(sizeof(LinkMan));//创建好第一个联系人的空间
	fReadLinkMan(&fp, cla->First);//调用读取联系人的函数以构建链表
}

//传入类结点,将此类的内容全部写入文件
void fWriter(FILE* fp, Class* cla)
{
	LinkMan* Linker = cla->First;//读出第一个联系人
	fprintf(fp, "#%-7s\n", cla->Name);//向文件写入  #类型名___   ("-"左端对齐)(“7”用空格补满7个字符)后换行
	for (; Linker != NULL; Linker = Linker->next) {//不读到最后一个联系人,就写入信息后继续读下一个
		fprintf(fp, "-%-11s", Linker->Name);//写入联系人名字
		fprintf(fp, "%-15s", Linker->PhoneNumber);//写入电话号码
		fprintf(fp, "%-21s\n", Linker->eMail);//写入邮箱地址并换行
	}
	fprintf(fp, "#\n");//写入   #   号,表示一类终了
}
其头文件(即iofile.h内容)
#ifndef _IOFILE_H_
#define _IOFILE_H_

#include <stdio.h>
#include "head.h"

void* MemoryOpenUp(int i);
LinkMan* fReadLinkMan(FILE** fp, LinkMan* lm);
void fBuildLinkMan(FILE* fp, Class* cla);
void fWriter(FILE* fp, Class* cla);

#endif

3、对读入内存后的记录进行操作(即rwmemory.cpp内容)(一些处理链表的函数)

#include <stdlib.h>
#include <windows.h>
#include "rwmemory.h"
#include "iofile.h"

//判断用户选择是y/n
int YON()
{
	while (1) {
		rewind(stdin);//清缓冲
		switch (getchar()) {//读一个字符,并进入判断
		case 'Y':
		case 'y':
			return 1;//Y/y返回1
		case 'N':
		case 'n':
			return 0;//N/n返回0
		default:
			printf("请输入y/n\n");//输入错误则循环
		}
	}
}

//读取类型中第i个联系人并返回其指针,如果位置不合理就返回NULL
LinkMan* VisitLinkMan(Class* cla, int i)
{
	LinkMan* p = cla->First;
	if (int n = CountLinkMan(p) < i || i <= 0) {
		printf("位置不合理!\n");
		return NULL;
	}//判断像访问的结点是否合理,不合理就返回NULL,反之继续
	for (int k = 0; k < i - 1; p = p->next, k++);//将p一直向下一个指,知道指向第i个结点(从1开始数
	return p;
}

//查找重复结点,重复传回1,反之0
int SearchRepeatLinkMan(Class* cla, LinkMan* lm)
{
	LinkMan* p = cla->First;
	for (; p != lm; p = p->next) {
		if ((!strcmp(lm->Name, p->Name)) && (!strcmp(lm->PhoneNumber, p->PhoneNumber))) {
			printf("此联系人已录入!\n");
			system("pause");
			return 1;
		}
	}
	return 0;
}

//新建类结点,如果传入NULL就是第一次启动,从头新建
Class* InputClass(int* numberofclass, Class* CLA)
{
	int i;
	int n = *numberofclass;
	rewind(stdin);//清空缓冲区留下的一个回车,fflush(stdin)已无效
	while (1) {
		printf("要定义多少个类?\n");
		scanf_s("%d", &i);
		if (i >= 1)
			break;
		printf("不少于1个!!\n\n");
		system("pause");//暂停
		system("cls");//清屏
	}
	if (!CLA)
		CLA = (Class*)realloc(CLA, (*numberofclass = i) * sizeof(Class));//动态内存分配,获取一个  Class类数组(包含n个Class
	else
		CLA = (Class*)realloc(CLA, (*numberofclass = *numberofclass + i) * sizeof(Class));//重新分配内存增加类结点个数
	for (; n < *numberofclass; n++) {//搭配构建函数,循环构建各个类
		system("cls");//清屏
		printf("输入第 %d 个类的名字:(名字不多于3个汉字/6个字母\n", n + 1);
		rewind(stdin);//置空缓冲区
		gets_s((CLA)[n].Name);//获取类型名
		(CLA)[n].First = (LinkMan*)MemoryOpenUp(sizeof(LinkMan));
		if (!((CLA)[n].First = InputLinkMan(0, &(CLA)[n], (CLA)[n].First))) {//链接第一个联系人(每个类最多15个联系人‘0’指这是第1个人
			printf("不能是空的类!\n");
			system("pause");//暂停
			n--;
		}
	}
	return CLA;
}

//传入此类联系人总数n,对lm结点修改数据,并可在其后继续添加结点,最后一个结点的next赋位NULL
LinkMan* InputLinkMan(int n, Class* cla, LinkMan* lm)
{
	do {
		system("cls");//清屏
		printf("目前已录入%d个联系人,不超过15个,是否继续录入?y/n\n", n);
		rewind(stdin);//清缓冲
		if (!YON()) {
			free(lm);//释放空间
			return NULL;//不同意就结束
		}
		printf("请输入名字?(最多5个汉字/10个字母\n");
		scanf_s("%11s", &lm->Name, 11);//读入字符串作为名字
		printf("请输入电话号码?(最多14位数字\n");
		scanf_s("%15s", &lm->PhoneNumber, 15);
		/*if (n)
			if (SearchRepeatLinkMan(cla, lm)) {
				free(lm);
				return NULL;
			}*/
	} while (n && SearchRepeatLinkMan(cla, lm));
	printf("请输入邮箱?(最多20位\n");
	scanf_s("%21s", &lm->eMail, 21);
	rewind(stdin);
	lm->next = (LinkMan*)MemoryOpenUp(sizeof(LinkMan));//创建下一个联系人的空间
	lm->next = InputLinkMan(n + 1, cla, lm->next);//下一个联系人的地址用递归返回接收来链接
	return lm;//返回这个联系人的地址作为上一联系人的链接内容
}

//传入类结点总个数和类结点数组,读取并显示所有类
void ReadClass(int n, Class* cla)
{
	system("cls");//清屏
	printf("保存的所有类型如下:\n");
	for (int i = 0; i < n; printf_s("%d	%s\n", i, cla[i++].Name));
}

//读取并显示传入的类cla下的所有联系人
void ReadLinkMan(Class* cla, LinkMan* lm)
{
	static int n = 1;//唯一一次初始化:在程序运行全过程只执行一次
	if (lm == NULL)
		return;
	printf_s("%d、姓名:%-11s\t电话:%-15s\t%-7s\t邮箱:%-21s\n", n++, lm->Name, lm->PhoneNumber, cla->Name, lm->eMail);//左端对齐,补全位数后制表对齐
	ReadLinkMan(cla, lm->next);
	n = 1;//显示完后重置为1,以保证下次读取编号不出错
}

//数一数一共多少个联系人(从1开始
int CountLinkMan(LinkMan* lm)
{
	if (lm->next == NULL)
		return 1;
	return (CountLinkMan(lm->next) + 1);//递归计数类型下联系人总数
}

//传入类结点及目标联系人序号,在其下一个位置添加新联系人节点
void AddLinkMan(Class* cla, int i)
{
	LinkMan* p, * q;
	if (!i) {//添加在现有第一位前时
		if (!(p = InputLinkMan(CountLinkMan(cla->First), cla, (LinkMan*)MemoryOpenUp(sizeof(LinkMan)))))//p被赋值为动态分配内存所得地址,且已基于此地址已构建单链表
			return;//注:为保证计数函数正常,p的赋值必须在计数结束后
		for (; p->next; p = p->next);//不读到最后一个联系人就不停读下一个
		p->next = cla->First;//接上后段
		cla->First = p;//接上头
		return;
	}
	if (!(p = VisitLinkMan(cla, i)))//给p赋值输入编号对应联系人的地址,若找不到就结束函数
		return;
	q = p->next;//保存后段
	if (!(p->next = InputLinkMan(CountLinkMan(cla->First), cla, (LinkMan*)MemoryOpenUp(sizeof(LinkMan))))) {
		p->next = q;//选择不添加联系人,接回后段,无事发生
		return;
	}
	for (; p->next; p = p->next);
	p->next = q;//接上后段,前段一直接着没动过
}

//传入类结点及目标联系人序号,删除此联系人节点
void DeleteLinkMan(Class* cla, int i)
{
	LinkMan* q = VisitLinkMan(cla, i);
	if (i == 1)
		cla->First = cla->First->next;
	else {
		LinkMan* p = VisitLinkMan(cla, i - 1);
		p->next = q->next;
	}
	free(q);
	return;
}

//传入类及联系人序号对联系人内容进行修改
void ChangeLinkMan(Class* cla, int i)
{
	LinkMan* p = VisitLinkMan(cla, i);
	while (1) {
		system("cls");
		printf("联系人信息如下:\n%s   %s   %s   %s\n要修改那一项(输入 0 退出)?\n1、姓名\n2、手机号\n3、此类型的名字\n4、邮箱\n", p->Name, p->PhoneNumber, cla->Name, p->eMail);
		rewind(stdin);//清缓冲
		switch (getchar()) {
		case '1':
			printf("输入新的联系人姓名:\n");
			rewind(stdin);
			gets_s(p->Name);
			break;
		case '2':
			printf("输入新的联系人手机号:\n");
			rewind(stdin);
			gets_s(p->PhoneNumber);
			break;
		case '3':
			printf("输入新的类型名:\n");
			rewind(stdin);
			gets_s(cla->Name);
			break;
		case '4':
			printf("输入新的联系人邮箱:\n");
			rewind(stdin);
			gets_s(p->eMail);
			break;
		case '0':
			return;
		default:
			printf("请重新输入选择\n");
		}
	}
}

//选择类,读取并显示其中联系人
int ChooseLM(int n, Class* CLA)
{
	ReadClass(n, CLA);
	printf("选择哪一个类?(输入 0 退出\n");
	rewind(stdin);
	scanf_s("%d", &n);
	system("cls");
	if (!n)
		return 0;
	ReadLinkMan(&CLA[n - 1], CLA[n - 1].First);
	return n;
}
其头文件(即rwmemory.h)
#ifndef _RWMEMORY_H_
#define _RWMEMORY_H_

#include <stdio.h>
#include "head.h"

int YON();
LinkMan* VisitLinkMan(Class* cla, int i);
int SearchRepeatLinkMan(Class* cla, LinkMan* lm);
Class* InputClass(int* numberofclass, Class* CLA);
LinkMan* InputLinkMan(int n, Class* cla, LinkMan* lm);
void ReadClass(int n, Class* cla);
void ReadLinkMan(Class* cla, LinkMan* lm);
int CountLinkMan(LinkMan* lm);
void AddLinkMan(Class* cla, int i);
void DeleteLinkMan(Class* cla, int i);
void ChangeLinkMan(Class* cla, int i);
int ChooseLM(int n, Class* CLA);

#endif

4、别的一些函数和main函数(即main.cpp)

#include <stdlib.h>
#include <windows.h>
#pragma comment (lib, "winmm.lib") //引入播放声音的而相关函数
#include "rwmemory.h"
#include "iofile.h" //在自定义头文件中统一声明结构体和各个函数

//拨打电话效果
void Call(Class* cla, int i)
{
	LinkMan* p;
	if ((p = VisitLinkMan(cla, i)) == NULL)
		return;
	printf("%s\n%s\n", p->Name, p->eMail);
	for (int i = 0; i < 12; printf("%c", p->PhoneNumber[i++]), PlaySound(TEXT("//*电话拨号声音文件名,请将文件至于代码同一目录下*//"), NULL, SND_FILENAME | SND_ASYNC), Sleep(700));
	printf("\n");
}

//功能菜单
char Menu()
{
	system("cls");//清空屏幕
	printf("          |-------------------------------|\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |      欢迎使用通信录管理软件   |\n");
	printf("          |                               |\n");
	printf("          |---- - - - - - - - - - - - ----|\n");
	printf("          |                               |\n");
	printf("          |	  |-------------          |         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |1.添加联系人           |         \n");
	printf("          |	  |(可新建类              |         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |2.查看联系人           |         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |3.修改联系人           |         \n");
	printf("          |	  |(可修改其所在类的类型名|         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |4.删除联系人           |         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |5.拨号功能             |         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |0.退出                 |         \n");
	printf("          |	  |                       |         \n");
	printf("          |	  |------------           |         \n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |     输入数字择所需要的功能      |\n");
	printf("          |---- - - - - - - - - - - - ----|\n");
	printf("\n");
	return (getchar());//读取输入的一个字符并作为返回值
}

//结束页面
void EDMenu()
{
	system("cls");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |      运行完毕,感谢使用       |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("          |                               |\n");
	printf("\n");
}

//主程序
int main()
{
	FILE* fp; Class* CLA; int numberofclass, i, j; char c;//定义变量:文件指针;3个类型;数字\字符变量(随取随用)
	numberofclass = 0;
	if ((fp = fopen("Linker.txt", "r")) == NULL) {//以读取方式打开文件,如果没有文件就执行,否则跳到else
		printf("欢迎使用...开始添加联系人?y/n\n");
		if (YON()) //如果是y就继续,不是就结束
			CLA = InputClass(&numberofclass, NULL);
		else {
			EDMenu();
			system("pause");
			return 0;
		}
		printf("录入完毕");
		system("pause");
		fp = fopen("Linker.txt", "w");//从头写入所有数据
		fprintf(fp, "%d\n", numberofclass);//在文件头写入所有类型总个数
		for (i = 0; i < numberofclass; fWriter(fp, &(CLA)[i++]));//按类型写入文件
		fclose(fp);
		fp = fopen("Linker.txt", "r");
		system("cls");
	}
	//首次运行相关操作
	fscanf_s(fp, "%d", &numberofclass);//读取文件开头保存的类型总个数
	CLA = (Class*)malloc((numberofclass) * sizeof(Class));//建立类型组,方便调用
	for (i = 0; i < numberofclass; fBuildLinkMan(fp, &(CLA)[i++]));//读取文件中数据,创建第类及其联系人链表
	fclose(fp);//关掉只读权限的文件
	while (1) {
		switch (c = Menu()) {//调用打开菜单并获取返回值到c
			//在这里添代码不会被执行...
		case '1'://返回值是1就执行以下内容
			system("cls");
			printf("是否需要新建类?y/n\n");
			if (YON()) {
				CLA = InputClass(&numberofclass, CLA);
				break;
			}
			if (!(i = ChooseLM(numberofclass, CLA)))
				break;
			printf("将联系人添加在第几位后?(输入 0 添加在第一位)\n");
			rewind(stdin);
			scanf_s("%d", &j);
			rewind(stdin);
			AddLinkMan(&(CLA)[i - 1], j);
			break;
		case '2'://是2就以下(下同
			while (1) {
				if (!(i = ChooseLM(numberofclass, CLA)))
					break;
				printf("\n切换类别否则退出?y/n\n");
				if (!YON())
					break;
			}
			break;
		case '3':
			while (1) {
				if (!(i = ChooseLM(numberofclass, CLA)))
					break;
				printf("修改第几位联系人?(输入 0 退出)\n");
				rewind(stdin);
				scanf_s("%d", &j);
				if (!j)
					break;
				ChangeLinkMan(&(CLA)[i - 1], j);
			}
			break;
		case '4':
			if (!(i = ChooseLM(numberofclass, CLA)))
				break;
			printf("删除第几位联系人?(输入 0 退出)\n");
			rewind(stdin);
			scanf_s("%d", &j);
			if (!j)
				break;
			DeleteLinkMan(&(CLA)[i - 1], j);
			printf("联系人已被删除...");
			system("pause");
			break;
		case '5':
			if (!(i = ChooseLM(numberofclass, CLA)))
				break;
			printf("拨打第几位联系人?(输入 0 退出\n");
			rewind(stdin);
			scanf_s("%d", &j);
			if (!j)
				break;
			Call(&(CLA)[i - 1], j);
			system("PAUSE");
			break;
		case '0':
			EDMenu();
			break;
		default:
			printf("无效的选择%c\n", c);
		}
		if (c == '0')
			break;
	}
	fp = fopen("Linker.txt", "w");//从头写入所有数据
	fprintf(fp, "%d\n", numberofclass);//在文件头写入所有类型总个数
	for (i = 0; i < numberofclass; fWriter(fp, &(CLA)[i++]));//按类型写入文件
	fclose(fp);
	return 0;
}

总结:

团队开发是如项目开发中常见的模式,学习了解模块化开发等相关规范,也有助于我们平时个人练习时提高代码可读性和可维护性,是十分实用的技能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值