结构体类型
C语言允许用户自己建立由不同类型数据组成的组合类型的数据结构,它称为结构体。
一般形式:
struct 结构体名
{成员表列};
例如:
struct Student
{
int num;
char name[20];
char sex;
int age;
float score;
};
注意:结构体类型的名字是由一个关键字struct和结构体名组合而成的。结构体名是由用户指定的,又称“结构体标记。上面的结构体声明中Student就是结构体名(结构体标记)。
花括号内是该结构体所包括的子项,成为结构体的成员(member)。
对各成员都应进行类型声明,即类型名 成员名
成员表列也称“域表”,每个成员是结构体中的一个域。成员名命名规则与变量名相同。
说明:
- 结构体类型可以设计出许多种结构体类型。
- 成员可以属于另一个结构体类型。
定义结构体类型变量
1.先声明结构体类型,再定义该类型的变量
在本文的开头已声明了一个结构体类型struct Student,可以用它来定义变量。
例如:strcut Student student1,student2;
定义了student1和student2为struct Student类型变量。
2.在声明类型的同时定义变量
例如:
struct
{
int num;
char name[20];
char sex;
int age;
float score;
} student1,student2;
声明类型和定义变量放在一起进行,能直接看到结构体的结构,比较直观,但不常用。
3.不指定类型名而直接定义结构体类型变量
形式:
struct
{
成员表列
} 变量名表列;
指定了一个无名的结构体类型,没有名字。不能再以此结构体类型去定义其他变量。
说明:
- 结构体类型与结构体变量是不同的概念,不要混淆。
- 结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一个对象。
- 对结构体变量中的成员,可以单独使用,它的作用域地位相同于普通变量。
结构体变量的初始化和引用
例如:
struct Student
{
int num;
char name[20];
char sex;
int age;
} a={11,"allen",'M',22}; // 定义结构体变量a并初始化
-
在定义结构体变量时可以对它的成员初始化。注意:是对结构体变量初始化,而不是结构体类型初始化。
允许对某一个成员初始化,如:
struct Student b={.name="Lisi"}; // 成员运算符"."
其他未被指定初始化的数值型成员被系统初始化为0,字符型成员为’\0’,指针型成员为NULL。
-
可以引用结构体变量中的成员的值,引用方式为:
结构体变量名.成员名
。注意:不能企图输出结构体变量名来达到输出结构体变量所有成员的值。
如:
printf("%s\n",student1); // 错误操作
-
如果成员本身又属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最低一级的成员。只能对最低级的成员进行赋值或存取以及运算。
-
对结构体变量的成员可以像普通变量一样进行各种运算。
-
同类的结构体变量可以相互赋值。
-
可以引用结构体变量成员的地址,也可以引用结构体变量的地址。
说明:结构体变量的地址主要用作函数参数,传递结构体变量的地址。
使用结构体数组
一个结构体变量中可以存放一组有关联的数据,这就是结构体数组。结构体数组与之前介绍过的数值型数组的不同之处在于每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项。
说明:
(1)定义结构体数组的一般形式是
-
struct 结构体名
{成员表列} 数组名[数组长度];
-
先声明一个结构体类型,然后再用此类型定义结构体数组:
结构体类型 数组名[数组长度];
如:
struct Person leader[3]; // leader是结构体数组名
(2)对结构体数组初始化的形式是在定义数组的后面加上:={初值表列};
如:struct Person leader[3]={"Li",0,"Zhang",1,"Sun",'2'};
应用举例
例:有n个学生的信息,要求按成绩的高低顺序输出各学生的信息。
#include <stdio.h>
struct Student
{
int num;
char name[20];
float score;
};
int main()
{
struct Student stu[5]={{10101,"Zhang",78},{10102,"Wang",98.5},{10103,"Li",86},{10104,"Ling",73.5},{10105,"Sun",99}};
struct Student temp; // 定义结构体变量temp,用作交换时的临时变量
const int n=5; // 定义常变量n
int i,j,k;
printf("The order is:\n");
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
if(stu[j].score>stu[k].score) // 进行成绩的比较
k=j;
temp=stu[k];stu[k]=stu[i];stu[i]=temp; // stu[k]与stu[i]元素的互换
}
for(i=0;i<n;i++)
printf("%6d %8s %6.2f\n",stu[i].num,stu[i].name,stu[i].score);
printf("\n");
return 0;
}
结构体指针
所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。
指向结构体变量的指针
指向结构体对象的指针变量即可指向结构体变量,也可指向结构体数组中的元素。指针的基类型必须与结构体变量的类型相同。
程序举例:
#include <stdio.h>
#include <string.h>
int main()
{
struct Student
{
int num;
char name[20];
float score;
};
struct Student stu_1;
struct Student *p;
p=&stu_1;
stu_1.num=10010;
strcpy(stu_1.name,"Lisi");
stu_1.score=89.5;
printf("No.:%ld\nname:%s\nscore:%5.1f\n",(*p).num,(*p).name,(*p).score);
return 0;
}
注意:*p
两侧的括号不能省,因为成员运算符"."优先于"*"
运算符,*p.num
就等价于*(p.num)
了。
说明:C语言允许把*(p.num)
用p->num
来代替,它们等价,"->"
称为指向运算符。
如果用p指向一个结构体变量stu,以下有3种用法等价:
- stu.成员名(如stu.num)
(*p)
.成员名(如*(p.num)
)- p->成员名(如p->num)。
指向结构体数组的指针
程序举例:
#include <stdio.h>
struct Student
{
int num;
char name[20];
float score;
};
struct Student stu[5]={{10101,"Zhang",78},{10102,"Wang",98.5},{10103,"Li",86},{10104,"Ling",73.5},{10105,"Sun",99}};
int main()
{
struct Student *p;
printf("No. Name score\n");
for(p=stu;p<stu+5;p++)
printf("%5d %-20s %5.1f\n",p->num,p->name,p->score);
return 0;
}
注意:
- 如果p的初值为stu,即指向stu的第一个元素,p加上1后,就指向下一个元素。
- 程序定义了p是一个指向struct Student类型对象的指针变量,它用来指向一个struct Student类型的对象,不应用来指向stu数组元素中的某一个成员。
用结构体变量和结构体变量的指针作函数参数
- 用结构体变量的成员做参数。应注意实参与形参的类型保持一致。
- 用结构体变量做实参。采取的是“值传递”方式,如果在执行被调用函数期间改变了形参的值,该值不能返回主调函数,这往往造成使用上的不便。
- 用指向结构体变量的指针作实参,将结构体变量的地址传给形参。
程序举例:有n个结构体变量,内含学生学号、姓名和3门课程的成绩。要求输出平均成绩最高的学生的信息。
#include <stdio.h>
#define N 3
struct Student
{
int num;
char name[20];
float score[3];
float aver;
};
int main()
{
void input(struct Student stu[]);
struct Student max(struct Student stu[]);
void print(struct Student stu);
struct Student stu[N],*p=stu;
input(p);
print(max(p));
return 0;
}
void input(struct Student stu[])
{
int i;
printf("请输入各学生的信息:学号、姓名、三门课成绩:\n");
for(i=0;i<N;i++)
{
scanf("%d %s %f %f %f %f",&stu[i].num,stu[i].name,&stu[i].score[0],&stu[i].score[1],&stu[i].score[2]);
stu[i].aver=(&stu[i].score[0],&stu[i].score[1],&stu[i].score[2])/3.0;
}
}
struct Student max(struct Student stu[])
{
int i,m=0;
for(i=0;i<N;i++)
if(stu.aver>stu[m].aver) m=i;
return stu[m];
}
void print(struct Student stud)
{
printf("\n成绩最高的学生是:\n");
printf("学号:%d\n姓名:%s\n 三门课成绩:%5.1f,%5.1f,%5.1f\n平均成绩:%6.2f\n",stud.num,sutd.name,stud.score[0],stud.score[1],stud.score[2],stud.aver);
}
用指针处理链表
什么是链表
链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。链表这种数据结构,必须利用指针变量才能实现,即一个结点中应包含一个指针变量,用它来存放下一个结点的地址。
例如:
struct Student
{
int num;
float score;
struct Student *next; // next是指针变量,指向结构体变量
};
注意:上面只是定义了一个struct Student类型,并未实现分配存储空间,只有定义了变量才分配存储单元。
简单的静态链表
#include <stdio.h>
struct Student
{
int num;
float score;
struct Student *next; // next是指针变量,指向结构体变量
};
int main()
{
struct Student a,b,c,*head,*p;
a.num=10101;a.score=89.5;
b.num=10101;b.score=80.5;
head=&a;
a.next=&b;
b.next=NULL;
p=head;
do
{
printf("%1d %5.1f\n",p->num,p->score);
p=p->next;
} while(p!=NULL);
return 0;
}
本例程序中,所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放,这种链表成为“静态链表”。
动态链表
所谓建立动态链表是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各结点数据,并建立起前后相连的关系。
#include <stdio.h>
#include <stdlib.h>
#define LEN sizeof(struct Student)
struct Student
{
long num;
float score;
struct Student *next; // next是指针变量,指向结构体变量
};
int n;
struct Student *creat(void)
{
struct Student *head;
struct Student *p1,*p2;
n=0;
p1=p2=(sturct Student * )malloc(LEN);
scanf("%1d,%f",&p1->num,&p2->score);
head=NULL;
while(p1->num!=0)
{
n=n+1;
if(n==1) head=p1;
else p2->next=p1;
p2=p1;
p1=(struct Student * )malloc(LEN);
scanf("%1d,%f",&p1->num,&p2->score);
}
p2->next=NULL;
return(head);
}
int main()
{
struct Student *pt;
pt=creat();
printf("\nnum:%1d\nscore:%5.1f\n",pt->num,pt->score);
reutrn 0;
}
在malloc(LEN)之前加了"(struct Student *)"
,它的作用是使malloc返回的指针转换为struct Student类型数据的指针。注意括号中的"*"号不可省略,否则编程转换成struct Student类型了,而不是指针类型。
输出链表
程序举例:编写一个输出链表的函数print。
#include <stdio.h>
#include <stdlib.h>
#define LEN sizeof(struct Student)
struct Student
{
long num;
float score;
struct Student *next;
};
int n;
void print(struct Student * head);
{
struct Student *p;
printf("\nNow,There %d records are:\n",n);
p=head;
if(head!=NULL)
do
{
printf("&ld %5.1f\n",p->num,p->score);
p=p->next;
} while(p!=NULL);
}
共用体
共用体类型
例如:把一个短整型变量、一个字符型变量和一个实型变量放在同一个地址开始的内存单元中。以上3个变量在内存中占的字节数不同,但都从同一个地址开始存放,也就是使用覆盖技术,后一个数据覆盖了前面的数据。这种使几个不同的变量共享同一段内存的结构,称为“共用体”类型的结构。
定义的一般形式:
union 共用体名
{ 成员表列
} 变量表列;
例如:
union Data
{
int i;
char ch;
float f;
} a,b,c;
结构体变量所占内存长度是各成员占的内存长度之和。
引用共用体变量的方式
只有先定义了共用体变量才能引用它,但应注意,不能引用共用体变量,而只能引用共用体变量中的成员。
a.i
(引用共用体变量中的整型变量i)
共用体类型数据的特点
- 共用体变量中只能存放一个值。
- 可以对共用体变量初始化,但初始化表中只能有一个常量。
- 共用体变量中起作用的成员是最后一次被赋值的成员,原有变量存储单元中的值被取代。
- 共用体变量的地址和它的各成员的地址都是同一个地址。
- 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
- 以前的C规定不能把共用体变量作为函数参数,但可以使用指向共用体变量的指针作函数参数。
- 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。
#include <stdio.h>
struct
{
int num;
char name[10];
char sex;
char job;
union
{
int class;
char position[10];
}categroy;
}person[2];
int main()
{
int i;
for(i=0;i<2;i++)
{
printf("please enter the data of person:\n");
scanf("%d %s %c %c",&person[i].num,&person[i].name,&person[i].sex,&person[i].job);
if(&person[i].job=='s')
scanf("%d",&&person[i].category.clas);
else if(&person[i].job=='t')
scanf("%s",&person[i].category.position);
else
printf("Input Error!");
}
printf("\n");
printf("No. name sex job class/position\n");
for(i=0;i<2;i++)
{
if(&person[i].job=='s')
printf("%-6d%-10s%-4c%-4c%-10d\n",&person[i].num,&person[i].name,&person[i].sex,&person[i].job,person[i].category.clas);
else
printf("%-6d%-10s%-4c%-4c%-10d\n",&person[i].num,&person[i].name,&person[i].sex,&person[i].job,person[i].category.position);
}
return 0;
}
枚举类型
如果一个变量只有几种可能的值,则可以定义为枚举类型,所谓“枚举”就是指可能的值一一列举出来,变量的值只限于列举出来的值的范围内。
声明枚举类型用enum
开头。
例如:enum Weekday{sun,mon,tue,wed,thu,fri,sat};
以上声明了一个枚举类型 enum Weekday
。然后用此类型来定义变量。
例如:enum Weekday workday,weekday;
workday和weekday被定义为枚举变量,花括号中的sun……sat被称为枚举元素或枚举常量。
一般形式:enum [枚举名] {枚举元素列表};
其中枚举名应遵循标识符的命名规则。
说明:
- C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符而把它们看作常量,不能对它们赋值。
- 每一个枚举元素都代表一个整数。由于枚举型变量的值是整数,因此C99把枚举类型也作为整型数据中的一种,即用户自行定义的整数类型。
- 枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。
程序举例:口袋中有红、黄、蓝、白、黑5种颜色的球若干个。每次从口袋中先后取出3个球,问得到3种不同颜色的球可能取法,输出每种排列的情况。
#include <stdio.h>
int main()
{
enum Color(red,yellow,blue,white,black);
enum Color i,j,k,pri;
int n,loop;
n=0;
for(i=red;i<=black;i++)
{
for(j=red;j<=black;j++)
if(i!=j)
{
for(k=red;k<=black;k++)
if((k!=i)&&(k!=j))
{
n=n+1;
printf("%-4d",n);
for(loop=1;loop<=3;loop++)
{
switch(loop)
{
case 1: pri=i;break;
case 2: pri=j;break;
case 3: pri=k;break;
defalut:break;
}
switch (pri)
{
case red:printf("%-10s","red");break;
case yellow:printf("%-10s","yellow");break;
case blue:printf("%-10s","blue");break;
case white:printf("%-10s","white");break;
case black:printf("%-10s","black");break;
default;break;
}
}
printf("\n");
}
}
}
printf("\ntotal:%5d\n",n);
return 0;
}
typedef声明新类型名
1. 用一个新类型名代替原有的类型名
例如:
typedef int Integer; // 指定用Integer作为类型名,作用与int相同
typedef float Real; // 指定Real为类型名,作用与float相同
int i,j;
等价于Integer i,j;
float a,b;
等价于Real a,b;
2.命名一个简单的类型名代替复杂的类型
- 命名新的类型名代表结构体类型
typedef struct
{
int month;
int day;
int year;
} Date;
然后用新类型名Date去定义变量,
如:Date birthday;
- 命名新的类型名代表数组类型
命名:typedef int Num[100]; // 声明Num为整型数组类型名
定义:Num a;
- 命名新的类型名代表指针类型
命名:typedef char * String;
定义String p,s[10];
- 命名新的类型名代表指向函数的指针类型
命名:typedef int ( * Pointer)();
定义:Pointer p1,p2;
归纳:
- 先按定义变量的方法写出定义体
- 将变量名换成新类型名
- 在最前面加typedef
- 然后可以用新类型名去定义变量
总结:
按定义变量的方式,把变量名换上新类型名,并且在最前面加"typedef",就声明了新类型名代表原来的类型。
说明:
- 以上的方法实际上是为特定的类型指定了一个同义字。
- 用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。
- 当不同源文件中用到同一类数据时,常用typedef声明一些数据类型。
- 使用typedef名称有利于程序的移植和通用。