C语言程序设计(十二) 结构体和共用体

时间:2024-06-02 21:06:20

第十二章 结构体和共用体

当需要表示复杂对象时,仅使用几个基本数据类型显然是不够的

根本的解决方法是允许用户自定义数据类型

构造数据类型(复合数据类型)允许用户根据实际需要利用已有的基本数据类型来构造自己所需的数据类型

它们是由基本数据类型派生而来的,用于表示链表、树、堆栈等复杂的数据对象

抽象数据类型不再单纯是一组值的集合,还包括作用在值集上的操作的集合

即在构造数据类型的基础上增加了对数据的操作,且类型的表示细节和操作的实现细节是对外不可见的

结构体类型数据存储的优点:

  • 结构紧凑,易于管理
  • 局部数据的相关性强,便于查找
  • 赋值时只针对某个具体的对象,局部的输入错误不会影响全局
  • 结构体:对关系紧密、逻辑相关、具有相同或者不同属性的数据进行处理
  • 共用体:数据成员的情形是互斥的,每一时刻只有一个数据成员起作用

结构体模板的定义格式:

struct 结构体名
{
数据类型 成员1的名字;
数据类型 成员2的名字;
……
数据类型 成员n的名字;
};
  • 结构体的名字,称为结构体标签
  • 构成结构体的变量,称为结构体成员

结构体模板只是声明了一种数据类型,定义了数据的组织形式,并未声明结构体类型的变量

typedef定义数据类型:

typedef用于为系统固有的或程序员自定义的数据类型定义一个别名

typedef int INTEGER;

typedef struct student STUDENT;

结构体变量的初始化:

STUDENT stu1 = {100310121,"王刚",'M',1991,{72,83,90,82}};

嵌套的结构体

typedef struct date
{
int year;
int month;
int day;
}DATE;
typedef struct student;
{
long studentID;
char studentName[10];
char studentSex;
DATE birthday;
int score[4];
}STUDENT;

结构体变量的引用:

不能将一个结构体变量作为一个整体进行输入、输出操作,只能对每个具体的成员进行输入、输出操作

因此,需要使用成员选择运算符(圆点运算符)

结构体变量名.成员名

当出现结构体嵌套时,必须以级联方式访问结构体数据,即通过成员选择运算符逐级找到最底层的成员时再引用

//L12-1

#include  <stdio.h>
typedef struct date
{
int year;
int month;
int day;
}DATE;
typedef struct student
{
long studentID; /* 学号 */
char studentName[10]; /* 姓名 */
char studentSex; /* 性别 */
DATE birthday; /* 出生日期 */
int score[4]; /* 4门课程的成绩 */
}STUDENT;
  • 结构体类型的声明既可放在函数体外部,也可放在函数体内部
  • 在函数体外声明的结构体类型可为所有函数使用,称为全局声明
  • 在函数体内声明的结构体类型只能在本函数体内使用,称为局部声明
int main()
{
STUDENT stu1 = {100310121, "王刚", 'M', {1991,5,19}, {72,83,90,82}};
STUDENT stu2;
stu2 = stu1; /* 同类型的结构体变量之间的赋值操作 */
printf("stu2:%10ld%8s%3c%6d/%02d/%02d%4d%4d%4d%4d\n",
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;
}

结构体所占内存的字节数:

//L12-2

#include  <stdio.h>
typedef struct sample
{
char m1;
int m2;
char m3;
}SAMPLE; /* 定义结构体类型SAMPLE */
int main()
{
SAMPLE s = {'a', 2, 'b'}; /* 定义结构体变量s并对其进行初始化 */
printf("bytes = %d\n", sizeof(s));/* 打印结构体变量s所占内存字节数 */
return 0;
}
//运行结果
bytes = 12

内存对齐:处理器在处理结构体数据等特定的数据类型时,为了满足处理器的对齐要求,可能会在较小的成员后加入补位

系统为结构体变量分配内存的大小,或者说结构体类型所占的字节数,并非所有成员所占内存字节数的总和,它不仅与所定义的结构体类型有关,还与计算机系统本身有关

结构体数组:

//L12-3

#include  <stdio.h>
typedef struct date
{
int year;
int month;
int day;
}DATE;
typedef struct student
{
long studentID; /* 学号 */
char studentName[10]; /* 姓名 */
char studentSex; /* 性别 */
DATE birthday; /* 出生日期 */
int score[4]; /* 4门课程的成绩 */
}STUDENT;
int main()
{
int i, j, sum[30];
STUDENT stu[30] = {{100310121,"王刚",'M',{1991,5,19},{72,83,90,82}},
{100310122,"李小明",'M',{1992,8,20},{88,92,78,78}},
{100310123,"王丽红",'F',{1991,9,19},{98,72,89,66}},
{100310124,"陈莉莉",'F',{1992,3,22},{87,95,78,90}}
};
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;
}
//运行结果
100310121 王刚 M 1991/05/19 72 83 90 82 81.8
100310122 李小明 M 1992/08/20 88 92 78 78 84.0
100310123 王丽红 F 1991/09/19 98 72 89 66 81.3
100310124 陈莉莉 F 1992/03/22 87 95 78 90 87.5

指向结构体变量的指针:

STUDENT *pt = &stu1;

访问方式

  • 成员选择运算符(圆点运算符)
  • 指向运算符(剪头运算符)

指向结构体的指针变量名->成员名

//L12-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()
{
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;
}
//运行结果
Before function call:1999/04/23
After function call:1999/04/23

向函数传递结构体变量时,实际传递给函数的是该结构体变量成员值的副本,即结构体变量的成员值是不会在被调函数中被修改的

仅当将结构体的地址传递给函数时,结构体变量的成员值才可以在被调函数中被修改

//L12-5

#include  <stdio.h>
struct date
{
int year;
int month;
int day;
};
void Func(struct date *pt) /* 结构体指针变量作函数形参 */
{
pt->year = 2000;
pt->month = 5;
pt->day = 22;
}
int main()
{
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;
}
//运行结果
Before function call:1999/04/23
After function call:2000/05/22

从函数返回结构体变量的值:

//L12-6

#include  <stdio.h>
struct date
{
int year;
int month;
int day;
};
struct date Func(struct date p) /* 函数的返回值为结构体类型 */
{
p.year = 2000;
p.month = 5;
p.day = 22;
return p; /* 从函数返回结构体变量的值 */
}
int main()
{
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;
}

//L12-7

#include  <stdio.h>
#define N 30
typedef struct date
{
int year;
int month;
int day;
}DATE;
typedef struct student
{
long studentID; /* 学号 */
char studentName[10]; /* 姓名 */
char studentSex; /* 性别 */
DATE birthday; /* 出生日期 */
int score[4]; /* 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()
{
float aver[N];
STUDENT stu[N];
int n;
printf("How many student?");
scanf("%d", &n);
InputScore(stu, n, 4);
AverScore(stu, aver, n, 4);
PrintScore(stu, aver, n, 4);
return 0;
}
/* 输入n个学生的学号、姓名、性别、出生日期以及m门课程的成绩到结构体数组stu中 */
void InputScore(STUDENT stu[], int n, int m)
{
int i, j;
for (i=0; i<n; i++)
{
printf("Input record %d:\n", i+1);
scanf("%ld", &stu[i].studentID);
scanf("%s", stu[i].studentName);
scanf(" %c", &stu[i].studentSex); /* %c前有一个空格 */
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]);
}
}
}
/* 计算n个学生的m门课程的平均分,存入数组aver中 */
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<m; j++)
{
sum[i] = sum[i] + stu[i].score[j];
}
aver[i] = (float)sum[i]/m;
}
}
/* 输出n个学生的学号、姓名、性别、出生日期以及m门课程的成绩 */
void PrintScore(STUDENT stu[], float aver[], int n, int m)
{
int i, j;
printf("Results:\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]);
}
}
//运行结果
How many student?4
Input record 1:
100310121 王刚 M 1991 05 19 72 83 90 82
Input record 2:
100310122 李小明 M 1992 08 20 88 92 78 78
Input record 3:
100310123 王丽红 F 1991 09 19 98 72 89 66
Input record 4:
100310124 陈莉莉 F 1992 03 22 87 95 78 90
Results:
100310121 王刚 M 1991/05/19 72 83 90 82 81.8
100310122 李小明 M 1992/08/20 88 92 78 78 84.0
100310123 王丽红 F 1991/09/19 98 72 89 66 81.3
100310124 陈莉莉 F 1992/03/22 87 95 78 90 87.5

共用体:

共用体,也称联合,是将不同类型的数据组织在一起共同占用同一段内存的一种构造数据类型

共用体与结构体的声明方法类似,只是关键字变为union

//L12-8

#include  <stdio.h>
union sample
{
short i;
char ch;
float f;
}; /* 定义共用体类型union sample的模板 */
typedef union sample SAMPLE; /* 定义union sample的别名为SAMPLE */
int main()
{
printf("bytes = %d\n", sizeof(SAMPLE));/*打印共用体类型所占内存字节数*/
return 0;
}
//运行结果
bytes = 4

与结构体不同的是,共用体是从同一起始地址开始存放成员的值,即共用体中不同类型的成员共用同一段内存单元,因此共用体类型所占内存空间的大小取决于其成员中占内存空间最多的那个成员变量

共用体使用覆盖技术来实现内存的共用,即当对一个成员进行赋值操作时,其他成员的内容将被改变而失去自身的意义

由于同一内存单元在每一瞬时只能存放其中一种类型的成员,也就是说同一时刻只有一个成员是有意义的

因此,在每一瞬时起作用的成员就是最后一次被赋值的成员

不能为共用体的所有成员同时进行初始化,只能对第一个成员进行初始化

此外,共用体不能进行比较操作,也不能作为函数参数

共用体可以使用成员选择运算符或指向运算符来访问共用体的成员变量

采用共用体存储程序中逻辑相关但情形互斥的变量,使其共享内存空间

不仅可以节省内存空间,还可以避免因操作失误引起逻辑上的冲突

枚举数据类型:

当某些量仅由有限个数据值组成时,通常用枚举类型表示

枚举数据类型描述的是一组整型数据值的集合

enum responder{no, yes, none};
enum responder answer;

除非特别指定,一般情况下第1个枚举常量的值为0,第2个枚举常量的值为1,以后依次增1

枚举常量只能作为整型值而不能作为字符串来使用

单向链表:

结构体和指针配合使用可以表示许多复杂的动态数据结构,如链表、堆栈、对列、树、图等

链表实际是线性表的链式存储结构,它是用一组任意的存储单元来存储线性表中的数据

存储单元不一定是连续的,且链表的长度不是固定的

  • 节点
  • 数据域
  • 指针域

链表只能顺序访问,不能随机访问

单向链表的建立:

#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()
{
int i = 0;
char c;
struct link *head = NULL; /* 链表头指针 */
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&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); /* %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);/* 打印第j个节点的数据 */
p = p->next; /* 让p指向下一个节点 */
j++;
}
}
/* 函数功能:释放head指向的链表中所有节点占用的内存 */
void DeleteMemory(struct link *head)
{
struct link *p = head, *pr = NULL;
while (p != NULL) /* 若不是表尾,则释放节点占用的内存 */
{
pr = p; /* 在pr中保存当前节点的指针 */
p = p->next; /* 让p指向下一个节点 */
free(pr); /* 释放pr指向的当前节点占用的内存 */
}
}

单向链表的删除操作:

#include <stdio.h>
#include <stdlib.h>
struct link *AppendNode(struct link *head);
void DisplyNode(struct link *head);
void DeleteMemory(struct link *head);
struct link *DeleteNode(struct link *head, int nodeData); struct link
{
int data;
struct link *next;
};
int main()
{
int i = 0;
char c;
struct link *head = NULL; /* 链表头指针 */
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&c); /* %c前面有一个空格 */
while (c=='Y' || c=='y')
{
head = AppendNode(head);
DisplyNode(head); /* 显示当前链表中的各节点信息 */
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&c); /* %c前面有一个空格 */
i++;
}
printf("%d new nodes have been apended!\n", i);
head = DeleteNode(head, 20);
DisplyNode(head);
DeleteMemory(head); /* 释放所有动态分配的内存 */
return 0;
}
/* 函数功能:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针 */
struct link *AppendNode(struct link *head)
{
struct link *p = NULL;
struct link *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; /* 将新建节点添加到链表的末尾 */
}
pr = p; /* 让pr指向新建节点 */
printf("Input node data:");
scanf("%d", &data); /* 输入节点数据 */
pr->data = data;
pr->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);/* 打印第j个节点的数据 */
p = p->next; /* 让p指向下一个节点 */
j++;
}
}
/* 函数功能:释放head指向的链表中所有节点占用的内存 */
void DeleteMemory(struct link *head)
{
struct link *p = head, *pr = NULL;
while (p != NULL) /* 若不是表尾,则释放节点占用的内存 */
{
pr = p; /* 在pr中保存当前节点的指针 */
p = p->next; /* 让p指向下一个节点 */
free(pr); /* 释放pr指向的当前节点占用的内存 */
}
}
/* 函数功能:从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;
p = p->next;
}
if (nodeData == p->data) /* 若找到节点nodeData,则删除该节点 */
{
if (p == head) /* 若待删除节点为首节点,则让head指向第2个节点 */
{
head = p->next;
}
else /*若待删除节点不是首节点,则将前一节点的指针指向当前节点的下一节点*/
{
pr->next = p->next;
}
free(p); /* 释放为已删除节点分配的内存 */
}
else /* 没有找到待删除节点 */
{
printf("This Node has not been found!\n");
}
return head; /* 返回删除节点后的链表的头节点指针 */
}

//单向链表的插入操作

#include <stdio.h>
#include <stdlib.h>
struct link *AppendNode(struct link *head);
void DisplyNode(struct link *head);
void DeleteMemory(struct link *head);
struct link *InsertNode(struct link *head, int nodeData); struct link
{
int data;
struct link *next;
};
int main()
{
int i = 0;
char c;
struct link *head = NULL; /* 链表头指针 */
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&c); /* %c前面有一个空格 */
while (c=='Y' || c=='y')
{
head = AppendNode(head);
DisplyNode(head); /* 显示当前链表中的各节点信息 */
printf("Do you want to append a new node(Y/N)?");
scanf(" %c",&c); /* %c前面有一个空格 */
i++;
}
printf("%d new nodes have been apended!\n", i);
head = InsertNode(head, 20);
DisplyNode(head);
DeleteMemory(head); /* 释放所有动态分配的内存 */
return 0;
}
/* 函数功能:新建一个节点并添加到链表末尾,返回添加节点后的链表的头指针 */
struct link *AppendNode(struct link *head)
{
struct link *p = NULL;
struct link *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; /* 将新建节点添加到链表的末尾 */
}
pr = p; /* 让pr指向新建节点 */
printf("Input node data:");
scanf("%d", &data); /* 输入节点数据 */
pr->data = data;
pr->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);/* 打印第j个节点的数据 */
p = p->next; /* 让p指向下一个节点 */
j++;
}
}
/* 函数功能:释放head指向的链表中所有节点占用的内存 */
void DeleteMemory(struct link *head)
{
struct link *p = head, *pr = NULL;
while (p != NULL) /* 若不是表尾,则释放节点占用的内存 */
{
pr = p; /* 在pr中保存当前节点的指针 */
p = p->next; /* 让p指向下一个节点 */
free(pr); /* 释放pr指向的当前节点占用的内存 */
}
}
/* 函数功能:在已按升序排序的链表中插入一个节点,返回插入节点后的链表头指针 */
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; /* 为待插入节点数据域赋值为nodeData */
if (head == NULL) /* 若原链表为空表 */
{
head = p; /* 待插入节点作为头节点 */
}
else
{ /* 若未找到待插入节点的位置且未到表尾,则继续找 */
while (pr->data < nodeData && pr->next != NULL)
{
temp = pr; /* 在temp中保存当前节点的指针 */
pr = pr->next;/* pr指向当前节点的下一节点 */
}
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; /* 返回插入新节点后的链表头指针head的值 */
}