Глубокое копирование
Часть раздела Object Oriented Programming путешествия по C на Coddy — урок 15 из 61.
Когда вы присваиваете одну структуру другой в C, вы получаете поверхностную копию — все поля копируются побайтово. Это отлично работает для простых значений, таких как целые числа, но создает проблему с указателями: обе структуры в итоге указывают на одну и ту же область памяти.
typedef struct {
char *name;
int age;
} Person;
Person alice = {"Alice", 30};
Person copy = alice; // Поверхностное копирование!
// И alice.name, и copy.name указывают на ОДНУ И ТУ ЖЕ строку
Если вы освобождаете один, другой становится недействительным. Если вы изменяете строку через один, оба видят изменения. Это редко то, что вам нужно.
Глубокое копирование выделяет новую память для членов-указателей и копирует фактические данные:
Person *clone_person(const Person *original) {
Person *copy = malloc(sizeof(Person));
copy->name = malloc(strlen(original->name) + 1);
strcpy(copy->name, original->name);
copy->age = original->age;
return copy;
}
Теперь каждый объект владеет своей собственной строкой. Вы можете изменять или освобождать одну, не затрагивая другую. Этот паттерн прост: для каждого члена-указателя выделите новую память и скопируйте содержимое, а не только адрес.
Задание
ЛегкоДавайте создадим модуль Student, который демонстрирует паттерн глубокого копирования (deep copy). Вы создадите функцию клонирования, которая создает полностью независимую копию объекта студента — такую, где изменение или освобождение оригинала никак не влияет на клон.
Вы создадите три файла:
student.h: Объявите структуруStudentс двумя членами:char *name(динамически выделенная строка) иint grade. Объявите три функции:create_student— конструктор, который принимает имя и оценку, возвращает указатель на новый объект Studentclone_student— принимаетconst Student *и возвращает указатель на новую, независимую копиюfree_student— деструктор, который правильно освобождает всю выделенную память
STUDENT_H.student.c: Реализуйте все три функции. Ваша функцияclone_studentявляется ключевой — она должна выделять новую память как для структуры, так и для строки имени, а затем копировать сами данные строки (а не просто адрес указателя). Это гарантирует, что клон владеет собственной памятью, полностью отделенной от оригинала.main.c: Создайте студента, клонируйте его, выведите информацию о обоих студентах, а затем правильно очистите оба объекта.
Вы получите два входных значения: имя студента (строка) и оценка (целое число).
В вашем основном файле создайте студента с предоставленными значениями, затем создайте клон этого студента. Выведите информацию об оригинале и клоне, чтобы убедиться, что они содержат одинаковые данные. Наконец, освободите обоих студентов и выведите подтверждение.
Выводите результат в следующем формате:
Original: {name}, Grade: {grade}
Clone: {name}, Grade: {grade}
Both freedНапример, при входных данных Alice и 95, вывод будет следующим:
Original: Alice, Grade: 95
Clone: Alice, Grade: 95
Both freedПаттерн глубокого копирования гарантирует, что каждый объект действительно независим — у клона есть своя выделенная строка, а не общий указатель на память оригинала.
Шпаргалка
Когда вы присваиваете одну структуру другой в C, вы получаете поверхностную копию (shallow copy) — все поля копируются побайтово. В случае с указателями обе структуры в итоге указывают на одну и ту же область памяти:
typedef struct {
char *name;
int age;
} Person;
Person alice = {"Alice", 30};
Person copy = alice; // Поверхностная копия!
// И alice.name, и copy.name указывают на ОДНУ И ТУ ЖЕ строку
Глубокое копирование (deep copy) выделяет новую память для членов-указателей и копирует сами данные:
Person *clone_person(const Person *original) {
Person *copy = malloc(sizeof(Person));
copy->name = malloc(strlen(original->name) + 1);
strcpy(copy->name, original->name);
copy->age = original->age;
return copy;
}
Паттерн глубокого копирования: для каждого члена-указателя выделяется новая память и копируется содержимое, а не просто адрес. Это гарантирует, что каждый объект владеет собственной памятью и может быть изменен или освобожден независимо.
Попробуйте сами
#include <stdio.h>
#include <stdlib.h>
#include "student.h"
int main() {
char name[100];
int grade;
// Чтение входных данных
scanf("%s", name);
scanf("%d", &grade);
// TODO: Создать студента с указанным именем и оценкой
// TODO: Создать копию студента
// TODO: Вывести информацию об оригинальном студенте
// Формат: "Original: {name}, Grade: {grade}"
// TODO: Вывести информацию о клоне
// Формат: "Clone: {name}, Grade: {grade}"
// TODO: Освободить память обоих студентов
// Вывести подтверждение
printf("Both freed\n");
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Основы модульного программирования
Заголовочные файлыСтражи включенияИсходные файлыСтатические функцииПовторение: Модульный калькулятор4Инкапсуляция
Концепция непрозрачных указателейОпределение непрозрачных структурГеттеры и сеттерыВалидация в сеттерахИтоги: Секретный ящик2Объекты и методы
Структуры как объектыУказатель 'Self'Константная корректностьУказатель против значенияВспомогательные методыИтоги: Point Manager5Проект: Простой банковский счет
Настройка проектаРеализация счета3Жизненный цикл объекта
Паттерн «Конструктор»Паттерн «Деструктор»Инициализация в стекеГлубокое копированиеПовторение: String Wrapper6Наследование через композицию
Встраивание структурПравило первого элементаДоступ к элементам родителяUpcastingПовторение: Иерархия фигур9Проект: Рисование фигур
Обзор проектаРеализация круга