網站首頁 編程語言 正文
一、通訊錄需要實現的功能
1,通訊錄可以存儲編號,聯系人的姓名,電話號碼和家庭住址。
2,通訊錄最基本的功能是添加聯系人,用戶可以隨時添加聯系人。
3,通訊錄可以展示已經添加的所有聯系人。
4,通訊錄中用戶可以根據聯系人的姓名刪除對應通訊錄中的信息。
5,通訊錄中姓名可以重復,所以為了刪除準確的信息,需要實現按位置刪除的功能。
6,通訊錄中用戶可以根據聯系人的姓名找到聯系人在通訊錄中的信息,因為聯系人可以重名,所以如果有重名的聯系人的時候就需要返回兩個或兩個以上的聯系人信息到一個查找表單中。
7,通訊錄中用戶可以根據通訊錄的位置修改該位置的聯系人信息,而修改這樣的功能就只需要按照位置來進行修改,不需要再按照姓名進行修改,因為存在重名的情況,并且可以根據返回的表單看到具體的聯系人位置,所以就不需要再 設計像按照名字進行修改這樣冗余的功能了。
8,通訊錄可以按照位置插入聯系人
二、項目目的
制作本項目是為了將所學到的鏈表的知識進行鞏固學習,做到學以致用,并通過做這樣的小項目來增強理解開發,算法和C語言的指針,結構體等知識,同時收獲開發經驗,項目重點是能夠使用鏈表的知識做出的小項目,所以該項目不會考慮到實現數據持久化的操作和GUI編程,只是基于DOS的命令行程序。
三、項目開發
開發IDE:Visual Studio 2019
IDE注意:
1,在該IDE中不能夠直接使用scanf()函數,因為它可能會存在一些不安全的因素,所以在該IDE中使用的是scanf_s()函數,但是scanf和scanf_s函數具有本質的區別,并且scanf_s函數只能在該IDE中使用,不廣泛,所以還是推薦使用scanf()函數,為了能正常使用scanf()函數,需要將聲明#define _CRT_SECURE_NO_WARNINGS放在項目的最頂部。
2,為了更方便的開發該項目,所以使用了一些c++的庫函數,所以為了能夠正確運行程序,建議將后綴改為.cpp,其實c++是c的升級版本,解決了c不能面向對象開發的模式,它的編譯器既可以運行.c的程序也可以運行.cpp的程序,但是.c的編譯器是不能夠運行.cpp的程序的,它并不能識別一些.cpp的源碼。
首先,根據需求,選擇合適的數據結構,這里選擇的數據結構是鏈表為主體,采用帶有頭結點的單鏈表的形式,通過傳入指向頭結點的指針進行添加結點,遍歷結點,刪除結點,插入結點等對結點的操作,這樣每次對鏈表進行操作就只需要傳入指向頭結點的指針就可以了,可以這樣理解:
當程序運行在內存中的時候,首先先使用一個指針指向一個頭結點
將該指針傳入到添加結點的函數中,在該函數中通過指針從頭結點開始遍歷,使用頭插法或尾插法,將生成的結點插入到頭結點之后
然后再次傳入指向頭結點的指針到添加結點的函數,此時該鏈表已經有兩個結點,頭結點和一個結點,內部函數使用指針進行遍歷,然后添加結點,形成頭結點為起點的后面帶有兩個結點的單向鏈表
依次如此....
理解這里最重要的是對于指針的理解
然后我們根據需要在通訊錄中的內容可以存儲編號,聯系人的姓名,電話號碼和家庭住址定義一個結構體,如下:
typedef struct Node {
Number num;//編號
Name name[23];//聯系人姓名
Phone phone[33];//聯系人電話
Addr addr[50];//聯系人地址
struct Node* next;//next指針
}LNode;
在這里需要考慮到的問題是對于編號的實現,編號可以在程序中由程序自主的實現,也就是在程序中可以定義一個全局變量number,并且賦初值為0,當調用添加結點的函數時,number自增并將number的值的賦給結構體成員num,但是它只能夠不斷的自增,當調用刪除功能時,它的序號就會變得混亂,比如,現在已經添加了5個聯系人,編號分別為1,2,3,4,5,如果刪除編號為3的聯系人的話,那么此時通訊錄中的編號只剩下1,2,4,5,這樣就會造成編號無序的情況,就沒有意義也不便于管理操作。
如果編號在添加結點的時候由用戶輸入實現的話,對于用戶來說可能會增加操作的負擔,同時也不便于管理,可能它會無序甚至是重復。
所以最后可以解決的方式就是將編號不作為結構體成員的變量而是作為功能函數體中的一部分,也就是說我們傳入頭結點之后添加結點形成的單鏈表,如果要將信息輸出到屏幕上時,可以定義一個指針指向單鏈表的首元結點并定義一個num為1的計數器,指針遍歷到的個數就是計數器上的數字,這樣如果刪除了某個結點,它都會重新遍歷一次,這樣編號就是有序的,也就是說在打印聯系人名單的函數中每次傳入頭結點都需要重新遍歷一下,編號由函數中的num給出,所以此時重新修改結構體為:
typedef struct Node {
Name name[23];//聯系人姓名
Phone phone[33];//聯系人電話
Addr addr[50];//聯系人地址
struct Node* next;//next指針
}LNode;
然后定義函數printNode(Node* head)用來打印通訊錄的名單,這里可以更好的理解如何解決編號的問題
void printNode(Node* head)
{
Node* move;
move = head->next;
int num = 1;
printf("================================通訊錄頁面=============================\n");
while (move)
{
printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
printf("編號:%d 姓名:%s 電話:%s 住址:%s\n", num, move->name, move->phone, move->addr);
printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
move = move->next;
num++;
}
printf("================================通訊錄頁面=============================\n");
}
接下來,我們來定義完整的鏈表實現:
首先,可以定義一個函數Node* initList()用來初始化鏈表,也就是創建一個頭結點并讓它的指針域指向NULL,定義一個指針函數,該指針函數返回一個指向頭結點的指針。
Node* initList()
{
Node* head;
head = (Node*)malloc(sizeof(Node));
head->next = NULL;
return head;
}
然后,定義一個函數用來添加聯系人也就是創建結點并使用頭插法添加到單向鏈表中,此時我們需要傳入指向頭結點的指針。
void addListNode(Node* head)
{
Node* node;
node = (Node*)malloc(sizeof(Node));
printf("請輸入聯系人信息:\n");
printf("姓名:");
scanf("%s", &node->name);
printf("電話號碼:");
scanf("%s", &node->phone);
printf("家庭地址:");
scanf("%s", &node->addr);
//使用頭插法將結點鏈接到頭結點之后
node->next = head->next;
head->next = node;
if (head->next != NULL) {
printf("添加聯系人成功\n");
}
else {
printf("添加聯系人失敗\n");
}
}
此時,可以在主方法中進行測試,此時已經能夠打印出三個聯系人了
int main()
{
Node* head;
head=initList();
addListNode(head);
addListNode(head);
printNode(head);
return 0;
}
我們已經實現了通訊錄的存儲要求,添加聯系人和打印出通訊錄的聯系人信息了。
此時我們會考慮到如果該通訊錄為空的話,我們直接進行打印可能會造成nullptr,所以需要實現一個判斷表空的函數,該函數返回bool值便于調用判斷布爾類型。
bool isempty(Node* head)
{
if (head->next == NULL) {
return false;
}
else {
return true;
}
}
此時在打印聯系人目錄的函數中使用該函數在函數的最開始進行判斷,并在main中進行測試
if (!isempty(head)) {
printf("檢測到通訊錄為空,請先添加聯系人再進行操作\n");
return;
}
接下來需要實現的是通訊錄的其他功能。
首先實現用戶可以根據聯系人的姓名刪除對應通訊錄中的信息,根據聯系人的姓名刪除對應通訊錄中的信息,我們可以想到,聯系人的姓名是可以重復的,所以在刪除時需要判斷是否有重名的情況,所以可以先實現第6個需求,也就是通訊錄中用戶可以根據聯系人的姓名找到聯系人在通訊錄中的信息,因為聯系人可以重名,所以如果有重名的聯系人的時候就需要返回兩個或兩個以上的聯系人信息到一個查找表單中。
首先我們知道該功能是通過用戶輸入聯系人的姓名也就是字符串然后在鏈表中找到相應字符串的位置進行返回,那么我們就需要實現一個字符串匹配的函數,它應當返回一個bool類型的值,當指針在鏈表中不斷遍歷并取出聯系人的姓名進行比較直到找到這個聯系人為止也就是該函數返回false的時候循環結束,所以可以定義一個字符匹配函數bool isBatch(Name n1[], Name n2[]);它的具體實現如下:
bool isBatch(Name n1[], Name n2[])
{
Name* n11, * n22;//定義兩個char類型的指針
n11 = n1;//讓n11指向字符串n1的首地址
n22 = n2;//讓n22指向字符串n2的首地址
int num1 = 1;//定義長度器用來計量n1的長度
int num2 = 1;//定義長度器用來計量n2的長度
//定義兩個長度器的原因是如果是n1:NUM,n2:NUM2的話,沒有計數器的情況下當跳出循環后它依然是返回true的
while (1)
{
if (*n11 == *n22)
{
n11++;
n22++;
num1++;
num2++;
if (*n11 == '\0' && *n22 == '\0') {
break;
}
}
else {
return false;
}
}
if (num1 != num2) {
return false;
}
return true;
}
然后我們需要返回聯系人在鏈表中的位置通過再打印查找單的函數中遍歷打印出信息,聯系人可能重名,所以位置可能會返回多個,一個或者是無聯系人的情況,因此這個返回位置的函數可以想到使用返回一個int數組的方式來操作,在C語言中如果想要返回數組的話需要返回的是指向這個數組的指針,也就是說C語言不能夠直接就返回一個數組類型的數據,所以可以定義一個函數int* lookupByname(Node* head, Name name[]);傳入頭結點和輸入的名字,它的具體實現如下:
int* lookupByname(Node* head, Name name[])
{
Node* target;//定義一個目標指針,該目標指針是用來獲取每一個結點中的name并與輸入的name進行比較
target = head->next;//讓其指向第一個結點
int* summary = NULL;//定義一個指向返回數組的指針
summary = (int*)calloc(NUM, sizeof(int));//該指針指向一片大小為NUM的int類型的數組,該數組中所有值為0
int loc = 1;//獲取位置從1開始,放在所申請的數組中
int numd = 0;//數組的下標
while (target)//遍歷完整個鏈表
{
if (isBatch(target->name, name))//如果匹配
{
summary[numd] = loc;//位置放入數組
numd++;//只要放入numd就會加1,所以只要有一個聯系人numd是1不是0
}
target = target->next;
loc++;
}
if (numd == 0) {//如果最后遍歷完整個鏈表numd還是0就說明沒有這個人
loc = -1;//讓位置為-1
summary[0] = loc;
}
return summary;//返回指向整型數組的指針
}
最后,我們來實現遍歷打印出查找單的函數void printsMenu(Node* head, int* loc);傳入鏈表的頭結點和指向整型數組的指針,它的具體實現如下:
void printsMenu(Node* head, int* loc) {
if (!isempty(head)) {
printf("檢測到通訊錄為空,請先添加聯系人再進行操作\n");
return;
}
Node* ptr;//定義一個指針用來獲取位置并打印信息
ptr = head->next;
int num = 1;//定義位置尋找器用來找到指定的位置
printf("================================查找人匯總=============================\n");
for (int i = 0; i < NUM; i++)//遍歷返回的數組,數組最大值為NUM其實不必要遍歷到NUM,找到0的時候就可以跳出循環
{
if (loc[i] == -1) {//如果位置返回的是-1的話就表明無此人
printf(" 未查詢到此人\n");
break;
}
while (num!=loc[i]) {//指針指到相應位置的結點
num++;
ptr = ptr->next;
}
//打印信息
printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
printf("編號:%d 姓名:%s 電話:%s 住址:%s\n", num, ptr->name, ptr->phone, ptr->addr);
printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
if (ptr->next == NULL) {//如果指針指到結尾就結束方法
return;
}
}
return;
}
此時我們可以來測試一下這個功能,在main方法中:
int main()
{
Node* head;
Name name[23]="李白";
//添加兩個李白,一個李白,沒有李白和一個叫李白一個叫李白1進行測試
head=initList();
addListNode(head);
addListNode(head);
addListNode(head);
addListNode(head);
int* loc;
loc=lookupByname(head,name);
printsMenu(head, loc)
return 0;
}
接下來,既然現在已經能夠按照姓名查到位置并輸出了,那么實現用戶可以根據聯系人的姓名刪除對應通訊錄中的信息,這里可以考慮到姓名重名的話我們可以再設計一個按照位置刪除聯系人的函數,這樣在按姓名刪除的函數中需要通過返回的數組的大小提醒用戶是否有重名的聯系人,并提供建議如果有則建議結束函數然后采用按位置刪除聯系人的方式解決,沒有就可以直接刪除還有就是沒有此聯系人的情況,所以定義函數void deleteByname(Node* head, Name name[]),依然傳入頭結點和要刪除的名字
void deleteByname(Node* head, Name name[])
{
if (!isempty(head)) {
printf("檢測到通訊錄為空,請先添加聯系人再進行操作\n");
return;
}
Node* p, * q;//定義兩個指針,p指針指向的是頭結點,q指針指向的是首元結點
p = head;
q = head->next;
//當q查詢到名字時就將q所指結點進行釋放,然后將p所指結點也就是q所指結點的前一個結點連接到q所指結點的后一個結點
while (q)
{
if (q->next == NULL && !isBatch(q->name, name)) {//當遍歷完整個鏈表并且最后一個結點的名字與輸入的名字不符合的情況下結束遍歷
printf("刪除失敗,未找到該聯系人\n");
return;
}
if (isBatch(q->name, name))//如果遍歷的過程中找到與輸入的名字相對于的名字時
{
int* ptr = lookupByname(head, name);//調用查找名字位置的函數用于查找該名稱是否有重名的情況
int li=1;//定義計數器記錄有無重名的情況
for (int i = 0; i < NUM; i++) {
if (ptr[i] == 0) {
break;
}
li++;
}
if (li-1 == 1) {//計數器為1,也就是說只有一個名字無重名
printf("沒有重名的聯系人\n");
}
else {
printf("有%d個重名的聯系人,建議查詢后使用位置刪除\n",li-1);
printf("按1選擇繼續刪除,按2選擇結束本次刪除:");
int input;
scanf("%d", &input);
if (input == 1) {
goto loop;
}
else {
return;
}
}
break;
}
p = p->next;
q = q->next;
}
loop:
//刪除結點的過程
p->next = q->next;
q->next = NULL;
free(q);
printf("刪除聯系人成功\n");
}
然后在main方法寫測試的用例:
int main()
{
Node* head;
Name deletename[23]="李白";
//添加兩個李白,一個李白,沒有李白和一個叫李白一個叫李白1進行測試
head=initList();
addListNode(head);
addListNode(head);
addListNode(head);
addListNode(head);
deleteByName(head,deletename);
return 0;
}
添加兩個李白的情況:
添加一個李白的情況:
沒有李白的情況:
接下來,既然能夠按照聯系人的名稱進行刪除了,那么在重名的情況下,還需要一種刪除的方法,也就是按照位置的刪除方法,所以可以定義函數void deleteByLoc(Node* head, int loc),按照位置刪除聯系人,它的具體實現如下:
void deleteByLoc(Node* head, int loc)
{
if (!isempty(head)) {
printf("檢測到通訊錄為空,請先添加聯系人再進行操作\n");
return;
}
Node* move,*q,*choic;
choic = head->next;//choic指向首元結點,用來判斷鏈表中結點的數量,避免程序出現nullptr
//刪除操作的兩個指針
q = head;
move = head->next;
int num = 1;//位置計數器,一直自增到對應的位置loc
int t = 1;//鏈表數量器,用來遍歷獲取鏈表的結點數量
while (choic) {//獲取鏈表的實際長度
if (choic->next == NULL) {
break;
}
choic = choic->next;
t++;
}
while (num!=loc&&move) {//尋找到要刪除的位置
q = q->next;
move = move->next;
num++;
}
if (num >= t) {//如果要刪除的位置比鏈表的長度都長就說明刪除錯誤
printf("查詢錯誤,已經超出已有人數上限,會造成程序異常\n");
return;
}
else {
q->next = move->next;
move->next = NULL;
free(move);
printf("刪除成功\n");
}
}
再次使用main方法進行測試:
int main()
{
Node* head;
int loc=1;
head=initList();
addListNode(head);
deleteByLoc(head,1);
return 0;
}
到目前位置,整個通訊錄的功能已經完成了,接下來完成修改聯系人的信息和插入新的聯系人的功能,先完成修改聯系人信息的功能,既然可以查找到重名的聯系人,所以此時需要按照位置修改聯系人信息,所以定義函數void modifyByName(Node* head, int loc),闖入頭結點和需要修改的位置,在修改的時候,我們希望可以展示修改人原先的信息,有些算法的思想和按位刪除聯系人的思想一致,具體實現如下:
void modifyByName(Node* head, int loc)
{
if (!isempty(head)) {
printf("檢測到通訊錄為空,請先添加聯系人再進行操作\n");
return;
}
Node* move, * choic;
choic = head->next;
move = head->next;
int num = 1;
int t = 1;
while (choic) {
choic = choic->next;
t++;
if (choic->next == NULL) {
break;
}
}
while (num != loc && move) {
move = move->next;
num++;
}
if (num > t) {
printf("位置錯誤,已經超出已有人數上限,會造成程序異常\n");
return;
}
else {
printf("找到聯系人信息\n");
printf("正在檢測聯系人信息...........\n");
printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
printf("編號:%d 姓名:%s 電話:%s 住址:%s\n", num, move->name, move->phone, move->addr);
printf("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
printf("按1鍵更改聯系人信息,按2鍵不更改聯系人信息并結束:");
int inputs;
scanf("%d", &inputs);
if (inputs==1)
{
int istrue;
do {
printf("請輸入聯系人信息:\n");
printf("姓名:");
scanf("%s", &move->name);
printf("電話號碼:");
scanf("%s", &move->phone);
printf("家庭地址:");
scanf("%s", &move->addr);
printf("按1鍵確認更改,按2鍵重新更改:");
scanf("%d", &istrue);
} while (istrue!=1);
printf("修改聯系人信息成功\n");
}
else {
printf("ERROR");
return;
}
}
}
同樣在main函數中進行測試
最后完成最后一個功能,按位置插入聯系人,這個函數的意義不大,但是為了鞏固鏈表的插入而設計的,定義一個函數void insertNodeByLoc(Node* head, int loc)算法的實現思路與按位刪除的一致,只是找到后是將新的結點插入而已,具體實現如下:
void insertNodeByLoc(Node* head, int loc)
{
if (!isempty(head)) {
printf("檢測到通訊錄為空,請先添加聯系人再進行操作\n");
return;
}
Node* p, * q,*m;
m = head->next;
p = head->next;
q = head;
int num=1;
int i=1;
while (m)
{
i++;
if (m->next == NULL) {
break;
}
}
while (num!=loc&&p) {
p = p->next;
q = q->next;
num++;
}
if (num >= i) {
printf("插入位置錯誤,已經超出已有數量上限,會造成程序異常\n");
return;
}
else {
Node* node;
node = (Node*)malloc(sizeof(Node));
printf("請輸入聯系人信息:\n");
printf("姓名:");
scanf("%s", &node->name);
printf("電話號碼:");
scanf("%s", &node->phone);
printf("家庭地址:");
scanf("%s", &node->addr);
node->next = p->next;
p->next = node;
printf("插入成功\n");
}
}
同樣進行測試:
原文鏈接:https://blog.csdn.net/EMCred/article/details/127517004
相關推薦
- 2022-08-22 C++回溯與分支限界算法分別解決背包問題詳解_C 語言
- 2022-09-24 Go?GORM?事務詳細介紹_Golang
- 2022-07-23 asp.net6?blazor?文件上傳功能_實用技巧
- 2022-12-27 Python中的類的定義和對象的創建方法_python
- 2022-11-26 詳解Python如何實現惰性導入-lazy?import_python
- 2022-10-16 Android面向切面基于AOP實現登錄攔截的場景示例_Android
- 2022-08-04 淺析.net?core?拋異常對性能影響_實用技巧
- 2022-11-20 WPF實現自帶觸控鍵盤的文本框_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支