Menu
CoddyTech

首成员规则

本课属于 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 中继承的基础。为基类型编写的函数可以处理任何派生类型,只要基类型嵌入在最前面即可。这种类型转换是安全的,因为内存布局完全一致。

challenge icon

挑战

简单

让我们通过构建一个简单的动物层次结构来探索首成员规则。你将创建一个基础 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;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C 在线编译器