1
第 11章 结构体与共用体
2
11.1 概述
C 语言定义的几种基本数据类型,它们有规定的类型说明符、数据长度及数据组织和存储形式,程序设计时可直接用它们来定义数据对象。
在实际应用中仅有这些基本数据类型是不够的,为了增强数据的表示能力,能够使用各种数据,
常需要各种新的数据类型来满足问题求解的需要。
例如,一个传统的例子,
3
假定要建立员工的简单档案,为了反映每一个员工的基本情况,如果把员工的姓名、性别、年龄、
职称,工资、电话号码和家庭住址等不同类型的数据项分别用 单独的变量表示,就不能很好地反映与一员工相关的诸数据项之间的关系,且很难有效地组织、处理和使用它们 。
4
如果能把这些相关的数据项组织在一起,定义成一种新的、独立的数据类型,再定义一个实际的对象与之相联系,那么这个对象便代表了某个员工,
访问这个对象就可获得该员工的全部信息。显然这对组织、处理复杂的数据十分便利有效的。
为了能满足这类问题的需要,C语言允许用户自定义各种不同的数据类型,并用它们定义与之相关的对象。
本章将讨论 用户自定义数据类型的定义 及 相应数据对象的定义和使用 。
5
11.2 定义结构体类型及结构变量的方法
1,什么是 结构类型?
一个结构是将一个或多个不同类型的数据有序地组织在一起,并为之确定一个名字所构成的一种数据类型。
不同名的结构代表一种不同的数据类型,即使两个结构具有完全相同的成员项,但结构名字不同也表示两种不同的数据类型。
6
定义一个结构就是命名一个结构并说明它的组成情况。 目的是让程序中的函数知道,存在这样的一种数据类型,可用它来定义与之相关的数据对象。
结构的一般定义形式为:
struct 结构类型名 {
数据类型 1 成员名 1 ;
数据类型 2 成员名 2 ;
………
数据类型 n 成员名 n ;
} ;
7
其中,
struct
指出一个结构定义的开始 ;
结构类型名是对该结构类型的命名,按标识符的构成规则确定;
{…}
一对括号中定义了组成该结构类型的诸成员项,每个成员项由成员的名字、成员的数据类型和一个分号组成。 一个结构中成员项的多少、顺序没有限制。
8
C中固有的那些数据类型说明符,如 int,float、
char,double等;
也可以是本程序中已经定义的另一个结构类型名,因结构名是一种自定义数据类型名;
特别,“数据类型 i” 还可以是本身的结构类型名,
但其后的成员名只能是指针变量名,非指针变量名是错误的 。
数据类型 i
用来指出对应成员项的数据类型,它可以是任何数据类型名,
9
按标识符组成规则确定。,成员名 i” 和,结构类型名” 可与函数或程序中的其他对象名及其他结构中的成员名相同,不产生矛盾。C编译程序总是根据上,下文来区别它们。
成员名 i
结构定义形式是一个整体,在程序中是作为一个 定义语句 (声明语句 ) 出现的。因此括住诸成员项说明的一对大括号和最后的分号不能遗漏,否则引起错误。
10
又因为它是一个声明语句,所以一个结构定义可以在函数的内部和函数的外部进行。
最后必须强调,上述形式的结构定义仅是描述了一个结构的组成情况,或者说只是定义了一种类型为,结构类型名,的数据类型,C编译程序并不给这样的定义分配对应的空间,因为它不是一个实际的数据对象。
下面是一个结构定义的典型例子。
11
struct date {
int day ;
char month[4] ;
int year ;
} ;
该例定义了一个名为 date 的结构类型,它由 3个成员项组成:
成员 day,是一个 int 数据对象;
成员 month,是具有个 4元素的 字符型数组;
成员 year,也是一个 int 型数据对象。
12
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date birthdate ;
} ;
而对于结构类型定义:
该结构类型定义了如下的数据结构:
成员 的数据类型可以是先前已定义过的结构类型。
name salary tel sex birthdate
day month year
13
2,结构变量的定义定义了结构类型后,便可以象用C固有的数据类型关键字那样定义该结构类型的变量,其定义形式是:
struct 结构类型名 结构变量 1,结构变量 2,… ;
必须是前面已经定义过的结构类型名。如前面定义过的 date等例如,
struct date birthdate,nationalday,hiredate ;
C编译程序将为这三个 date 型的结构变量各分配如下的存储空间:
day month year
14
从上面的这个例子可以看出:
结构变量的存储空间分配是按照结构变量的诸成员项顺序进行的。 存储空间大小是各成员项所占空间之和。大小可通过 sizeof运算符对该结构名进行运算得到。 即有:
sizeof (struct 结构名 )
同一结构类型变量的存储空间大小都相同。
如 date结构变量的大小 sizeof(struct date)应为 8。
只有在定义了结构类型名之后才能定义结构变量。
15
这种 定义结构类型及对应的结构变量方法适用于程序中仅使用少数几个该类结构变量的情况。
struct {
int day ;
char month[4] ;
int year ;
} birthdate,nationalday,hiredate ;
C语言也允许定义 无名结构,即缺省结构类型名 。
例如:
16
main ( )
{
struct a {
int b,c ;
} ;
register struct a d ;
……
}
在定义结构变量时,也可以指定存储类型。但 不能定义 register 型结构变量,因为结构变量通常都占用较多的存储空间,在寄存器中存放不下。
例如,连续的、相同类型的成员项可以这样书写错误
17
struct a {
int b,c ;
} d ;
main ( )
{
struct a e ;
……
}
在函数外部定义的结构变量对应的结构类型的定义也必须在函数的外部被定义。
struct a d ;
main ( )
{
struct a {
int b,c ;
} ;
……
}
例如:
错误 ! 因 struct
a 结构类型是在 main函数内定义的。
这个是正确的因 struct a 结构类型是在 main函数外定义的。
18
结构变量也有局部与全局、临时与永久的特性。
总之,结构变量与用C固有的数据类型关键字定义的变量具有相同的性质。
例如:
struct a {
int b,c ;
} d ;
main ( )
{
static struct a e ;
……
}
外部全局结构变量 静态局部结构变量
19
11.3 结构变量的初始化与普通变量相同,C语言也允许在声明结构变量的同时对结构变量初始化。对结构变量的初始化实际上是对结构变量每个成员的初始化。
带有初始化值的结构变量的定义形式为:
struct 结构类型名 结构变量名 1 = {初始化值表 1 },
结构变量名 2 = {初始化值表 2 },
………
结构变量名 n = {初始化值表 n } ;
20
struct 结构类型名 {
数据类型名 1 成员名 1 ;
数据类型名 2 成员名 2 ;
………
数据类型名 n 成员名 n ;
}结构变量名 1 = { 初始化值表 1 },
结构变量名 2 = { 初始化值表 2 },
………
结构变量名 n = { 初始化值表 n } ;
或者,
21
当然在定义 无名结构类型 时也可以对同时定义的结构变量指定初始化值。例如:
struct {
数据类型名 1 成员名 1 ;
数据类型名 2 成员名 2 ;
………
数据类型名 n 成员名 n ;
} 结构变量名 1 = { 初始化值表 1 },
………
结构变量名 n = { 初始化值表 n } ;
22
其中 初始化值表 i 列出了 结构变量名 i 的各个成员的初始化数据。
这些数据项都必须是常量,且给出的数据个数、顺序、类型应与结构变量的成员的个数,顺序、类型一致,它们之间用逗号隔开。
C编译程序将把它们顺序赋给 结构变量名 i
对应的 成员名 i 。
下面几个例子给出了结构变量初始化的几种不同形式。
23
struct date birthdate = { 14,"sep ",1947 },
nationalday = { 1,"oct ",1949 },
hiredate = { 2,"dec",1970 } ;
示例 1:
struct {
float eletronic ;
int gas ;
int water ;
} paybill = { 286,3,140,33 } ;
示例 2:
24
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date birthdate ;
} person1 = { "Wang",2096.86,83594107,'F',
14,"sep",1950 },
person2 = { "Chen",3200.31,83594298,'M',
23,"jul",1937 },
person3 = { "Zhen",1663.45,83592623,'F',
18,"may",1963 } ;
示例 3,
25
11.4 结构变量的引用结构变量与数组在很多方面都是类似的,
(a) 它们的元素 /成员都必须存放在一片连续的存储空间中;
(b) 通过存取数组元素来访问数组,而对结构通过存取结构变量的成员来访问结构变量;
(c) 数组元素有数组元素的表示形式,结构变量的成员也有它的专用表示形式等等。
26
但是,结构变量与数组在概念上有重要区别:
(1) 数组名是数组元素存储区域的起始地址,
是地址量,而结构变量名只代表一组成员,它不是地址量;
(2) 数组中的元素都有相同的数据类型,而结构变量中的成员的数据类型却可以不相同。
结构变量成员的表示形式或访问形式一般是:
结构变量名?成员名
27
其中的,?” 号称为,结构成员运算符,,它有最高优先级,左结合性。 它用来连接结构变量名与成员名,具有,从属于,的含义,表示其后的成员名是前面结构变量中的一个成员。
如前面示例 2中的结构变量 paybill,它的三个成员分别表示为:
paybill?eletronic
paybill?gas
paybill?water
28
它是一个运算表达式,且是一个左值表达式 。
它具有与普通变量完全相同的性质,可以像普通变量那样参于各种运算,既可以出现在赋值号的左边向它赋值、也可以出现在赋值号的右边作为一个运算分量参于表达式的计算、也可以作读入数据存放的对象、当然可作为 ++,--等要求左值对象的运算符的操作数。
,结构变量名,成员名,,这样的结构变量成员的表示或访问形式有如下特点,
29
下面是一些表示、访问结构变量成员的的例子:
person2?salary -= 350.20 ;
paybill?gas++ ;
scanf (,%s,,hiredate?month ) ;
scanf (,%d”,&paybill?water ) ; /*可取成员的地址 */
printf (,%o\n,,person2?tel ) ;
30
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date birthdate ;
}person1={ "Wang",2096.86,83594107,'F',14,"sep",1950 },
person2={ "Chen",3200.31,83594298,'M',23,"jul",1937 },
person3={ "Zhen",1663.45,83592623,'F',18,"may",1963 };
31
main( )
{
struct {
int a,b ;
} c= { 1,2 } ;
int d = c.a ;
……
gets ( person3?name );
if ( strcmp (person3?hiredate?month,"Dec" )
{
……
}
person1?salary=person2?salary+person3?salary ;
leap=person3?birthdate?year%4==0&&
person3?birthdate.year%100!=0||birthdate.year=0;
}
结构变量的成员可以作为其他变量的初始化值
32
如果一个结构的一个成员又是另一个结构对象,
那么要访问嵌套结构变量中的某个成员项需要使用多个,.”运算符,直至最内层的成员项为止。例如:
person1,birthdate,year
因为,.” 号运算符具有最高优先级,且其结合性从左往右,所以这个表达式表示或访问的是
person1中的 birthdate结构型成员的成员项 year。
从上述例中可知:对结构变量的引用一般是针对结构变量的成员而不是指整个结构变量。
33
printf (,%f%d%d”,paybill ) ; /*错误 */
但允许对结构变量整体赋值,条件必须是同类型的结构变量。 例如:
person1 = person2 ; /*正确 */
允许取结构变量的地址,如 &paybill,该地址与它的第一个成员的地址相同。
与一般变量相同,结构变量的成员有了初值才可使用;否则其值无定义,不可使用。
除特殊情况外,不能用一个结构变量代替结构的全部成员。如不能将结构变量作为整体 I/O:
34
11.5 结构数组结构数组中的每一个元素都是同一个结构类型的对象。结构数组的定义与普通数组的定义在语法上完全一样。也可以定义多维结构数组。
struct 结构类型名 数组名 1 [元素个数 1],…… ;
定义形式 1,结构类型先前已被单独定义的情况下面以一维数组为例讨论结构数组的定义。
35
struct 结构类型名 {
…… /* 成员项定义 */
} 数组名 1[元素个数 1],数组名 2[元素个数 2],… ;
定义 形式 2:结构类型与该类型的数组同时定义的情况定义 形式 3,缺省结构类型名的结构数组定义的情况
struct {
…… /* 成员项定义 */
} 数组名 1[元素个数 1],数组名 2 [元素个数 2],… ;
36
struct stock {
char *negotiable_securities_name ;
float opening_price ;
float closing_price ;
float highest_price ;
float lowest_price ;
float rise_and_fall ;
long volume_of_business ;
} ;
下面是关于股市行情的一个结构类型:
37
假定当前有 200种证券,为了反映每天的股市行情,较好的办法就是建立一个与 stock结构类型相关的结构数组:
struct stock current_prices_of_stock [ 200 ] ;
在该定义中,结构数组 current_prices_of_stock
中包括 200 个数据类型为 stock的数组元素,每个元素相当于一个 stock 结构类型 的变量。
C编译程序将分配给该数组 200个 stock 型对象的连续的存储空间,分别用于存放该数组中的每一个元素。该结构数组在这片连续的存储区域中的安排顺序如下图所示:
38
39
与定义结构变量相同,也可以在定义结构数组时对其进行初始化。带有初始化值的结构数组的定义和定义普通数组的形式与要求类似,几种形式是:
struct 结构类型名 数组名 [元素个数 ]={ 初始化值表 },…;
struct 结构类型名 数组名 [ ] = { 初始化值表 },…… ;
struct 结构类型名 {
…… /*成员项定义 */
} 数组名 1 [元素个数 ]={初始化值表 1},
数组名 2 [ ]={初始化值表 2},…… ;
40
struct {
…… /*成员项定义 */
} 数组名 1 [元素个数 ] = { 初始化值表 1 },
数组名 2[ ] = { 初始化值表 2 },
…… ;
其中,
,初始化值表 i”中列出的初始化值对应于每一个结构数组元素中的每一个成员项。
41
C语言也允许把对应于每个结构数组元素的初始化值括在一对大括号中:
{
{……},/*对应于结构数组元素 0中的所有成员 */
{……},/*对应于结构数组元素 1中的所有成员 */
…
{……} /*对应于结构数组元素 n-1中的所有成员 */
} ;
当然,当结构数组元素的成员较少且比较简单时,
不使用内部大括号括住给出的初始化值也是允许的。
在定义带有初始化值的结构数组时也可以不指定数组中元素的个数(,[ ]” 中为空)。
42
struct stock current_prices_of_stock[200] = {
{ "Qingdaobeer",4.0,4.05,3.94,3.99,-0.11,206900 },
{ "Nanjingstore",4.03,4.05,3.99,3.96,-.12,327300 },
{ "Yongshenpen",10.95,11.99,10.90,11.27,0.17,218300},
………………
{ "Yuyuanstore",17.30,17.59,17.20,17.20,-55,229720 }
} ;
下面是带有初始化值的 current_prices_of_stock
结构数组的定义:
43
定义了结构数组之后,访问结构数组元素成员的要求、限制与访问结构变量是一样的。一个结构数组元素的某个成员的表示及访问形式是:
结构数组名 [i],成员名例如:
current_prices_of_stock[0],rise_and_fall
将表示、访问 current_prices_of_stock 这个结构数组的第 0 个元素的成员 rise_and_fall 。
44
结构数组应用例,
该例对 给出的数据 实现 Excel中的分类汇总、求平均值功能。
struct score {
char *name ;
unsigned long number ;
char department[20] ;
float math ;
float english ;
float average ;
} ;
先定义反映成绩表的数据结构:
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main( )
{
struct score score_table[10],temp ;
char name[20],department[20] ;
int i,j,min,k=0 ;
float math_sum,english_sum,average[6] ;
FILE *fp;
scanf ( "%f",&math_sum ) ;
fp=fopen ( "data.in","r" ) ;
46
for ( i=0; i<10; i++ ) {
fscanf ( fp,“%s”,name ) ;
fp 表示从文件 data.in中读数据
score_table[i].name=(char*)malloc(strlen(name)+1) ;
if ( !score_table[i].name ) {
puts ( "Memory allocation failure!" ) ;
exit (1) ;
}
strcpy (score_table[i].name,name ) ;
fscanf(fp,"%lu%s%f%f ",&score_table[i].number,
score_table[i].department,
&score_table[i].math,
&score_table[i].english) ;
}
/*从文件中读数据 */
47
for ( i=0; i<9; i++ ) {
min=i ;
for ( j=i+1; j<10; j++ )
if ( strcmp ( score_table[j].department,
score_table[min].department ) <0 ) min=j ;
temp=score_table[i] ;
score_table[i]=score_table[min] ;
score_table[min]=temp ;
}
/*按院系名,将结构数组按字典顺序排序 */
48
for ( i=0; i<10; i++ )
score_table[i].average=(score_table[i].math +
score_table[i].english)/2 ;
printf (“\t%-15s%-6s%15s%10s%10s%10s\n\t”,
“Name”,“Number”,
“Department”,“Math”,
“English","average” ) ;
for ( i=0; i<66; i++ ) printf (,%c”,196 ) ;
printf ( "\n" ) ;
for ( i=0; i<10; i++ ) {
math_sum=0 ;
english_sum=0 ;
j=0 ;
strcpy ( department,score_table[i].department ) ;
某系的学生计数
/*计算平均值 */
/*显示输出表头 */
49
while ( i<10 &&
!strcmp ( department,score_table[i].department)) {
printf(“\t%-15s%05lu%16s%10.1f%10.1f%10.1f\n”,
score_table[i].name,
score_table[i].number,
score_table[i].department,
score_table[i].math,
score_table[i].english,
score_table[i].average ) ;
math_sum+=score_table[i].math ;
english_sum+=score_table[i].english ;
j++ ;
i++ ;
}
50
math_sum=0 ;
english_sum=0 ;
for( i=0 ; i<k ; i+=2 ) {
math_sum+=average[i] ;
english_sum+=average[i+1] ;
}
printf ( "\t%23sTotal Average %9.1f %9.1f\n",
' ',math_sum/3,english_sum/3 ) ;}
average[k]=math_sum/j ;
average[k+1]=english_sum/j ;
printf ( "\n\t%28s Average %9.1f %9.1f\n\n",
department,average[k],
average[k+1] ) ;k+=2 ;
i-- ;
} ;
51
52
11.6 指向结构变量的指针变量
C 语言允许定义指向结构变量的指针变量,并用它来访问它所指向的结构变量。
1,结构指针变量的声明声明结构指针变量的一般形式:
struct 结构类型名 *结构指针变量名 ;
,结构类型,前面必须已定义过。
,*” 不能遗漏,否则就变成了结构变量定义。
例如:
53
struct date *pd ;
结构指针变量也可以在定义结构类型名时同时定义,并可同时为其指定初始化值。 例如:
struct date {
int day,month,year ;
} birthday,*pd,*pq=&birthday ;
如果结构类型名 date 先前已被单独定义,那么下面的定义与上述定义具有同样的效果:
struct date birthday,*pd,*pq=&birthday ;
例如:
54
一旦定义了结构指针变量,且已使它指向了一个结构变量,那么便可以用这个结构指针变量来存取它所指向的结构变量中的成员,使用 形式是:
( *结构指针名 ),成员名如 访问 birthday 中的三个成员:
( *pq ),day
( *pq ),month
( *pq ),year
2、用 结构指针变量表示与访问结构成员注意:
*pq 两边的小括号是必须的,因为结构成员运算符,,” 的运算优先级高于,*” 运算符。
55
像 ( *pq ),year 这样的引用结构变量中的成员的方式既不直观且易写错,所以 C 语言另外又提供了一种新的运算符,->” (一个,-” 号和一个,>”
号),专用于使用指针变量访问结构对象中的成员 。
例如:
结构指针 变量 名 ->成员名这样,访问变量 birthday中的成员便可写成:
pq->day 等价于 (*pq),day
pq->month 等价于 (*pq),month
pq->year 等价于 (*pq),year
,->” 和,,” 的优先级和结合性相同。
56
如果结构指针变量 p指向结构变量 employee,结构指针变量 q 是 employee的一个成员、且 year是 q 指向对象的一个成员,那么成员 year的表示形式是:
p -> q -> year
但如果 employee是结构变量,birthday是 employee
的一个结构变量成员,year又 是 birthday的一个成员,
那么成员 year只能按如下 形式表示:
employee,birthday,year
在这种情况下,其中的,,” 运算符不能用,->” 来替代。因为 employee 和 birthday不是结构指针变量。
57
struct date {
int day ;
char *month ;
int year ;
} birthday = { 14,"sep",1947 } ;
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date *q ;
} person1={“Wang”,2960.81,83594107},*p=&person1 ;
为了正确用好 -> 运算符,请研究下面的例子,
这里仅给出前三个成员的初始化值
58
#include <stdio.h>
main( )
{
p->q = &birthday ;
p->sex = 'M' ;
printf ( "Name,%s\nSex,%c\n "
"Date of birth,%2d,%s,%4d\n "
"Tele,No,%ld\n ",
p->name,p->sex,p->q->day,
p->q->month,p->q->year,p->tel ) ;
}
59
Name,Wang
Sex,M
Date of birth,14,sep,1947
Tele,No,83594107
该例运行后的输出是,
由于,->”,,,”,,( )”,,[ ]” 之类的运算符具有相同的运算优先级和结合性,所以当它们被组合在一起使用时难于分析与理解。
假定有如下的声明:
60
main( )
{
int a = 4 ;
struct {
int x ;
int *y ;
} z,*p=&z ;
z · x = 10 ;
z · y = &a ;
……
}
61
则执行:
*p->y 对 p->y 的内容作间接访问,即访问 a得到 4
*p->y++ 先执行 *p->y 取得 4,
然后使 p->y加 1,使得
z.y 的值变成 FFD4,
指向 z.x
*++p->y 先使 p->y 加 1,使得 z.y 的值变成 FFD4,
指向 z.x,然后再执行 *p->y,因 此时
p->y 已指向 z.x,所以获得 10
*p ++->y 先执行 *p->y 取得 4,然后使 p加 1,使
P 指向 FFD8( 即 z′)
62
则执行:
(*p->y)++ 先执行 *p->y,取 a的值
4,然后再使得 (*p->y)
加 1,即使 a的值为 5
++*p->y 与 ++(*p->y)等价,使
(*p->y)加 1 → *p->y,
即使 a为 5
++*p->y++ 先执行 ++*p->y,即使 a为 5,然后再使得
p->y加 1,使 p->y变成 FFD4 指向 x
*(p->y++) 这里括号无用,因 ++在后。先执行 *p->y,
然后使 p->y加 1,使 p->y变成 FFD4指向 x
63
3、结构指针变量作函数参数在函数间传递结构变量有如下几种办法:
1) 传递结构变量的成员
2) 传递整个结构变量本身
3) 传递结构变量的地址例 1:把结构 变量 的成员传递给函数
struct employee {
long no_of_ss ;
char name[20] ;
float salary ;
} ;
64
#include <stdio.h>
main ( )
{
void bill ( long no,float salary ) ;
struct employee person ;
scanf ( "%ld%s%f",&person,no_of_ss,
person,name,&person,salary );
bill ( person,no_of_ss,person,salary ) ;
printf ( "%ld %s %f\n",person,no_of_ss,
person,name,person,salary );
}
结构变量的成员作实参传递给函数 bill
65
void bill ( long no,float salary )
{
salary += 300 ;
printf ( "%ld\t%f\n",no,salary ) ;
}
person,no_of_ss person,salary
66
在这个简单的例子中,函数间的数据传递采用赋值方式把结构变量的成员传递给被调用函数对应的形参,函数中对形参的改变并不影响对应的实参的值。 如函数中对 salary的改变并不影响对应的实参 person.salary的值。
当一个结构变量中的成员很多时采用这种数据传递方式是不现实的,不但要求很多参数,而且程序显得冗长,还易出错。这可以改用直接传递整个结构变量的办法进行:
67
main( )
{
void bill ( struct employee p ) ;
struct employee person ;
scanf ( "%ld%s%f",&person,no_of_ss,
person,name,&person,salary);
bill ( person ) ;
printf ( "%ld %s %f\n",person,no_of_ss,
person,name,person,salary ) ;
}
void bill ( struct employee p )
{
p,salary += 300 ;
printf ( "%ld %s %f\n",p,no_of_ss,
p,name,p,salary ) ;
}
例 2,传递结构变量 本身结构变量实参传递给函数 bill
68
改写后的这个例子直接把 实参 结构变量 赋给 函数对应的 形参 结构变量。
这种办法一次向函数传递了整个结构变量的全部成员,传递仍然是单向的,亦即被调用函数中对 形参结构变量成员的修改仍然不影响对应的实参结构变量的成员值。因为,person 和 p各占用自己的存储空间。
即有:
no_of_ss
name
salary
no_of_ss
name
salary
person p
69
函数间传递结构变量必须保证形参和实参的类型匹配,它们必须是同一结构类型,否则将会产生错误。
直接传递结构变量时,调用函数与被调用函数双方都必须定义同结构类型的变量,要使用双倍的结构变量的存储空间,而要实际传送全部成员数据,
既花空间又费时间,因此这不是值得提倡的办法。
在函数间传递结构对象,通常采用传递指向结构对象指针的办法来实现,这不仅方便而且高效。
70
例 3,传递结构变量的地址
#include <conio.h>
#include <dos.h>
struct tm {
int h ;
int m ;
int s ;
} ;
因程序中调用了 clrscr()函数因程序中调用了 sleep()函数
71
main( )
{
void update ( struct tm *t ) ;
void display ( struct tm *t ) ;
struct tm time ;
printf (,Enter current time”
,in hours:minutes:second),) ;
scanf (,%d:%d:%d,,
&time,h,&time,m,&time,s ) ;
for ( ; ; ) {
update ( &time ) ;
display ( &time ) ;
}
}
结构变量的地址作实参
72
void update ( struct tm *t )
{
t->s++;
if ( t->s == 60 ) {
t->s = 0 ;
t->m ++ ;
}
if ( t->m == 60 ) {
t->m = 0 ;
t->h ++ ;
}
if ( t->h == 24 )
t->h = 0 ;
sleep ( 1u ) ; /*暂停执行 1秒钟 */
}
&time
73
注,sleep( )的原型,void sleep ( unsigned seconds ) ;
功能:暂停执行当前程序指定的秒数。
clrscr( )的原型,void clrscr ( void ) ;
功能:清除当前窗口,将光标定位到左上角。
void display ( struct tm *t )
{
clrscr ( ) ; /* 清除当前窗口 */
printf ( "%d:%d:%d\n",t->h,t->m,t->s ) ;
}
&time
74
例 4 在函数间传递结构数组该例用来统计一个C语言源程序文件中每一个C语言关键字的使用次数。
被统计的C语言源程序文件从键盘上直接输入 ( 即边输入边统计 ),按 Ctrl-Z 键(在 DOS 或
Windows中)或 Ctrl-D 键(在 UNIX中)结束输入。
75
struct key {
char *keyword ;
int keycount ;
} keytab[ ]={
{ "break",0 },
{ "case",0 },
{ "char",0 },
{ "continue",0 },
……
{ "unsigned",0 },
{ "while",0 }
} ;
关键字名称关键字使用次数按字典顺序排列的关键字表
76
#define MAXWORD 20
#define NKEYS (sizeof(keytab)/sizeof(struct key))
#include <stdio.h>
#include <string.h>
#include <ctype.h>
main( )
{
int n,t ;
char word [MAXWORD] ;
计算关键字表中数组元素的个数假定关键字最长为 20个字符用于判断当前读入的标识符是否是关键字
77
while((t=getword(word,MAXWORD))!=EOF)
if(t==1)
if((n=binary(word,keytab,NKEYS)) >= 0)
keytab[n],keycount++ ;
for(n=0 ; n<NKEYS ; n++)
if(keytab[n],keycount>0)
printf ( "%4d %s\n",\
keytab[n],keycount,keytab[n],keyword );
}
78
getword ( char *w,int limit )
{
int c,t ;
if ( type ( (c=*w++=getchar( )) ) != 1) {
*w='\0' ;
return (c) ;
}
while ( --limit>0 ) {
t=type ( c=*w++=getchar( ) ) ;
if ( t != 1 && t != 0 ) {
ungetch (c) ;
break;
}
}
*(w-1)='\0';
return ( 1 ) ;
}
把字符退回到 stdin
79
type ( int c )
{
if ( isalpha (c) )
return(1) ;
else if ( isdigit (c) )
return ( 0 ) ;
else
return (c) ;
}
/*如果是字母字符返回1 */
/*如果是数字字符返回0 */
/*如果是其他字符返回原字符 */
80
binary( char *word,struct key keytab[ ],int n )
{
int low,high,mid,cond ;
low=0 ;
high=n-1 ;
while ( low<=high ) {
mid=(low+high)/2 ;
if((cond=strcmp(word,keytab[mid].keyword))<0)
high=mid-1 ;
else if ( cond>0 )
low=mid+1 ;
else
return(mid) ;
}
return ( -1 ) ;
}
81
例 5 返回结构变量一个函数也可以返回结构型的数据,条件是该函数必须定义成返值为特定结构类型的函数 ;
当然接收该函数返回值的对象也必须是该结构型的变量。
82
#include <string.h>
struct namect {
char fname [20],lname[20] ;
int letters ;
} getinfo(void),makeinfo(struct namect ) ;
void showinfo ( struct namect ) ;
main( )
{
struct namect person ;
person=getinfo( ) ;
person=makeinfo ( person ) ;
showinfo ( person ) ;
}
83
struct namect getinfo (void)
{
struct namect temp ;
printf (,Please enter your first name:\n ),;
gets( temp,fname ) ;
printf(“Please enter your last name:\n)”;
gets ( temp,lname ) ;
return temp ;
}
struct namect makeinfo ( struct namect info )
{
info,letters = strlen (info,fname) +
strlen( info,lname);
return info ;
}
84
程序运行过程中的 I/O:
Please enter your first name:
Huailing
Please enter your last name:
Wang
Huailing Wang,your name contains 12 letters.
void showinfo ( struct namect info )
{
printf(“%s %s,your name contains %d letters.\n”,
info,fname,info,lname,info,letters ) ;
}
85
11.7 用指针处理链表在讨论结构变量定义时曾指出:组成结构的成员项可以是任何数据类型。
那么,一个结构中的成员项当然可以是另一个结构类型的变量,或指向另一个结构对象的结构指针变量,甚至还可以是指向本结构对象的一个结构指针变量。
如果一个结构中的某个成员是另一个结构变量,这样的结构称之为 嵌套结构 。
86
而如果一个结构的成员项是本结构类型的一个结构指针变量,那么把这样的结构称之为,自引用结构,。
struct node {
int data ;
struct node *next ;
} ;
例如:
87
下面的结构定义是错误的:
int main( )
{
struct s {
int x ;
struct s y ;
} k ;
k,x=5 ;
printf (,%d\n”,k,x ) ;
}
/* 这种情况的成员名只能是指针变量名 */
88
上述 node结构的直观表示:
这是一种单向链表数据结构。链表中的元素称之为,结点,(node),个数可以有任意多个。
data data data data
next next next next ……
89
这种动态数据结构相当于结构数组,但是它要比结构数组优越:
首先,结构数组中的元素要连续存放,而链表则不必;
其次,数组中的元素个数是确定的,而链表中的元素个数却没有限制。因此在实际使用中如果元素个数不确定,特别是需要动态增加元素的情况使用链表更合适;
最后,C编译程序对数组必须给其分配存放它的全部元素的存储空间,而对链表则 不必也不可能预先分配全部存储空间,因为C编译程序无法确定链表中 结点 的个数。
90
用自引用结构实现链表有三个问题要解决:
第一,必须指出链表第一个结点的位臵,否则无法存取该链表中的结点。这很容易做到:
只要定义一个指向这个结构对象的指针变量,
使其指向链表的第一个结点。 例如:
struct node *p ;
第二,在建立一个链表时,如何获得下一个新结点的存放空间?
这可以调用内存分配库函数 (结构类型
*)malloc (sizeof(结构类型 ) ) 达 到。
91
struct node *p1,*p2;
……
p2=(struct node*)malloc(sizeof(struct node)) ;
if(p2==NULL) /* 分配成功吗? */
exit(1) ;
p1->next=p2 ; /*假定 p1已指向新结点的上一个结点 */
……
程序中只要把由 malloc函数返回的地址赋给上一结点的成员项 next,便链接到下一个结点的存储位臵。
处理过程应该是:
92
第三,要指出链表的链尾。
这个处理比较简单,通常只要把最后结点中的成员项 next臵为空指针即可。
一般来说,对链表有如下基本操作:
1)建立链表
2)遍历链表
3)插入结点
4)删除结点
5)更新结点数据
6)按结点的某个成员项对链表排序
7) 归并与连接链表
93
一般而言,一个 链表 结点中包含两类成员域:
数据成员域 和 链接成员域 。
数据成员域包含的数据个数、类型、顺序位臵没有限制;但链接成员域 至少包含一个用于链接的指针型成员,且该指针型成员的基类型必须为 链表结点的结构类型。
数据成员域链接成员域
struct node{
long num ;
char name[10] ;
struct node *next ;
};
1,定义 链表 结点的结构数据类型例如:
94
建立一个无序新链表比较简单:新结点总可以插在链表前面;或总是插在链表后面;
建立一个有序新链表 ( 按数据域中某个成员排序 ),稍复杂一些,需要搜索、比较链表的结点,确定插入位臵。
下面的函数根据给出的结点数 number建立一个无序新链表。
2、建立链表
95
struct node *create( int number)
{
struct node *current,*front,*head= NULL ;
for( ; number>0 ; number--) {
current=(struct node *)malloc(sizeof(struct node ));
scanf("%ld%s",¤t->num,current->name);
if ( head==NULL )
head= front =current ;
else
front->next=current ;
front=current;
current->next=NULL;
}
return head;
}
96
void display ( struct node *head )
{
if ( head==NULL )
printf (,The list is empty!,) ;
else
while ( head != NULL ) {
printf(“%ld%s\n”,head->num,head->name);
head=head->next ;
}
}
3,遍历链表在链表中查找指定的数据,显示链表全部结点中的数据,统计链表中的结点数等 操作都需要 遍历链表操作。
下列函数 显示指定链表全部结点中的数据 。
97
struct node* clear ( struct node *head )
{
struct node *p=head ;
while ( p!=NULL ) {
head=p->next ;
free ( p ) ;
p=head ;
}
return NULL ;
}
4,清空链表清空链表意味着归还全部结点占用的存储空间,
并非取消链表。因此操作完成后应将指向该链表的指针变量臵为 NULL( 空链表)。
98
struct node *delete(struct node *head,long num)
{
struct node *current=head,*front ;
if ( head==NULL )
return head ;
else if(head->num==num){/*要删除的是第一个结点 */
head= head ->next ;
free (current ) ; /*开始时保存了 head */
return head ;
}
else
5,删除一个结点
99
do { /*要删除的结点在链表中间和最后 */
if ( current->num==num ) {
front->next=current->next ;
free ( current ) ;
return head;
}
front=current ;
current=current->next ;
} while ( front->next != NULL ) ;
return head; /*无 要删除的结点 */
}
current front
ado ado
ad1 ad1
ad2
100
情况 1,第一个结点就是要删除的结点
10 20 30 40
(ad1)
next next 0
zhao qian sun li
next
(ad2) (ad3)
ad1 ad2 ad3ad0ad0head
101
情况 2,要删除的结点在链表中间和最后。
10 20 30 40
(ad1)
next next 0
zhao qian sun li
next
(ad2) (ad3)
ad1 ad2 ad3ad0ad0head
102
struct node * insert (struct node *head,struct node *new )
{
struct node *current,*front ;
new->next=NULL ;
if(head==NULL) /* 原链表为空 */
head=new ;
else{
current=head;
while(current->num<=new->num &&
current->next!=NULL) {
front=current ;
current=current->next ;
} /*查找插入位臵 */
6,插入结点 /*假定链表已按成员 num从小到大排过序 */
103
if ( current->num < new->num ) /*插到末结点之后 */
current->next=new ;
else if ( current==head ) { /*插到首结点之前 */
new->next=head ;
head=new ;
}
else{ /*插在链表中间,总是插在找到的位臵的前面 */
new->next=current;
front->next=new;
}
}
return head;
}
104
struct node {
int data ;
struct node *next ;
} ;
放入链表各结点成员 data 中的数值按从小到大次序排列;向链表中插入,或从链表中删除一个结点仍保持链表的这种排序特性。
例:按 链表结点某个成员的值,建立 /处理有序链表。
链表结点的结构定义如下:
105
void addx (struct node **head,int x)
函数 findpoint 的第一个形式参数 head 是指向链表的首指针,第二个形式参数 x 为待插入链表结点中成员 data的值。
该函数用来确定在链表中的插入点,并返回插入点的指针。该指针指向的结点成员 data 的值是链表中小于 x 值中的最大者。
若链表为空,或给出的 x 小于链表第一个结点的
data的值,则该函数返回一个空指针。
下面给出处理该链表的两个有关函数,
struct node * findpoint(struct node *head,int x)
和
106
函数 addx 功能是在指定的链表中插入成员 data
的值为 x的结点。
其中:
第一个形式参数 head 是指向链首的指针变量的地址 ;
第二个形式参数 x 为待插入结点成员 data的值。
注意:
该函数是 void形函数,函数的返回值是通过改变实参的值而实现的。
107
struct node *findpoint ( struct node *head,int x )
{
struct node *q = NULL,*p = head ;
if ( head == NULL )
return NULL ;
while ( p != NULL && p->data < x ) {
q = p ;
p = p->next ;
}
return q ;
}
108
void addx (struct node **head,int x)
{ struct node *findpoint(struct node*,int),*r,*q ;
r=(struct node*)malloc(sizeof(struct node));
r->data=x ;
if((q=findpoint(*head,x))==NULL)
if(*head==NULL){ /*空链表 */
r->next=NULL ;
*head=r ;
}
else{ /*链表不空,但 x < 第一个结点的 data值 */
r->next=*head ;
*head=r ;
}
else{ /*插到 q的后面 */
r->next=q->next ;
q->next=r ;}
}
109
结构体的其他使用注意事项:
①,成员名 i”和,结构类型名,可以与函数或程序中的 其他对象名及其他结构中的成员名相同,不会产生矛盾。
② 不能有相同的成员名字。
1,关于结构类型定义
struct 结构类型名 {
数据类型 1 成员名 1 ;
数据类型 2 成员名 2 ;
………
数据类型 n 成员名 n ;
} ;
110
③ 定义成员时只能有,数据类型,部分,不能使用存储类型关键字。
④ 允许按如下形式定义结构类型:
struct {
double k ;
struct {
int a,b ;
} c ;
float d ;
} val={ 3.14,100,200,2.71 },*p=&val ;
111
struct {
double k ;
struct abc {
int a,b ;
} c ;
float d ;
};
main( )
{
struct abc k={ 100,200 } ;
printf ( "%d\n",k.a ) ;
}
甚至于允许:
112
11.8 共用体( 联合)
联合可以被理解为这样的一种数据类型,
在该类型对应的变量中,不同的时刻可以存储不同类型的数据。简言之,允许利用同一存储区域来存储、处理不同类型的数据。
联合数据类型的定义在语法上与结构类型定义类似,一般形式为:
union 联合类型名 {
数据类型 1 变量名 1 ;
数据类型 2 变量名 2 ;
……
数据类型 n 变量名 n ;
} ;
113
union uval {
int iobj ;
double dobj ;
char cobj ;
} val ;
例如:
注意:
① 上面定义的 uval 联合型变量 val 的三个成员:
val,iobj
val,dobj
val,cobj
114
C 编译程序并不是给它们各自分配一个独立的存储空间,而是按这三个成员中具有最大存储长度的那个成员,分配相应大小的存储空间给变量 val,
让这三个成员使用同一个存储区,因此它们的存储起始地址都是相同的。
当访问 val.iobj时,把这个空间中的前两个字节的内容作为 int 型据解释;当访问 val.dobj时,把这个空间中的内容作为 double型数据来解释 ;同理,
当访问 val.cobj时,把这个空间中的第一个字节当作为 char型数据来解释。
115
因此,程序设计者要清楚当前联合型变量中究竟存储的是哪一个成员的数据,才能正确存取联合变量中的内容。
② 在定义一个联合类型时,联合中的成员项可以是任何类型的数据对象,当然也可以是联合类型的变量。
当前存入的某个成员,总是冲掉该成员所作用的存储区中内容例如:
116
union uval {
int iobj ;
double dobj ;
char cobj ;
} ;
struct {
char *p ;
int i ;
union uval val ;
} struarray[10] ;
117
struct {
char *p ;
int i ;
union {
int iobj ;
double dobj ;
char cobj ;
} val ;
} struarray[10] ;
上例甚至于可以直接写成:
118
按上面的定义,则 struarray[5],val,iobj 即访问结构数组 struarray 中序号为 5 的元素中的联合成员变量 val的成员 iobj。
同理,对于:
struct use {
int i ; float f ;
} ;
union exam { struct use tag ;
char ch ;} p ;
则 p,tag,i 用来访问联合变量 p中的 use 结构型成员 tag中的成员项 i 。
119
③ C语言也允许定义联合型指针。
main( )
{
union {
int i[2] ;
long k ;
char c[4] ;
} r,*s=&r ;
s->i[0] = 0x39 ;
s->i[1] = 0x38 ;
printf (,%c\n”,s->i[0] ) ;
}
程序输出,9
例如,
120
假定某校学生会组织了一次向,希望工程,捐书的活动,组织者设计了如下的一张表格用来登记捐书者的有关情况:
其中,职业按工、农、商、学、兵分类,并利用字母 W,P,B,S,A 分别代表之。由于军人无工作单位,所以对军人而言,工作单位,栏填写的是该军人所在的部队番号 。
联合型数据的使用例:
姓名 职业 捐书量 工作单位/部队番号
121
#define MAXSIZE 888
main( )
{
union unit {
long n ;
char serv[30] ;
} ;
struct person {
char name[20] ;
char job ;
int books ;
union unit punit ;
} record[MAXSIZE] ;
int i,j ;
122
for( i=0 ; i<MAXSIZE ; i++){
printf (,Enter data please:” ) ;
scanf (,%s%1s%d”,record[i].name,
&record[i].job,&record[i].books ) ;
if ( record[i].books == 0 )
break;
else if ( record[i].job ==?A? ) {
printf (,Enter designation:” ) ;
scanf (,%ld,,&record[i].punit.n ) ;
}
else {
printf (,Enter unit of service:” ) ;
scanf (,%s”,record[i].punit.serv ) ;
}
}
}
123
printf (,\n%-20s%6s%6s%6s\n”,
“name”,“job”,“books”,“unit” ) ;
for ( j=0 ; j<i ; j++) {
printf (,%-20s %6c %6d”,
record[j].name,record[j].job,record[j].books ) ;
if ( record[j].job ==?A? )
printf (,%ld\n”,record[j].punit.n ) ;
else
printf (,%s\n”,record[j].punit.serv ) ;
}
}
124
定义联合类型变量时,也可以指定初始化值。 若指定 初始化值,只能有一个初值,且这个值只能是作为第一个成员的初始化值。 当然,初始化值的数据类型也必须与第一个成员的数据类型相同。
例如:
union uval u1=32767 ;
定义、使用联合型数据时应注意:
当计算联合变量大小时,总是按最大成员的存储长度计算。但是若计算其中的某个成员的长度,则是按成员的长度计算。
例如:
125
main( )
{
union ucal {
int a ;
char b ;
double c ;
} a ;
a.a = 100 ;
printf (,%d,%d\n”,sizeof a,sizeof ( a.a ) ) ;
}
程序的输出将是:
8,2
126
11.9 枚举类型枚举是 C提供的 一种数据类型 。可以把这种数据类型作为符号常量的一个集合来理解。
枚举类型实际上是 C中的另一种整数类型。 因此,C中作为整数处理的数据类型有三种,整型,字符型,枚举型 。
如果一个变量仅能取几种可能的值,那么可以把这样的变量定义成为枚举型变量。枚举型变量所能取的值,即是它对应的符号常量集合中符号常量的值。
枚举类型对象的定义有三种形式:
127
enum {枚举项表 } 枚举对象 1,枚举对象 2,… ;
enum 枚举类型名 {枚举项表 }枚举对象 1,枚举对象 2,… ;
enum 枚举类型名 枚举对象 1,枚举对象 2,… ;
其中,枚举项表,由若干个枚举符号常量组成,
它们之间用逗号隔开。每一个枚举符号常量都可以是形如,符号常量名 =常量表达式,这样的带有指定值的符号名。
128
例如:
enum color { red,green,yellow } ;
这里定义了一个枚举类型 color,对应于 color
类型的对象仅能取 red,green,yellow 三种符号常量的值( 注:在 TC 中取消了该限制! )。
这三种符号常量的值分别为 0,1,2 。如果其后有更多的枚举项,依此顺序递增。
如果其中有一个枚举项是带有指定值的项,那么从这个枚举项设定的值开始,其后的枚举项以此值为基础顺序递增。
129
例如:
enum color1 { red,green,yellow,chartreuse=100,
burgundy,claret,winedark } ;
其中诸符号常量的值分别为 0,1,2,100,101、
102,103
一旦定义了枚举类型,便可以定义相应的枚举变量或枚举指针变量。例如:
enum color *cp,col ;
这里把 cp定义为指向 color枚举类型对象的一个指针变量,把 col 定义为 color 型的枚举变量且仅能取
red,green,yellow的值。
130
按照上述定义,可以执行如下的各种操作:
col=green ;
cp=&col ;
*cp=red ;
if ( *cp == red ) …
printf (,%d”,yellow ) ;
……
131
关于枚举对象的使用应注意:
无论是枚举常量还是变量,C编译程序都把它们作为整型数据来处理。 因此它们的值都是整型值。
如 col=green,因为 green代表 1,所以 col的值为 1 。
枚举项是一个符号常量,不能向它赋值。例如执行,yellow=2;”,“red=0;”等都是错误的。
由于枚举量是整型量,所以在程序中要求使用整型常数的地方,都可以使用枚举常量符号名。
132
main( )
{
enum color1 { red,green=3,yellow } ;
int x[green],i ;
for ( i=red; i<green; i++ )
scanf("%d",&x[i] ) ;
for ( i=red; i<green; i++ )
printf ( "%d ",x[i] ) ;
}
例如:
133
例如:
main( )
{
enum color { red,green=3,yellow } col;
……
col=yellow %green ;
switch ( col ) {
case red,printf ( "red\n" ) ; break ;
case green,printf ( "green\n" ) ; break ;
case yellow,printf( "yellow\n" ) ; break ;
default,printf ( "undefined\n" ) ; break ;
}
}
134
程序中可以把一个整数值直接赋给枚举变量。但最好先强制转换成枚举型后再赋。
例如:
col=(enum color)2 ;
在同一作用域范围内,所有的枚举常量名和变量名必须互不相同,而且也不能与该作用域范围内的其它普通对象同名。
135
11.10 用 typedef 定义数据类型
C语言允许用 类型定义关键字 typedef 把目前已有的任一数据类型的类型名字用一个新的名字来代替。 typedef 定义的一般形式是:
typedef 类型名 新类型名 ;
其中,类型名,是目前已存在的数据类型名
(包括结构类型名、联合类型名、枚举类型名 )。
,新类型名,是为前面的,类型名,所取的另一个新的名字。
例如:
typedef float real ;
136
这以后在程序中便可用 real来说明 float型的数据对象;使用 real就相当于使用 float。
例如:
real i,*p ; 等价 float i,*p ;
但必须明白:
首先,定义的新名只是原名的一个别名,并不是建立一个新的数据类型;
其次,新名和老名同时存在并有效,即老名并不失去效用,在程序中仍可继续使用;
最后,用新名和老名定义的对象具有相同的性质和效果。
137
C语言也允许为某个特定基类型的指针类型用一个新类型名代替。 例如:
typedef char* pointer ;
将 pointer定义成为字符指针型 (char *),随后便可以用 pointer来定义 char型的指针对象。例如:
pointer p,string=“redefine example” ;
138
typedef struct node {
char *word ;
int count ;
struct tnode *left ;
struct tnode *right ;
} treenode ;
也可用 typedef重新 定义 一个以存在的结构类型、
联合类型、枚举类型的一个新类型名。 例如:
这之后便可用 treenode来定义 struct node类型 的对象。 例如:
treenode p ; 等价于 struct node p ;
注意,这里的
treenode 不是变量,是类型名
139
前面曾经提及,无结构名的结构类型定义,只有在定义它们时才可以声明对应的结构变量名。 但可以利用 typedef定义手段,为无名结构规定一个类型名。
typedef struct {
int day ;
char month[4] ;
int year ;
} noname ;
noname birthdate,nationalday,hiredate ;
这之后便可用 noname 来定义该 结构类型 对象:
140
C语言也允许用 typedef 定义数组类型。
例如:
typedef int num[100] ;
这以后便可用 num来定义有 100个元素的 int
型数组。
例如:
num a,b,c ;
则相当于定义:
int a[100],b[100],c[100] ;
141
还可以用 typedef 来定义指向函数的指针类型,
typedef int (*pointer)( ) ;
其后即可用 pointer来声明指向返值为 int型的函数指针变量。
例如:
pointer fp ;
则相当于:
int (*fp)( ) ;
142
typedef union {
long i ;
int k[5];
char c;
} DATE ;
struct date {
int cat ;
DATE cow ;
double dog ;
} too ;
例:若有以下定义:
例如,DATE max ;
则语句
printf (,%d”,sizeof(struct date)+sizeof(max) ) ;
的输出是,30
143
typedef 与 #define 的区别:
a) typedef 的后面仅限于数据类型名;
b) typedef 是由 C编译程序解释的;
c) 有些 typedef 定义用 #define是不能实现的。
例如:
typedef char* string ;
对于 string name,sign; 则定义了两个字符型的指针变量 name 和 sign 。
而对于:
#define string char *
那么 string name,sign ; 将被翻译成,char *name,
sign ; 此时只有 name是指针,而 sign是字符变量。
第 11章 结构体与共用体
2
11.1 概述
C 语言定义的几种基本数据类型,它们有规定的类型说明符、数据长度及数据组织和存储形式,程序设计时可直接用它们来定义数据对象。
在实际应用中仅有这些基本数据类型是不够的,为了增强数据的表示能力,能够使用各种数据,
常需要各种新的数据类型来满足问题求解的需要。
例如,一个传统的例子,
3
假定要建立员工的简单档案,为了反映每一个员工的基本情况,如果把员工的姓名、性别、年龄、
职称,工资、电话号码和家庭住址等不同类型的数据项分别用 单独的变量表示,就不能很好地反映与一员工相关的诸数据项之间的关系,且很难有效地组织、处理和使用它们 。
4
如果能把这些相关的数据项组织在一起,定义成一种新的、独立的数据类型,再定义一个实际的对象与之相联系,那么这个对象便代表了某个员工,
访问这个对象就可获得该员工的全部信息。显然这对组织、处理复杂的数据十分便利有效的。
为了能满足这类问题的需要,C语言允许用户自定义各种不同的数据类型,并用它们定义与之相关的对象。
本章将讨论 用户自定义数据类型的定义 及 相应数据对象的定义和使用 。
5
11.2 定义结构体类型及结构变量的方法
1,什么是 结构类型?
一个结构是将一个或多个不同类型的数据有序地组织在一起,并为之确定一个名字所构成的一种数据类型。
不同名的结构代表一种不同的数据类型,即使两个结构具有完全相同的成员项,但结构名字不同也表示两种不同的数据类型。
6
定义一个结构就是命名一个结构并说明它的组成情况。 目的是让程序中的函数知道,存在这样的一种数据类型,可用它来定义与之相关的数据对象。
结构的一般定义形式为:
struct 结构类型名 {
数据类型 1 成员名 1 ;
数据类型 2 成员名 2 ;
………
数据类型 n 成员名 n ;
} ;
7
其中,
struct
指出一个结构定义的开始 ;
结构类型名是对该结构类型的命名,按标识符的构成规则确定;
{…}
一对括号中定义了组成该结构类型的诸成员项,每个成员项由成员的名字、成员的数据类型和一个分号组成。 一个结构中成员项的多少、顺序没有限制。
8
C中固有的那些数据类型说明符,如 int,float、
char,double等;
也可以是本程序中已经定义的另一个结构类型名,因结构名是一种自定义数据类型名;
特别,“数据类型 i” 还可以是本身的结构类型名,
但其后的成员名只能是指针变量名,非指针变量名是错误的 。
数据类型 i
用来指出对应成员项的数据类型,它可以是任何数据类型名,
9
按标识符组成规则确定。,成员名 i” 和,结构类型名” 可与函数或程序中的其他对象名及其他结构中的成员名相同,不产生矛盾。C编译程序总是根据上,下文来区别它们。
成员名 i
结构定义形式是一个整体,在程序中是作为一个 定义语句 (声明语句 ) 出现的。因此括住诸成员项说明的一对大括号和最后的分号不能遗漏,否则引起错误。
10
又因为它是一个声明语句,所以一个结构定义可以在函数的内部和函数的外部进行。
最后必须强调,上述形式的结构定义仅是描述了一个结构的组成情况,或者说只是定义了一种类型为,结构类型名,的数据类型,C编译程序并不给这样的定义分配对应的空间,因为它不是一个实际的数据对象。
下面是一个结构定义的典型例子。
11
struct date {
int day ;
char month[4] ;
int year ;
} ;
该例定义了一个名为 date 的结构类型,它由 3个成员项组成:
成员 day,是一个 int 数据对象;
成员 month,是具有个 4元素的 字符型数组;
成员 year,也是一个 int 型数据对象。
12
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date birthdate ;
} ;
而对于结构类型定义:
该结构类型定义了如下的数据结构:
成员 的数据类型可以是先前已定义过的结构类型。
name salary tel sex birthdate
day month year
13
2,结构变量的定义定义了结构类型后,便可以象用C固有的数据类型关键字那样定义该结构类型的变量,其定义形式是:
struct 结构类型名 结构变量 1,结构变量 2,… ;
必须是前面已经定义过的结构类型名。如前面定义过的 date等例如,
struct date birthdate,nationalday,hiredate ;
C编译程序将为这三个 date 型的结构变量各分配如下的存储空间:
day month year
14
从上面的这个例子可以看出:
结构变量的存储空间分配是按照结构变量的诸成员项顺序进行的。 存储空间大小是各成员项所占空间之和。大小可通过 sizeof运算符对该结构名进行运算得到。 即有:
sizeof (struct 结构名 )
同一结构类型变量的存储空间大小都相同。
如 date结构变量的大小 sizeof(struct date)应为 8。
只有在定义了结构类型名之后才能定义结构变量。
15
这种 定义结构类型及对应的结构变量方法适用于程序中仅使用少数几个该类结构变量的情况。
struct {
int day ;
char month[4] ;
int year ;
} birthdate,nationalday,hiredate ;
C语言也允许定义 无名结构,即缺省结构类型名 。
例如:
16
main ( )
{
struct a {
int b,c ;
} ;
register struct a d ;
……
}
在定义结构变量时,也可以指定存储类型。但 不能定义 register 型结构变量,因为结构变量通常都占用较多的存储空间,在寄存器中存放不下。
例如,连续的、相同类型的成员项可以这样书写错误
17
struct a {
int b,c ;
} d ;
main ( )
{
struct a e ;
……
}
在函数外部定义的结构变量对应的结构类型的定义也必须在函数的外部被定义。
struct a d ;
main ( )
{
struct a {
int b,c ;
} ;
……
}
例如:
错误 ! 因 struct
a 结构类型是在 main函数内定义的。
这个是正确的因 struct a 结构类型是在 main函数外定义的。
18
结构变量也有局部与全局、临时与永久的特性。
总之,结构变量与用C固有的数据类型关键字定义的变量具有相同的性质。
例如:
struct a {
int b,c ;
} d ;
main ( )
{
static struct a e ;
……
}
外部全局结构变量 静态局部结构变量
19
11.3 结构变量的初始化与普通变量相同,C语言也允许在声明结构变量的同时对结构变量初始化。对结构变量的初始化实际上是对结构变量每个成员的初始化。
带有初始化值的结构变量的定义形式为:
struct 结构类型名 结构变量名 1 = {初始化值表 1 },
结构变量名 2 = {初始化值表 2 },
………
结构变量名 n = {初始化值表 n } ;
20
struct 结构类型名 {
数据类型名 1 成员名 1 ;
数据类型名 2 成员名 2 ;
………
数据类型名 n 成员名 n ;
}结构变量名 1 = { 初始化值表 1 },
结构变量名 2 = { 初始化值表 2 },
………
结构变量名 n = { 初始化值表 n } ;
或者,
21
当然在定义 无名结构类型 时也可以对同时定义的结构变量指定初始化值。例如:
struct {
数据类型名 1 成员名 1 ;
数据类型名 2 成员名 2 ;
………
数据类型名 n 成员名 n ;
} 结构变量名 1 = { 初始化值表 1 },
………
结构变量名 n = { 初始化值表 n } ;
22
其中 初始化值表 i 列出了 结构变量名 i 的各个成员的初始化数据。
这些数据项都必须是常量,且给出的数据个数、顺序、类型应与结构变量的成员的个数,顺序、类型一致,它们之间用逗号隔开。
C编译程序将把它们顺序赋给 结构变量名 i
对应的 成员名 i 。
下面几个例子给出了结构变量初始化的几种不同形式。
23
struct date birthdate = { 14,"sep ",1947 },
nationalday = { 1,"oct ",1949 },
hiredate = { 2,"dec",1970 } ;
示例 1:
struct {
float eletronic ;
int gas ;
int water ;
} paybill = { 286,3,140,33 } ;
示例 2:
24
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date birthdate ;
} person1 = { "Wang",2096.86,83594107,'F',
14,"sep",1950 },
person2 = { "Chen",3200.31,83594298,'M',
23,"jul",1937 },
person3 = { "Zhen",1663.45,83592623,'F',
18,"may",1963 } ;
示例 3,
25
11.4 结构变量的引用结构变量与数组在很多方面都是类似的,
(a) 它们的元素 /成员都必须存放在一片连续的存储空间中;
(b) 通过存取数组元素来访问数组,而对结构通过存取结构变量的成员来访问结构变量;
(c) 数组元素有数组元素的表示形式,结构变量的成员也有它的专用表示形式等等。
26
但是,结构变量与数组在概念上有重要区别:
(1) 数组名是数组元素存储区域的起始地址,
是地址量,而结构变量名只代表一组成员,它不是地址量;
(2) 数组中的元素都有相同的数据类型,而结构变量中的成员的数据类型却可以不相同。
结构变量成员的表示形式或访问形式一般是:
结构变量名?成员名
27
其中的,?” 号称为,结构成员运算符,,它有最高优先级,左结合性。 它用来连接结构变量名与成员名,具有,从属于,的含义,表示其后的成员名是前面结构变量中的一个成员。
如前面示例 2中的结构变量 paybill,它的三个成员分别表示为:
paybill?eletronic
paybill?gas
paybill?water
28
它是一个运算表达式,且是一个左值表达式 。
它具有与普通变量完全相同的性质,可以像普通变量那样参于各种运算,既可以出现在赋值号的左边向它赋值、也可以出现在赋值号的右边作为一个运算分量参于表达式的计算、也可以作读入数据存放的对象、当然可作为 ++,--等要求左值对象的运算符的操作数。
,结构变量名,成员名,,这样的结构变量成员的表示或访问形式有如下特点,
29
下面是一些表示、访问结构变量成员的的例子:
person2?salary -= 350.20 ;
paybill?gas++ ;
scanf (,%s,,hiredate?month ) ;
scanf (,%d”,&paybill?water ) ; /*可取成员的地址 */
printf (,%o\n,,person2?tel ) ;
30
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date birthdate ;
}person1={ "Wang",2096.86,83594107,'F',14,"sep",1950 },
person2={ "Chen",3200.31,83594298,'M',23,"jul",1937 },
person3={ "Zhen",1663.45,83592623,'F',18,"may",1963 };
31
main( )
{
struct {
int a,b ;
} c= { 1,2 } ;
int d = c.a ;
……
gets ( person3?name );
if ( strcmp (person3?hiredate?month,"Dec" )
{
……
}
person1?salary=person2?salary+person3?salary ;
leap=person3?birthdate?year%4==0&&
person3?birthdate.year%100!=0||birthdate.year=0;
}
结构变量的成员可以作为其他变量的初始化值
32
如果一个结构的一个成员又是另一个结构对象,
那么要访问嵌套结构变量中的某个成员项需要使用多个,.”运算符,直至最内层的成员项为止。例如:
person1,birthdate,year
因为,.” 号运算符具有最高优先级,且其结合性从左往右,所以这个表达式表示或访问的是
person1中的 birthdate结构型成员的成员项 year。
从上述例中可知:对结构变量的引用一般是针对结构变量的成员而不是指整个结构变量。
33
printf (,%f%d%d”,paybill ) ; /*错误 */
但允许对结构变量整体赋值,条件必须是同类型的结构变量。 例如:
person1 = person2 ; /*正确 */
允许取结构变量的地址,如 &paybill,该地址与它的第一个成员的地址相同。
与一般变量相同,结构变量的成员有了初值才可使用;否则其值无定义,不可使用。
除特殊情况外,不能用一个结构变量代替结构的全部成员。如不能将结构变量作为整体 I/O:
34
11.5 结构数组结构数组中的每一个元素都是同一个结构类型的对象。结构数组的定义与普通数组的定义在语法上完全一样。也可以定义多维结构数组。
struct 结构类型名 数组名 1 [元素个数 1],…… ;
定义形式 1,结构类型先前已被单独定义的情况下面以一维数组为例讨论结构数组的定义。
35
struct 结构类型名 {
…… /* 成员项定义 */
} 数组名 1[元素个数 1],数组名 2[元素个数 2],… ;
定义 形式 2:结构类型与该类型的数组同时定义的情况定义 形式 3,缺省结构类型名的结构数组定义的情况
struct {
…… /* 成员项定义 */
} 数组名 1[元素个数 1],数组名 2 [元素个数 2],… ;
36
struct stock {
char *negotiable_securities_name ;
float opening_price ;
float closing_price ;
float highest_price ;
float lowest_price ;
float rise_and_fall ;
long volume_of_business ;
} ;
下面是关于股市行情的一个结构类型:
37
假定当前有 200种证券,为了反映每天的股市行情,较好的办法就是建立一个与 stock结构类型相关的结构数组:
struct stock current_prices_of_stock [ 200 ] ;
在该定义中,结构数组 current_prices_of_stock
中包括 200 个数据类型为 stock的数组元素,每个元素相当于一个 stock 结构类型 的变量。
C编译程序将分配给该数组 200个 stock 型对象的连续的存储空间,分别用于存放该数组中的每一个元素。该结构数组在这片连续的存储区域中的安排顺序如下图所示:
38
39
与定义结构变量相同,也可以在定义结构数组时对其进行初始化。带有初始化值的结构数组的定义和定义普通数组的形式与要求类似,几种形式是:
struct 结构类型名 数组名 [元素个数 ]={ 初始化值表 },…;
struct 结构类型名 数组名 [ ] = { 初始化值表 },…… ;
struct 结构类型名 {
…… /*成员项定义 */
} 数组名 1 [元素个数 ]={初始化值表 1},
数组名 2 [ ]={初始化值表 2},…… ;
40
struct {
…… /*成员项定义 */
} 数组名 1 [元素个数 ] = { 初始化值表 1 },
数组名 2[ ] = { 初始化值表 2 },
…… ;
其中,
,初始化值表 i”中列出的初始化值对应于每一个结构数组元素中的每一个成员项。
41
C语言也允许把对应于每个结构数组元素的初始化值括在一对大括号中:
{
{……},/*对应于结构数组元素 0中的所有成员 */
{……},/*对应于结构数组元素 1中的所有成员 */
…
{……} /*对应于结构数组元素 n-1中的所有成员 */
} ;
当然,当结构数组元素的成员较少且比较简单时,
不使用内部大括号括住给出的初始化值也是允许的。
在定义带有初始化值的结构数组时也可以不指定数组中元素的个数(,[ ]” 中为空)。
42
struct stock current_prices_of_stock[200] = {
{ "Qingdaobeer",4.0,4.05,3.94,3.99,-0.11,206900 },
{ "Nanjingstore",4.03,4.05,3.99,3.96,-.12,327300 },
{ "Yongshenpen",10.95,11.99,10.90,11.27,0.17,218300},
………………
{ "Yuyuanstore",17.30,17.59,17.20,17.20,-55,229720 }
} ;
下面是带有初始化值的 current_prices_of_stock
结构数组的定义:
43
定义了结构数组之后,访问结构数组元素成员的要求、限制与访问结构变量是一样的。一个结构数组元素的某个成员的表示及访问形式是:
结构数组名 [i],成员名例如:
current_prices_of_stock[0],rise_and_fall
将表示、访问 current_prices_of_stock 这个结构数组的第 0 个元素的成员 rise_and_fall 。
44
结构数组应用例,
该例对 给出的数据 实现 Excel中的分类汇总、求平均值功能。
struct score {
char *name ;
unsigned long number ;
char department[20] ;
float math ;
float english ;
float average ;
} ;
先定义反映成绩表的数据结构:
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
main( )
{
struct score score_table[10],temp ;
char name[20],department[20] ;
int i,j,min,k=0 ;
float math_sum,english_sum,average[6] ;
FILE *fp;
scanf ( "%f",&math_sum ) ;
fp=fopen ( "data.in","r" ) ;
46
for ( i=0; i<10; i++ ) {
fscanf ( fp,“%s”,name ) ;
fp 表示从文件 data.in中读数据
score_table[i].name=(char*)malloc(strlen(name)+1) ;
if ( !score_table[i].name ) {
puts ( "Memory allocation failure!" ) ;
exit (1) ;
}
strcpy (score_table[i].name,name ) ;
fscanf(fp,"%lu%s%f%f ",&score_table[i].number,
score_table[i].department,
&score_table[i].math,
&score_table[i].english) ;
}
/*从文件中读数据 */
47
for ( i=0; i<9; i++ ) {
min=i ;
for ( j=i+1; j<10; j++ )
if ( strcmp ( score_table[j].department,
score_table[min].department ) <0 ) min=j ;
temp=score_table[i] ;
score_table[i]=score_table[min] ;
score_table[min]=temp ;
}
/*按院系名,将结构数组按字典顺序排序 */
48
for ( i=0; i<10; i++ )
score_table[i].average=(score_table[i].math +
score_table[i].english)/2 ;
printf (“\t%-15s%-6s%15s%10s%10s%10s\n\t”,
“Name”,“Number”,
“Department”,“Math”,
“English","average” ) ;
for ( i=0; i<66; i++ ) printf (,%c”,196 ) ;
printf ( "\n" ) ;
for ( i=0; i<10; i++ ) {
math_sum=0 ;
english_sum=0 ;
j=0 ;
strcpy ( department,score_table[i].department ) ;
某系的学生计数
/*计算平均值 */
/*显示输出表头 */
49
while ( i<10 &&
!strcmp ( department,score_table[i].department)) {
printf(“\t%-15s%05lu%16s%10.1f%10.1f%10.1f\n”,
score_table[i].name,
score_table[i].number,
score_table[i].department,
score_table[i].math,
score_table[i].english,
score_table[i].average ) ;
math_sum+=score_table[i].math ;
english_sum+=score_table[i].english ;
j++ ;
i++ ;
}
50
math_sum=0 ;
english_sum=0 ;
for( i=0 ; i<k ; i+=2 ) {
math_sum+=average[i] ;
english_sum+=average[i+1] ;
}
printf ( "\t%23sTotal Average %9.1f %9.1f\n",
' ',math_sum/3,english_sum/3 ) ;}
average[k]=math_sum/j ;
average[k+1]=english_sum/j ;
printf ( "\n\t%28s Average %9.1f %9.1f\n\n",
department,average[k],
average[k+1] ) ;k+=2 ;
i-- ;
} ;
51
52
11.6 指向结构变量的指针变量
C 语言允许定义指向结构变量的指针变量,并用它来访问它所指向的结构变量。
1,结构指针变量的声明声明结构指针变量的一般形式:
struct 结构类型名 *结构指针变量名 ;
,结构类型,前面必须已定义过。
,*” 不能遗漏,否则就变成了结构变量定义。
例如:
53
struct date *pd ;
结构指针变量也可以在定义结构类型名时同时定义,并可同时为其指定初始化值。 例如:
struct date {
int day,month,year ;
} birthday,*pd,*pq=&birthday ;
如果结构类型名 date 先前已被单独定义,那么下面的定义与上述定义具有同样的效果:
struct date birthday,*pd,*pq=&birthday ;
例如:
54
一旦定义了结构指针变量,且已使它指向了一个结构变量,那么便可以用这个结构指针变量来存取它所指向的结构变量中的成员,使用 形式是:
( *结构指针名 ),成员名如 访问 birthday 中的三个成员:
( *pq ),day
( *pq ),month
( *pq ),year
2、用 结构指针变量表示与访问结构成员注意:
*pq 两边的小括号是必须的,因为结构成员运算符,,” 的运算优先级高于,*” 运算符。
55
像 ( *pq ),year 这样的引用结构变量中的成员的方式既不直观且易写错,所以 C 语言另外又提供了一种新的运算符,->” (一个,-” 号和一个,>”
号),专用于使用指针变量访问结构对象中的成员 。
例如:
结构指针 变量 名 ->成员名这样,访问变量 birthday中的成员便可写成:
pq->day 等价于 (*pq),day
pq->month 等价于 (*pq),month
pq->year 等价于 (*pq),year
,->” 和,,” 的优先级和结合性相同。
56
如果结构指针变量 p指向结构变量 employee,结构指针变量 q 是 employee的一个成员、且 year是 q 指向对象的一个成员,那么成员 year的表示形式是:
p -> q -> year
但如果 employee是结构变量,birthday是 employee
的一个结构变量成员,year又 是 birthday的一个成员,
那么成员 year只能按如下 形式表示:
employee,birthday,year
在这种情况下,其中的,,” 运算符不能用,->” 来替代。因为 employee 和 birthday不是结构指针变量。
57
struct date {
int day ;
char *month ;
int year ;
} birthday = { 14,"sep",1947 } ;
struct employee {
char name[20] ;
double salary ;
long tel ;
char sex ;
struct date *q ;
} person1={“Wang”,2960.81,83594107},*p=&person1 ;
为了正确用好 -> 运算符,请研究下面的例子,
这里仅给出前三个成员的初始化值
58
#include <stdio.h>
main( )
{
p->q = &birthday ;
p->sex = 'M' ;
printf ( "Name,%s\nSex,%c\n "
"Date of birth,%2d,%s,%4d\n "
"Tele,No,%ld\n ",
p->name,p->sex,p->q->day,
p->q->month,p->q->year,p->tel ) ;
}
59
Name,Wang
Sex,M
Date of birth,14,sep,1947
Tele,No,83594107
该例运行后的输出是,
由于,->”,,,”,,( )”,,[ ]” 之类的运算符具有相同的运算优先级和结合性,所以当它们被组合在一起使用时难于分析与理解。
假定有如下的声明:
60
main( )
{
int a = 4 ;
struct {
int x ;
int *y ;
} z,*p=&z ;
z · x = 10 ;
z · y = &a ;
……
}
61
则执行:
*p->y 对 p->y 的内容作间接访问,即访问 a得到 4
*p->y++ 先执行 *p->y 取得 4,
然后使 p->y加 1,使得
z.y 的值变成 FFD4,
指向 z.x
*++p->y 先使 p->y 加 1,使得 z.y 的值变成 FFD4,
指向 z.x,然后再执行 *p->y,因 此时
p->y 已指向 z.x,所以获得 10
*p ++->y 先执行 *p->y 取得 4,然后使 p加 1,使
P 指向 FFD8( 即 z′)
62
则执行:
(*p->y)++ 先执行 *p->y,取 a的值
4,然后再使得 (*p->y)
加 1,即使 a的值为 5
++*p->y 与 ++(*p->y)等价,使
(*p->y)加 1 → *p->y,
即使 a为 5
++*p->y++ 先执行 ++*p->y,即使 a为 5,然后再使得
p->y加 1,使 p->y变成 FFD4 指向 x
*(p->y++) 这里括号无用,因 ++在后。先执行 *p->y,
然后使 p->y加 1,使 p->y变成 FFD4指向 x
63
3、结构指针变量作函数参数在函数间传递结构变量有如下几种办法:
1) 传递结构变量的成员
2) 传递整个结构变量本身
3) 传递结构变量的地址例 1:把结构 变量 的成员传递给函数
struct employee {
long no_of_ss ;
char name[20] ;
float salary ;
} ;
64
#include <stdio.h>
main ( )
{
void bill ( long no,float salary ) ;
struct employee person ;
scanf ( "%ld%s%f",&person,no_of_ss,
person,name,&person,salary );
bill ( person,no_of_ss,person,salary ) ;
printf ( "%ld %s %f\n",person,no_of_ss,
person,name,person,salary );
}
结构变量的成员作实参传递给函数 bill
65
void bill ( long no,float salary )
{
salary += 300 ;
printf ( "%ld\t%f\n",no,salary ) ;
}
person,no_of_ss person,salary
66
在这个简单的例子中,函数间的数据传递采用赋值方式把结构变量的成员传递给被调用函数对应的形参,函数中对形参的改变并不影响对应的实参的值。 如函数中对 salary的改变并不影响对应的实参 person.salary的值。
当一个结构变量中的成员很多时采用这种数据传递方式是不现实的,不但要求很多参数,而且程序显得冗长,还易出错。这可以改用直接传递整个结构变量的办法进行:
67
main( )
{
void bill ( struct employee p ) ;
struct employee person ;
scanf ( "%ld%s%f",&person,no_of_ss,
person,name,&person,salary);
bill ( person ) ;
printf ( "%ld %s %f\n",person,no_of_ss,
person,name,person,salary ) ;
}
void bill ( struct employee p )
{
p,salary += 300 ;
printf ( "%ld %s %f\n",p,no_of_ss,
p,name,p,salary ) ;
}
例 2,传递结构变量 本身结构变量实参传递给函数 bill
68
改写后的这个例子直接把 实参 结构变量 赋给 函数对应的 形参 结构变量。
这种办法一次向函数传递了整个结构变量的全部成员,传递仍然是单向的,亦即被调用函数中对 形参结构变量成员的修改仍然不影响对应的实参结构变量的成员值。因为,person 和 p各占用自己的存储空间。
即有:
no_of_ss
name
salary
no_of_ss
name
salary
person p
69
函数间传递结构变量必须保证形参和实参的类型匹配,它们必须是同一结构类型,否则将会产生错误。
直接传递结构变量时,调用函数与被调用函数双方都必须定义同结构类型的变量,要使用双倍的结构变量的存储空间,而要实际传送全部成员数据,
既花空间又费时间,因此这不是值得提倡的办法。
在函数间传递结构对象,通常采用传递指向结构对象指针的办法来实现,这不仅方便而且高效。
70
例 3,传递结构变量的地址
#include <conio.h>
#include <dos.h>
struct tm {
int h ;
int m ;
int s ;
} ;
因程序中调用了 clrscr()函数因程序中调用了 sleep()函数
71
main( )
{
void update ( struct tm *t ) ;
void display ( struct tm *t ) ;
struct tm time ;
printf (,Enter current time”
,in hours:minutes:second),) ;
scanf (,%d:%d:%d,,
&time,h,&time,m,&time,s ) ;
for ( ; ; ) {
update ( &time ) ;
display ( &time ) ;
}
}
结构变量的地址作实参
72
void update ( struct tm *t )
{
t->s++;
if ( t->s == 60 ) {
t->s = 0 ;
t->m ++ ;
}
if ( t->m == 60 ) {
t->m = 0 ;
t->h ++ ;
}
if ( t->h == 24 )
t->h = 0 ;
sleep ( 1u ) ; /*暂停执行 1秒钟 */
}
&time
73
注,sleep( )的原型,void sleep ( unsigned seconds ) ;
功能:暂停执行当前程序指定的秒数。
clrscr( )的原型,void clrscr ( void ) ;
功能:清除当前窗口,将光标定位到左上角。
void display ( struct tm *t )
{
clrscr ( ) ; /* 清除当前窗口 */
printf ( "%d:%d:%d\n",t->h,t->m,t->s ) ;
}
&time
74
例 4 在函数间传递结构数组该例用来统计一个C语言源程序文件中每一个C语言关键字的使用次数。
被统计的C语言源程序文件从键盘上直接输入 ( 即边输入边统计 ),按 Ctrl-Z 键(在 DOS 或
Windows中)或 Ctrl-D 键(在 UNIX中)结束输入。
75
struct key {
char *keyword ;
int keycount ;
} keytab[ ]={
{ "break",0 },
{ "case",0 },
{ "char",0 },
{ "continue",0 },
……
{ "unsigned",0 },
{ "while",0 }
} ;
关键字名称关键字使用次数按字典顺序排列的关键字表
76
#define MAXWORD 20
#define NKEYS (sizeof(keytab)/sizeof(struct key))
#include <stdio.h>
#include <string.h>
#include <ctype.h>
main( )
{
int n,t ;
char word [MAXWORD] ;
计算关键字表中数组元素的个数假定关键字最长为 20个字符用于判断当前读入的标识符是否是关键字
77
while((t=getword(word,MAXWORD))!=EOF)
if(t==1)
if((n=binary(word,keytab,NKEYS)) >= 0)
keytab[n],keycount++ ;
for(n=0 ; n<NKEYS ; n++)
if(keytab[n],keycount>0)
printf ( "%4d %s\n",\
keytab[n],keycount,keytab[n],keyword );
}
78
getword ( char *w,int limit )
{
int c,t ;
if ( type ( (c=*w++=getchar( )) ) != 1) {
*w='\0' ;
return (c) ;
}
while ( --limit>0 ) {
t=type ( c=*w++=getchar( ) ) ;
if ( t != 1 && t != 0 ) {
ungetch (c) ;
break;
}
}
*(w-1)='\0';
return ( 1 ) ;
}
把字符退回到 stdin
79
type ( int c )
{
if ( isalpha (c) )
return(1) ;
else if ( isdigit (c) )
return ( 0 ) ;
else
return (c) ;
}
/*如果是字母字符返回1 */
/*如果是数字字符返回0 */
/*如果是其他字符返回原字符 */
80
binary( char *word,struct key keytab[ ],int n )
{
int low,high,mid,cond ;
low=0 ;
high=n-1 ;
while ( low<=high ) {
mid=(low+high)/2 ;
if((cond=strcmp(word,keytab[mid].keyword))<0)
high=mid-1 ;
else if ( cond>0 )
low=mid+1 ;
else
return(mid) ;
}
return ( -1 ) ;
}
81
例 5 返回结构变量一个函数也可以返回结构型的数据,条件是该函数必须定义成返值为特定结构类型的函数 ;
当然接收该函数返回值的对象也必须是该结构型的变量。
82
#include <string.h>
struct namect {
char fname [20],lname[20] ;
int letters ;
} getinfo(void),makeinfo(struct namect ) ;
void showinfo ( struct namect ) ;
main( )
{
struct namect person ;
person=getinfo( ) ;
person=makeinfo ( person ) ;
showinfo ( person ) ;
}
83
struct namect getinfo (void)
{
struct namect temp ;
printf (,Please enter your first name:\n ),;
gets( temp,fname ) ;
printf(“Please enter your last name:\n)”;
gets ( temp,lname ) ;
return temp ;
}
struct namect makeinfo ( struct namect info )
{
info,letters = strlen (info,fname) +
strlen( info,lname);
return info ;
}
84
程序运行过程中的 I/O:
Please enter your first name:
Huailing
Please enter your last name:
Wang
Huailing Wang,your name contains 12 letters.
void showinfo ( struct namect info )
{
printf(“%s %s,your name contains %d letters.\n”,
info,fname,info,lname,info,letters ) ;
}
85
11.7 用指针处理链表在讨论结构变量定义时曾指出:组成结构的成员项可以是任何数据类型。
那么,一个结构中的成员项当然可以是另一个结构类型的变量,或指向另一个结构对象的结构指针变量,甚至还可以是指向本结构对象的一个结构指针变量。
如果一个结构中的某个成员是另一个结构变量,这样的结构称之为 嵌套结构 。
86
而如果一个结构的成员项是本结构类型的一个结构指针变量,那么把这样的结构称之为,自引用结构,。
struct node {
int data ;
struct node *next ;
} ;
例如:
87
下面的结构定义是错误的:
int main( )
{
struct s {
int x ;
struct s y ;
} k ;
k,x=5 ;
printf (,%d\n”,k,x ) ;
}
/* 这种情况的成员名只能是指针变量名 */
88
上述 node结构的直观表示:
这是一种单向链表数据结构。链表中的元素称之为,结点,(node),个数可以有任意多个。
data data data data
next next next next ……
89
这种动态数据结构相当于结构数组,但是它要比结构数组优越:
首先,结构数组中的元素要连续存放,而链表则不必;
其次,数组中的元素个数是确定的,而链表中的元素个数却没有限制。因此在实际使用中如果元素个数不确定,特别是需要动态增加元素的情况使用链表更合适;
最后,C编译程序对数组必须给其分配存放它的全部元素的存储空间,而对链表则 不必也不可能预先分配全部存储空间,因为C编译程序无法确定链表中 结点 的个数。
90
用自引用结构实现链表有三个问题要解决:
第一,必须指出链表第一个结点的位臵,否则无法存取该链表中的结点。这很容易做到:
只要定义一个指向这个结构对象的指针变量,
使其指向链表的第一个结点。 例如:
struct node *p ;
第二,在建立一个链表时,如何获得下一个新结点的存放空间?
这可以调用内存分配库函数 (结构类型
*)malloc (sizeof(结构类型 ) ) 达 到。
91
struct node *p1,*p2;
……
p2=(struct node*)malloc(sizeof(struct node)) ;
if(p2==NULL) /* 分配成功吗? */
exit(1) ;
p1->next=p2 ; /*假定 p1已指向新结点的上一个结点 */
……
程序中只要把由 malloc函数返回的地址赋给上一结点的成员项 next,便链接到下一个结点的存储位臵。
处理过程应该是:
92
第三,要指出链表的链尾。
这个处理比较简单,通常只要把最后结点中的成员项 next臵为空指针即可。
一般来说,对链表有如下基本操作:
1)建立链表
2)遍历链表
3)插入结点
4)删除结点
5)更新结点数据
6)按结点的某个成员项对链表排序
7) 归并与连接链表
93
一般而言,一个 链表 结点中包含两类成员域:
数据成员域 和 链接成员域 。
数据成员域包含的数据个数、类型、顺序位臵没有限制;但链接成员域 至少包含一个用于链接的指针型成员,且该指针型成员的基类型必须为 链表结点的结构类型。
数据成员域链接成员域
struct node{
long num ;
char name[10] ;
struct node *next ;
};
1,定义 链表 结点的结构数据类型例如:
94
建立一个无序新链表比较简单:新结点总可以插在链表前面;或总是插在链表后面;
建立一个有序新链表 ( 按数据域中某个成员排序 ),稍复杂一些,需要搜索、比较链表的结点,确定插入位臵。
下面的函数根据给出的结点数 number建立一个无序新链表。
2、建立链表
95
struct node *create( int number)
{
struct node *current,*front,*head= NULL ;
for( ; number>0 ; number--) {
current=(struct node *)malloc(sizeof(struct node ));
scanf("%ld%s",¤t->num,current->name);
if ( head==NULL )
head= front =current ;
else
front->next=current ;
front=current;
current->next=NULL;
}
return head;
}
96
void display ( struct node *head )
{
if ( head==NULL )
printf (,The list is empty!,) ;
else
while ( head != NULL ) {
printf(“%ld%s\n”,head->num,head->name);
head=head->next ;
}
}
3,遍历链表在链表中查找指定的数据,显示链表全部结点中的数据,统计链表中的结点数等 操作都需要 遍历链表操作。
下列函数 显示指定链表全部结点中的数据 。
97
struct node* clear ( struct node *head )
{
struct node *p=head ;
while ( p!=NULL ) {
head=p->next ;
free ( p ) ;
p=head ;
}
return NULL ;
}
4,清空链表清空链表意味着归还全部结点占用的存储空间,
并非取消链表。因此操作完成后应将指向该链表的指针变量臵为 NULL( 空链表)。
98
struct node *delete(struct node *head,long num)
{
struct node *current=head,*front ;
if ( head==NULL )
return head ;
else if(head->num==num){/*要删除的是第一个结点 */
head= head ->next ;
free (current ) ; /*开始时保存了 head */
return head ;
}
else
5,删除一个结点
99
do { /*要删除的结点在链表中间和最后 */
if ( current->num==num ) {
front->next=current->next ;
free ( current ) ;
return head;
}
front=current ;
current=current->next ;
} while ( front->next != NULL ) ;
return head; /*无 要删除的结点 */
}
current front
ado ado
ad1 ad1
ad2
100
情况 1,第一个结点就是要删除的结点
10 20 30 40
(ad1)
next next 0
zhao qian sun li
next
(ad2) (ad3)
ad1 ad2 ad3ad0ad0head
101
情况 2,要删除的结点在链表中间和最后。
10 20 30 40
(ad1)
next next 0
zhao qian sun li
next
(ad2) (ad3)
ad1 ad2 ad3ad0ad0head
102
struct node * insert (struct node *head,struct node *new )
{
struct node *current,*front ;
new->next=NULL ;
if(head==NULL) /* 原链表为空 */
head=new ;
else{
current=head;
while(current->num<=new->num &&
current->next!=NULL) {
front=current ;
current=current->next ;
} /*查找插入位臵 */
6,插入结点 /*假定链表已按成员 num从小到大排过序 */
103
if ( current->num < new->num ) /*插到末结点之后 */
current->next=new ;
else if ( current==head ) { /*插到首结点之前 */
new->next=head ;
head=new ;
}
else{ /*插在链表中间,总是插在找到的位臵的前面 */
new->next=current;
front->next=new;
}
}
return head;
}
104
struct node {
int data ;
struct node *next ;
} ;
放入链表各结点成员 data 中的数值按从小到大次序排列;向链表中插入,或从链表中删除一个结点仍保持链表的这种排序特性。
例:按 链表结点某个成员的值,建立 /处理有序链表。
链表结点的结构定义如下:
105
void addx (struct node **head,int x)
函数 findpoint 的第一个形式参数 head 是指向链表的首指针,第二个形式参数 x 为待插入链表结点中成员 data的值。
该函数用来确定在链表中的插入点,并返回插入点的指针。该指针指向的结点成员 data 的值是链表中小于 x 值中的最大者。
若链表为空,或给出的 x 小于链表第一个结点的
data的值,则该函数返回一个空指针。
下面给出处理该链表的两个有关函数,
struct node * findpoint(struct node *head,int x)
和
106
函数 addx 功能是在指定的链表中插入成员 data
的值为 x的结点。
其中:
第一个形式参数 head 是指向链首的指针变量的地址 ;
第二个形式参数 x 为待插入结点成员 data的值。
注意:
该函数是 void形函数,函数的返回值是通过改变实参的值而实现的。
107
struct node *findpoint ( struct node *head,int x )
{
struct node *q = NULL,*p = head ;
if ( head == NULL )
return NULL ;
while ( p != NULL && p->data < x ) {
q = p ;
p = p->next ;
}
return q ;
}
108
void addx (struct node **head,int x)
{ struct node *findpoint(struct node*,int),*r,*q ;
r=(struct node*)malloc(sizeof(struct node));
r->data=x ;
if((q=findpoint(*head,x))==NULL)
if(*head==NULL){ /*空链表 */
r->next=NULL ;
*head=r ;
}
else{ /*链表不空,但 x < 第一个结点的 data值 */
r->next=*head ;
*head=r ;
}
else{ /*插到 q的后面 */
r->next=q->next ;
q->next=r ;}
}
109
结构体的其他使用注意事项:
①,成员名 i”和,结构类型名,可以与函数或程序中的 其他对象名及其他结构中的成员名相同,不会产生矛盾。
② 不能有相同的成员名字。
1,关于结构类型定义
struct 结构类型名 {
数据类型 1 成员名 1 ;
数据类型 2 成员名 2 ;
………
数据类型 n 成员名 n ;
} ;
110
③ 定义成员时只能有,数据类型,部分,不能使用存储类型关键字。
④ 允许按如下形式定义结构类型:
struct {
double k ;
struct {
int a,b ;
} c ;
float d ;
} val={ 3.14,100,200,2.71 },*p=&val ;
111
struct {
double k ;
struct abc {
int a,b ;
} c ;
float d ;
};
main( )
{
struct abc k={ 100,200 } ;
printf ( "%d\n",k.a ) ;
}
甚至于允许:
112
11.8 共用体( 联合)
联合可以被理解为这样的一种数据类型,
在该类型对应的变量中,不同的时刻可以存储不同类型的数据。简言之,允许利用同一存储区域来存储、处理不同类型的数据。
联合数据类型的定义在语法上与结构类型定义类似,一般形式为:
union 联合类型名 {
数据类型 1 变量名 1 ;
数据类型 2 变量名 2 ;
……
数据类型 n 变量名 n ;
} ;
113
union uval {
int iobj ;
double dobj ;
char cobj ;
} val ;
例如:
注意:
① 上面定义的 uval 联合型变量 val 的三个成员:
val,iobj
val,dobj
val,cobj
114
C 编译程序并不是给它们各自分配一个独立的存储空间,而是按这三个成员中具有最大存储长度的那个成员,分配相应大小的存储空间给变量 val,
让这三个成员使用同一个存储区,因此它们的存储起始地址都是相同的。
当访问 val.iobj时,把这个空间中的前两个字节的内容作为 int 型据解释;当访问 val.dobj时,把这个空间中的内容作为 double型数据来解释 ;同理,
当访问 val.cobj时,把这个空间中的第一个字节当作为 char型数据来解释。
115
因此,程序设计者要清楚当前联合型变量中究竟存储的是哪一个成员的数据,才能正确存取联合变量中的内容。
② 在定义一个联合类型时,联合中的成员项可以是任何类型的数据对象,当然也可以是联合类型的变量。
当前存入的某个成员,总是冲掉该成员所作用的存储区中内容例如:
116
union uval {
int iobj ;
double dobj ;
char cobj ;
} ;
struct {
char *p ;
int i ;
union uval val ;
} struarray[10] ;
117
struct {
char *p ;
int i ;
union {
int iobj ;
double dobj ;
char cobj ;
} val ;
} struarray[10] ;
上例甚至于可以直接写成:
118
按上面的定义,则 struarray[5],val,iobj 即访问结构数组 struarray 中序号为 5 的元素中的联合成员变量 val的成员 iobj。
同理,对于:
struct use {
int i ; float f ;
} ;
union exam { struct use tag ;
char ch ;} p ;
则 p,tag,i 用来访问联合变量 p中的 use 结构型成员 tag中的成员项 i 。
119
③ C语言也允许定义联合型指针。
main( )
{
union {
int i[2] ;
long k ;
char c[4] ;
} r,*s=&r ;
s->i[0] = 0x39 ;
s->i[1] = 0x38 ;
printf (,%c\n”,s->i[0] ) ;
}
程序输出,9
例如,
120
假定某校学生会组织了一次向,希望工程,捐书的活动,组织者设计了如下的一张表格用来登记捐书者的有关情况:
其中,职业按工、农、商、学、兵分类,并利用字母 W,P,B,S,A 分别代表之。由于军人无工作单位,所以对军人而言,工作单位,栏填写的是该军人所在的部队番号 。
联合型数据的使用例:
姓名 职业 捐书量 工作单位/部队番号
121
#define MAXSIZE 888
main( )
{
union unit {
long n ;
char serv[30] ;
} ;
struct person {
char name[20] ;
char job ;
int books ;
union unit punit ;
} record[MAXSIZE] ;
int i,j ;
122
for( i=0 ; i<MAXSIZE ; i++){
printf (,Enter data please:” ) ;
scanf (,%s%1s%d”,record[i].name,
&record[i].job,&record[i].books ) ;
if ( record[i].books == 0 )
break;
else if ( record[i].job ==?A? ) {
printf (,Enter designation:” ) ;
scanf (,%ld,,&record[i].punit.n ) ;
}
else {
printf (,Enter unit of service:” ) ;
scanf (,%s”,record[i].punit.serv ) ;
}
}
}
123
printf (,\n%-20s%6s%6s%6s\n”,
“name”,“job”,“books”,“unit” ) ;
for ( j=0 ; j<i ; j++) {
printf (,%-20s %6c %6d”,
record[j].name,record[j].job,record[j].books ) ;
if ( record[j].job ==?A? )
printf (,%ld\n”,record[j].punit.n ) ;
else
printf (,%s\n”,record[j].punit.serv ) ;
}
}
124
定义联合类型变量时,也可以指定初始化值。 若指定 初始化值,只能有一个初值,且这个值只能是作为第一个成员的初始化值。 当然,初始化值的数据类型也必须与第一个成员的数据类型相同。
例如:
union uval u1=32767 ;
定义、使用联合型数据时应注意:
当计算联合变量大小时,总是按最大成员的存储长度计算。但是若计算其中的某个成员的长度,则是按成员的长度计算。
例如:
125
main( )
{
union ucal {
int a ;
char b ;
double c ;
} a ;
a.a = 100 ;
printf (,%d,%d\n”,sizeof a,sizeof ( a.a ) ) ;
}
程序的输出将是:
8,2
126
11.9 枚举类型枚举是 C提供的 一种数据类型 。可以把这种数据类型作为符号常量的一个集合来理解。
枚举类型实际上是 C中的另一种整数类型。 因此,C中作为整数处理的数据类型有三种,整型,字符型,枚举型 。
如果一个变量仅能取几种可能的值,那么可以把这样的变量定义成为枚举型变量。枚举型变量所能取的值,即是它对应的符号常量集合中符号常量的值。
枚举类型对象的定义有三种形式:
127
enum {枚举项表 } 枚举对象 1,枚举对象 2,… ;
enum 枚举类型名 {枚举项表 }枚举对象 1,枚举对象 2,… ;
enum 枚举类型名 枚举对象 1,枚举对象 2,… ;
其中,枚举项表,由若干个枚举符号常量组成,
它们之间用逗号隔开。每一个枚举符号常量都可以是形如,符号常量名 =常量表达式,这样的带有指定值的符号名。
128
例如:
enum color { red,green,yellow } ;
这里定义了一个枚举类型 color,对应于 color
类型的对象仅能取 red,green,yellow 三种符号常量的值( 注:在 TC 中取消了该限制! )。
这三种符号常量的值分别为 0,1,2 。如果其后有更多的枚举项,依此顺序递增。
如果其中有一个枚举项是带有指定值的项,那么从这个枚举项设定的值开始,其后的枚举项以此值为基础顺序递增。
129
例如:
enum color1 { red,green,yellow,chartreuse=100,
burgundy,claret,winedark } ;
其中诸符号常量的值分别为 0,1,2,100,101、
102,103
一旦定义了枚举类型,便可以定义相应的枚举变量或枚举指针变量。例如:
enum color *cp,col ;
这里把 cp定义为指向 color枚举类型对象的一个指针变量,把 col 定义为 color 型的枚举变量且仅能取
red,green,yellow的值。
130
按照上述定义,可以执行如下的各种操作:
col=green ;
cp=&col ;
*cp=red ;
if ( *cp == red ) …
printf (,%d”,yellow ) ;
……
131
关于枚举对象的使用应注意:
无论是枚举常量还是变量,C编译程序都把它们作为整型数据来处理。 因此它们的值都是整型值。
如 col=green,因为 green代表 1,所以 col的值为 1 。
枚举项是一个符号常量,不能向它赋值。例如执行,yellow=2;”,“red=0;”等都是错误的。
由于枚举量是整型量,所以在程序中要求使用整型常数的地方,都可以使用枚举常量符号名。
132
main( )
{
enum color1 { red,green=3,yellow } ;
int x[green],i ;
for ( i=red; i<green; i++ )
scanf("%d",&x[i] ) ;
for ( i=red; i<green; i++ )
printf ( "%d ",x[i] ) ;
}
例如:
133
例如:
main( )
{
enum color { red,green=3,yellow } col;
……
col=yellow %green ;
switch ( col ) {
case red,printf ( "red\n" ) ; break ;
case green,printf ( "green\n" ) ; break ;
case yellow,printf( "yellow\n" ) ; break ;
default,printf ( "undefined\n" ) ; break ;
}
}
134
程序中可以把一个整数值直接赋给枚举变量。但最好先强制转换成枚举型后再赋。
例如:
col=(enum color)2 ;
在同一作用域范围内,所有的枚举常量名和变量名必须互不相同,而且也不能与该作用域范围内的其它普通对象同名。
135
11.10 用 typedef 定义数据类型
C语言允许用 类型定义关键字 typedef 把目前已有的任一数据类型的类型名字用一个新的名字来代替。 typedef 定义的一般形式是:
typedef 类型名 新类型名 ;
其中,类型名,是目前已存在的数据类型名
(包括结构类型名、联合类型名、枚举类型名 )。
,新类型名,是为前面的,类型名,所取的另一个新的名字。
例如:
typedef float real ;
136
这以后在程序中便可用 real来说明 float型的数据对象;使用 real就相当于使用 float。
例如:
real i,*p ; 等价 float i,*p ;
但必须明白:
首先,定义的新名只是原名的一个别名,并不是建立一个新的数据类型;
其次,新名和老名同时存在并有效,即老名并不失去效用,在程序中仍可继续使用;
最后,用新名和老名定义的对象具有相同的性质和效果。
137
C语言也允许为某个特定基类型的指针类型用一个新类型名代替。 例如:
typedef char* pointer ;
将 pointer定义成为字符指针型 (char *),随后便可以用 pointer来定义 char型的指针对象。例如:
pointer p,string=“redefine example” ;
138
typedef struct node {
char *word ;
int count ;
struct tnode *left ;
struct tnode *right ;
} treenode ;
也可用 typedef重新 定义 一个以存在的结构类型、
联合类型、枚举类型的一个新类型名。 例如:
这之后便可用 treenode来定义 struct node类型 的对象。 例如:
treenode p ; 等价于 struct node p ;
注意,这里的
treenode 不是变量,是类型名
139
前面曾经提及,无结构名的结构类型定义,只有在定义它们时才可以声明对应的结构变量名。 但可以利用 typedef定义手段,为无名结构规定一个类型名。
typedef struct {
int day ;
char month[4] ;
int year ;
} noname ;
noname birthdate,nationalday,hiredate ;
这之后便可用 noname 来定义该 结构类型 对象:
140
C语言也允许用 typedef 定义数组类型。
例如:
typedef int num[100] ;
这以后便可用 num来定义有 100个元素的 int
型数组。
例如:
num a,b,c ;
则相当于定义:
int a[100],b[100],c[100] ;
141
还可以用 typedef 来定义指向函数的指针类型,
typedef int (*pointer)( ) ;
其后即可用 pointer来声明指向返值为 int型的函数指针变量。
例如:
pointer fp ;
则相当于:
int (*fp)( ) ;
142
typedef union {
long i ;
int k[5];
char c;
} DATE ;
struct date {
int cat ;
DATE cow ;
double dog ;
} too ;
例:若有以下定义:
例如,DATE max ;
则语句
printf (,%d”,sizeof(struct date)+sizeof(max) ) ;
的输出是,30
143
typedef 与 #define 的区别:
a) typedef 的后面仅限于数据类型名;
b) typedef 是由 C编译程序解释的;
c) 有些 typedef 定义用 #define是不能实现的。
例如:
typedef char* string ;
对于 string name,sign; 则定义了两个字符型的指针变量 name 和 sign 。
而对于:
#define string char *
那么 string name,sign ; 将被翻译成,char *name,
sign ; 此时只有 name是指针,而 sign是字符变量。