C语言学习笔记(15)——结构体程序设计

前言

C语言的基本数据类型有整数型、实数型及字符型,使用这些基本数据类型可以构造数组类型,并且可以定义相关数据类型的指针。本节介绍的结构体类型区别于以上任何数据类型,它还能把各种不同类型的数据组合成一个数据整体,使一个数据体内包括多种不同类型的数据。本节详细介绍结构体的基本概念、定义和使用方法,介绍结构体的典型应用——链表,通过具体实例,介绍应用结构体的程序设计方法。


目录

前言

15.1 结构体数据概述

15.2 结构体类型和结构体变量

15.2.1 结构体数据类型的定义

15.2.2 结构体变量的定义与使用

15.3  结构体数组

15.3.1 结构体数组的定义及元素引用

15.3.2 结构体数组的初始化

15.3.3 结构体数组的应用

15.4 结构体指针变量

15.4.1 结构体指针变量的定义与使用

15.4.2 结构体指针作函数的参数

15.5 使用链表存储数据

15.5.1 链表的概念

15.5.2 链表的特点

15.5.3 定义链表结构

15.6 链表的基本操作

15.6.1 链表结点的插入

15.6.2 链表结点的删除

15.6.3 链表结点的查找


15.1 结构体数据概述

结构体数据是由多个数据项组合而成的数据。

如下图所示的学生信息表,当把每一行视为一个完整数据时,该数据就是一个结构体数据。表中的每一行反映了一个学生的综合信息,是一个学生的整体数据。每一个数据,都由多个数据项组成,包括学生的学号、姓名、性别、年龄、成绩、地址等,各数据项的数据类型也不尽相同。要表示这样一个组合数据,仅靠单一的任何一种数据类型,如整型、实型、数组等,都是不能实现的。为了有效处理这样一类组合数据,C语言提供了”结构体“技术,它可以把多个数据项组合起来,作为一个数据整体进行处理。

学生信息表

学  号

姓  名

性别

年龄

成绩

地    址

990101

Liujia

M

19

87.5

  shanghai

990102

Wangkai

M

18

89.5

  Beijing

990103

Xiaohua

F

20

81.0

  Qingdao

990104

Zhangli

F

19

82.0

 Guangzhou

990105

wangfeng

M

20

88.5

  nanchang 

与使用其他数据类型不同,在使用结构体数据时需要经过更多的步骤。下面是在程序中使用结构体数据的一般过程:​​​​​
针对具体的组合数据,定义专门的结构体数据类型;  
使用定义好的结构体数据类型,定义要使用的结构体变量;
使用定义的结构体变量存储和表示结构体数据。


15.2 结构体类型和结构体变量

要处理结构体数据,就要定义相应的结构体数据类型和结构体变量,现进行相关介绍。

15.2.1 结构体数据类型的定义

  • 结构体类型定义一般格式

定义结构体类型的一般格式如下:
struct  结构体名
{
         成员表
};

如下是对学生组合数据的结构体类型定义:
struct student
{
        int num;        /* 学号 */
        char name[20];  /* 姓名 */
        char sex;       /* 性别 */
        int age;        /* 年龄 */
        float score;    /* 成绩 */
        char addr[30];  /* 地址 */
};

  • 结构体类型定义说明

(1)结构体名是用户定义的结构体的名字,在以后定义结构体变量时,使用该名字进行类型标识。
(2)成员表是对结构体数据中每一个数据项的变量说明,其格式与说明一个变量的一般格式相同,如:数据类型名 成员名;
(3)“struct是关键字,struct 结构体名是结构体类型标识符,在类型定义和类型使用时,struct都不能省略。
(4)结构体名称可以省略,此时定义的结构称为无名结构。
(5)整个定义作为一个完整的语句用分号结束。
(6)结构体成员名允许和程序中的其他变量同名,二者不会混淆。
(7)在程序中,结构体的定义可以在一个函数的内部进行,也可以在所有函数的外部进行。在函数内部定义的结构体,仅在该函数内部有效,而定义在外部的结构体,在其后出现的所有函数中都可以使用。

  • 结构体的嵌套

C语言结构体成员的数据类型既可以是简单的数据类型,也可以是复杂的数据类型,成员也可以是一个结构体。当结构体的成员又是结构体时,称为结构体的嵌套

例如:

struct date
{
    int month;
    int day;
    int year;
}; 

struct stud
{
    int num;
    char name[20];
    char sex;
    int age;
    struct date birthday;
    char addr[30];
}stud1,stud2; 

由此定义的struct stud结构如下表所示:

num

name

sex

age

birthday

addr

month

day

year

15.2.2 结构体变量的定义与使用

  • 定义结构体变量

结构体类型的定义只说明了它的组成,要使用该结构体必须定义结构体类型的变量。在程序中,结构体类型的定义要先于结构体变量的定义,不能用尚未定义的结构体类型对变量进行定义。结构体变量的定义有三种方法。

(1)先定义结构体类型,再定义结构体变量。一般格式为:struct 结构体名称 结构变量名;
若已经定义了结构体类型student,可以用它来定义变量。例如:
struct student student1,student2;//该语句定义了student1、student2两个struct student类型的结构体变量

存储时,结构体变量的各个成员,按照定义时的顺序依次占用连续的存储空间,所占内存空间的长度不小于结构体中每个成员的存储长度之和。

(2)在定义结构体类型的同时定义结构体变量。一般格式如下:
struct 结构体名
{
         成员表
}结构体变量1, 结构体变量2,┅,结构体变量n;

例如:
struct student
{
int num;        /* 学号 */
char name[20];  /* 姓名 */
char sex;       /* 性别 */
int age;        /* 年龄 */
float score;    /* 成绩 */
char addr[30];  /* 地址 */
}student1,student2;

(3)不定义结构体类型名,直接定义结构类型变量。一般格式如下:
struct
{
        成员表
}结构体变量1, 结构体变量2,┅,结构体变量n;

例如:
struct
{
        int num;        /* 学号 */
        char name[20];  /* 姓名 */
        char sex;       /* 性别 */
        int age;        /* 年龄 */
        float score;    /* 成绩 */
        char addr[30];  /* 地址 */
}student1,student2;

  • 引用结构体成员

结构作为若干成员的集合是一个整体,但在使用结构时,不仅要对结构整体进行操作,而且更多的是要访问结构中的每个成员。在程序中使用结构体成员的方法为:结构体变量名.成员名称

 如,当使用student1变量的age成员时,用如下方式:
     student1.age

.是结构体成员运算符,.操作的优先级在C语言中是最高的,其结合性为从左到右。

程序示例(输入一个学生的一组数据,然后输出其姓名、年龄和地址)。

#include "stdio.h"
struct student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}stu; 

main()
{
    printf("Enter num,age,score:");
    scanf("%d,%d,%f",&stu.num,&stu.age,&stu.score);
    printf("Enter name:");
    getchar();
    gets(stu.name);
    printf("Enter sex:");
    scanf("%c",&stu.sex);
    printf("Enter address:");
    getchar();
    gets(stu.addr);
    printf("name:%s,age:%d,address:",stu.name,stu.age);
    puts(stu.addr);
}
  • 结构体变量的使用说明

(1)结构体变量输入输出时,只能以成员引用的方式进行,不能对结构体变量进行整体的输入输出。

(2)当成员又是一个结构体类型时,若要引用它的成员,要从高到低逐级引用

(3)与其他变量一样,结构体变量成员可以进行各种运算。而作为代表结构整体的结构体变量,要进行整体操作就有很多限制,仅在以下两种情况下,可以把结构体变量作为一个整体来访问:
结构体变量整体赋值,此时必须是同类型的结构体变量
例如:student2=student1; //该赋值语句把student1变量中各成员的值,对应传送给student2变量的同名成员,从而使student2具有与student1完全相同的值。
取结构体变量地址。通常把结构体变量的地址用作函数的参数。例如:&student1。

  • 结构体变量的初始化 

结构体变量初始化的一般形式如下:
struct 结构体名 结构体变量={初始化数据};

例如:
struct student
{
long num;        /* 学号 */
char name[20];  /* 姓名 */
char sex;       /* 性别 */
int age;        /* 年龄 */
float score;    /* 成绩 */
char addr[30];  /* 地址 */
} stu={990101,"liujia",'M',19,87.5,"shanghai"};

说明:
(1)初始化数据的个数与结构体成员的个数相同,它们是按成员的先后顺序一一对应赋值的。
(2)每个初始化数据必须符号与其对应和成员的数据类型。


15.3  结构体数组

数组元素是结构体类型的数组,称为结构体数组。结构体数组具有数组的一切性质:数组元素具有相同的类型;结构体数组中每个元素的起始下标从0开始;数组名称表示该结构体数组的存储首地址;结构体数组存放在一个连续的内存区域中,它所占内存数目为结构体类型的大小乘以数组元素的个数;结构体数组名和数组的指针都可以作为函数的参数等。

15.3.1 结构体数组的定义及元素引用

  • 定义结构体一维数组

定义结构体数组的方法有两种:

(1)先定义结构体类型,然后用结构体类型定义数组变量。即结构体类型 数组名[长度]
例如:struct student information[100];

(2)在定义结构体类型的同时,定义数组变量,该结构体类型可以是有名类型,也可以是无名类型。
例如:
struct date
{
        int year,month,day;
}date1[10],date2[10];

  • 引用结构体数组元素

当指明了数组元素后,即可访问该数组元素的某个成员。一般形式如下:
结构体数组名[下标].成员名

例如:
information[20].score=91; //该语句为结构体数组元素information[20]的score成员赋值。

15.3.2 结构体数组的初始化

结构体数组的初始化是在定义结构体数组时为它的元素赋初值。

例如:
struct student info[3]={{990101,"liujia",'M',19,87.5,"shanghai"},
{990102,"wangkai",'M',18,9.5,"beijing"}, 
{990103,"xiaohua",'F',820,81.0,"qingdao"}};
上面语句定义了struct student类型的结构体数组info,并对数组元素info[0],info[1],info[2]进行了初始化。

15.3.3 结构体数组的应用

按照表13-1的数据情况,输入一个班级的学生信息,要求:
分别统计出男生和女生人数。
 把学习成绩在85以上的学生找出来,并输出这部分学生如下信息:姓名、成绩、地址。

程序如下:

#define N 10
struct student  
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};

main()
{
    struct student stu[N];
    int i,count_m=0,count_f=0;
    for(i=0;i85)   
            printf("name:%s,score:%d,address:%d",stu[i].name,stu[i].score,stu[i].addr);
    }
    printf("\nschoolboy=%d, schoolgirl=%d\n",count_m,count_f);
}
 

15.4 结构体指针变量

指向结构体数据的指针变量,简称为结构体指针变量。与其他类型的指针变量一样,结构体指针变量既可以指向单一的结构体变量,也可以指向结构体数组,结构体指针变量还可以作为函数的参数。

15.4.1 结构体指针变量的定义与使用

  • 概念

定义结构体指针的一般形式:struct 结构体名 *结构体指针变量名;

例如:
已定义结构体类型struct student,则可用如下形式定义结构体指针变量p和q:
struct student *p,*q; 
指针变量p、q既可以指向单一的结构体变量,也可以指向结构体数组。例如:
struct student stud1,info[10];
p=&stud1;  //p指向结构体变量stud1
q=info;  //q指向结构体数组info

当定义的结构体指针变量指向具体的对象之后,凡是可以使用结构体变量名引用的成员,都可通过指针变量用指向运算符“->”引用结构体中的成员。例如:变量stud1的成员num,既可以用stud1.num引用,也可以是p->num引用,二者是等价的。而用指向结构体数组的指针引用成员时,需要使用指针的下标形式例如,对于info数组中info[1]元素的num成员,既可以用info[1].num引用,也可以用q[1]->num引用。

对于指向结构体数组的指针变量,当指针进行加1运算时,其结果是指向下一个结构体数组元素。

  • 结构体指针用法示例
#include "stdio.h"
#include "string.h"
struct stud
{
    char *number;
    char name[20];
    int score;
};

main()
{
    struct stud student,*p;
    p=&student; //p指向结构体变量student
    p->number="991001";
    strcpy(p->name,"changjiang"); //用字符串复制函数为name成员添加数据
    student.score=91;
    printf("\n");
    printf("student No.%s\nname:%s\nscore:%d\n",p->number,p->name,p->score);
}
  • 指向结构体数组的指针应用示例
#include "stdio.h"
struct stud  
{ 
    char *number;
    char name[20];
    int score;
}stu[3]={"990103","xiaohua",81,"990104","zhangli",82,"990105","wangfeng",88};

main()
{
    struct stud *p;
    printf("\nstudent No.name score\n");
    for(p=stu;pnumber,p->name,p->score);
}

15.4.2 结构体指针作函数的参数

结构体指针作函数的参数,与其他数据类型的指针作函数的参数本质上没有区别,只是指针的数据类型不同而已。

用结构体指针作函数的参数,实现删去成绩统计功能。程序如下:

#include "stdio.h"
#define N  5

struct stu_info
{
    char num[7];
    int score;
}stu[]={"990101",87,"990102",89,990103",81,"990104",82,"990105",88};

main()
{
    void sort_stu(struct stu_info *,int); 
    int i;
    sort_stu(stu,N);
    for(i=0;i

15.5 使用链表存储数据

15.5.1 链表的概念

链表是结构体最重要的应用,它是一种非固定长度的数据结构,是一种动态存储技术,它能够根据数据的结构特点和数量使用内存,尤其适用于数据个数可变的数据存储

使用链表存储数据的原理与数组不同,它不需要事先说明要存储的数据数量,系统也不会提前为它准备大的存储空间,而是当需要存储数据时,通过动态内存分配函数malloc()或calloc()向系统获取一定数量的内存,用于数据存储。需要多少,就申请多少,系统就分配多少

本节讨论一个使用链表存储数据的示例:使用链表存储表15-1中学生信息表的前3个学生数据。

用链表存储学生数据时的概括描述如下:
①申请一段内存M,并把它分成两部分:一部分为数据区,用于存储数据;另一部分为地址区,用于存储下一次申请到的内存段的首地址。
②将一个学生数据存储在M的数据区中。
③若当前是第一个数据,则将M的首地址保存在指针变量head中;否则将M的首地址保存在存储上一个数据的内存段中。
④重复①②③的过程,直到所有数据都存储完毕,在最后一段内存的地址区位置存储一个结束标志NULL。

产生的链表如下图所示。

C语言学习笔记(15)——结构体程序设计_第1张图片

构成链表的每一个独立的内存段称为一个链表结点,结点中存储数据的部分称为结点数据域,存储下一个结点地址的部分称为结点指针域,指向第一个结点的指针称为链表的头指针。只要有了头指针,就能沿着指针链遍历链表的每一个结点。如果链表不提供头指针,那么链表的任何一个结点都将无法访问。

15.5.2 链表的特点

链表作为一种动态的数据结构,具有如下特点:
链表中的结点具有完全相同的结构,每一个存储一个独立的结构体数据;
链表的结点由系统随机分配,它们在内存中的位置可能是相邻的,也可能是不相邻的,结点之间的联系是通过指针域实现的;
为了能准确的定位第一个结点,每个链表要有一个表头指针,从第一个结点开始,沿指针链能遍历链表中的所有结点;
链表中的结点是在需要时用calloc()申请的,当不再需要时,应有free()函数释放所占用的内存段。
一个链表不需要事先说明它要包括的结点数目,在需要存储新的数据时,就可增加结点,需要删除数据时,就减少结点,链表结点是动态变化的。

与数组相比,其特点是:存储长度可变;插入、删除数据时不需大量移动其他数据;但访问数据时,不如数组方便。

15.5.3 定义链表结构

定义一个链表结点的结构,需要包括两个方面:一方面定义数据存储所对应的各个成员;另一方面定义指向其他结点的指针成员

例如,假若要用链表逐个存储一批整数,其结点结构可定义如下:
struct node
{
 int data;
 struct node *next;  /* 指向struct node类型的指针 */
};

 如下是生成一个链表结点的一般过程
(1)向系统申请一个内存段,其大小由结点的数据类型决定。
例如,calloc(1,sizeof(struct node)) 。
(2)指定内存段的数据类型。形式为:p=(结点数据类型 *)calloc(1,sizeof(结点数据类型));例如,可使用如下形式申请一个struct node类型的结点:p=(struct node *)calloc(1,sizeof(struct node));
(3)为申请得到的结点添加数据。这一过程,就是为结构体变量的各个成员赋值。为指针域成员赋值的目的是使一个独立的结点链接到链表上。 

前面15-1实例的结点结构可定义如下:
struct student
{
 int num;                    /* 学号 */
 char name[20];        /* 姓名 */
 char sex;                  /* 性别 */
 int age;                     /* 年龄 */
 float score;              /* 成绩 */
 char addr;              /* 地址 */
 struct student *next;/*  指向struct student类型的指针 */
};
其中,num、name、sex、age、score、addr是结点数据域的成员,next是结点指针域的成员。


15.6 链表的基本操作

对链表的操作有多种,基本操作是在链表中插入结点、删除结点、查找结点等,这些基本操作也是链表其他操作的基础,现分别予以介绍。为方便约束,约定如下:
(1)不特别指明链表的头指针时,head即为链表的头指针。
(2)在一般性描述中,使用的结点类型为struct node型。
(3)把p指针指向的结点称为p结点。

15.6.1 链表结点的插入

在链表中插入结点,就是把一个新结点连接到链表中。通常有两种情况:一种情况是在空链表中插入一个结点,此时,插入的结点既是链表的第一个结点,也是链表的最后一个结点;另一种情况是在链表的p结点之后插入一个新结点。

  • 在空链表中插入一个结点

空链表就是头指针head为空的链表。

⑴ 用如下语句申请一个new结点:new=(struct node *)calloc(1,sizeof(struct node));
⑵ 为p结点填充数据:将要存储的数据对应传给p结点数据域的各个成员;
⑶ 修改有关指针的指向:
    ①将head指向new结点。
    ②将new的next成员置空,使new结点成为链表的最后一个结点;

C语言学习笔记(15)——结构体程序设计_第2张图片

  • head链表的p结点之后插入一个结点

head链表和要插入结点new如下图所示。要将new结点插入在p结点之后,就是将new结点变成结点C的下一个结点,而使new的下一个结点成为结点D

newp结点的指针域进行如下操作:
使new的指针域存储结点D的首地址:new->next=p->next
new的首地址存储到结点p的指针域中:p->next=new

如上两步操作完成后,即在p结点后插入了new结点。插入后的链表如图所示。

C语言学习笔记(15)——结构体程序设计_第3张图片

  • 程序示例

(1) 如下是head链表的p结点之后插入值为m的结点的函数insert(),函数的返回值是链表的头指针。

#include "malloc.h"
#include "stdio.h"

struct student *insert(struct node *head,struct node *p,int m)
{
    struct node *new;
    new=(struct node *)calloc(1,sizeof(struct node));
    new->data=m;
    if(head==NULL)
    {
        head=new;
        head->next=NULL;
    }
    else
    {
        new->next=p->next;
        p->next=new;
    }
    return(head);
}

(2)用插入结点的方法建立下图所示的学生成绩链表,链表head10个结点,每个结点存储一个学生的学号和学习成绩数据。

#include "stdio.h"
#define N 5

struct s_node 
{
    char num[4];
    float score;  
    struct s_node *next;
};

main()
{
    struct s_node *creat_node(void);
    struct s_node *creat_list(int n);  
    void out_list(struct s_node *head);
    struct s_node *head=NULL;
    head=creat_list(N); 
    out_list(head);  
}

struct s_node *creat_node(void)
{
    struct s_node *p;
    float score;
    fflush(stdin);
    p=(struct s_node *)calloc(1,sizeof(struct s_node));
    gets(p->num);
    scanf("%f",&score);
    p->score=score;
    p->next=NULL;
    return(p);
}

struct s_node *creat_list(int n)
{
    struct s_node *new,*p;
    struct s_node *head;
    int i;
    if(n>=1)
    {
        new=creat_node(); 
        head=new;
        p=new;
    }
    for(i=2;i<=n;i++)
    {
        new=creat_node();
        p->next=new;
        p=new;
    }
    if(n>=1)
        return(head);
    else
        return(NULL);
}

void out_list(struct s_node *head)
{
    struct s_node *p;
    if(head!=NULL)
    {
        p=head;
        while(p!=NULL)
        {
            printf("%s  %f\n",p->num,p->score);
            p=p->next;
        }
    }
}

15.6.2 链表结点的删除

在链表中删除结点一般有两个过程:一是把指定的结点从链表中拿下来,它需要通过修改有关结点的指针域来完成;二是释放该结点使用的内存空间,它需要使用free()函数来实现。

C语言学习笔记(15)——结构体程序设计_第4张图片

上图是一个head链表,删除p结点的过程如下:
p结点是链表的第一个结点,则将p指针域的地址保存到head中,使p的后继结点成为head链表的第一个结点,然后转步骤⑶。修改指针的操作如下:head=head->next;或    head=p->next; 
p结点不是链表的第一个结点,则首先从head开始,找到p结点的前一个结点q,然后使q的指针域存储p的后继结点的地址,这样沿链表的指针访问链表中的结点时,p结点将不会被访问到,亦即p结点从链表head中被删除了。链表指针的修改操作如下:  q->next=p->next
释放p结点使用的内存空间。操作为:free(p)

C语言学习笔记(15)——结构体程序设计_第5张图片

如下是在head链表中删除p结点的delete()函数:

#include "stdio.h"
void delete(struct node *head,struct node *p)
{
    struct node *q;
    q=head;
    if(p==head)
        head=head->next;     /* p是第一个结点是,修改链表的头指针 */
    else
    {
        while(q->next!=p)    /* 找到p的前一个结点的地址 */
            q=q->next;
        q->next=p->next;     /* 删除p结点 */
    }
    free(p);               /* 释放p结点占用的内存 */
}

15.6.3 链表结点的查找

  • 概念

在链表中进行查找,就是从链表的第一个结点开始,沿着指针链,用查找值与链表结点逐个比较的过程。找到符合要求的结点之后,停止查找过程,返回相应结点的指针,否则返回一个空指针。

如下是在head链表中查找data值为m的结点的过程,其中pstruct node型指针:
p=head;
pNULL时,若p->data==m,则找到要求结点,查找结束,返回结点地址p;否则,执行下一步⑶;当p== NULL时,链表中不存在要找的结点,查找结束,返回空指针NULL;
p=p->next/* p指向下一个结点 */
重复⑵、⑶步骤。

链表结点的查找函数find()设计如下: 

​​​​​​​#include "stdio.h"

struct node *find(struct node *head,int m)
{
    struct node *p=head;
    while(p!=NULL&&p->data!=m)
        p=p->next;
    if(p==NULL)
        return(NULL);
    else
        return(p);
}
  • 程序示例

对如下图所示的head链表,删除其值是x的结点。具体要求如下:⑴ 输出被删除结点的值;⑵ 当指定值结点不存在时,显示一个提示信息;x的值由键盘输入。

该问题的关键点有两点:第一是查找data等于x的结点p第二是删除p结点。需要注意的是,p结点删除之后,要将其使用的内存释放。

程序如下:

#include "stdio.h"

struct node  
{
    int data;
    struct node *next;
};

struct node *find_x(struct node *head,int x)
{
    struct node *p,*q;
    p=q=head;  
    while(p!=NULL&&p->data!=x)  
    {
        q=p;
        p=p->next;
    }
    if(p==NULL)
        return(NULL);
    else  
        return(q);
}

void delete_p(struct node *head, struct node *p)
{
    struct node *q;
    if(p==NULL) 
    {
        printf("no found\n");
        return;
    }
    if(p==head)    
    {
        head=p->next;
        q=p;
    }
    else   
    {
        q=p->next;
        p->next=q->next;
    }
    printf("\ndelete: %d\n",q->data);
    free(q);
}

struct node *creat_number(int n) 
{
    int i;
    struct node *head,*new,*p;
    if(n>=1)
    {
        new=(struct node*)malloc(sizeof(struct node));
        new->data=1;
        new->next=NULL;
        head=new;
        p=new;
    }
    for(i=2;i<=n;i++) 
    {
        new=(struct node*)malloc(sizeof(struct node));
        new->data=i;
        new->next=NULL;
        p->next=new;
        p=new;
    }
    if(n>=1)  return(head);
    else   return(NULL);
}

main()
{
    int n,x;
    struct node *head,*p;
    printf("Enter n,x: ");
    scanf("%d,%d",&n,&x);
    head=creat_number(n);
    p=find_x(head,x);
    delete_p(head,p);
}

你可能感兴趣的:(C语言,c语言)