シーケンステーブル小規模プロジェクト --- アドレス帳の実現


序文

前のセクションのシーケンス テーブルに従って、アドレス帳を実装する小さなプロジェクトを実行しましょう。このアドレス帳には、静的と動的の 2 つのバージョンも実装されており、通信を行うために学習したファイル操作も追加されます。記録はディスクに保存できるため、過去の操作に関する連絡先のデータをクエリすることもできます。さっそく始めましょう。

1. 要件分析と静的アドレス帳の実装

1. アドレス帳の構造要件

まず連絡先情報を見てみましょう。連絡先の名前、性別、電話番号、年齢、住所などを保存する必要があります。文字配列を使用して、名前、性別、電話番号、住所をそれぞれ保存する必要があります。年齢を保存するには int 整数を使用する必要があります。これらの情報を保存するための連絡先構造
を作成します。アドレス帳構造を作成しています。1 つの構造メンバーは連絡先構造配列、もう 1 つのメンバーは現在の連絡先の数です。

#define MAX 3			//通讯录最大的容量
#define MAX_NAME 10		//联系人的姓名最大字符个数
#define MAX_GENDER 3	//联系人的性别最大字符个数
#define MAX_PHONE 15	//联系人的电话最大字符个数
#define MAX_ADDRESS 15	//联系人的住址最大字符个数
typedef struct People//联系人的信息
{
    
    
	char name[MAX_NAME];//姓名
	char gender[MAX_GENDER];//性别
	int age;//年龄
	char phone[MAX_PHONE];//电话
	char address[MAX_ADDRESS];//住址
}People;
typedef struct Communicate//通讯录的结构体
{
    
    
	People people[MAX];//联系人的结构体数组
	int position;//通讯录中人物的个数和要插入人物的位置
}Com;

連絡先を格納する情報構造配列の最大値を 3 に設定し、データがいっぱいになった場合のテストに便利であり、連絡先の個人情報には将来のデータ変更に備えて上限を設定します。連絡先用に作成する構造体配列は、シーケンス テーブルの整数配列と同等ですが、型が異なり、1 つは組み込み型で、もう 1 つはカスタム型です。ただし、実装はほぼ同じです。

2. アドレス帳の機能要件

これで、連絡先情報を保存するために使用できる構造ができました。次に、実装する機能を見てみましょう。連絡先を追加する
機能(ここではテールプラグを直接使用します)、連絡先を削除する機能(ここではクエリ関数と連携する必要があります)、連絡先を検索して変更する必要があります。最後に、すべての連絡先を name でソートして出力しますこの時点で、シンプルなメニューと実装される機能が表示されます。

enum OPTION//选则
{
    
    
	EXIT,//退出通讯录
	ADD,//增加联系人
	DEL,//删除联系人
	FIND,//查找联系人
	MODF,//修改联系人
	SORT,//对通讯录的人进行姓名排序
	PRIN//对通讯录的人物进行打印
};
void Initialization(Com* com);//初始话通讯录
void AddPeople(Com* com);//增加联系人
void DelPeople(Com* com);//删除联系人
void FindPeople(const Com* com);//查找联系人
void ModfPeople(Com* com);//修改联系人
void SortPeople(Com* com);//对通讯录的人进行姓名排序
void PrintPeople(const Com* com);//对通讯录的人物进行打印

ここでは列挙形式を使用しています。これは、サイクルを選択するときに名前を見ることで実現したい機能を直接知るのに便利です。これは今後実装する予定の機能です。必要なヘッダー ファイルをインクルードし、これまでのところ、独自のヘッダー ファイルのコンテンツ全体が完成しました。他の追加関数を実装する必要がある場合は、追加したい関数関数の宣言をヘッダー ファイルに直接置くことができます。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>

上記は私がインクルードしたヘッダーファイルです。

3.アドレス帳のメイン機能を作成する

main 関数の場合、応答の呼び出しインターフェイスを実装するだけで済みます。

#include"main1.h"
void menu()
{
    
    
	printf("****************************\n");
	printf("****   1.ADD    2.DEL   ****\n");
	printf("****   3.FIND   4.MODF  ****\n");
	printf("****   5.SORT   6.PRIN  ****\n");
	printf("****	   0.EXIT	****\n");
	printf("****************************\n");
}
int main()
{
    
    
	Com com;//创建通讯录
	Initialization(&com);//初始化通讯录
	int input = 0;
	do
	{
    
    
		menu();
		printf("请输入你的选则>>\n");
		scanf("%d", &input);
		switch (input)
		{
    
    
		case EXIT:
			printf("通讯录已退出\n");
			break;
		case ADD:
			AddPeople(&com);
			break;
		case DEL:
			DelPeople(&com);
			break;
		case FIND:
			FindPeople(&com);
			break;
		case MODF:
			ModfPeople(&com);
			break;
		case SORT:
			SortPeople(&com);
			break;
		case PRIN:
			PrintPeople(&com);
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	} while (input);
	return 0;
}

ここでは、switch の選択ループ ステートメントを通じて実装します。その中で、私たちの関数はメニュー関数で実装されています. スイッチ選択は列挙型なので、将来他の関数を追加するのに便利です. 必要な関数名を列挙型に追加し、選択に追加します構造体 目的の機能の名前。この利点は、達成したい機能に対応する番号を調べる代わりに、達成したい機能を直接知ることができることです
上記はメイン関数のソース ファイルの内容全体です。

4. アドレス帳で使用する機能の実装

1.アドレス帳の初期化

関数を実装する前に、まず独自のヘッダー ファイルをインポートする必要があります。

#include"main1.h"//引入我们自己的头文件
void Initialization(Com* com)//初始话通讯录
{
    
    
	assert(com);//进行断言,防止传入的指针或者地址不合法而造成的错误
	com->position = 0;//开始没有元素,所以位置为0
}

アドレス帳の初期化は、シーケンス テーブルの初期化と同じです。もちろん、連絡先の構造を初期化することも選択できます。ここではライブラリ関数を使用するだけです。

memset(com->people, 0, MAX*sizeof(People));//要加的代码
//函数原型:
//void *memset( void *dest, int c, size_t count );
//dest:要初始化的起始位置
//c:要初始化的内容
//count:要初始化的字节数

上記のコードを初期関数に追加して、連絡先の構造を初期化するだけです。
ここに画像の説明を挿入
これは、連絡先のメモリの構造体部分で関数を実行する前と後です。

2. アドレス帳に連絡先を追加する

静的アドレス帳を実装しているため、静的シーケンス テーブルを実装しているのと同じように、スペースが満杯かどうかを判断する必要があり、満杯でない場合にのみ連絡先を追加できます。

void AddPeople(Com* com)//增加联系人
{
    
    
	assert(com);
	if (com->position == MAX)//判断是否已满
	{
    
    
		printf("通讯录已满,无法添加\n");
		return;
	}
	printf("请输入要添加联系人的姓名:");
	scanf("%s", com->people[com->position].name);
	printf("请输入要添加联系人的性别:");
	scanf("%s", com->people[com->position].gender);
	printf("请输入要添加联系人的年龄:");
	scanf("%d", &(com->people[com->position].age));//年龄是整形,这里我们需要加上取地址符
	printf("请输入要添加联系人的电话:");
	scanf("%s", com->people[com->position].phone);
	printf("请输入要添加联系人的住址:");
	scanf("%s", com->people[com->position].address);
	printf("添加成功\n");
	com->position++;//对联系人进行加一
}

ここに画像の説明を挿入
ここで十分な 3 人を追加すると、すでに追加されていることがわかります。弊社の機能が正常に利用できることを示します。
注: 年齢を入力するときは住所文字を使用する必要がありますが、その他の場合は住所文字を使用する必要はありません。これは、配列名が配列の最初の要素のアドレスを表すためです (配列名が単独の場合)。アドレス文字または配列名が sizeof に単独で含まれる場合、配列名は配列全体のアドレスです)。

3. アドレス帳で連絡先を検索する

連絡先の削除と変更にはどちらも検索機能が必要です。私たちは、機能の低結合と高い凝集性を実現したいと考えています。したがって、シーケンス テーブル ルックアップ関数の実装と同様に、ルックアップ関数を個別にカプセル化します。

int Find(const Com* com)//查找人物,并返回该人物的下标,找不到则返回-1
{
    
    
	assert(com);
	char name[MAX_NAME] = {
    
     0 };//通过姓名查找
	printf("请输入要操作联系人的姓名:");
	scanf("%s", name);
	int i = 0;
	for (i = 0; i < com->position; i++)
	{
    
    
		if ( 0 == strcmp(com->people[i].name, name) )//判断改联系人是否和我们要查找的联系人姓名相同
		{
    
    
			return i;//相同返回改联系人的下标
		}
	}
	return -1;
}
void FindPeople(const Com* com)//查找联系人
{
    
    
	int find = Find(com);//用来判断是否找到该人物
	if (-1 == find)
	{
    
    
		printf("未找到该联系人\n");
		return;
	}
	printf("%-10s %-5s %-5s %-15s %-10s\n", "姓名", "性别", "年龄", "电话", "住址");//打印信息
	printf("%-10s %-5s %-5d %-15s %-10s\n", com->people[find].name, com->people[find].gender,
		com->people[find].age, com->people[find].phone, com->people[find].address);
	return;
}

検索関数のみを実装する find 関数を別途カプセル化します。ルックアップ関数は、検索した人物を出力します。
ここに画像の説明を挿入
ここでは、検索と検索の 2 つの操作を実行しました。
注: 文字列の比較は等号によって直接判断することはできませんが、比較を行うにはライブラリ関数 (strcmp) を使用する必要があります。

4. アドレス帳から連絡先を削除する

連絡先の削除を実装する前に、まず印刷機能を実装して、連絡先が正常に削除されたかどうかを確認できるようにします。

void PrintPeople(const Com* com)//对通讯录的人物进行打印
{
    
    
	assert(com);
	if (com->position == 0)//判断我们的结构体中是否有人
	{
    
    
		printf("暂无联系人\n");
		return;
	}
	int i = 0;
	printf("%-10s %-5s %-5s %-15s %-10s\n", "姓名", "性别", "年龄", "电话", "住址");
	for (i = 0; i < com->position; i++)
	{
    
    
		printf("%-10s %-5s %-5d %-15s %-10s\n", com->people[i].name, com->people[i].gender, 
			com->people[i].age,com->people[i].phone, com->people[i].address);
	}
	return;
}

印刷機能は、整数配列の走査が構造体配列の走査に変更されることを除いて、シーケンス テーブルとまったく同じです。
次に、削除関数を実装しましょう。まず、削除する人の名前を知る必要があります。このとき、先ほど検索関数で削除した人の添字を見つけることができます。後は、シーケンスの途中の要素を削除する操作です。テーブル。

void DelPeople(Com* com)//删除联系人
{
    
    
	assert(com);
	if (com->position == 0)//判断我们的结构体中是否有人
	{
    
    
		printf("暂无联系人,先添加联系人在删除吧\n");
		return;
	}
	int find = Find(com);//查找我们要删除的人
	if (-1 == find)
	{
    
    
		printf("未找到该联系人\n");
		return;
	}
	int i = 0;
	for (i = find; i < com->position - 1; i++)
	{
    
    
		com->people[i] = com->people[i + 1];//把这个联系人用后面的元素进行覆盖
	}
	com->position--;//对联系人的数量进行减一
	printf("删除成功\n");
	return;
}

ここに画像の説明を挿入
上記の操作によると、2 つの関数に何も問題がないことがわかります。

5. アドレス帳の連絡先を変更する

変更の前提は、まず連絡先を見つけることです

void ModfPeople(Com* com)//修改联系人
{
    
    
	int find = Find(com);
	if (-1 == find)
	{
    
    
		printf("未找到该联系人\n");
		return;
	}
	printf("请输入要修改联系人的姓名:");
	scanf("%s", com->people[find].name);
	printf("请输入要修改联系人的性别:");
	scanf("%s", com->people[find].gender);
	printf("请输入要修改联系人的年龄:");
	scanf("%d", &(com->people[find].age));
	printf("请输入要修改联系人的电话:");
	scanf("%s", com->people[find].phone);
	printf("请输入要修改联系人的住址:");
	scanf("%s", com->people[find].address);
	printf("修改成功\n");
}

ここでの変更はシーケンス テーブルと同じであり、変更するものは何もありません。ここでは、検索関数を呼び出して、見つかったかどうかを判断する必要があります。
ここに画像の説明を挿入
上記の操作によると、変更関数に何も問題がないことがわかります。私たちのニーズを満たすことができます。

6. 連絡先の並べ替え

連絡先の並べ替えには、次のように実装される名前による並べ替えを使用します。

int compar_by_name(const void* e1, const void* e2)
{
    
    
	return *((People*)e1)->name - *((People*)e2)->name;
}
void SortPeople(Com* com)//对通讯录的人进行姓名排序
{
    
    
	qsort(com->people, com->position, sizeof(People), compar_by_name);
}

ここでは並べ替えにライブラリ関数 (qsort) を使用していますが、比較ルールを自分で実装する必要があります。もちろん、配列表のソートも使用できますが、プラスチックの比較を構造内の名前の比較に変更する必要があります。
ここに画像の説明を挿入
こうして、静的アドレス帳が完成しました。

2. 要件分析と静的アドレス帳の実装

静的アドレス帳を実装しました。静的シーケンス テーブルを動的シーケンス テーブルに変更することで、静的アドレス帳を動的アドレス帳に簡単に変換できます。

1. アドレス帳の構造要件

typedef struct Communicate
{
    
    
	People* people;
	int position;//通讯录中人物的个数和要插入人物的位置
	int max;//通讯录的容量
}Com;

ここでは構造体配列は必要なくなり、代わりに構造体ポインタが必要になります。容量を表す整数値が追加されており、その他は静的シーケンス テーブルとまったく同じです。

2. アドレス帳の機能要件

ここでは、動的に割り当てられたメモリの解放を実現するための追加関数を追加します。プログラムが終了するときにこの関数を呼び出します。

void Destroy(Com* com);//对通讯录进行销毁

3. アドレス帳で使用する機能の実装

1.アドレス帳の初期化

void Initialization(Com* com)//初始话通讯录
{
    
    
	assert(com);//进行断言,防止传入的指针或者地址不合法而造成的错误
	com->position = 0;//开始没有元素,所以位置为0
	com->max = 3;//初始容量赋为3
	com->people = (People*)malloc(sizeof(People) * com->max);//开辟容量大小的联系人结构体
	if (com->people == NULL)//开辟失败就进行报错
	{
    
    
		perror("malloc");
		return;
	}
}

ここでは、データを保存できるように連絡先構造用のスペースを開く必要があります。これは、動的シーケンス テーブル用のスペースを空けることと同じです。思考の流れも変わりません。

2. アドレス帳に連絡先を追加する

void Expansion(Com* com)//判断是否进行扩容
{
    
    
	if (com->position == com->max)
	{
    
    
		People* peo = (People*)realloc(com->people, sizeof(People) * com->max * 2);
		if (peo == NULL)
		{
    
    
			perror("realloc");
			return;
		}
		com->people = peo;
		com->max *= 2;
	}
}
void AddPeople(Com* com)//增加联系人
{
    
    
	assert(com);
	Expansion(com);//判断否进行扩容
	printf("请输入要添加联系人的姓名:");
	scanf("%s", com->people[com->position].name);
	printf("请输入要添加联系人的性别:");
	scanf("%s", com->people[com->position].gender);
	printf("请输入要添加联系人的年龄:");
	scanf("%d", &(com->people[com->position].age));
	printf("请输入要添加联系人的电话:");
	scanf("%s", com->people[com->position].phone);
	printf("请输入要添加联系人的住址:");
	scanf("%s", com->people[com->position].address);
	printf("添加成功\n");
	com->position++;
}

ここでは、静的アドレス帳と比較して、静的アドレス帳が一杯であるかどうかを、拡張操作を実現するための関数に置き換えます。
ここに画像の説明を挿入

3. アドレス帳の破棄

void Destroy(Com* com)
{
    
    
	free(com->people);//释放为联系人结构体开辟的空间
	com->people = NULL;//把空间指向空
	com->max = 0;
	com->position = 0;
	return;
}

destroy 関数は、プログラムが終了するときに呼び出されます。
ここまでで、動的アドレス帳が完成しました。その他は静的アドレス帳と全く同じなので変更する必要はありません。

三、アドレス帳のファイル操作

1.アドレス帳の読み込み

void Loading(Com* com)//加载文件
{
    
    
	FILE* pf = fopen("test.txt", "rb");//以二进制方式读文件
	if (pf == NULL)
	{
    
    
		perror("read file open");
		return;
	}
	while (fread((com->people) + com->position, sizeof(People), 1, pf))//把文件读入我们的结构体中
	{
    
    
		com->position++;
		Expansion(com);
	}
	fclose(pf);
	pf == NULL;
	return;
}

初期化関数の最後に変更関数を置き、構造を初期化するときに、前回保存した情報をロードできます。
初めて実行するとき:
ここに画像の説明を挿入
まだファイルがありません。この操作を保存すると、次回からこの種のプロンプトは表示されません。

2.アドレス帳の保管

void Save(Com* com)
{
    
    
	FILE* pf = fopen("test.txt", "wb");//以二进制方式写文件
	if (pf == NULL)
	{
    
    
		perror("write file open");
		return;
	}
	int i = 0;
	for (i = 0; i < com->position; i++)
	{
    
    
		fwrite((com->people) + i, sizeof(People), 1, pf);//把联系人结构体的内容写道文件中
	}
	fclose(pf);
	pf == NULL;
	return;
}

変更関数を破棄関数の先頭に置き、アドレス帳を破棄する前に変更関数を保存したいと考えています。
![ここに画像の説明を挿入](https://img-blog.csdnimg.cn/46b227c115f549ac8983387edbfd7b3b.png
これはファイルに保存された内容ですが、保存と読み込みにはバイナリを使用しているため、中身を理解することはできません。
これまでのところ、ファイルのバージョンも変換されています。

要約する

最初に実装したアドレス帳の静的バージョン、動的バージョンは静的バージョンに小さな変更を加えるもの、ファイル バージョンは動的バージョンにコンテンツを追加するものです。シーケンステーブルについての理解はさらに深まったと思いますが、アドレス帳には他の機能を追加することもできますが、周囲の知識が不足しているため、それができません。ファイルの追加操作と同様に、ファイルの操作方法が分からなければ動的アドレス帳を変更することはできませんが、学習が進むにつれて、このアドレス帳をオンライン バージョンに変更することもできます。一緒に進歩していきましょう。

おすすめ

転載: blog.csdn.net/2301_76986069/article/details/132039854