一、结构体的概念
结构体(struct)是 C 语言中一种自定义数据类型,它允许你将不同类型的数据项组合在一起,形成一个新的复合数据类型。
想象一下:如果要表示一个 "学生",需要包含姓名(字符串)、年龄(整数)、成绩(浮点数)等信息。这些数据类型不同,无法用单一的基本类型表示,这时就需要结构体来将它们组织起来。
二、结构体的定义和声明
2.1 结构体的基本定义格式
struct 结构体名 {数据类型 成员名1;数据类型 成员名2;// ... 更多成员
};
示例:定义一个学生结构体
struct Student {char name[20]; // 姓名int age; // 年龄float score; // 成绩
};
注意:
结构体类型的名字是由一个关键字 struct 和结构体名组合而成的(例如 struct Student )。结构体名是由用户指定的,又称“结构体标记”(structure tag),以区别于其他结构体类型。上面的结构体声明中Student就是结构体名(结构体标记)。花括号内是该结构体所包括的子项,称为结构体的成员(member)。
2.2 结构体的声明
2.2.1 先定义结构体类型,再声明变量
struct Student {char name[20];int age;float score;
};// 声明结构体变量
struct Student stu1, stu2;
2.2.2 定义结构体类型的同时声明变量
struct Student {char name[20];int age;float score;
} stu1, stu2;
它的作用与第一种方法相同,但是在定义struct Student类型的同时定义两个struct Student类型的变量stu1和stu2。这种定义方法的一般形式为
struct 结构体名
{ 成员表列
} 变量名表列;
声明类型和定义变量放在一起进行,能直接看到结构体的结构,比较直观,在写小程序时用此方式比较方便,但写大程序时,往往要求对类型的声明和对变量的定义分别放在不同的地方,以使程序结构清晰,便于维护,所以一般不多用这种方式。
2.2.3 匿名结构体(不指定类型名而直接定义结构体类型变量)
struct {char name[20];int age;float score;
} stu1, stu2;
其一般形式为
struct
{ 成员表列
} 变量名表列;
指定了一个无名的结构体类型,它没有名字(不出现结构体名)。显然不能再以此结构体类型去定义其他变量。这种方式用得不多。
说明:
(1)结构体类型与结构体变量是不同的概念,不要混淆。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。在编译时,对类型是不分配空间的,只对变量分配空间。
(2)结构体类型中的成员名可以与程序中的变量名相同,但二者不代表同一对象。例如,程序中可以另定义一个变量 num,它与 struct Student 中的 num 是两回事,互不干扰。
(3)对结构体变量中的成员(即“域”),可以单独使用,它的作用与地位相当于普通变量。
2.3 结构体嵌套
结构体可以嵌套在另一个结构体中,即成员可以属于另一个结构体类型:
#include <stdio.h>// 定义日期结构体
struct Date {int year;int month;int day;
};// 定义学生结构体,嵌套了日期结构体
struct Student {char name[20];struct Date birthday; // 嵌套结构体float score;
};int main() {struct Student stu = {"张三",{2005, 3, 15}, // 初始化嵌套的结构体95.5f};printf("姓名:%s\n", stu.name);printf("生日:%d年%d月%d日\n", stu.birthday.year, stu.birthday.month, stu.birthday.day);printf("成绩:%.1f\n", stu.score);return 0;
}
运行结果:
三、结构体变量的初始化和引用
3.1 结构体变量的初始化
(1)在定义结构体变量时可以对它的成员初始化。初始化列表是用花括号括起来的一些常量,这些常量依次赋给结构体变量中的各成员。
(2)注意:是对结构体变量初始化,而不是对结构体类型初始化。
// 方法1:按顺序初始化
struct Student stu1 = {"张三", 18, 95.5f};// 方法2:指定成员初始化(C99标准支持)
struct Student stu2 = {.name = "李四",.age = 19,.score = 88.0f
};
3.2 结构体变量的引用
(1)可以引用结构体变量中成员的值,引用方式为
结构体变量名.成员名
例:
#include <stdio.h>
#include <string.h>struct Student {char name[20];int age;float score;
};int main() {struct Student stu;// 给结构体成员赋值strcpy(stu.name, "王五"); // 字符串赋值需使用strcpy函数stu.age = 20;stu.score = 92.5f;// 访问结构体成员printf("姓名:%s\n", stu.name);printf("年龄:%d\n", stu.age);printf("成绩:%.1f\n", stu.score);return 0;
}
运行结果:
“.”是成员运算符,它在所有的运算符中优先级最高,因此可以把stu.age作为一个整体来看待,相当于一个变量。上面赋值语句 stu.age = 20; 的作用是将整数20赋给stu变量中的成员 age。
注意:不能企图通过输出结构体变量名来达到输出结构体变量所有成员的值。
下面用法不正确:
printf("%s\n",stu); //企图用结构体变量名输出所有成员的值
只能对结构体变量中的各个成员分别进行输入和输出。
(2)如果成员本身又属一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级的成员。只能对最低级的成员进行赋值或存取以及运算。如果在结构体struct Student类型的成员中包含另一个结构体struct Date类型的成员 birthday(见2.3节结构体嵌套代码),则引用成员的方式为
stu.name (结构体变量stu中的成员name)
stu. birthday.month (结构体变量stu中的成员birthday中的成员month)
不能用 stu.birthday来访问stu变量中的成员 birthday,因为 birthday本身是一个结构体成员。
(3)对结构体变量的成员可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。例如:
stu2.score=stu1.score; //(赋值运算)
sum=stu1.score+stu2.score; //(加法运算)
stu1.age++; //(自加运算)
由于“.”运算符的优先级最高,因此 stu1.age++是对(stu1.age)进行自加运算,而不是先对 age 进行自加运算。
(4)同类的结构体变量可以互相赋值,如:
stu1=stu2; //假设stu1和stu2已定义为同类型的结构体变量
(5)可以引用结构体变量成员的地址,也可以引用结构体变量的地址。例如:
scanf(“%d”, &stu1. num); (输人stu1.num的值)
printf("%o",&stu1); (输出结构体变量studentl的起始地址)
但不能用以下语句整体读入结构体变量,例如:
scanf("%d,%s,%c,%d,%f,%s\n",&stu1);
说明:结构体变量的地址主要用作函数参数,传递结构体变量的地址。
四、结构体数组
4.1 结构体数组的定义与声明
定义结构体数组一般形式是
①struct 结构体名
{成员表列} 数组名[数组长度];
②先声明一个结构体类型(如struct Person),然后再用此类型定义结构体数组;
结构体类型 数组名[数组长度];
4.2 结构体数组的初始化
对结构体数组初始化的形式是在定义数组的后面加上:
={初值表列};
例:
#include <stdio.h>struct Student {char name[20];int age;float score;
};int main() {// 定义并初始化结构体数组struct Student students[3] = {{"张三", 18, 95.5f},{"李四", 19, 88.0f},{"王五", 20, 92.5f}};// 遍历结构体数组for (int i = 0; i < 3; i++) {printf("第%d个学生:\n", i+1);printf(" 姓名:%s\n", students[i].name);printf(" 年龄:%d\n", students[i].age);printf(" 成绩:%.1f\n", students[i].score);}return 0;
}
五、结构体指针
所谓结构体指针就是指向结构体变量的指针,一个结构体变量的起始地址就是这个结构体变量的指针。如果把一个结构体变量的起始地址存放在一个指针变量中,那么,这个指针变量就指向该结构体变量。
5.1 指向结构体变量的指针
指向结构体对象的指针变量既可指向结构体变量,也可指向结构体数组中的元素。指针变量的基类型必须与结构体变量的类型相同。例如:
struct Student * pt; //pt可以指向struct Student类型的变量或数组元素
例题:
#include <stdio.h>
#include <string.h>
int main()
{struct Student //声明结构体类型 struct Student{long num;char name[20];char sex;float score;};struct Student stu_1; //定义 struct Student 类型的变量 stu_1struct Student* p; //定义指向 struct Student 类型数据的指针变量 pp = &stu_1; //p 指向 stu_1stu_1.num = 10101; //对结构体变量的成员赋值strcpy_s(stu_1.name, "Li Lin");//用字符串复制函数给 stu_1.name 赋值stu_1.sex = 'M';stu_1.score = 89.5;printf("No.:%ld\nname:%s\nsex:%c\nscore:%5.1f\n",stu_1.num, stu_1.name, stu_1.sex, stu_1.score); //输出结果printf("\nNo.:%ld\nname:%s\nsex:%c\nscore:%5.1f\n",(*p).num, (*p).name, (*p).sex, (*p).score);return 0;
}
运行结果:
第1个 printf 函数是通过结构体变量名 stu_1 访问它的成员,输出 stu_1 的各个成员的值。用 stu_1.num 表示 stu_1 中的成员 num,依此类推。第2个 printf 函数是通过指向结构体变量的指针变量访问它的成员,输出 stu_1 各成员的值,使用的是 (*p).num 这样的形式。(*p) 表示p指向的结构体变量,(*p).num是p所指向的结构体变量中的成员 num。注意 *p两侧的括号不可省,因为成员运算符“.”优先于”*”运算符, *p.num 就等价于 *(p.num) 了。
说明:为了使用方便和直观,C语言允许把 (*p).num 用 p->num 代替,“->” 代表一个箭头,p->num 表示 p 所指向的结构体变量中的 num 成员。同样,(*p).name 等价于 p->name。“->” 称为指向运算符。
如果 p 指向一个结构体变量 stu,以下3种用法等价:
① stu. 成员名(如 stu.num);
② (*p).成员名(如 (*p).num);
③ p-> 成员名(如 p->num)。
例:结构体指针是指向结构体的指针变量,使用箭头运算符(->
)访问成员:
#include <stdio.h>
#include <string.h>struct Student {char name[20];int age;float score;
};int main() {struct Student stu;struct Student *p_stu = &stu; // 结构体指针指向stu变量// 通过指针给结构体成员赋值strcpy(p_stu->name, "赵六");p_stu->age = 19;p_stu->score = 89.0f;// 也可以使用(*p_stu).成员名的方式访问printf("姓名:%s\n", (*p_stu).name);printf("年龄:%d\n", p_stu->age);printf("成绩:%.1f\n", p_stu->score);return 0;
}
5.2指向结构体数组的指针
例:
#include <stdio.h>
// 声明结构体类型 struct Student
struct Student
{int num;char name[20];char sex;int age;
};struct Student stu[3] = {{10101, "Li Lin", 'M', 18},{10102, "Zhang Fang", 'M', 19},{10104, "Wang Min", 'F', 20}
};int main()
{// 定义指向 struct Student 结构体变量的指针变量struct Student* p;printf(" No. Name sex age\n");// 遍历结构体数组for (p = stu; p < stu + 3; p++){// 通过指针访问成员输出printf("%5d %-20s %2c %4d\n", p->num, p->name, p->sex, p->age);}return 0;
}
运行结果:
六、结构体作为函数参数
结构体可以作为函数参数传递,有两种方式:
6.1 传递结构体变量(值传递)
#include <stdio.h>struct Student {char name[20];int age;float score;
};// 结构体作为函数参数(值传递)
void printStudent(struct Student s) {printf("姓名:%s\n", s.name);printf("年龄:%d\n", s.age);printf("成绩:%.1f\n", s.score);
}int main() {struct Student stu = {"张三", 18, 95.5f};printStudent(stu); // 传递结构体变量return 0;
}
6.2 传递结构体指针(地址传递)
#include <stdio.h>struct Student {char name[20];int age;float score;
};// 结构体指针作为函数参数(地址传递)
void updateScore(struct Student *s, float newScore) {s->score = newScore; // 修改结构体成员的值
}int main() {struct Student stu = {"张三", 18, 95.5f};printf("修改前成绩:%.1f\n", stu.score);updateScore(&stu, 98.0f); // 传递结构体地址printf("修改后成绩:%.1f\n", stu.score);return 0;
}
注意:值传递会复制整个结构体,效率较低;地址传递只传递指针,效率更高,且可以在函数内部修改结构体的值。
七、typedef 与结构体
使用typedef
可以为结构体类型定义一个别名,简化代码:
#include <stdio.h>// 使用typedef定义结构体别名
typedef struct {char name[20];int age;float score;
} Student; // Student是结构体的别名int main() {// 直接使用别名声明变量,无需再写structStudent stu = {"张三", 18, 95.5f};printf("姓名:%s\n", stu.name);return 0;
}
八、结构体应用示例
下面是一个综合示例,演示结构体的各种用法:
#include <stdio.h>
#include <string.h>// 定义学生结构体
typedef struct {char name[20];int id;float scores[3]; // 三门课成绩float average; // 平均分
} Student;// 计算平均分
void calculateAverage(Student *stu) {float sum = 0;for (int i = 0; i < 3; i++) {sum += stu->scores[i];}stu->average = sum / 3;
}// 打印学生信息
void printStudent(Student stu) {printf("学号:%d\n", stu.id);printf("姓名:%s\n", stu.name);printf("成绩:%.1f, %.1f, %.1f\n", stu.scores[0], stu.scores[1], stu.scores[2]);printf("平均分:%.1f\n\n", stu.average);
}int main() {// 定义学生数组Student students[3];// 初始化学生信息strcpy(students[0].name, "张三");students[0].id = 1001;students[0].scores[0] = 90.5f;students[0].scores[1] = 85.0f;students[0].scores[2] = 92.5f;strcpy(students[1].name, "李四");students[1].id = 1002;students[1].scores[0] = 88.0f;students[1].scores[1] = 91.5f;students[1].scores[2] = 89.0f;strcpy(students[2].name, "王五");students[2].id = 1003;students[2].scores[0] = 95.0f;students[2].scores[1] = 87.5f;students[2].scores[2] = 93.0f;// 计算每个学生的平均分for (int i = 0; i < 3; i++) {calculateAverage(&students[i]);}// 打印所有学生信息printf("学生信息列表:\n");for (int i = 0; i < 3; i++) {printStudent(students[i]);}return 0;
}
九、结构体的注意事项
结构体变量占用的内存空间是所有成员占用空间的总和(可能存在内存对齐)
结构体成员名可以与普通变量名相同,两者的作用域不同
不能在结构体内部初始化成员变量
结构体可以嵌套,但不能包含自身类型的成员(可以包含自身类型的指针)
结构体赋值可以直接使用
=
进行整体赋值(浅拷贝)