这两个运算符都与结构体和联合体有关,它们的作用是访问其成员,它们的主要区别在于操作的对象不同。

(图片来源网络,侵删)
结构体 和 联合体 回顾
我们需要知道 -> 和 是用来做什么的,它们是用来访问结构体或联合体内部变量的。
// 定义一个结构体类型 Person
struct Person {
char name[50];
int age;
float height;
};
int main() {
// 声明一个结构体变量
struct Person p1;
// 使用点运算符 . 来访问和赋值成员
strcpy(p1.name, "Alice"); // 注意:不能直接用 p1.name = "Alice";
p1.age = 30;
p1.height = 1.68;
printf("Name: %s, Age: %d, Height: %.2f\n", p1.name, p1.age, p1.height);
return 0;
}
在上面的例子中,p1 是一个结构体变量,我们使用 运算符来直接访问它的成员 name, age, height。
点运算符
用途:通过结构体变量本身来访问其成员。
语法:结构体变量名.成员名

(图片来源网络,侵删)
核心: 运算符作用于一个实实在在的变量上。
示例:
struct Point {
int x;
int y;
};
int main() {
struct Point p1; // p1 是一个 Point 类型的变量
p1.x = 10; // 通过变量 p1 访问成员 x
p1.y = 20; // 通过变量 p1 访问成员 y
printf("Point is at (%d, %d)\n", p1.x, p1.y);
return 0;
}
p1.x 的意思是:“取 p1 这个变量的 x 成员”。
-> 箭头运算符
用途:通过指向结构体的指针来访问其成员。
语法:结构体指针名->成员名
核心:-> 运算符作用于一个指向结构体的指针上,它是 (*指针名).成员名 的简写形式。
为什么需要 ->?
假设我们有一个结构体指针,如果用 运算符会发生什么?
struct Point {
int x;
int y;
};
int main() {
struct Point p1 = {10, 20};
struct Point *ptr = &p1; // ptr 指向 p1
// 错误用法!
// ptr.x; // 编译错误!ptr 是一个指针,它没有 x 成员。
// *ptr 才是那个结构体变量。
}
指针 ptr 本身存储的是内存地址,它不包含 x 和 y 成员,它所指向的 *ptr 才包含。
正确的访问方式有两种:
解引用 + 点运算符
// (*ptr).x // 1. *ptr: 解引用 ptr,得到它指向的结构体变量(也就是 p1) // 2. .x: 然后对这个结构体变量使用点运算符访问 x 成员 (*ptr).x = 30; (*ptr).y = 40;
这种方式语法上有点别扭,因为括号和星号混在一起,可读性不强。
箭头运算符(推荐)
C 语言为了解决上述问题,提供了 -> 运算符,它是 (*ptr). 的完美简写。
// ptr->x // 1. ptr->: 等价于 (*ptr). // 2. x: 然后访问 x 成员 ptr->x = 30; ptr->y = 40;
这种方式更简洁、更直观,清晰地表达了“通过这个指针去访问它的成员”的意图。
核心区别与总结
| 特性 | (点运算符) | -> (箭头运算符) |
|---|---|---|
| 操作对象 | 结构体变量 | 指向结构体的指针 |
| 语法 | 变量名.成员名 |
指针名->成员名 |
| 含义 | 访问变量本身的成员 | 通过指针访问其指向的变量的成员 |
| 本质 | 直接访问 | 间接访问(先解引用,再访问) |
| 等价形式 | - | (*指针名).成员名 |
一个完整的对比示例
这个例子将清晰地展示 和 -> 在不同场景下的用法。
#include <stdio.h>
#include <string.h>
// 定义一个学生结构体
struct Student {
int id;
char name[50];
float score;
};
int main() {
// 场景一:使用结构体变量
struct Student student1;
student1.id = 101;
strcpy(student1.name, "Bob");
student1.score = 95.5;
printf("--- Using Dot Operator (.) ---\n");
printf("ID: %d, Name: %s, Score: %.1f\n", student1.id, student1.name, student1.score);
// 场景二:使用指向结构体的指针
struct Student *ptr_student = &student1; // ptr_student 指向 student1
// 修改 student1 的数据,通过指针
ptr_student->id = 102; // 使用 -> 访问
strcpy(ptr_student->name, "Charlie");
ptr_student->score = 88.0;
printf("\n--- Using Arrow Operator (->) ---\n");
printf("ID: %d, Name: %s, Score: %.1f\n", ptr_student->id, ptr_student->name, ptr_student->score);
// 场景三:验证 -> 和 (*ptr). 的等价性
printf("\n--- Verifying Equivalence: ptr->id vs (*ptr).id ---\n");
printf("ptr->id is: %d\n", ptr_student->id);
printf("(*ptr_student).id is: %d\n", (*ptr_student).id);
// 这两个输出结果完全相同
return 0;
}
程序输出:
--- Using Dot Operator (.) ---
ID: 101, Name: Bob, Score: 95.5
--- Using Arrow Operator (->) ---
ID: 102, Name: Charlie, Score: 88.0
--- Verifying Equivalence: ptr->id vs (*ptr).id ---
ptr->id is: 102
(*ptr_student).id is: 102
记忆技巧
一个非常简单粗暴的记忆方法:
- 有名字(变量名)用 :
student1.id(student1 是个名字) - 有箭头(->)用
->:ptr->id(ptr 看起来像一支箭)
希望这个详细的解释能帮助你彻底理解 C 语言中这两个至关重要的运算符!
