Menu
CoddyTech

Sao chép sâu

Thuộc phần Lập trình hướng đối tượng trong hành trình C của Coddy. Bài học 15 trên 61.

Khi gán một struct cho một struct khác trong C, bạn sẽ có một bản sao nông: mọi trường đều được sao chép nguyên vẹn từng byte. Cách này hoạt động tốt với các giá trị đơn giản như số nguyên, nhưng gây ra vấn đề với con trỏ: cả hai struct đều trỏ đến cùng một vùng nhớ.

typedef struct {
    char *name;
    int age;
} Person;

Person alice = {"Alice", 30};
Person copy = alice;  // Sao chép nông!
// Cả alice.name và copy.name đều trỏ đến CÙNG MỘT chuỗi

Nếu bạn giải phóng một đối tượng, đối tượng còn lại sẽ không còn hợp lệ. Nếu bạn sửa chuỗi (string) thông qua một đối tượng, cả hai đều thấy thay đổi đó. Đây hiếm khi là điều bạn muốn.

Một bản sao sâu cấp phát bộ nhớ mới cho các thành viên con trỏ và sao chép dữ liệu thực tế:

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;
}

Giờ đây, mỗi đối tượng sở hữu chuỗi riêng. Bạn có thể sửa đổi hoặc giải phóng một chuỗi mà không ảnh hưởng đến chuỗi còn lại. Cách làm rất đơn giản: với mỗi thành viên con trỏ, hãy cấp phát vùng nhớ mới và sao chép nội dung, không chỉ sao chép địa chỉ.

challenge icon

Thử thách

Dễ

Hãy xây dựng một module Student minh họa mẫu sao chép sâu. Bạn sẽ tạo một hàm sao chép để tạo ra một bản sao hoàn toàn độc lập của đối tượng sinh viên: khi bạn sửa đổi hoặc giải phóng bản gốc, bản sao vẫn không bị ảnh hưởng.

Bạn sẽ tạo ba tệp:

  • student.h: Khai báo một struct Student có hai thành viên: char *name (một chuỗi được cấp phát động) và int grade. Khai báo ba hàm:
    • create_student: hàm khởi tạo nhận tên và điểm, trả về con trỏ đến một đối tượng Student mới
    • clone_student: nhận một const Student * và trả về con trỏ đến một bản sao mới, độc lập
    • free_student: hàm hủy giải phóng đúng cách toàn bộ bộ nhớ đã cấp phát
    Dùng các chỉ thị bảo vệ include với ký hiệu STUDENT_H.
  • student.c: Triển khai cả ba hàm. Hàm clone_student là phần cốt yếu. Hàm này phải cấp phát bộ nhớ mới cho cả struct lẫn chuỗi tên, sau đó sao chép dữ liệu chuỗi thực tế, không chỉ sao chép địa chỉ con trỏ. Nhờ vậy, bản sao hoàn toàn sở hữu vùng nhớ riêng, tách biệt với bản gốc.
  • main.c: Tạo một sinh viên, sao chép sinh viên đó, hiển thị thông tin của cả hai, rồi dọn dẹp cả hai đối tượng đúng cách.

Bạn sẽ nhận hai đầu vào: tên sinh viên (một chuỗi) và điểm (một số nguyên).

Trong tệp main, hãy tạo một sinh viên bằng các giá trị đã cho, rồi tạo bản sao của sinh viên đó. In thông tin của cả bản gốc lẫn bản sao để xác nhận chúng chứa cùng dữ liệu. Cuối cùng, giải phóng cả hai sinh viên và in thông báo xác nhận.

In đầu ra theo định dạng này:

Original: {name}, Grade: {grade}
Clone: {name}, Grade: {grade}
Both freed

Ví dụ, với đầu vào Alice và 95, đầu ra sẽ là:

Original: Alice, Grade: 95
Clone: Alice, Grade: 95
Both freed

Mẫu sao chép sâu đảm bảo mỗi đối tượng thực sự độc lập: bản sao có chuỗi được cấp phát riêng, không dùng chung con trỏ trỏ đến vùng nhớ của bản gốc.

Tự làm thử

#include <stdio.h>
#include <stdlib.h>
#include "student.h"

int main() {
    char name[100];
    int grade;
    
    // Đọc đầu vào
    scanf("%s", name);
    scanf("%d", &grade);
    
    // TODO: Tạo một sinh viên với tên và điểm đã cho
    
    // TODO: Tạo một bản clone của sinh viên
    
    // TODO: In ra thông tin của sinh viên gốc
    // Định dạng: "Original: {name}, Grade: {grade}"
    
    // TODO: In ra thông tin của bản clone
    // Định dạng: "Clone: {name}, Grade: {grade}"
    
    // TODO: Giải phóng bộ nhớ của cả hai sinh viên
    
    // In ra thông báo xác nhận
    printf("Both freed\n");
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C online