返回本章首页 下一页
上一页
第 7章 数据的链式存储及应用
为将不同数据类型、但相互关联的一组数据,组合
成一个有机整体使用,C语言提供一种称为“结构体”
的数据类型 。
7.1 结构体类型与结构体变量的定义
7.2 结构体变量的引用与初始化
7.3 结构体数组
7.4 指向结构体类型数据的指针
7.5 线性表的链式存储结构及运算
7.6 共用型和枚举型
7.7 定义已有类型的别名
返回目录
上一章
下一章
返回本章首页 下一页
上一页
7.1 结构体类型与结构体变量的定义
C语言中的结构体类型,相当于其它高级语言中的“记
录”类型。
一,结构体类型定义
struct 结构体类型名 /* struct是结构体类型关键字 */
{数据类型 数据项 1;
数据类型 数据项 2;
…… ……
数据类型 数据项n ;
}; /* 此行分号不能少! */
[案例 7.1] 定义一个反映学生基本情况的结构类型,用以存储学生的
相关信息。
/*案例代码文件名,AL7_1.C。 */
/*功能:定义一个反映学生基本情况的结构体类型 */
返回本章首页 下一页
上一页
struct date /*日期结构体类型:由年, 月, 日三项组成 */
{int year;
int month;
int day;
};
struct std_info /*学生信息结构体类型:由学号, 姓名, 性别和生日共 4项组成
*/
{char no[7];
char name[9];
char sex[3];
struct date birthday;
};
struct score /*成绩结构体类型:由学号和三门成绩共 4项组成 */
{char no[7];
int score1;
int score2;
int score3;
};
返回本章首页 下一页
上一页
( 1), 结构体类型名, 和, 数据项, 的命名规则,
与变量名相同 。
( 2) 数据类型相同的数据项, 既可逐个, 逐行分别
定义, 也可合并成一行定义 。
例如, 本案例代码中的日期结构体类型, 也可改为如
下形式:
struct date
{int year,month,day;
};
( 3) 结构体类型中的数据项, 既可以是基本数据类
型, 也允许是另一个已经定义的结构体类型 。
例如, 本案例代码中的结构体类型 std_info,其数据
项, birthday”就是一个已经定义的日期结构体类型 date。
( 4) 本书将1个数据项称为结构体类型的1个成员
( 或分量 ) 。
返回本章首页 下一页
上一页
二, 结构体变量定义
用户自己定义的结构体类型, 与系统定义的标准类型
( int,char等 ) 一样, 可用来定义结构体变量 。
1.定义结构体变量的方法, 可概括为两种:
( 1) 间接定义法 ──先定义结构体类型, 再定义结构
体变量
例如, 利用 [案例 7.1]中定义的学生信息结构体类型
std_info,定义了一个相应的结构体变量 student:
struct std_info student;
结构体变量 student:拥有结构体类型的全部成员, 其
中 birthday成员是一个日期结构体类型, 它又由 3个成员构
成 。
注意,使用间接定义法定义结构体变量时, 必须同
时指定结构体类型名 。
返回本章首页 下一页
上一页
( 2) 直接定义法 ──在定义结构体类型的同时, 定义结构体变量
例如, 结构体变量 student的定义可以改为如下形式:
struct std_info
{……
} student;
同时定义结构体类型及其结构体变量的一般格式如下:
struct [结构类型名 ]
{ ……
} 结构变量表;
2.说明
( 1) 结构体类型与结构体变量是两个不同的概念, 其区别如同
int类型与 int型变量的区别一样 。
( 2) 结构体类型中的成员名, 可以与程序中的变量同名, 它们
代表不同的对象, 互不干扰 。
返回本章首页 下一页
上一页
7.2 结构体变量的引用与初始化
[案例 7.2] 利用 [案例 7.1]中定义的结构体类型 struct std_info,
定义一个结构体变量 student,用于存储和显示一个学生的基本情况。
/*案例代码文件名,AL7_2.C*/
#include"struct.h"
/*定义并初始化一个外部结构变量 student */
struct std_info student={"000102","张三 ","男 ",{1980,9,20}};
main()
{ printf("No,%s\n",student.no);
printf("Name,%s\n",student.name);
printf("Sex,%s\n",student.sex);
printf("Birthday,%d-%d-%d\n",student.birthday.year,
student.birthday.month,student.birthday.day);
}
返回本章首页 下一页
上一页
程序运行结果:
No,000102
Name,张三
Sex,男
Birthday:1980-9-20
1.结构体变量的引用规则
对于结构体变量, 要通过成员运算符,,”,逐个访问
其成员, 且访问的格式为:
结构体变量,成员 /*其中的,,”是成员运算符 */
例如, 案例中的 student.no,引用结构体变量 student
中的 no成员; student.name引用结构体变量 student中的
name成员, 等等 。
返回本章首页 下一页
上一页
如果某成员本身又是一个结构体类型, 则只能通过多
级的分量运算, 对最低一级的成员进行引用 。
此时的引用格式扩展为:
结构变量,成员,子成员,…,最低 1级子成员
例如, 引用结构体变量 student中的 birthday成员的格
式分别为:
student.birthday.year
student.birthday.month
student.birthday.day
( 1) 对最低一级成员, 可像同类型的普通变量一样,
进行相应的各种运算 。
( 2) 既可引用结构体变量成员的地址, 也可引用结
构体变量的地址 。
返回本章首页 下一页
上一页
例如, &student.name, &student 。
2.结构体变量的初始化
结构体变量初始化的格式, 与一维数组相似:
结构体变量 ={初值表 }
不同的是:如果某成员本身又是结构类型, 则该成员
的初值为一个初值表 。
例如, [案例 7.2]中的 student={"000102","张三 ","男 ",
{1980,9,20}}。
注意,初值的数据类型, 应与结构体变量中相应成员
所要求的一致, 否则会出错 。
返回本章首页 下一页
上一页
7.3 结构体数组
结构体数组的每一个元素,都是结构体类型数据,均
包含结构体类型的所有成员。
[案例 7.3] 利用 [案例 7.1]中定义的结构类型 struct
std_info,定义一个结构体数组 student,用于存储和显示
三个学生的基本情况。
/*案例代码文件名,AL7_3.C*/
#include"struct.h"
/*定义并初始化一个外部结构数组 student[3] */
struct std_info student[3]={{“000102”,“张三”,“男”,{1980,9,20}},
{“000105”,“李四”,“男”,{1980,8,15}},
{“000112”,“王五”,“女”,{1980,3,10}} };
返回本章首页 下一页
上一页
/*主函数 main()*/
main()
{ int i;
/*打印表头, " □ "表示 1个空格字符 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
/*输出三个学生的基本情况 */
for(i=0; i<3; i++)
{ printf("%-7s",student[i].no);
printf("%-9s",student[i].name);
printf("%-4s",student[i].sex);
printf("%d-%d-%d\n",student[i].birthday.year,
student[i].birthday.month,student[i].birthday.day);
}
}
返回本章首页 下一页
上一页
程序运行结果:
No,Name Sex Birthday
000102 张三 男 1980-9-20
000105 李四 男 1980-8-15
000112 王五 女 1980-3-10
与结构体变量的定义相似, 结构体数组的定义也分直
接定义和间接定义两种方法, 只需说明为数组即可 。
与普通数组一样, 结构体数组也可在定义时进行初始
化 。 初始化的格式为:
结构数组 [n]= {{初值表 1},{初值表 2},...,{初值表 n}}
例如, 本案例中的结构体数组 student[3]。
返回本章首页 下一页
上一页
7.4 指向结构体类型数据的指针
结构体变量在内存中的起始地址称为结构体变量的指针。
一,指向结构体变量的指针
[案例 7.4] 使用指向结构体变量的指针来访问结构体变
量的各个成员。
/*案例代码文件名,AL7_4.C*/
#include“struct.h”
struct std_info student={“000102”,“张三”,“男”,{1980,9,20}};
main()
{ struct std_info *p_std=&student;
printf("No,%s\n",p_std->no);
printf("Name,%s\n",p_std->name);
printf("Sex,%s\n",p_std->sex);
printf("Birthday,%d-%d-%d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
返回本章首页 下一页
上一页
通过指向结构体变量的指针来访问结构体变量的成员,
与直接使用结构体变量的效果一样 。 一般地说, 如果指
针变量 pointer已指向结构变量 var,则以下三种形式等价:
( 1) var.成员
( 2) pointer->成员
( 3) (*pointer).成员 /*“*pointer”外面的括号不能省 ! */
注意,在格式 ( 1) 中, 分量运算符左侧的运算对象,
只能是结构体变量;而在格式 ( 2) 中, 指向运算符左侧
的运算对象, 只能是指向结构体变量 ( 或结构体数组 )
的指针变量, 否则都出错 。
思考题,如果要求从键盘上输入结构体变量 student的
各成员数据, 如何修改程序?
返回本章首页 下一页
上一页
二, 指向结构体数组的指针
[案例 7.5]使用指向结构体数组的指针来访问结构体数组 。
/*案例代码文件名,AL7_5.C*/
#include"struct.h"
/*定义并初始化一个外部结构数组 student */
struct std_info student[3]={{"000102","张三 ","男 ",{1980,5,20}},
{"000105","李四 ","男 ",{1980,8,15}},
{“000112”,“王五,,“女,,{1980,3,10}} };
main()
{ struct std_info *p_std=student;
int i=0;
/*打印表头 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
返回本章首页 下一页
上一页
/*输出结构数组内容 */
for( ; i<3; i++,p_std++)
{ printf("%-7s%-9s%-4s",p_std->no,p_std->name,p_std->sex);
printf("%4d-%2d-%2d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
}
如果指针变量 p已指向某结构体数组, 则 p+1指向结
构体数组的下一个元素, 而不是当前元素的下一个成员 。
另外, 如果指针变量 p已经指向一个结构体变量 ( 或
结构体数组 ), 就不能再使之指向结构体变量 ( 或结构
数组体元素 ) 的某一成员 。
返回本章首页 下一页
上一页
三, 指向结构体数据的指针作函数参数
[案例 7.6] 用函数调用方式, 改写 [案例 7.5]:编写一个专门的显
示函数 display(),通过主函数调用来实现显示 。
/*案例代码文件名,AL7_6.C*/
#include"struct.h"
/*定义并初始化一个外部结构数组 student */
struct std_info student[3]={{"000102","张三 ","男 ",{1980,5,20}},
{"000105","李四 ","男 ",{1980,8,15}},
{“000112”,,王
五,,“女,,{1980,3,10}} };
/*主函数 main()*/
main()
{ void display(); /*函数说明 */
int i=0;
/*打印表头 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
返回本章首页 下一页
上一页
/*打印内容 */
for( ; i<3; i++)
{ display( student + i );
printf("\n");
}
}
void display(struct std_info *p_std)
{ printf("%-7s%-9s%-4s",p_std->no,
p_std->name,p_std->sex);
printf("%4d-%2d-%2d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
返回本章首页 下一页
上一页
7.5 线性表的链式存储结构及运算
一, 线性链表
1,线性链表是线性表的链式存储结构, 是一种物理存储单元上非连续,
非顺序的存储结构, 数据元素的逻辑顺序是通过链表中的指针链接次序实
现的 。 因此, 在存储线性表中的数据元素时, 一方面要存储数据元素的值
,另一方面要存储各数据元素之间的逻辑顺序, 为此, 将每一个存储结点
分为两部分:一部分用于存储数据元素的值, 称为数据域;另一部分用于
存放下一个数据元素的存储结点的地址, 即指向后继结点, 称为指针域 。
此种形式的链表因只含有一个指针域, 又称为单向链表, 简称单链表 。
2,单链表的基本操作
对单链表的基本操作有:创建, 检索 ( 查找 ), 插入, 删除和修改等 。
3、C语言对 单 链表结点的结构描述
在 C语言中,定义链表结点的形式如下:
struct 结构体名
{ 数据成员表;
struct 结构体名 * 指针变量名; }
返回本章首页 下一页
上一页
例如,下面定义的结点类型中,数据域包含三个数据项:学号、姓
名、成绩。
Struct student
{ long num; /*数据域 */
char name[20]; /*数据域 */
int score; /*数据域 */
struct student *next; /*指针域 */
}
通常在线性链表的第一结点之前附设一个称为头结点的结点。
如图 7_1所示:图 7-1(a)所示为一个空线性链表。图 7-1(b)所示为一个
非空线性链表( a0,a1,a2,…, an-1)。
a0 a1 an-1head head …
(a) (b)
图 7-1 线性链表的存储结构
返回本章首页 下一页
上一页
上图中, 头结点的数据域可以不存放任何数据, 也可以存放链表的结
点个数的信息 。 对空线性表, 附加头结点的指针域为空 ( NULL或 0表示 ),
用 ∧ 表示 。 头指针 head指向链表附加头结点的存储位置 。 对于链表的各种
操作必须从头指针开始 。
假设 h,p,q为指针变量,可用下列语句来说明:
struct student *h,*p,*q;
在 C语言中,用户可以利用 malloc函数向系统申请分配链表结点的存储空间
,该函数返回存储区的首地址,如:
p=(struct student *)malloc(sizeof(struct student));
指针 p指向一个新分配的结点。
如果要把此结点归还给系统,则用函数 free(p)来实现。
返回本章首页 下一页
上一页
二, 线性链表的基本操作
下面给出的单链表的基本操作实现算法都是以图 7-1所示的带头结点的单链
表为数据结构基础 。
为了使问题简单化, 先定义一个最简单的结构体类型, 它的结点只需
两个成员:
struct student
{
int data;
struct student *next;
}
( 1) 初始化
【 案例 7.7 单链表的初始化 】
int Initiate(struct student * *h)
{ if((*h=(struct student *)malloc(sizeof(struct student)))==NULL)
return 0;
(*h)->next=NULL;
return 1 ; }
注意, 形参 h定义为指针的指针类型, 若定义为指针类型, 将无法带回函数
中建立的头指针值 。
返回本章首页 下一页
上一页
( 2) 单链表的插入操作
1) 已知线性链表 head,在 p指针所指向的结点后插入一个元素 x。
在一个结点后插入数据元素时, 操作较为简单, 不用查找便可直接插入 。
操作过程如图 7-2所示 。
head
p
p
s
head
(a) 插入前
…a0 a1 a
i an-1
…a
i+1
(b) 插入后
图 7-2 单链表的后插入
x
xs
an-1ai …a0 a1 …
返回本章首页 下一页
上一页
相关语句如下:
【 案例 7.8 单链表的后插入 】
{ s=(struct student *)malloc(sizeof(struct student ));
s->data=x;
s->next=p->next;p->next=s;}
2) 已知线性链表 head,在 p指针所指向的结点前插入一个元
素 x。
前插时,必须从链表的头结点开始,找到 P指针所指向的结
点的前驱。设一指针 q从附加头结点开始向后移动进行查找
,直到 p的前趋结点为止。然后在 q指针所指的结点和 p指针
所指的结点之间插入结点 s。
操作过程如图 7-3所示 。
相关语句如下:
【 案例 7.9 单链表的结点插入 】
{q=head;
while(q->next!=p) q=q->next;
s=(struct student *)malloc(sizeof(struct student));
s->data=x;
s->next=p;
q->next=s;}
返回本章首页 下一页
上一页
(b)插入后
图 7-3 单链表的前插入
s x
pa
0 ai-1 ai an-1
… …
qhead
x
0a ai-1 an-1aihead … …
q p
s
( a)插入前
【 案例 7.10 单链表的前插入 】
int insert(struct student *h,int i,int x)
{/*在链表 h中, 在第 i个数据元素前插入一个数据元素 x */
struct student *p,*s;
int j=0;
p=h;
while(p!=NULL&&j<i-1) { p=p->next;j++; /*寻找第 i-1个结点 */
}
if ( j!=i-1) {printf(“Error!”);return 0; /*插入位置错误 */}
if ((s=(struct student *)malloc(sizeof(struct student)))==NULL)
return 0;
s->data=x;
s->next=p->next;
p->next=s;
return 1;}
返回本章首页 下一页
上一页
【 案例 7.11】
下面 C程序中的功能是, 首先建立一个线性链表 head={3,5,7,9},其
元素值依次为从键盘输入正整数 ( 以输入一个非正整数为结束 ) ;在线
性表中值为 x的元素前插入一个值为 y的数据元素 。 若值为 x的结点不存在,
则将 y插在表尾 。
#include“stdlib.h”
#include“stdio.h”
struct slnode
{int data;
struct slnode *next;} /*定义结点类型 */
main()
{int x,y,d;
struct slnode *head,*p,*q,*s;
head=NULL; /*置链表空 */
q=NULL;
scanf(“%d”,&d); /*输入链表数据元素 */
while(d>0)
返回本章首页 下一页
上一页
{p=(struct slnode*)malloc(sizeof(struct slnode));
p->data=d;
p->next=NULL;
if(head==NULL) head=p; /*若链表为空,则将头指针指向当前结点 p*/
else q->next=p; /*链表不为空时,则将新结点链接在最后 */
q=p; /*将指针 q指向链表的最后一个结点 */
scanf(“%d”,&d);}
scanf(“%d,%d”,&x,&y);
s=(struct slnode*)malloc(sizeof(struct slnode));
s->data=y;
q=head;p=q->next;
while((p!=NULL)&&(p->data!=x)) {q=p;p=p->next;} /*查找元素为 x的
指针 */
s->next=p;q->next=s; /*插入元素 y*/
}
返回本章首页 下一页
上一页
( 3) 单链表的删除操作
若要删除线性链表 h中的第 i个结点, 首先要找到第 i个结点并使指针 p指向其前
驱第 i-1个结点, 然后删除第 i个结点并释放被删除结点空间 。 操作过程如图 2-10所
示 。
其算法如下:
【 案例 7.12 单链表的删除 】
int Delet(slnodetype*h,int i)
{ /*在链表 h中删除第 i个结点 */
slnodetype *p,*s;
int j;
p=h;j=0;
while(p->next!=NULL&&j<i-1)
{ p=p->next;j=j+1; /*寻找第 i-1个结点,p指向其前驱 */}
if(j!=i-1)
{printf(“Error!”); /*删除位置错误 !*/
return FALSE; }
s=p->next;
p->next=p->next->next; /*删除第 i个结点 */
free(s); /*释放被删除结点空间 */
return TRUE;
}
返回本章首页 下一页
上一页
head … …
head … …
(b)删除并释放第 i个结点
图 2-10 在线性链表中删除一个结点的过程
aiai-1 ai+1 an-1
p
(a) 寻找第 i个结点指
向其前驱 p
p s
an-1ai+1aiai-1
图 7-4
返回本章首页 下一页
上一页
(p!=NULL&&j<i-1)与 (p->next!=NULL&&j<i-1)
的不同 。
从线性链表的插入与删除算法中, 我们可以看到要取链表中某个结点,
必须从链表的头结点开始一个一个地向后查找, 即我们不能直接存取线性
链表中的某个结点, 这是因为链式存储结构不是随机存储结构 。 虽然在线
性链表中插入或删除结点不需要移动别的数据元素, 但算法寻找第 i-1个或
第 i个结点的时间复杂度为 O(n)。
【 案例 7.13 】,假设已有线性链表 La,编制算法将该链表逆置 。
利用头结点和第一个存放数据元素的结点之间不断插入后继元素结点 。
如图 2-11所示 。
请读者比较插入算法与删除算法中所用的条件
算法如下:
void converse(slnodetype *head)
{slnodetype *p,*q;
p=head->next;
head->next=NULL;
while(p!=NULL)
{ q=p->next;
p->next=head->next;
head->next=p;
p=q;
}
返回本章首页 下一页
上一页
a0 a1 an-1a3 …
p q
a0 a1 an-1a3 …
p q
a1 a0 an-1a3 …
p q
a0 a1 an-1a
3
…head
head
head
head
图 7-5 单链表逆置
返回本章首页 下一页
上一页
7.6 共用型和枚举型简介
一,共用型
1.概念
使几个不同的变量占用同一段内存空间的结构称为共
用型。
2.共用类型的定义 ──与结构类型的定义类似
union 共用类型名
{成员列表 ;};
3.共用变量的定义 ──与结构变量的定义类似
( 1)间接定义 ──先定义类型、再定义变量
例如,定义 data共用类型变量 un1,un2,un3的语句如下:
union data un1,un2,un3;
返回本章首页 下一页
上一页
( 2) 直接定义 ──定义类型的同时定义变量
例如, union [data]
{ int i;
char ch;
float f;
} un1,un2,un3;
共用变量占用的内存空间, 等于最长成员的长度,
而不是各成员长度之和 。
例如, 共用变量 un1,un2和 un3,在 16位操作系统中,
占用的内存空间均为4字节 ( 不是 2+1+4=7字节 ) 。
4, 共用变量的引用 ──与结构变量一样, 也只能逐
个引用共用变量的成员
例如, 访问共用变量 un1各成员的格式为,un1.i、
un1.ch,un1.f。
返回本章首页 下一页
上一页
5,特点
( 1) 系统采用覆盖技术, 实现共用变量各成员的内
存共享, 所以在某一时刻, 存放的和起作用的是最后一
次存入的成员值 。
例如, 执行 un1.i=1,un1.ch='c',un1.f=3.14后, un1.f才
是有效的成员 。
( 2) 由于所有成员共享同一内存空间, 故共用变量
与其各成员的地址相同 。
例如, & un1=& un1.i=& un1.ch=& un1.f。
( 3) 不能对共用变量进行初始化 ( 注意:结构变量
可以 ) ;也不能将共用变量作为函数参数, 以及使函数
返回一个共用数据, 但可以使用指向共用变量的指针 。
( 4) 共用类型可以出现在结构类型定义中, 反之亦
然 。
返回本章首页 下一页
上一页
二, 枚举型
1,枚举类型的定义
enum 枚举类型名 {取值表 };
例如, enum weekdays {Sun,Mon,Tue,Wed,Thu,Fri,Sat};
2, 枚举变量的定义 ──与结构变量类似
( 1) 间接定义
例如, enum weekdays workday;
( 2) 直接定义
例如, enum [weekdays]
{Sun,Mon,Tue,Wed,Thu,Fri,Sat } workday;
3, 说明
( 1) 枚举型仅适应于取值有限的数据 。
例如, 根据现行的历法规定, 1周7天, 1年12个月 。
( 2) 取值表中的值称为枚举元素, 其含义由程序解释 。
例如, 不是因为写成, Sun”就自动代表, 星期天, 。 事实上,
枚举元素用什么表示都可以 。
返回本章首页 下一页
上一页
( 3) 枚举元素作为常量是有值的 ──定义时的顺序号
( 从0开始 ), 所以枚举元素可以进行比较, 比较规则
是:序号大者为大 !
例如, 上例中的 Sun=0,Mon=1,……, Sat=6,所以
Mon>Sun,Sat最大 。
( 4) 枚举元素的值也是可以人为改变的:在定义时
由程序指定 。
例如, 如果 enum weekdays {Sun=7,Mon=1,Tue,
Wed,Thu,Fri,Sat};则 Sun=7, Mon=1, 从 Tue=2开始,
依次增1 。
返回本章首页 下一页
上一页
7.7 定义已有类型的别名
除可直接使用C提供的标准类型和自定义的类型(结
构、共用、枚举)外,也可使用 typedef定义已有类型的别
名。该别名与标准类型名一样,可用来定义相应的变量。
定义已有类型别名的方法如下:
( 1)按定义变量的方法,写出定义体;
( 2)将变量名换成别名;
( 3)在定义体最前面加上 typedef。
[案例 7.14] 给实型 float定义 1个别名 REAL。
( 1)按定义实型变量的方法,写出定义体,float f;
( 2)将变量名换成别名,float REAL;
( 3)在定义体最前面加上 typedef,typedef float REAL;
返回本章首页 下一页
上一页
[案例 7.15] 给如下所示的结构类型 struct date定义 1个别名 DATE。
struct date
{ int year,month,day;
};
( 1) 按定义结构变量的方法, 写出定义体,struct date {…… } d;
( 2) 将变量名换成别名,struct date {…… } DATE;
( 3) 在定义体最前面加上 typedef,typedef struct date {…… } DATE;
说明,
( 1) 用 typedef只是给已有类型增加1个别名, 并不能创
造1个新的类型 。 就如同人一样, 除学名外, 可以再取一个
小名 ( 或雅号 ), 但并不能创造出另一个人来 。
( 2) typedef与 #define有相似之处, 但二者是不同的:前
者是由编译器在编译时处理的;后者是由编译预处理器在编
译预处理时处理的, 而且只能作简单的字符串替换 。
上一页
第 7章 数据的链式存储及应用
为将不同数据类型、但相互关联的一组数据,组合
成一个有机整体使用,C语言提供一种称为“结构体”
的数据类型 。
7.1 结构体类型与结构体变量的定义
7.2 结构体变量的引用与初始化
7.3 结构体数组
7.4 指向结构体类型数据的指针
7.5 线性表的链式存储结构及运算
7.6 共用型和枚举型
7.7 定义已有类型的别名
返回目录
上一章
下一章
返回本章首页 下一页
上一页
7.1 结构体类型与结构体变量的定义
C语言中的结构体类型,相当于其它高级语言中的“记
录”类型。
一,结构体类型定义
struct 结构体类型名 /* struct是结构体类型关键字 */
{数据类型 数据项 1;
数据类型 数据项 2;
…… ……
数据类型 数据项n ;
}; /* 此行分号不能少! */
[案例 7.1] 定义一个反映学生基本情况的结构类型,用以存储学生的
相关信息。
/*案例代码文件名,AL7_1.C。 */
/*功能:定义一个反映学生基本情况的结构体类型 */
返回本章首页 下一页
上一页
struct date /*日期结构体类型:由年, 月, 日三项组成 */
{int year;
int month;
int day;
};
struct std_info /*学生信息结构体类型:由学号, 姓名, 性别和生日共 4项组成
*/
{char no[7];
char name[9];
char sex[3];
struct date birthday;
};
struct score /*成绩结构体类型:由学号和三门成绩共 4项组成 */
{char no[7];
int score1;
int score2;
int score3;
};
返回本章首页 下一页
上一页
( 1), 结构体类型名, 和, 数据项, 的命名规则,
与变量名相同 。
( 2) 数据类型相同的数据项, 既可逐个, 逐行分别
定义, 也可合并成一行定义 。
例如, 本案例代码中的日期结构体类型, 也可改为如
下形式:
struct date
{int year,month,day;
};
( 3) 结构体类型中的数据项, 既可以是基本数据类
型, 也允许是另一个已经定义的结构体类型 。
例如, 本案例代码中的结构体类型 std_info,其数据
项, birthday”就是一个已经定义的日期结构体类型 date。
( 4) 本书将1个数据项称为结构体类型的1个成员
( 或分量 ) 。
返回本章首页 下一页
上一页
二, 结构体变量定义
用户自己定义的结构体类型, 与系统定义的标准类型
( int,char等 ) 一样, 可用来定义结构体变量 。
1.定义结构体变量的方法, 可概括为两种:
( 1) 间接定义法 ──先定义结构体类型, 再定义结构
体变量
例如, 利用 [案例 7.1]中定义的学生信息结构体类型
std_info,定义了一个相应的结构体变量 student:
struct std_info student;
结构体变量 student:拥有结构体类型的全部成员, 其
中 birthday成员是一个日期结构体类型, 它又由 3个成员构
成 。
注意,使用间接定义法定义结构体变量时, 必须同
时指定结构体类型名 。
返回本章首页 下一页
上一页
( 2) 直接定义法 ──在定义结构体类型的同时, 定义结构体变量
例如, 结构体变量 student的定义可以改为如下形式:
struct std_info
{……
} student;
同时定义结构体类型及其结构体变量的一般格式如下:
struct [结构类型名 ]
{ ……
} 结构变量表;
2.说明
( 1) 结构体类型与结构体变量是两个不同的概念, 其区别如同
int类型与 int型变量的区别一样 。
( 2) 结构体类型中的成员名, 可以与程序中的变量同名, 它们
代表不同的对象, 互不干扰 。
返回本章首页 下一页
上一页
7.2 结构体变量的引用与初始化
[案例 7.2] 利用 [案例 7.1]中定义的结构体类型 struct std_info,
定义一个结构体变量 student,用于存储和显示一个学生的基本情况。
/*案例代码文件名,AL7_2.C*/
#include"struct.h"
/*定义并初始化一个外部结构变量 student */
struct std_info student={"000102","张三 ","男 ",{1980,9,20}};
main()
{ printf("No,%s\n",student.no);
printf("Name,%s\n",student.name);
printf("Sex,%s\n",student.sex);
printf("Birthday,%d-%d-%d\n",student.birthday.year,
student.birthday.month,student.birthday.day);
}
返回本章首页 下一页
上一页
程序运行结果:
No,000102
Name,张三
Sex,男
Birthday:1980-9-20
1.结构体变量的引用规则
对于结构体变量, 要通过成员运算符,,”,逐个访问
其成员, 且访问的格式为:
结构体变量,成员 /*其中的,,”是成员运算符 */
例如, 案例中的 student.no,引用结构体变量 student
中的 no成员; student.name引用结构体变量 student中的
name成员, 等等 。
返回本章首页 下一页
上一页
如果某成员本身又是一个结构体类型, 则只能通过多
级的分量运算, 对最低一级的成员进行引用 。
此时的引用格式扩展为:
结构变量,成员,子成员,…,最低 1级子成员
例如, 引用结构体变量 student中的 birthday成员的格
式分别为:
student.birthday.year
student.birthday.month
student.birthday.day
( 1) 对最低一级成员, 可像同类型的普通变量一样,
进行相应的各种运算 。
( 2) 既可引用结构体变量成员的地址, 也可引用结
构体变量的地址 。
返回本章首页 下一页
上一页
例如, &student.name, &student 。
2.结构体变量的初始化
结构体变量初始化的格式, 与一维数组相似:
结构体变量 ={初值表 }
不同的是:如果某成员本身又是结构类型, 则该成员
的初值为一个初值表 。
例如, [案例 7.2]中的 student={"000102","张三 ","男 ",
{1980,9,20}}。
注意,初值的数据类型, 应与结构体变量中相应成员
所要求的一致, 否则会出错 。
返回本章首页 下一页
上一页
7.3 结构体数组
结构体数组的每一个元素,都是结构体类型数据,均
包含结构体类型的所有成员。
[案例 7.3] 利用 [案例 7.1]中定义的结构类型 struct
std_info,定义一个结构体数组 student,用于存储和显示
三个学生的基本情况。
/*案例代码文件名,AL7_3.C*/
#include"struct.h"
/*定义并初始化一个外部结构数组 student[3] */
struct std_info student[3]={{“000102”,“张三”,“男”,{1980,9,20}},
{“000105”,“李四”,“男”,{1980,8,15}},
{“000112”,“王五”,“女”,{1980,3,10}} };
返回本章首页 下一页
上一页
/*主函数 main()*/
main()
{ int i;
/*打印表头, " □ "表示 1个空格字符 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
/*输出三个学生的基本情况 */
for(i=0; i<3; i++)
{ printf("%-7s",student[i].no);
printf("%-9s",student[i].name);
printf("%-4s",student[i].sex);
printf("%d-%d-%d\n",student[i].birthday.year,
student[i].birthday.month,student[i].birthday.day);
}
}
返回本章首页 下一页
上一页
程序运行结果:
No,Name Sex Birthday
000102 张三 男 1980-9-20
000105 李四 男 1980-8-15
000112 王五 女 1980-3-10
与结构体变量的定义相似, 结构体数组的定义也分直
接定义和间接定义两种方法, 只需说明为数组即可 。
与普通数组一样, 结构体数组也可在定义时进行初始
化 。 初始化的格式为:
结构数组 [n]= {{初值表 1},{初值表 2},...,{初值表 n}}
例如, 本案例中的结构体数组 student[3]。
返回本章首页 下一页
上一页
7.4 指向结构体类型数据的指针
结构体变量在内存中的起始地址称为结构体变量的指针。
一,指向结构体变量的指针
[案例 7.4] 使用指向结构体变量的指针来访问结构体变
量的各个成员。
/*案例代码文件名,AL7_4.C*/
#include“struct.h”
struct std_info student={“000102”,“张三”,“男”,{1980,9,20}};
main()
{ struct std_info *p_std=&student;
printf("No,%s\n",p_std->no);
printf("Name,%s\n",p_std->name);
printf("Sex,%s\n",p_std->sex);
printf("Birthday,%d-%d-%d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
返回本章首页 下一页
上一页
通过指向结构体变量的指针来访问结构体变量的成员,
与直接使用结构体变量的效果一样 。 一般地说, 如果指
针变量 pointer已指向结构变量 var,则以下三种形式等价:
( 1) var.成员
( 2) pointer->成员
( 3) (*pointer).成员 /*“*pointer”外面的括号不能省 ! */
注意,在格式 ( 1) 中, 分量运算符左侧的运算对象,
只能是结构体变量;而在格式 ( 2) 中, 指向运算符左侧
的运算对象, 只能是指向结构体变量 ( 或结构体数组 )
的指针变量, 否则都出错 。
思考题,如果要求从键盘上输入结构体变量 student的
各成员数据, 如何修改程序?
返回本章首页 下一页
上一页
二, 指向结构体数组的指针
[案例 7.5]使用指向结构体数组的指针来访问结构体数组 。
/*案例代码文件名,AL7_5.C*/
#include"struct.h"
/*定义并初始化一个外部结构数组 student */
struct std_info student[3]={{"000102","张三 ","男 ",{1980,5,20}},
{"000105","李四 ","男 ",{1980,8,15}},
{“000112”,“王五,,“女,,{1980,3,10}} };
main()
{ struct std_info *p_std=student;
int i=0;
/*打印表头 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
返回本章首页 下一页
上一页
/*输出结构数组内容 */
for( ; i<3; i++,p_std++)
{ printf("%-7s%-9s%-4s",p_std->no,p_std->name,p_std->sex);
printf("%4d-%2d-%2d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
}
如果指针变量 p已指向某结构体数组, 则 p+1指向结
构体数组的下一个元素, 而不是当前元素的下一个成员 。
另外, 如果指针变量 p已经指向一个结构体变量 ( 或
结构体数组 ), 就不能再使之指向结构体变量 ( 或结构
数组体元素 ) 的某一成员 。
返回本章首页 下一页
上一页
三, 指向结构体数据的指针作函数参数
[案例 7.6] 用函数调用方式, 改写 [案例 7.5]:编写一个专门的显
示函数 display(),通过主函数调用来实现显示 。
/*案例代码文件名,AL7_6.C*/
#include"struct.h"
/*定义并初始化一个外部结构数组 student */
struct std_info student[3]={{"000102","张三 ","男 ",{1980,5,20}},
{"000105","李四 ","男 ",{1980,8,15}},
{“000112”,,王
五,,“女,,{1980,3,10}} };
/*主函数 main()*/
main()
{ void display(); /*函数说明 */
int i=0;
/*打印表头 */
printf("No.□□□□ Name□□□□□ Sex□ Birthday\n");
返回本章首页 下一页
上一页
/*打印内容 */
for( ; i<3; i++)
{ display( student + i );
printf("\n");
}
}
void display(struct std_info *p_std)
{ printf("%-7s%-9s%-4s",p_std->no,
p_std->name,p_std->sex);
printf("%4d-%2d-%2d\n",p_std->birthday.year,
p_std->birthday.month,p_std->birthday.day);
}
返回本章首页 下一页
上一页
7.5 线性表的链式存储结构及运算
一, 线性链表
1,线性链表是线性表的链式存储结构, 是一种物理存储单元上非连续,
非顺序的存储结构, 数据元素的逻辑顺序是通过链表中的指针链接次序实
现的 。 因此, 在存储线性表中的数据元素时, 一方面要存储数据元素的值
,另一方面要存储各数据元素之间的逻辑顺序, 为此, 将每一个存储结点
分为两部分:一部分用于存储数据元素的值, 称为数据域;另一部分用于
存放下一个数据元素的存储结点的地址, 即指向后继结点, 称为指针域 。
此种形式的链表因只含有一个指针域, 又称为单向链表, 简称单链表 。
2,单链表的基本操作
对单链表的基本操作有:创建, 检索 ( 查找 ), 插入, 删除和修改等 。
3、C语言对 单 链表结点的结构描述
在 C语言中,定义链表结点的形式如下:
struct 结构体名
{ 数据成员表;
struct 结构体名 * 指针变量名; }
返回本章首页 下一页
上一页
例如,下面定义的结点类型中,数据域包含三个数据项:学号、姓
名、成绩。
Struct student
{ long num; /*数据域 */
char name[20]; /*数据域 */
int score; /*数据域 */
struct student *next; /*指针域 */
}
通常在线性链表的第一结点之前附设一个称为头结点的结点。
如图 7_1所示:图 7-1(a)所示为一个空线性链表。图 7-1(b)所示为一个
非空线性链表( a0,a1,a2,…, an-1)。
a0 a1 an-1head head …
(a) (b)
图 7-1 线性链表的存储结构
返回本章首页 下一页
上一页
上图中, 头结点的数据域可以不存放任何数据, 也可以存放链表的结
点个数的信息 。 对空线性表, 附加头结点的指针域为空 ( NULL或 0表示 ),
用 ∧ 表示 。 头指针 head指向链表附加头结点的存储位置 。 对于链表的各种
操作必须从头指针开始 。
假设 h,p,q为指针变量,可用下列语句来说明:
struct student *h,*p,*q;
在 C语言中,用户可以利用 malloc函数向系统申请分配链表结点的存储空间
,该函数返回存储区的首地址,如:
p=(struct student *)malloc(sizeof(struct student));
指针 p指向一个新分配的结点。
如果要把此结点归还给系统,则用函数 free(p)来实现。
返回本章首页 下一页
上一页
二, 线性链表的基本操作
下面给出的单链表的基本操作实现算法都是以图 7-1所示的带头结点的单链
表为数据结构基础 。
为了使问题简单化, 先定义一个最简单的结构体类型, 它的结点只需
两个成员:
struct student
{
int data;
struct student *next;
}
( 1) 初始化
【 案例 7.7 单链表的初始化 】
int Initiate(struct student * *h)
{ if((*h=(struct student *)malloc(sizeof(struct student)))==NULL)
return 0;
(*h)->next=NULL;
return 1 ; }
注意, 形参 h定义为指针的指针类型, 若定义为指针类型, 将无法带回函数
中建立的头指针值 。
返回本章首页 下一页
上一页
( 2) 单链表的插入操作
1) 已知线性链表 head,在 p指针所指向的结点后插入一个元素 x。
在一个结点后插入数据元素时, 操作较为简单, 不用查找便可直接插入 。
操作过程如图 7-2所示 。
head
p
p
s
head
(a) 插入前
…a0 a1 a
i an-1
…a
i+1
(b) 插入后
图 7-2 单链表的后插入
x
xs
an-1ai …a0 a1 …
返回本章首页 下一页
上一页
相关语句如下:
【 案例 7.8 单链表的后插入 】
{ s=(struct student *)malloc(sizeof(struct student ));
s->data=x;
s->next=p->next;p->next=s;}
2) 已知线性链表 head,在 p指针所指向的结点前插入一个元
素 x。
前插时,必须从链表的头结点开始,找到 P指针所指向的结
点的前驱。设一指针 q从附加头结点开始向后移动进行查找
,直到 p的前趋结点为止。然后在 q指针所指的结点和 p指针
所指的结点之间插入结点 s。
操作过程如图 7-3所示 。
相关语句如下:
【 案例 7.9 单链表的结点插入 】
{q=head;
while(q->next!=p) q=q->next;
s=(struct student *)malloc(sizeof(struct student));
s->data=x;
s->next=p;
q->next=s;}
返回本章首页 下一页
上一页
(b)插入后
图 7-3 单链表的前插入
s x
pa
0 ai-1 ai an-1
… …
qhead
x
0a ai-1 an-1aihead … …
q p
s
( a)插入前
【 案例 7.10 单链表的前插入 】
int insert(struct student *h,int i,int x)
{/*在链表 h中, 在第 i个数据元素前插入一个数据元素 x */
struct student *p,*s;
int j=0;
p=h;
while(p!=NULL&&j<i-1) { p=p->next;j++; /*寻找第 i-1个结点 */
}
if ( j!=i-1) {printf(“Error!”);return 0; /*插入位置错误 */}
if ((s=(struct student *)malloc(sizeof(struct student)))==NULL)
return 0;
s->data=x;
s->next=p->next;
p->next=s;
return 1;}
返回本章首页 下一页
上一页
【 案例 7.11】
下面 C程序中的功能是, 首先建立一个线性链表 head={3,5,7,9},其
元素值依次为从键盘输入正整数 ( 以输入一个非正整数为结束 ) ;在线
性表中值为 x的元素前插入一个值为 y的数据元素 。 若值为 x的结点不存在,
则将 y插在表尾 。
#include“stdlib.h”
#include“stdio.h”
struct slnode
{int data;
struct slnode *next;} /*定义结点类型 */
main()
{int x,y,d;
struct slnode *head,*p,*q,*s;
head=NULL; /*置链表空 */
q=NULL;
scanf(“%d”,&d); /*输入链表数据元素 */
while(d>0)
返回本章首页 下一页
上一页
{p=(struct slnode*)malloc(sizeof(struct slnode));
p->data=d;
p->next=NULL;
if(head==NULL) head=p; /*若链表为空,则将头指针指向当前结点 p*/
else q->next=p; /*链表不为空时,则将新结点链接在最后 */
q=p; /*将指针 q指向链表的最后一个结点 */
scanf(“%d”,&d);}
scanf(“%d,%d”,&x,&y);
s=(struct slnode*)malloc(sizeof(struct slnode));
s->data=y;
q=head;p=q->next;
while((p!=NULL)&&(p->data!=x)) {q=p;p=p->next;} /*查找元素为 x的
指针 */
s->next=p;q->next=s; /*插入元素 y*/
}
返回本章首页 下一页
上一页
( 3) 单链表的删除操作
若要删除线性链表 h中的第 i个结点, 首先要找到第 i个结点并使指针 p指向其前
驱第 i-1个结点, 然后删除第 i个结点并释放被删除结点空间 。 操作过程如图 2-10所
示 。
其算法如下:
【 案例 7.12 单链表的删除 】
int Delet(slnodetype*h,int i)
{ /*在链表 h中删除第 i个结点 */
slnodetype *p,*s;
int j;
p=h;j=0;
while(p->next!=NULL&&j<i-1)
{ p=p->next;j=j+1; /*寻找第 i-1个结点,p指向其前驱 */}
if(j!=i-1)
{printf(“Error!”); /*删除位置错误 !*/
return FALSE; }
s=p->next;
p->next=p->next->next; /*删除第 i个结点 */
free(s); /*释放被删除结点空间 */
return TRUE;
}
返回本章首页 下一页
上一页
head … …
head … …
(b)删除并释放第 i个结点
图 2-10 在线性链表中删除一个结点的过程
aiai-1 ai+1 an-1
p
(a) 寻找第 i个结点指
向其前驱 p
p s
an-1ai+1aiai-1
图 7-4
返回本章首页 下一页
上一页
(p!=NULL&&j<i-1)与 (p->next!=NULL&&j<i-1)
的不同 。
从线性链表的插入与删除算法中, 我们可以看到要取链表中某个结点,
必须从链表的头结点开始一个一个地向后查找, 即我们不能直接存取线性
链表中的某个结点, 这是因为链式存储结构不是随机存储结构 。 虽然在线
性链表中插入或删除结点不需要移动别的数据元素, 但算法寻找第 i-1个或
第 i个结点的时间复杂度为 O(n)。
【 案例 7.13 】,假设已有线性链表 La,编制算法将该链表逆置 。
利用头结点和第一个存放数据元素的结点之间不断插入后继元素结点 。
如图 2-11所示 。
请读者比较插入算法与删除算法中所用的条件
算法如下:
void converse(slnodetype *head)
{slnodetype *p,*q;
p=head->next;
head->next=NULL;
while(p!=NULL)
{ q=p->next;
p->next=head->next;
head->next=p;
p=q;
}
返回本章首页 下一页
上一页
a0 a1 an-1a3 …
p q
a0 a1 an-1a3 …
p q
a1 a0 an-1a3 …
p q
a0 a1 an-1a
3
…head
head
head
head
图 7-5 单链表逆置
返回本章首页 下一页
上一页
7.6 共用型和枚举型简介
一,共用型
1.概念
使几个不同的变量占用同一段内存空间的结构称为共
用型。
2.共用类型的定义 ──与结构类型的定义类似
union 共用类型名
{成员列表 ;};
3.共用变量的定义 ──与结构变量的定义类似
( 1)间接定义 ──先定义类型、再定义变量
例如,定义 data共用类型变量 un1,un2,un3的语句如下:
union data un1,un2,un3;
返回本章首页 下一页
上一页
( 2) 直接定义 ──定义类型的同时定义变量
例如, union [data]
{ int i;
char ch;
float f;
} un1,un2,un3;
共用变量占用的内存空间, 等于最长成员的长度,
而不是各成员长度之和 。
例如, 共用变量 un1,un2和 un3,在 16位操作系统中,
占用的内存空间均为4字节 ( 不是 2+1+4=7字节 ) 。
4, 共用变量的引用 ──与结构变量一样, 也只能逐
个引用共用变量的成员
例如, 访问共用变量 un1各成员的格式为,un1.i、
un1.ch,un1.f。
返回本章首页 下一页
上一页
5,特点
( 1) 系统采用覆盖技术, 实现共用变量各成员的内
存共享, 所以在某一时刻, 存放的和起作用的是最后一
次存入的成员值 。
例如, 执行 un1.i=1,un1.ch='c',un1.f=3.14后, un1.f才
是有效的成员 。
( 2) 由于所有成员共享同一内存空间, 故共用变量
与其各成员的地址相同 。
例如, & un1=& un1.i=& un1.ch=& un1.f。
( 3) 不能对共用变量进行初始化 ( 注意:结构变量
可以 ) ;也不能将共用变量作为函数参数, 以及使函数
返回一个共用数据, 但可以使用指向共用变量的指针 。
( 4) 共用类型可以出现在结构类型定义中, 反之亦
然 。
返回本章首页 下一页
上一页
二, 枚举型
1,枚举类型的定义
enum 枚举类型名 {取值表 };
例如, enum weekdays {Sun,Mon,Tue,Wed,Thu,Fri,Sat};
2, 枚举变量的定义 ──与结构变量类似
( 1) 间接定义
例如, enum weekdays workday;
( 2) 直接定义
例如, enum [weekdays]
{Sun,Mon,Tue,Wed,Thu,Fri,Sat } workday;
3, 说明
( 1) 枚举型仅适应于取值有限的数据 。
例如, 根据现行的历法规定, 1周7天, 1年12个月 。
( 2) 取值表中的值称为枚举元素, 其含义由程序解释 。
例如, 不是因为写成, Sun”就自动代表, 星期天, 。 事实上,
枚举元素用什么表示都可以 。
返回本章首页 下一页
上一页
( 3) 枚举元素作为常量是有值的 ──定义时的顺序号
( 从0开始 ), 所以枚举元素可以进行比较, 比较规则
是:序号大者为大 !
例如, 上例中的 Sun=0,Mon=1,……, Sat=6,所以
Mon>Sun,Sat最大 。
( 4) 枚举元素的值也是可以人为改变的:在定义时
由程序指定 。
例如, 如果 enum weekdays {Sun=7,Mon=1,Tue,
Wed,Thu,Fri,Sat};则 Sun=7, Mon=1, 从 Tue=2开始,
依次增1 。
返回本章首页 下一页
上一页
7.7 定义已有类型的别名
除可直接使用C提供的标准类型和自定义的类型(结
构、共用、枚举)外,也可使用 typedef定义已有类型的别
名。该别名与标准类型名一样,可用来定义相应的变量。
定义已有类型别名的方法如下:
( 1)按定义变量的方法,写出定义体;
( 2)将变量名换成别名;
( 3)在定义体最前面加上 typedef。
[案例 7.14] 给实型 float定义 1个别名 REAL。
( 1)按定义实型变量的方法,写出定义体,float f;
( 2)将变量名换成别名,float REAL;
( 3)在定义体最前面加上 typedef,typedef float REAL;
返回本章首页 下一页
上一页
[案例 7.15] 给如下所示的结构类型 struct date定义 1个别名 DATE。
struct date
{ int year,month,day;
};
( 1) 按定义结构变量的方法, 写出定义体,struct date {…… } d;
( 2) 将变量名换成别名,struct date {…… } DATE;
( 3) 在定义体最前面加上 typedef,typedef struct date {…… } DATE;
说明,
( 1) 用 typedef只是给已有类型增加1个别名, 并不能创
造1个新的类型 。 就如同人一样, 除学名外, 可以再取一个
小名 ( 或雅号 ), 但并不能创造出另一个人来 。
( 2) typedef与 #define有相似之处, 但二者是不同的:前
者是由编译器在编译时处理的;后者是由编译预处理器在编
译预处理时处理的, 而且只能作简单的字符串替换 。