المؤشر مقابل القيمة
جزء من قسم Object Oriented Programming في رحلة C على Coddy — الدرس 9 من 61.
عندما تقوم بتمرير struct إلى دالة، تمنحك لغة C خيارين: تمريره بالقيمة (نسخ الـ struct بالكامل) أو تمريره بواسطة مؤشر (نسخ العنوان فقط). هذا الاختيار يهم بشكل كبير من أجل الأداء.
يؤدي التمرير بالقيمة إلى إنشاء نسخة كاملة من الـ struct على المكدس (stack). بالنسبة للـ struct الصغيرة التي تحتوي على عدد قليل من الأعداد الصحيحة (integers)، يعد هذا أمراً جيداً. لكن تخيل وجود struct بمئات البايتات — فكل استدعاء دالة سيقوم بنسخ كل تلك البيانات:
typedef struct {
char name[100];
int scores[50];
double average;
} Student;
// بالقيمة: ينسخ حوالي 608 بايت في كل استدعاء
void print_by_value(Student s) {
printf("%s: %.2f\n", s.name, s.average);
}
// بواسطة المؤشر: ينسخ 8 بايت فقط (العنوان)
void print_by_pointer(const Student *s) {
printf("%s: %.2f\n", s->name, s->average);
}
تختلف الصيغة أيضاً. مع معامل القيمة، تستخدم عامل النقطة (s.name). ومع المؤشر، تستخدم عامل السهم (s->name).
إصدار المؤشر أكثر كفاءة لأنه يمرر فقط عنوان ذاكرة—عادةً ما يكون 8 بايت بغض النظر عن حجم struct.
هذا هو السبب في أن مبرمجي لغة C يمررون الهياكل (structs) عبر المؤشرات (pointers) دائمًا تقريبًا. استخدم const عندما لا ينبغي للدالة تعديل البيانات، ومؤشرًا عاديًا عندما ينبغي لها ذلك. ستحصل على الكفاءة والوضوح في الغرض معًا.
التحدي
سهللنقم ببناء وحدة Product توضح الفرق بين تمرير الهياكل (structs) بالقيمة (by value) وبالعنوان (by pointer). ستقوم بإنشاء دالتين للعرض—واحدة تستقبل الهيكل بالكامل كنسخة، والأخرى تستقبل مجرد مؤشر إليه.
ستقوم بإنشاء ثلاثة ملفات:
product.h: قم بالتصريح عن هيكلProductبثلاثة أعضاء:char name[50]، وdouble price، وint quantity. قم أيضاً بالتصريح عن دالتين:display_by_value— تأخذProductبالقيمة وتطبع معلوماتهdisplay_by_pointer— تأخذconst Product *وتطبع نفس المعلومات
PRODUCT_H.product.c: قم بتنفيذ كلتا الدالتين. لاحظ الفرق في بناء الجملة: إصدار القيمة يستخدم عامل النقطة (p.name)، بينما إصدار المؤشر يستخدم عامل السهم (p->name). يجب أن تطبع كلتا الدالتين بنفس التنسيق.main.c: قم بإنشاء Product، ثم استدعِ كلتا دالتي العرض لإظهار أنهما تنتجان مخرجات متطابقة على الرغم من استخدام أنماط معاملات مختلفة.
ستتلقى ثلاثة مدخلات: اسم المنتج (سلسلة نصية)، و السعر (عدد عشري مزدوج)، و الكمية (عدد صحيح).
في ملفك الرئيسي، قم بتهيئة Product بهذه القيم، ثم استدعِ display_by_value متبوعة بـ display_by_pointer.
يجب أن تطبع كلتا الدالتين بهذا التنسيق:
Product: {name}, Price: {price}, Qty: {quantity}استخدم %.2f للسعر لعرض منزلتين عشريتين.
على سبيل المثال، مع المدخلات Laptop، و 999.99، و 5، ستكون المخرجات:
Product: Laptop, Price: 999.99, Qty: 5
Product: Laptop, Price: 999.99, Qty: 5ورقة مرجعية
عند تمرير الهياكل (structs) إلى الدوال، يمكنك الاختيار بين التمرير بالقيمة (نسخ الهيكل بالكامل) أو التمرير بالمؤشر (نسخ العنوان فقط).
التمرير بالقيمة ينشئ نسخة كاملة من الهيكل:
void print_by_value(Student s) {
printf("%s: %.2f\n", s.name, s.average); // Use dot operator
}
التمرير بالمؤشر يمرر عنوان الذاكرة فقط:
void print_by_pointer(const Student *s) {
printf("%s: %.2f\n", s->name, s->average); // Use arrow operator
}
الاختلافات الرئيسية:
- التمرير بالقيمة يستخدم عامل النقطة (
s.member) - التمرير بالمؤشر يستخدم عامل السهم (
s->member) - المؤشرات أكثر كفاءة للهياكل الكبيرة (8 بايت فقط مقابل حجم الهيكل بالكامل)
- استخدم
constمع المؤشرات عندما لا ينبغي للدالة تعديل البيانات
يفضل مبرمجو C عادةً تمرير الهياكل بالمؤشر للحصول على أداء أفضل.
جرّب بنفسك
#include <stdio.h>
#include <string.h>
#include "product.h"
int main() {
char name[50];
double price;
int quantity;
// قراءة المدخلات
scanf("%s", name);
scanf("%lf", &price);
scanf("%d", &quantity);
// TODO: قم بإنشاء متغير Product وتهيئته بقيم المدخلات
// تلميح: استخدم strcpy لنسخ الاسم إلى حقل الاسم في الهيكل (struct)
// TODO: قم باستدعاء display_by_value مع الـ Product
// TODO: قم باستدعاء display_by_pointer مع مؤشر (pointer) إلى الـ Product
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مشروع: رسّام الأشكال
نظرة عامة على المشروعتنفيذ الدائرة