结构体
概述
结构体是自定义数据类型, 是由 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; // 用定义后的结构体类型定义结构体变量
定义了两个变量 stu1
和 stu2
, 它们都是 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;
获取结构体成员
获取结构体变量的成员的方法:
-
结构体变量.成员
-
指向结构体变量的指针 -> 成员
-
(*指向结构体变量的指针).成员
通过结构体指针可以获取指针指向的结构体变量的结构体成员, 一般形式为:
(*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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术