首先,对于stdlib.h头文件,你们不需要知道别的,只要了解,它里面高喊有申请动态内存空间的函数malloc() 就可以了。
所以,想要使用该函数,必须加此头文件,就像想要使用printf() 和scanf()必须要加上stdio.h一样。
同样,string.h头文件里也包含有好多函数,在本程序里面用的是strcmp() strcpy(),strlen()等等。
下面讲讲#define。
这就是传说中的宏定义了。
(额外讲一点,之所以称之为“传说中”,是因为我在接触它之前,感觉有多么多么神秘,但真正了解了,其实也没啥神秘可言)。
好了不废话了,举个例子#define MAXQQ11.其中,MAXQQ 是宏名,11 是宏体,用宏名可以代替宏体。
如若有一下语句:#define M 10 int a =M;则a的值就为10了;理解了吧?或许你们会问:为什么要用那么一大串字母代替一个数字呢?不麻烦么?直接a = 10;不更简单么?呵呵,其实这正是为了修改的方便才用的。
比如,一个很大的程序里面多次用到常量10,但后来需要把10改为11,那么只要修改开头部分的#define M 11就可以了,而大可不必在程序中一个一个的闷着头在那里一个劲的傻找。
是吧?下面是结构体,tpyedef struct _person {…}person;至于typedef语句,我想,理论是不能解开困惑的,还是举个例子吧:如有typedef int LIKAI;LIKAI a; 语句就等同于:int a; 两位聪明的三姐四姐,明白了没?总之一句话:typedef就是为数据类型取个别名,(刚刚讲的宏定义是给常量(或表达式)取个别名);所以,我们看到下面的一个结构体里的person per;语句就等同于struct _person per; 还有再下面的addr_book *head =NULL;就等同于struct addr_book *head =NULL啦!这样可以少写一些代码(这里其实也就是少写了一个单词而已)再下面从void add()~void input_person(person *p); 都是函数声明,这一点徐慧丽同学比较了解,有疑问可以问她。
再下面是定义的多个字符串数组,其长度已经隐式的指出,就是下面所以字符个数之和。
再下面就是主函数main()了,对了,关于函数的调用,三位同学还是有必要再了解一下的,但是限于篇幅,老师在这里就不再赘述。
希望掌握欠佳的同学课后自行温习一下。
main()函数开头,调用perint_welcome()函数,我们跳到这个函数里面看一看它的运行机制:外重for 循环内的变量i控制着屏幕的行数,在第四行(i从0~3),开始打印,if里面的三个打印字符串都打印完了呢,就直接跳到第七行,退出外重for循环,打印“回车键进入…”,等用户敲回车时,该函数执行完毕,返回到main()里调用它的地方。
然后进入while循环,循环条件永远为真,因为里面的switch语句里包含很多break语句,所以我们并不怕陷入死循环的泥沼中,嘿嘿,(因为我们不知道用户会输入多少条命令,所以要用while循环),在switch语句里,开关的钥匙是一个函数的返回值,这样可以少定义一个变量,代码也变得精简多了,好了,我们再跳到print_menu()函数里面看看:首先,清屏函数system(“cls”),(我帮你们想好了。
要是老师问你们,你们只需说这是个清屏函数,是你们需要清除Dos窗口内的字符的时候,请教高人才得以知道的,嘿嘿),printf(menu);函数打印主菜单界面,并等待用户选择命令,若不在1~8的命令范围只内,提示有误,并请求再次输入,当输入命令合法的时候,返回该命令数字。
然后我们在跳回主函数的switch 语句里,可以看到,无论用户选择那个命令,都会调用一个函数,这就是面向过程程序设计的一个特点,(徐慧丽同学现在知道了所谓的函数串的概念了吧?)而C++则不同,它所操作的都是由一个个类所定义的对象,而每个对象都有自己的“方法”(“方法”就是成员函数的一种牛叉的叫法,即专业名词,呵呵),体现了C++不同于C的一个特点:封装性。
其实我想我们以开始学C的相对于一开始学C++的同学还是很幸运的,因为我们首先接触的是面向过程程序设计,C++ 我们以后一定会学(会了C还怕不会C++?),而我们有C的基础,可以透过语法结构,从思想上、跟不上区别于二者,对照其异同点,效果会更好,而对于他们一开始就学C++的同学,此生恐怕没有机会接触C了…所以….唉,都是题外话,不说了。
继续讲switch,若print_menu()返回值为1,则调用开关语句里的add(),我们再跳进来看看:addr_book *last;addr_book*_new=(addr_book*)malloc( sizeof(addr_book) );第一句是定义一个结构体指针变量last(这里在强调一下,无论C还是C++,指针是精华,十分、非常、极其、特别、尤为、至关重要,所以,以后以编程为饭碗的同学,必须搞得十分清晰十分熟悉,希望引起三位同学的重视),第二句,定义一个结构体指针变量_new,并指向刚刚用malloc()函数申请的内存块,需要讲一下,malloc()是一个void类型的函数,无返回值,我们想想,啥也不返回,那怎么赋值给_new呢?对吧?所以必须使用强制类型转换,即(addr_book*)malloc()来把它的返回值转换成跟_new一个类型的变量的地址,并赋给_new,对了,sizeof(addr_book) 需要我讲么?这个函数是计算括号内数据类型的字节长度的,我们想呀:要开辟一块空地,应该指出它的尺寸大小吧?(长多少,宽多少)是吧,其实有很多东西,我们都可以把它形象化的,如数组、指针、函数模板、类模板,淡然模板是C++的内容。
不好意思,又跑题了。
然后,把刚刚申请的节点的指针域置空(为了防止它乱指,所以置空,但是其实这句是废话,不置空也行的)。
面我们要做啥呢?呵呵,当然是把这个节点加到链表当中啦!所以首先判断链表是否为空(全局指针head是始终指向链表的头结点的,所以,单链表中,没事别乱懂head,更千万别乱给它赋值,这咱伤不起啊╮(╯3╰)╭,否则整个链表会没有头绪,整个程序会崩溃…然后…然后,我们就完蛋了)如果链表头为空,则说明链表没有建立起来,那么就把刚刚申请的那块内存当作头吧,即把_new的地址赋给head,若head 不为空,说明已经有了节点,有多少个呢?不知道,好吧,那咱们调用find_last()(顾名思义)找找吧,找到链表的尾巴,然后把刚刚申请的内存块加到它的尾巴上就OK啦!那咱再跳到find_last()里看看它是怎么工作的:想找到尾巴,我们从那里开始下手呢?嘿嘿,你答对了,当然先从头开始啦,所以,find_last()函数的实参就是head指针。
需要注意的是,尾巴的一个特点就是:它的指针域是为空的,即:若p为尾节点,则p->next 的值为NULL Find_last()函数里,首先定义了一个结构指针p,将头head赋给他,然后head就可以回家吃饭去啦,哈哈,因为没它啥事了嘛!判断p所指的节点的下一个指向是否为空呢?这节链子下面挂的还有没有下一节呢?当然用眼睛是看不到的,我们上一段说过的,如果为尾巴,则指针域为空(NULL),立即结束find_last()函数,并将尾节点返回给调用它的地方,如果不为空,则把p的指向改为p当前所指的节点的下一个节点,继续判断,直到找到尾巴...找到了尾巴后,立即返回到add(),把尾巴赋值给last指针,并将该节点的指针域的nex指向申请的节点_new。
然后呢?呵呵,当然啦,要往这个节点里装东西啦!(其实就跟火车一样),即调用input_person(),形参就是这个节点的数据域的地址,而数据域就是我们以前讲过的那个小结构的全部成员。
现在,我们再跳到input_person()里看看:其实这个也没啥好讲的,就是简单的赋值嘛。
依次为数据域变量赋值后,返回到add(),然后问是否继续添加,选择“是”,则递归调用(自身调用自身),选择“否”,退出。
需要讲一下的是fflush(stdin) ;这个是清除内存缓冲区的函数,即把内存缓冲区的东西都清除掉,到时候老师要是提问你们的话,你们也说,是请教高人才知道的(目前只有这个理由能应付老师了,当然你们也可以自己想理由)。
好了,add()函数执行完毕,我们回到main()函数里调用add()的地方,呵呵它的下一条语句是break;所以,跳出了switch语句,进入while循环。
执行条件为1(真),所以再次进入了switch语句,然后再次调用print_menu()打印屏幕主菜单,根据其返回值来选择需要使用的功能,第二个是显示函数show(),这个没什么,所以咱们跳过。
第三个是查找函数search(),这里我就跟你们写了一种查找方式,其实可以写很多种的(比如:性别,年龄,手机,还有姓名模糊查找),这些功能就不添加了,否则代码会很长,老师会怀疑的(当然,我这里不是轻视三姐四姐的能力,请不要误会哈!)。
当然,如果你们很自信,并且希望添加这些查找方式的话,各位同学不必客气,直接跟我说,我再帮你们添加就是了。
search()我们也不说了,下面我们将删除函数delet(),首先,清屏、打印删除标题、定义结构变量(看看,head又出来了,这个东西可是个VIP级的关键变量啊),然后提示输入姓名,判断链表是否为空,如果为空,则不执行while循环体,直接跳到下面的if语句,因为一次也没有执行count++;所以,count肯定为0,所以输出没有此人的提示信息,否则,count 肯定不为0,既然不为0,说明必定执行了count++语句。
我们现在再拐回头,看看上面的while语句,它的循环体的if语句的条件里面调用了string.h头文件里的strcmp()函数,这个函数运行原理就是:比较两个字符串实参(这里就是p -> 和sea_name)如果相同,就返回0,所以,如果返回0,则说明找到了,调用printf()函数,打印该联系人的信息(该节点的数据域所有数据),count++;然后break;跳出while循环,进入下面的else语句,如果选择Y,就开始删除该联系人(该节点),首先判断要删除的联系人是否为第一个联系人(头结点),如果是,那可不得了啦!刚刚我们说过的,head可是个很很很重要的结构变量,没了它,链表就没了头绪,程序核心就崩溃,我们就完蛋了…(链式反应),言归正传,如果为头结点(p==head),则把head的指向修改为第二个节点,然后删除头结点(free(p)),如果要删除的联系人不是第一个,则我们不要碰head,(对了,这里先讲一下单链表的节点删除原则:先连后删,举例子把,假设有链表A→B→C,要删除B,则必须找到A,先把A 和C相连之后,在释放掉B,如果直接释放B,则链表会断的,程序一样会崩溃,我们一样会完蛋)所以,我们要用p1来找到前一个节点(当然从头head开始一个一个找啦,看看看看head多重要呀),如果找到(判断条件为p1所指向的节点的指针域所指向的下一个节点为p,p就是我们刚刚需要删除的那个节点),就把这个节点与p所指向的下一个节点相连(即实现A与C相连),然后再释放掉指针p所指向的节点。