تمرير مؤشرات الهياكل
جزء من قسم Logic & Flow في رحلة C على Coddy — الدرس 47 من 63.
بينما يعمل تمرير الهياكل (structs) بالقيمة بشكل جيد لقراءة البيانات، فإنه يصبح غير فعال مع الهياكل الكبيرة لأن الهيكل بأكمله يتم نسخه. والأهم من ذلك، لا يمكنك تعديل الهيكل الأصلي من داخل الدالة. هنا يصبح تمرير المؤشرات إلى الهياكل (pointers to structs) أمراً ضرورياً.
عندما تقوم بتمرير مؤشر (pointer) إلى هيكل (struct) بدلاً من الهيكل نفسه، تستقبل الدالة عنوان الذاكرة للهيكل الأصلي. يوفر هذا النهج ميزتين رئيسيتين: فهو أكثر كفاءة (لا توجد عملية نسخ) ويسمح للدالة بتعديل البيانات الأصلية.
صيغة الدالة التي تقبل مؤشراً إلى هيكل (struct) هي:
void functionName(struct StructureName *ptr) {
// جسم الدالة باستخدام ptr->member
}داخل الدالة، تستخدم عامل السهم (arrow operator) للوصول إلى أعضاء الـ struct وتعديلها. أي تغييرات يتم إجراؤها من خلال المؤشر (pointer) ستؤثر بشكل مباشر على الـ struct الأصلي في الدالة المستدعية، مما يجعل هذا النهج مثاليًا للدوال التي تحتاج إلى تحديث أو تحويل بيانات الـ struct.
تعد هذه التقنية أساسية لبناء برامج C فعالة تتعامل مع هياكل بيانات معقدة، حيث تجمع بين قوة المؤشرات (pointers) وتنظيم الـ structs.
التحدي
سهلقم بإنشاء برنامج بلغة C يوضح كيفية تمرير مؤشرات (pointers) للهياكل (structs) إلى الدوال لتعديل البيانات بكفاءة. يجب أن يقوم برنامجك بما يلي:
- تعريف
structباسمBankAccountبالأعضاء التالية:- عدد صحيح
accountNumberلتخزين رقم الحساب - مصفوفة أحرف
ownerNameبحجم 30 لتخزين اسم صاحب الحساب - رقم عشري
balanceلتخزين رصيد الحساب - عدد صحيح
transactionCountلتخزين عدد المعاملات
- عدد صحيح
- كتابة دالة باسم
depositMoneyتقوم بما يلي:- تستقبل مؤشراً إلى هيكل
BankAccountومبلغاً عشرياً كمعاملات - تضيف المبلغ إلى رصيد الحساب باستخدام عامل السهم (arrow operator)
- تزيد عدد المعاملات بمقدار 1
- تطبع
"Deposit successful. New balance: %.2f\n"
- تستقبل مؤشراً إلى هيكل
- كتابة دالة باسم
withdrawMoneyتقوم بما يلي:- تستقبل مؤشراً إلى هيكل
BankAccountومبلغاً عشرياً كمعاملات - تتحقق مما إذا كان مبلغ السحب أكبر من الرصيد الحالي
- في حالة عدم كفاية الأموال، تطبع
"Insufficient funds. Current balance: %.2f\n" - في حالة كفاية الأموال، تطرح المبلغ من الرصيد، وتزيد عدد المعاملات، وتطبع
"Withdrawal successful. New balance: %.2f\n"
- تستقبل مؤشراً إلى هيكل
- كتابة دالة باسم
displayAccountتقوم بما يلي:- تستقبل مؤشراً إلى هيكل
BankAccountكمعامل - تطبع معلومات الحساب بهذا التنسيق الدقيق:
"Account Information:\n""Account Number: %d\n""Owner: %s\n""Balance: %.2f\n""Transactions: %d\n"
- تستقبل مؤشراً إلى هيكل
- في الدالة الرئيسية (main function):
- إنشاء متغير من نوع
BankAccountباسمaccount - قراءة رقم الحساب، واسم المالك، والرصيد الأولي من المدخلات
- تعيين عدد المعاملات إلى 0
- استدعاء
displayAccountلإظهار حالة الحساب الأولية - قراءة مبلغ الإيداع واستدعاء
depositMoney - قراءة مبلغ السحب واستدعاء
withdrawMoney - استدعاء
displayAccountلإظهار حالة الحساب النهائية
- إنشاء متغير من نوع
يوضح هذا التحدي قوة تمرير المؤشرات إلى الهياكل (structs) في الدوال. على عكس التمرير بالقيمة (pass-by-value)، تستقبل الدوال عنوان الذاكرة للهيكل الأصلي، مما يسمح لها بتعديل البيانات الأصلية مباشرة. هذا النهج أكثر كفاءة (لا يوجد نسخ للبيانات) ويمكّن الدوال من تغيير قيم الهيكل بشكل دائم في الدالة المستدعية.
ورقة مرجعية
عند تمرير الهياكل (structs) بالقيمة، يتم نسخ الهيكل بالكامل، مما يجعله غير فعال للهياكل الكبيرة ويمنع تعديل البيانات الأصلية. تمرير المؤشرات إلى الهياكل (pointers to structs) يحل كلتا المشكلتين عن طريق تمرير عنوان الذاكرة بدلاً من ذلك.
صيغة الدالة لقبول مؤشر إلى هيكل:
void functionName(struct StructureName *ptr) {
// جسم الدالة باستخدام ptr->member
}داخل الدالة، استخدم عامل السهم (->) للوصول إلى أعضاء الهيكل وتعديلها من خلال المؤشر. أي تغييرات يتم إجراؤها ستؤثر بشكل مباشر على الهيكل الأصلي في الدالة المستدعية.
يوفر هذا النهج ميزتين رئيسيتين:
- أكثر كفاءة (لا يتم نسخ الهيكل بالكامل)
- يسمح للدوال بتعديل بيانات الهيكل الأصلية
جرّب بنفسك
#include <stdio.h>
#include <string.h>
// TODO: قم بتعريف هيكل BankAccount هنا
// TODO: اكتب دالة depositMoney هنا
// TODO: اكتب دالة withdrawMoney هنا
// TODO: اكتب دالة displayAccount هنا
int main() {
// قراءة المدخلات
int accountNum;
char ownerName[30];
float initialBalance;
float depositAmount;
float withdrawAmount;
scanf("%d", &accountNum);
scanf("%s", ownerName);
scanf("%f", &initialBalance);
scanf("%f", &depositAmount);
scanf("%f", &withdrawAmount);
// TODO: قم بإنشاء متغير BankAccount وتهيئته
// TODO: استدعِ displayAccount لعرض الحالة الأولية
// TODO: استدعِ depositMoney مع مبلغ الإيداع
// TODO: استدعِ withdrawMoney مع مبلغ السحب
// TODO: استدعِ displayAccount لعرض الحالة النهائية
return 0;
}يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Logic & Flow
1أساسيات المؤشرات
ما هو المؤشر؟الإعلان عن المؤشراتعامل العنوان (&)عامل إلغاء المرجعية (*)مؤشرات NULLمراجعة: أساسيات المؤشرات4مشروع: أداة نصوص بسيطة
نظرة عامة على المشروععد الأحرف2المؤشرات والمصفوفات
أسماء المصفوفات كمؤشراتعناصر المصفوفة - المؤشراتالعمليات الحسابية على المؤشراتمقارنة المؤشراتمراجعة: التنقل في المصفوفة باستخدام المؤشرات5المؤشرات والدوال
التمرير بالقيمةتمرير المؤشرات إلى الدوالتعديل المتغيرات عبر المؤشراتمثال كلاسيكي: التبديلتمرير المصفوفات إلى الدوالملخص: وسائط المؤشرات في الدوال8الهياكل والمؤشرات
المؤشرات إلى الهياكلعامل السهم (->)تمرير الهياكل بالقيمةتمرير مؤشرات الهياكلالتخصيص الديناميكي للهياكلمراجعة: تعديل الهياكل باستخدام المؤشرات11تحديات المراجعة النهائية
مراجعة: دمج السلاسل النصية الديناميكيمراجعة: مصفوفة من الـ Structsمراجعة: عداد تكرار الكلمات3مصفوفات الأحرف والسلاسل النصية
السلاسل النصية كمصفوفات charالمُنهي الصفري (Null Terminator)إدخال السلاسل النصية باستخدام scanfاستخدام strlen()استخدام strcpy()استخدام strcat()استخدام strcmp()مراجعة: دوال السلاسل النصية الأساسية