12.1从基本数据类型到抽象数据类型
1 所有的程序设计语言都不能将所有复杂数据对象作为其基本数据类型,因此需要允许用户自定义数据类型,在C语言中,就存在构造数据类型(复合数据类型)。
2 结构体是构造数据类型的一种经典代表。
3 抽象数据类型是不再单纯是一组值的集合,还包括作用在值集上操作的集合,且类型的表示细节和操作的实现对外是不可见的。
4 C++中的类是抽象数据的一种具体实现,也是面向对象程序设计语言中的一个重要概念,但是不能将C++看成是带类的C。
12.2 结构体的定义
1 数组的存储方式存在一些问题,如:(1)分配内存不集中,寻址效率低;(2)对数组赋初值时容易发生错位;(3)结构显得零散,不易管理。
2 数组适合对具有相同属性的数据进行批处理;而结构体将不同类型的数据成员组织到统一的名字下;共用体虽然也能表示逻辑相关的不同类型的集合,但是数据成员是情景互斥的。
3 结构体模板的格式如下:
注意,结构体模板并未声明结构体类型的变量,因此不会被分配内存。
4 C语言有两种方式定义结构体变量.
5 关键字typedef可用于为系统固有的或自定义的数据类型定义一个别名,它只是定义一个新的名字,而不是定义一种新的数据类型。
6 结构体变量的初始化格式:
结构体类型 结构体变量={值1,值2……值n};
7 结构体可以实现嵌套,即在一个结构体中包含了另一个结构体作为其成员。
8 访问结构体变量的成员必须使用成员选择运算符(也称圆点运算符),其格式为:
结构体变量名 . 成员名
9 出现结构体嵌套时,必须以级联方式访问结构体成员,也就是通过成员选择运算符逐级找到最底层的成员再引用。
例12.1见文末
10 C语言允许对具有相同结构体类型的变量进行整体赋值,如stu1=stu2;
11 并非所有的结构体成员都可以用赋值运算符来赋值,对字符数组类型的结构体成员进行赋值时,必须使用字符串处理函数strcpy()。
12 结构体类型的声明既可以放在所有函数体的外部(全局声明,可以被所有函数使用),也可以放在函数体的内部(局部声明,只能再本函数体内使用)。
13 结构体变量的地址是结构体变量所占内存空间的首地址,而结构体成员的地址值与结构体成员再结构体中所处的位置以及该成员所占内存的字节数有关。
例12.2见文末
14 系统为结构体变量分配内存的大小,或者说结构体类型所占内存的字节数,并非是所有成员所占内存字节数的总和,它不仅与所定义的结构体类型有关,还与计算机系统本身有关。
12.3 结构体数组的定义和初始化
1 结构体数组的定义格式为:
结构体类型 结构体变量[数量];
2 可以在定义时初始化结构体数组,否则其他数组元素被系统自动赋值为0。
例12.3见文末
12.4 结构体指针的定义和初始化
1 如果已经声明了STUDENT的结构体类型,那么指向该结构类型的指针变量的方法为:
STUDENT *pt; //指针变量pt的值是随机值
为了使pt 指向确定的存储单元,需要对指针变量进行初始化,如:
pt=&stu1;
以上两条语句等价于:
STUDENT *pt=&stu1;
2 访问指针变量指向的结构体成员有两种方式:(一)使用成员选择符;(二)使用指向运算符(也称箭头运算符)。
3 使用箭头运算符访问结构体成员的标准形式为:
pt->studentID=1003101221;
等价于
(*pt).studentID=1002101221;//不常用
4 访问嵌套结构体指针变量下的成员,通常结合成员选择符和指向运算符,如:
pt->birthday.year=2002;
12.5 向函数传递结构体
1 向函数体传递结构体有三种方法。
2 方法一:用结构体的单个成员作为函数参数,向函数传递结构体的单个成员。这种方法与普通类型的变量作为函数实参没有区别,都是按值调用。
3 方法二:用结构体变量作函数参数,向函数传递结构体的完整结构。通过这种方法,可以在函数内用成员选择运算符引用结构体其他成员,同样是按值传递,但是时空开销大,并且只能当行参和实参是同一结构体才可以使用。
4 方法三:用结构体指针或结构体数组作函数参数,向函数传递结构体的地址。这种方法属于传地址调用,效率更高。
例12.4见文末
5 使用方法一和方法二,结构体变量的成员值是不能在被调函数中被修改,而方法三可以被修改。
例12.5~例12.7见文末
12.6 共用体
1 共用体也称为联合,是将不同类型的数据组织在一起共同占有同一段内存的一种构造数据类型。
2 共用体与结构体的声明方法类似,只是关键字变成union。
例12.8见文末
3 共用体所占的内存空间大小取决于成员中占内存空间最多的那个成员变量。
4 共用体采用与开始地址对齐的方式分配内存空间。
5 共用体通过覆盖技术来实现内存的共用,因此在每一瞬起作用的成员是最后一次被赋值的成员。
6 不能为共用体的所有成员同时初始化。
7 共用体不能进行比较操作,也不能作为函数参数。
12.7 枚举数据类型
1 当某些量仅由有限个数据值组成时,通常用枚举类型来表示。
2 枚举描述的是一组整型值的集合,需要使用关键字enum来定义。
3 枚举类型的声明格式为:
enum 枚举标签{枚举常量1,枚举常量2……枚举常量n};
4 枚举常量的定义格式为:
enum 枚举标签 枚举变量名;
可以将枚举常量赋值给对应的枚举标签。
5 当枚举标签和枚举变量放在一起定义时,枚举标签可以省略不写,如:
enum {no,yes,none} answer;
6 虽然枚举标签后面的花括号内的标识符代表枚举型变量的可能取值,但其值是整型常数,不是字符串,因此不能作为字符串来使用。
12.8 动态数据结构—单向链表
1 动态数据结构是利用动态内存分配,使用结构体并且配合指针来实现的一种数据结构。
2 在结构体声明时不能包含结构体类型成员,但可以包含指向本结构体类型的指针成员。
3 链表实际是链式存储、顺序访问的线性表,它是用一组任意的存储单元来存储线性表中的数据,存储单元不一定是连续的,链表长度是不固定的。
4 链表的每一个元素称为一个节点,节点包含数据域和指针域两部分。
5 节点的数据域用于存储本身的数据信息,节点的指针域是一个结构体指针,用于存储其直接后继的节点信息,当其值为空指针(NULL)时,表示链表结束。
6 链表中还需有一个指向链表的起始节点的头指针变量。
7 只包含一个指针域,由n个节点链接形成的链表就被称为线性链表或者单向链表。
8 链表只能顺序访问,不能随机访问,因此最大的缺点是容易出现断链,且断链后影响大。
9 单向链表的建立考虑(1)若原链表为空表,则将新建节点置为头结点;(2)若原链表非空,则将新建节点添加到表尾.
例12.9见文末
10 单向链表的删除考虑 (1)若原链表为空表,则无须删除,直接退出程序;(2)若找到的待删除节点是头节点,则将头指针变量指向当前节点的下一个节点;(3)若找到的待删除节点不是头结点,则将前一节点的指针域指向当前节点的下一节点;(4)搜索到表尾,依旧未找到待删除节点,则提示未找到.
例12.10见文末
11 单向链表的插入考虑 (1)若原链表为空表,则将新节点作为头结点;(2)若原链表非空,则按节点值的大小确定插入新节点;(3)若在表尾插入新节点,则末节点指针域指向新节点.
例12.11见文末
代码
12.1a
演示结构体变量的赋值与用于方法,从键盘输入数据
/12.1a演示结构体变量的赋值与用于方法
#include<stdio.h>
typedef struct date
{
int year;
int month;
int day;
}DATE;//别名为DATE
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATE birthday;//嵌套结构体
int score[4];
}STUDENT;
int main(void)
{
STUDENT stu1={100210121,"王刚",'M',{1991,5,19},{72,83,90,82}};//初始化,赋值用花括号
STUDENT stu2;//定义一个相同结构体类型的结构体变量stu2
stu2=stu1;//相同结构体类型之间进行赋值
printf("stu2:%10ld%8s%3c%6d/%02d/%02d%4d%4d%4d%4d\n",//这里的%02d表示输出数据时若左边有多余位则补0
stu2.studentID,stu2.studentName,stu2.studentSex,//对应学号、姓名、性别
stu2.birthday.year,stu2.birthday.month,stu2.birthday.day,//对应出生年月日
stu2.score[0], stu2.score[1], stu2.score[2], stu2.score[3]);
return 0;
}
12.1b
演示结构体变量的赋值与用于方法,从键盘输入数据
//12.1b演示结构体变量的赋值与用于方法,从键盘输入数据
#include<stdio.h>
typedef struct date
{
int year;
int month;
int day;
}DATE;//别名为DATE
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATE birthday;//嵌套结构体
int score[4];
}STUDENT;
int main(void)
{
STUDENT stu1,stu2; //定义结构体变量
int i;//用于控制成绩输入
printf("Input a recoed:\n");
scanf("%ld",&stu1.studentID);
scanf("%s",&stu1.studentName);
scanf(" %c",&stu1.studentSex);//%c前面有一个空格
scanf("%d",&stu1.birthday.year);
scanf("%d",&stu1.birthday.month);
scanf("%d",&stu1.birthday.day);
for(i=0;i<4;i++)
{
scanf("%d",&stu1.score[i]);
}
stu2=stu1;//相同结构体类型之间进行赋值
printf("&stu2=%p\n",&stu2);//打印stu2的地址
printf("stu2:%10ld%8s%3c%6d/%02d/%02d%4d%4d%4d%4d\n",//这里的%02d表示输出数据时若左边有多余位则补0
stu2.studentID,stu2.studentName,stu2.studentSex,//对应学号、姓名、性别
stu2.birthday.year,stu2.birthday.month,stu2.birthday.day,//对应出生年月日
stu2.score[0], stu2.score[1], stu2.score[2], stu2.score[3]);
return 0;
}
12.2
演示结构体所占字节数的计算方法
//12.2 演示结构体所占字节数的计算方法
#include<stdio.h>
typedef struct sample
{
char m1;
int m2;
char m3;
}SAMPLE;
int main(void)
{
SAMPLE s={'a',2,'b'};
printf("bytes=%d\n",sizeof(s));
return 0;
}
12.3
利用结构体数组计算每个学生的4门课程的平均分
//例12.3 利用结构体数组计算每个学生的4门课程的平均分
#include<stdio.h>
typedef struct date
{
int year;
int month;
int day;
}DATE;//别名为DATE
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATE birthday;//嵌套结构体
int score[4];
}STUDENT;
int main(void)
{
int i,j,sum[30];
STUDENT stu[30]={
{100210121,"张三",'M',{1991,5,19},{72,93,80,52}},
{100210122,"李四",'F',{1990,3,22},{83,93,73,12}},
{100210123,"王二",'F',{1991,11,9},{92,73,93,86}},
{100210124,"赵五",'M',{1991,5,5},{92,84,95,86}}
};//注意花括号的对应
for(i=0;i<4;i++)
{
sum[i]=0;
for(j=0;j<4;j++)
{
sum[i]=sum[i]+stu[i].score[j];
}
printf("%10ld%8s%3c%6d/%02d/%02d%4d%4d%4d%4d%6.1f\n",
stu[i].studentID,stu[i].studentName,stu[i].studentSex,//对应学号、姓名、性别
stu[i].birthday.year,stu[i].birthday.month,stu[i].birthday.day,//对应出生年月日
stu[i].score[0], stu[i].score[1], stu[i].score[2], stu[i].score[3],
sum[i]/4.0);
}
return 0;
}
12.4
演示结构体变量作函数实参实现传值调用
//12.4 演示结构体变量作函数实参实现传值调用
#include<stdio.h>
struct date
{
int year;
int month;
int day;
};//无别名,不要忘记分号
void Func(struct date p)//结构体变量作函数形参
{
p.year=2000;
p.month=5;
p.day=22;
}
int main(void)
{
struct date d;
d.year=1999;
d.month=4;
d.day=23;
printf("Before function call:%d/%02d/%02d\n",d.year,d.month,d.day);
Func(d);
printf("After function call:%d/%02d/%02d\n",d.year,d.month,d.day);
return 0;
}
12.5
修改12.4,改用结构体指针变量作函数实参
//12.5 修改12.4,改用结构体指针变量作函数实参
#include<stdio.h>
struct date
{
int year;
int month;
int day;
};//无别名,不要忘记分号
void Func(struct date *pt)//结构体zz变量作函数形参
{
pt->year=2000;//指针运算符
pt->month=5;
pt->day=22;
}
int main(void)
{
struct date d;
d.year=1999;
d.month=4;
d.day=23;
printf("Before function call:%d/%02d/%02d\n",d.year,d.month,d.day);
Func(&d);//传地址调用
printf("After function call:%d/%02d/%02d\n",d.year,d.month,d.day);
return 0;
}
12.6
从函数返回结构体变量的值
//12.6 从函数返回结构体变量的值
#include<stdio.h>
struct date
{
int year;
int month;
int day;
};
struct date Func(struct date p)//返回类型是struct date型
{
p.year=2000;
p.month=5;
p.day=22;
return p;
}
int main(void)
{
struct date d;
d.year=1999;
d.month=4;
d.day=23;
printf("Before function call:%d/%02d/%02d\n",d.year,d.month,d.day);
d=Func(d);
printf("After function call:%d/%02d/%02d\n",d.year,d.month,d.day);
return 0;
}
12.7
修改12.3程序,用结构体数组作函数参数编程并输出计算学生的平均分
//例12.7 修改12.3程序,用结构体数组作函数参数编程并输出计算学生的平均分
#include<stdio.h>
#define N 30
typedef struct date
{
int year;
int month;
int day;
}DATE;//别名为DATE
typedef struct student
{
long studentID;
char studentName[10];
char studentSex;
DATE birthday;//嵌套结构体
int score[4];
}STUDENT;
void InputScore(STUDENT stu[],int n,int m);
void AverScore(STUDENT stu[],float aver[],int n,int m);
void PrintScore(STUDENT stu[],float aver[],int n,int m);
int main(void)
{
float aver[N];
STUDENT stu[N];
int n;
printf("How many students?");
scanf("%d",&n);
InputScore(stu,n,4);
AverScore(stu,aver,n,4);
PrintScore(stu,aver,n,4);
return 0;
}
void InputScore(STUDENT stu[],int n,int m){
int i,j;
for(i=0;i<n;i++)
{
printf("Input recore %d:\n",i+1);
scanf("%ld",&stu[i].studentID);
scanf("%s",stu[i].studentName);//这里无取地址符
scanf(" %c",&stu[i].studentSex);
scanf("%d",&stu[i].birthday.year);
scanf("%d",&stu[i].birthday.month);
scanf("%d",&stu[i].birthday.day);
for(j=0;j<m;j++){
scanf("%d",&stu[i].score[j]);
}
}
}
void AverScore(STUDENT stu[],float aver[],int n,int m){
int i,j,sum[N];
for(i=0;i<n;i++)
{
sum[i]=0;
for(j=0;j<4;j++)
{
sum[i]=sum[i]+stu[i].score[j];
}
aver[i]=(float)sum[i]/m;
}
}
void PrintScore(STUDENT stu[],float aver[],int n,int m){
int i,j;
printf("Result:\n");
for(i=0;i<n;i++)
{
printf("%10ld%8s%3c%6d/%02d/%02d",
stu[i].studentID,stu[i].studentName,stu[i].studentSex,//对应学号、姓名、性别
stu[i].birthday.year,stu[i].birthday.month,stu[i].birthday.day); //对应出生年月日
for(j=0;j<m;j++){
printf("%4d",stu[i].score[j]);
}
printf("%6.1f\n",aver[i]);
}
}
12.8
演示共用体所占内存字节数的计算方法
//12.8 演示共用体所占内存字节数的计算方法
#include<stdio.h>
union sample
{
char i;
int ch;
char f;
};
typedef union sample SAMPLE;
int main(void)
{
printf("bytes=%d\n",sizeof(SAMPLE));
return 0;
}
12.9
单向链表的建立
//12.9 单向链表的建立
#include<stdio.h>
#include<stdlib.h>
struct link *AppendNode(struct link *head);
void DisplyNode(struct link *head);
void DeleteMemory(struct link *head);
struct link
{
int data;
struct link *next;
};
int main(void)
{
int i=0;
char c;
struct link *head=NULL;//链表的头指针
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&c);
while(c=='Y'||c=='y')
{
head=AppendNode(head);//向head为头指针的链表末添加节点
DisplyNode(head);//显示当前链表中的各节点信息
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&c);
i++;
}
printf("%d new nodes have been apended!\n",i);
DeleteMemory(head);//释放所有动态分配的内存
return 0;
}
//函数功能:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针
struct link *AppendNode(struct link *head)
{
struct link *p=NULL,*pr=head;
int data;
p=(struct link *)malloc(sizeof(struct link));//p指向新建节点
if(p==NULL){
printf("No enough memory to allocate!\n");//提醒用户分配失败
exit(0);//退出程序
}
if(head==NULL){//原链表为空表
head=p;//新建节点置为头结点
}
else{
while(pr->next!=NULL)//未到末尾,就移动pr,让pr指向表尾
{
pr=pr->next;//让pr指向下一个节点
}
pr->next=p;//末节点的指针域指向新建节点
}
printf("Input node data:");
scanf("%d",&data);
p->data=data;
p->next=NULL;//将新建节点置为表尾
return head;
}
//函数功能:显示链表中所有节点的节点号和该节点中的数据项
void DisplyNode(struct link *head)
{
struct link *p=head;
int j=1;
while(p!=NULL)//不是表尾
{
printf("%5d%10d\n",j,p->data);
p=p->next;
j++;
}
}
//函数功能:释放head指向的链表中所有节点占用的内存
void DeleteMemory(struct link *head)
{
struct link *p=head,*pr=NULL;
while(p!=NULL)
{
pr=p;//用pr保存当前节点
p=p->next;//移动指针,使其指向下一个节点
free(pr);//释放当前节点
}
}
12.10
单向链表的删除操作函数
//12.10 单向链表的删除操作函数
//函数功能:从head 指向的链表中删除一个节点,返回删除节点后的链表的头指针
struct link *DeleteNode(struct link *head,int nodeData)
{
struct link *p=head,*pr=head;
if(head==NULL)//空表
{
printf("Linked Table is empty!\n")
return (head);
}
while(nodeData!=p->data&&p->next!=NULL)//未找到并且 未到表尾
{
pr=p;//pr保存当前节点
p=p->next; //指向下一节点
}
if(nodeData==p->data)//找到待删除节点
{
if(p==head)
{
head=p->next;//如果是头节点,就让头结点指向待删除节点的下一个节点
}
else
{
pr->next=p->next;//前一节点的指针域指向待删除节点的下一个节点
}
free(p);//释放删除节点的内存
}
else //到来表尾依旧没有找到待删除节点
{
printf("This Node has nor been found!\n");
}
return head;
}
12.11
单向链表的插入操作函数
//12.11单向链表的插入操作函数
//函数功能:在已按升序排序的链表中插入一个新节点,返回插入节点后的链表的头指针
struct link *InsertNode(struct link *head,int nodeData)
{
struct link *pr=head,*p=head,*temp=NULL;
p=(struct link *)malloc(sizeof(struct link));//p指向待插入节点
if(p=NULL){//内存分配失败
printf("No enough memory!\n");
exit(0);
}
p->next=NULL;//指针域赋值为空
p->data=nodeData;//赋值数据
if(head==NULL){//空表
head=p;
}
else{
while(pr->data<nodeData&&pr->next!=NULL)
{
temp=pr;//用temp保存当前节点的指针
pr=pr->next;//指向下一节点
}
if(pr->data>=nodeData)
{
if(pr==head)//在头结点前插入
{
p->next=head;//将新节点的指针域指向原链表的头节点
head=p;//让head指向新节点
}
else//在链表之间插入
{
pr=temp;
p->next=pr->next;//新节点的指针域指向下一节点
pr->next=p;//前一节点的指针域指向新节点
}
}
else//在链表末尾插入
{
pr->next=p;//末节点指向新节点
}
}
return head;
}