1. 什么是结构体?
结构体是一种用户自定义的数据类型,它允许你将多个不同类型的变量组合在一起,形成一个逻辑上的整体。这些变量称为结构体的成员(Members)。
2. 如何定义结构体?
使用 struct
关键字来定义结构体。
语法:
struct 结构体标签 {
数据类型 成员1;
数据类型 成员2;
// ... 更多成员
};
示例:定义一个表示学生的结构体
#include <stdio.h>
#include <string.h>
// 定义结构体类型 (这只是一份蓝图,尚未分配内存)
struct Student {
int id; // 学号
char name[20]; // 姓名
float score; // 成绩
}; // 注意这里的分号不能丢
3. 如何声明和初始化结构体变量?
定义了结构体类型后,就可以像使用 int
, char
一样来声明这种类型的变量。
方法一:先定义类型,再声明变量(推荐)
struct Student { // 定义类型
int id;
char name[20];
float score;
};
int main() {
struct Student stu1, stu2; // 声明两个结构体变量
// ... 使用变量
return 0;
}
方法二:定义类型的同时声明变量
struct Student {
int id;
char name[20];
float score;
} stu1, stu2; // 同时声明了 stu1 和 stu2 两个变量
初始化结构体变量:
可以在声明时按顺序初始化所有成员,也可以指定成员初始化(C99标准支持)。
int main() {
// 1. 按顺序初始化所有成员
struct Student stu1 = {101, "Alice", 95.5f};
// 2. 指定成员初始化(C99及以上),顺序可以打乱
struct Student stu2 = { .name = "Bob", .score = 88.5f, .id = 102};
// 3. 先声明,后逐个赋值
struct Student stu3;
stu3.id = 103;
// stu3.name = "Charlie"; // 错误!数组不能直接赋值
strcpy(stu3.name, "Charlie"); // 正确,使用strcpy函数拷贝字符串
stu3.score = 91.0f;
return 0;
}
4. 如何访问结构体成员?
使用成员访问运算符 .
来访问结构体变量的成员。
int main() {
struct Student stu = {101, "Alice", 95.5f};
// 访问成员并赋值
printf("学号: %d\n", stu.id);
printf("姓名: %s\n", stu.name);
printf("成绩: %.1f\n", stu.score);
// 修改成员的值
stu.score = 98.0f;
printf("修改后的成绩: %.1f\n", stu.score);
return 0;
}
5. 结构体与指针
a) 指向结构体的指针
可以声明一个指针,让它指向一个结构体变量。
int main() {
struct Student stu = {101, "Alice", 95.5f};
struct Student *pStu; // 声明一个指向Student结构体的指针
pStu = &stu; // 让指针指向stu变量
b) 通过指针访问成员
通过指针访问结构体成员有两种方法:
使用
->
运算符(箭头运算符):这是最常用、最清晰的方式。使用
(*ptr).member
:先解引用指针,再用.
访问。
// 方法1:使用 -> 运算符
printf("学号 (通过指针): %d\n", pStu->id);
printf("姓名 (通过指针): %s\n", pStu->name);
pStu->score = 100.0f; // 通过指针修改成员
// 方法2:使用 (*ptr).member
printf("学号: %d\n", (*pStu).id); // *pStu 等价于 stu
// 注意:(*pStu) 的括号是必须的,因为 . 的优先级比 * 高
->
运算符是 (*ptr).
的语法糖,两者完全等价,但前者更简洁。
6. 结构体与函数
a) 结构体作为函数参数
结构体可以作为参数传递给函数。默认是值传递,即函数内部会得到一份原结构体的副本,修改副本不会影响原结构体。
// 值传递:修改不会影响原数据
void printStudent(struct Student s) {
printf("ID: %d, Name: %s, Score: %.1f\n", s.id, s.name, s.score);
s.score = 0; // 这只修改了函数内的副本,外面的原数据不变
}
int main() {
struct Student stu = {101, "Alice", 95.5f};
printStudent(stu);
printf("原成绩未改变: %.1f\n", stu.score); // 输出 95.5
return 0;
}
b) 结构体指针作为函数参数(按引用传递)
如果希望函数能修改原结构体的内容,或者结构体很大时为了避免拷贝整个结构体的开销,应该传递结构体指针。
// 指针传递(引用传递):修改会影响原数据
void changeScore(struct Student *sPtr, float newScore) {
sPtr->score = newScore; // 通过指针修改原结构体的成员
}
int main() {
struct Student stu = {101, "Alice", 95.5f};
changeScore(&stu, 88.0f); // 传递结构体的地址
printf("成绩已被修改: %.1f\n", stu.score); // 输出 88.0
return 0;
}
7. 结构体数组
可以创建元素为结构体的数组,用于管理多个相同类型的结构体数据。
int main() {
// 声明并初始化一个结构体数组
struct Student class[3] = {
{101, "Alice", 95.5f},
{102, "Bob", 88.0f},
{103, "Charlie", 91.0f}
};
// 遍历结构体数组
for (int i = 0; i < 3; i++) {
printf("学生%d: %s, 分数: %.1f\n", class[i].id, class[i].name, class[i].score);
}
// 通过指针遍历
struct Student *p;
for (p = class; p < class + 3; p++) {
printf("学生%d: %s, 分数: %.1f\n", p->id, p->name, p->score);
}
return 0;
}
8. typedef
关键字:为结构体创建别名
使用 typedef
可以为结构体类型定义一个更简短或更易读的别名,这样在声明变量时就不用每次都写 struct
关键字。
语法:
typedef struct 原结构体标签 {
成员;
} 新类型名;
示例:
// 方法1:定义结构体同时用typedef
typedef struct {
int id;
char name[20];
float score;
} Student; // Student 现在是一个类型别名,等价于 struct {...}
// 方法2:先定义结构体,再typedef
struct Person_ {
int age;
char name[20];
};
typedef struct Person_ Person; // 为 struct Person_ 起别名为 Person
int main() {
// 现在声明变量可以省略 struct 关键字
Student stu1 = {101, "Alice", 95.5f};
Person p1 = {20, "David"};
Student *pStu = &stu1;
printf("%s\n", pStu->name);
return 0;
}
总结
知识点 | 描述 | 关键语法 |
---|---|---|
定义 | 创建一种新的复合数据类型 | struct Tag { ... }; |
声明变量 | 创建该类型的实例 | struct Tag var; 或使用 typedef 别名 |
初始化 | 给结构体变量赋初值 | {val1, val2} 或 {.member=val} |
访问成员 | 读写结构体中的数据 | var.member |
结构体指针 | 指向结构体的指针 | struct Tag *ptr = &var; |
指针访问成员 | 通过指针读写成员 | ptr->member 或 (*ptr).member |
函数参数 | 传递给函数 | 值传递:func(struct Tag var) 指针传递: func(struct Tag *ptr) |
结构体数组 | 存储多个结构体 | struct Tag arr[size]; |
typedef |
为结构体类型创建别名 | typedef struct { ... } NewName; |