C语言结构体复习

 

 结构体的定义方式和变量以及数组的定义方式相同,只是不能初始化

需要注意的是,大括号后面的分号不能省,因为这是一条完整的语句

结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。像 char、int、float 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。

 它定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字 struct 不能少。stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。

你也可以在定义结构体的同时定义结构体变量,只需将结构体变量放在结构体定义的最后即可:

你也可以在定义结构体的同时定义结构体变量,只需将结构体变量放在结构体定义的最后即可:

struct stu {
    char *name; //姓名
    int num; //学号
    int age; //年龄
    char group; //小组
    float score; //成绩
} stu1, stu2;

如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名:

struct {
    char *name; //姓名
    int num; //学号
    int age; //年龄
    char group; //小组
    float score; //成绩
} stu1, stu2;

这样做书写简单,但是因为没有结构体名,后面就没法用该结构体定义新的变量。

理论上讲结构体的各个成员在内存中是连续存储的,和数组非常类似,例如,结构体变量 stu1、stu2 的内存分布就是这样的:

 

共占用 17 个字节。

但是在编译器的具体实现中,各个成员之间可能会存在缝隙,对于 stu1、stu2,成员变量 group 和 score 之间就存在 3 个字节的空白填充,这样算来,stu1、stu2 其实占用了 20 个字节。

 

关于成员变量之间存在“裂缝”的原因,是因为 C 语言将内存对齐,能提高寻址效率。

以上为结构体的定义,接下来我们讲结构体如何使用。结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用 [] 获取单个元素,一般格式为:

arrayName[index];

结构体使用 . 获取单个成员。获取结构体成员的一般格式为:

sturctName.memberName;

通过这种方式可以获取成员的值,也可以给成员赋值。

例如这段代码:

#include <stdio.h>
int main () {
    struct {
        char *name; //姓名
        int num; //学号
        int age; //年龄
        char group; //所在小组
        float score; //成绩
    } stu1;
    /* 给结构体变量赋值 */
    stu1.name = "Tom";
    stu1.num = 12;
    stu1.age = 18;
    stu1.group = 'A';
    stu1.score = 136.5;
    /* 读取结构体成员的值 */
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
    return 0;
}

它能给结构体变量 stu1 的成员赋值,并读取出结构体成员的值。运行这段代码,屏幕输出为:

Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!

除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如这段代码

#include <stdio.h>
int main () {
    struct {
        char *name; //姓名
        int num; //学号
        int age; //年龄
        char group; //所在小组
        float score; //成绩
    } stu1, stu2 = {"Tom", 12, 18, 'A', 126};
    /* 给结构体变量赋值 */
    stu1.name = "Tom";
    stu1.num = 12;
    stu1.age = 18;
    stu1.group = 'A';
    stu1.score = 136.5;
    /* 读取结构体成员的值 */
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
    return 0;
}

不过整体赋值仅限于定义结构体变量的时候,在使用过程中只能对成员逐一赋值,这和数组的赋值非常类似。需要注意的是,结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间;结构体变量才包含了实实在在的数据,需要内存空间来存储。

 

 

赋值样例

 

 

 结构体数组的使用也很简单,例如,可以通过 class[4].score; 获取 SuYun 的成绩;可以通过 class[0].group = ‘A’; 修改 ChenGe 的学习小组。

结构体指针

 

当一个指针变量指向结构体时,我们就称它为结构体指针。C语言结构体指针的定义形式一般为:

struct 结构体名 *变量名;

这段代码就是定义结构体指针的实例:

/* 结构体 */
struct stu {
    char *name; //姓名
    int num; //学号
    int age; //年龄
    char group; //小组
    float score; //成绩
} stu1 = {"Tom", 12, 18, 'A', 136};
/* 结构体指针 */
struct stu *pstu = &stu1;

同样,你也可以在定义结构体的同时定义结构体指针:

/* 结构体 */
struct stu {
    char *name; //姓名
    int num; //学号
    int age; //年龄
    char group; //小组
    float score; //成绩
} stu1 = {"Tom", 12, 18, 'A', 136}, *pstu = &stu1;

注意,结构体变量名和数组名不同,数组名在表达式中会被转换为数组指针,而结构体变量名不会,无论在任何表达式中它表示的都是整个集合本身,要想取得结构体变量的地址,必须在前面加 &,所以给 pstu 赋值只能写作:

struct stu *pstu = &stu1;

而不能写作:

struct stu *pstu = stu1;

还应该注意,结构体和结构体变量是两个不同的概念:结构体是一种数据类型,是一种创建变量的模板,编译器不会为它分配内存空间,就像 int、float、char 这些关键字本身不占用内存一样;结构体变量才包含实实在在的数据,才需要内存来存储。

所以,这两行代码是错误的:

struct stu *pstu = &stu;
struct stu *pstu = stu;

 

因为不可能去取一个结构体名的地址,也不能将它赋值给其他变量。

通过结构体指针可以获取结构体成员,一般形式为:

(*pointer).memberName;

或者:

pointer->memberName;

第一种写法中,.的优先级高于*(*pointer)两边的括号不能少。如果去掉括号写作*pointer.memberName,那么就等效于*(pointer.memberName),这样意义就完全不对了。

第二种写法中,->是一个新的运算符,习惯称它为“箭头”,有了它,就可以通过结构体指针直接取得结构体成员;这也是 -> 在 C 语言中的唯一用途。这两种写法是等效的,但我们通常采用第二种写法,这样更加直观。

例如这段代码:

#include <stdio.h>
int main () {
    struct {
        char *name; //姓名
        int num; //学号
        int age; //年龄
        char group; //所在小组
        float score; //成绩
    } stu1 = {"Tom", 12, 18, 'A', 136.5}, *pstu = &stu1;
    /* 读取结构体成员的值 */
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n",(*pstu).name,(*pstu).num,(*pstu).age,(*pstu).group,(*pstu).score);
    printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", pstu->name, pstu->num, pstu->age, pstu->group, pstu->score);
    return 0;
}

 

运行这段程序,可以发现,输出的两行内容是一样的。

如果将结构体作为函数参数传递,结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。

如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,非常快速。

例如这段代码:

#include <stdio.h>
struct stu {
    char *name; //姓名
    int num; //学号
    int age; //年龄
    char group; //小组
    float score; //成绩
} stus[] = {
    {"ChenGe", 5, 18, 'C', 145.0},
    {"ZhangYa", 4, 19, 'A', 130.5},
    {"ZhengRen", 3, 18, 'A', 148.5},
    {"XieYiRen", 2, 17, 'F', 139.0},
    {"SuYun", 1, 17, 'B', 144.5}
}; 
void average (struct stu *ps, int len);
int main () {
    int len = sizeof(stus) / sizeof(struct stu);
    average(stus, len);
    return 0;
}
void average (struct stu *ps, int len) {
    int i, num_140 = 0;
    float average, sum = 0;
    for (i=0; i<len; i++) {
        sum += (ps + i) -> score;
        if ((ps + i) -> score < 140) num_140++;
    }
    printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum/5, num_140);
}

它将计算全班学生的总成绩、平均成绩 以及 成绩在 140 分以下的人数。

结构体变量构成链表

这一节,我们讲《结构体变量构成链表》。

之前我们使用数组存放数据时,必须事先定义固定的数组长度,如果有的班级有 100 个人,有个班级有 50 个人,若用同一个数组先后存放不同班级的学生数据,就必须定义长度为 100 的数组。那如果事先很难确定哪个班的人数最多,就必须把数组长度定义的足够大才行,这样显然很浪费内存。为了解决这个问题,C语言的开发者们设计了链表。

链表也是一种数据类型,它能动态的进行存储分配。也就是说,它能根据需要开辟内存单元。一个链表就像一条铁链一样,一环扣一环,中间不能断开。

一条铁链有链首,链表也是这样,它也有一个链首,它保存一个地址,这个地址就是链表的起始地址,通过链表的起始地址,我们访问到链表的第一元素,这个元素,称为链表的结点,之后的每一个链表的元素都称为链表的结点,每一个结点都有不同的地址。每个结点都分为两部分,一部分用来保存用户需要用到的实际数据,可以称它为数据域,另一部分用来保存访问下一个节点的地址,可以称它为地址域。链表中的结点一个连着一个,直到最后一个结点,该结点不再指向其他元素,它称为表尾,它的地址域放一个空地址,链表到此结束。

需要注意的是,链表中各个元素在内存中的地址可以是不连续的。因为要找某个结点,只能先找到它的上一个结点,通过上一个结点提供的地址才能找到下一个结点,因此,只能通过这个起始地址,才能访问链表,否则,将无法访问整个链表。

因为一个结点即包含用户需要的数据,又包含下一个结点的地址,因此,用结构体变量创建链表是最合适的。

例如这个结构体:

#include <stdio.h>
struct Student {
    int num;
    float score;
    struct Student * next;
};

其中,num 保存学生的学号,socre 保存学生的成绩,next 是指针类型的成员,它指向 struct Student 类型数据。因为 next 是 struct Student 类型的成员,它又指向 struct Student 类型的数据,因此构成了链表。

需要注意的是,这只是定义了一个 struct Student 类型,并未分配存储空间,只有定义了变量才分配存储单元。

也就是说,需要先给 struct Student 类型定义变量:

struct Student a, b, c, *head;
a.num = 1; a.score = 89.5;
b.num = 2; b.score = 90;
c.num = 3; c.score = 85;

然后将它们一环一环连起来:

head = &a;
a.next = &b;
b.next = &c;
c.next = NULL;

这样就构成了一个链表。其中,head 是链表的首地址。

类似这种所有结点都是在程序中定义,不是临时开辟,也不能用完后释放的链表,称为“静态链表”。与之对应的是“动态链表”。所谓动态链表,就是指在程序执行过程中,从无到有的建立一个链表,即一个一个的开辟结点和输入各结点数据,并建立前后相链的关系。

例如这段程序:

#include <stdio.h>
#include <stdlib.h>
struct Student {
    int num;  /* 学号 */
    float score;  /* 成绩 */
    struct Student * next;/*地址*/
};
int main () {
    struct Student *head, *p1, *p2;
    int n = 0;
    p1 = p2 = (struct Student)malloc(sizeof(struct Student));
    scanf("%d %f", &p1->num, &p1->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(sizeof(struct Student));
        scanf("%d %f", &p1->num, &p1->score);
    }
    p2->next = NULL;
    return 0
}

因为要动态开辟结点,所以定义两个结构体指针变量 p1 和 p2 来保存动态分配的内存,这样,就能使用 scanf() 读取数据保存到 结点 p1 中。然后判断是否是起始地址,如果是起始地址,就将第一个结点的地址保存到 head 中,否则,将 p1 的地址保存 p2 的 next 中,然后将 p1 的其他的数据保存到 p2 中,接着就可以重新开辟一个结点,用来保存新输入的学生信息。一直循环,直到输入为 0 时,结束循环,然后让 p2 的 next 指向 NULL,结束链表。如此,动态链表就创建好了。head 是链表的起始地址。

链表创建好了,那如何将各个链表结点中的数据输出呢?

输出相对比较简单,例如这段程序:

#include <stdio.h>
struct Student {
    int num;  /* 学号 */
    float score;  /* 成绩 */
    struct Student * next;/*地址*/
};
void print_link (struct Student * head) {
    struct Student * p;
    p = head;
    if (head != NULL) {
        do {
            printf("%d %5.1f\n", p->num, p->score);
            p = p->next;
        } while (p != NULL);
    }
}

新建一个输出链表的函数 print_link(),并将链表的首地址传给它,如果起始地址不为空,那么第一个结点的数据可以直接输出,然后将下一个结点的地址取出,再循环打印即可,直到最后一个结点为空的时候停止。

链表是一个比较深入的内容,如果你想成为计算机专业领域人员,就必须掌握这部分内容。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值