|
| قسم لغات البرمجة php java c++ جافا Visual Basic فيجوال بيسك c# Delphi دلفي API PERL HTML و TML JavaScript ASP XML Oracle اوركال MySql sql server Access |
|
|
| F.Abdelaziz |
| مشاهدة ملفه الشخصي |
| زيارة موقع F.Abdelaziz المفضل |
| البحث عن كافة المشاركات المكتوبة بواسطة F.Abdelaziz |
| اعلانات |
|
||||||
|
العودية (استدعاء الدالة لنفسها أو الاستدعاء الذاتى) 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() . وبالتالي في هذا المثال ، لدينا خمس استدعاءات للدالة تتبعها عوائد الدالة الخمسة. فيما يلي ملخص لهذه العملية: ![]() يجب توفير لكل دالة متكررة طريقة لإنهاء العودية. وإلا فإنها سوف تستدى نفسها إلى الأبد وتحطم البرنامج . تلعب العبارة if في factfunc() هذا الدور ، وتنتهي من العودية عندما تكون n بواحد 1 . هل صحيح أن العديد من إصدارات الدالة العودية يتم تخزينها في الذاكرة أثناء استدعاء نفسها؟ ليس صحيحا. يتم تخزين متغيرات كل إصدار ، ولكن هناك نسخة واحدة فقط من كود الدالة . ومع ذلك ، يمكن أن تؤدي العودية المتداخلة بعمق إلى إنشاء عدد كبير من المتغيرات المخزنة ، والتي يمكن أن تشكل مشكلة للنظام إذا لم يكن لديها مساحة كافية لها. ////////////////////////////////////////////////////////////////////////////////////// مراجعة على الدوال التى تستدعى نفسها Recursion كيف تعمل الدوال التى تستدعى نفسها : void recurse() { ... .. ... recurse(); ... .. ... } int main() { ... .. ... recurse(); ... .. ... } يوضح الشكل أدناه كيفية عمل recursion الدوال التى تستدعى نفسها مرارًا وتكرارًا ![]() يستمر الاستدعاء الذاتى 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 = 8) . يستمر هذا حتى يصل العدد إلى 0 ، عندما ترجع الدالة 0. الآن ، يتم إرجاع كل دالة لحساب النتيجة النهائية : 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55 . ////////////////////////////////////////////////////////////////////////////////////// |
| F.Abdelaziz |
| مشاهدة ملفه الشخصي |
| زيارة موقع F.Abdelaziz المفضل |
| البحث عن كافة المشاركات المكتوبة بواسطة F.Abdelaziz |
| اعلانات اضافية ( قم بتسجيل الدخول لاخفائها ) | |
|
|
|
||||||
|
الوسائط الافتراضية 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 ![]() مثال : كود:
// 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); بغض النظر عن كيفية استخدام الوسائط الافتراضية ، يجب دائمًا كتابة الدالة بحيث تخدم غرضًا واحدًا فقط. إذا كانت دالتك تقوم بأكثر من شيء واحد أو إذا كان المنطق يبدو معقدًا للغاية ، فيمكنك استخدام التحميل الزائد للدالة لفصل المنطق بشكل أفضل. |
|
||||||
|
بارامترات ووسائط الدالة 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
{
}
كود:
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 |
|
||||||
|
الدوال المضمنة 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;
}
|
|
||||||
|
النطاق وفئة التخزين 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 . |
|
||||||
|
الدرس السادس المصفوفات والسلاسل النصية Arrays and Strings تمهيد : في الحياة اليومية نجمع الأشياء المتشابهة في وحدات. نشتري البازلاء بالعبوة والبيض بالكرتونة . نحتاج أيضًا فى لغات الكمبيوتر إلى تجميع عناصر البيانات من نفس النوع معًا. الآلية الأساسية التي تنجز هذا في لغة C++ هي المصفوفة array . يمكن أن تحتوي المصفوفات على بعض عناصر البيانات أو عشرات الآلاف. يمكن أن تكون عناصر البيانات التي تم تجميعها في المصفوفة أنواعًا بسيطة مثل int أو float ، أو يمكن أن تكون أنواعًا معرّفة من قِبل المستخدم مثل الهياكل والكائنات. تشبه المصفوفات الهياكل structures حيث يجمعان عددًا من العناصر في وحدة أكبر. ولكن في حين أن الهيكل يجمع عادة عناصر من أنواع مختلفة ، فإن المصفوفة تجمع عناصر من نفس النوع. الأهم من ذلك ، يتم الوصول إلى العناصر الموجودة في هيكل بالاسم ، في حين يتم الوصول إلى العناصر الموجودة في مصفوفة من خلال رقم الفهرس index number . يتيح استخدام رقم فهرس لتحديد عنصر سهولة الوصول إلى عدد كبير من العناصر. توجد المصفوفات في كل لغة كمبيوتر تقريبًا. تتشابه المصفوفات في لغة C++ مع تلك الموجودة باللغات الأخرى ، وهي مماثلة لتلك الموجودة في لغة C. في هذا الدرس ، سننظر أولاً في مصفوفة أنواع البيانات الأساسية مثل int و char. بعد ذلك سنقوم بفحص المصفوفات المستخدمة كأعضاء بيانات في الفئات ، والمصفوفات المستخدمة للاحتفاظ بالكائنات. وبالتالي ، لا يهدف هذا الدرس إلى تقديم المصفوفات فحسب ، بل لزيادة فهمك للبرمجة الموجهة للكائنات. في لغة C++ القياسية ، المصفوفة ليست هى الطريقة الوحيدة لتجميع عناصر من نفس النوع. المتجه vector ، الذي يعد جزءًا من مكتبة القوالب القياسية Standard Template library ، يعد طريقة أخرى. في هذا الدرس ، سننظر أيضًا في طريقتين (نهجين) مختلفتين للسلاسل النصية strings ، والتي تُستخدم لتخزين النصوص ومعالجتها. النوع الأول من السلسلة هو مصفوفة من النوع char ، والثاني هو عضو في فئة سلسلة C++ القياسية. أساسيات المصفوفة Array Fundamentals مثال بسيط لبرنامج بسيط سيعمل على تقديم (التعرف على ) المصفوفات. ينشئ هذا البرنامج ، REPLAY ، مصفوفة لمجموعة من أربعة أعداد صحيحة تمثل أعمار أربعة أشخاص. ثم يطلب من المستخدم إدخال أربع قيم ، والتي يضعها في المصفوفة. أخيرًا ، يعرض القيم الأربعة. كود:
// replay.cpp
// gets four ages from user, displays them
#include <iostream>
using namespace std;
int main()
{
int age[4]; //array ‘age’ of 4 ints
for(int j=0; j<4; j++) //get 4 ages
{
cout << “Enter an age: “;
cin >> age[j]; //access array element
}
for(j=0; j<4; j++) //display 4 ages
cout << “You entered “ << age[j] << endl;
return 0;
}
إليك نموذج تفاعلي مع البرنامج: كود:
Enter an age: 44 Enter an age: 16 Enter an age: 23 Enter an age: 68 You entered 44 You entered 16 You entered 23 You entered 68 تحصل أول حلقة for على الأعمار ages من المستخدم وتضعها في المصفوفة ، بينما حلقة for الثانية تقرأها من المصفوفة وتعرضها. • تعريف المصفوفات Defining Arrays مثل المتغيرات الأخرى في لغة C++ ، يجب تعريف المصفوفة قبل استخدامها لتخزين المعلومات. ومثل التعريفات الأخرى ، يحدد تعريف المصفوفة نوعًا المتغيرً واسمًا له. لكنه يتضمن ميزة أخرى: الحجم size . يحدد الحجم عدد عناصر البيانات التي ستحتويها المصفوفة . وهو يتبع الاسم مباشرةً ، وتحيط به أقواس مربعة [ ] . يوضح الشكل بناء جملة تعريف المصفوفة . ![]() الشكل يبين بناء جملة تعريف المصفوفة. في المثال REPLAY ، المصفوفة هى من نوع int. يأتي اسم المصفوفة بعد ذلك ، يليه مباشرةً قوس الفتح وحجم المصفوفة وقوس الإغلاق. يجب أن يكون الرقم بين الأقواس ثابتًا أو تعبيرًا يتم تقييمه إلى ثابت ، ويجب أن يكون أيضًا عددًا صحيحًا. في المثال نستخدم القيمة 4. • عناصر المصفوفة الصفيف Array Elements تسمى البنود items الموجودة في المصفوفة عناصر elements (على عكس البنود items الموجودة في هيكل ، والتي تسمى أعضاء members ). كما لاحظنا ، جميع العناصر في المصفوفة هي من نفس النوع ، فقط القيم تختلف. يوضح الشكل عناصر المصفوفة age . ![]() الشكل يبين عناصر المصفوفة Array elements باتباع النهج التقليدي ، الذاكرة تكون كما في الشكل. أى يكون ، أول عناصر المصفوفة في الجزء العلوي ؛ العناصر الاحقة تمتد لأسفل. كما هو محدد في التعريف ، المصفوفة تحتوى على أربعة عناصر بالضبط. لاحظ أن عنصر المصفوفة الأول مُرقّم 0. وبالتالي ، نظرًا لوجود أربعة عناصر ، يكون العنصر الأخير هو رقم 3. وهذا الوض محتمل أن يكون مربكا ؛ قد تعتقد أن العنصر الأخير في مصفوفة مكونة من أربعة عناصر سيكون رقم 4 ، لكنه ليس كذلك. • الوصول إلى عناصر المصفوفة Accessing Array Elements في مثال REPLAY ، نصل إلى كل عنصر مصفوفة مرتين. في المرة الأولى ، نقوم بإدخال (إدراج) قيمة في المصفوفة ، بالسطر كود:
cin >> age[j]; في المرة الثانية ، نقرأه بالسطر كود:
cout << “\nYou entered “ << age[j]; في كلتا الحالتين التعبير عن عنصر المصفوفة يكون بالصيغة كود:
age[j] يتكون هذا التعبير من اسم المصفوفة ، متبوعًا بأقواس تحدد delimiting متغيرًا j . أي من عناصر المصفوفة الأربعة المحددة بواسطة هذا التعبير يعتمد على قيمة j ؛ يشير age[0] إلى العنصر الأول ، و age[1] إلى العنصر الثاني ، و age[2] إلى العنصر الثالث ، و age[3] إلى العنصرالرابع. يُطلق على المتغير (أو الثابت) في الأقواس فهرس (مؤشر – دليل – رقم تسلسلى ) المصفوفة array index . نظرًا لأن j هو متغير الحلقة في كلا من حلقتى for الحلقات ، فإنه يبدأ عند 0 ويتزايد حتى يصل إلى 3 ، وبالتالي الوصول إلى كل عنصر من عناصر المصفوفة كل فى دوره. • متوسطات عناصر المصفوفة Averaging Array Elements إليك مثال آخر على المصفوفة . البرنامج ، SALES ، يدعو المستخدم لإدخال سلسلة من ستة قيم تمثل مبيعات قطعة لكل يوم من أيام الأسبوع (باستثناء الأحد) ، ثم يحسب متوسط هذه القيم. نحن نستخدم مصفوفة من النوع double بحيث يمكن إدخال القيم النقدية . كود:
// sales.cpp
// averages a weeks’s widget sales (6 days)
#include <iostream>
using namespace std;
int main()
{
const int SIZE = 6; //size of array
double sales[SIZE]; //array of 6 variables
cout << “Enter widget sales for 6 days\n”;
for(int j=0; j<SIZE; j++) //put figures in array
cin >> sales[j];
double total = 0;
for(j=0; j<SIZE; j++) //read figures from array
total += sales[j]; //to find total
double average = total / SIZE; // find average
cout << “Average = “ << average << endl;
return 0;
}
إليك بعض نماذج التفاعل مع SALES : كود:
Enter widget sales for 6 days 352.64 867.70 781.32 867.35 746.21 189.45 Average = 634.11 تفصيل جديد في هذا البرنامج هو استخدام متغير const لحجم المصفوفة وحدود الحلقة. يتم تعريف هذا المتغير في بداية القائمة: كود:
const int SIZE = 6; استخدام متغير (بدلاً من رقم ، مثل الرقم 4 المستخدم في المثال الأخير) يسهل تغيير حجم المصفوفة : يتم تغيير سطر برنامج واحد فقط لتغيير حجم المصفوفة وحدود الحلقة وفي أي مكان آخر يظهر حجم المصفوفة . يذكرنا اسم الأحرف الكبيرة SIZE بأنه لا يمكن تعديل المتغير في البرنامج. • تهيئة المصفوفات Initializing Arrays يمكنك إعطاء قيم لكل عنصر مصفوفة عند تعريف المصفوفة لأول مرة. إليك مثال ، DAYS ، يقوم بتعيين 12 عنصر مصفوفة في المصفوفة days_per_month إلى عدد الأيام في كل شهر. كود:
// days.cpp
// shows days from start of year to date specified
#include <iostream>
using namespace std;
int main()
{
int month, day, total_days;
int days_per_month[12] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
cout << “\nEnter month (1 to 12): “; //get date
cin >> month;
cout << “Enter day (1 to 31): “;
cin >> day;
total_days = day; //separate days
for(int j=0; j<month-1; j++) //add days each month
total_days += days_per_month[j];
cout << “Total days from start of year is: “ << total_days
<< endl;
return 0;
}
يقوم البرنامج بحساب عدد الأيام من بداية السنة إلى تاريخ يحدده المستخدم. (احذر: إنه لا يعمل للسنوات الكبيسة leap years ). إليك بعض نماذج التفاعل: كود:
Enter month (1 to 12): 3 Enter day (1 to 31): 11 Total days from start of year is: 70 بمجرد حصوله على قيم الشهر واليوم ، يقوم البرنامج أولاً بتعيين القيمة اليوم للمتغير total_days. ثم ينتقل عبر حلقة ، حيث يضيف قيمًا من المصفوفة days_per_month إلى total_days. عدد هذه القيم المراد إضافتها واحد أقل من عدد الأشهر. على سبيل المثال ، إذا أدخل المستخدم الشهر 5 ، تتم إضافة قيم عناصر المصفوفة الأربعة الأولى (31 و 28 و 31 و 30) إلى الإجمالي. القيم التي تتم تهيئة days_per_month محاطة بأقواس ومفصولة بفواصل. ترتبط بتعبير المصفوفة بواسطة علامة المساواة. يوضح الشكل بناء الجملة. ![]() الشكل يبين بناء جملة تهيئة المصفوفة Syntax of array initialization في الواقع ، لا نحتاج إلى استخدام حجم المصفوفة عندما نقوم بتهيئة جميع عناصر المصفوفة ، نظرًا لأن المترجم يمكنه تحديد ذلك عن طريق حساب متغيرات التهيئة. هكذا يمكننا كتابة كود:
int days_per_month[] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31 };
ماذا يحدث إذا استخدمت حجم مصفوفة صريحًا ، لكنه لا يتفق مع عدد المُهيئات؟ إذا كان هناك عدد قليل جدًا من المهيئات ، فسيتم تعيين العناصر المفقودة على 0. إذا كان هناك الكثير ، فسيتم الإشارة إلى حدوث خطأ. |
|
||||||
|
المصفوفات متعددة الأبعاد Multidimensional Arrays حتى الآن فحصنا المصفوفات ذات البعد الواحد : يحدد متغير واحد كل عنصر من عناصر المصفوفة . لكن المصفوفات يمكن أن يكون لها أبعاد أعلى. إليك برنامج ، SALEMON ، يستخدم مصفوفة ثنائية الأبعاد two-dimensional لتخزين أرقام المبيعات للعديد من المناطق DISTRICTS ولعدة أشهر MONTHS: كود:
// salemon.cpp
// displays sales chart using 2-d array
#include <iostream>
#include <iomanip> //for setprecision, etc.
using namespace std;
const int DISTRICTS = 4; //array dimensions
const int MONTHS = 3;
int main()
{
int d, m;
double sales[DISTRICTS][MONTHS]; //two-dimensional array
//definition
cout << endl;
for(d=0; d<DISTRICTS; d++) //get array values
for(m=0; m<MONTHS; m++)
{
cout << “Enter sales for district “ << d+1;
cout << “, month “ << m+1 << “: “;
cin >> sales[d][m]; //put number in array
}
cout << “\n\n”;
cout << “ Month\n”;
cout << “ 1 2 3”;
for(d=0; d<DISTRICTS; d++)
{
cout <<”\nDistrict “ << d+1;
for(m=0; m<MONTHS; m++) //display array values
cout << setiosflags(ios::fixed) //not exponential
<< setiosflags(ios::showpoint) //always use point
<< setprecision(2) //digits to right
<< setw(10) //field width
<< sales[d][m]; //get number from array
} //end for(d)
cout << endl;
return 0;
} //end main
يقبل هذا البرنامج أرقام المبيعات من المستخدم ثم يعرضها في جدول. ![]() • تعريف المصفوفات متعددة الأبعاد Defining Multidimensional Arrays يتم تعريف المصفوفة بمحددي حجم size specifiers ، كل منهما محاط بأقواس: كود:
double sales[DISTRICTS][MONTHS]; يمكنك أن تفكر في sales (المبيعات) كمصفوفة ثنائية الأبعاد ، تم وضعها مثل لوحة الشطرنج. هناك طريقة أخرى للتفكير في الأمر وهي أن sales عبارة عن مصفوفة من المصفوفات. إنها مصفوفة من عناصر DISTRICTS ، كل عنصر منها عبارة عن مصفوفة من عناصر MONTHS. يوضح الشكل كيف يبدو هذا. ![]() الشكل يبين مصفوفة ثنائية الأبعاد Two-dimensional array بالطبع يمكن أن يكون هناك مصفوفات بأكثر من بعدين. المصفوفة ثلاثية الأبعاد هي مصفوفة من مصفوفات المصفوفات . يتم الوصول إليها accessed بثلاثة فهارس indexes : كود:
elem = dimen3[x][y][z]; هذا مشابه تمامًا للمصفوفات أحادية وثنائية الأبعاد. • الوصول إلى عناصر المصفوفة متعددة الأبعاد Accessing Multidimensional Array Elements تتطلب عناصر المصفوفة في المصفوفات ثنائية الأبعاد فهرسين indexes : كود:
sales[d][m] لاحظ أن كل فهرس له مجموعتة من الأقواس. لا تستخدم الفواصل. لا تكتب sales[d,m]; هذا يعمل في بعض اللغات ، ولكن ليس في C++ . • تنسيق الأرقام Formatting Numbers يعرض برنامج SALEMON جدول للقيم بالدولار. من المهم أن يتم تنسيق هذه القيم بشكل صحيح ، لذلك دعونا نستكشف كيف يتم ذلك في C++ . مع قيم الدولار ، عادة ما تريد أن يكون لديك رقمان بالضبط على يمين العلامة العشرية ، وتريد أن تصطف النقاط العشرية لجميع الأرقام الموجودة في العمود. من الجيد أيضًا عرض الأصفار الزائدة ؛ تريد 79.50 ، وليس 79.5. يتطلب جعل تدفقات الإدخال / الإخراج I/O streams فى لغة C++ القيام بكل هذا القليل من العمل . لقد شاهدت بالفعل المناور setw() ، واستخدمته لتعيين عرض حقل الإخراج. تنسيق الأرقام العشرية يتطلب عدة معالجات إضافية. إليك عبارة تطبع العدد ذو الفاصلة العائمة يسمى fpn في حقل بعرض 10 أحرف ، مع رقمين digits على يمين العلامة العشرية: كود:
cout << setiosflags(ios::fixed) //fixed (not exponential)
<< setiosflags(ios::showpoint) //always show decimal point
<< setprecision(2) //two decimal places
<< setw(10) //field width 10
<< fpn; //finally, the number
تحدد مجموعة من أعلام (إشارات) flags التنسيق ذات البت الواحد في long int في الفئة ios كيفية تنفيذ التنسيق. في هذه المرحلة ، لا نحتاج إلى معرفة ماهية الفئة ios أو أسباب بناء الجملة الدقيقة المستخدمة مع هذه الفئة ، لجعل المناورين manipulators يعملون. نحن مهتمون باثنين من أعلام ios: fixed و showpoint . لتعيين الأعلام ، استخدم المناور setiosflags ، مع اسم العلم كوسيط. يجب أن يسبق الاسم اسم الفئة ios وعامل دقة النطاق scope resolution operator (: .تعين أول سطرين من عبارة cout أعلام ios. (إذا كنت بحاجة إلى إلغاء تعيين - أي ، مسح clear - الأعلام في وقت لاحق من البرنامج ، يمكنك استخدام المعالج (المناور) resetiosflags ) يمنع العلم fixed من طباعة الأرقام بالتنسيق الأسي exponential ، مثل 3.45e3 . يحدد العلم showpoint أنه ستكون هناك دائمًا علامة عشرية ، حتى إذا كان الرقم لا يحتوي على جزء كسري : 123.00 بدلاً من 123. لتعيين الدقة على رقمين على يمين المكان العشري ، استخدم المناور setprecision ، مع عدد الأرقام كوسيط. لقد رأينا بالفعل كيفية ضبط عرض الحقل باستخدام المناور setw. بمجرد أن يتم إرسال جميع هذه المعالجات إلى cout ، يمكنك إرسال الرقم نفسه ؛ سيتم عرضه بالتنسيق المطلوب. سنتحدث أكثر عن إشارات (أعلام) تنسيق ios لاحقا . • تهيئة المصفوفات متعددة الأبعاد Initializing Multidimensional Arrays كما قد تتوقع ، يمكنك تهيئة المصفوفات متعددة الأبعاد. الشرط الوحيد الوحيد هو الاستعداد لكتابة الكثير من الأقواس والفواصل. إليك صيغة مختلفة من برنامج SALEMON يستخدم مصفوفة مُهيأة بدلاً من طلب إدخال من المستخدم. هذا البرنامج يسمى SALEINIT. كود:
// saleinit.cpp
// displays sales chart, initializes 2-d array
#include <iostream>
#include <iomanip> //for setprecision, etc.
using namespace std;
const int DISTRICTS = 4; //array dimensions
const int MONTHS = 3;
int main()
{
int d, m;
//initialize array elements
double sales[DISTRICTS][MONTHS]
= { { 1432.07, 234.50, 654.01 },
{ 322.00, 13838.32, 17589.88 },
{ 9328.34, 934.00, 4492.30 },
{ 12838.29, 2332.63, 32.93 } };
cout << “\n\n”;
cout << “ Month\n”;
cout << “ 1 2 3”;
for(d=0; d<DISTRICTS; d++)
{
cout <<”\nDistrict “ << d+1;
for(m=0; m<MONTHS; m++)
cout << setw(10) << setiosflags(ios::fixed)
<< setiosflags(ios::showpoint) << setprecision(2)
<< sales[d][m]; //access array element
}
cout << endl;
return 0;
}
تذكر أن المصفوفة ثنائية الأبعاد هي بالفعل مصفوفة من المصفوفات. يعتمد تنسيق تهيئة مثل هذه المصفوفة على هذه الحقيقة. يتم إحاطة قيم التهيئة لكل مصفوفة فرعية بأقواس ومفصولة بفواصل كود:
{ 1432.07, 234.50, 654.01 }
وبعد ذلك ، يتم ربط جميع هذه المصفوفات الفرعية الأربعة ، والتي يعد كل منها عنصرًا في المصفوفة الرئيسية ، بالأقواس وتكون مفصولة بفواصل ، كما هو موضح في القائمة. تمرير المصفوفات إلى الدوال Passing Arrays to Functions يمكن استخدام المصفوفات كوسائط للدوال . في ما يلي مثال ، صيغة مختلفة لبرنامج SALEINIT ، يمرر مصفوفة أرقام المبيعات إلى دالة تهدف إلى عرض البيانات كجدول. فيما يلي قائمة SALEFUNC: كود:
// salefunc.cpp
// passes array as argument
#include <iostream>
#include <iomanip> //for setprecision, etc.
using namespace std;
const int DISTRICTS = 4; //array dimensions
const int MONTHS = 3;
void display( double[DISTRICTS][MONTHS] ); //declaration
//--------------------------------------------------------------
int main()
{ //initialize two-dimensional array
double sales[DISTRICTS][MONTHS]
= { { 1432.07, 234.50, 654.01 },
{ 322.00, 13838.32, 17589.88 },
{ 9328.34, 934.00, 4492.30 },
{ 12838.29, 2332.63, 32.93 } };
display(sales); //call function; array as argument
cout << endl;
return 0;
} //end main
//--------------------------------------------------------------
//display()
//function to display 2-d array passed as argument
void display( double funsales[DISTRICTS][MONTHS] )
{
int d, m;
cout << “\n\n”;
cout << “ Month\n”;
cout << “ 1 2 3”;
for(d=0; d<DISTRICTS; d++)
{
cout <<”\nDistrict “ << d+1;
for(m=0; m<MONTHS; m++)
cout << setiosflags(ios::fixed) << setw(10)
<< setiosflags(ios::showpoint) << setprecision(2)
<< funsales[d][m]; //array element
} //end for(d)
} //end display
• إعلان دالة بوسائط مصفوفة Function Declaration with Array Arguments في إعلان الدالة ، يتم تمثيل وسائط المصفوفة بنوع البيانات وحجم المصفوفة إليك إعلان الدالة display() : void display( float[DISTRICTS][MONTHS] ); // declaration في الواقع ، هناك قطعة واحدة غير ضرورية من المعلومات هنا. العبارة التالية تعمل كذلك: كود:
void display( float[][MONTHS] ); // declaration لماذا لا تحتاج الدالة إلى حجم البعد الأول؟ مرة أخرى ، تذكر أن المصفوفة ثنائية الأبعاد عبارة عن مصفوفة من المصفوفات. تفكر الدالة أولاً في الوسيط كمصفوفة المناطق districts . فهى لا تحتاج الأمر إلى معرفة عدد المناطق الموجودة هناك ، لكنها تحتاج إلى معرفة حجم كل عنصر من عناصر كل منطقة ، بحيث يمكنها حساب مكان عنصر معين (بضرب البايتات لكل عنصر في الفهرس). لذلك يجب أن نقول لها حجم كل عنصر ، وهو MONTHS ، ولكن ليس كم عددهم ، وهو DISTRICTS . يترتب على ذلك أنه إذا كنا نعلن عن دالة تستخدم مصفوفة أحادية البعد كوسيط ، فلن نحتاج إلى استخدام حجم المصفوفة : كود:
void somefunc( int elem[] ); // declaration • استدعاء دالة بوسائط مصفوفة Function Call with Array Arguments عند استدعاء الدالة ، يتم استخدام اسم المصفوفة فقط كوسيط. كود:
display(sales); // function call يمثل هذا الاسم (sales في هذه الحالة) بالفعل عنوان ذاكرة المصفوفة . لن نستكشف العناوين بالتفصيل حتى ندرس ، "المؤشرات" Pointers . |
|
جميع الحقوق محفوظة 2026
|


