天天看點

[C]項目--通訊錄【功能子產品】【函數主體】【功能測試】

通訊錄

  • 【功能子產品】
    • 1. 新增聯系人
    • 2. 删除聯系人
    • 3. 修改聯系人
    • 4. 查詢聯系人
    • 5. 顯示所有聯系人
    • 6. 清空所有聯系人
  • 【函數主體】
  • 【功能測試】
  • 楔子:

    我們日常使用的手機,無論大小,應該都具備通訊錄這個實用的功能。仔細想想其實它的實作原理其實也很簡單,那麼本篇部落格就按照邏輯來實作一下一個簡單的通訊錄系統。

  • 通訊錄可以抽象成為一下幾個子產品:
  1. 管理很多聯系人
  2. 每個聯系人的資訊:姓名 + 電話 (為主)
  3. 增删改查的功能
  4. 和使用者通過控制台進行互動

以下為程式主體:

【功能子產品】

1. 新增聯系人

void Add_Contact(telephone_directory* telephone_directory) {
	assert(telephone_directory != NULL);
	if (telephone_directory->size >= CONTACT_MAX_SIZE) {
		printf("目前通訊錄已滿");
		return;
	}
	printf("\n\n******開始新增聯系人:******\n");
	//每次都把這個新的聯系人放到有效數組的最後一個元素上
	Contact* p = &telephone_directory->person[telephone_directory->size];		//取出結構體指針友善下面幾行的代碼可讀性與簡潔性
	//這裡如果取結構體變量,不使用結構體指針:Contact p = telephone_directory->person[telephone_directory->size];
	//這個結構體變量相當于數組中對應元素的一份拷貝,修改結構體變量時隻會對副本修改,不會影響到原來的數組
	
	printf("請輸入新增聯系人的姓名:");
	scanf("%s", p->name);
	printf("\n請輸入新增聯系人的電話号碼:");
	scanf("%s", p->Cell_phone);
	
	//新增完成後,需要更新 size
	++telephone_directory->size;
	
	printf("******插入聯系人成功!******\n\n");
	printf("\n******目前通訊錄中共有%d條資料******\n\n", telephone_directory->size);
}
           

2. 删除聯系人

void Delete_Contact(telephone_directory* telephone_directory) {
	//1. 将删除對象之後的所有元素都往前挪一位,然後将最後一位設為無效位,即--size;
	//這樣的方法局限性太強,如果是一個大型公司的通訊錄,之後的元素如果也是數以億計,那麼這樣多次搬運方法就顯得很笨拙
	//也可以使用連結清單,這裡主要講解下面這種方法

	//2. 将最後一個元素覆寫掉删除對象元素,之後--size;
	//這種方法是順序表的思路,因為順序表是有順序的,不可以打斷其中元素的順序關系。此時通訊錄的情況,就不要求實實在在的順序,對該方法的一個拓展可以很好地解決現在等問題
	
	assert(telephone_directory != NULL);
	//如果通過姓名删除,操作很危險,因為存在重名風險,可能删除多個聯系人
	//如果通過電話号碼删除,雖然是唯一的,但是号碼太長難以記憶,不太科學
	//是以此時選擇 通過通訊錄序号 進行删除.
	
	printf("請輸入想要删除的聯系人序号:");
	int id = 0;
	scanf("%d", &id);
	if (id < 0 || id >= telephone_directory->size) {
		printf("您輸入的序号有誤!删除失敗!\n");
		return;
	}
	Contact* p = &telephone_directory->person[id];
	printf("确認删除[%d]\t%s\t%s?(使用 Y 确認)", id, p->name, p->Cell_phone);
	char choice[1024];		//	使用者輸入的内容
	scanf("%s", &choice);
	if (strcmp(choice, "Y") != 0) {
		printf("删除操作取消!\n");
		return;
	}
	
	//開始删除邏輯
	//1. 取出最後一個數組元素的結構體指針
	Contact* from = &telephone_directory->person[telephone_directory->size - 1];
	//2. 取出被删除元素結構體指針
	Contact* to = p;
	//3. 替換被删除元素
	//【結構體之間允許同類型結構體的直接指派】【相當于from結構體中的内容拷貝到to結構體中】
	*to = *from;
	--telephone_directory->size;
	printf("******删除操作完成!******\n");
}
           

3. 修改聯系人

void Modify_Contact(telephone_directory* telephone_directory) {
	assert(telephone_directory != NULL);
	printf("******開始修改聯系人******\n\n");
	printf("請輸入需要修改的聯系人序号:");
	int id = 0;
	scanf("%d", &id);
	if (id < 0 || id > telephone_directory->size) {
		printf("您輸入的序号有誤!修改失敗!\n");
		return;
	}
	Contact* p = &telephone_directory->person[id];
	char input[1024];
	printf("請輸入要修改的姓名;(不需要修改此項請輸入 !)");
	scanf("%s", input);
	if (strcmp(input, "!") != 0) {	
		strcpy(p->name, input);
	}
	printf("請輸入要修改的電話号碼;(不需要修改此項請輸入 !)");
	scanf("%s", input);
	if (strcmp(input, "!") != 0) {	//如果使用者沒有選擇!,就是選擇修改内容,把使用者輸入的内容寫入結構體中。
		//如果使用者輸入了!,表示不修改内容,那麼就不需要把輸入替換原來的内容了,if條件中的語句就不執行了
		strcpy(p->Cell_phone, input);
	}
	printf("******修改成功!******\n");
}
           

4. 查詢聯系人

void Seek_Contact(telephone_directory* telephone_directory) {
	assert(telephone_directory != NULL);
	printf("******開始進行查找******\n\n");		
	printf("請輸入要查找的姓名:");		//一般都是按照姓名進行查找,是以此處設定為姓名索引
	char name[1024] = { 0 };
	scanf("%s", name);
	int count = 0;
	for(int i = 0;i < telephone_directory->size;++i){
		Contact* p = &telephone_directory->person[i];
		if (strcmp(p->name, name) == 0) {	//表示找到了
			printf("[%d]\t%s\t%s\n", i, p->name, p->Cell_phone);
			//姓名是有可能重複的 是以此時不可以加上break;,展示所有内容
			++count;
		}
	}
	printf("\n\n******查找完畢!******\n");
	printf("******共顯示了 %d 條資料!******\n",count);

}
           

5. 顯示所有聯系人

void Print_all_Contact(telephone_directory* telephone_directory) {
	assert(telephone_directory != NULL);
	for (int i = 0; i < telephone_directory->size; ++i) {
		Contact* p = &telephone_directory->person[i];
		//這時如果不設定為指針,其實是可以的,因為此時不涉及更改元素内容,而是簡單的讀取檢視,無論是副本還是原始資料都是一個值
		//但是這個過程涉及對實參進行拷貝,如果實參結構體大小很小影響不大,如果結構體占存許多個G,那麼不使用指針的這個行為開銷就太大了,還是推薦使用指針
		printf("\n\n");
		printf("序号\t姓名\t聯系方式\n");
		printf("[%d]\t%s\t%s\n", i, p->name, p->Cell_phone);
	}
	printf("\n******以上共顯示了%d條資料******\n\n", telephone_directory->size);
}
           

6. 清空所有聯系人

void Clear_all_Contact(telephone_directory* telephone_directory) {
	assert(telephone_directory != NULL);
	printf("确認清空所有聯系人:(輸入 Y 表示确認)");	//防止使用者誤觸點選清空
	char input[1024];
	scanf("%s", input);
	if (strcmp(input, "Y") != 0) {
		printf("\n******清空操作取消!******\n");
		return;
	}
	telephone_directory->size = 0;
	printf("\n******清空操作完成!******\n");
}
           

【函數主體】

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#define CONTACT_MAX_SIZE 500  //最大聯系人的宏定義,可以随時在這裡修改本參數在程式中的值


//建立聯系人結構體
typedef struct Contact {
	char name[1024];
	char Cell_phone[1024];
}Contact;


//聯系人結構體數組
typedef struct telephone_directory {
	Contact person[CONTACT_MAX_SIZE];
	int size;	//描述前size個元素是有效的,[0 ,size)範圍是數組的有效範圍區間
}telephone_directory;


telephone_directory g_telephone_directory;	//聲明一個全局變量作為設計的通訊錄的實體


//清空函數
void Init(telephone_directory* telephone_directory) {	//這裡通過指針傳參,如果不使用指針,函數的形參就不會影響到實參
	assert(telephone_directory != NULL);//設定斷言來校驗傳進來的指針的有效性
	telephone_directory->size = 0;
	//這個結構體數組是通過 size 控制數組元素的有效個數
	//當把 size 設為 0 ,證明無論數組元素内容是什麼都是無效的,等同于清空。
}


//菜單函數
int menu() {
	printf("=================\n");
	printf("|  < 選項清單 > |\n");
	printf("|		|\n");
	printf("|  1. 增加	|\n");
	printf("|  2. 删除	|\n");
	printf("|  3. 修改	|\n");
	printf("|  4. 查詢	|\n");
	printf("|  5. 顯示全部	|\n");
	printf("|  6. 清空全部	|\n");
	printf("|  0. 退出	|\n");
	printf("|		|\n");
	printf("=================\n");
	printf("\n請輸入您的選項:");
	int choice = 0;
	scanf("%d", &choice);
	return choice;
}


int main() {
	//1. 對通訊錄進行初始化
	Init(&g_telephone_directory);
	//開始設定表驅動
	typedef void(*pfunc_t)(telephone_directory*);	//設定函數指針數組
	pfunc_t table[] = {
		Add_Contact,
		Delete_Contact,
		Modify_Contact,
		Seek_Contact,
		Print_all_Contact,
		Clear_all_Contact
	};
	while (1) {
		int choice = menu();
		if (choice < 0 || choice >(int)(sizeof(table) / sizeof(table[0]))) {	//	這裡不直接設定一個數字,而選擇麻煩的去設
			//sizeof 的傳回值是無符号整數,而choice變量我們設定的是有符号的整數,我們選擇使無符号類型強制類型轉換為有符号的類型
			//筆者建議盡量少使用無符号整數,因為unsigned的使用有很多注意點,比如兩個無符号整型作差,被減數如果小于減數就會變成一個非常大的數超乎平常預期等等,感興趣的讀者可以自行了解
			printf("您的輸入有誤! 請重新輸入:\n");
			continue;
		}
		if (choice == 0) {
			printf("\n感謝您的使用,再見~\n");
			break;
		}
		table[choice - 1](&g_telephone_directory);	//使用者輸入的值和函數指針數組中對應的選項相差 1 ,是以這裡選擇将使用者輸入的數字 -1 ,對應到數組中正确的值

		//if (choice == 1) {
		//	AddContact(&g_telephone_directory);
		//}
		//else if (choice == 2) {
		//	DelContact(&g_telephone_directory);
		//}
		//else if (choice == 3) {
		//	ModifyContact(&g_telephone_directory);
		//}
		 ...
		//如果這樣設計,“圈複雜度”過高,通過使用函數指針數組實作的“表驅動”解決
	}
	system("pause");
	return 0;
}
           

【功能測試】

  1. 運作程式,列印互動菜單:
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  2. 新增功能
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  3. 删除功能

    (1) 聯系人序号輸入錯誤

    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (2) 确認密鑰輸入錯誤
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (3) 正确删除
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  4. 修改功能

    (1) 聯系人序号輸入錯誤

    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (2) 姓名預設,修改電話号碼
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    【通訊錄中内容發生變化】:
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (3) 電話号碼預設,修改姓名
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    【通訊錄中内容發生變化】:
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  5. 查詢功能

    (1) 輸入通訊錄不存在的聯系人

    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (2) 正确查找聯系人
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (3) 添加多條聯系人資料,同時設定重名聯系人,再次查證
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    【查詢結果】:
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  6. 顯示所有聯系人功能
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  7. 清空所有聯系人功能

    (1) 确認密鑰輸入錯誤

    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    (2) 正确清空
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
    【清空結果】
    [C]項目--通訊錄【功能子產品】【函數主體】【功能測試】
  • 小結:
  1. 每一個項目的完成都是多個子產品功能的加和,是以設計階段一定要把要實作的功能構思好,再化整為零,逐個攻破。
  2. 每個函數都要進行參數有效性檢驗,這是確定程式執行高效性的一個名額,及時發現傳參錯誤的問題。
  3. 程式參數一定要設定的足夠大,否則資料溢出或者數組越界就是非常棘手的問題了,防患于未然,注意要提前将參數留有餘地。
  4. 結構體成員通路的書寫有點複雜,是以一開始就選擇定義一個結構體指針,之後就可以簡化寫法,用指針來代表某段結構體成員通路。使用指針調用時就可以減少不必要的開銷,避免将整個結構體在函數中以形參的形式再拷貝一份。
  5. 對于重大的危險操作設定确認密鑰,以防使用者誤觸點選按鍵造成萬劫不複的行為,通過加上确認密鑰這種二重保證就可以防止這種事件的發生。

繼續閱讀