منتديات الهندسة الكهربية والإلكترونية والميكاترونكس والكومبيوتر
هل تريد التفاعل مع هذه المساهمة؟ كل ما عليك هو إنشاء حساب جديد ببضع خطوات أو تسجيل الدخول للمتابعة.

الدرس الخامس الدوال Functions

اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الأربعاء يوليو 03, 2019 6:21 pm

الدرس الخامس
الدوال Functions


تقوم الدالة بتجميع عدد من عبارات البرنامج في وحدة وتمنحها (تعطيها) اسمًا. يمكن بعد ذلك استدعاء هذه الوحدة من أجزاء أخرى من البرنامج.
السبب الأكثر أهمية لاستخدام الدوال هو المساعدة في تنظيم البرنامج.
تقسيم البرنامج إلى دوال هو أحد المبادئ الرئيسية للبرمجة المنظمة (المهيكلة). (ومع ذلك ، توفر البرمجة الموجهة للكائنات طرقًا أكثر قوة لتنظيم البرامج.)

سبب آخر لاستخدام الدوال (والسبب في اختراعها ، منذ فترة طويلة) هو تقليل حجم البرنامج. أي تسلسل من التعليمات التي تظهر في برنامج أكثر من مرة هو مرشح ليصبح في دالة . يتم تخزين كود الدالة في مكان واحد فقط في الذاكرة ، على الرغم من أن الدالة يتم تنفيذها عدة مرات في سياق البرنامج. يوضح الشكل 5-1 كيف يتم استدعاء دالة من أقسام مختلفة من البرنامج.

الدرس الخامس الدوال Functions 7A529F62F8054F4DA6D2780C30747685

الشكل 5-1 تدفق التحكم للدالة Flow of control to a function


الدوال في C++ (و C) تشبه الإجراءات الفرعية subroutines والإجراءات procedures بلغات أخرى مختلفة.

الدوال البسيطة Simple Functions
يوضح مثالنا الأول دالة بسيطة تهدف إلى طباعة سطر من 45 من النجوم asterisks .
ينشئ برنامج المثال جدولًا ، ويتم استخدام أسطرالنجوم لجعل الجدول أكثر قابلية للقراءة. إليك القائمة TABLE:


الكود:
// table.cpp
// demonstrates simple function
#include <iostream>
using namespace std;

void starline();       //function declaration
// (prototype)

int main()
{
starline();             //call to function
cout << “Data type Range” << endl;
starline();             //call to function
cout << “char -128 to 127” << endl
        << “short -32,768 to 32,767” << endl
                    << “int System dependent” << endl
                    << “long -2,147,483,648 to 2,147,483,647” << endl;
          starline();                     //call to function
return 0;
}
//--------------------------------------------------------------
// starline()
// function definition
void starline() //function declarator
{
for(int j=0; j<45; j++) //function body
cout << ‘*’;
cout << endl;
}

الإخراج من البرنامج يشبه ما يلى :

الكود:
*********************************************
Data type Range
*********************************************
char -128 to 127
short -32,768 to 32,767
int System dependent
long -2,147,483,648 to 2,147,483,647
*********************************************


يتكون البرنامج من دالتين : main() و starline() . لقد شاهدت بالفعل العديد من البرامج التي تستخدم main() بمفردها . ما المكونات الأخرى الضرورية لإضافة دالة إلى البرنامج؟ هناك ثلاثة مكونات :
• إعلان declaration الدالة ،
• إستدعاءات calls الدالة
• وتعريف definition الدالة .

1- إعلان الدالة The Function Declaration
تمامًا كما لا يمكنك استخدام متغير دون إخبار المترجم أولاً بما هو عليه ، لا يمكنك أيضًا استخدام دالة دون إخبار المترجم عنها. هناك طريقتان للقيام بذلك . الطريقة (النهج) التي نظهرها هنا هي إعلان declare الدالة قبل أن يتم استدعاؤها. (الطريقة الأخرى هي تعريفها define قبل أن يتم استدعاؤها ، وسوف ندرس ذلك بعد ذلك.) في البرنامج TABLE ، يتم الإعلان عن الدالة starline() في السطر :

الكود:
 void starline();


يخبر الإعلان declaration المترجم أننا في مرحلة لاحقة نخطط لتقديم دالة تسمى starline. تحدد الكلمة المفتاحية void أن الدالة لا تحتوي على (ليس لها) قيمة إرجاع ، وأن الأقواس الفارغة () تشير إلى أنها لا تأخذ أي وسائط arguments. (يمكنك أيضًا استخدام الكلمة المفتاحية void بين القوسين للإشارة إلى أن الدالة لا تحتوي على أي وسائط ، كما هو الحال غالبًا في لغة C ، ولكن تركها فارغة هو الممارسة الأكثر شيوعًا في C++ ) سيكون لدينا المزيد لنقوله حول الوسائط وإرجاع القيم قريبا.
لاحظ أنه يتم إنهاء إعلان declaration الدالة بفاصلة منقوطة . إنه عبارة كاملة في حد ذاتها.

تسمى أيضًا إعلانات الدالة النماذج الأولية prototypes ، لأنها توفر نموذجًا model أو مخططًا blueprint لهذه الدالة . وهى تخبر المترجم ، "الدالة التي تبدو مثل هذه تظهر في وقت لاحق في البرنامج ، لذلك كل شيء على ما يرام إذا رأيت إشارات (مرجعيات) إليها قبل أن ترى الدالة نفسها." المعلومات الواردة في الإعلان (نوع الإرجاع وعدد وأنواع الوسائط) يشار إليها أيضًا في بعض الأحيان باسم توقيع signature الدالة .

2- استدعاء الدالة Calling the Function
يتم إستدعاء called الدالة (أو يتم استحضارها invoked أو تنفيذها executed ) ثلاث مرات من الدالة main() . يبدو كل استدعاء من الإستدعاءات الثلاثة كما يلي:

الكود:
starline();


هذا هو كل ما نحتاجه لاستدعاء الدالة : اسم الدالة ، متبوعة بالأقواس . يشبه بناء جملة الاستدعاء تلك الموجودة في الإعلان ، باستثناء أنه لا يتم استخدام نوع الإرجاع.
يتم إنهاء الإستدعاء بفاصلة منقوطة. يؤدي تنفيذ عبارة الاستدعاء إلى تنفيذ الدالة ؛ بمعنى ، يتم نقل التحكم إلى الدالة ، ويتم تنفيذ العبارات في تعريف الدالة (التي سنقوم بفحصها بعد لحظة) ، ثم يتم عودة التحكم إلى العبارة التالية لاستدعاء الدالة .

3- تعريف الدالة The Function Definition
أخيرًا ، نأتي إلى الدالة نفسها ، والتي يشار إليها باسم تعريف definition الدالة . يحتوي التعريف على الكود الفعلي للدالة . إليك تعريف الدالة starline() :


الكود:
void starline()          //declarator
{
for(int j=0; j<45; j++) /   /function body
cout << ‘*’;
cout << endl;
}


يتكون التعريف من سطر المُعلن declarator ، متبوعًا بجسم body الدالة . يتكون جسم الدالة من العبارات التي تشكل الدالة ، وتحددها الأقواس {} .
يجب أن يتوافق المُعلن مع الإعلان declaration : يجب أن يستخدم نفس اسم الدالة ، وأن يكون له نفس أنواع الوسائط بنفس الترتيب (إذا كانت هناك وسائط ) ، وأن يكون له نفس نوع الإرجاع.
لاحظ أن المُعلن لا يُنهي بفاصلة منقوطة. يوضح الشكل 5-2 صيغة جملة syntax إعلان declaration الدالة واستدعاء call الدالة وتعريف definition الدالة .

الدرس الخامس الدوال Functions 0A5B85010F9A4D3A968DE646CD0CC24F

الشكل 5-2 بناء جملة الدالة Function syntax

عند استدعاء الدالة ، يتم نقل التحكم إلى العبارة الأولى في جسم الدالة .
ثم يتم تنفيذ العبارات الأخرى في جسم الدالة ، وعندما تتم مواجهة قوس الإغلاق ، يعود التحكم إلى برنامج الاستدعاء.

يلخص الجدول المكونات المختلفة للدالة :

الدرس الخامس الدوال Functions 5F70426AF5DC4318983D4FEA316ECB21

مقارنة مع دوال المكتبة Comparison with Library Functions
لقد رأينا بالفعل بعض دوال المكتبة قيد الاستخدام. لدينا إستدعاءات مدمجة إلى دوال المكتبة ، مثل

الكود:
ch = getche();

في كود برنامجنا. أين الإعلان وتعريف دالة المكتبة هذه ؟ الإعلان declaration موجود في ملف الرأس المحدد في بداية البرنامج (CONIO.H ، من أجل getche() ). التعريف definition (يترجم إلى كود قابل للتنفيذ) موجود في ملف مكتبة والذى يرتبط linked تلقائيًا بالبرنامج عند إنشائه (بنائه) build .

عندما نستخدم دالة مكتبة ، لا نحتاج إلى كتابة الإعلان أو التعريف. ولكن عندما نكتب دوالنا الخاصة ، يعد الإعلان والتعريف جزءًا من الملف المصدر ، كما هو موضح في مثال TABLE.

إزالة الإعلان Eliminating the Declaration
تتمثل الطريقة (النهج) الثانية لإدراج دالة في برنامج ما في إزالة إعلان الدالة ووضع تعريف الدالة (الدالة نفسها) في القائمة قبل الاستدعاء الأول للدالة . على سبيل المثال ، يمكننا إعادة كتابة TABLE لإنتاج TABLE2 ، حيث يظهر تعريف starline() أولاً.



الكود:
// table2.cpp
// demonstrates function definition preceding function calls
#include <iostream>
using namespace std; //no function declaration
//--------------------------------------------------------------
// starline() //function definition
void starline()
{
for(int j=0; j<45; j++)
cout << ‘*’;
cout << endl;
}
//--------------------------------------------------------------
int main() //main() follows function
{
starline(); //call to function
cout << “Data type Range” << endl;
starline(); //call to function
cout << “char -128 to 127” << endl
      << “short -32,768 to 32,767” << endl
      << “int System dependent” << endl
      << “long -2,147,483,648 to 2,147,483,647” << endl;
starline(); //call to function
return 0;
}

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


Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الخميس يوليو 04, 2019 1:17 pm

تمرير الوسائط إلى الدوال Passing Arguments to Functions
الوسيط argument هو جزء من البيانات (قيمة int ، على سبيل المثال) يتم تمريرها passed من برنامج إلى الدالة .
تتيح الوسائط للدالة أن تعمل بقيم مختلفة ، أو حتى القيام بأشياء مختلفة ، حسب متطلبات البرنامج الذي يستدعيها .

تمرير الثوابت Passing Constants
على سبيل المثال ، لنفترض أننا قررنا أن الدالة starline() في المثال الأخير جامدة للغاية. بدلاً من دالة تطبع دومًا 45 نجمة ، نريد دالة تطبع أي حرف بأي عدد من المرات.
إليك البرنامج ، TABLEARG ، يتضمن مثل هذه الدالة فقط. نستخدم الوسائط لتمرير الحرف المراد طباعته وعدد مرات طباعته.


الكود:
// tablearg.cpp
// demonstrates function arguments
#include <iostream>
using namespace std;

void repchar(char, int);    //function declaration

int main()
{
repchar(‘-’, 43);             //call to function
cout << “Data type Range” << endl;
repchar(‘=’, 23);             //call to function
cout << “char -128 to 127” << endl
        << “short -32,768 to 32,767” << endl
        << “int System dependent” << endl
        << “double -2,147,483,648 to 2,147,483,647” << endl;
repchar(‘-’, 43);             //call to function
return 0;
}
//--------------------------------------------------------------
// repchar()
// function definition
void repchar(char ch, int n)       //function declarator
{
for(int j=0; j<n; j++)       //function body
cout << ch;
cout << endl;
}


• الدالة الجديدة تسمى repchar() . إعلانها يكون بالشكل التالى :

الكود:
void repchar(char, int);    // declaration specifies data types

العناصر الموجودة في الأقواس هي أنواع بيانات الوسائط التي سيتم إرسالها إلى repchar() : char و int.
• في استدعاء الدالة ، يتم إدراج قيم محددة - ثوابت في هذه الحالة - في المكان المناسب بين الأقواس:

الكود:
 repchar(‘-’, 43);    // function call specifies actual values

تعطى هذه العبارة تعليمات للدالة repchar() لطباعة سطر من 43 شرطة (-) . يجب أن تكون القيم الواردة في الاستدعاء من الأنواع المحددة في الإعلان declaration : الوسيط الأول ، الحرف - ، يجب أن تكون من النوع char ؛ والوسيط الثانى ، العدد 43 ، يجب أن يكون من النوع int. يجب أن تتفق الأنواع فى الإعلان والتعريف أيضًا.
الاستدعاء التالى للدالة repchar() :

الكود:
repchar(‘=’, 23); 


يخبرها بطباعة سطر من 23 علامات التساوى . الاستدعاء الثالث يطبع 43 شرطة مرة أخرى. وإليك الإخراج من TABLEARG:


الكود:
 -------------------------------------------
Data type Range
=======================
char    -128 to 127
short    -32,768 to 32,767
int    System dependent
long    -2,147,483,648 to 2,147,483,647
-------------------------------------------

• يوفر برنامج الاستدعاء الوسائط ، مثل ‘- ' و 43 ، للدالة. تسمى المتغيرات المستخدمة داخل الدالة للاحتفاظ بقيم الوسيط "البارامترات" parameters ؛ في الدالة repchar() البارامترات هى ch و n. (يجب أن نلاحظ أن العديد من المبرمجين يستخدمون مصطلحي الوسيط argument والبارامتر parameter كبدائل إلى حد ما.)
• يحدد المُعلن في تعريف الدالة كل من أنواع البيانات وأسماء البارامترات :

الكود:
 void repchar(char ch, int n)    //declarator specifies parameter
//names and data types

يتم استخدام أسماء البارامترات ch و n ، في الدالة كما لو كانت متغيرات عادية.
وضعهم في المُعلن يعادل تعريفهم بعبارات مثل

الكود:
char ch;
int n;


عند استدعاء الدالة ، تتم تهيئة بارامتراتها تلقائيًا للقيم التي تم تمريرها بواسطة برنامج الاستدعاء .

تمرير المتغيرات Passing Variables
في مثال TABLEARG ، كانت الوسائط ثوابت : ‘- '، 43 ، وهكذا. دعنا ننظر إلى مثال حيث يتم تمرير المتغيرات ، بدلاً من الثوابت ، كوسائط . يشتمل هذا البرنامج ، VARARG ، على نفس الدالة repchar() مثلما فى TABLEARG ، لكنه يتيح للمستخدم تحديد الحرف وعدد المرات التي يجب أن يتكرر فيها.



الكود:
// vararg.cpp
// demonstrates variable arguments
#include <iostream>
using namespace std;

void repchar(char, int);    //function declaration

int main()
{
char chin;
int nin;

cout << “Enter a character: “;
cin >> chin;
cout << “Enter number of times to repeat it: “;
cin >> nin;

repchar(chin, nin);
return 0;
}
//--------------------------------------------------------------
// repchar()
// function definition
void repchar(char ch, int n) //function declarator
{
for(int j=0; j<n; j++) //function body
cout << ch;
cout << endl;
}

إليك بعض نماذج التفاعل مع VARARG:


الكود:
Enter a character: +
Enter number of times to repeat it: 20
++++++++++++++++++++


هنا ، عند استدعاء الدالة ، يتم استخدام chin و nin في الدالة main() كوسائط للدالة repchar() :

الكود:
repchar(chin, nin); // function call


يجب أن تتطابق أنواع بيانات المتغيرات المستخدمة كوسائط مع تلك المحددة في إعلان وتعريف الدالة ، تمامًا كما يجب بالنسبة للثوابت. وهذا يعنى ، يجب أن تكون chin من نوع char ، ويجب أن تكون nin من نوع int.


التمرير عن طريق (حسب) القيمة Passing by Value
في البرنامج VARARG ، سيتم تمرير القيم الخاصة التي يمتلكها chin و nin ،عند تنفيذ استدعاء الدالة ، إلى الدالة . كما فعلت عندما تم تمرير الثوابت إليها ، تقوم الدالة بإنشاء متغيرات جديدة للاحتفاظ بقيم هذه الوسائط المتغيرة. تعطي الدالة هذه المتغيرات الجديدة أسماء وأنواع بيانات البارامترات المحددة في المُعلن : ch من نوع char و n من نوع int . فهى تهىء هذه البارامترات إلى القيم values التي تم تمريرها. ثم يتم الوصول إليها مثل المتغيرات الأخرى بواسطة عبارات في جسم الدالة.

تمرير الوسائط بهذه الطريقة ، حيث تقوم الدالة بإنشاء نسخ من الوسائط التي تم تمريرها إليها ، تسمى "التمرير عن طريق (حسب) القيمة" passing by value . سنستكشف طريقة (نهج) أخرى ، "التمرير عن طريق (حسب) المرجع " passing by reference ، لاحقًا في هذا الدرس . يوضح الشكل 5-3 كيفية إنشاء متغيرات جديدة في الدالة عندما يتم تمرير الوسائط حسب القيمة.

الدرس الخامس الدوال Functions C54B2B91E6E6492E853B69A0123C4FB1

الشكل 5-3 التمرير حسب القيمة Passing by value

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الجمعة يوليو 05, 2019 9:43 pm

إرجاع (إعادة) القيم من الدوال Returning Values from Functions
عندما تكمل دالة تنفيذها ، يمكنها إرجاع قيمة واحدة إلى برنامج الاستدعاء.
تتكون قيمة الإرجاع عادةً من إجابة للمشكلة التي حلتها الدالة . يوضح المثال التالي دالة تقوم بإرجاع وزن بالكيلوغرام بعد إعطاء وزن بالباوند (رطل) pounds . إليك القائمة CONVERT:


الكود:
// convert.cpp
// demonstrates return values, converts pounds to kg
#include <iostream>
using namespace std;

float lbstokg(float); //declaration

int main()
{
float lbs, kgs;
cout << “\nEnter your weight in pounds: “;
cin >> lbs;
kgs = lbstokg(lbs);
cout << “Your weight in kilograms is “ << kgs << endl;
return 0;
}
//--------------------------------------------------------------
// lbstokg()
// converts pounds to kilograms
float lbstokg(float pounds)
{
float kilograms = 0.453592 * pounds;
return kilograms;
}



إليك بعض نماذج التفاعل مع هذا البرنامج:

الكود:
Enter your weight in pounds: 182
Your weight in kilograms is 82.553741


عندما تقوم دالة بإرجاع قيمة ، يجب تحديد نوع بيانات هذه القيمة. يقوم إعلان الدالة بذلك عن طريق وضع نوع البيانات ، float في هذه الحالة ، قبل اسم الدالة في الإعلان والتعريف. لم تُرجع الدالات في أمثلة البرامج السابقة أي قيمة ، لذلك كان نوع الإرجاع void (فارغ) . في برنامج CONVERT ، ترجع الدالة lbstokg() (رطل إلى كيلوغرام ، حيث lbs تعني رطل) نوع float ، لذلك يكون الإعلان

الكود:
float lbstokg(float);

أول float تحدد نوع الإرجاع. وتحدد float بين القوسين أن وسيط يتم تمريره إلى lbstokg() هو أيضًا من النوع float.
عندما تقوم دالة بإرجاع قيمة ، فإن استدعاء الدالة lbstokg(lbs) يعتبر تعبيرًا يأخذ القيمة التي تم إرجاعها بواسطة الدالة. يمكننا التعامل مع هذا التعبير مثل أي متغير آخر ؛ في هذه الحالة ، نستخدمه في عبارة التخصيص :

الكود:
kgs = lbstokg(lbs);

وهذه تتسبب في أن يتم تعيين (تخصيص) المتغير kgs بالقيمة التي يتم إرجاعها بواسطة الدالة lbstokg() .


عبارة الإرجاع The return Statement
تمرر الدالة lbstokg() وسيط يمثل الوزن بالباوندات pounds ، والذى يتم تخزينه في البارامتر pounds . وتحسب الوزن المقابل بالكيلوغرام بضرب قيمة الباوند هذه في ثابت ؛ يتم تخزين النتيجة فى المتغير kilograms . ثم يتم إرجاع قيمة هذا المتغير إلى برنامج الاستدعاء باستخدام عبارة الإرجاع:

الكود:
 return kilograms;


لاحظ أن كل من main() و lbstokg() لديهما مكان لتخزين تغير الكيلوجرام : kg في main() ، و kilograms في lbstokg() . عند إرجاع الدالة ، يتم نسخ القيمة kilograms إلى kgs . لا يقوم برنامج الاستدعاء بالوصول إلى المتغير kilograms في الدالة ؛ يتم إرجاع القيمة فقط . تظهر هذه العملية في الشكل 5-6 .

الدرس الخامس الدوال Functions 2BEF1443D3594447B70E0860C2C26D88

الشكل 5-4 إرجاع (إعادة) قيمة Returning a value


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

يجب عليك دائمًا تضمين نوع إرجاع دالة في إعلان الدالة . إذا لم تُرجع الدالة أي شيء ، فاستخدم الكلمة المفتاحية void للإشارة إلى هذه الحقيقة. إذا لم تستخدم نوع الإرجاع في الإعلان ، فسوف يفترض المترجم أن الدالة تُرجع قيمة int. على سبيل المثال ، الإعلان

الكود:
somefunc(); // declaration -- assumes return type is int



يخبر المترجم أن somefunc() لديها نوع إرجاع int.

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


إزالة (التخلص من ) المتغيرات غير الضرورية Eliminating Unnecessary Variables
يحتوي برنامج CONVERT على العديد من المتغيرات التي يتم استخدامها بغرض الوضوح ولكنها ليست ضرورية حقًا. البرنامج CONVERT2 يُظهر شكل مختلف من هذا البرنامج ، ، فهو يوضح كيف يمكن غالبًا استخدام التعبيرات التي تحتوي على دوال بدلاً من المتغيرات.


الكود:
 // convert2.cpp
// eliminates unnecessary variables
#include <iostream>
using namespace std;

float lbstokg(float);       //declaration

int main()
{
float lbs;

cout << “\nEnter your weight in pounds: “;
cin >> lbs;
cout << “Your weight in kilograms is “ << lbstokg(lbs)
        << endl;
return 0;
}
//--------------------------------------------------------------
// lbstokg()
// converts pounds to kilograms
float lbstokg(float pounds)
{
return 0.453592 * pounds;
}


في main() تم التخلص من المتغير kgs من برنامج CONVERT. بدلاً من ذلك ، يتم إدخال الدالة lbstokg(lbs) مباشرةً في عبارة cout:

الكود:
cout << “Your weight in kilograms is “ << lbstokg(lbs) << endl;


أيضًا في الدالة lbstokg() ، لم يعد يتم استخدام المتغير kilograms . يتم إدراج التعبير 0.453592*pounds مباشرة في عبارة الإرجاع:

الكود:
 return 0.453592 * pounds;

يتم تنفيذ الحساب ويتم إرجاع القيمة الناتجة إلى برنامج الاستدعاء ، تمامًا كما ستكون قيمة المتغير.


للتوضيح ، غالبًا ما يضع المبرمجون أقواس حول التعبير المستخدم في عبارة الإرجاع:

الكود:
return (0.453592 * pounds);


حتى إذا لم يكن مطلوبًا بواسطة المترجم ، فإن الأقواس الزائدة في التعبير لا تسبب أي ضرر ، وقد تساعد في جعل القائمة أسهل لنا نحن البشر في القراءة.
ربما يفضل مبرمجو C++ (و C) ذوو الخبرة النموذج المختصر CONVERT2 على CONVERT المطوَّل أكثر. ومع ذلك ، CONVERT2 ليس من السهل فهمه ، خاصة بالنسبة لغير الخبراء. مسألة الإيجاز مقابل الوضوح هي مسألة أسلوب ، اعتمادًا على تفضيلاتك الشخصية وتوقعات أولئك الذين سيقرأون الكود.

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الأحد يوليو 07, 2019 12:23 pm

الدوال المحملة حملا زائدا (مثقلة) Overloaded Functions
الدالة المحملة حملا زائد تؤدي أنشطة مختلفة بناءً على نوع البيانات المرسلة إليها. كيف تعرف الدالة المحملة حملا زائد ما يجب القيام به. تنفذ عملية معينة على نوع معين من البيانات ولكن تنفذ عملية أخرى على نوع مختلف. دعونا نوضح لأمور مع بعض الأمثلة.

أعداد مختلفة من الوسائط Different Numbers of Arguments
استرجع الدالة starline() في مثال TABLE والدالة repchar() من مثال TABLEARG ، وكلاهما موضح سابقًا في هذا الدرس . طبعت الدالة starline() سطرا باستخدام 45 نجمة ، بينما استخدمت repchar() حرفًا وطول السطر اللذين تم تحديدهما عند استدعاء الدالة . قد نتخيل وظيفة ثالثة ، هي charline() ، التي تطبع دائمًا 45 حرفًا ولكن تسمح لبرنامج الاستدعاء بتحديد الحرف المراد طباعته.
هذه الدوال الثلاثة starline(), repchar(),charline() تؤدي أنشطة مماثلة ولكن لها أسماء مختلفة. بالنسبة للمبرمجين الذين يستخدمون هذه الدوال ، يعني ذلك ثلاثة أسماء يجب أن تتذكرها وثلاثة أماكن للبحث عنها إذا كانت مدرجة أبجديًا في وثائق مرجع الدالة للتطبيق.

سيكون أكثر ملاءمة استخدام نفس الاسم لجميع الدوال الثلاثة ، على الرغم من أن لكل منها وسائط مختلفة. إليك برنامج ، OVERLOAD ، يجعل هذا ممكنًا:




الكود:
// overload.cpp
// demonstrates function overloading
#include <iostream>
using namespace std;

void repchar();       //declarations
void repchar(char);
void repchar(char, int);

int main()
{
repchar();
repchar(‘=’);
repchar(‘+’, 30);
return 0;
}   
//--------------------------------------------------------------
// repchar()
// displays 45 asterisks
void repchar()
{
for(int j=0; j<45; j++) // always loops 45 times
cout << ‘*’; // always prints asterisk
cout << endl;
}
//--------------------------------------------------------------
// repchar()
// displays 45 copies of specified character
void repchar(char ch)
{
for(int j=0; j<45; j++) // always loops 45 times
cout << ch; // prints specified character
cout << endl;
}
//--------------------------------------------------------------
// repchar()
// displays specified number of copies of specified character
void repchar(char ch, int n)
{
for(int j=0; j<n; j++) // loops n times
cout << ch; // prints specified character
cout << endl;
}

يطبع هذا البرنامج ثلاثة أسطر من الأحرف. إليك الإخراج:


الكود:
*********************************************
=============================================
++++++++++++++++++++++++++++++

يبلغ طول السطرين الأولين 45 حرفًا والثالث 30 حرفًا.

يحتوي البرنامج على ثلاث دوال بنفس الاسم. هناك ثلاثة إعلانات declarations ، وثلاثة استدعاءات calls للدوال ، وثلاثة تعريفات definitions للدوال الثلاثة. ما الذي يمنع المترجم من أن يصبح في حيرة من أمره؟ إنه يستخدم توقيع signature الدالة - عدد الوسائط arguments ، وأنواع البيانات الخاصة بها - لتمييز دالة عن الأخرى. وبعبارة أخرى ، الإعلان

الكود:
 void repchar();

والذى لا يأخذ أي وسائط ، يصف دالة مختلفة تمامًا عن الإعلان

الكود:
void repchar(char);

الذي يأخذ وسيط واحد من نوع char ، أو الإعلان

الكود:
void repchar(char, int);

الذي يأخذ وسيط من نوع char وآخر من نوع int .

يمكن للمترجم ، الذي يرى عدة دوال بنفس الاسم ولكن بأعداد مختلفة من الوسائط ، أن يقرر أن المبرمج قد ارتكب خطأ (وهو ما سيفعله في لغة C).
بدلاً من ذلك ، وبكل تسامح (تساهل) ، يقوم بإعداد دالة منفصلة بكل تعريف. تعتمد أى من هذه الدوال التي سيتم استدعاؤها على عدد الوسائط المتوفرة في الاستدعاء . الشكل 5-8 يوضح هذه العملية.

الدرس الخامس الدوال Functions 17A7018BBD894DE68520F65023AE9E95

الشكل 5-8 الدوال المحملة تحميلا زائدا Overloaded functions


أنواع مختلفة من الوسائط Different Kinds of Arguments
في المثال OVERLOAD ، أنشأنا العديد من الدوال بنفس الاسم ولكن بأعداد مختلفة من الوسائط . يستطيع المترجم أيضًا التمييز بين الدالات التي تم تحميلها بشكل زائد بنفس عدد الوسائط ، بشرط أن يكون نوعها مختلفًا. إليك برنامج ، OVERENGL ، يستخدم دالة محمّلة بشكل زائد لعرض كمية بتنسيق أقدام وبوصات . يمكن أن يكون الوسيط الفردى للدالة إما هيكل من نوع المسافة Distance (كما هو مستخدم في مثال ENGLDISP) أو متغير بسيط من النوع float. يتم استخدام دوال مختلفة حسب نوع الوسيط.


الكود:
// overengl.cpp
// demonstrates overloaded functions
#include <iostream>
using namespace std;
////////////////////////////////////////////////////////////////
struct Distance //English distance
{
int feet;
float inches;
};
////////////////////////////////////////////////////////////////
void engldisp( Distance ); //declarations
void engldisp( float );

int main()
{
Distance d1; //distance of type Distance
float d2; //distance of type float

//get length d1 from user
cout << “\nEnter feet: “; cin >> d1.feet;
cout << “Enter inches: “; cin >> d1.inches;

//get length d2 from user
cout << “Enter entire distance in inches: “; cin >> d2;
cout << “\nd1 = “;

engldisp(d1); //display length 1
cout << “\nd2 = “;

engldisp(d2); //display length 2
cout << endl;
return 0;
}
//--------------------------------------------------------------
// engldisp()
// display structure of type Distance in feet and inches
void engldisp( Distance dd ) //parameter dd of type Distance
{
cout << dd.feet << “\’-” << dd.inches << “\””;
}
//--------------------------------------------------------------
// engldisp()
// display variable of type float in feet and inches
void engldisp( float dd ) //parameter dd of type float
{
int feet = static_cast<int>(dd / 12);
float inches = dd - feet*12;
cout << feet << “\’-” << inches << “\””;
}
 

يُدعى المستخدم إلى إدخال مسافتين ، الأولى تتضمن مدخلات منفصلة للأقدام والبوصات ، والثانية بعدد كبير واحد للبوصات (109.5 بوصة ، على سبيل المثال ، بدلاً من 9'–1.5'' ). يستدعي البرنامج الدالة المحملة حملا زائدا engldisp() لعرض قيمة نوع Distance للمسافة الأولى ونوع float للمسافة الثانية. إليك بعض نماذج التفاعل مع البرنامج:

الكود:
 Enter feet: 5
Enter inches: 10.5
Enter entire distance in inches: 76.5
d1 = 5’-10.5”
d2 = 6’-4.5”

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

يمكن للدوال زائدة التحميل أن تبسط حياة المبرمج عن طريق تقليل عدد أسماء الدوال التي يجب تذكرها. كمثال على التعقيد الذي ينشأ عند عدم استخدام التحميل الزائد ، اعتبر إجراءات مكتبة C++ لإيجاد القيمة المطلقة للعدد . لأن هذه الإجراءات يجب أن تعمل مع C (التي لا تسمح بالحمل الزائد) وكذلك مع C++ ، يجب أن يكون هناك إصدارات منفصلة من روتين القيمة المطلقة لكل نوع بيانات. هناك أربعة منهم : abs() للنوع int ، و cabs() للأعداد المركبة ، و fabs() للنوع double ، و labs() للنوع long . في C++ ، يكفي اسم واحد ، abs() ، لجميع أنواع البيانات هذه.

كما سنرى لاحقًا ، فإن الدوال المحملة حملا زائد مفيدة أيضًا لمعالجة أنواع مختلفة من الكائنات.

//////////////////////////////////////////////
مراجعة التحميل الزائد للدالة :
دالتان أو أكثر لها نفس الاسم ولكن بوسائط مختلفة تعرف بالدوال زائدة التحميل .

الدرس الخامس الدوال Functions 5A10A7A9688948E5932A8F04E98C1757


في البرمجة بلغة C++ ، يمكن أن يكون لدالتين نفس الاسم إذا كان عدد و / أو نوع الوسائط التي يتم تمريرها مختلفًا.
تُعرف هذه الدوال التي لها عدد أو نوع (أو كليهما) مختلف من الوسائط بالدوال زائدة التحميل . فمثلا:


الكود:
int test() { }
int test(int a) { }
float test(double a) { }
int test(int a, double b) { }


هنا ، 4 دوال محملة بشكل زائد لأن الوسائط التى يتم تمريرها إلى هذه الدوال مختلفة.
لاحظ أن نوع الإرجاع لكل هذه الدوال ليس هو نفسه. قد يكون أو لا يكون للدوال الزائدة التحميل نوع إرجاع مختلف ، لكن يجب أن يحتوي على وسائط مختلفة.


الكود:
// Error code
int test(int a) { }
double test(int b){ }

عدد ونوع الوسائط التي تم تمريرها إلى هاتين الدالتين متماثلان على الرغم من اختلاف نوع الإرجاع. وبالتالي ، فإن المترجم يظهر خطأ.

مثال رقم 1 :

الكود:
   #include <iostream>
   using namespace std;
   
   void display(int);
   void display(float);
   void display(int, float);
   
   int main() {
   
       int a = 5;
       float b = 5.5;
   
       display(a);
       display(b);
       display(a, b);
   
       return 0;
   }
   
   void display(int var) {
       cout << "Integer number: " << var << endl;
   }
   
   void display(float var) {
       cout << "Float number: " << var << endl;
   }
   
   void display(int var1, float var2) {
       cout << "Integer number: " << var1;
       cout << " and float number:" << var2;
   }

الخرج :

الكود:
Integer number: 5
Float number: 5.5
Integer number: 5 and float number: 5.5

هنا ، تستدعى الدالة display() ثلاث مرات بنوع أو عدد مختلف من الوسائط.
نوع الإرجاع لكل هذه الوظائف متماثل ولكنه ليس ضروريًا.

مثال 2 :

الكود:
    // Program to compute absolute value
   // Works both for integer and float
   
   #include <iostream>
   using namespace std;
   
   int absolute(int);
   float absolute(float);
   
   int main() {
       int a = -5;
       float b = 5.5;
      
       cout << "Absolute value of " << a << " = " << absolute(a) << endl;
       cout << "Absolute value of " << b << " = " << absolute(b);
       return 0;
   }
   
   int absolute(int var) {
       if (var < 0)
           var = -var;
       return var;
   }
   
   float absolute(float var){
       if (var < 0.0)
           var = -var;
       return var;
   }



الخرج :

الكود:
Absolute value of -5 = 5
Absolute value of 5.5 = 5.5

في المثال أعلاه ، يتم تحميل دالتين absolute() بحمل زائد .
كلا الدالتين تأخذ وسيط واحد. ومع ذلك ، تأخذ دالة نوع العددً الصحيحً int كوسيط بينما تأخذ الدالة الأخرى النوع float كوسيط.
عندما يتم استدعاء الدالة absolute() مع عدد صحيح كوسيط ، تستدعى هذه الدالة :

الكود:
   int absolute(int var) {
       if (var < 0)
           var = -var;
       return var;
   }

عندما يتم استدعاء الدالة absolute() مع النوع float كوسيط ، تستدعى هذه الدالة :

الكود:
   float absolute(float var){
       if (var < 0.0)
           var = -var;
       return var;
   }



/////////////////////////////////////////////////////////////////////////////////////

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الأحد يوليو 07, 2019 8:52 pm

العودية (استدعاء الدالة لنفسها أو الاستدعاء الذاتى) Recursion
وجود دوال functions يجعل من الممكن تقنية برمجة تسمى العودية recursion (تعريف الشىء بدلالة نفسه والمقصود بها هنا استدعاء الدالة لنفسها وهذا يؤدى إلى استدعائها عديد من المرات)
العودية recursion تنطوي على أن دالة تستدعى نفسها. هذا يبدو غير محتمل إلى حد ما ، وبالفعل دالة تستدعى نفسها ، وغالبا ما يكون خطأ bug. ومع ذلك ، عند استخدامها بشكل صحيح يمكن أن تكون هذه التقنية قوية بشكل مدهش.
العودية Recursion أسهل بكثير في الفهم بمثال من التفسيرات المطولة ، لذلك دعونا نطبقها على برنامج رأيناه من قبل: برنامج العامل FACTOR بالدرس الثالث ، "الحلقات والقرارات". استخدم هذا البرنامج حلقة for لحساب مضروب (مفكوك) factorial العدد. (انظر هذا المثال للحصول على شرح المفكوك ) يستخدم برنامجنا الجديد ، FACTOR2 ، العودية recursion بدلاً من الحلقة.



الكود:
 //factor2.cpp
//calculates factorials using recursion
#include <iostream>
using namespace std;

unsigned long factfunc(unsigned long); //declaration

int main()
{
int n;             //number entered by user
unsigned long fact;       //factorial

cout << “Enter an integer: “;
cin >> n;
fact = factfunc(n);

cout << “Factorial of “ << n << “ is “ << fact << endl;
return 0;
}

//-------------------------------------------------------------
// factfunc()
// calls itself to calculate factorials
unsigned long factfunc(unsigned long n)
{
if(n > 1)
return n * factfunc(n-1);    //self call
else
return 1;
}

خرج هذا البرنامج هو نفس خرج برنامج FACTOR في الدرس 3.


يبدو جزء main() من FACTOR2 معقولًا : فهو يستدعي الدالة ، factfunc() ، بوسيط يمثل عددا أدخله المستخدم. هذه الدالة من ثم ترجع مضروب هذا العدد إلى main() .

الدالة factfunc() هي قصة أخرى . ماذا تفعل؟ إذا كانت n أكبر من 1 ، فإن الدالة تستدعي نفسها. لاحظ أنه عند القيام بذلك ، يستخدم وسيط factfunc(n-1) أقل واحد من الوسيط factfunc(n) التي تم استدعاؤها بها. افترض أنه تم استدعاؤه من main() بوسيط 5. فإنها سوف تدعو إلى إصدار ثانٍ من نفسها باستخدام وسيط 4. ثم ستقوم هذه الدالة باستدعاء إصدار ثالث بوسيط 3 وهكذا .
لاحظ أن كل إصدار من الدالة تخزن قيمتها الذاتية n بينما تنشغل في استدعاء نسخة أخرى من نفسها.
بعد أن تستدعي الدالة factfunc() نفسها أربع مرات ، يتم استدعاء الإصدار الخامس من الدالة باستخدام وسيط 1. وتكتشف ذلك باستخدام العبارة if ، وبدلاً من استدعاء نفسها ، كما تفعل فى الإصدارات السابقة ، فإنها ترجع 1 إلى الإصدار الرابع . قام الإصدار الرابع بتخزين قيمة 2 ، لذلك يضاعف (يضرب) 2 المخزنة فى 1 التي تم إرجاعها ، وإرجاع 2 إلى الإصدار الثالث. يقوم الإصدار الثالث بتخزين 3 ، لذلك يضاعف (يضرب) 3 فى 2 ، ويعود 6 إلى الإصدار الثاني. يقوم الإصدار الثاني بتخزين 4 ، لذلك يضاعف (يضرب) هذا في 6 التى تم إرجاعها ويعيد 24 إلى الإصدار الأول. يقوم الإصدار الأول بتخزين 5 ، لذلك يضاعف (يضرب) هذا في 24 المرتجعة ويرجع 120 إلى main() .
وبالتالي في هذا المثال ، لدينا خمس استدعاءات للدالة تتبعها عوائد الدالة الخمسة. فيما يلي ملخص لهذه العملية:

الدرس الخامس الدوال Functions 8D57EDD2000F4DF59309C7BF5DCD927B

يجب توفير لكل دالة متكررة طريقة لإنهاء العودية. وإلا فإنها سوف تستدى نفسها إلى الأبد وتحطم البرنامج . تلعب العبارة if في factfunc() هذا الدور ، وتنتهي من العودية عندما تكون n بواحد 1 .

هل صحيح أن العديد من إصدارات الدالة العودية يتم تخزينها في الذاكرة أثناء استدعاء نفسها؟ ليس صحيحا. يتم تخزين متغيرات كل إصدار ، ولكن هناك نسخة واحدة فقط من كود الدالة . ومع ذلك ، يمكن أن تؤدي العودية المتداخلة بعمق إلى إنشاء عدد كبير من المتغيرات المخزنة ، والتي يمكن أن تشكل مشكلة للنظام إذا لم يكن لديها مساحة كافية لها.

//////////////////////////////////////////////////////////////////////////////////////
مراجعة على الدوال التى تستدعى نفسها Recursion

كيف تعمل الدوال التى تستدعى نفسها :

void recurse()
{
... .. ...
recurse();
... .. ...
}

int main()
{
... .. ...
recurse();
... .. ...
}
يوضح الشكل أدناه كيفية عمل recursion الدوال التى تستدعى نفسها مرارًا وتكرارًا

الدرس الخامس الدوال Functions 5405888656B648679D503CAC170DAFB7


يستمر الاستدعاء الذاتى recursion حتى يتم استيفاء بعض الشروط.
لمنع التكرار غير المحدود ، يمكن استخدام عبارة if…else (أو نهج مماثل) حيث يقوم أحد الفروع بإجراء الاستدعاءالمتكرر بينما لا يستخدم الآخر الاستدعاء .

مثال 1 :


الكود:
   // Factorial of n = 1*2*3*...*n
   
   #include <iostream>
   using namespace std;
   
   int factorial(int);
   
   int main()
   {
       int n;
       cout<<"Enter a number to find factorial: ";
       cin >> n;
       cout << "Factorial of " << n <<" = " << factorial(n);
       return 0;
   }
   
   int factorial(int n)
   {
       if (n > 1)
       {
           return n*factorial(n-1);
       }
       else
       {
           return 1;
       }
   }


الخرج

الكود:
Enter a number to find factorial: 4
Factorial of 4 = 24

شرح : كيف يعمل هذا المثال؟


لنفترض أن المستخدم أدخل 4 ، والتى بتم تمريرها كوسيط إلى الدالة factorial() .
1. في الدالة factorial() الأولى ، تعبير الاختبار داخل عبارة if يكون صحيح true . يتم تنفيذ العبارة

الكود:
 return num*factorial(num-1);

التي تستدعي الدالة factorial() الثانية ويتم تمرير الوسيط num-1 وهو 3.
2. في الدالة factorial() الثانية ، تعبير الاختبار التعبير داخل عبارة if يكون صحيح. يتم تنفيذ العبارة

الكود:
return num*factorial(num-1);

التي تستدعي الدالة factorial() الثالثة ويتم تمرير الوسيط num-1 وهو 2.

3- في الدالة factorial() الثالثة ، تعبير الاختبار داخل عبارة if يكون صحيح. يتم تنفيذ العبارة

الكود:
return num*factorial(num-1); 

التي تستدعي الدالة factorial() الرابعة ويتم تمرير الوسيط n-1 وهو 1 .


4. في الدالة factorial() الرابعة ، تعبير الاختبار داخل عبارة if يكون خطأ false . يتم تنفيذ العبارة

الكود:
return 1;

التي تقوم بإرجاع 1 إلى الدالة factorial() الثالثة.


5. ترجع الدالة factorial() الثالثة 2 إلى الدالة factorial() الثانية.

6. ترجع الدالة factorial() الثانية 6 إلى الدالة factorial() الأولى .

7. أخيرًا ، ترجع الدالة factorial() الأولى 24 إلى الدالة main() ، والتي يتم عرضها على الشاشة.

مثال 2 :
برنامج لإيجاد مجموع الأعداد الطبيعية باستخدام Recursion
الأعداد الموجبة 1 ، 2 ، 3 ... تُعرف بالأعداد الطبيعية. يأخذ البرنامج أدناه عددًا صحيحًا موجبًا من المستخدم ويحسب المجموع sum حتى الرقم المحدد المعطى .
يمكنك إيجاد مجموع الأعداد الطبيعية باستخدام الحلقات كذلك.



الكود:
   #include<iostream>
   using namespace std;
   
   int add(int n);
   
   int main()
   {
       int n;
   
       cout << "Enter a positive integer: ";
       cin >> n;
   
       cout << "Sum =  " << add(n);
   
       return 0;
   }
   
   int add(int n)
   {
       if(n != 0)
           return n + add(n - 1);
       return 0;
   } 

الخرج :

الكود:
 Enter an positive integer: 10
Sum = 55

في هذا البرنامج ، يتم تمرير العدد الذي أدخله المستخدم إلى الدالة add() .

افترض ، أنه يتم إدخال 10 من قبل المستخدم . الآن ، يتم تمرير 10 إلى الدالة add() . تضيف هذه الدالة 10 إلى نتيجة الجمع 9 (10 - 1 = 9) .
في المرة التالية ، تتم إضافة 9 إلى نتيجة الإضافة 8 (9 - 1 = Cool . يستمر هذا حتى يصل العدد إلى 0 ، عندما ترجع الدالة 0.
الآن ، يتم إرجاع كل دالة لحساب النتيجة النهائية : 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55 .


//////////////////////////////////////////////////////////////////////////////////////

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الإثنين يوليو 08, 2019 12:21 pm

الوسائط الافتراضية Default Arguments
والمثير للدهشة ، يمكن استدعاء دالة دون تحديد جميع وسائطها . لن يعمل هذا على أي دالة : يجب أن يوفر إعلان الدالة القيم الافتراضية لتلك الوسائط غير المحددة.

إليك مثال على ذلك ، التباين في برنامج OVERLOAD يوضح هذا التأثير. في OVERLOAD استخدمنا ثلاث دوال مختلفة بنفس الاسم للتعامل مع أعداد مختلفة من الوسائط. المثال الحالي ، MISSARG ، يحقق التأثير نفسه بطريقة مختلفة.


الكود:
// missarg.cpp
// demonstrates missing and default arguments
#include <iostream>
using namespace std;

void repchar(char=’*’, int=45);       //declaration with
//default arguments
int main()
{
repchar();       //prints 45 asterisks
repchar(‘=’);       //prints 45 equal signs
repchar(‘+’, 30);    //prints 30 plus signs
return 0;
}
//--------------------------------------------------------------
// repchar()
// displays line of characters
void repchar(char ch, int n)    //defaults supplied
{             // if necessary
for(int j=0; j<n; j++)    //loops n times
cout << ch;       //prints ch
cout << endl;
}




في هذا البرنامج ، تأخذ الدالة repchar() وسيطين. وتستدعى ثلاث مرات من main() . في المرة الأولى يتم استدعاؤها بدون أي وسائط ، والمرة الثانية بوسيط واحد ، والثالثة مع اثنين. لماذا تعمل النداءان الأوليان؟ لأن الدالة التي تم استدعاؤها توفر الوسائط الافتراضية ، والتي سيتم استخدامها في حالة عدم توفير برنامج الاستدعاء لها. يتم تحديد الوسائط الافتراضية في الإعلان الخاص بالدالة repchar() :


الكود:
void repchar(char=’*’, int=45); //declaration

يتبع الوسيط الافتراضى علامة المساواة ، والتي يتم وضعها مباشرة بعد اسم النوع . يمكنك أيضًا استخدام أسماء متغير ، كما هو الحال في

الكود:
 void repchar(char reptChar=’*’, int numberReps=45);

إذا كانت إحدى الوسائط مفقودة عند استدعاء الدالة ، فمن المفترض أن يكون الوسيط الأخير.
تقوم الدالة repchar() بتعيين قيمة الوسيط الفردى للبارامتر ch وتستخدم القيمة الافتراضية 45 للبارامتر n.

إذا كان كلا الوسيطين مفقود ، فإن الدالة تقوم بتعيين القيمة الافتراضية "*" إلى ch والقيمة الافتراضية 45 إلى n. وبالتالي ، تعمل جميع الاستدعاءات الثلاثة للدالة ، رغم أن لكل منها عددًا مختلفًا من الوسائط.

تذكر أن الوسائط المفقودة يجب أن تكون الوسائط فى الزيل - تلك الموجودة في نهاية قائمة الوسائط. يمكنك استبعاد الوسائط الثلاث الأخيرة ، لكن لا يمكنك تجاهل الوسيط قبل الأخير ثم وضع الوسيط الأخير. هذا معقول ، كيف يعرف المترجم الوسائط التي تقصدها إذا تركت البعض في المنتصف؟ (يمكن الإشارة إلى الوسائط المفقودة بفواصل ، لكن الفواصل تخضع لسمات خاطئة ، لذلك تجاهل مصممو C++ هذا الاحتمال.) وليس من المستغرب أن يقوم المترجم بوضع علامة خطأ إذا تركت وسائط لا توفر الدالة القيم الافتراضية لها .

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


//////////////////////////////////////////////////////////////////////////////////////
مراجعة على الوسائط الافتراضية :
في البرمجة بلغة C++ ، يمكنك توفير القيم الافتراضية لبارامترات الدالة .
الفكرة وراء الوسائط الافتراضية بسيطة. إذا تم استدعاء دالة بتمرير وسيط / وسائط ، يتم استخدام هذه الوسائط بواسطة الدالة.
ولكن إذا لم يتم تمرير وسيط / وسائط أثناء استدعاء دالة ، فسيتم استخدام القيم الافتراضية.
يتم تمرير القيمة / القيم الافتراضية إلى الوسيط / الوسائط النموذج الأولي prototype للدالة .

عمل الوسائط الافتراضية Working of default arguments

الدرس الخامس الدوال Functions 048C397A88554953911B811B7E5A9688

مثال :

الكود:
   // C++ Program to demonstrate working of default argument
   
   #include <iostream>
   using namespace std;
   
   void display(char = '*', int = 1);
   
   int main()
   {
       cout << "No argument passed:\n";
       display();
      
       cout << "\nFirst argument passed:\n";
       display('#');
      
       cout << "\nBoth argument passed:\n";
       display('$', 5);
   
       return 0;
   }
   
   void display(char c, int n)
   {
       for(int i = 1; i <= n; ++i)
       {
           cout << c;
       }
       cout << endl;
   }

الخرج :

الكود:
No argument passed:
*
First argument passed:
#
Both argument passed:
$$$$$
 

في البرنامج أعلاه ، يمكنك رؤية القيمة الافتراضية المعينة للوسائط

الكود:
void display(char = '*', int = 1);

• في البداية ، يتم استدعاء الدالة display() دون تمرير أي وسائط . في هذه الحالة ، تستخدم الدالة display() كلا الوسيطين الافتراضيين c=* و n = 1.
• بعد ذلك ، يتم تمرير الوسيط الأوى فقط باستخدام الدالة فى المرة الثانية. في هذه الحالة ، لا تستخدم الدالة القيمة الافتراضية الأولى التي تم تمريرها. تستخدم البارامتر الفعلى التي تم تمريره كوسيط أول c=# وتأخذ القيمة الافتراضية n = 1 كوسيط ثانى لها.
• عند استدعاء display() للمرة الثالثة بتمرير كلا الوسيطين ، لا يتم استخدام الوسائط الافتراضية. لذلك ، قيمة c=$ و n = 5.

الأخطاء الشائعة عند استخدام الوسائط الافتراضية

الكود:
void add(int a, int b = 3, int c, int d = 4);


الدالة أعلاه سوف لا يتم ترجمتها . لا يمكنك تفويت وسيط افتراضى بين وسيطين.
في هذه الحالة ، يجب أيضًا تعيين قيمة افتراضية للمتغير c.

الكود:
void add(int a, int b = 3, int c, int d);


الدالة أعلاه سوف لا يتم ترجمتها كذلك. يجب عليك توفير القيم الافتراضية لكل وسيطة بعد b
في هذه الحالة ، يجب أيضًا تعيين القيم الافتراضية c و d.

إذا كنت تريد وسيط واحد افتراضى ، فتأكد من أن الوسيط هي آخر وسيط.

الكود:
void add(int a, int b, int c, int d = 4); 

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

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الجمعة يوليو 12, 2019 11:51 am

بارامترات ووسائط الدالة Function parameters and arguments
البارامترات مقابل الوسائط Parameters vs Arguments
في الاستخدام الشائع ، غالبًا ما يتم التبديل (الخلط) بين مصطلحى البارمتر parameter والوسيط argument . لذلك ، سنميز بين الاثنين:
• بارامتر الدالة (يسمى أحيانًا البارامتر الرسمى formal parameter ) هو متغير معلن في إعلان declaration الدالة :

الكود:
 void foo(int x); // declaration (function prototype) -- x is a parameter
 
void foo(int x) // definition (also a declaration) -- x is a parameter
{
}
• الوسيط argument (يسمى أحيانًا البارامتر الفعلى actual parameter ) هو القيمة التي يتم تمريرها إلى الدالة بواسطة المستدعى :

الكود:
foo(6); // 6 is the argument passed to parameter x
foo(y+1); // the value of y+1 is the argument passed to parameter x

• عند استدعاء دالة ، يتم إنشاء جميع بارامترات الدالة كمتغيرات ، ويتم نسخ قيمة الوسائط في البارمترات . فمثلا:

الكود:
void foo(int x, int y)
{
}
 
foo(6, 7);

عندما يتم استدعاء foo() بالوسيطين arguments 6 و 7 ، يتم إنشاء البارامتر (المتغير) x وتعيين القيمة (الوسيط) 6 له ، ويتم إنشاء البارامتر y وتعيين القيمة (الوسيط) له 7.
على الرغم من عدم إعلان البارامترات داخل كتلة الدالة ، فإن بارامترات الدالة لها نطاق محلي local scope . هذا يعني أنها يتم إنشاؤها عند استدعاء الدالة ، ويتم إتلافها عند إنهاء كتلة الدالة :

الكود:
void foo(int x, int y) // x and y are created here
{
} // x and y are destroyed here

هناك 3 طرق أساسية لتمرير الوسائط إلى الدوال :
• تمرير حسب القيمة pass by value ،
• وتمرير حسب المرجع pass by reference ،
• وتمرير حسب العنوان pass by address.

تمرير الوسائط حسب القيمة Passing arguments by value
بشكل افتراضي ، يتم تمرير الوسائط غير المؤشر non-pointer في لغة C++ حسب القيمة. عندما يتم تمرير وسيط حسب القيمة ، يتم "نسخ قيمة الوسيط في قيمة بارامتر الدالة المقابل" .
بالنظر في الكود التالي:


الكود:
#include <iostream>
 using namespace std;

void foo(int y)
{
    cout << "y = " << y << '\n';
}
 
int main()
{
    foo(5);       // first call
 
    int x = 6;
    foo(x);       // second call
    foo(x+1);       // third call
 
    return 0;
}


• في أول استدعاء إلى الدالة foo(5) ، يكون الوسيط هو العدد 5. عند استدعاء foo() ، يتم إنشاء المتغير y ، ويتم نسخ القيمة 5 إلى y. ثم يتم إتلاف المتغير y عندما تنتهي foo() .
• في الاستدعاء الثاني إلى الدالة foo(x) ، يكون الوسيط هو المتغير x. يتم تقييم x لإنتاج القيمة 6. عندما يتم استدعاء foo() للمرة الثانية ، يتم إنشاء المتغير y مرة أخرى ، ويتم نسخ القيمة 6 إلى y. ثم يتم إتلاف المتغير y عندما تنتهي foo() .
• في الاستدعاء الثالث إلى الدالة foo(x+1) ، يكون الوسيط هوالتعبير x + 1. يتم تقييم x + 1 لإنتاج القيمة 7 ، والتي يتم تمريرها إلى المتغير y. المتغير y يتم إتلافه مرة أخرى عندما تنتهي foo() .
ومن ثم ، فإن هذا البرنامج يطبع :

الكود:
y = 5
y = 6
y = 7

لأنه يتم تمرير نسخة من الوسيط إلى الدالة ، لا يمكن تعديل الوسيط الأصلى بواسطة الدالة . هذا هو المبين في المثال التالي:


الكود:
 #include <iostream>
 using namespace std;
 
void foo(int y)
{
    cout << "y = " << y << '\n';
 
    y = 6;
 
    cout << "y = " << y << '\n';
} // y is destroyed here
 
int main()
{
    int x = 5;
    cout << "x = " << x << '\n';
 
    foo(x);
 
    cout << "x = " << x << '\n';
    return 0;
}


هذا البرنامج يخرج ما يلى :

الكود:
 x = 5
y = 5
y = 6
x = 5

في بداية الدالة main ،تكون x بالقيمة 5. عند استدعاء foo() ، يتم تمرير قيمة x (5) إلى البارامتر y للدالة foo() داخل foo() ، يتم تعيين قيمة y بالقيمة 6 ، ثم يتم إتلافها. قيمة x لم تتغير ، على الرغم من تغيير y.
إيجابيات وسلبيات التمرير حسب القيمة
مزايا التمرير حسب بالقيمة:
• يمكن أن تكون الوسائط التي يتم تمريرها حسب القيمة عبارة عن متغيرات (مثل x) والأرقام الحرفية (على سبيل المثال 6) والتعبيرات (مثل x + 1) والهياكل والفئات. وبعبارة أخرى ، تقريبا أي شيء.
• لا يتم تغيير الوسائط أبدًا بواسطة الدالة التي يتم استدعاؤها ، والتي تمنع الآثار الجانبية.

عيوب التمرير حسب القيمة:
• قد ينتج عن نسخ هياكل structs وفئات classes تحمل سوء أداء كبير ، خاصةً إذا كانت الدالة تستدعى عدة مرات.

متى يجب استخدام تمرير القيمة:
• عند تمرير نوع البيانات الأساسية ، ولا تحتاج الدالة إلى تغيير الوسيط.

متى لا تستخدم تمرير بالقيمة:
• عند تمرير هياكل أو فئات.

في معظم الحالات ، يمثل تمرير القيمة أفضل طريقة لقبول بارامترات الأنواع الأساسية عندما لا تحتاج الدالة إلى تغيير الوسيط. التمرير بالقيمة مرن وآمن ، وفي حالة الأنواع الأساسية ، يتسم بالكفاءة.

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

التمرير حسب المرجع Pass by reference
لتمرير متغير حسب المرجع ، نعلن declare ببساطة بارامترات الدالة كمراجع references وليس كمتغيرات عادية ويكون بالشكل التالى &ref (إضافة & بين النوع واسم المتغير بالشكل int& ref أو int & ref أو int &ref ) :


الكود:
 void addOne(int &ref) // ref is a reference variable
{
    ref = ref + 1;
}

عندما يتم استدعاء الدالة ، سيصبح ref مرجعًا للوسيط. نظرًا لأن المرجع (الإشارة) إلى المتغير تعامل تمامًا مثل المتغير نفسه ، يتم تمرير أي تغييرات تم إجراؤها على المرجع إلى الوسيط!
المثال التالي يوضح هذا:


الكود:
 void addOne(int &ref)
{
    ref = ref + 1;
}
 
int main()
{
    int value = 5;
 
    cout << "value = " << value << '\n';
    addOne(value);
    cout << "value = " << value << '\n';
    return 0;
}

هذا البرنامج هو نفس البرنامج الذي استخدمناه في مثال التمرير حسب القيمة ، باستثناء بارامتر foo الآن أصبح مرجعًا reference بدلاً من المتغير العادي . عندما نستدعى addOne(value) ، يصبح ref مرجعًا لمتغير لقيمة main . ينتج هذا الإخراج التالى :

الكود:
value = 5
value = 6

كما ترى ، فإن الدالة غيرت قيمة الوسيط من 5 إلى 6

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في الجمعة يوليو 12, 2019 12:18 pm

الدوال المضمنة Inline functions
يوفر استخدام الدوال العديد من الفوائد ، بما في ذلك:
• يمكن إعادة استخدام الكود الموجود داخل الدالة .
• من الأسهل بكثير تغيير أو تحديث الكود في دالة (والتي يجب القيام بها مرة واحدة) عن كل مثيل. الكود المكرر هو وصفة لعدم الكفاءة والأخطاء.
• تسهل قراءة وفهم الكود ، حيث لا يتعين عليك معرفة كيفية تنفيذ الدالة لفهم ما تفعله (على افتراض المسئولية عن تسمية الدالة أو التعليقات).
• توفر الدوال فحص النوع للتأكد من تطابق وسائط استدعاء الدالة مع بارامترات الدالة (لا تقوم وحدات الماكرو المشابهة للدالة بذلك ، مما قد يؤدي إلى حدوث أخطاء).
• الدوال تجعل البرنامج أسهل فى التصحيح.
ومع ذلك ، فإن الجانب السلبي الرئيسي للدوال هو أنه في كل مرة يتم استدعاء الدالة ، يكون هناك قدر معين من نفقات الأداء performance overhead تحدث . وذلك لأن وحدة المعالجة المركزية يجب أن تخزن عنوان التعليمات الحالية التي تنفذها (حتى تعرف مكان العودة فى وقت لاحق) بجانب السجلات الأخرى ، ويجب إنشاء جميع بارامترات الدالة وتعيين القيم ، ويجب أن يتفرع البرنامج إلى موقع جديد. الكود المكتوب في المكان in-place أسرع بكثير.
بالنسبة للدوال الكبيرة و / أو التي تؤدي مهام معقدة ، عادة ما يكون مقدار الحمل overhead الخاص باستدعاء الدالة ضئيلًا مقارنةً بالوقت الذي تستغرقه الدالة لتشغيلها. ومع ذلك ، بالنسبة للدوال الصغيرة شائعة الاستخدام ، غالبًا ما يكون الوقت اللازم لإجراء استدعاء الدالة أكثر بكثير من الوقت اللازم لتنفيذ كود الدالة بالفعل. هذا يمكن أن يؤدي إلى نفقات أداء كبيرة.
توفر C++ طريقة للجمع بين مزايا الدوال وسرعة الكود المكتوب في المكان in-place : الدالات المضمنة inline . يتم استخدام الكلمة المفتاحية inline في الطلب من المترجم التعامل مع الدالة الخاصة بك كدالة مضمّنة inline . عندما يقوم المترجم بترجمة الكود الخاص بك ، يتم توسيع جميع الوظائف المضمّنة في مكانها - أي ، يتم استبدال استدعاء الدالة بنسخة من محتويات الدالة نفسها ، مما يزيل الحمل الإضافي نتيجة لاستدعاء الدالة . الجانب السلبي هو أنه نظرًا لأن الدالة المضمنة يتم توسيعها في مكانها لكل استدعاء دالة ، فإن هذا يمكن أن يجعل الكود الخاص بك أكبر قليلاً ، خاصة إذا كانت الدالة المضمنة طويلة و / أو هناك العديد من الاستدعاءات إلى الدالة المضمنة.
اعتبر جزء البرنامج التالى :

الكود:
int min(int x, int y)
{
    return x > y ? y : x;
}
 
int main()
{
    cout << min(5, 6) << '\n';
    cout << min(3, 2) << '\n';
    return 0;

يستدعي هذا البرنامج الدالة min() مرتين ، مما يؤدي إلى فرض نفقات لاستدعاء الدالة مرتين . نظرًا لأن min() دالة قصيرة ، فهي المرشح المثالي للتضمين inline:

الكود:
inline int min(int x, int y)
{
    return x > y ? y : x;

الآن عندما يقوم البرنامج بترجمة main() ، فإنه سينشئ كود آلة كما لو كان main() قد كتب على النحو التالي:


الكود:
 int main()
{
    cout << (5 > 6 ? 6 : 5) << '\n';
    cout << (3 > 2 ? 2 : 3) << '\n';
    return 0;
}
سينفذ هذا بشكل أسرع قليلاً ، على حساب أن يكون الكود المترجم أكبر قليلاً.

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الدرس الخامس الدوال Functions Empty رد: الدرس الخامس الدوال Functions

مُساهمة من طرف Admin في السبت يوليو 13, 2019 7:37 pm

النطاق وفئة التخزين Scope and Storage Class
الآن بعد أن علمنا عن الدوال ، يمكننا استكشاف سمتين للغة C++ والتى ترتبط بتفاعل المتغيرات والدوال : النطاق scope وفئة التخزين storage class .
• يحدد "نطاق المتغير" أجزاء البرنامج التي يمكنها الوصول إليه ،
• وتحدد فئة التخزين مدة بقاءه في الوجود. سنلخص هذا بإيجاز ثم نلقي نظرة على الموقف بمزيد من التفاصيل.

هناك نوعان مختلفان من النطاق مهمان ا: النطاق المحلي local ونطاق الملف file. (سنرى واحدًا آخر ، نطاق الفئة ، لاحقًا.)
• المتغيرات ذات النطاق المحلي local scope تكون مرئية فقط داخل كتلة block.
• المتغيرات ذات نطاق الملف file scope تكون مرئية في جميع أنحاء الملف file .
الكتلة block هي في الأساس الكود بين قوس الفتح وقوس الإغلاق. وبالتالي فإن جسم الدالة هو كتلة.

هناك فئتان للتخزين: تلقائي automatic وثابت (ساكن) static .
• توجد المتغيرات ذات فئة تخزين التلقائية خلال مدة عمر lifetime الدالة التي تم تعريف المتغيرات بها.
• توجد المتغيرات ذات فئة تخزين الثابتة طوال مدة عمر البرنامج.
الآن دعونا نرى ما يعنيه كل هذا.

1- المتغيرات المحلية Local Variables
حتى الآن تم تعريف جميع المتغيرات التي استخدمناها في البرامج داخل الدالة التي تستخدم فيها. أي أن التعريف يحدث داخل الأقواس التي تحدد جسم الدالة :


الكود:
void somefunc()
{
int somevar;       //variables defined within
float othervar;    //the function body
// other statements
}

يمكن تعريف المتغيرات داخل main() أو داخل دالة أخرى ؛ التأثير هو نفسه ، لأن main() هي دالة . تسمى المتغيرات المعرفة داخل جسم دالة المتغيرات المحلية local variables لأن لها نطاق محلي . ومع ذلك ، تسمى أيضًا المتغيرات التلقائية automatic variables في بعض الأحيان ، لأنها تحتوي على فئة التخزين التلقائي.
دعونا نلقي نظرة على هاتين الخاصيتين المهمتين للمتغيرات التي يتم تعريفها داخل الدوال.

فئة التخزين Storage Class
لا يتم إنشاء متغير محلي حتى يتم استدعاء الدالة التي يتم تعريفه بها. (بشكل أكثر دقة ، يمكننا القول أن المتغيرات المعرفة في أي كتلة من الكود لا يتم إنشاؤها حتى يتم تنفيذ الكتلة. وبالتالي ، فإن المتغيرات المعرفة داخل جسم الحلقة لا تكون موجودة إلا أثناء تنفيذ الحلقة.)

في جزء البرنامج المعطى للتو ، لا توجد المتغيرات somevar و othervar حتى يتم استدعاء الدالة somefunc() . أي أنه لا يوجد مكان في الذاكرة حيث يمكن تخزين قيمهم ؛ فهم غير معرفين. عندما يتم نقل التحكم إلى somefunc() ، يتم إنشاء المتغيرات ويتم تخصيص مساحة الذاكرة لهم. في وقت لاحق ، عندما تُرجع somefunc() وتُعاد السيطرة إلى برنامج الاستدعاء ، يتم إتلاف destroyed المتغيرات وتفقد قيمها. يتم استخدام الاسم تلقائيًا automatic لأن المتغيرات يتم إنشاؤها تلقائيًا عند استدعاء دالة وتدميرها تلقائيًا عند إرجاعها.

تسمى الفترة الزمنية بين إنشاء المتغير وتدميره بعمره lifetime (أو في بعض الأحيان مدته duration). يتزامن (يتطابق) عمر المتغير المحلي مع الوقت الذي يتم فيه تنفيذ الدالة التي يتم تعريفه بها.
الفكرة وراء الحد من عمر المتغيرات هي توفير مساحة الذاكرة. إذا لم يتم تنفيذ دالة ، فمن المفترض أن المتغيرات التي تستخدمها أثناء التنفيذ ليست ضرورية. تؤدي إزالتها إلى تحرير الذاكرة التي يمكن استخدامها من قبل دوال أخرى.

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

تكون المتغيرات المعرفة داخل دالة مرئية فقط ، مما يعني أنه يمكن الوصول إليها فقط ، من داخل الدالة التي تم تعريفها بها. افترض أن لديك دالتين في البرنامج:


الكود:
void somefunc()
{
int somevar;       //local variables
float othervar;
somevar = 10;       //OK
othervar = 11;       //OK
nextvar = 12;       //illegal: not visible in somefunc()
}

void otherfunc()
{
int nextvar;       //local variable
somevar = 20;       //illegal: not visible in otherfunc()
othervar = 21;       //illegal: not visible in otherfunc()
nextvar = 22;       //OK
}

المتغير nextvar غير مرئي في الدالة somefunc() ، والمتغيرات somevar و othervar غير مرئية في الدالة otherfunc() .
يساعد الحد من رؤية visibility المتغيرات في تنظيم البرنامج فى شكل وحدات . يمكنك أن تكون واثقًا من أن المتغيرات في إحدى الدوال تكون في مأمن من التغيير العرضي بواسطة دوال أخرى لأن الدوال الأخرى لا يمكنها رؤيتها. هذا جزء مهم من البرمجة المنظمة (المهيكلة) ، منهجية تنظيم البرامج الإجرائية القديمة. الحد من الرؤية هو أيضا جزء مهم من البرمجة الموجهة للكائنات.

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

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

الكود:
 int n = 33;

عندئذ سوف يبدأ بهذه القيمة.

2- المتغيرات العمومية (العامة) Global Variables
النوع التالي من المتغيرات هو "العمومى" global . بينما يتم تعريف المتغيرات المحلية داخل الدوال ، يتم تعريف المتغيرات العامة خارج أي دالة . (يتم تعريفها أيضًا خارج أي فئة ، كما سنرى لاحقًا.) المتغير العمومي مرئي لجميع الدوال في الملف (وربما في ملفات أخرى).
بتعبير أدق ، يكون مرئيًا لجميع الدوال التي تتبع (تلى) تعريف المتغير في القائمة. عادة ما تريد أن تكون المتغيرات العامة مرئية لجميع الدوال ، لذلك يمكنك وضع إعلاناتها في بداية القائمة. تسمى المتغيرات العامة في بعض الأحيان بالمتغيرات الخارجية external variable ، حيث يتم تعريفها خارج أي دالة .
إليك برنامج ، EXTERN ، حيث تعمل ثلاث دوال على الوصول إلى متغير عمومي.

الكود:
 
// extern.cpp
// demonstrates global variables
#include <iostream>
using namespace std;
#include <conio.h>       //for getch()

char ch = ‘a’;          //global variable ch

void getachar();       //function declarations
void putachar();

int main()
{
while( ch != ‘\r’ )    //main() accesses ch
{
getachar();
putachar();
}
cout << endl;
return 0;
}
//--------------------------------------------------------------
void getachar()       //getachar() accesses ch
{
ch = getch();
}
//--------------------------------------------------------------
void putachar()       //putachar() accesses ch
{
cout << ch;
}


أحد الدوال في EXTERN ، getachar() ، تقرأ الأحرف من لوحة المفاتيح . تستخدم دالة المكتبة getch() ، والتى تشبه getche() إلا أنها لا تعكس echo الحرف الذى تمت كتابته على الشاشة (ومن ثم عدم وجود e النهائية في الاسم). الدالة الثانية فى EXTERN ، putachar() ، تعرض كل حرف على الشاشة. التأثير هو أن ما تكتبه يتم عرضه بالطريقة العادية .

الشيء المهم في هذا البرنامج هو أن المتغير ch لم يتم تعريفه في أي من الدوال . بدلاً من ذلك ، يتم تعريفه في بداية الملف ، قبل الدالة الأولى. إنه متغير عمومي (خارجي). يمكن لأي دالة تلى (تتبع) تعريف ch في القائمة الوصول إليه — وفي هذه الحالة ، فإن جميع الدوال في EXTERN : main() و getachar() و putachar() . وبالتالي فإن visibility "رؤية" ch هو ملف المصدر بأكمله.

• دور (وظيفة) المتغيرات العمومية Role of Global Variables
يتم استخدام متغير عمومي عندما يجب أن يكون في متناول (مطلوب أن يصل إلى ) أكثر من دالة واحدة في البرنامج.
المتغيرات العمومية غالبًا ما تكون أهم المتغيرات في البرامج الإجرائية. ومع ذلك تخلق المتغيرات العمومية مشكلات تنظيمية لأنه يمكن الوصول إليها من خلال أي دالة . قد تصل إليها الدوال الخطأ ، أو قد تصل إليها الدوال بشكل غير صحيح . في البرمجة الموجه للكائنات ، هناك حاجة أقل بكثير للمتغيرات العمومية .

• التهيئة Initialization
إذا تم تهيئة متغير عمومي ، كما هو الحال في :

الكود:
 int exvar = 199;

تحدث هذا التهيئة عند تحميل البرنامج لأول مرة. إذا لم يتم تهيئة المتغير العام بشكل صريح بواسطة البرنامج — على سبيل المثال ، إذا تم تعريفه على أنه :

الكود:
int exvar;

عندئذ تتم تهيئته تلقائيًا إلى 0 عند إنشائه. (هذا بخلاف المتغيرات المحلية ، التي لم تتم تهيئتها وربما تحتوي على قيم عشوائية أو قيم غير مقبولة (نفايات)عند إنشائها.)

• العمر والرؤية Lifetime and Visibility
المتغيرات العامة لها فئة تخزين ثابتة static ، مما يعني أنها موجودة لحياة البرنامج.
يتم تخصيص مساحة الذاكرة لهم عند بدء البرنامج ، ويستمر وجودها حتى ينتهي البرنامج. لا تحتاج إلى استخدام الكلمة المفتاحية static عند الإعلان عن المتغيرات العامة ؛ يتم منحهم فئة التخزين هذه تلقائيًا.
كما لاحظنا ، تكون المتغيرات العامة مرئية في الملف الذي تم تعريفها به ، بدءًا من النقطة التي تم تعريفها بها. إذا تم تعريف ch بعد main() ولكن قبل getachar() ، فسيكون مرئيًا في getachar() و putachar()، ولكن ليس في main() .

3- متغيرات محلية ثابتة (ساكنة) Static Local Variables
دعونا ننظر إلى نوع آخر من المتغيرات: المتغير المحلي الثابت. هناك متغيرات عامة ثابتة ، لكنها ذات مغزى فقط في البرامج متعددة.
المتغير المحلي الثابت له رؤية visibility مثل متغير محلي تلقائي (أي ، داخل الدالة التي تحتوي عليه). ومع ذلك ، فإن عمرة lifetime هو نفسه كمتغير عام ، باستثناء أنه لا يتواجد إلى حيز الوجود حتى أول استدعاء للدالة التي تحتوي عليه. بعد ذلك يبقى في الوجود لحياة البرنامج.
يتم استخدام المتغيرات المحلية الثابتة عندما يكون من الضروري لدالة ما أن تتذكر قيمة عندما لا يتم تنفيذها ؛ وهذا يكون ، بين استدعاءات الدالة . في المثال التالي ، تقوم الدالة ، getavg() ، بحساب المتوسط الجارى . إنها تتذكر إجمالي الأرقام التي كانت قد حسبت متوسطها من قبل ، وعددها. في كل مرة تستقبل فيها رقمًا جديدًا ، يتم إرساله كوسيطة من برنامج الاستدعاء ، فإنها تضيف هذا الرقم إلى الإجمالي ، وتضيف 1 إلى العدد ، وتعيد المتوسط الجديد بقسمة الإجمالي على العدد. إليك القائمة STATIC:

الكود:

// static.cpp
// demonstrates static variables
#include <iostream>
using namespace std;

float getavg(float);       //declaration

int main()
{
float data=1, avg;

while( data != 0 )
{
cout << “Enter a number: “;
cin >> data;
avg = getavg(data);
cout << “New average is “ << avg << endl;
}
return 0;
}
//--------------------------------------------------------------
// getavg()
// finds average of old plus new data
float getavg(float newdata)
{
static float total = 0;       //static variables are initialized
static int count = 0;       // only once per program
count++;          //increment count
total += newdata;       //add new data to total
return total / count;       //return the new average
}
 


إليك بعض نماذج التفاعل:
Enter a number: 10

الكود:

New average is 10 ← total is 10, count is 1
Enter a number: 20
New average is 15 ← total is 30, count is 2
Enter a number: 30
New average is 20 ← total is 60, count is 3
 

تحتفظ المتغيرات الثابتة total و count في getavg() بقيمها بعد إرجاع getavg() ، لذلك فهي متوفرة في المرة التالية التي تستدعى فيها .


• التهيئة Initialization
عندما تتم تهيئة المتغيرات الساكنة ، مثل total و countفي getavg() ، تتم التهيئة مرة واحدة فقط - أول مرة يتم فيها استدعاء دالتها . لا يتم إعادة تهيئتها فى الاستدعاءات اللاحقة للدالة ، مثل المتغيرات المحلية العادية.

• التخزين Storage
إذا كنت معتادًا على بنية نظام التشغيل ، فقد تكون مهتمًا بمعرفة أنه يتم تخزين المتغيرات المحلية ووسائط الدالة على المكدس stack ، بينما يتم تخزين المتغيرات العامة والثابتة على الكومة heap .

Admin
Admin

عدد المساهمات : 1187
تاريخ التسجيل : 28/01/2014

https://fathallaabdelaziz.forumarabia.com

الرجوع الى أعلى الصفحة اذهب الى الأسفل

الرجوع الى أعلى الصفحة


 
صلاحيات هذا المنتدى:
لاتستطيع الرد على المواضيع في هذا المنتدى