结构体

概述

结构体是自定义数据类型, 是由 int, char, float 等基本类型组成的. 可认为结构体是一种聚合类型. 可以将一组类型不同的, 但是用来描述同一件事物的变量放到结构体中, 这样就不必再定义多个变量.

结构体是结构体类型的简称.

定义结构体类型

结构体的定义形式为:

struct 结构体名{
结构体所包含的变量(基本类型或派生类型)
};

每个变量都称为结构体的成员. 成员可以是基本类型, 也可以是派生类型.

结构体成员的定义方式与变量和数组的定义方式相同, 只是不能在结构体内初始化. 结构体是定义了一个结构体类型, 结构体成员定义了类型和变量名. 结构体成员不可以初始化, 但结构体变量可以初始化.

注意大括号后面的分号 ; 不能少, 这是一条完整的语句.

struct stu
{
int age;
float grade;
char *name;
int id;
}; // struct stu 是一个类型名, 就像 int, short 之类的

定义结构体变量

既然结构体是一种数据类型, 那么就可以用它来定义结构体变量.

stu 就像一个 “模板”, 定义出来的变量都具有相同的性质. 也可以将结构体比作 “图纸”, 将结构体变量比作 “零件”, 根据同一张图纸生产出来的零件的特性都是一样的.

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

方法一

先定义结构体类型, 再定义结构体变量.

struct stu // 先定义结构体类型
{
int age;
float grade;
char *name;
int id;
}; // struct stu 是一个类型名, 就像 int, short 之类的
struct stu stu1, stu2; // 用定义后的结构体类型定义结构体变量

定义了两个变量 stu1stu2, 它们都是 struct stu 类型, 都由 5 个结构体成员组成. 关键字 struct 不能少.

方法二

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

struct stu
{
int age;
float grade;
char *name;
int id;
} stu1, stu2;

如果只需要 stu1, stu2 两个变量, 后面不需要再使用结构体名定义其他变量, 那么在定义时也可以不给出结构体名. 这样做书写简单, 但是因为没有结构体名, 后面就没法用该结构体定义新的变量.

struct
{
int age;
float grade;
char *name;
int id;
} stu1, stu2;

理论上结构体的各个成员在内存中是连续存储的, 和数组非常类似, 但是在编译器的具体实现中, 各个成员之间可能会存在缝隙,使得结构体变量所占内存的字节数大于理论数目.

获取或赋给结构体成员的值

结构体和数组一样, 都是一组数据的集合. 数组使用下标 [] 获取单个元素. 结构体使用点号 . 获取单个成员.

获取结构体成员的一般格式为:

结构体变量名.成员名;

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

程序示例:

#include <stdio.h>
struct
{
int age;
float grade;
char *name;
int id;
} stu1, stu2;
int main(void)
{
stu1.age = 10; // 给成员赋值
printf("stu1 age = %d.\n", stu1.age); // 获取成员的值
}

结果:

stu1 age = 10.

第 12 行只能在 main() 函数内, 不能在 main() 函数外.

除了可以对成员进行逐一赋值, 也可以在定义结构体变量时对所有成员整体赋值.

程序示例:

#include <stdio.h>
struct stu
{
int age;
float grade;
char* name;
int id;
} stu1, stu2;
int main(void)
{
struct stu stu1={12, 2, "Tom", 20210003}; // 给成员赋值
printf("stu1 age = %d.\n", stu1.age); // 获取成员的值
stu1.age = 100; // 修改成员的值
printf("stu1 age = %d.\n", stu1.age); // 获取成员的值
return 0;
}

结果:

stu1 age = 12.
stu1 age = 100.

整体赋值仅限于定义结构体变量的时候, 在使用过程中只能对成员逐一赋值. 这和数组赋值非常类似.

整体赋值可以在函数体外进行, 逐一赋值只能在函数体内进行.

struct
{
int age;
float grade;
char *name;
int id;
} stu1, stu2 = {12, 90.8, "Tom", 2021}; // 可以在函数体外

程序示例:

#include<stdio.h>
struct Book
{
char name[20];
char id[20];
int price;
};
int main(void)
{
int num = 10;
struct Book b = { "C语言","2020",40 };
struct Book* pb = &b;
//结构体变量名.成员名
printf("书名:%s\n", b.name);
printf("书号:%s\n", b.id);
printf("定价:%d\n", b.price);
//结构体指针->成员名
printf("书名:%s\n", pb->name);
printf("书号:%s\n", pb->id);
printf("定价:%d\n", pb->price);
//(*结构体指针).成员名
printf("书名:%s\n", (*pb).name);
printf("书号:%s\n", (*pb).id);
printf("定价:%d\n", (*pb).price);
return 0;
}

结果:

书名:C语言
书号:2020
定价:40
书名:C语言
书号:2020
定价:40
书名:C语言
书号:2020
定价:40

结构体数组

结构体数组是指数组中的每个元素都是一个结构体, 即元素是结构体的数组.

实际应用中, C 语言结构体数组常被用来表示一个拥有相同数据结构的群体, 比如一个班的具有相同种类信息的多个学生, 一个车间的具有相同种类信息的多个职工等.

定义结构体数组

struct stu
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
} class[5];

表示一个班级有 5 个学生.

结构体数组的类型是 struct stu [5] , 数组的元素的类型是结构体类型, 即 struct stu.

初始化结构体数组

方法一:

结构体数组在定义的同时也可以初始化, 例如:

struct stu
{
char *name; // 姓名
int num; // 学号
int age; // 年龄
char group; // 所在小组
float score; // 成绩
} class[5] = {
{"Li ping", 5, 18, 'C', 145.0}, // 数组的每一个元素都是一个结构体变量
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};

方法二:

当对数组中全部元素赋值时, 也可不给出数组长度, 例如:

struct stu
{
char *name; // 姓名
int num; // 学号
int age; // 年龄
char group; // 所在小组
float score; // 成绩
} class[] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}};

获取 Wang ming 的成绩:

class[4].score; // class[4] 是数组的元素, 是一个结构体变量

修改 Li ping 的学习小组:

class[0].group = 'B';

计算全班学生的总成绩, 平均成绩和以及 140 分以下的人数:

#include <stdio.h>
struct
{
char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在小组
float score; //成绩
} class[] = {
{"Li ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"He fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
int main()
{
int i, num_140 = 0;
float sum = 0;
for (i = 0; i < 5; i++)
{
sum += class[i].score;
if (class[i].score < 140)
num_140++;
}
printf("sum=%.2f\naverage=%.2f\nnum_140=%d\n", sum, sum / 5, num_140);
return 0;
}

结果:

sum=707.50
average=141.50
num_140=2

结构体指针

即指向结构体变量的指针.

C 语言结构体指针的定义形式一般为:

struct 结构体名 *指针变量名;

指针的类型是 struct 结构体名 *, 指向的元素的类型是 struct 结构体名. 指向的是某个结构体变量而不是指向结构体类型.

实例:

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

可以在定义结构体的同时定义结构体指针:

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

结构体变量名不会被转换为指针, 无论在任何表达式中它表示的都是整个结构体变量本身, 要想取得结构体变量的地址, 必须在结构体变量前面加 &.

不可能去取一个结构体类型名的地址, 也不能将它赋值给其他变量,下面的写法是错误的.

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

获取结构体成员

获取结构体变量的成员的方法:

  1. 结构体变量.成员

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

  3. (*指向结构体变量的指针).成员

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

(*pointer).memberName

或者:

pointer->memberName

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

第二种写法中, -> 是一个新的运算符, 习惯称它为 “箭头”, 可以通过结构体指针直接取得结构体变量的成员; 这也是 -> 在 C 语言中的唯一用途.

上面的两种写法是等效的, 通常采用后面的写法, 这样更加直观.

#include <stdio.h>
int main(void)
{
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;
}

结果:

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

结构体数组指针: 指向结构体数组的指针.

#include <stdio.h>
struct stu
{
char *name;
int num;
int age;
char group;
float score;
} stus[] = {
{"Zhou ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"Liu fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
}, *ps;
int main(void)
{
int len = sizeof(stus) / sizeof(struct stu); // 求出结构体数组的元素的个数
printf("Name\t\tNum\t\tAge\t\tGroup\tScore\t\n");
for (ps = stus; ps < stus + len; ps++)
{
printf("%s\t%d\t%d\t%c\t\t%.1f\n", ps->name, ps->num, ps->age, ps->group, ps->score);
}
return 0;
}

结果:

Name Num Age Group Score
Zhou ping 5 18 C 145.0
Zhang ping 4 19 A 130.5
Liu fang 1 18 A 148.5
Cheng ling 2 17 F 139.0
Wang ming 3 17 B 144.5

struct stu * 类型的指针指向元素的类型为 struct stu 的数组.

即数组的元素的类型为 struct stu, 数组的类型为 struct stu [5].

类似于 int * 类型的指针指向元素类型为 int, 数组类型为 int [5] 的数组.

程序示例:

#include <stdio.h>
struct Book
{
char name[20];
short price;
};
int main(void)
{
struct Book b1 = { "C程序设计", 55 };
struct Book* pb = &b1; // 指向结构体变量的指针
printf("%s\n", (*pb).name); // 使用指向结构体变量的指针获取该变量的成员的值
printf("%d\n", (*pb).price);
printf("%s\n", pb->name); // 使用指向结构体变量的指针获取该变量的成员的值
printf("%d\n", pb->price);
return 0;
}

结果:

C程序设计
55
C程序设计
55

结构体指针作为函数参数

结构体变量名代表的是整个集合本身, 作为函数参数时传递的整个集合, 也就是所有成员, 而不是像数组一样被编译器转换成一个指针. 如果结构体成员较多, 尤其是成员为数组时, 传送的时间和空间开销会很大, 影响程序的运行效率. 最好用结构体指针, 实参传向形参的是一个地址, 非常快速.

程序示例:

// 计算全班学生的总成绩、平均成绩和以及 140 分以下的人数
#include <stdio.h>
struct stu
{
char *name;
int num;
int age;
char group;
float score;
} stus[] = {
{"Zhou ping", 5, 18, 'C', 145.0},
{"Zhang ping", 4, 19, 'A', 130.5},
{"Liu fang", 1, 18, 'A', 148.5},
{"Cheng ling", 2, 17, 'F', 139.0},
{"Wang ming", 3, 17, 'B', 144.5}
};
void average(struct stu *ps, int len);
int main(void)
{
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.0f;
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);
}

结果:

sum=707.50
average=141.50
num_140=2
posted @   有空  阅读(18)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示

目录导航