*注:如遇安全性错误C4996可添加此行代码于文件头:#pragma warning(disable :4996)。
**注:【文中代码作为笔记,作者已不再维护,仅供参考】
1、本文案例粗糙模拟了一个多人团队的共同项目场景,有多个 源文件 和 头文件 ,代码内容仅供参考,如有需要,请自行根据实际情况更改文件名等相关参数。
2、由于数据存储结构使用了并不合适此案例的 动态内存分配 ,且个人精力有限、部分bug修补难度较大,修补安排将无限期推迟。所以本文代码仅作抛砖引玉,分享经验之用,请谅解。
【重要】文中代码已有部分bug已知如下:
①(恶性)请不要删除某一类的所有联系人,否则一旦再次读取就会崩坏。
弥补:可以修改联系人时顺便改变类型名(原因:结构中用到了数组,改动数据比较麻烦,修补难度大)
其他:如遇问题可关闭程序,打开项目目录,手动修改 或 删除 Linker.txt文件,再重启即可从头再来
项目文件大概构成
一、案例解决方案代码分享:
相关介绍:头文件的编写(格式)
//头文件应可独立编译,不依赖其他 额外 头文件,但也不能包含无用头文件
//头文件格式如下: 以保证其对应源文件自调用不出错,且使得函数对应关系可查
#ifndef _头文件名全大写_H_
#define _头文件名全大写_H_
中间部分写头文件内容
#endif // ! _头文件名全大写_H_
可以在任何可以使用
#if
的地方使用#ifdef
和#ifndef
指令。 如果定义了identifier
,#ifdef
identifier
语句等效于#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;
}
总结:
团队开发是如项目开发中常见的模式,学习了解模块化开发等相关规范,也有助于我们平时个人练习时提高代码可读性和可维护性,是十分实用的技能。