【文档说明】第09章C语言高级程序设计课件.ppt,共(77)页,497.047 KB,由小橙橙上传
转载请保留链接:https://www.ichengzhen.cn/view-44648.html
以下为本文档部分文字说明:
C语言程序设计湖南工学院第9章C语言高级程序设计▪9.1编译预处理命令▪9.2位运算▪9.3结构体高级应用-链表▪本章小结9.1编译预处理命令ANSIC标准规定可以在C源程序中加入一些“预处理命令”,以改进程序环境,提高编程效率。C程序中编译预处
理语句的作用不是实现程序的功能,它们是发送给编译系统的信息。也就是说,对于预处理命令,必须在程序编译之前,先对这些特殊命令进行“预处理”。经过预处理后程序不再包含预处理命令了。C语言提供的预处理功能主要有宏定义、文件包含及条件编译三种。分别用宏定义命令,文件包含命令,条件编译
命令来实现。为了与一般C语句相区别,这些命令以符号“#”开头。9.1.1宏宏定义功能是定义符号常量和常参数的宏,宏定义编译预处理语句的格式如下:#define字符串1字符串2它把字符串1定义为字符串2,字符串1称为字符串2的宏定义,例如,下面是符号常量的宏定
义:#defineON1#defineOFF0它把符号常量ON定义为1,OFF定义为0。符号常量经过宏定义后,就可以在程序中作为常量使用。例如:if(a==ON)printf(“SwitchisON\n”);elseif(a==OFF)pr
intf(“SwitchisOFF\n”);不带参数的宏定义:用一个指定的标识符(即名字)来代表一个字符串。一般形式:#define标识符字符串例:#definePI3.14159说明:(1)宏名一般习惯用大写字母表示。(2)使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工
作量。(3)宏定义是用宏名代替一个字符串,也就是作简单的置换,不作正确性检查。(4)宏定义不是C语句,不必在行末加分号.#definePI3.14159;area=PI*r*r;展开:area=3.14159;*r*r;出现语法错误(5)#define命令出现在程序中函数的外面,宏名
的有效范围为定义命令之后到本源文件结束。(6)可以用#undef命令终止宏定义的作用域。格式:#undef宏名(7)在进行宏定义时,可以引用已定义的宏名,可以层层置换。#defineR3.0#definePI3.14159#def
ineL2*PI*R#defineSPI*R*Rmain(){printf(“L=%f\nS=%f\n”,L,S);}(8)对程序中用双括号括起来的字符串内的字符,即使与宏名相同,也不进行置换。(9)宏定义是专门用于预处理命令,只作字符替换。带
参数的宏定义:不是进行简单的字符串替换,还要进行对数替换。一般形式:#define宏名(参数表)字符串例:#defineS(a,b)a*barea=S(3,2)#definePI3.14159#defineS(r)PI*r*rmain
(){floata,area;a=3.6;area=S(a);printf(“r=%f\narea=%f\n”,a,area);}说明:(1)对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替
#define命令行中的形参。area=S(a+b)把实参a+b代替PI*r*r中的形参r,成为:area=PI*a+b*a+b则#defineS(r)PI*(r)*(r)9.1.2文件包含“文件包含”处理所谓“文件包含”处理是指一个源文件可以将另外一个源文件的全部内容包含进来
,即将另外的文件包含到本文件之中。C语言提供了#inctude命令用来实现“文件包含”的操作。其一般形式为#include“文件名”或#include<文件名>注意:(1)一个include命令只能指定一个被包含文件,如果要包含几个文件,要用几个inc
lude命令。(2)如果文件1包含文件2,而文件2中要用到文件3的内容,则可在文件1中用两个inctude命令分别包含文件2和文件3,而且文件3应出现在文件2之前,即在filel.C中定义:#include“file3.h”#include“file2.h”这样,fite1和file2都可以用fi
le3的内容。在file2中不必再用#include<file3.h>了。(3)在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。【例9.2】文件put.h使用TYPE命令显示的内容如p
ut.h代码,它包含有各种输出格式的带参数宏的定义:程序如L9-2.c【例9.3】有如下两个源文件:filel.hL9-3.c9.1.3条件编译为了解决程序的可移植性问题,C语言提供了条件编译命令,它能使用一个源程序在不同的编译环境下生成不同的目标文件,条件编译命令有以下几种:(1)
#ifdef标识符程序段1#else程序段2#endif其功能是用来测试测试一个标识符(宏名)是否被定义,如果标识符已被定义,则在程序编译阶段只编译程序段1,否则编译程序段2。该命令形式的简化形式是没有#else部分,这时,若标识符未定义,则此命令中没有程序段被编译。(2)#ifnde
f标识符程序段1#else程序段2#endif其功能是用来测试测试一个标识符(宏名)是否未曾被定义,如果标识符未被定义,则编译程序段1,否则编译程序段2。该命令形式的简化形式是没有#else部分,这时,若标识符已定义,则此命令中没有程序段被编译。(
3)#if表达式程序段1#else程序段2#endif它的作用是当指定的表达式为真时就编译程序段1,否则编译程序段2。其中的表达式必须是整型常量表达式(不包含sizeof运算符、强制类型转换和枚举常量)。该命令形式的简化形式是没有#else部分,这时,若表达式为“假”,则此命令中
没有程序段被编译。(4)#if表达式1程序段1#elif表达式2程序段2#elif表达式3程序段3┇#else程序段n#endif这里的#elif其含义是“elseif”,该命令的功能是,如果常量表达式1的值为“真”,则编译程序段1,否则如果常量表达式2的值为“真”,则编译程序段2,如果常量表
达式的值都为“假”,则编译程序段n。也可以没有#else部分,这时,若所有表达式的值都为“假”,则此命令中没有程序段被编译。(5)#ifdefined(宏名)程序段1#else程序段2#endif该命令等价于#ifdef标识符,但该命令可以判别多
个宏名的定义情况,而#ifdef命令只能判断一个宏名的定义情况。!Defined(宏名)的作用是当宏名未定义时其值为真,因此#if!defined(宏名)等价于#ifndef标识符。例如,在alloc.h文件中,可以看到以下关
于NULL的定义,其目的是适合不同编译模式的兼容性。#ifndefNULL#ifdefined(_TINY_)||defined(_SMALL_)||defined(_MEDIUM_)#defineNULL0#else#defineNULL0L#endif#
endif【例9.4】输入一个口令,根据需要设置条件编译,使之在调试程序时,按原码输出;在使用时输出“*”号。#defineDEBUGvoidmain(viod){charpass[80];int=-1;print
f(“\nPleasaInputPassword:”);do{i++;pass[i]=getch();#ifdefDEBUGputchar(pass[i]);#elseputchar(‘*’);#endif}while
(pass[i]!=’\n’);┇}9.2位运算所谓位运算,是指对一个数据的某些二进制位进行的运算。每个二进制位只能存放1位二进制数“0”或者“1”。通常把组成一个数据的最右边的二进制位称作第0位,从右以此称为第1位,第二位,
…,最左边一位称作最高位,如图9—1所示。9.2.1位运算和位运算符C语言提供6种运算符,如表9-1所示。说明:反运算符“~”是单目运算符,其余是双目运算符,即要求两侧各有一个运算量。位运算的运算对象只能是整型或字符型数据,而不能是实型数据。9.2.2位运算符的使用1按位取反运算符“
~”按位取反运算为单目运算,它将运算对象的各位取反,即将1变0,0变1。例如~O24是对八进制数24(即二进制数00010100)按位求反:~0001010011101011运算结果为八进制数353。2按位与运算符“&”“按位与”是指两个运算对象
按对应二进制位进行“逻辑与”运算,即当且仅当参加运算的两个对象的对应二进制都为1时,结果的对应二进制位为1,否则为0。即0&0=0;0&1=0;1&0=0;1&1=1;例如:设intx=3,y=5;因为x,y是整型,占两个字节,对应的二进制形式分别为000000000000
0011和0000000000000101,所以x=0000000000000011y=0000000000000101x&y=0000000000000001因此,3&5的值值等于1。如果参加&运算的是负数(如-3&-5),则以补码形式表示为二进制数,然后按位进行“与”运算。“按位
与”运算的应用主要为:位清零、测试指定位的值和获取指定位的值。(1)位清零如果想将一个数据中的某些位清零,根据“按位与”运算的含义,只需要鼗这些位与0进行“按位与”运算即可。例9.5设有一个字符型变量(8位二进制位),把它的低4位
清0。分析:欲使x的低4位为0,只需将x的低4位与0进行“按位与”即可。即x=x&0xf0。运算过程如下:x=********&11110000****0000其中“*”是1或0。(2)测试指定位的值要想判断某一指定位的值是1
还是0,只需将这一位与1进行“按位与”运算,然后判断结果是否为0即可。例9.6设x是一个字符型变量(8位二进制位),判断x的最低位是否为0。分析:想要判断x的最低位是0还是1,可以进行如下运算:if((x&
0x01)!=0)则x的最低位是1;或者if((x&0x0x)==0)则x的最低位是0;因为:x=********&000000010000000*所以若x最低位为1,则表达式结果为1;若最低位为0,则表达式结果为0。注意:“按位与”运算&的优先级低于关系运算符!=和==,所以表达式(x&
0x01)的圆括号不能省略。(3)获取指定的值要想获得某些位的值只需将这些位与1“按位与”运算,而将其他位清0即可。例9.7设X是unsigned类型的整数(16位二进制数),要想获取X的低8位,可做运算X&0X00ff。运算过程为:x
=****************&000000001111111100000000********可以看出,结果只保存了X的低8位。例9.8从键盘输入一个正整数,判断此数是奇数还是偶数。分析:一个正数是奇数还是偶数,只需要判断最低位是1还是0。若最
低位为0,该数是偶数;最低位为1,则是奇数。程序如L9-8.c3按位或运算符“|”“按位或”运算是指两个运算对象按对应二进制位进行“逻辑或”运算,即当参加运算的两个对象的对应二进制位有一个“1”时,结果的对应二进制位为“1”,如下所示:0|0=0;0|1=1;1
|0=1;1|1=1;例如:设intx=3,y=-5,则x|y的结果如下:x=0000000000000011|11111111111110111111111111111011“按位或“运算常用于对一个数据中的某些位置1。例9.9
编一程序,从键盘输入一个字符,如果是大写字母,则转换成小写字母输出。分析:字符在计算机内是以ASC‖码表示的,小写字母的ASC‖码值范围为16进制数61H~7AH,大写字母的ASC‖码值范围为16进制数41H~5AH,即小写字母和大写字母的ASC‖码值相差20H。因此要想把大写字母转
换成小写字母只需把大写字母的第6位置1即可。如:A的ASC‖码为41H,a的ASC‖码值为61H。A=01000001(41H)|00100000(20H)a=01100001(61H)程序如L9-9.c4按位异或运算符“^”“按位异或”运算是指两个
运算对象按对应二进制位进行“逻辑异或”运算,即当参加运算的两个对象的相应二进制位一个为“0”,另一个为“1”时,结果的对应二进制位为“1”,如下所示:0^0=0;0^1=1;1^0=1;1^1=0;“按位异或”运算的应用;(1)使数据中的某
些位求反。因为0和1的异或结果为1,1和1的“异或”结果为0,所以只需将要求反的位与1进行“异或”运算,就可以将该位求反。(2)将变量清0。任何一个属于变量本身的“异或”结果都为0。利用“异或”运算的这个性质可以完成对变量的
清0操作。5左移运算符“<<”左移运算符“<<”的使用方式为:运算对象<<左移位数左移运算符将运算对象的每个二进制位同时向左移动指定的位数,从左边移出的高位部分被丢弃,空出的低位部分补0。6右移运算符“>>”右移运算符
“>>”的使用方式为:运算对象>>右移位数右移运算符将运算对象的每个二进制位同时向右移动指定的位数,从右边移出的低位部分被丢弃。对无符号数,左边空出的高位补0;对有符号数,正数的高位部分补0,负数的高位部分补0还是1和计算机系统有关。移入0的称为“逻辑右移”,移入1
的称为“算术右移”。“逻辑右移”相当于无符号数除以2,“算术右移”相当于有符号数除以2。例如:a:1001011111101101a>>1:0100101111110110------逻辑右移a>>1:1100101111110110---
---算术右移7位复合赋值运算符类似于算术运算的复合运算符,位运算符和赋值运算符也可以构成“复合赋值运算符”。如9-2所示。8位运算的应用(1)键盘扫描码键盘上除了ASC||码外,还有非ASC||码(如左移键“←”对应的编码),叫扩充键盘码。
我们把扩充键盘码放在高八位,ASC||码放在低八位所组成的代码称为扫描码。对于某一特定的扫描码,若其低八位不为零,则此八位就是相应字符的ASC||码值;若低八位是零,则高八位是扩充键盘码,需要再读取入第二个字节,根据它的值来判断它是那一个功能键。表9-3给出了单功能键和组合功能键的键
值,表中代码系指第二个字节键值的十进制数。例如,“←”的扫描码低八位应为零,而高八位是0X4B,所以,“←”键的扫描码为0X4B00;回车键也有对应的ASC||码,故其扫描码的低八位是回车键的ASC||码值0X0
D。(2)测试键盘扫描码调用标准库函数bioskey()读取键盘扫描码,再通过位运算分离低八位字符的ASC||码和高八位的扩充键盘码。注意使用bioskey()函数,应在头部加上#include“bios
.h”。例9.10利用bioskey()函数,测试键盘扫描码,按ESC键退出。算法设计:(1)利用位的“与”运算,提取低8位low=key&0x00ff;如果low不为零,则此8位就是相应字符的ASC||码值;否则(2)利用位的“与”和右移“>>”运算,提取高8位的扩充键盘码hig=(
key&0xff00)>>8;程序如L9-10.c说明:(1)关于函数bioskey()函数bioskey()的功能是用于识别用户按键和获得按键值。函数原型是:intbioskey(intcmd);在<bios.h>中定义,在使用它时,应
用include命令将bios.h文件包含进来。其中参数cmd可取0或1。①当cmd=1时,检测键盘是否击键,如果没有击键,函数将返回0,否则返回非零;②当cmd=0时,返回从键盘输入的扫描码。语句key=bioskey(0);读取键盘输入的扫描码,并存储在变量key中。9.2.3位段
在计算机中一般以字节为单位存放数据,但实际上有时存储一个信息不必用一个字节或多个字节,例如,“真”或“假”用0或1表示,即只需要1位表示即可。因此,在计算机里常常在一个字节中放几个信息。C语言提供两种方法,操作一个字节中的一个或几个二进制位。1位运算法如图9-2所示,假设a、b、
c和d分别占2位、6位、4位和4位,设data由a、b、c和d组成。如果想将c的值置为12,用位运算方法,操作如下:将数x=12左移4位,使1100成为右面的第4~7位,即:x<<=4(0000000011000000)(2)将data与0xff0f进行“与”运算,使data的第
4~7位全为0,即:data&=0xffof;(3)将data与“12<<4”进行“按位或”运算,即:data|=(12<<4)或将3步结合起来,可以表示为:data=data&0xff0f|(12<<4);显然用位运算方法太麻
烦,下面介绍使用位段结构体的方法2.位段C语言允许在一个结构体中以位为单位来指定其成员所占内在长度,这种以位为单位的成员称为“位段”或称“位域”(bitfield)。利用位段能够用较少的位数存储数据。(1)位段结构类型定义
位段是一种构造类型,类型定义的方法为:struct类型名{基类型位段名1:位段1占用位数;基类型位段名2:位段2占用位数;┇基类型位段名n:位段n占用位数;};例如,图9-2所示的问题,可以用位段结构类型定
义为:structmy_data{unsigneda:2;unsignedb:6;unsignedc:4;unsignedd:4;};其中my_data称位段名,它包含4个位段(bitfield),每个位段的数据类型都是unsigned,各位段所占的二进制位数由冒号后面的
数字指定:2,6,4,4。(2)位段变量的定义与引用定义了位段结构类型后,就可以定义相应的位段结构类型变量了。其定义方法和其他变量的定义方法一样。如,structmy_datadata;位段数据的引用方法和结构成员的引用方式相同。例如:
data.c=12;就实现了上题的目的。(3)关于位段的说明位段的基类型必须为unsignedint类型。可以将类型说明和变量说明一起完成。例如,structmy_data{unsigneda:2;unsignedb:6;unsignedc:4;unsignedd
:4;}data;可以定义无名位段,它起着位段之间的分隔作用。例如:structmy_data1{unsigneda:4;unsignedb:4;unsigned:2;unsignedc:2;}data1;该位段的第3个位段是无名位段,占2个二进制位,其作用是交b和c两个位段分隔开,如图9-3所示
。④若某一位段要从另一个字开始存放。可以定义无名位段的长度为0,用以下形式定义:structmy_data2{unsigneda:4;unsignedb:4;unsigned:0;/*无名位段长度为0*/unsign
edc:2;}data2;位段变量data2在内存的存储格式如图9-4所示。由于用了长度为0的位段,使其下一个位段从下一个字(每字2个字节)开始存放。因此,a、b存储在一个字中,c存储在下一个字中。⑤一个位段必须存储在同一存储单元(字)中,不能
跨单元(字)存储。如果一个单元(字)空间不能容纳下一个位段,则该空间不用,而从下一个单元(字)起存放该位段。⑥位段的长度不能大于存储单元(字)的长度,也不能定义位段数组。9.3结构体高级应用-链表数组作为存放同类数据的集合,给我们在程序设计时带来很多的方便,增加了灵活性。但数
组也同样存在一些弊病。如数组的大小在定义时要事先规定,不能在程序中进行调整,这样一来,在程序设计中针对不同问题有时需要30个大小的数组,有时需要50个数组的大小,难于统一。我们只能够根据可能的最大需求来定义数组,常常会造成一定
存储空间的浪费。我们希望构造动态的数组,随时可以调整数组的大小,以满足不同问题的需要。链表就是我们需要的动态数组。它是在程序的执行过程中根据需要有数据存储就向系统要求申请存储空间,决不构成对存储区的浪费。9.3.
1链表和动态存储分配概述在数组一章中,曾介绍过数组的长度是预先定义好的,在整个程序中固定不变。C语言中不允许动态数组类型。例如:intn;scanf("%d",&n);inta[n];用变量表示长度,想对数组的大小作动态说明,这是错误的。但是在实际的编程中,往往会发生这种情况,即所
需的内存空间取决于实际输入的数据,而无法预先确定。对于这种问题,用数组的办法很难解决。为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间
回收待用,为有效地利用内存资源提供了手段。常用的内存管理函数有以下三个:1.分配内存空间函数malloc调用形式:(类型说明符*)malloc(size)功能:在内存的动态存储区中分配一块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。“类型说明符”表
示把该区域用于何种数据类型。(类型说明符*)表示把返回值强制转换为该类型指针。“size”是一个无符号数。例如:pc=(char*)malloc(100);表示分配100个字节的内存空间,并强制转换为字符数组类型,函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc。2
.分配内存空间函数calloccalloc也用于分配内存空间。调用形式:(类型说明符*)calloc(n,size)功能:在内存动态存储区中分配n块长度为“size”字节的连续区域。函数的返回值为该区域的首地址。(类型说明符*)用于强制类型转换。calloc函数与malloc函数
的区别仅在于一次可以分配n块区域。例如:ps=(struetstu*)calloc(2,sizeof(structstu));其中的sizeof(structstu)是求stu的结构长度。因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变
量ps。3.释放内存空间函数free调用形式:free(void*ptr);功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,它指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域:例9.11分配一块区域,输入一个学生数据。程序如L9-11.c本例中
,定义了结构stu,定义了stu类型指针变量ps。然后分配一块stu大内存区,并把首地址赋予ps,使ps指向该区域。再以ps为指向结构的指针变量对各成员赋值,并用printf输出各成员值。最后用free函数释放ps指向的内存空间。整个程序包含了申请内存空间、使用内存空间、
释放内存空间三个步骤,实现存储空间的动态分配。链表的概念在例7.9中采用了动态分配的办法为一个结构分配内存空间。每一次分配一块空间可用来存放一个学生的数据,我们可称之为一个结点。有多少个学生就应该申请分配多少块内存空间
,也就是说要建立多少个结点。当然用结构数组也可以完成上述工作,但如果预先不能准确把握学生人数,也就无法确定数组大小。而且当学生留级、退学之后也不能把该元素占用的空间从数组中释放出来。用动态存储的方法可以
很好地解决这些问题。有一个学生就分配一个结点,无须预先确定学生的准确人数,某学生退学,可删去该结点,并释放该结点占用的存储空间。从而节约了宝贵的内存资源。另一方面,用数组的方法必须占用一块连续的内存区域。而使用动态分配时,每
个结点之间可以是不连续的(结点内是连续的)。结点之间的联系可以用指针实现。即在结点结构中定义一个成员项用来存放下一结点的首地址,这个用于存放地址的成员,常把它称为指针域。可在第一个结点的指针域内存入第二个结点的首地址,在第二个结点的指针域内又存放第三个结点的首地
址,如此串连下去直到最后一个结点。最后一个结点因无后续结点连接,其指针域可赋为0。这样一种连接方式,在数据结构中称为“链表”。在链表中,第0个结点称为头结点,它存放有第一个结点的首地址,它没有数据,只是一个指针变量。以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如学
号num,姓名name,性别sex和成绩score等。另一个域为指针域,存放下一结点的首地址。链表是一种常见的重要的数据结构。链表中的每一个结点都是同一种结构类型。它是动态地进行存储分配的一种结构。它可以根据需要开辟内存单元
。链表有一个“头指针”变量,以head表示,它存放一个地址。该地址指向一个元素。链表中每一个元素称为“结点”,每个结点都应包括两个部分:一为用户需要用的实际数据,二为下一个结点的地址。因此,head指向第一个元素:第一个元素又指向第二个元素;……
,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,下
面将逐一介绍。9.3.2单链表单链表有一个头节点head,指向链表在内存的首地址。链表中的每一个节点的数据类型为结构体类型,节点有两个成员:整型成员(实际需要保存的数据)和指向下一个结构体类型节点的指针即下一个节点的地址(事实上,此单链表是用于存放整型数据的动态数组)。链表按此9.3.2.1单
链表的结构结构对各节点的访问需从链表的头找起,后续节点的地址由当前节点给出。无论在表中访问那一个节点,都需要从链表的头开始,顺序向后查找。链表的尾节点由于无后续节点,其指针域为空,写作为NULL。图9-5还给出这样一层含义,链表中的各节点在内存的存储地址不是连续的,其各节点的地址是在需要时向
系统申请分配的,系统根据内存的当前情况,既可以连续分配地址,也可以跳跃式分配地址。看一下链表节点的数据结构定义:structnode{intnum;structnode*p;};在链表节点的定义中,除一个整型的成员外,成员p是指向与节点类型完全相同的指针。在链表节点的数据结构中
,非常特殊的一点就是结构体内的指针域的数据类型使用了未定义成功的数据类型。这是在C中唯一规定可以先使用后定义的数据结构。•单链表的创建过程有以下几步:1)定义链表的数据结构。2)创建一个空表。3)利用ma
lloc()函数向系统申请分配一个节点。4)将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新节点接到表尾。5)判断一下是否有后续节点要接入链表,若有转到3),否则结束。•单链表的输出过程有以下几步1)找到表头。2)若是非空表,输出节点的值成员,是空表则退出。
3)跟踪链表的增长,即找到下一个节点的地址。4)转到2)。例9.12创建一个存放正整数(输入-999做结束标志)的单链表,并打印输出。程序如L9-12.c在链表的创建过程中,链表的头指针是非常重要的参数。因为对链表
的输出和查找都要从链表的头开始,所以链表创建成功后,要返回一个链表头节点的地址,即头指针。9.3.2.2单链表的插入与删除在链表这种特殊的数据结构中,链表的长短需要根据具体情况来设定,当需要保存数据时向系统申请存储空间,并将数据接入链表中。对链表而言,表中的数据可以依
此接到表尾或连结到表头,也可以视情况插入表中;对不再需要的数据,将其从表中删除并释放其所占空间,但不能破坏链表的结构。这就是下面将介绍的链表的插入与删除。1.链表的删除在链表中删除一个节点,用图9-6描述如下:例9.13创建一个学
生学号及姓名的单链表,即节点包括学生学号、姓名及指向下一个节点的指针,链表按学生的学号排列。再从键盘输入某一学生姓名,将其从链表中删除。首先定义链表的结构:struct{intnum;/*学生学号*/charstr[20];/*姓名*/structnode*ne
xt;};从图9-6中看到,从链表中删除一个节点有三种情况,即删除链表头节点、删除链表的中间节点、删除链表的尾节点。题目给出的是学生姓名,则应在链表中从头到尾依此查找各节点,并与各节点的学生姓名比较,若相同,则查找成功,否则,找不到节点。由于删除的
节点可能在链表的头,会对链表的头指针造成丢失,所以定义删除节点的函数的返回值定义为返回结构体类型的指针。structnode*delet(head,pstr)以/*head为头指针,删除pstr所在节点*/structnode*head;char*p
str;{structnode*temp,*p;temp=head;/*链表的头指针*/if(head==NULL)/*链表为空*/printf("\nListisnull!\n");else/*非空表*/{temp=h
ead;while(strcmp(temp->str,pstr)!=0&&temp->next!=NULL)/*若节点的字符串与输入字符串不同,并且未到链表尾*/{p=temp;temp=temp->next;/*跟踪链表的增长,即指针后移*/}if(strcmp(temp->str,pstr)=
=0)/*找到字符串*/{if(temp==head){/*表头节点*/printf("deletestring:%s\n",temp->str);head=head->next;free(temp);/*释放被删节点*/}else{p->n
ext=temp->next;/表*中节点*/printf("deletestring:%s\n",temp->str);free(temp);}}elseprintf("\nnofindstring!\n");没/找*到要删除的字符串*/}return(head);/*返回表头指针*/}
2.链表的插入首先定义链表的结构:struct{intnum;/*学生学号*/charstr[20];/*姓名*/structnode*next;};在建立的单链表中,插入节点有三种情况,如图9-7所示。插入的节点可以在表头、表中
或表尾。假定我们按照以学号为顺序建立链表,则插入的节点依次与表中节点相比较,找到插入位置。由于插入的节点可能在链表的头,会对链表的头指针造成修改,所以定义插入节点的函数的返回值定义为返回结构体类型的指针。节点的插入函数如下:structnode*insert(head,pstr
,n)/*插入学号为n、姓名为pstr的节点*/structnode*head;/*链表的头指针*/char*pstr;intn;{structnode*p1,*p2,*p3;p1=(structnode*)
malloc(sizeof(structnode))分;配/*一个新节点*/strcpy(p1->str,pstr);/*写入节点的姓名字串*/p1->num=n;/*学号*/p2=head;if(head==NULL)/*空表*/{head
=p1;p1->next=NULL;/*新节点插入表头*/}else{/*非空表*/while(n>p2->num&&p2->next!=NULL)/*输入的学号小于节点的学号,并且未到表尾*/{p3=p2;p2=p2->next;/*跟踪链表增长*/}if(n
<=p2->num)/*找到插入位置*/if(head==p2)/*插入位置在表头*/{head=p1;p1->next=p2;}else{/*插入位置在表中*/p3->next=p1;p1->next=p2;}else{/*插入位置在表尾*/p2->next=p1;p1->next=NULL
;}}return(head);/*返回链表的头指针*/}9.3.3遍历链表由于链表是一个动态的数据结构,链表的各个结点由指针链接在起,访问链表元素时通过每个链表结点的指针逐个找到该结点的下一个结点,—直
找到链表尾,链表的最后一个结点的指针为空。例如:编历链表函数。voidoutputlist(LINKLIST*head){LINKLIST*current=head->next;while(current!=NULL){printf("
%d\n",current->info);current=current->next;}return;}9.3.4双向链表每个结点中只包括一个指向下个结点的指针域,这种链表称为单向链表。如果要在单向链表一个指针所指的当前位置插入一
个新结点,就必须从链表头指针开始逐个遍历直到当前指针所指结点的前一结点,修改这个结点的指针。双向链表的每个结点中包括两个指针域,分别指向该结点的前一个结点和后一个结点。在双向链表中由任何一个结点都很容易找到其前面的
结点和后面的结点,而不需要在上述的插入(及删除)操作中由头结点开始寻找。定义双向链表的结点结构为typedefstructnode{DATATYPEinfo;node*priv,*next;}DINKLIST;下面给出双向链表中插入删除一个结点的函数。操作过程见下图:例9-14将一
个结点插入到双向链表指定结点之后。insertafter(DINKLIST*current,DINKLIST*new){new->next=current->next;new->priv=current;current->next->priv=new;current->next->new
;}例9-15将一个结点插入到双向链表指定结点之前。insertbefor(DINKLIST*current,DINKLIST*new){new->next=current;new->priv=curre
nt->priv;current->priv=current->priv;current->priv=new;}例9-16在双向链表中删除一个指定结点。deleteelement(DINKLIST*current){current->nex
t->priv=current->priv;current->priv->next=current->next;delete(current);}9.3.5循环链表单向链表的最后一个结点的指针域为空(NULL)。如果将这个指针里利用起来,以指向单向链表的第一个结点,就
组成一个单向循环链表。如图9-15所示:9.3.6链表应用实例例9.17创建包含学号、姓名节点的单链表。其节点数任意个,表以学号为序,低学号的在前,高学号的在后,以输入姓名为空作结束。在此链表中,要求删除一个给定姓名的节点,并插入一个给定学号和姓名的节点
。程序如L9-17.c例9-18已有a,b两个链表,每个链表中的结点包括学号,成绩,要求把两个链表合并,按学号升序排列。程序如L9-18.c本章小结1.编译预处理AnsiC标准规定可以在C源程序中加入一些“预处理命令”,以改进程序环境,提高编
程效率。对于预处理命令,必须在程序编译之前,先对这些特殊命令进行“预处理”。经过预处理后程序不再包含预处理命令了。C语言提供的预处理功能主要有以下三种:A、宏定义B、文件包含C、条件编译分别用宏定义命令,文件包含命令,条件编译命令来实现。为了与一般C语句相区别,这些命令以符号“
#”开头。2.位运算C语言提供了位运算的功能,这使得C语言也能像汇编语言一样用来编写系统程序。位运算符C语言提供了六种位运算符:&按位与|按位或^按位异或~取反<<左移>>右移1)按位与运算按位与运算
符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1,否则为0。参与运算的数以补码方式出现。2)按位或运算按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个
为1时,结果位就为1。参与运算的两个数均以补码出现。3)按位异或运算按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现。4)求反运算求反运算符~为单目运算符,具有右结合性。其功能
是对参与运算的数的各二进位按位求反。例如~9的运算为:~(0000000000001001)结果为:11111111111101105)左移运算左移运算符“<<”是双目运算符。其功能把“<<”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动
的位数,高位丢弃,低位补0。例如:a<<4指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。6.右移运算右移运算符“>>”是双目运算符。其功能是把“>>”左边的运算数的各二进位全部右移若干位,“
>>”右边的数指定移动的位数。例如:设a=15,a>>2表示把000001111右移为00000011(十进制3)。应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为1,最高位是补0或是补1取决于编译系统的规定。Turb
oC和很多系统规定为补1。2.位段有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一
个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。位段的定义和位域变量的说明位域定义与结构定义相仿
,其形式为:struct位段结构名{位段列表};其中位域列表的形式为:类型说明符位段名:位段长度。位段变量的说明与结构变量说明的方式相同。3.链表链表是一种重要的数据结构,原因就在于它可以动态的进行存储分配。链表都有一个头指针,用来存放整个链表的首地址
。链表的定义形式如下:structnode{intnum;…structnode*next;};next用来存放下一节点的地址。如何进行动态的开辟和释放存储单元呢?c提供了以下有关函数:1)malloc(size)在内存的动态存储区开辟一个长度为size的连续空间。成功返
回空间首地址,失败返回0;2)calloc(n,size)在内存的动态存储区开辟n个长度为size的连续空间。成功返回空间首地址,失败返回0;3)free(ptr)释放由ptr指向的内存区。Ptr是最近调用一次调用mallo
c和calloc时返回的值。上面函数中,n和size为整型,ptr为字符指针。请将实验报告(10次)和作业本在下周二前交系办.否则后果自负.