محتويات الدورة

C++ / الأساسي / الدرس 4

الدوال والمراجع والعقود

عبّر عن الحساب بواجهة صغيرة، واختر طريقة تمرير الوسائط، وامنع المراجع من تجاوز عمر الكائنات التي تشير إليها.

وقت القراءة التقريبي: 4 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • فصل التصريحات والتعريفات والوسائط والقيم المعادة.
  • اختيار التمرير بالقيمة أو بمرجع قابل للتعديل أو ثابت بوعي.
  • توثيق الشروط السابقة واللاحقة وأعمار الاستعارة.

إسناد مهمة واضحة للدالة

يصف تصريح الدالة اسمها وأنواع معاملاتها ونوع الإرجاع، ويقدّم تعريفها الجسم. يهيّئ الوسيط معاملًا عند الاستدعاء. فضّل دالة صغيرة يصف اسمها عملية المسألة بدل خطوات تنفيذها. يوضّح العقد ما يجب أن يوفّره المستدعي وما تضمنه الدالة بعد ذلك. في حساب الخصم تهم الأسعار المقبولة وحدود النسبة بقدر أهمية الحساب. تجعل إعادة القيمة النتيجة ظاهرة وتتفادى التغييرات المفاجئة في حالة مشتركة بعيدة.

المعامل بالقيمة كائن مستقل

ينسخ تمرير int بالقيمة قيمته إلى معامل مستقل. لا يغيّر إسناد المعامل متغيّر المستدعي. وهذا غالبًا أوضح اختيار للقيم العادية الصغيرة. تنقل القيمة المعادة الإجابة صراحة. في المثال الأول تستقبل doubled نسخة من tickets وتحسب ناتجها وتترك tickets كما هو. وتناسب إعادة الكائن بالقيمة نتائج أكبر أيضًا؛ إذ تستطيع C++ الحديثة إنشاء النتيجة مباشرة، فلا تُعد مرجعًا إلى كائن محلي لتجنب نسخة تتخيل أنها ضرورية.

cpp نسخ الإدخال وإعادة النتيجة
#include <iostream>
int doubled(int value) { return value * 2; }
int main() {
    const int tickets{6};
    const int result = doubled(tickets);
    std::cout << tickets << " " << result << "\n";
}
الناتج المتوقع
6 12

المعامل مستقل عن tickets. الضرب قابل للتمثيل لهذا الإدخال الصغير، ويحتفظ المستدعي بالقيمة ستة.

المرجع يتيح الوصول إلى كائن قائم

يمثل معامل int& اسمًا بديلًا لعدد صحيح قائم ويسمح بتعديله. ويستعير const std::string& سلسلة للقراءة دون نسخها عبر المعامل. تقيد const مسار الوصول هذا ولا تنشئ لقطة مملوكة. يجب تهيئة المراجع ولا يمكن إعادة توجيهها لاحقًا. في المثال الثاني تعدّل add_bonus وسيطها بوضوح، بينما تراقب length_report سلسلة حية فقط. ينبغي أن يوضّح اسم الدالة المعدّلة أثرها، ويجب أن يبقي المستدعي الكائن المشار إليه حيًا طوال الاستدعاء.

cpp واجهتان مختلفتان للتعديل والمراقبة
#include <iostream>
#include <string>
void add_bonus(int& points) { points += 3; }
std::string::size_type length_report(const std::string& name) {
    return name.size();
}
int main() {
    int points{7};
    const std::string name{"Lina"};
    add_bonus(points);
    std::cout << points << " " << length_report(name) << "\n";
}
الناتج المتوقع
10 4

يغيّر مرجع العدد points إلى عشرة، ويقرأ المرجع الثابت أربعة بايتات دون تعديل السلسلة أو تخزينها.

الاستعارة لا تمدّد كل الأعمار

يحدّد المرجع كائنًا ولا يملك عمره. تترك إعادة مرجع إلى متغيّر محلي مرجعًا متدليًا بعد عودة الدالة؛ واستخدامه ككائن حي سلوك غير معرّف. يبقى المؤقت المرتبط بمعامل مرجعي ثابت طوال الاستدعاء، وليس طوال أي استعمال لاحق لمرجع مخزّن. أعد البيانات المنشأة حديثًا بالقيمة، واقصر المراجع المعادة على كائنات مالكها وعمرها واضحان. ارسم نطاق المالك وكل مستعير عندما يصعب فهم الواجهة.

إبقاء التحميل الزائد متوقعًا

يمنح التحميل الزائد دوال متعددة الاسم نفسه مع قوائم معاملات مختلفة. لا يكفي نوع الإرجاع وحده للتمييز. قد تختار التحويلات الضمنية دالة غير متوقعة أو تجعل الاستدعاء ملتبسًا، فتجنب مجموعات تتطلب تخمينًا عدديًا دقيقًا. صرّح بالدوال قبل استخدامها أو ضع تعريفاتها أول الملف. اختبر العقد بالقيم العادية والحدية، بما في ذلك تغير الإدخال. يسهل اختبار الحساب الخالص لأن مدخلاته ومخرجاته صريحة، وتحتاج العملية المعدّلة كذلك إلى فحص الحالة الناتجة.

الأخطاء الشائعة وإصلاحها

قيمة المستدعي لا تتغير.

لماذا يحدث؟
تعدّل الدالة نسخة معامل مرر بالقيمة.
كيف تصلحه؟
أعد النتيجة المعدّلة أو استخدم مرجعًا قابلًا للتعديل عن قصد.
كيف تتجنب تكراره؟
حدّد دلالة المعاملات قبل تنفيذ الجسم.

مرجع معاد يصبح غير صالح.

لماذا يحدث؟
دُمّر مالكه المحلي عند عودة الدالة.
كيف تصلحه؟
أعد الكائن الجديد بالقيمة.
كيف تتجنب تكراره؟
وثّق مالك كل مرجع يغادر نطاقه.

استدعاء ملتبس بين دوال محمّلة.

لماذا يحدث؟
تحويلات ضمنية متعددة لها ترتيب متقارب.
كيف تصلحه؟
استخدم نوع معامل أوضح أو أسماء عمليات مختلفة.
كيف تتجنب تكراره؟
اجعل عائلة التحميل الزائد متسقة دلاليًا.

اختبر فهمك

1. اكتب عقد دالة تعيد الأكبر من قيمتي int دون تغيير أي إدخال. اختر أسلوب المعاملات والنتيجة.

اعرض الحل المشروح

استخدم int larger(int left, int right) وأعد left >= right ? left : right. المعاملان نسختان صغيرتان والنتيجة قيمة، فلا يغادر اعتماد على عمر كائن. عند تساويهما تعود القيمة المشتركة. تعطي الاختبارات larger(3, 8) وlarger(8, 3) وlarger(4, 4) النتائج 8 و8 و4.

2. تنشئ دالة سلسلة std::string محلية ثم تعيد const std::string&. اشرح الخلل وأعد التصميم دون إسناد ناتج للبرنامج غير الصالح.

اعرض الحل المشروح

تُدمّر السلسلة المحلية قبل أن يستطيع المستدعي استخدام المرجع بأمان. غيّر نوع الإرجاع إلى std::string وأعد السلسلة بالقيمة، فيملك المستدعي نتيجته مستقلًا. قد تجعل إزالة النسخ أو النقل ذلك فعالًا، لكن صحة الملكية هي السبب الأساسي. لا تختبر النسخة المتدلية على افتراض أن نصها المرصود مضمون.

أهم ما تعلمته

  • مرّر القيم الصغيرة بالقيمة وأظهر التعديل صراحة.
  • أعد نتائج مملوكة للكائنات المنشأة حديثًا.
  • يحتاج كل مرجع مستعار إلى عمر مالك صالح.

المراجع الرسمية