购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.5 结构体与联合体

结构体和联合体(也称共用体)是自定义的数据类型,用于构造非数值数据类型,在处理实际问题中应用非常广泛。数据结构中的链表、队列、树、图等结构都需要用到结构体。本节主要介绍结构体、联合体及其应用。

2.5.1 结构体的定义

一个教职工基本情况表包括编号、姓名、性别、职称、学历和联系电话等信息,每个数据信息的类型并不相同,使用前面学过的数据类型不能将这些信息有效组织起来。每一个教职工都包含编号、姓名、性别、职称、学历和联系电话等数据项,这些数据项放在一起构成的信息称为一个记录。例如,一个教师基本情况表如表2-1所示。

表2-1 教师基本情况表

要用C语言描述表中的某一条记录,需要定义一种特殊的类型,这种类型就是结构体类型。它的定义如下。


struct teacher                           
     /*
结构体类型*/
{
                int no;                         /*
编号*/
                char name[20];                  /*
姓名*/
                char sex[4];                    /*
性别*/
                char headship[8];               /*
职称*/
                char degree[6];                 /*
学历*/
                long int phone;                 /*
联系电话*/
};

其中,struct teacher就是新的数据类型──结构体类型,no、name、sex、headship、degree和phone为结构体类型的成员,表示记录中的数据项。这样,结构体类型struct teacher就可以完整地表示一个教师信息了。

定义结构体类型的一般格式如下。


struct 
结构体名
{ 
成员列表;
};

struct与结构体名合在一起构成结构体类型,结构体名与变量名的命名规则一样。前面的student就是结构体名。使用一对花括号将成员列表括起来,在右花括号外使用一个分号作为定义结构体类型的结束。前面的no、name、sex等都是结构体类型的成员,每个成员需要说明其类型,就像定义变量一样。

注意 在定义结构体类型时,struct不可以省略。struct和结构体名一起构成结构体类型,例如,前面的struct teacher是结构体类型,而teacher只是结构体名。不要遗漏结构体外的分号,这是非常容易出错的地方。

像上面介绍的struct teacher类型是无法用C语言中的整型、浮点型、字符型等基本数据类型描述的,只能用其他数据类型构造出新的数据类型。例如,一个学生记录可能包括学号、姓名、性别、年龄及成绩等数据项,学生的记录可以用以下的结构体类型描述。


struct student
{
                char *no;
                int *name;
                char sex;
                int age;
                float score;
};

其中,struct是关键字,表示是一个结构体类型,大括号里面是结构体的成员;struct student是一个类型名,如果要定义一个结构体变量,例如如下语句。


struct student stu1;

stu1就是类型为结构体struct student类型的变量。如果给结构体变量stu1的成员分别赋值,语句如下。


stu1.no="13001";
stu1.age=20;
stu1.name="Zhu Tong";
stu1.sex='m';
stu1.score=89.0;

则stu1的结构如图2-51所示。

图2-51 stu1的结构

结构体的变量定义也可以和定义结构体类型同时定义。例如如下语句。


struct student
{
                char *no;
                int *name;
                char sex;
                int age;
                float score;
}stu1;

同样,也可以定义结构体数组类型。结构体变量的定义与初始化可以分开进行,也可以在结构体数组的定义的时候初始化,例如如下语句。


struct student
{
                char *no;
                char *name;
                char sex;
                int age;
                float score;
}stu[2]={{"13001","Zhu Tong",'m',22,90},
                        {"13002","Guo Jing",'f',21,82}};

数组中各个元素在内存中的情况如图2-52所示。

图2-52 结构体数组stu在内存中的结构

2.5.2 指向结构体的指针

指针可以指向整型、浮点型、字符等基本类型变量,同样也可以指向结构体变量。指向结构体变量的指针的值是结构体变量的起始地址。指针可以指向结构体,也可以指向结构体数组。指向结构体的指针和指向变量和指向数组的指针的用法类似。

【例2-16】 利用指向结构体数组的指针输出学生基本信息。

【说明】 指向结构体的指针与指向数组的指针一样,结构体中的成员变量地址是连续的,将指针指向结构体数组,就可直接访问结构体中的所有成员。程序实现如下。


#include <stdio.h>                       /*
包含输入输出函数*/
#define N 10
/*
结构体类型及变量定义、初始化*/
struct student
{
                char *no;
                char *name;
                char sex;
                int age;
                float score;
}stu[3]={{"13001","Zhu Tong",'m',22,90.0},
                        {"13002","Li Hua",'f',21,82.0},
                        {"13003","Yang Yang",'m',22,95.0}};
void main()
{
                struct student *p;
                printf("
学生基本情况表:\n");
                printf("
学号          
姓名       
性别    
年龄   
成绩\n");
                for(p=stu;p<stu+3;p++)                               /*
通过指向结构体的指针输出学生信息*/
                        printf("%-8s%12s%8c%8d%8.1f\n",p->no,p->name,p->sex,p->age,p->score);
}

程序运行结果如图2-53所示。

首先定义了一个指向结构体的指针变量p,在循环体中,指针指向结构体数组p=stu,即指针指向了结构体变量的起始地址。通过p->no、p->name等访问各个成员。如果p+1,表示数组中第2个元素stu[1]的起始地址。p+2表示数组中的第3个元素地址,如图2-54所示。

图2-53 通过结构体指针输出学生信息

图2-54 指向结构体数组的指针在内存的情况

2.5.3 用typedef定义数据类型

通常情况下,在定义结构体类型时,使用关键字typedef为新的数据类型起一个好记的名字。typedef是C语言中的关键字,它的主要作用是为类型重新命名,一般形式如下。


typedef 
类型名1 
类型名2

其中,类型名1是已经存在的类型,如int、float、char、long等;也可以是结构体类型,如struct student。类型名2是你重新起的名字,命名规则与变量名的命名规则类似,必须是一个合法的标识符。

1.使用typedef为基本数据类型重新命名

例如如下语句。


typedef int COUNT;                       /*
将int
型重新命名为COUNT*/
typedef float SCORE;                    /*
将float
型重新命名为SCORE*/

经过以上重新定义变量,COUNT就代表了int,SCORE就表示了float。这样,以上语句与如下语句等价。


int a,b,c;                                        /*
定义int
型变量a
、b
、c*/
COUNT a,b,c;                                    /*
定义COUNT
型变量a
、b
、c*/

2.使用typedef为数组类型重新命名

例如如下代码是将NUM定义数组类型。


typedef int NUM[20];                  /*NUM
被定义为新的数组类型*/

代码表示NUM被定义为数组类型,该数组的长度为20,类型为int。可以使用NUM定义int型数组,代码如下。


NUM a;                                            /*
使用NUM
定义int
型数组*/

a表示长度为20的int型数组,它与如下代码等价。


int a[20];                                     /*
使用int
定义数组*/

3.使用typedef为指针类型重新命名

使用typedef为指针类型变量重新命名与重新命名数组类型的方法是类似的。例如如下语句。


typedef float *POINTER;             /*POINT
被定义为指针类型*/

POINTER表示指向float类型的指针类型。如果要定义一个float类型的指针变量p,代码如下。


POINTER p;                                    /*
使用POINTER
定义指针变量*/

p被定义为指向float类型的指针变量。同样,也可以使用typedef重新为指向函数的指针类型命名,例如定义一个函数指针类型,代码如下。


typedef int (*PTR)(int,int);        /*PTR
被定义为函数指针类型*/

PTR被定义为函数指针类型,PTR是指向返回值为int且有两个int型参数的函数指针。如下语句使用PTR定义变量。


PTR pm;                                                /*
使用PTR
定义一个函数指针变量pm*/

pm被定义为一个函数指针变量。

4.使用typedef为用户自定义数据类型重新命名

用户自己定义的数据类型主要包括结构体、联合体、枚举类型,最为常用的是为结构体类型重新命名,联合体和枚举类型的命名方法与结构体的重新命名方法类似。例如,将一个结构体命名为DATE,代码如下。


typedef struct                    /*
为结构体类型重新命名*/
{
                int year;
                int month;
                int day;
}DATE;

从类型名DATE可以很容易看出,DATE是表示日期的类型。上面的类型重新定义是在定义结构体类型的同时为结构体命名;也可以先定义结构体类型,然后重新为结构体命名,代码如下。


struct date                                /*
定义结构体类型*/
{
                int year;
                int month;
                int day;
};
typedef date DATE;              /*
为结构体类型重新命名*/

以上两段代码是等价的。注意,date和DATE是两个不同的名字,C语言是区分大小写的。接下来,就可以使用DATE定义变量了,代码如下。


DATE d;                                      /*
定义变量d*/

上面的变量定义与如下变量定义等价。


struct date d;

2.5.4 联合体

与结构体一样,联合体也是一种派生的数据类型。但是与结构体不同的是,联合体的成员共享同一个存储空间。

1.定义联合体类型及变量

定义联合体的方式与定义结构体的方式类似,定义联合体的一般形式如下。


union 
共用体名
{
成员列表;
}
变量列表;

其中,union是C语言的关键字,用来定义联合体类型。例如,一个联合体的类型定义如下。


union data                           /*
定义联合体类型和变量abc*/
{
                int a;
                float b;
                char c;
                double d;
}abc;

以上代码是将联合体类型与变量同时定义,当然也可以先定义联合体类型,然后定义变量,例如如下语句。


union data                               /*
定义联合体类型union abc*/
{
                int a;
                float b;
                char c;
                double d;
};
union data abc;                 /*
定义联合体类型的变量abc*/

当然也可以省略联合体名data,代码如下。


union                                      /*
省略了联合体名data*/
{
                int a;
                float b;
                char c;
                double d;
};      
union data abc;                 /*
定义联合体类型的变量abc*/

以上3段代码是等价的。

从联合体的类型定义可以看出,联合体与结构体的定义非常相似。但是联合体与结构体在存储方式上却是不同的。联合体中的成员在同一时刻只有一个有效,联合体中的成员占用同一块内存单元。上面定义的变量abc在内存中的情况如图2-55所示。

图2-55 变量abc在内存中的情况

其中,变量abc中包含4个成员,它以占用内存单元最长的成员作为变量的长度。因此,abc占用8个字节,而不是4+4+1+8=17个字节。

2.引用联合体成员变量

引用联合体成员变量的方式与引用结构体成员变量的方式相同。例如,前面定义了联合体变量abc,引用变量中的成员的代码如下。


abc.a                           /*
引用联合体变量中的成员a*/
abc.b                           /*
引用联合体变量中的成员b*/
abc.c                           /*
引用联合体变量中的成员c*/

不可以整体对联合体变量进行输入和输出,以下代码的写法是错误的。


scanf("%d",&abc);            /*
错误!试图整体为联合体变量输入数据*/
printf("%d",&abc);           /*
错误!试图整体输出联合体变量的值*/

正确的写法应该是分别输入和输出成员变量的值,代码如下。


scanf("%d",&abc.a);      /*
正确!为联合体变量的成员a
输入数据*/
printf("%f",&abc.b);        /*
正确!输出联合体变量的成员b
的值*/

3.使用联合体应该注意的问题

在使用联合体时,需要注意以下几个问题。

(1)联合体变量中的成员共同占有同一块内存单元,同时只能有一个成员存放其中。同一时刻,只能有一个成员起作用,其他成员不起作用。

(2)联合体变量有效的成员是最后被赋值的成员,每存入一个新的数据,前面的成员的值不起作用。例如如下代码。


abc.a=5;
abc.b=7.5;
abc.c='r';

经过3次赋值之后,只有最后一个赋值语句有效,即联合体变量abc中的成员c的值为字符'r',成员a、b、d中的值没有意义。

(3)联合体变量中的各个成员的地址都是相同的,每个成员的存放都是从这个地址开始存放。例如&abc.a、&abc.b、&abc的值都是相同的。

(4)不能对联合体变量像结构体一样地赋值。这是因为同一时刻只能有一个成员有效。例如如下代码是错误的:


union data                                            /*
定义联合体类型*/
{
                int a;
                float b;
                char c;
                double d;
}abc={3,7.9,'x',5.6};                                 /*
错误!同一时刻只能有一个成员有效,不能这样赋值*/

(5)不能将联合体变量作为函数的参数。

4.联合体应用举例

【例2-17】 建立一个教师和学生基本情况登记表,其中,教师基本情况由编号、姓名、性别、年龄、职业和职称构成,学生基本情况由编号、姓名、性别、年龄、职业和班级构成。

【分析】 教师和学生的基本情况基本相同,只有一项不同,教师有职称而没有班级,学生有班级而没有职称,因此可以将职称和班级放在一起,构成一个联合体。将这个联合体类型与编号、姓名、性别、年龄、职业放在一起构成一个结构体,结构体的定义如下。


             struct STAFFER                     /*
定义结构体类型*/
                {
                        int num;
                        char name[20];
                        char sex[4];
                        int age;
                        int job;
                        union                   /*
定义联合体类型*/
                        {
                                int class;
                                char prof[20];
                        }category;              /*
定义联合体变量*/
                };

结构体类型为struct STAFFER,为了简便,可以将结构体类型重新命名,结构体类型的定义可以写成如下形式。


typedef struct
{
                int num;
                char name[20];
                char sex[4];
                int age;
                int job;
                union                   /*
定义联合体类型*/
                {
                        int class;
                        char prof[20];
                }category;              /*
定义联合体变量*/
}STAFFER;                               /*
结构体类型为STAFFER*/

使用typedef重新对结构体定义后,结构体类型为STAFFER。完整的程序代码如下。


#include<stdio.h>
/*------------------------
定义结构体类型STAFFER----------------------*/
typedef struct 
{
                int num;
                char name[20];
                char sex[4];
                int age;
                int job;
                union
                {
                        int class;
                        char prof[20];
                }category;
}STAFFER;
/*----------------------------
结构体类型定义结束--------------------------*/
void main()
{
                STAFFER staf[20];                                       /*
定义结构体数组staf*/
                int i;                                                  /*
定义变量i*/
/*---------------------------------
输入3
条记录-------------------------------*/
                for(i=0;i<3;i++)
                {
                        printf("
编号:");
                        scanf("%d",&staf[i].num);           /*
输入编号*/
                        printf("
姓名:");
                        scanf("%s",staf[i].name);               /*
输入姓名*/
                        printf("
性别:");
                        scanf("%s",staf[i].sex);                /*
输入性别*/
                        printf("
年龄:");
                        scanf("%d",&staf[i].age);           /*
输入年龄*/
                        printf("
职业(1
表示教师,0
表示学生)
:");
                        scanf("%d",&staf[i].job);           /*
输入职业*/
                        if(staf[i].job==1)                      /*
如果输入的是1
,表示教师*/
                        {
                                printf("
职称:");
                                scanf("%s",staf[i].category.prof);      /*
输入职称*/
                        }
                        else if(staf[i].job==0)                 /*
如果输入的是0
,则表示学生*/
                        {
                                printf("
班级:");
                                scanf("%d",&staf[i].category.class);                /*
输入班级*/
                        }
                }
/*-------------------------------
记录输入结束-----------------------------*/
/*------------------------------------
输出记录------------------------------*/
                printf("  
编号    
姓名  
性别  
年龄  
职业  
班级/
职称\n");
                for(i=0;i<3;i++)
                {
                        if(staf[i].job==1)                              /*
如果是教师*/
                                printf("%7d%7s%5s%6d%6s%8s\n",staf[i].num,staf[i].name,staf[i].sex,
                                staf[i].age,"
教师",staf[i].category.prof);
                        else if(staf[i].job==0)                         /*
如果是学生*/
                            printf("%7d%7s%5s%6d%6s%8d\n",staf[i].num,staf[i].name,staf[i].sex,
                                staf[i].age,"
学生",staf[i].category.class);
                }
}
/*----------------------------------
记录输出结束----------------------------*/

程序运行结果如图2-56所示。

图2-56 程序运行结果 8JTPuXFNg5/dnQN9oVgmM0nYc2+hLCZpOxj1YBpFOeK9f1iOzLDTw15Q6UnwtXj4

点击中间区域
呼出菜单
上一章
目录
下一章
×