Menu
Coddy logo textTech

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

Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 29 из 61.

Когда вы встраиваете struct в качестве первого члена другой struct, происходит нечто мощное: адрес памяти внешней struct идентичен адресу памяти её первого члена. Это гарантируется стандартом 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: Определите обе структуры с использованием include guards. Ваша структура 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.

Шпаргалка

Когда структура вложена в качестве первого члена другой структуры, обе структуры имеют один и тот же начальный адрес памяти. Это гарантируется стандартом C.

typedef struct {
    int id;
} Parent;

typedef struct {
    Parent parent;  // Первый член
    int grade;
} Child;

Указатель на внешнюю структуру можно безопасно привести к указателю на её первый член:

Child c;
Child* child_ptr = &c;
Parent* parent_ptr = (Parent*)child_ptr;  // Безопасное приведение — тот же адрес

Это позволяет передавать указатель на производный тип в функции, ожидающие базовый тип:

void print_id(Parent* p) {
    printf("ID: %d\n", p->id);
}

Child c;
c.parent.id = 42;
print_id((Parent*)&c);  // Работает, так как структуры памяти совпадают

Эта техника является основой наследования в 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
    // Это демонстрирует правило первого члена (first member rule)!
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Object Oriented Programming