首成员规则
本课属于 Coddy C 学习旅程的“面向对象编程”单元。第 29 课,共 61 课。
当你将一个结构体嵌入另一个结构体作为第一个成员时,会发生一件很有用的事:外层结构体的内存地址与其第一个成员的内存地址相同。C 标准对此有保证。
看看我们之前的例子:
typedef struct {
int id;
} Parent;
typedef struct {
Parent parent; // 第一个成员
int grade;
} Child;创建 Child 变量时,Parent 成员位于 Child 内存的最开头。这意味着,指向 Child 的指针与指向其 Parent 成员的指针指向完全相同的地址。
Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr; // 安全的类型转换!两个指针保存着相同的地址。这样,你就可以将 Child* 传递给任何需要 Parent* 的函数:
void print_id(Parent* p) {
printf("ID: %d\n", p->id);
}
// 在 main 中:
Child c;
c.parent.id = 42;
print_id((Parent*)&c); // 运行正常这种技术是 C 中继承的基础。为基类型编写的函数可以处理任何派生类型,只要基类型嵌入在最前面即可。这种类型转换是安全的,因为内存布局完全一致。
挑战
简单让我们通过构建一个简单的动物层次结构来探索首成员规则。你将创建一个基础 Animal 结构体和一个派生的 Dog 结构体,然后演示如何将指向 Dog 的指针安全地转换为指向 Animal 的指针。
你要应用的关键知识是:当 Animal 是 Dog 的第一个成员时,这两个结构体的起始内存地址相同。这意味着你可以通过转换指针,将 Dog* 传递给任何需要 Animal* 的函数。
你要将代码分放在三个文件中:
animal.h:使用包含保护定义两个结构体。你的Animal结构体应该只有一个字段:legs(一个表示腿数量的整数)。你的Dog结构体应将Animal嵌入为第一个成员,并添加一个name字段(一个包含 50 个字符的字符数组)。还要声明函数print_animal,它接受一个Animal*参数。animal.c:实现print_animal函数。这个函数使用基础类型。它只了解Animal,并输出动物有几条腿。main.c:创建一个Dog变量并填充它的字段(嵌入的动物的腿数和狗的名字)。然后,将Dog*转换为Animal*,并将其传递给print_animal,以此演示首成员规则。还要单独输出狗的名字,以展示完整的效果。
你会收到两个输入:狗的名字(一个字符串)和腿的数量(一个整数)。
你的输出应如下所示:
Dog: Buddy
Legs: 4其中,Buddy 是狗的名字,4 是腿的数量。先在 main.c 中输出狗的名字,然后使用转换后的指针调用 print_animal,以显示腿的数量。
这展示了如何通过指针转换,让为基础类型(Animal)编写的函数顺畅地处理派生类型(Dog)。这是 C 语言继承的基础。
亲手试试
#include <stdio.h>
#include <string.h>
#include "animal.h"
int main() {
// 读取输入
char name[50];
int legs;
scanf("%s", name);
scanf("%d", &legs);
// TODO: 创建一个 Dog 变量
// TODO: 使用 strcpy 设置狗的名字
// TODO: 通过嵌入的 Animal 设置腿的数量
// TODO: 按以下格式输出狗的名字:"Dog: <name>"
// TODO: 将 Dog* 转换为 Animal*,然后调用 print_animal
// 这展示了首成员规则!
return 0;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C 在线编译器