Правило первого элемента
Часть раздела Объектно-ориентированное программирование путешествия по 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.
Задание
ЛегкоДавайте изучим правило первого 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 functionprint_animal, принимающую параметрAnimal*.animal.c: Implement functionprint_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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация кругаРеализация прямоугольникаПолиморфное использованиеКонтейнер фигурПотренируйтесь самостоятельно: Онлайн-компилятор C