1、C语言基础教程 8.1.1 结构和结构变t的定义 结构是一种数目固定、类型不同的若干个变量的有序的集合。结构变量是指具有某种结构类型的变量,定义结构变量之前应该先定义某种结构类型,有了这种结构类型后,再定义具有这种结构类型的结构变量、指向结构变量的指针以及结构数组等。结构数组是指数组元素为结构变量的数组。 下面是定义结构类型即结构模式的格式; struct结构名)不 ; 其中,struct是关键字。(结构名)同标识符,(结构成员说明是给出该结构模式的若干个成员,包含各个成员的名字(即变量名)和类# oC语言中允许出现的各种类型的变量都可以作结构成员,包含int型,float型,double型,
2、char型变量,以及指针、数组、联合变量等。结构变量和指向结构变量的指针也可以作结构的成员,即结构定义是可以嵌套的。但是,某类结构的结构变量可以是另一类结构的结构成员,而不可是本身结构的结构变量。指向结构变量的指针可以是本身结构的成员。 例如: struct card int pips char suit; ; 这是一个结构模式,其结构名是card,它有两个成员:一个是int型变量pips,另一个是字符型变量suit.该结构是用来描述一张扑克牌的,pips表示该牌的点数,而suit表示该牌的花色,其花色用字符s(黑桃), h (红桃). d(方块)c(梅花)来表示。任何一种结构模式都是对某种客
3、观事物的抽象。 在结构模式已定义好后,可以用下述格式来定义具有某种结构模式的结构变量。 Srruct; 其中,是已被定义的某种结构模式,(结构变量名表)中可有若干个结构变量、指向结构变量的指针、结构数组等。它们之间用逗号分隔。例如: struct card c1 .c2,*pc,cc52; 其中,c1和c2是具有card结构模式的结构变量,pc是指向具有card结构模式的结构变量的指针,cc是结构数组名,它有52个元素,每个元素是一个具有card结构模式的结构变量。 由此可见,定义一个结构变量可以分两步,先定义好一个结构模式,再定义具有这种结构模式的结构变量。 在名写J、可以在定义结构模式后,
4、马上定义结构变量,其格式如下: StTUCI ; 例如: struct card int ptps; char suits ).c1,c2,*pc,cc52; 这种连起来定义与前面分开定义是等价的。如果所要定义的结构变量一次性定义完,即不再出现用这种结构模式来定义新的结构变量,则可将该结构模式的结构名省略,即为无名结构。用这种结构只能一次定义所有的结构变量。例如: struct int pips; char suit; cl.c2,*pc, cc52; 下面举例说明结构定义的嵌套,即用另一个结构的结构变量作该结构的成员。 struct date ins day,month,year: char
5、 rnon name4; ); struct ;student char*name char sex int old; atruct date birthday; 这里有两个结构模式date和student.在date结构中,使用了char型数组作结构成员,在student结构中,使用了char型指针作结构成员,又使用结构变量birthday作结构成员。struct student Wang, Li,Zhang,其中,Wang* Li, Zhang是3个具有student结构模式的结构变量。该变量将具有student 结构模式所具有的4个结构成员,其中有一个是结构变量作成员。 下面举一个指向自
6、身结构的指针作结构成员的例子,这又是一种结构定义的嵌套。 struct node int float b. struct node*P; 其中,P是指向该结构的结构变量的指针,它作为结构成员,又称指向自身结构的指针作该结构的成员。 最后指出,上面讲过的结构名、结构成员名和结构变量名是三个完全不同的概念,它们允许同名。例如: street s Int a; char*b; Char*s; s,m,*pn; 这里出现了三个S,它们分别表示不同的含义。第一次出现的s表示结构名,第二次出现的S表示某个成员名,该成员是字符指针,第三个S表示结构变量名。C语言基础教程 8.1.2 结构变量成员的表示 结构
7、变量成员的表示有如下两种方法。 1.结构变童的成员 结构变量的成员用运算符。表示。其格式如下: (结构变量名)一 例如,在前面定义过的结构变量c1和c2,它们是Card结构模式的结构变量名。 c1. pips和c1.suit分别表示结构变量c1的两个成员pips和suit. 同样,c2. pips和c2.suit分别表示结构变量cl的两个成员pips和suit. 又例如: strnrt student char name20: int student no; char grade; sl,s2; 其中,s1和S2分别是student结构模式的两个结构变量。 s1.name表示S1的name成员
8、; s1. student_ no表示s1的student_no成员; s1. grade表示s1的grade成员。 2.指向结构变量的脂针的成员 指向结构变量的指针的成员用运算符一表示。其格式如下: 一(结构变量名) 或者 (* pips表示指针Pc的成员PiPs. pc-suit表示指针pc的成员suit. 或者 (*pc). pips等价于pc一pips (*pc). suit等价于pc一suitC语言基础教程 8.1.3 结构变量的赋值 结构变量可以被赋值,也可以被赋初值。一般编译系统要求外部或静态的结构变量才能被赋初值。 给结构变量赋初值的方法与给数组斌初值的方法相似,使用初始值表。
9、例如,给前面已定义过的结构变量cl和c2赋初值如下: StCUCt CaCd C1=(l2,s) struct card c2=(1,h) 这里,给结构变量c1赋初值后,c1为黑桃Q,又给结构变量cz赋初值后,使cz为红桃A 又例如。前面定义了student结构模式。下面给该模式的一个结构变量赋值: ctruct student s1=Wang Ping,7601,A; s1是一个具有,tudent结构模式的一个结构变量,它有3个成员,赋初值时使用初始值表对它的三个成员分别赋初值,要求顺序与类型要一致。 给指向结构变量的指针赋初值要用相同类型的地址值。或者将一个相同类型结构变员的地址值赋给指针
10、,或者将一个指向相同类型结构变量的指针赋给它。例如: struct card c1,*pc=&c1, struct card c1,*pc=&c1,*pd =pc, 给结构变量赋值,类似于给数组赋值,即给该结构变量的各个成员赋值,在给结构变量成员赋值时一般要求类型一致。例如: struct student s1,*ps; s1,name=Li Ping; s1. student -no=7602: s1. grade=B; PS一name-Zhang han PS一student一no= 8603 pe一grade=A; 另外,可将一个结构变量整个地赋值给另一个结构变量,但要求两者是同一个结构
11、模式的结构变量,这相当于给所有成员一次赋值。例如: struct card c1,c2=5,h; cl=c2; 这里,c1和c2是同一个结构模式cand的两个结构变量,并且c2已被赋了初值。可将c2赋值给c1,使得c1的各个成员的值与c2的相同。 给指向结构变量的指针斌值,可将某个相同类型的结构变量的地址值赋给它,也可以使用malloc ( )函数直接给指针分配内存空间。例如: struct student s1,*Pc; Ps=&s1; 或者 PS=(struct student,)malloc (sizeof (struct student); 这两种方法都将为指针Ps提供了地址值,第一种
12、方法,使指针P,指向结构变量s1.第二种方法是使用malloc( )函数直接在内存中分配一个大小为student结构模式的结构变量的大小的空间。 下面举一个给结构变量赋值的例子。 例8.1 结构变量赋值和输出部分成员值。 struct point float x2; struct point * next; ; main() struct point p1,p2, *top; top=&pl; p1.x0=1.2; top-x1=2.3; p1.next=&p2, p2.x0=一3.4; p1. next一x1=一4.5; p2. next=0; printf (%.2f,%,2fn ,p1.
13、 x0,pl. x1); printf (%.2f,%,2fn .p2. x0.p2. x1); 执行该程序输出如下结果: 1,2, 2. 3 一3.4,一4. 5 说明: (1)程序开始定义一个名为point的结构模式,它有两个成员:一个是float型数组,另一个是指向自身结构的指针。 (2)在main ( )内,定义了3个具有point结构模式的结构变量P1和p2以及指向结构变量的指针top. (3)分别给指针top和两个结构变量赋值。使top指向结构变量pi;又给结构变量pi的2个成员赋值,数组成员的两个元素值分别为1. 2和2. 3,指针成员获得结构变量p2的地址值;结构变量P2的两个
14、成员中,数组成员的两个元素分别获得- 3. 4和一4. 5,指针成员被赋值为0三个变量赋值情况如下图8.1所示: (4)主函数中又使用两个printf ( )函数的语句输出p1和p2结构变量中的x数组成员的两个元素值,它们是用结构变量的成员表示的。C语言基础教程 8.1.4 结构变量的运算 结构变量的整体上只有赋值运算,即将一个已被赋值(包含赋初值)的结构变量赋给相同类型的另一个结构变量。此外,结构变量的运算主要是指结构变量成员的运算。结构变量的成员具有该成员类型所允许的所有运算。 例8.2 结构变量的运算。 strut student char * name; long stu no; fl
15、oat math, float c一language; float engli5h; ; main() static struct student s1=Li jing,9705,89,5,90,0,80,5 s2=Ma li li,970,95,0,87,5,84,5; float ml,m2; ml= (s1. math+s1.c_language+s1. english )/3; rn2=(s2. math+s2.c_fanguage+s2. english )/3: printf(%s;t%.2fn,sl. name,ml); printf(%s;t%.2fn,s2, namem2):
16、 执行该程序输出结果如下; Li jing: 86.67 Ma li li: 89.00 说明:该程序中开始定义了两个结构变量81和S,并且赋了初值。在程序中对s1和s2两个结构变量的成员进行求和等计算。C语言基础教程 8.2.1 数组作为结构成员 下面是一个数组作为结构成员的例子。 struct student char name20, long student no; float score3; s2,s2.*ps 该结构模式中,有两个数组成员:一个是char型的一维数组name,有20个元素,另一个是float型的一维数组score,有3个元素。第一个数组成员可改为指针,即用char*n
17、ame;代替char name20 结构成员是数组时,该数组的各元素相当于该结构的成员。上例中,数组二ore各元素的表示如下:以s1结构变量为例。 s1.score0 sl.score1 s1.score2 可以直接给该数组的各元素赋值,如下所示: s1.score0=80.0; s1.score1=58.5; s1.score2=91.5; 对于具有数组成员的结构变量,可以赋初值,对数组成员的赋值方法与给数组赋初值方法相同。例如: struct student s2= (Wang Li,9705. 80. 0. 85. 5,91. 5 ; 这里,将一个字符串赋初值给char型数组name,将
18、由三个数据项组成的初始值表赋初值给float型数组score.具有数组成员的结构变量可按结构成员的办法输出数组成员的各元素。例如: printf(%cn,s2. nameo); 输出字符W printf(%,2fn,s2, scare2 : 输出浮点数91.50C语言基础教程 8.2.2 结构数组 结构数组在C.语言程序中使用较多。下面通过实例来说明结构数组的应用。 例8.3 给52张扑克牌赋值,并且将13张红桃输出显示。 程序是由主函数和三个被调用函数组成,三个被调用函数中assign_valueC)用来给扑克牌赋值,extract_value()用来对某张扑克牌析值,print_walue
19、( )函数用来显示输出某张扑克牌的花色与点数。每张扑克牌用下述结构表示: struct card int pips; char suit; 52张扑克牌用结构数组表示如下: struct card card52; 程序内容如下: struct card int pips; char suit; main) struct card card52; int i; for(i=O;i13;i+) assign.valuesl(card+i,i+1,c); assign_values (card+i+13,i+1,d). assign-values(card+i+26.i+1,h). assign-v
20、alues(card+i+39,i+1,s); for(i=O;ipipq=p; c_ptr一suit=s: extract value(c-ptr,p_.ptr,s_ptr) struct card *c ptr; Int*p_ptr Char *s_ptr *p_ptr=c_ptr-pips; *s_ptr=c_ptr-suit; Print_value(c_ptr) Struct card*c ptr Int p; Char s,*name; Extract_value(c.ptr,&p,&a) Name=(s=c)?clubs:(s=d?diamonds:(s=h) ?hearts(s
21、=s)?spades:error; Printf(ncard;%dof%s,p,name); 执行该程序输出如下结果: Card:1 of hearts Card:2 of hearts Card:3 of hearts Card:4 of hearts Card:5 of hearts Card:6 of hearts Card:7 of hearts Card:8 of hearts Card:9 of hearts Card:10 of hearts Card:11 of hearts Card:12 of hearts Card:13 of hearts 说明: 该程序中的card52
22、是一个结构数组,它有52个元素,每一个元素是一个结构变量,它是通过调用assign_value()函数来赋值的。 爱输出10张红桃牌时,调用了print_value()函数,在该函数中用调用了析值函数extract_value()用来将一张扑克牌的点数和花色分别存放在两个变量中,并将表示花色的单字符转换为该种花色的英语单词表示,然后输出显示在屏幕上。 在该程序中,由于多次进行函数调用,在调用了传址调用的方式,函数的形参用指向结构变量的指针,关于这一点后面还会详述。 例8.4 给定某个月的英文单词表示前3个字符,输出该月的天数,假定2月为28天。 程序内容如下: Struct ,onth Int
23、 number of day; Char name4; Main() ( Int i; Char*m; Static struct month months12= 31,Jan,28,Feb,31,Mar, 30,Apr,(31,May,30,Jun, 31,Jul,31,Aug,30,Sep; 31,Oct,30,Nov,31,Dec; Printf(Input months name(3characters):) Scanf(%s,m): For(i=0;i12;i+) If(strcmp(m,monthsi.name)!-0 Printf(%s;%dn,m,momthsi.number
24、of day) Hreak 执行该程序显示如下信息: Input months name(3 characters):May MAy:31 例8.5 分析下列程序的输出结果。 下列程序中,有结构数组a,还有指针结构数组p,即该数组的元素是指向结构变量的指针,该结构模式又是由指针成员的构成。从该程序中可以看出:指针可作为结构成员,指向结构变量的指针,又可作为数组元素,此例稍为复杂,为了便于分析,采用图解法。 程序内容: struct s1 Char *s; Struct s1*s1p; ; Main() Static struct s1 a=abcd,a+1 efgh,a+2,ijkl,a St
25、atic s1*p3; Int i; For(i-0;is,(*p)-s,(*p).s) Swap(*p,a); Printf(%st,p0-s); Printf(%st.(*+p0).s); Printf(%sn.+(*+(*p)-s1p.s); Swap(p1,p2) Struct s1,*p1,*p2 Char *temp; Temp=p1-s P1 s=p2- s; P2-s=temp 在分析输出结果前,先给出该程序中各个变量赋值后之间的相互关系。图解如图8.2所示: 执行该程序输出如下结果: Efgh efgh efgh Abcd abcd ijkl Ijkl abcd jkl 说明
26、: (1) 该程序较为复杂,其复杂性表现如下: .指针作结构成员。这里有一般的字符指针,还有指向自身结构的指针。 .结构数组。a是一个一维结构数组各。它有3个元素,每个元素是一个具有s1结构名的结构变量。该数组被赋初值。 .指针结构数组。程序中P是一个指针结构数组,P有3个元素,每个元素是一个指向结构的指针,它是通过for循环给赋值的,P【o】的地址值为a【0】。 slp, p【1】的地址值为a【1】。 slp.p2的地址值为a【2】。slp (2)根据数组a和P被赋值后的关系给出前面的关系图。 (3)主函数中两次调用:wap()函数。swap()函数的功能是用来交换两个指向结构变量的指针所指
27、向的结构变量的S成员所指向的字符串。第一次调用swap )函数时,实参用,*P,a,即将al的结构变量成员s所指向的字符串efgh与a0的结构变量成员s所指向的字符串abed进行交换。第二次调用,wap )函数时,实参用PQ,Pfl-slp,即将Pd指向的a1的结构变量成员S所指向的字符串abed与p0的slp成员所指向的结构变量a2的结构变量成员5所指n的字符串,ijkl进行交换。 (4)分析输出结果。 程序中第一个Printf ()函数输出结果是相同的,即为efgh.因为po一s,可表示为(*P十0)一s,即(*P)一S,而(*P)一s又可表示为(*p). s,所以,三个参数表达式是等价的
28、,都是al结构变量的s所指向的字符串。 第二个printf ( )函数输出中,前两个参数表达式是等价的,它们是经过第一次交换后,a1结构变量成员s所指向的字符串abcd.而第二个参数表达式(*p)一slp-s是a2结构变量的s成员所指向的字符串ijkl 第三、四、五个printf)函数输出是在第二个交换后进行的。P0一s是a1结构变量成员、所指向的字符串ijkl; (* +po). s等价于(+po一S,即pl一s.是a2结构变量成员S所指句的字符串abcd;+(* +(*p)一slp).s等价于+(+p0-slp)一s,其1,+po一slp是指al,因为这时p0已指向a2(前一个printf
29、()函数对p0进行了增1运算),a2一slp是a0再增1后为a1,这时十十a2一s是一个指向ijkl字符串中j字符的地址值,因此,输出为jkl.8.3.1 结构变量和指向结构变量的指针作为函数参数1.结构变量作函数参数 结构变量作函数参数时,形参和实参都要求是同一种结构模式的结构变量。函数调用时,系统将实参拷贝一个副本给形参,这种调用方式在被调用函数中无法改变调用函数的参数。下面是一个结构变量作函数参数的实例。 例8. 6 已知两个复数 1.0+i2.0和3.C+i4.0 求它们之和与积。 定义表示复数的结构如下:struct complexfloat re,im;程序内容如下:struct
30、complexfloat re,im;#define CMP struct complexmain()static CMP x=1.0,2.0,y=3.0.4.0;CMP zl,z2,add(0,mulitipiy()zl=add(x,y)z2=mulitipily(x,y);printf(%,2f+i%.2f)+(%.2f+i%.2f)=x,re,x,im,y,re,y,im);printf(%,2f+i%.2fn,zl,re,z,l,im);printf(%,2f+i%.2f)*(%.2f+i%.2f)=x,re,x,im,y,re,y,im);printf(%,2f+i%.2fn,z2,
31、re,z2,im);CMP add(x,y)CMP x,y;CMP zz.re=x.re+y.rez.im=x.im+y.im;return(z);CMP mulitipiy(x.y)CMPx,yCMP z;z,re=x.re*y.re-x.im*y.im;z.im=x.re*y.im+x.im*y.re;return(z); 说明:该程序由一个main()数和两个被调用函数add ( )和mulitiply ( )组成。两个被调 用函数的形参都是结构变量,结构变量可作为函数的参数。另外,这两个函数的返回值又都是结构变量,结构变量可作为函数的返回值,这种函数称为结构函数。2.指向结构变童的指针
32、作函数参数 结构变量可以作为函数的参数,前面举过的例子中已经出现。指向结构变量的指针作函数的形参,要求对应的调用函数的实参用相同类型的结构变量的地址值,实现传址调用,在被调用函数中可通过改变形参指针所指向结构变量的值来改变实参的值。另外,这种传址调用比用结构变量作函数参数实现的传值调用具有更高的效率。因为传值调用时,系统将拷贝实参的副本给被调用函数的形参,而传址调用时,只传递其地址值,特别是当结构变量比较复杂时,传值调用要花费较多的时间和占用较大的空间,因此效率较低。因此,在实际应用中较多地使用指向结构变量的指针作函数参数。 例8.7 已知今天额度日期(包含年、月、日)输出明天的日期。 程序内
33、容如下:struct ydateint day,month,year;)main()struct ydate today,tomorrow;printfEnter todays date(yyyy/mm/dd);n);scanf(%d/%d/%d,&today,year,&today,month,&today,day);if9today,day!=number_of_days(&today)tomorroew.day=+today,day;tomorrow.month=today.monthtomorrow.year=today.yearelse if(today.month=12)tomor
34、row.day=1tomorrow.month=1tomorrow.year=today.year+1printf9:(Tomorrows date is %d/%d/%dn,tomorrow.year.tomorrow.month.tomorrow.day);is_leap_year(pd)struct ydate*pdint leap_year=0if(pd-year%4=0&pd-year%1001=0)|pd-year%400=0);leap_yeyar=1;return(leap_year);number_of_days(pd)struct ydatw*pd;int day;stat
35、ic int days_ptr month13=0,31,28,31,30,31,30,31,31,30,31,30,31if(is_leap_year9pd)&(pd-month=2)day=29;elseday=days ptr monthpd-month;return(day) 执行该程序,提示如下信息: Enter todays date (yyyy/mm/dd) 1997/12/31 输出如下信息: Tommorrows date is 1998/1/1 说明:该程序中,在被调用函数is_leap_year()和number of_days()中都使用了指向结构变量的指针作函数形参,
36、调用时所对应的实参为相同结构类型的结构变量的地址值。在这两个函数中,使用指向结构变量的指针都不是为了改变调用函数的实参值,而是为了提高函数定义时传递的数据的效率。8.3.2结构变量和指向结构变量的指针作函数返回值 结构变量和指向结构变量的指针都可以用作函数的返回值。结构变量作函数返回值,该函数称为结构函数。1.结构函数 在前面讲过的例8.6中,出现了两个结构函数add()和mulitiply( ).这两个函数的返回值都是结构名为complex的结构变量。 结构函数在应用中也是常出现的,由于前面已举过例子,这里不再详述,2.指向结构变全的指针作函数返回值 函数的返回值可以是结构变量,也可以是指向
37、结构变量的指针。 下面举一个函数返回值是指向结构变量的指针的例子。 例8.8 查找某个学生的成绩。描述学生的结构模式如下: struct student char*names float score3; ; 其中,name用来存放学生的名字,score是存放学生三门功课成绩的float型数组。 程序内容如下:struct stuCfent(char*namefloat scoret3;stu5=Li,(90,87,83 Ma,98,85.78 ,Wang,96.89,87);Huang,(80.82.72,Zhang,88.75.68;main(0struct student *s1,*fin
38、d();s1=find(stu);printf(:%s:%.2f.%2f.2fn.s1-name.s1-score0.s1-score1,s1-score2;struct student *find(s)struct student s;int i;char namel20;printf(Input students name;);scanf(%s,namel):for(i-0;i5;i+)ifstrcmp(namel,si.name)=0)return(s+i); 执行该程序,显示如下信息: Input students name:Zhang Zhang:88.00,75.00,68.00
39、说明:该程序中,函数find()的返回值是一个指向结构变量的指针。它所返回的地址值在主函数中赋给结构变量指针s1.通过函数的返回值达到函数之间的信息传递。C语言基础教程 8.4.1 链表的概念 链表是一种常用的数据结构。它是一种动态地进行存储分配的数据结构。它不同于数组, 它不必事先确定好元素的个数,它可以根据当时的需要来开辟内存单元,它的各个元素不要求顺序存放,因此,它克服使用数组存放数据的一些不足。 链表是由若干个称为结点的元素构成的。每个结点包含有数据字段和链接字段。数字字段是用来存放结点的数据项的;链接字段是用来存放该结点指向另一结点的指针的。每个链表都有一个头指针,它是存放该链表的起始地址,即指向该链表的起始结点,它是识别链表的标志,对某个链表进行操作,首先要知道该链表的头指针。链表的最后一个结点,称为表尾,它不再指向