结构是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字下。C语言中典型的例子来自图形领域:点由一对坐标定义,矩形由两个点定义。
(结构可以拷贝、赋值、传递给函数,函数可以返回结构类型的返回值)
例如,采用结构存放这两个坐标,其声明如下:
struct point{
int x;
int y;
};
关键字struct引入结构声明,结构声明包含在花括号内的一系列声明组成。struct后面的名字称为结构标记(这里是point),在定义之后,结构标记就代表花括号内的声明。
结构中定义的变量称为成员。(结构成员、结构标记和普通变量(非成员)可以采用相同的名字)
struct 声明定义一种数据类型,在标志结构成员表结束的右花括号之后可以跟一个变量表,这与其他基本类型的变量声明是相同的。
struct {…} x,y,z; 从语法角度,这种方式的声明与声明 int x,y,z;类似,这两个声明都将x、y与z声明为指定类型的变量,并且为它们分配存储空间。如果结构声明的后面不带变量表,则不需要为它分配存储空间,仅仅描述一个结构的模板,在以后定义结构实例时便可以使用该标记定义,例如,对于上面给出结构声明point,语句struct point pt;定义一个struct point 类型pt。
结构的初始化可以在定义后面使用初值表进行,初值表中同每个成员对应的初值必须是常量表达式。
struct point maxpt = {320 ,200};
在表达式中,可以通过下列形式引用某个特定结构的成员:
结构名.成员
例如,可用下列语句打印坐标:printf("%d,%d",pt.x,pt.y);
或者通过下列代码计算原点(0,0)到点pt的距离:
double dist,sqrt(double);
dist = sqrt((double)pt.x * pt.x + (double)pt.y * pt.y);
结构可以嵌套:
struct rect{
struct point pt1;
struct point pt2;
};
结构rect包含两个point类型的成员,
如果按照下列方式声明screen变量:struct rect screen;
则可以用语句:screen.pt1.x 引用screen的成员pt1的x坐标。
即结构体变量有以下特点:
- 在定义结构体变量时可以对它的成员初始化
例如:struct Student b = {.name=“Zhang Fang”};其他未指定初始化的数值型成员初始化为0,字符型成员被系统初始化为’\0‘,指针型成员被系统初始化为NULL - 可以引用结构体变量中成员的值
- 只能对最低级的成员进行赋值或存取以及计算(可以对结构体的变量根据类型进行计算)
- 同类的结构体变量可以互相赋值
- 可以引用结构体变量成员的地址,也可以引用结构体变量的地址
例如:输入两个学生的学号、姓名和成绩,输出成绩较高的学生的学号、姓名和成绩
#include<stdio.h>
int main()
{
struct student{
int num;
char name[20];
float core;
}student1,student2;
scanf("%d %s %f",&student1.num,student1.name,&student1.core);//由于数组名本身就代表地址,所以无需&取地址符号
scanf("%d %s %f",&student2.num,student2.name,&student2.core);
if(student1.core>student2.core)
printf("%d %s %f",student1.num,student1.name,student1.core);
else if(student1.core<student2.core)
printf("%d %s %f",student2.num,student2.name,student2.core);
else
{
printf("%d %s %f",student1.num,student1.name,student1.core);
printf("%d %s %f",student2.num,student2.name,student2.core);
}
}
结构体数组
每个数组元素都是一个结构体类型的数据,分别包括各个成员项
例如:有n个学生的信息(包括学号、姓名、成绩),要求按照成绩的高低顺序输出各学生的信息
#include<stdio.h>
struct student{
int num;
char name[20];
float score;
};
int main()
{
struct student stu[3]={{10101,"zhang",78},{10103,"li",85.2},{10105,"wang",45.2}};
struct student temp;
const n=3;
int i,j;
for(i=0;i<n-1;i++)//使用冒泡法,比较相邻值
{
for(j=0;j<n-1-i;j++)
{
if(stu[j].score>stu[j+1].score)
{
temp=stu[j+1];
stu[j+1]=stu[j];
stu[j]=temp;
}
}
}
for(i=0;i<n;i++)
{
printf("%6d %8s %6.2f",stu[i].num,stu[i].name,stu[i].score);
}
}
指向结构体变量的指针
一个结构体变量的起始地址就是这个结构体变量的指针,指向结构体对象的指针变量即可指向结构体变量,也可以指向结构体数组中的数据。
为了方便,(*p).num 用 p->num 来代替,p->num表示p所指向的结构体变量中的num成员
即以下三种形式等价:
- stu.成员名
- (*p).成员名
- p ->成员们
下例用指针变量指向结构体数组的元素:
#include <stdio.h>
struct student{
int num;
char name[20];
float score;
};
struct student stu[3] = {{10000,"zhansan",84.5},{10001,"lisi",89.3},{10002,"wangpo",59.9}};
int main(){
struct student *p;
for(p=stu;p<stu+3;p++)
{
printf("%6d %6s %6.1f\n",p->num,p->name,p->score);
}
}
注意:程序定义了p是一个指向struct student 类型对象的指针变量,它用来指向一个struct student类型的对象(p的值是stu数组的一个元素的起始地址),不应用来指向stu数组元素中的某一个成员。
例如:以下用法是不正确的 p=stu[1].name;
如果要将某一成员的地址赋给p,可以用强制类型转换,先将成员的地址转换成p的类型
p=(struct student *)stu[0].name;
此时,p的值是stu[0]元素的name成员的起始地址
可以用printf("%s",p);输出stu[0]中成员name的值
如果执行printf("%s,p+1);则输出stu[1]中name的值
执行p++时,p的值增加了结构体struct student的长度
将一个结构体变量的值传递给另一个函数
- 用结构体变量的成员作参数,用法和用普通变量作实参是一样。
- 用结构体变量作实参,将结构体变量所占的内存单元的内容全部按顺序传递给形参
- 用指向结构体变量(或数组元素)的指针作实参,将地址传给形参
下例分别用3个函数实现不同的功能:
- 用input函数来输入数据
- 用max函数找出成绩最高的学生
- 用print函数来输出成绩最高学生的信息
#include <stdio.h>
struct student{
int num;
char name[20];
float score;
};
int main(){
void input(struct student stu[]);
struct student max(struct student stu[]);
void print(struct student stu);
struct student stu[3];
struct student *p;
p=stu;
input(p);
print(max(p));
return 0;
}
void input(struct student stu[])
{int i;
for(i=0;i<3;i++)
scanf("%d%s%f",&stu[i].num,&stu[i].name,&stu[i].score);
}
struct student max(struct student stu[]){
int i,m=0;
for(i=0;i<3;i++)
if(stu[i].score>stu[m].score)
m=i;
return stu[m];
}
void print(struct student stud)
{
printf("%d%s%6.1f",stud.num,stud.name,stud.score);
}
注意:用max§的值作为实参调用print函数,print函数的形参stud是struct student 类型的变量(而不是数组),在调用时,将stu[m]的值传递给形参stud,这时传递的不是地址,而是结构体变量中的信息。
用指针处理链表
链表是动态地进行存储分配的一种结构,用数组存放数据时,必须事先定义固定的数组长度(需要将数组定得足够大但是可能会浪费内存),而链表根据需要开辟内存单元。
链表有一个头指针变量,用head表示,存放一个地址,该地址指向一个元素,链表中每一个元素称为结点,每一个节点都包括两部分:(1)用户需要用的实际数据;(2)下一个节点的地址。最后一个元素的地址存放一个NULL,链表到此结束。
静态链表:
所有结点都是在程序中定义的,不是临时开辟的,也不能用完后释放。
#include<stdio.h>
struct student
{
int num;
float score;
struct student *next;
};
int main()
{
struct student a,b,c,*head,*p;
a.num=10101;a.score=89.5;
b.num=10103;b.score=90;
c.num=10105;c.score=85.3;
head=&a;
a.next=&b;
b.next=&c;
c.next=NULL;
p=head;
do
{printf("%ld%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
{
int num;
float score;
struct student *next;
};
int n;
struct student *creat(void)
{
struct student *head;
struct student *p1,*p2;
n=0;
p1=p2=(struct student *)malloc(LEN);
scanf("%d,%f",&p1->num,&p1->scor
e);
head=NULL;//当没有分配内存,即链表为空时的情况
while(p1->num!=0)//如果输入的p1->num不等于0,则输入的是第1个结点数据,令head=p1,使head指向新开辟的结点
{
n=n+1;
if(n==1)
head=p1;//p1所指的结点作为第一个结点
else
p2->next=p1;//将p1所指的结点连接到表位
p2=p1;
p1=(struct student *)malloc(LEN);
scanf("%d,%f",&p1->num,&p1->score);
p2->next=NULL;
return(head);
}
int main()
{
struct student *pt;
pt = creat();
printf("%d,%5.1f\n",pt->num,pt->score);
return 0;
}
共用体union:
同一个内存单元存放不同类型的变量。例如,将一个短整型变量、一个字符型变量放在同一个地址开始的内存单元中。
(1)同一个内存段可以用来存放不同类型的成员,但在每一瞬间只能存放其中一个成员,而不是同时存放几个
(2)可以对共用体初始化,但初始化表只能有一个常量
union Data
{int i;
char ch;
float f;
}a={1,'a',1.5};//不能初始化3个成员
union Data a={16};
union Data a={.ch='j'};
(3)共用体变量中起作用的成员使最后一次被赋值的成员
(4)共用体变量的地址和成员的地址都是相同的
(5)不能对共用体变量名赋值,也不能引用变量来得到一个值,允许同类型的共用体变量互相赋值
typedef声明新类型名
typedef指定新的类型名来代替已有的类型名(并没有创建一个新类型,只是为某个存在的类型增加了一个新的名称而已)