بسم الله الرحمان الرحيم
المقدمة ..
في بداية دراستي للغة C++ سمعت عن شئ اسمه الدوال ، استغربت هل هو مفهوم مشابه لمفهوم الالدوال الرياضية ، هل تقع عليها نفس العمليات ونفس القواعد والشروط ولكن تكون بصورة برمجية ، ولكن عندما تقدمت في هذه اللغة ووصلت الى مرحلة الدوال بدأت بدراسة مستفيضة عن هذا الجزء من اللغة ووجدت انه يتشابه مع دوال الرياضيات بالاسم والفكرة فقط ، فلو اردنا ان نتكلم عن الدوال الرياضية سنقول انها معادلة تحتوي على مجاهيل لها مهمة معينة ، ابسط مثال على ذلك ..
F(x)=x+3
هذه دالة رياضية ، ومن الواضح انها تحتوي على مجهول واحد .. اما الدالة البرمجية ..
parameters وتلفظ باراميتر : فالباراميتر هو الشئ الذي يمكن ان يتغير في الدالة كان يكون اسم او رقم او اي شئ يتوقع ان يتغير عند ادخال المستخدم للمعلومات (المستخدم وليس المبرمج ) . فمثلا لو برمجنا دالة صنع الحليب سنطلب من السمتخدم ادخال عدد الاقداح وهو الشئ الوحيد الذي يمكن ان يتغير ... فمهما كان العدد ستبقى طريقة الصنع ثابتة ..
ومن الممكن تطوير الدالة لتستقبل باريمتر اخر لو كان اخي لديه اختلاف معي في عدد معالق السكر التي اضعها له .. فهذا يمكن وضعه في السؤال ايضا وكباراميتر ايضا . فنساله كم عدد المعالق التي تريد من السكر فيجيب اثنين مثلا وانا دائما اصع ثلاثة وهذا اختلاف متوقع حصوله ..
لذلك بعد هذا المثال المتكامل الذي وضحت فيه اهم وابرز اجزاء الدوال سنأتي الى تعريفها العلمي ..
هي مجموعة من الجمل والخطوات البرمجية تنفذ سوية مهمة معينة وايضا الدوال تعتبر برنامجا فرعيا أي يمكن اخذ الداله من برنامج نعمل عليها ونضعها في برنامج اخر لتعطي نفس النتيجة .
وظيفة الدالة او الفائدة منها تظهر عندما تصبح برامجنا اكثر تعقيدا وطولا .. أي تلك التي تصل الى الاف الاسطر البرمجية .. حيث تكون عملية اكتشاف خطأ يحصل في برنامج بسهولة اكثر مما لو استخدمة الدالة الرئيسية main فقط . انا متأكد انك تفكر في ان اطول برنامج قد قمت ببرمجته لم يصل الى العشرين سطرا .. ولكن لا .. كلما توسعت في اللغة اكثر كلما زاد برنامجك طولا وتعقيدا .. لاننا لانبقى على نفس البرنامج طوال حياتنا وانما نتوسع في طريقة الحل والتفكير ..
من الواضح ان ابسط امور الحياة تتضمن دوال لم نكن نفكر بها سابقا لذلك فانا اعطيكم نصيحة لتخيل هذا المثال مع امثلة اخرى مثلا لدينا دالة من الواقع ايضا ولها نفس الامور في مثالنا السابق
وهو عن دالة الاتصال بالهاتف . .. فلكي نقوم بالاتصال نقوم اولا بالبحث عن الاسم المراد الاتصال به ثم نضغط على زر الاتصال وهنا لدينا شرط وهو ان يكون الهاتف مشغولا او خارج نطاق التغطيه او ان يتم رفع السماعه .
سؤال يطرح نفسه .. هل يتطلب هذا المثال(الدالة ) باراميتر او معامل له ..؟؟؟؟؟
نعم .. والمعامل هنا هو رقم الهاتف .. فالشئ الوحيد الذي يختلف لدينا في كل مرة عندما نتصل هو رقم الهاتف فقط فلا يمكن ان نتصل في المرة القادمة بالضغط على زر الخروج مثلا
انواع الدوال ..
اولا .. الدالة التي تعيد قيمة .
هي تلك الدالة التي يراد من خلالها ترجيع قيمة لكي تستخدم في البرنامج الريئيسي
مثلا في دالة اتصال الهاتف التي ذكرتها في الدرس السابق كنا نحتاج لترجيع قيمة وهي اذا تمت عملية الاتصال بنجاح ..
التوضيح ..
بعد ان نبرمج الدالة الخاصة بالاتصال سنختبرها هل تمت عملية الاتصال ام ان الهاتف مشغول ولاحظوا معي هنا جملة شرطية تحتوي اذا (IF) أي انه اذا قمنا بالاتصال فارجع القيمة تم والا فارجع لم يتم ..
مالفائدة من هذا الشئ (الترجيع ) .؟؟؟
الفائدة .. لو اننا اختبرنا عمل هذه الدالة في البرنامج الرئيسي MAIN ووضعنا القيمة 1 عند نجاح عملية الاتصال والقيمة 0 عند عدم نجاحها لظهرت لنا قيمة من تلك القيم لنتيجة الشرط اما 1 او 0 .
وانواع الترجيع هي على انواع المتغيرات نفسها ((طبعا لا اقصد المتغيرات ولكن اقصد انواع المتغيرات )) ان هنالك ترجيع من نوع عدد صحيح int وهنالك ترجيع من نوع رمز char . ويعتمد نوع الترجيع على نوع الدالة
واذا كانت الدالة لا ترجع أي قيمة ونعرف ذلك من خلال تفكيرنا وتحليلنا لها قبل عملية الكتابة البرمجية فنحن نكتب الكلمة المحجوزة void والتي تبين بان الدالة لاتعيد أي قيمة ..
طريقة تعريف الدالة ..
نستطيع تعريف الدالة في مكانين من البرنامج وهذين المكانين هما اما قبل الدالة main او بعدها ولا توجد اختلافات كبيره بينها سوى اختلافين سنوضحهما بعد قليل .
الشكل العام للتعريف
Data type Function_name ()
المقصود من data type هو نوع الدالة اذا كانت عددية او رمزية او ..... . اما function name فهو اسم الدالة ويخضع اسم الدالة لجميع قوعد الاسماء في المتغيرات وهي واضحة للجميع .
مثال : عرف دالة من نوع عدد صحيح اسمها number ..
الحل : طبعا لا يوجد سؤال في الدنيا مثل هذا السؤال ولكن هذا للتوضيح فقط وتعلم طريقة الاستخدام
int number ()
هذا هو تعريف الدالة المطلوبه بالسؤال .. ولو دققنا النظر لوجدنا ان number يشبه الى حد كبير تعريف المتغيرات ولكن الفرق هو وجود اقواس ومن هنا اكتب لكم ملاحظة جوهريه ومهمه جدا جدا ...
ملاحظة .. تتميز الدالة عن أي تعبير موجود في لغتي السي والسي ++ بوجود اقواس امامها .. وهذا بالنسبة للدوال الجاهزة وللدوال التي نبنيها ونستدعيها نحن .. فمثلا دالة الطباعة في لغة السي Printf فمن الخطأ التعبير عنها سواء بشرح او بعملية الكتابة البرمجيه بالصورة التي كتبتها الان انا .. لكن الصورة الصحيحه هي ان تكتب هكذا printf() لتتميز على انها دالة .
سؤال يطرح نفسه .. ما هي فائدة الاقواس .. ؟؟
الجواب .. الغرض الاول كما قلنا للتدليل على انها دالة ولكن ليست هذه هي الفائده فقط وانما لان المعامل او الparameter يوضع داخل الاقواس .. كما قلنا في الدرس السابق عنه ...
لتوضيح ما سبق سنقوم بكتابة مثالنا السابق الخاص بصنع الحليب بدالة برمجية متكاملة مع شرحها ..
اولا نحلل الدالة .. الدالة خطواتها معروفة مسبق ولكن هل تحتاج الى معاملات وكذلك الى ترجيع ..؟؟
من الواضح انها تحتاج الى معاملات وهو عدد الاقداح .. اذا لدينا معامل من نوع int ومعامل اخر هو عدد ملاعق السكر وهو ايضا من نوع int .
لا تحتاج هذه الدالة الى ترجيع لانه كما تعلمون مالذي يمكن ان نعيده .. نحن سنصنع بواسطتها كوب حليب ولا نرجع أي شئ ... ولكي لا يصبح هناك فهم خاطئ فالترجيع هنا ليس ترجيع ما يبقى من الحليب او السكر ولكن ترجيع شئ نستفاد منه بالدالة الرئيسية main تفيدنا في عمل البرنامج . واذا ما فهمتوا هذا الجزء انا اقوللكم هذا المتوقع ولكن مع الدروس وكذلك مع الممارسة ستفهموه جيدا .
Void milkmake( int cubs , int shugar ) { }
هذا هو تعريف الدالة وسنوضحه بالتفصيل الان ..
استعملنا كلمة void لان الدالة لا ترجع أي شئ وقد وضحنا هذا الشئ قبل قليل . لذلك فان الدالة لا نوع لها .
Milkmake هنا هو اسم الدالة وهي دالة صنع الحليب .
بعد اسم الدالة تاتي الاقواس التي في داخلها تكتب المعاملات . ولدينا اول معامل هو cubs والذي هو عدد الاقداح واخذ النوع int وايضا للدالة معامل اخر وهو shugar الخاص بعدد معالق السكر .
اما الاقواس {} فهي تعني ان بما داخلها هو جسم الدالة .
يمكننا ان نضع الخطوات الخاصة بتلك الدالة بين هذه الاقواس
هنا بينا للمصرف الخاص بلغة سي ++ باننا قد قمنا بتعريف دالة .
لدينا الان برنامج سنقوم ببرمجته مرتين الا وهو برنامج الاس التربيعي .. ففي المرة الاولى سنجعل البرنامج يرجع قيمة الناتج وفي المرة الثانية سنطبع النتيجة من دون ترجيع ..
الاول ...
// Creating a function and using it. Doesn’t use return. #include <iostream> using namespace std; void square(int i); int main(void) { int num; cout << "Please enter a number: "; cin >> num; square(num); return 0; } void square(int i) { int sq = i * i; cout << "Square of " << i << " is " << sq; }
والثاني
// Creating a function and using it. Uses return. #include <iostream> using namespace std; int square(int i); int main(void) { int num; int sq; cout << "Please enter a number: "; cin >> num; sq = square(num); cout << "Square of " << num << " is " << sq; return 0; } int square(int i) { int sq = i * i; return sq; }
والان سنأتي الى تفصيل البرنامجين ..
في البرنامج الاول قمنا بتعريف الدالة قبل الدالة الرئيسية وهو شئ مسموح به باللغتين السي والسي++ . ولكن مع ملاحظة شئ وهو وجود الفارزة المنقوطة في نهاية العبارة . لكن لو قمنا بتعريفها بعد الدالة main فلا يمكن وضع فارزة منقوطة ...
void square(int i);
استخدمنا الكلمة المحجوزة void والتي مفادها كما اسلفت سابقا بان الدالة لا تقوم بترجيع اي شئ لذلك نستخدمها هنا في هذا المثال .
لو خرجنا لدقائق من السي وذهبنا الى الرياضيات .. عندما نقوم بعملية تربيع رقم فنحن نحتاج الى كم معامل لذلك
مثلا لو سالتك ربع الرقم 5 ستقول مباشرة 25 لانك استخدمت الطريقة المعروفة بضرب العدد في نفسه اي
5 * 5 .. وبذلك كان الناتج 25 ..
نفس الطريقة سنتخدمها ولكن اين ..؟؟؟ ستكون داخل اقواس جسم دالة square وايضا ان عدد معاملات هذه الدالة هو معامل واحد لان الدالة لا تتطلب اكثر من رقم واحد هو الرقم الذي سنقوم بعملية الضرب عليه .
وهذا ما نلاحظه هنا ..
void square(int i) { int sq = i * i; cout << "Square of " << i << " is " << sq; }
قمنا بتعريف المعامل i على انه عدد صحيح ومن ثم تم تعريف المتغير sq الذي سيحتفظ بنتيجة الضرب .. ثم بعد ان تتم عملية الضرب هذه تذهب الدالة الى كائن الطباعة cout ليطبع لنا النتيجة ..
هنالك ملاحظة اود ذكرها وهي اننا ندرس لحد الان الدالة الجديدة ولم ندخل في main ونرى كيفية الاستدعاء وانا اقول هذا لكي لا تختلط الامور لديكم ..
اما البرنامج الثاني فانه يعمل على نفس الفكرة وهي ضرب المعامل i بنفسه لكي تظهر النتيجة ولكن الفرق هو اننا استخدمنا فيه الترجيع فقمنا بتعيين نوع الدالة على انها int
int square(int i) { int sq = i * i; return sq; }
وبعدها عرفنا المتغير sq الذي تخزن بداخله نتيجة الضرب ثم نقوم بترجيع sq .
سؤال لماذا قمنا بترجيع sq و لم نقم بترجيع بترجيع i ؟؟
الجواب .. هو لاننا نريد ترجيع قيمة ناتج الضرب... فلا يوجد اي خطأ اذا قمنا بترجيع i ولكن المشكلة هي اننا سنرجع قيمة i الى main ونقوم بطباعتها بعد ان ادخلناها .. فما الفائدة من البرنامج اذا .؟؟؟؟
المقصود من الترجيع هو ترجيع قيمة الناتج الى الدالة الرئيسية main للتصرف بها هناك حسب ما نحتاج من البرنامج ففي هذا البرنامج نحن نحتاج الى ان نطبع هذه القيمة فعندما نرجع قيمة ناتج الضرب في الدالة square اي تذهب قيمة ناتج الضرب الى الدالة الرئيسية فاننا سنقوم بطباعتها من هناك ...
ملاحظة اتمنى دراستها جيدا وهي ..
عندما نعرف دالة قبل دالة main فانه يمكننا ان نكتب فقط نوع المعامل من دون ان نضع له اسما ولكن بعد ان نكمل كتابة الmain نبدأ ببرمجة الدالة الجديدة فانه يستوجب علينا ان نكتب اسم ونوع المعامل .
ولكن اذا قمنا بتعريف الدالة وبرمجتها بعد الmain فانه يتم كتابة الدالة كاملة اي اسمها ونوع واسم معاملاتها ..
من هذين البرنامجين استطعنا ان نفهم شيئا مبسطا عن الترجيع وعدم الترجيع ...
شرح عن استدعاء الدوال ...
بعد انهاء برمجة دالة تؤدي غرض معين في برنامجنا يجب ان نقوم بتشغيل تلك الدالة .. وتشغيلها يكون في وقت نحن نحدده كأن يكون نتيجة لاختبار شئ في داخل البرنامج او نتيجة لطلب من المستخدم ..
والمقصود من تشغيل الدالة هنا هو استدعائها او مايسمى بـ calling .
سنشاهد هذا المثال البرمجي المهم وهو برمجة واستدعاء عدة دوال ..
#include<iostream.h> int sum(int , int); int sub(int,int; int mul(int,int); int dev(int,int); int main() { int a,b,result; char op; cout << "pleas enter the first number" << endl; cin >> a; cout << "pleas enter the second number" << endl; cin >> b; cout << "type + for summing and - for subtracting and * for multiplaying>> " << endl; cin >> op; switch(op) { case '+': result = sum(a,B); cout << result; break; case '-': result = sub(a,B); cout << result; break; case '*': result = mul(a,B); cout << result; break; } } int sum(int x,int y) { return x+y; } int sub(int x,int y) { return x-y; } int mul(int x,int y) { return x*y; }
طبعا هذا المثال هو لانشاء حاسبة تقوم بعملية الجمع والطرح والضرب بين عددين
قمنا ااولا بتعريف ثلاثة دوال الاولى sum الخاصة بجمع عددين والثانية sub والخاصة بطرح عددين والثالثة mul والتي تختص بضرب عددين .. لاحظوا ان الدوال عندما عرفناها في الجزء العلوي فوق الدالة الرئيسية كانت معاملاتها لا تحتوي اسماء وانما مجرد انواع وهذا مقبول في السي بلس بلس والسي القياسية ايضا .. اما برمجتها وضع اسماء للمعاملات فكانت بعد ان اكملنا برمجة الدالة الرئيسية mian .
معلومة برمجية .. انا قلت في الدرس السابق يمكن برمجة الدالة في مكانين اما قبل الدالة الرئيسية او بعدها ولكن هناك فرق في المكانين وهو .. لو قمنا ببرمجة الدالة كما في المثال السابق اي بعد الدالة الرئيسية يتوجب علينا ان نقوم بتعريفها قبل الدالة الرئيسية main وهذا هو الذي حصل في مثالنا السابق .. والسبب في ذلك هو ان مصرف لغة السي والسي بلس بلس يجب ان يعرف عندما يواجه دالة في الدالة الرئيسية يجب ان يكون قد واجه اسم تلك الدالة قبل ان يتفاجئ بها ..
والمكان الاخر لبرمجة الدالة يكون قبل الدالة الرئيسية وهنا لا نحتاج الى تعريفها بل مباشرة اعطاءها النوع والبارمترات ((ويجب هنا تسمية البارمترات )) ثم القيام ببناء جسم الدالة ..
تعودتوا في طريقة شرحي على اخذ امثلة واقعيه وها انا الان اعطيكم مثال واقعي وهو الاتي ..
لو ذهب اي شخص منا لزيارة شخص اخر اليس من المفروض ان ناخذ موعد سابق منه .. ؟؟؟
لو ذهبت الى احد اصدقائي يجب ان ابلغه مسبقا بانني قادم اليه لكي ينتظرني او يمكن انه يتعذر مني لاسباب شخصية لديه كانشغاله بعمل ما او لديه ضيوف اخرين .. وكذلك لكي لا يتفاجئ بي عند قدومي من دون موعد فقد يسبب الاحراج له ولي ..
هذا هو الحاصل في مصرف السي ... يجب ان تعطي اسم ونوع الدالة وعدد بارمتراتها قبل ان تقوم بالدخول الى الدالة main لكي لا يتفاجئ بها وتظهر لك رسالة خطأ عند القيام بعملية التصريف والخطأ هو استدعاء دالة غير معروفة ..
هذا ما قصدته من الموعد في المثال ..
الان دخلنا الى الدالة main .. اولا قمنا بتعريف متغيرين a و b وهما الخاصان بالاعداد المدخلة والمتغير result وهو الذي ستخزن به قيمة ناتج كل دالة ..
بالنسبة للمتغير الرمزي op هذا المتغير خاص بالعملية الحسابية التي نريد القيام بها على العددين .. وهذه معلومة يجب ان تعرفونها جيمعا ومن لم يعرفها سابقا سيعرفها الان وهي ان رموز العمليات الحسابية كالـ+ و - تعتبر من نوع char لانها ستوضع بين علامتا اقتباس مفردة ' + ' وكما هو معروف اي رمز يوضع بين علامتا اقتباس مفردة او مزدوجة يتحول الى القيمة المقابلة له في جدول الاسكي كود ...
بعدها قمنا باختبار المتغير op الذي سيدخله المستخدم .. باستخدام عبارة الاختبار switch
اولا .. اذا كان المدخل + سنتدعي الدالة sum التي هي خاصة بجمع عددين .. ثم نفوم بترجيع ناتج الجمع باستخدام عبارة return وكما قلت سابقا الفائدة من الreturn هو ان ناتج الجمع هذا سيذهب الى الدالة الرئيسية main وفي مثالنا هذا سيخزن في المتغير result ,, بعدها نقوم بطباعة المتغير result الذي هو خاص بناتج الجمع ..
ثانيا وثالثا نفس اولا باستثناء المدخل فاذا كان * او - سنستدعي الدالة الخاصة بالعملية المطلوبة اي نفس ما جرى للجمع ..
ملاحظة مهمه جدا جدا ..
الان نحن اكملنا العمل في الدالة الرئيسية واقفلنا القوس .. ووصلنا الى برمجة الدوال .. واضن انني شرحت برمجة الدوال في الدرس السابق ولا توجد لديكم مشكلة فيها فكل شخص يستطيع برمجة برنامج يجمع او يطرح او يضرب عددين فقط يضعها في دالة يكمل لديه برمجة تلك الدالة .. اما الملاحظة فهي التالية ,,,,
عندما قمنا باستدعاء دالة من تلك الدوال ولتكن دالة الجمع مثلا ..
result = sum(a,B);
قمنا بوضع المتغيرين a و b داخل اقواس البارمترات .. ركزوا معي قليلا رجاءا لانها ملاحظة هامه وهي اساس استدعاء الدوال ..
هنا سيقوم مصرف لغة سي بنقل نسخة من الارقام التي ادخلها المستخدم عندما طلبنا منه ادخال الرقم الاول والثاني الى المتغيرين الموضوعين في برمجة تلك الدالة وهما x و y ..
مقلا لو ادخل المستخدم الرقم 5 على انه الرقم الاول فسيخزن في المتغير a وعند استدعاء الدالة سيتم نسخ نسخه من هذا الرقم وارساله الى جزء برمجة الدالة بعد الدالة الرئيسيه لكي تتم معالجته في الدالة وسيخزن في المتغير x والسبب انه خزن في x وليس في y لان الx اول بارمتر في الدالة وكذللك a هو اول بارمتر فسيتم الارسال من اول بارمتر في الدالة المستدعاة الى اول بارمتر في جزء برمجة تلك الدالة ..
كذلك لو فرضنا ان المستخدم قام بادخال العدد 7 على انه العدد الثاني فسيخزن في المتغير b وستم نسخه وارساله الى المتغير y في جزء برمجة تلك الدالة وهذا ايضا يخضع لنفس القاعده التي قلتها قبل قليل وهي ان البارمتر الثاني في الدالة المستدعاة داخل الدالة الرئيسية main ينسخ ويرسل الى جزء برمجة الدوال ويوضع في المتغير y لكي تتم معالجته داخل الدالة