目录
一、运行效果
二、编写主页
main.c文件
验证
三、功能编写
1、目录结构
2、功能实现
operation.h
init.c
create.c
delete_name.c
show_all.c
search_name.c
free_all.c
operation.h
main.c
四、cmake编写
1、编写子目录的cmake文件
2、顶层目录CMakeLists.txt
五、编译运行
实现的源代码也上传到csdn,需要可以自行下载!
输入密码之后按回车进入菜单
在这就可以选择对应的功能,下面创建个手机号信息,输入1按回车即可
可以看到输入公司号码之后按回车就提示成功
可以看到这里创建了两个用户信息,下面试试修改
想修改名字为2的手机号信息,按下回车
这提示修改那些信息,下面修改name把,把2改成3
可以看到修改成功了,按下回车就回到主菜单了,下面测试查找,查一下刚刚修改为3的信息
可以看到确实是修改成3了,下面测试删除3
可以看到3被删除了,下面看一下全部信息
只剩下信息1了,退出软件就会销毁全部数据,测试完了就退出
创建目录为phone,改目录下创建文件menu.h、menu.c、main.c
定义需要用到的头文件,第8行的函数定义就是menu.c文件要实现的函数
实现主页登录函数 login_menu()
1行,引用头文件, 3行,设置登录密码
9-15行,\033[0;96m
可以用于在终端输出时将后面的文本颜色设置为青色
16-30行,当log页面打印完之后进入while循环,接着就提示输入密码,接收密码之后就用stcmp函数进行比较,如果比较等于0说明和密码匹配,就退出循环,返回0,如果比较不等与0,就提示错误,并且还剩下2次机会,当机会全部用完之后直接退出循环返回-1
"\033" 表示转义字符,,"0" 表示重置所有属性。\033[0;33m" 表示将颜色设置为 33m(黄色) 且不设置任何样式,"+" 则是输出的文本内容。因此,"\033[0;33m+" 将 "+" 文本以黄色输出到终端上,"\033[0m" 恢复终端的默认颜色和样式
引用头文件;进入main函数之后,对menu.c的主页登录函数的返回值进行判断,如果返回值为0,说明登录成功;如果是-1,说明用完登录机会,并退出程序运行
主页实现了,下面正式开始编写
在phone目录下,每个函数功能都分开为一个目录,除了方便维护,还方便移植
上面创建的三个文件按下面放在对应的目录下即可
create.c用来创建新的电话信
free_all.c用来清除全面存储的信息,退出该软件的时候使用
init.c用来初始化链表
main.c主函数
management.c实现选择增删改查操作
menu.c实现主页菜单和登录页面
search_name.c通过名字查找其信息和修改信息
show_all展示通讯录全部内容
menu.h就是menu.c(登录页面和主页菜单函数)的头文件
operation.h就是其余全部操作的头文件
在这头文件里面定义一个phone_struct结构体,别名为PHONE
即PHONE等价struct phone_struct
姓名、手机号码、家庭地址和公司号码都是20个字符空间,如果需要更大可以修改SIZE值
把SIZE作为宏定义就方便,修改宏,四个空间就全部修改了
在上面已经实现了登录主页,在该文件后面添加登录后的菜单
使用 do-while 循环会比 while 循环更好的原因是:在这段代码中,我们需要先输出一次菜单,然后等待用户输入选项。如果用户输入了无效的选项,则需要清空屏幕并重新输出菜单,然后继续等待用户输入选项。只有当用户输入了有效选项时,才会退出菜单循环。也就是说,无论用户输入的选项是什么,程序都会先输出一次菜单,并等待用户输入选项。而使用 while 循环则无法保证循环体内的语句至少被执行一次,如果用户一开始就输入了无效选项,则程序会直接退出循环而无法输出菜单。
如果输入的选择小于0或者大于4,说明不在软件的服务范围内,那么就进入if控制块
fflush(stdin) 是一种常用的输入缓冲清空方法。它的作用是清空标准输入缓冲区,以便能够接收新的输入。缓冲区是程序为 I/O 操作预先分配的内存空间,用于存储待输入或输出的数据。当使用 scanf() 等函数从标准输入 (stdin) 中读取数据时,这些数据会被存放在输入缓冲区中,直到被程序读取。但有时候可能会出现输入缓冲区没有被完全清空而导致的问题,此时可以使用 fflush(stdin) 来清空输入缓冲区
system("clear") 是一个常用的清屏操作方法,它可以清空终端屏幕并将光标移动到左上角。
getchar() 是一个函数,用于从标准输入 (stdin) 中读取一个字符,在执行 getchar() 函数时,程序会等待用户输入一个字符,然后将该字符从输入缓冲区中读取并返回其 ASCII 码值。如果输入缓冲区中没有可读取的字符,则 getchar() 函数会阻塞程序继续执行,直到有字符被输入为止
continue 是一个关键字,用于跳过当前循环中剩余的语句并开始下一次循环。它通常用于循环体内部,具体来说,当程序执行到 continue 语句时,它会立即跳过当前循环中之后的语句,并回到循环开始处进行下一轮循环,因为输入的不是服务范围,所以提示输入错误之后就重新给用户输入。
需要在头文件里面添加写好的函数,方便引用该头文件的文件调用
在带头结点的单链表中,头结点是第一个节点之前增加的一个节点。头结点不存储任何数据,仅用来辅助链表的操作 ,next就是保存连接下一个节点的地址,如果是最后一个节点,那么next一定要设置为NULL,表示空,没有下一个节点了。可以看出,下一个地址都保存在前一个节点中,依此类推,如果头节点没了,那么就无法找到链表了,所以操作的时候需要留意头节点。
init代码如下
实现初始化功能
函数首先通过 malloc 函数为新节点分配内存空间,如果分配失败则返回 -1,表示初始化失败。接着,将新节点的 next 指针设置为 NULL,即新节点为链表的尾节点,没有后继节点。最后,将头指针指向新节点,使其成为链表的头结点。该函数的返回值是一个整型数,如果为 0 表示初始化成功,否则表示初始化失败
在初始化的时候,一般会传进一个定义好的结构体指针,使用该函数初始化,意思就是创建一个链表头,并且链表头中不存数据,使其符合单链表的定义。init函数里用二级指针接收参数,创建新的结构体指针new并开辟内存,让这个指针的netx成员指向null,说明这个创建了链表头,没有后继节点,*head指向传递进来的指针的地址,现在使用*head指向new的地址,从而使传递进来的指针也指向new的地址,初始化就完成
从上图看,二级指针head指向传进来的结构体指针,执行*head=new之后,地址0x123456就会被改成new的地址,从而使结构体指针指向new
这样结构体指针就能访问到创建出来的链表头,初始化成功
实现添加信息的功能
新建一个指针p指向把传进来的head,此时*head和*p是等价的,即指向同一个头节点;创建一个指针new_next并开辟空间,加以判断是否开辟空间失败;接着11-20行就是与用户交互的过程,引导用户输入对应信息,把信息都保存在new_next结构体中,然后使new_nex的next成员为NULL;最后通过while循环来找到链表的最后一个节点,并让该节点指向new_nex,也就是把new_nex连接到了链表的尾巴上(尾插法)。
用指针p操作或者用指针head操作都可以,因为它们等价,但是一定要有一个指向头节点,否则就无法找到链表起点
删除一个手机号信息,也就是删除一个节点,因为每一个节点存了一个手机号相关信息
定义p和q指针,p指向头节点,q就是头节点的下一个节点,一个长度为20(SIZE在头文件中定义)的字符数组name,打印提示引导用户输入名字,然后先循环判断q是不是为空,不为空说明有节点,进入用strcmp函数来对比这个节点存的名字和用户输入的名字,如果这两个名字对比结果为0,也就说明这两个名字一模一样,相符的节点进入第一个if语句控制块,把前一个节点链接相符节点断掉,链接到后一个结点,这样相符的节点就孤立无援了,就可以被干掉了(free),最后q继续移动到p的下一个节点;如果对比不相符就进入else控制语句,把q移动下一个节点,再把q移动到下一个节点,继续循环判断。 如果count为0,说明没有进入前一个if语句中,也就是符合删查完了链表都没有除条件的,提示并退出该功能,如果count>0,说明有删除操作,就打印删除成功
上图是把p已经移动到第一个节点了(创建时是最左边的p),可以看到,如果p->next符合条件,就会创建一个q来指向它,并把q的前后断掉,让它孤立无援,就把它"干掉"(free);如果是最后一个节点n需要删除,那么就直接"干掉"(free),n没了之后,就得让前一个节点n-1“上位”,让它指向null,这样它就成了最后一个节点
打印信息
这里定义了两个函数,首先看show_one,很明显就是打印一个节点的信息
第二个函数show_all,在循环体中,首先会调用show_one来打印当前节点的信息,然后将 p
更新为指向下一个节点的地址,即 p = p->next;
。这样就可以继续遍历下一个节点了。在循环结束时,也就是 p
指向 NULL 时,整个链表的遍历也就完成了。
第一个search_name函数功能为通过名字查找信息
通过名字来查找对应信息,和上面的删除功能几乎一样,也是用strcmp来比较,接着就调用show_one函数打印出其信息,如果没有就打印错误
第二个search_name函数功能为通过名字查找,修改其信息
先引导用户输入需要修改信息的name,用输入的name去比较,比较符合就先把屏幕清空然后输入选择功能列表,再引导用于输入需要的功能,根据输入的num去执行对应的switch语句的操作,语句都差不多,先提示,再接收新的数据,然后使value++,方便后续判断有无修改操作,接着调用打印功能函数把修改后的信息打印出来,循环往复
退出的时候把全部数据清空。定义一个指针函数,返回值类型是 PHONE*
,使用 while (head->next != NULL)
来判断是否还有剩余节点,如果 head->next
不为 NULL,则说明还存在节点待释放。进入循环控制语句中,使p与head等价,head就指向下一个节点,这个时候就是p为头节点,head为头节点的下一个节点,删除p之后,head就成为了新的头节点,循环往复,直到最后一个头节点退出循环单独free,为了防止指针所指向的内存区域已经被释放掉了,但是指针本身并没有被清空,仍然指向该内存区域的位置(悬挂指针),最好方法是在释放内存时及时将指针设置为NULL,也就是head = null,最后返回PHONE*
类型head
把功能函数都定义在改头文件里面
增加主菜单操作
在management函数里面,对用户输入菜单选行之后执行对应的功能,调用相关的函数; 在上面实现主页的时候就有main函数里面的部分功能,主要是添加定义phone_hand指针,调用init函数和management函数,在management函数里面就会根据用户需要的功能去调用函数
代码写完了,下面写cmake,用来编译多文件
除了main文件目录,在每一个文件目录下面都创建一个CMakeLists.txt,文件内容如menu.c
aux_source_directory()
将指定目录下的所有源文件添加到一个变量中
(. DIR_LIB_SRCS):
点表示源代码是当前目录,并将该目录下所有的源文件名字存入 DIR_LIB_SRCS
变量中
add_library()
命令的作用在 CMake 项目中添加一个库,这个命令会产生一个输出目标,可以是静态库(.a
文件)或共享(动态)库(.so
文件),默认生成静态库,自动添加.a后缀
(menu ${DIR_LIB_SRCS}):menu输出的文件名为menu,$用来引用变量的值DIR_LIB_SRCS,这个就是第一句命令中的当前目录下所有源文件
其他目录下的CMakeLists.txt全部一样,只需要修改add_library()的输出文件名即可
在顶层目录下创建一个CMakeLists.txt,内容如下
cmake_minimum_required(VERSION 3.5)
声明了此项目所需的 CMake 版本号,保障在不同的cmake版本间的兼容性,一般写3.5
project(phone)
声明了项目名称为 phone
include_directories(${PROJECT_SOURCE_DIR}/include)
添加了一个头文件目录,会将指定的目录路径添加到头文件搜索路径中。PROJECT_SOURCE_DIR
是一个由 CMake 自动生成的变量,它表示当前项目的根目录的绝对路径
add_subdirectory
命令添加子目录,添加前面创建的子目录。运行时会将当前的 CMakeLists.txt 文件的执行路径切换到指定的子目录,并且递归执行该目录下的 CMakeLists.txt 文件。因此,在执行子目录下的 CMakeLists.txt 文件之前,CMake 会先进入到子目录中,然后根据子目录中的 CMakeLists.txt 文件来构建该子目录的目标(如静态库等)
dd_executable
命令生成一个名为 phone
的可执行文件,main
目录只包含一个 main.c
文件,并且不依赖其他子目录中的源代码,则可以直接在顶层的 CMakeLists.txt 文件中使用 add_executable()
命令,将 main.c
编译成 phone
可执行文件,这也就是main目录下不需要CMakeLists.txt文件原因
target_link_libraries()
命令来链接静态库或其他目标等,这就是链接每一个子目录下利用cmake编译出来的静态库,这些静态库链接后,就可以生成对应的目标文件phone
创建一个build目录,进入该目录,执行“ cmake ../ ”命令构建Makefile
再执行" make "命令,利用Makefile编译出执行文件
查看当前目录可以看到
和最前面的运行一样,执行它就会运行文件
编译生成的全部杂项文件都在build目录下,如果要清理就直接把build目录删除即可,然后再重新建一个目录进入编译。如果修改了部分代码,直接使用make就可以重新编译
这里实现的部分代码与原版有所不同 ,增加了“修改”功能,用cmake构建编译。
本文学习于从0写一个电话号码管理的C入门项目【适合初学者】_一口Linux的博客-CSDN博客
一口Linux博主实力强悍、经验丰富、知识领域广,笔下文章内容丰富,大家可以点点关注