النسخ العميق (Deep Copy)
جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 15 من 61.
عندما تقوم بتعيين struct إلى آخر في لغة C، فإنك تحصل على shallow copy—يتم نسخ جميع الحقول بايت مقابل بايت. يعمل هذا بشكل جيد مع القيم البسيطة مثل integers، ولكنه يسبب مشكلة مع pointers: حيث ينتهي الأمر بكلا الـ structs بالإشارة إلى نفس الذاكرة.
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;
}
الآن يمتلك كل كائن سلسلته النصية الخاصة. يمكنك تعديل أو تحرير أحدها دون التأثير على الآخر. النمط بسيط: لكل عضو مؤشر (pointer member)، قم بتخصيص ذاكرة جديدة ونسخ المحتويات - وليس مجرد العنوان.
التحدي
سهللنقم ببناء وحدة Student توضح نمط النسخ العميق (deep copy). ستقوم بإنشاء وظيفة استنساخ تنتج نسخة مستقلة تماماً من كائن الطالب — نسخة لا يؤثر فيها تعديل أو تحرير الكائن الأصلي على النسخة المستنسخة.
ستقوم بإنشاء ثلاثة ملفات:
student.h: قم بالتصريح عن هيكلStudentيحتوي على عضوين:char *name(سلسلة نصية مخصصة ديناميكياً) وint grade. قم بالتصريح عن ثلاث وظائف:create_student— منشئ (constructor) يأخذ اسماً ودرجة، ويعيد مؤشراً إلى Student جديد.clone_student— يأخذconst Student *ويعيد مؤشراً إلى نسخة جديدة ومستقلة.free_student— مدمر (destructor) يقوم بتحرير جميع الذاكرة المخصصة بشكل صحيح.
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يضمن نمط النسخ العميق أن كل كائن مستقل حقاً — فالنسخة المستنسخة لديها سلسلة نصية مخصصة خاصة بها، وليست مؤشراً مشتركاً لذاكرة الأصل.
ورقة مرجعية
عندما تقوم بتعيين هيكل (struct) إلى آخر في لغة C، فإنك تحصل على نسخة ضحلة (shallow copy)—يتم نسخ جميع الحقول بايت تلو الآخر. مع المؤشرات (pointers)، ينتهي الأمر بكلا الهيكلين بالإشارة إلى نفس الذاكرة:
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: إنشاء نسخة (clone) من الطالب
// TODO: طباعة معلومات الطالب الأصلي
// التنسيق: "Original: {name}, Grade: {grade}"
// TODO: طباعة معلومات النسخة
// التنسيق: "Clone: {name}, Grade: {grade}"
// TODO: تحرير (Free) كلا الطالبين
// طباعة التأكيد
printf("Both freed\n");
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات البرمجة التركيبية
ملفات الترويسةحواجز التضمينملفات المصدرالدوال الساكنةمراجعة: آلة حاسبة تركيبية4التغليف (Encapsulation)
مفهوم الـ Opaque Pointersتعريف الـ Opaque Structsالـ Getters والـ Settersالتحقق من البيانات في الـ Settersملخص: الصندوق السري2الكائنات والأساليب
الـ Structs ككائناتمؤشر 'Self'صحة استخدام Constالمؤشر مقابل القيمةالأساليب المساعدةملخص: Point Manager5مشروع: حساب بنكي بسيط
إعداد المشروعتنفيذ الحساب3دورة حياة الكائن
نمط المنشئ (Constructor Pattern)نمط الهادم (Destructor Pattern)تهيئة الـ Stackالنسخ العميق (Deep Copy)مراجعة: String Wrapper6الوراثة عبر التركيب
تضمين الـ Structقاعدة العضو الأولالوصول إلى أعضاء الأبعملية الـ Upcastingمراجعة: هرمية الأشكال9مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة