Menu
Coddy logo textTech

Правило первого элемента

Часть раздела Объектно-ориентированное программирование путешествия по C на Coddy. Урок 29 из 61.

Когда вы встраиваете struct как first member другой struct, происходит нечто мощное: адрес памяти внешней struct идентичен адресу памяти её first member. Это гарантируется стандартом 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. function, написанная для базового типа, может работать с любым производным типом, если базовый тип embedded первым. cast безопасен, потому что memory layouts align perfectly.

challenge icon

Задание

Легко

Давайте изучим правило первого member, построив простую иерархию животных. Вы создадите базовую структуру Animal и производную структуру Dog, а затем продемонстрируете, как указатель на Dog можно безопасно привести к указателю на Animal.

Ключевая идея, которую вы примените: когда Animal является first member структуры Dog, обе структуры имеют один и тот же начальный адрес в memory. Это означает, что вы можете передать Dog* любой function, ожидающей Animal*, приведя указатель.

Вы организуете код в трёх файлах:

  • animal.h: Define обе структуры с защитой от повторного включения. Ваша структура Animal должна иметь единственное поле: legs (целое число, представляющее number ног). Ваша структура Dog должна включать Animal как свой first member и добавить поле name (массив символов из 50 символов). Также Declare function print_animal, принимающую параметр Animal*.
  • animal.c: Implement function print_animal. Эта function работает с базовым типом. Она знает только о Animal и выводит, сколько ног у животного.
  • main.c: Create переменную Dog и заполнить её поля (и legs встроенного animal, и имя собаки). Затем продемонстрируйте правило first member, приведя свой Dog* к Animal* и передав его в print_animal. Также отдельно выведите имя собаки, чтобы показать полную картину.

Вы получите два входных значения: имя собаки (строку) и number ног (целое число).

Ваш вывод должен выглядеть так:

Dog: Buddy
Legs: 4

Здесь Buddy — имя собаки, а 4 — number ног. Сначала выведите имя собаки из main.c, затем вызовите print_animal с приведённым указателем, чтобы вывести количество ног.

Это демонстрирует, как function, написанная для базового типа (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