Menu
Coddy logo textTech

Глубокое копирование

Часть раздела 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;
}

Теперь каждый объект владеет своей собственной строкой. Вы можете изменять или освобождать одну, не затрагивая другую. Этот паттерн прост: для каждого члена-указателя выделите новую память и скопируйте содержимое, а не только адрес.

challenge icon

Задание

Легко

Давайте создадим модуль Student, который демонстрирует паттерн глубокого копирования (deep copy). Вы создадите функцию клонирования, которая создает полностью независимую копию объекта студента — такую, где изменение или освобождение оригинала никак не влияет на клон.

Вы создадите три файла:

  • student.h: Объявите структуру Student с двумя членами: char *name (динамически выделенная строка) и int grade. Объявите три функции:
    • create_student — конструктор, который принимает имя и оценку, возвращает указатель на новый объект Student
    • clone_student — принимает const Student * и возвращает указатель на новую, независимую копию
    • free_student — деструктор, который правильно освобождает всю выделенную память
    Используйте include guards с символом 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;
}
quiz iconПроверьте себя

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

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