Courses & Tutorials
دورات عادل نسيم على اليوتيوب
المواضيع
C++
مرحبًا بكم في عالم برمجة لغة C++!
مرحبًا، أنا عادل نسيم، منشئ قناة Adel Nasim على اليوتيوب، ويسعدني أن أرشدك عبر أساسيات برمجة C++. سواء كنت مبتدئًا أو تتطلع إلى تعميق مهاراتك، فإن هذه الدورة التدريبية المكتوبة، إلى جانب دروسي على YouTube، هي مصدرك الشامل لإتقان لغة C++. سنغطي كل شيء بدءًا من الأساسيات وحتى المفاهيم المتقدمة، مما يضمن حصولك على المعرفة النظرية ومهارات البرمجة العملية.
استعد لرحلة تمزج بين الشرح النظري والأمثلة العملية، وتزودك بالثقة اللازمة لمواجهة تحديات العالم الحقيقي. سواء كنت تقوم ببناء أساس البرمجة الخاص بك أو تحسين المهارات الحالية، دعنا نتعمق في عالم C++ معًا. Happy coding!
المقدمة
C++ سي++ (تنطق: سي بلس بلس) (بالإنجليزية: ++C) هي لغة برمجة كائنية، متعددة أنماط البرمجة، مصرفة، سكونية الأنماط. وتضم العديد من ميزات لغات البرمجة عالية المستوى ومنخفضة المستوى.
تعد C++ خيارًا شائعًا لتطوير التطبيقات عالية الأداء لأنه يمنح المبرمجين مستوى عالٍ من التحكم في موارد النظام والذاكرة. كما أنها لغة فعالة جدًا، مما يعني أن برامج C++ عادةً ما تعمل بشكل أسرع من البرامج المكتوبة بلغات أخرى.
تعد لغة C++ لغة معقدة نسبيًا للتعلم، ولكنها أيضًا لغة قوية جدًا. بمجرد إتقان أساسيات لغة C++، ستتمكن من تطوير أي نوع من البرامج التي يمكنك تخيلها تقريبًا.
لماذا سي ++؟
لغة C++ هي واحدة من لغات البرمجة المشهورة لسبب. إذ أنها لغة قوية, متنوعة القدرات وذات كفاءة عالية حيث من الممكن إستخدامها في العديد من أنظمة البرمجة.
هنا سنذكر لك بعض الأسباب لماذا تُعَدّ لغة C++ لغة ذات شعبية عالية:
- الأداء: عادةً ما تكون برمجيات لغة C++ سريعة وفعّالة جدًّا. ذلك لأنّ لغة C++ تمنح المبرمجين درجة عالية من التحكّم في مصادر النظام والذاكرة المُستخدمة.
- تنوّع القدرات: يمكن إستخدام لغة C++ لتطوير جوانب متعددة من البرمجية, من أنظمة التشغيل إلى الألعاب إلى البرمجيات الضمنية.
- النقل: يمكن تجميع برامج C++ وتشغيلها على مجموعة متنوعة من المنصات، بما في ذلك Windows و macOS و Linux.
- المجتمع: تتمتع لغة C++ بمجتمع كبير ونشط من المطورين. وهذا يعني أن هناك ثروة من الموارد المتاحة لمساعدتك على تعلم لغة C++ واستخدامها.
الإختلافات بين لغتيّ C و C++
تم تطوير لغة C++ كامتداد للغة البرمجة C. كلتا اللغتين لهما بناء جملة مماثل، لكن لغة C++ تضيف عددًا من الميزات الجديدة، بما في ذلك:
- الـClasses والـObjects: تدعم لغة C++ الـClasses والـObjeccts، والتي توفر طريقة لتغليف البيانات والتعليمات البرمجية في وحدات قابلة لإعادة الاستخدام.
- القوالب: تتيح لك قوالب C++ إنشاء وظائف وClasses عامة يمكن استخدامها مع أنواع بيانات مختلفة.
- معالجة الاستثناء: يوفر C++ ميزات معالجة الاستثناءات التي تسمح لك بالتعامل مع الأخطاء بطريقة منظمة.
البداية
إذا كنت مهتمًا بتعلم لغة C++، فهناك عدد من الموارد المتاحة لمساعدتك على البدء. هناك العديد من الكتب والبرامج التعليمية الجيدة المتاحة عبر الإنترنت، وهناك أيضًا عدد من مجتمعات لغة C++ حيث يمكنك طلب المساعدة والمشورة.
بمجرد أن تتعلم أساسيات لغة C++، يمكنك البدء في تطوير تطبيقاتك الخاصة. هناك عدد من مترجمات C++ وبيئات التطوير المختلفة المتاحة، لذا يمكنك اختيار البيئة التي تناسب احتياجاتك.
الخلاصة
تعد لغة C++ لغة برمجة قوية ومتعددة الاستخدامات يمكن استخدامها لإنشاء مجموعة واسعة من البرامج. إنه خيار شائع لتطوير التطبيقات عالية الأداء، كما أنه خيار جيد للمبتدئين لأنه سهل التعلم نسبيًا.
البداية
ما هي لغة C++؟
C++ هي لغة برمجة للأغراض العامة تُستخدم لإنشاء مجموعة واسعة من التطبيقات، بما في ذلك أنظمة التشغيل والألعاب والأنظمة المدمجة وتطبيقات الويب. تشتهر لغة C++ بالسرعة والكفاءة والمرونة.
الشروع في العمل مع لغة C++
للبدء في استخدام C++، ستحتاج إلى تثبيت مترجم C++ Compiler وIDE. المترجم Compiler هو برنامج يقوم بتحويل لغة C++ إلى لغة الآلة الذي يمكن تنفيذه بواسطة الجهاز الحاسب Computer. إن الـ IDE هو عبارة عن بيئة تطوير متكاملة توفر مجموعة متنوعة من الأدوات لكتابة، تحرير، تجميع وتصحيح كود C++.
تحميل مترجم C++ Compiler و IDE
هناك العديد من برامج التحويل البرمجي Compilers وIDEs المختلفة المتوفرة لـ C++. في هذا البرنامج التعليمي، سوف نستخدم Visual Studio لـ C++. يعد Visual Studio بيئة تطوير متكاملة مجانية وقوية توفر دعمًا شاملاً لتطوير لغة C++.
لتثبيت Visual Studio لـ C++، قم بزيارة موقع ويب Visual Studio وقم بتنزيل أحدث إصدار.
أُكتُب أول برنامج لك بإستخدام لغة C++
بمجرد تثبيت Visual Studio لـ C++، يمكنك البدء في كتابة برنامج C++ الأول. للقيام بذلك، قم بإنشاء مشروع جديد وحدد قالب "C++ Console App".
بمجرد إنشاء مشروع جديد، يمكنك البدء في كتابة كود C++ في الملف المصدر الرئيسي. عادةً ما يتم تسمية الملف المصدر الرئيسي main.cpp
.
فيما يلي مثال بسيط لبرنامج C++:
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
return 0;
}
يطبع هذا البرنامج الرسالة “Hello, world!” إلى وحدة التحكم.
بمجرد الانتهاء من كتابة برنامج C++ الخاص بك، يمكنك برمجته وتشغيله بالضغط على F5
.
تعلم المزيد عن لغة C++
الآن بعد أن كتبت برنامجك الأول بلغة C++، يمكنك البدء في تعلم المزيد عن اللغة. هناك العديد من الموارد المتاحة على الإنترنت وفي المكتبات.
فيما يلي بعض النصائح لتعلم لغة C++:
- ابدأ بالأساسيات. تعرف على المتغيرات وأنواع البيانات وعوامل التشغيل والوظائف وبيانات تدفق التحكم.
- ابحث عن برنامج تعليمي أو كتاب جيد. هناك العديد من الموارد المتاحة التي يمكن أن تعلمك أساسيات لغة C++.
- تدرب بانتظام. أفضل طريقة لتعلم لغة C++ هي من خلال ممارستها. حاول كتابة برامج بسيطة في البداية، ثم انتقل تدريجيًا إلى البرامج الأكثر تعقيدًا.
- استخدم بيئة تطوير متكاملة IDE. يمكن لـ IDE أن يسهل كتابة كود C++ وتحريره وتجميعه وتصحيحه.
الخلاصة
قد يكون البدء باستخدام لغة C++ أمرًا شاقًا، ولكنه مجزٍ أيضًا. تعد لغة C++ لغة قوية ومتعددة الاستخدامات يمكن استخدامها لإنشاء مجموعة واسعة من التطبيقات.
باتباع النصائح الواردة في هذا الدليل، يمكنك البدء في تعلم لغة C++ اليوم والبدء في إنشاء تطبيقاتك الخاصة.
أول برنامج في لغة C++
مثال:
C++
#include <iostream> int main() { std::cout << "Welcome to C++"; return 0; }
هنا يظهر لك برنامج بسيط بإستخدام لغة C++ حيث يقوم بطباعة الرسالة "Welcome to C++" إلى شاشة النتائج.
- #include <iostream>: في هذا السطر نقوم بإخبار المترجم Compiler بتضمين ملف الـheader
iostream
إذ يحتوي هذا الملف على إعلان للمعايير المطلوبة لمكتبة الإداخال والإخراج. - int main(): يحدد هذا الخط الوظيفة الرئيسية. الوظيفة الرئيسية هي نقطة الدخول لجميع برامج C++.
- std::cout << “Welcome to C++”;: يطبع هذا السطر الرسالة “Welcome to C++” على وحدة التحكم باستخدام الملف
std::cout
object. - return 0;: يُرجع هذا السطر القيمة 0 من الدالة الرئيسية main. يشير هذا إلى أنه تم إنهاء البرنامج بنجاح.
عندما تقوم ببرمجة هذا البرنامج وتشغيله، ستتم طباعة الناتج التالي على وحدة التحكم:
Welcome to C++
هذا مثال بسيط جدًا لبرنامج C++، ولكنه يوضح بعض المفاهيم الأساسية للغة، مثل ملفات الـheader والوظائف والإدخال/الإخراج.
سلاسل الهروب (Escape Sequences)
سلاسل الهروب Escape Sequences هي أحرف خاصة تستخدم لتمثيل الأحرف غير المكتوبة أو التحكم في مخرجات البرنامج. وتكتب عادة بعد شرطة مائلة عكسية (\).
أنواع سلاسل الهروب Escape Sequences
يوجد ثلاث أنواع من سلاسل الهروب Escape Sequences في لغة C++
- أحرف سلاسل الهروب escape sequences: تمثل تسلسلات الهروب هذه أحرفًا غير قابلة للطباعة، مثل السطر الجديد وعلامة التبويب ومسافة للخلف.
- سلاسل هروب السداسي عشر Hexadecimal: تمثل تسلسلات الهروب هذه الأحرف باستخدام قيمها السداسية العشرية.
- تسلسل الهروب الثماني Octal: تمثل تسلسلات الهروب هذه الأحرف باستخدام قيمها الثمانية.
تسلسلات الهروب الشائعة:
فيما يلي بعض تسلسلات الهروب الأكثر شيوعًا في لغة C++:
سلاسل الهروب (Escape Sequences) | الوصف |
---|---|
\n |
سطر جديد |
\t |
مقدار ثابت من المساحة، عادة ما يعادل أربعة أو ثمانية مسافات Tab |
\\ |
شرطة مائلة عكسية |
\" |
اقتباس مزدوج |
\' |
اقتباس واحد |
\? |
علامة إستفهام |
\a |
تنبيه |
\b |
مسافة للخلف |
\f |
نموذج تغذية |
\r |
إرجاع |
\v |
علامة التبويب العمودية |
\x |
سلسلة هروب السداسي عشر Hexadecimal |
\NNN |
تسلسل الهروب الثماني Octal |
إستخدام سلاسل الهروب Escape Sequences
يمكن استخدام تسلسلات الهروب في النصوص والأحرف. على سبيل المثال، البرنامج التالي يطبع النص "Hello, World!" متبوعًا بحرف السطر الجديد:
std::cout << "Hello, world!" << std::endl;
الـ std::endl
يمثل حرف السطر الجديد. وهو ما يعادل تسلسل الهروب \n
.
يمكن أيضًا استخدام تسلسلات الهروب لتمثيل الأحرف غير المطبوعة، مثل tab. على سبيل المثال، البرنامج التالي يطبع النص "Hello, World!" متبوعًا بسلسلة الهروب للـ tab:
std::cout << "Hello, world!" << '\t';
مثال
فيما يلي بعض الأمثلة الإضافية حول كيفية استخدام تسلسل الهروب في C++:
// Print the string "This is a quote \" inside a string." std::cout << "This is a quote \" inside a string."; // Print the character 'a' with a backspace before it. std::cout << '\b' << 'a'; // Print the string "Hello, world!" followed by a newline character and a tab character. std::cout << "Hello, world!" << std::endl << '\t';
تعد تسلسلات الهروب أداة قوية يمكن استخدامها للتحكم في سلوك المخرجات وتمثيل الأحرف غير المطبوعة في لغة C++.
المتغيرات و نوع البيانات
المتغيرات و نوع البيانات في لغة C++
المتغيرات
يتم تسمية المتغيرات بمواقع الذاكرة التي يمكنها تخزين البيانات. يتم الإعلان عنها باستخدام بناء الجملة var_name : data_type
على سبيل المثال، البرنامج التالي يقوم بتعريف متغير اسمه my_integer
والذي يمكنه تخزين قيمة عددية:
int my_integer;
بمجرد الإعلان عن متغير، يمكن استخدامه لتخزين البيانات واسترجاعها. على سبيل المثال، يقوم البرنامج التالي بتعيين القيمة 10 إلى المتغير my_integer
:
my_integer = 10;
البرنامج التالي يطبع قيمة المتغير my_integer
إلى صفحة المخرجات:
std::cout << my_integer << std::endl;
المخرجات:
10
أنواع البيانات
تحدد أنواع البيانات نوع البيانات التي يمكن للمتغير تخزينها. تحتوي لغة C++ على مجموعة متنوعة من أنواع البيانات، بما في ذلك الأعداد الصحيحة، أرقام الفاصلة العشرية، الأحرف، النصوص والقيم المنطقية.
فيما يلي بعض الأمثلة على أنواع البيانات في لغة C++:
- int: يخزن قيم الأعداد الصحيحة.
- float: يخزن قيم الأعداد العشرية.
- char: يخزن حرف واحد.
- string: يخزن تسلسلاً من الأحرف.
- bool: يخزن قيمة منطقية (صواب True أو خطأ False)
عندما يتم الإعلان عن متغير، يجب أن يتم تعيين نوع بيانات له. يخبر هذا الـcompiler مقدار الذاكرة التي سيتم تخصيصها للمتغير ونوع البيانات التي يمكنه تخزينها.
على سبيل المثال، البرنامج التالي يعرف متغير اسمه my_string
والذي يمكنه تخزين نص:
string my_string;
الـ string
هو نوع بيانات خاص يُستخدم لتخزين تسلسلات من الأحرف.
مثال
فيما يلي مثال لكيفية استخدام المتغيرات وأنواع البيانات في لغة C++:
int my_integer = 10; float my_floating_point_number = 3.14159; char my_character = 'a'; string my_string = "Hello, world!"; bool my_boolean_value = true; // Print the values of the variables. std::cout << my_integer << std::endl; std::cout << my_floating_point_number << std::endl; std::cout << my_character << std::endl; std::cout << my_string << std::endl; std::cout << my_boolean_value << std::endl;
المخرجات:
10 3.14159 a Hello, world! true
المتغيرات وأنواع البيانات هي مفاهيم أساسية في لغة C++. ومن خلال فهم كيفية عملها، يمكنك كتابة كود أكثر كفاءة وفعالية.
Priorities & Calculations in C++
يحتوي C++ على مجموعة من قواعد الأولوية للعوامل الحسابية operators التي تحدد الترتيب الذي يتم به تقييم ترتيب التعبيرات الحسابية. يتم تقييم عوامل الحساب ذات الأولوية الأعلى قبل عوامل الحساب ذات الأولوية الأقل.
فيما يلي جدول لقواعد أولوية العوامل الحسابية في C++:
مجموعة العوامل الحسابية | العوامل الحسابية |
---|---|
الأقواس | () , [] , {} |
العمليات الحسابية الأحادية | + , - , ! , ~ , * , & , ++ , -- , sizeof , cast , type-name |
الضرب، القسمة ومعامل باقي القسمة | * , / , % |
الجمع والطرح | + , - |
Bitwise AND | & |
Bitwise OR | | |
Bitwise XOR | ^ |
إشارتي التساوي وعدم التساوي | == , != |
أقل من، أقل من أو يساوي، أكبر من، أكبر من أو يساوي | < , <= , > , >= |
"و" المنطقية AND | && |
"أو" المنطقية OR | || |
التعيينات Assignment | = , += , -= , *= , /= , %= , &= , ` |
العوامل الشرطية | ? : |
الفاصلة | , |
عند تقييم تعبير ما، يتم تقييم العوامل ذات الأولوية الأعلى أولاً، تليها العوامل ذات الأولوية الأقل.
فيما يلي بعض الأمثلة على كيفية عمل أولوية العوامل في C++:
// Evaluates to 13. int x = 5 * 2 + 3; // Evaluates to 11. int y = 5 + 2 * 3; // Evaluates to true. bool z = 5 < 10 && 10 > 5; // Evaluates to false. bool w = 5 > 10 || 10 < 5;
من المهم أن تكون على دراية بقواعد الأولويات عند كتابة كود C++. وإلا، فقد تحصل على نتائج غير متوقعة.
فيما يلي مثال آخر لكيفية استخدام قواعد الأولويات للتحكم في ترتيب التقريب:
// Evaluates to 16. int a = (5 + 3) * 2; // Evaluates to 11. int b = 5 + (3 * 2);
في التعبير الأول، عامل الجمع (+
) له أولوية أعلى من عامل الضرب (*
). ولذلك، تتم عملية الجمع أولا، ومن ثم عملية الضرب.
في التعبير الثاني، عملية الضرب لها أولوية أعلى من عملية الجمع. ولذلك، تتم عملية الضرب أولا.
من خلال فهم قواعد الأولويات للتعبيرات الحسابية والمنطقية المختلفة، يمكنك كتابة كود C++ أكثر دقة وفعالية.
Basic Arithmetic & Casting
العمليات الحسابية الأساسية في لغة C++
تقدم لغة C++ عددًا من العوامل التي تمثل العمليات الحسابية الأساسية. تشمل هذه العوامل:
- +: الجمع
- -: الطرح
- *: الضرب
- /: القسمة
- %: معامل القسمة (الباقي)
يمكن استخدام هذه العوامل لإجراء عمليات حسابية على متغيرات من أنواع البيانات المختلفة. على سبيل المثال، ينفذ البرنامج التالي عمليات حسابية على متغيرات من النوع int و float:
int a = 10; float b = 3.14159; // Add two integers. int c = a + 5; // Multiply two floats. float d = b * 2; // Divide two integers. float e = a / 2; // Calculate the remainder of a division operation. int f = a % 2;
يتم تخزين نتائج هذه العمليات في المتغيرات المقابلة.
التحويلات الحسابية Casting في لغة C++
التحويل Casting هو طريقة لتحويل قيمة من نوع بيانات إلى آخر. يمكن أن يكون هذا مفيدًا عندما تحتاج إلى إجراء عمليات حسابية على قيم أنواع البيانات المختلفة.
هناك نوعان من التحويلات Casting في لغة C++:
- التحويل الضمني: يحدث التحويل الضمني عندما يقوم المترجم تلقائيًا بتحويل قيمة من نوع بيانات إلى آخر. على سبيل المثال، يقوم البرنامج التالي ضمنيًا بتحويل قيمة المتغير
a
من النوع int إلى النوع float قبل إجراء عملية الضرب:
int a = 10; float b = 3.14159; // Multiply an int and a float. float c = a * b;
- التحويل الصريح: يحدث التحويل الصريح عندما يقوم المبرمج بتحويل قيمة بشكل صريح من نوع بيانات إلى آخر. يمكن القيام بذلك باستخدام العوامل التالية (
static_cast
,reinterpret_cast
,const_cast
، وdynamic_cast
). على سبيل المثال، يقوم البرنامج التالي بشكل صريح بتحويل قيمة المتغيرb
من النوع float إلى النوع int قبل إجراء عملية القسمة:
int a = 10; float b = 3.14159; // Divide a float by an int. int c = static_cast<int>(b) / a;
غالبًا ما يتم استخدام التحويل الصريح لمنع الأخطاء التي يمكن أن تحدث عند تحويل القيم ضمنيًا.
مثال
فيما يلي مثال لكيفية استخدام التحويل في C++:
// Convert a string to an integer. int a = static_cast<int>("10"); // Convert a floating-point number to a character. char b = static_cast<char>(3.14159); // Convert a pointer to an integer to a pointer to a float. float *c = reinterpret_cast<float*>(pInt); // Convert a const object to a non-const object. int *d = const_cast<int*>(pInt);
يمكن أن يكون التحويل أداة قوية لتحويل القيم بين أنواع البيانات المختلفة. ومع ذلك، فمن المهم استخدامه بعناية لتجنب الأخطاء.
Prefix and Postfix & Compound assignment
معاملات التشغيل للـPrefix والـPostfix في لغة C++
يتم استخدام عوامل الـPrefix والـPostfix لتعديل قيمة المتغير. يتم استخدام عامل الـPostfix قبل المتغير، في حين يتم استخدام عامل التشغيل postfix بعد المتغير.
يوضح الجدول التالي بعض عوامل تشغيل الـPrefix والـPostfix الأكثر شيوعًا في لغة C++:
معامل التشغيل | Prefix | Postfix |
---|---|---|
++ | زيادة قيمة المتغير بمقدار 1. | زيادة قيمة المتغير بمقدار 1، وإرجاع القيمة الأصلية. |
— | يقلل قيمة المتغير بمقدار 1. | إنقاص قيمة المتغير بمقدار 1، وإرجاع القيمة الأصلية. |
مثال
يوضح البرنامج التالي كيفية استخدام عوامل الPrefix والـPostfix لزيادة قيمة المتغير:
int a = 10; // Increment the value of a by 1 using the prefix operator. a++; // Increment the value of a by 1 using the postfix operator. int b = a++ // Print the values of a and b. std::cout << a << " " << b << std::endl;
المخرجات:
12 11
عوامل التعيين المركبة في C++
تقوم عوامل التعيين المركب بدمج العمليات الحسابية مع عمليات التعيين. وهذا يجعل من الممكن تعديل قيمة المتغير في عبارة واحدة.
يوضح الجدول التالي بعض عوامل التعيين المركبة الأكثر شيوعًا في لغة C++:
معامل التشغيل | الوصف | مثال |
---|---|---|
+= |
يضيف المعامل إلى المعامل الأيسر | x += 5; يعادل x = x + 5; |
-= |
يطرح المعامل من المعامل الأيسر | y -= 3; يعادل y = y - 3; |
*= |
ضرب المعامل بالمعامل الأيسر | z *= 2; يعادل z = z * 2; |
/= |
يقسم المعامل الأيسر على المعامل الأيمن | a /= 4; يعادل a = a / 4; |
%= |
يحسب معامل باقي قسمة المعامل الأيسر على المعامل الأيمن | b %= 7; يعادل b = b % 7; |
&= |
ينفذ عملية AND على المعامل الأيسر والمعامل الأيمن | c &= 6; يعادل c = c & 6; |
^= |
ينفذ عملية XOR للبت على المعامل الأيسر والمعامل الأيمن | e ^= 8; يعادل e = e ^ 8; |
<<= |
يقوم بإزاحة المعامل الأيسر إلى اليسار بمقدار عدد البتات bits المحددة بواسطة المعامل | f <<= 3; يعادل f = f << 3; |
>>= |
إزاحة المعامل الأيسر إلى اليمين بعدد البتات bits المحددة بواسطة المعامل | g >>= 2; يعادل g = g >> 2; |
مثال
يوضح البرنامج التالي كيفية استخدام عوامل التعيين المركبة لتعديل قيمة المتغير:
int a = 10; // Increment the value of a by 1 using the compound assignment operator. a += 1; // Print the value of a. std::cout << a << std::endl;
المخرجات:
11
يمكن أن تكون عوامل التعيين المركبة أداة قوية لكتابة كود C++ موجز وفعال.
النطاق المتغير (المحلي والعالمي)
النطاق المتغير هو مفهوم في البرمجة يحدد المكان الذي يمكن الوصول منه إلى المتغير في البرنامج. هناك نوعان من النطاق المتغير في لغة C++: محلي وعالمي.
- المتغيرات المحلية Local: يتم الإعلان عن المتغيرات المحلية داخل دالة أو مجموعة من التعليمات البرمجية. ولا يمكن الوصول إليها إلا من داخل الدالة أو مجموعة التعليمات البرمجية التي تم الإعلان عنها.
- المتغيرات العالمية Global: يتم الإعلان عن المتغيرات العامة خارج أي دالة أو مجموعة من التعليمات البرمجية. ويمكن الوصول إليها من أي مكان في البرنامج.
مثال على نطاق المتغير المحلي:
int main() { int local_variable = 10; // local_variable can only be accessed from within this function. std::cout << local_variable << std::endl; // This code will cause an error because local_variable is not accessible here. std::cout << global_variable << std::endl; return 0; }
مثال على نطاق المتغير العالمي:
int global_variable = 20; int main() { // global_variable can be accessed from anywhere in the program. std::cout << global_variable << std::endl; // This code is also valid. int local_variable = global_variable; return 0; }
من المهم أن تكون على دراية بالنطاق المتغير عند كتابة كود C++. بخلاف ذلك، قد تصل عن طريق الخطأ إلى متغير غير محدد أو ليس من المفترض أن تصل إليه.
فيما يلي بعض القواعد العامة للنطاق المتغير في C++:
- يتم تحديد نطاق المتغيرات المحلية حسب الـFunction أو مجموعة التعليمات البرمجية التي تم الإعلان عنها.
- يتم تحديد نطاق المتغيرات العالمية للبرنامج بأكمله.
- يمكن للمتغيرات المعلنة في دالة Function أن تظلل المتغيرات المعلنة في النطاق العام.
- يمكن للمتغيرات المعلنة في مجموعة من التعليمات البرمجية أن تظلل المتغيرات المعلنة في نطاق الوظيفة.
من خلال فهم النطاق المتغير، يمكنك كتابة كود C++ أكثر كفاءة وموثوقية.
Selection Statement - if Statement
تُستخدم الجمل الشرطيّة في لغة C++ للتحكم في تدفق البرنامج. إنها تسمح لك بتنفيذ مجموعات مختلفة من التعليمات البرمجية بناءً على شروط مختلفة.
هناك نوعان رئيسيان للجمل الشطريّة في لغة C++ هما:
- جملة if الشرطيّة: الـ
if
تسمح بتنفيذ مجموعة من التعليمات البرمجية إذا كان الشرط صحيحًا. - جملة switch: الـ
switch
تسمح بتحديد واحدة من مجموعات التعليمات البرمجية المتعددة لتنفيذها بناءً على قيمة التعبير.
في هذا القسم سوف نعرف المزيد عن جملة if الشرطيّة.
المعاملات المنطقية
تُستخدم العوامل المنطقية في لغة C++ لدمج أو تعديل التعبيرات المنطقية. إنها تسمح لك بإنشاء عبارات شرطية أكثر تعقيدًا.
العوامل المنطقية الثلاثة في C++ هي:
- And (&&): تكون نتيجته صحيحة إذا كان كلا المعاملين صحيحين. وإلا فإنها ترجع نتيجة خاطئة.
- Or (||): تكون نتيجته صحيحة إذا كان أحد المعاملين صحيح. وإلا فإنها ترجع نتيجة خاطئة.
- Not (!): ينفي قيمة المعامل الخاص به. إذا كان المعامل صحيحًا، فإنه يُرجع خطأ. وإلا فإنه يعود صحيحا.
يمكن استخدام العوامل المنطقية لإنشاء عبارات شرطية أكثر تعقيدًا. على سبيل المثال، يستخدم البرنامج التالي المعامل AND &&
للتحقق مما إذا كان الرقم موجودًا في نطاق معين من الأرقام أم لا:
#include <iostream> using namespace std; int main() { int x = 0; cout << "Enter a number"; cin >> x; if (x >= 1 && x <= 100) cout << "Ok" << endl; else cout << "Out of range" << endl; return 0; }
مثال إضافي:
#include <iostream> using namespace std; int main() { int a = 0; cout << "Enter your age"; cin >> a; char g = '\0'; cout << "Enter your gender"; cin >> g; if (a < 18 && g == 'm') cout << "male, " << "young boy" << endl; else if (a >= 18 && g == 'm') cout << "male," << "grown up man" << endl; else if (a < 18 && g == 'f') cout << "female," << "young girl" << endl; else cout << "female," << "grown up girl" << endl; return 0; }
ملاحظة: \0
في لغة C++ يكون قيمةً فارغة NULL. إنه قيمة تم ضبط جميع البتات bits فيها على الصفر. يتم استخدامه لوضع علامة على نهاية النص في C++.
البرنامج التالي يستخدم معامل ||
للتحقق مما إذا كان الرقم أكبر من 10 أو أقل من 5:
#include <iostream> using namespace std; int main() { int number = 3; if (number > 10 || number < 5) cout << "The number is greater than 10 or less than 5." << endl; return 0; }
البرنامج التالي يستخدم معامل !
لنفي قيمة التعبير المنطقي number > 10
:
#include <iostream> using namespace std; int main() { int number = 3; if (! (number > 10)) cout << "The number is not greater than 10." << endl; return 0; }
يمكن استخدام العوامل المنطقية لإنشاء عبارات شرطية معقدة للغاية. ومع ذلك، من المهم استخدامها بعناية لتجنب جعل البرنامج صعب القراءة والصيانة.
فيما يلي بعض القواعد العامة لاستخدام العوامل المنطقية:
- يمكن استخدام العوامل المنطقية لدمج التعبيرات المنطقية لإنشاء عبارات شرطية أكثر تعقيدًا.
- الـ
&&
يُرجع صحيحًا إذا كان كلا المعاملين صحيحين. وإلا فإنها ترجع قيمة خاطئة. - الـ
||
يُرجع صحيحًا إذا كان أحد المعاملين صحيح القيمة. وإلا فإنه يرجع قيمة خاطئة. - الـ
!
ينفي قيمة المعامل الخاص به. إذا كان المعامل صحيحًا، فإنه يُرجع خطأ. وإلا فإنه يعود صحيحا. - يمكن استخدام العوامل المنطقية في التعبيرات المتداخلة.
من خلال فهم كيفية استخدام العوامل المنطقية، يمكنك كتابة كود C++ أكثر كفاءة وموثوقية.
الجمل الشرطية - جملة switch
تُستخدم الجمل الشرطيّة في لغة C++ للتحكم في تدفق البرنامج. إنها تسمح لك بتنفيذ مجموعات مختلفة من التعليمات البرمجية بناءً على شروط مختلفة.
هناك نوعان رئيسيان للجمل الشطريّة في لغة C++ هما:
- جملة if الشرطيّة: الـ
if
تسمح بتنفيذ مجموعة من التعليمات البرمجية إذا كان الشرط صحيحًا. - جملة switch: الـ
switch
تسمح بتحديد واحدة من مجموعات التعليمات البرمجية المتعددة لتنفيذها بناءً على قيمة التعبير.
قمنا بشرح جملة if الشرطيّة تحت العنوانالجمل الشرطيّة - جملة if الشرطيّةوالآن سنشرح المزيد عن "جملة switch الشرطيّة".
جملة switch في لغة C++ تتحكم في السماح بتنفيذ مجموعات مختلفة من التعليمات البرمجية بناءً على قيمة موجودة في تعبير حسابي أو منطقي. بناء الجملة العام لجملة switch هو كما يلي:
switch (expression) { case value1: // code to execute if expression equals value1 case value2: // code to execute if expression equals value2 ... default: // code to execute if expression does not equal any of the values in the case statements }
الـ expression
يمكن أن يكون أي تعبير صحيح. حيث تقوم جملة switch بتقييمه الـ( expression
) ومقارنته بالقيمة المذكورة في جملة الحالة case
حيث إذا كان expression
يساوي واحدة من القيم الموجودة في جمل الحالة case
سيتم تنفيذ مجموعة التعليمات البرمجية الموجودة داخل جملة الحالة case
. أما إذا كان الـ expression
لا يساوي أي من القيم الموجودة في جمل الحالة case
، فإن مجموعة التعليمات البرمجية داخل الـ default
سيتم تنفيذها.
فيما يلي مثال لجملة switch:
#include <iostream> using namespace std; int main() { int x = 0; cin >> x; switch (x) { case 1: cout << "case #1" << endl; break; case 2: cout << "case #2" << endl; break; case 3: cout << "case #3" << endl; break; default: cout << "Out of range" << endl; break; } return 0; }
يمكنك أيضًا استخدام الأحرف بدلاً من الأرقام في جملة switch. على سبيل المثال:
#include <iostream> using namespace std; int main() { char c = 'a'; switch (c) { case 'a': cout << "case #a" << endl; break; case 'b': cout << "case #b" << endl; break; default: cout << "Out of range" << endl; break; } return 0; }
المخرجات:
case #a
تذكر!
إن استخدام حرف كبير مثل "A" في المثال السابق لا يشبه استخدام حرف صغير مثل "a". انهم ليسو نفس الشيء!
مميزات إستخدام جملة switch:
يمكن أن تكون جمل switch أكثر كفاءة من استخدام سلسلة من جمل if، خاصة عندما يكون هناك العديد من الحالات المختلفة. يمكن لجمل switch أيضًا أن تجعل التعليمات البرمجية الخاصة بك أكثر قابلية للقراءة والصيانة.
الخلاصة
تعد جملة switch أداة قوية للتحكم في تدفق برنامج C++ الخاص بك. ومن خلال فهم كيفية استخدامها، يمكنك كتابة تعليمات برمجية أكثر كفاءة وموثوقية.
جمل التكرار (الدوران)
جمل التكرار في لغة C++ تتحكم بالسماح لك بتنفيذ مجموعة من التعليمات البرمجية بشكل متكرر حتى يتم استيفاء شرط معين. هناك ثلاثة أنواع من جمل التكرار في لغة C++:
- جملة التكرار while: تنفذ جملة التكرار while مجموعة من التعليمات البرمجية بشكل متكرر عندما يكون الشرط صحيحًا.
- جملة التكرار do-while: تنفذ جملة تكرار do-while مجموعة من التعليمات البرمجية مرة واحدة على الأقل، ثم تقوم بتقييم الشرط. إذا كان الشرط صحيحا، فسيتم تنفيذ مجموعة التعليمات البرمجية مرة أخرى.
- جملة التكرار for: تنفذ جملة تكرار for كتلة من التعليمات البرمجية لعدد محدد من المرات.
تعد جمل التكرار أداة قوية للتحكم في تدفق برنامج C++. ويمكن استخدامها لحل مجموعة متنوعة من المشاكل، مثل:
- تكرار الدوران على مجموعة من البيانات
- أداء مهمة لعدد معين من المرات
- انتظار حدوث حدث معين
أمثلة على كسر الحلقة Break وإستمرار الحلقة Continue
break و continue عبارة عن أداتين يتم إستخدامهما للتحكم في التدفق في لغة C++ يمكن استخدامهما للتحكّم في حلقة الدوران والتلاعب بها.
break تنهي حلقة الدوران على الفور ودون قيد أو شرط.
continue تتخطى بقية الأوامر الموجودة في حلقة الدوران الحالية وتتسبب في استمرار الحلقة مع الدورة التالية.
فيما يلي مثال لكيفية استخدام continue في C++:
#include <iostream> using namespace std; int main() { for (size_t i = 0; i < 10; i++) { if (i == 5) continue; cout << "i = " << i << endl; } return 0; }
هذا البرنامج هو عبارة عن حلقة دوران for تقوم بطباعة قيمة المتغير i
على لوحة المخرجات، تكون القيم حصريًّا من 0 إلى 9 بإستثناء الرقم 5. الجملة الشرطيّة if
داخل الحلقة تتحقق ما إذا كانت قيمة المتغير i
تساوي 5. إذا كانت كذلك، فإن الأمر بالإستمرارية continue
يتخطى باقي محتويات التكرار في الحلقة ويستمر في الإنتقال إلى التكرار التالي.
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يقوم البرنامج بتعريف المتغير
i
وإعطاؤه القيمة البدائية 0. - يبدأ البرنامج بحلقة الدوران التي ستتكرر 10 مرات.
- في داخل حلقة التكرار for يقوم البرنامج بالتحقق ما إذا كانت قيمة المتغير
i
تساوي 5. - إذا كانت قيمة المتغير
i
تساوي 5، سيقوم البرنامج بتخطي ما تبقى من الأوامر الموجودة في حلقة التكرار وينتقل إلى التكرار التالي. بإستخدامcontinue
. - إذا لم تكن كذلك، سيقوم البرنامج بطباعة قيمة المتغير
i
إلى شاشة المخرجات. - يقوم البرنامج بزيادة قيمة المتغير
i
بمقدار 1. - تستمر حلقة التكرار بالدوران وإعادة الخطوات من 3 إلى 6 حتى تصل إلى 10 مرات ثم تتوقف.
تكون مخرجات البرنامج كالتالي:
i = 0 i = 1 i = 2 i = 3 i = 4 i = 6 i = 7 i = 8 i = 9
هنا مثال عن كيفية إستخدام كسر الحلقة break في لغة C++:
#include <iostream> using namespace std; int main() { for (size_t i = 0; i < 10; i++) { if (i == 4) break; cout << "i = " << i << endl; } return 0; }
هذا البرنامج هو عبارة عن حلقة دوران for تقوم بطباعة قيمة المتغير i
إلى لوحة المخرجات، من 0 إلى 3 حصريًّا. الجملة الشرطية if
داخل الحلقة تتحقق ما إذا كانت قيمة المتغير i
تساوي 4. إذا كانت كذلك، فإن أداة كسر الحلقة break
تقوم بإنهاء دوران الحلقة مباشرة وبدون أي شرط.
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يقوم البرنامج بتعريف المتغير
i
وإعطاؤه القيمة البدائية 0. - يبدأ البرنامج بحلقة الدوران التي ستتكرر 10 مرات.
- في داخل حلقة التكرار for يقوم البرنامج بالتحقق ما إذا كانت قيمة المتغير
i
تساوي 4. - إذا كانت قيمة المتغير
i
تساوي 4، فإنbreak
تقوم بإنهاء دوران الحلقة مباشرة وبدون أي شرط. - إذا لم تكن كذلك، سيقوم البرنامج بطباعة قيمة المتغير
i
إلى شاشة المخرجات. - يقوم البرنامج بزيادة قيمة المتغير
i
بمقدار 1. - تستمر حلقة التكرار بالدوران وإعادة الخطوات من 3 إلى 6 حتى تصل إلى 10 مرات ثم تتوقف بإستخدام
break
.
في هذه الحالة، ستتوقف حلقة التكرار من خلال الأمر break
عندما تكون i
تساوي 4. لذلك، مخرجات البرنامج تكون كالتالي:
i = 0 i = 1 i = 2 i = 3
أيهما نستخدم؟
يعتمد هذا على ما نريد تحقيقه من البرنامج. إذا أردنا إيقاف حلقة التكرار بشكل مباشر، نستخدم break
. أمّا إذا أردنا من البرنامج تخطي بقية التكرار الحالي والإستمرار لدورة التكرار التالية، نستخدم continue
.
فيما يلي بعض النصائح العامة لاستخدام break وcontinue:
- إستخدم
break
بشكل مقتصد. قد يؤدي إنهاء الحلقة مبكرًا إلى صعوبة في قراءة التعليمات البرمجية الخاصة بك وفهمها. - إستخدم
continue
لتجنب كتابة التعليمات البرمجية المكررة. إذا وجدت نفسك تكتب نفس الكود داخل حلقة عدة مرات، ففكر في استخدامcontinue
. - إستخدم
break
وcontinue
لجعل التعليمات البرمجية الخاصة بك أكثر كفاءة. على سبيل المثال، يمكنك استخدامbreak
لإنهاء حلقة مبكرًا إذا كنت قد عثرت بالفعل على ما تبحث عنه.
الحلقة المتداخلة
الحلقة المتداخلة هي حلقة دوران موجودة داخل حلقة دوران أخرى. هذا يعني أن الحلقة الداخلية سوف تتكرر مع كل تكرار للحلقة الخارجية.
فيما يلي مثال على حلقة متداخلة في لغة C++:
#include <iostream> using namespace std; int main() { for ( size_t i = 1; i <= 5; i++) { for ( size_t j = 1; j <= 6; j++) { cout << "*"; } cout << endl; } return 0; }
هذا البرنامج يحتوي على حلقة متداخلة تطبع مستطيلاً من النجوم إلى شاشة المخرجات. تتكرر الحلقة الخارجية 5 مرات، والحلقة الداخلية تتكرر 6 مرات. هذا يعني أنه سيتم تنفيذ الكود الموجود داخل الحلقة الداخلية 30 مرة.
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يعرّف البرنامج متغيرين إثنين
i
وj
ويعطيهما قيمة بدائية تساوي 1. - يبدأ البرنامج بحلقة الدوران الخارجية for.
- الشرط في الحلقة الخارجية يتحقق إذا كانت قيمة المتغير
i
أقل أو يساوي 5. إذا كانت كذلك، يتم الإنتقال إلى حلقة الدوران الداخلية. - الشرط في الحلقة الداخلية يتأكد ما إذا كانت قيمة المتغير
j
أقل أو تساوي 6. إذا كانت كذلك، يطبع البرنامج نجمة إلى لوحة المخرجات. - الحلقة الداخلية تقوم بزيادة قيمة المتغير
j
بمقدار 1. - تقوم الحلقة الداخلية بتكرار الخطوات 4 إلى 5 حتى تصبح قيمة المتغير
j
أكبر من 6. - بعد انتهاء حلقة for الداخلية، يقوم البرنامج بطباعة حرف السطر الجديد إلى شاشة المخرجات.
- حلقة for الخارجية تقوم بزيادة قيمة المتغير
i
بمقدار 1. - تقوم الحلقة الخارجية بتكرار الخطوات 3 إلى 8 حتى تصبح قيمة المتغير
i
أكبر من 5:
مخرجات البرنامج تكون كالتالي:
****** ****** ****** ****** ******
يمكن استخدام الحلقات المتداخلة لحل مجموعة متنوعة من المشكلات. على سبيل المثال، يمكنك استخدام حلقات متداخلة للتكرار على جميع عناصر مصفوفة ثنائية الأبعاد، أو لإنشاء جميع المجموعات الممكنة لمجموعتين من القيم.
فيما يلي بعض النصائح لاستخدام الحلقات المتداخلة:
- استخدم الحلقات المتداخلة بشكل مقتصد. يمكن أن تجعل الحلقات المتداخلة التعليمات البرمجية الخاصة بك أكثر صعوبة في القراءة والفهم.
- استخدم الحلقات المتداخلة لتجنب كتابة تعليمات برمجية مكررة. إذا وجدت نفسك تكتب نفس الكود داخل حلقة عدة مرات، فكر في استخدام حلقات متداخلة.
- استخدم الحلقات المتداخلة لجعل التعليمات البرمجية الخاصة بك أكثر كفاءة. على سبيل المثال، يمكنك استخدام حلقات متداخلة لتجنب البحث عن عنصر في مصفوفة عدة مرات.
رسم الأشكال (المثلثات)
يمكنك استخدام منطق الحلقات المتداخلة لإنشاء أشكال مختلفة، على سبيل المثال يمكنك رسم مثلث.
هذا البرنامج يحتوي على حلقة for متداخلة تطبع مثلثًا من النجوم إلى شاشة المخرجات. تتكرر الحلقة الخارجية 5 مرات، وتتكرر الحلقة الداخلية لكل تكرار للحلقة الخارجية.
#include <iostream> using namespace std; int main() { for ( size_t i = 1; i <= 5; i++) { for ( size_t j = 1; j <= i; j++) { cout << "*"; } cout << endl; } return 0; }
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يعرّف البرنامج متغيرين إثنين
i
وj
ويعطيهما قيمة بدائية تساوي 1. - يبدأ البرنامج بحلقة الدوران الخارجية for.
- الشرط في الحلقة الخارجية يتحقق إذا كانت قيمة المتغير
i
أقل أو يساوي 5. إذا كانت كذلك، يتم الإنتقال إلى حلقة الدوران الداخلية. - الشرط في الحلقة الداخلية يتأكد ما إذا كانت قيمة المتغير
j
أقل أو تساوي قيمة المتغيرi
.إذا كانت كذلك، يقوم البرنامج بطباعة نجمة إلى شاشة المخرجات. - الحلقة الداخلية تقوم بزيادة قيمة المتغير
j
بمقدار 1. - تقوم الحلقة الداخلية بتكرار الخطوات 4 إلى 5 حتى تصبح قيمة المتغير
j
أكبر من قيمة المتغيرi
. - بعد انتهاء حلقة for الداخلية، يقوم البرنامج بطباعة حرف السطر الجديد إلى شاشة المخرجات.
- حلقة for الخارجية تقوم بزيادة قيمة المتغير
i
بمقدار 1. - تقوم الحلقة الخارجية بتكرار الخطوات 3 إلى 8 حتى تصبح قيمة المتغير
i
أكبر من 5:
مخرجات البرنامج تكون كالتالي:
* ** *** **** *****
ماذا لو أردنا رسم المثلث السابق بشكل مقلوب؟
نغير الشرط الموجود في حلقة for الخارجية.
هذا البرنامج يحتوي على حلقة متداخلة تطبع مثلثًا مقلوبًا من النجوم إلى شاشة المخرجات. تتكرر الحلقة الخارجية تنازليًّا من 5 إلى 1، وتتكرر الحلقة الداخلية لكل تكرار للحلقة الخارجية.
#include <iostream> using namespace std; int main() { for ( size_t i = 5; i >= 1; i--) { for ( size_t j = 1; j <= i; j++) { cout << "*"; } cout << endl; } return 0; }
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يعرّف البرنامج متغيرين إثنين
i
وj
ويعطيهما قيمًا بدائية 5 و 1 على التوالي. - يبدأ البرنامج بحلقة الدوران الخارجية for.
- الشرط في الحلقة الخارجية يتحقق إذا كانت قيمة المتغير
i
أكبر أو تساوي 1. إذا كانت كذلك، ينتقل إلى حلقة for الداخلية. - الشرط في الحلقة الداخلية يتأكد ما إذا كانت قيمة المتغير
j
أقل أو تساوي قيمة المتغيرi
.إذا كانت كذلك، يقوم البرنامج بطباعة نجمة إلى شاشة المخرجات. - الحلقة الداخلية تقوم بزيادة قيمة المتغير
j
بمقدار 1. - تقوم الحلقة الداخلية بتكرار الخطوات 4 إلى 5 حتى تصبح قيمة المتغير
j
أكبر من قيمة المتغيرi
. - بعد انتهاء حلقة for الداخلية، يقوم البرنامج بطباعة حرف السطر الجديد إلى شاشة المخرجات.
- حلقة for الخارجية تنقّص من قيمة المتغير
i
بمقدار 1. - تقوم الحلقة الخارجية بتكرار الخطوات 3 إلى 8 حتى تصبح قيمة المتغير
i
أقل من 1.
مخرجات البرنامج تكون كالتالي:
***** **** *** ** *
هل نستطيع جعل الرسمة أكثر صعوبة؟
يطبع هذا الرمز مثلثًا قائمًا من النجوم إلى شاشة المخرجات. تتكرر الحلقة الخارجية من 1 إلى 5، وتتكرر الحلقة الداخلية من 4 إلى قيمة المتغير i
حيث يكون i
هو رقم التكرار الحالي في حلقة الدوران الخارجية.
#include <iostream> using namespace std; int main() { for ( size_t i = 1; i <= 5; i++) { for ( size_t j = 4; j >= i; j--) { cout << " "; } for (size_t k = 1; k <= i; k++) { cout << "*"; } cout << endl; } return 0; }
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- هذا البرنامج يُعرّف 3 متغيرات
i
,j
، وk
ويعطيهم القيم البدائية 1، 4 و 1، على التوالي. - يبدأ البرنامج بحلقة الدوران الخارجية for.
- الشرط في الحلقة الخارجية يتحقق إذا كانت قيمة المتغير
i
أقل أو يساوي 5. إذا كانت كذلك، يتم الإنتقال إلى حلقة الدوران الداخلية. - الشرط في الحلقة الداخلية يتأكد ما إذا كانت قيمة المتغير
j
أكبر أو تساويi
، يقوم البرنامج بطباعة مساحة إلى شاشة المخرجات. - ثم تتناقص قيمة المتغير في الحلقة الداخلية
j
بمقدار 1. - تقوم الحلقة الداخلية بتكرار الخطوات 4 إلى 5 حتى تصبح قيمة المتغير
j
أقل منi
. - بعد انتهاء حلقة for الداخلية، يقوم البرنامج بطباعة نجمة على شاشة المخرجات لكل تكرار للحلقة الداخلية.
- يقوم البرنامج بعد ذلك بطباعة حرف السطر الجديد إلى شاشة المخرجات.
- حلقة for الخارجية تقوم بزيادة قيمة المتغير
i
بمقدار 1. - تقوم الحلقة الخارجية بتكرار الخطوات 3 إلى 9 حتى تصبح قيمة المتغير
i
أكبر من 5:
مخرجات البرنامج تكون كالتالي:
* ** *** **** *****
دعونا نطبعه رأسًا على عقب!
#include <iostream> using namespace std; int main() { for ( size_t i = 5; i >= 1; i--) { for ( size_t j = 4; j >= i; j--) { cout << " "; } for (size_t k = 1; k <= i; k++) { cout << "*"; } cout << endl; } return 0; }
انظروا إلى ما لدينا هنا!
***** **** *** ** *
لنرسم مثلثًا مختلفًا
#include <iostream> using namespace std; int main() { int e = 1; for (int a = 1; a <= 5; a++) { for (int b = 4; b >= a; b--) { cout << " "; } for (int c = 0; c < e; c++) { cout << "*"; } cout << endl; e = e + 2; } return 0; }
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يقوم البرنامج بتعريف 4 متغيرات
e
,a, c
، وb
ويعطيهم القيم البدائية 1، 1، 0 و 4، على التوالي. - يبدأ البرنامج بحلقة الدوران الخارجية for.
- الشرط في الحلقة الخارجية يتحقق إذا كانت قيمة المتغير
a
أقل أو يساوي 5. إذا كانت كذلك، يتم الإنتقال إلى حلقة الدوران الداخلية. - الشرط في الحلقة الداخلية يتأكد ما إذا كانت قيمة المتغير
b
أكبر أو تساويa
، يقوم البرنامج بطباعة مساحة إلى شاشة المخرجات. - ثم تتناقص قيمة المتغير في الحلقة الداخلية
b
بمقدار 1. - تقوم الحلقة الداخلية بتكرار الخطوات 4 إلى 5 حتى تصبح قيمة المتغير
b
أقل منa
. - بعد انتهاء حلقة for الداخلية، يقوم البرنامج بطباعة نجمة على شاشة المخرجات لكل تكرار للحلقة الداخلية.
- يقوم البرنامج بعد ذلك بطباعة حرف السطر الجديد إلى شاشة المخرجات.
- حلقة for الخارجية تقوم بزيادة قيمة المتغير
a
بمقدار 1. - تقوم الحلقة الخارجية بتكرار الخطوات 3 إلى 9 حتى تصبح قيمة المتغير
a
أكبر من 5:
مخرجات البرنامج تكون كالتالي:
* *** ***** ******* *********
دعونا نطبعه رأسًا على عقب!
#include <iostream> using namespace std; int main() { int e = 9; for (int r = 1; r <= 5; r++) { for (int c = 0; c < e; c++) { cout << "*"; } cout << endl; e = e - 2; for (int s = 0; s < r; s++) { cout << " "; } } return 0; }
المخرجات:
********* ******* ***** *** *
يمكنك أيضًا استخدام الحلقات المتداخلة لرسم أشكال أخرى، مثل المربعات والمستطيلات والمعينات.
رسم الأشكال (مربع)، رسم أي حرف مثلا:( X,Z,N,E,F)
تعد الحلقات المتداخلة في لغة C++ أداة قوية لرسم أشكال مختلفة إلى شاشة المخرجات. باستخدام الحلقات المتداخلة، يمكننا التكرار على أبعاد متعددة وطباعة أحرف مختلفة لإنشاء أنماط مختلفة.
مثال 1:
#include <iostream> using namespace std; int main() { for (size_t i = 1; i <= 10; i++) { for (size_t j = 1; j <= 10; j++) { if (i == 1 || i == 10) cout << "*"; else if (j == 1 || j == 10) cout << "*"; else cout << " "; } cout << endl; } return 0; }
يطبع هذا الرمز مربعًا مجوفًا من النجوم إلى شاشة المخرجات. تتكرر حلقة for الخارجية من 1 إلى 10 مرات، وتتكرر حلقة for الداخلية من 1 إلى 10مرات.
الـ if
داخل الحلقة الداخلية تتحقق مما إذا كانت قيمة i
تساوي 1 أو 10، أو إذا كانت قيمة j
تساوي 1 أو 10. إذا تم استيفاء أي من هذه الشروط، يقوم البرنامج بطباعة نجمة إلى شاشة المخرجات وبخلاف ذلك، يقوم البرنامج بطباعة مسافة.
فيما يلي شرح خطوة بخطوة لما يحدث عند تشغيل هذا البرنامج:
- يعرّف البرنامج متغيرين إثنين
i
وj
ويعطيهما قيمة بدائية تساوي 1. - يبدأ البرنامج بحلقة الدوران الخارجية for.
- الشرط في الحلقة الخارجية يتحقق إذا كانت قيمة المتغير
i
أقل أو تساوي 10. إذا كانت كذلك، ينتقل إلى الحلقة الداخلية. - الشرط في الحلقة الداخلية يتأكد ما إذا كانت قيمة المتغير
j
أقل أو تساوي 10. إذا كانت كذلك، يطبع البرنامج نجمة إلى شاشة المخرجات إذا كانت قيمةi
تساوي 1 أو 10، أو إذا كانت قيمةj
تساوي 1 أو 10. وبخلاف ذلك، يقوم البرنامج بطباعة مساحة إلى شاشة المخرجات. - الحلقة الداخلية تقوم بزيادة قيمة المتغير
j
بمقدار 1. - تقوم الحلقة الداخلية بتكرار الخطوات 4 إلى 5 حتى تصبح قيمة المتغير
j
أكبر من 10. - بعد انتهاء حلقة for الداخلية، يقوم البرنامج بطباعة حرف السطر الجديد إلى شاشة المخرجات.
- حلقة for الخارجية تقوم بزيادة قيمة المتغير
i
بمقدار 1. - تقوم الحلقة الخارجية بتكرار الخطوات 3 إلى 8 حتى تصبح قيمة المتغير
i
أكبر من 10.
مخرجات البرنامج تكون كالتالي:
********** * * * * * * * * * * * * * * * * **********
مثال 2:
إذا أردنا طباعة حرف مثل X إلى شاشة المخرجات، نتوقع الشكل التالي:
* * * * * * * * *
نحن بحاجة إلى وضع أساس للمنطق المُستخدم.
لكي نفهم كيف يمكننا وضع النجوم كما في الشكل السابق، دعونا نعرضها بهذه الطريقة:
/* 1 2 3 4 5 1* * * * * 2* * * * * 3* * * * * 4* * * * * 5* * * * * */
الآن دعونا نفكر في هذا الشكل، عندما يلتقي العمود بالصف نحصل على نجمة، لذلك نحتاج إلى حلقة تمر عبر جميع نقاط الالتقاء بين كل صف وعمود ونحتاج إلى شرط if لتحديد مواقعنا الدقيقة لصنع خطين قطريين متقاطعين ليشكلا شكل حرف X.
كل هذا ممثل في الكود التالي:
#include <iostream> using namespace std; int main() { for (size_t i = 1; i <= 5; i++) { for (size_t j = 1; j <= 5; j++) { if (i == j || j == 6-i) cout << "*"; else cout << " "; } cout << endl; } return 0; }
مثال 3:
من خلال فهم كيف تصطف النجوم هنا:
/* 1 2 3 4 5 1* * * * * 2* * * * * 3* * * * * 4* * * * * 5* * * * * */
يمكننا التفكير في طرق متعددة للتعامل مع الكود الخاص بنا لإنتاج حروف وأشكال مختلفة.
إنظر إلى هذا البرنامج:
#include <iostream> using namespace std; int main() { for (size_t i = 1; i <= 5; i++) { for (size_t j = 1; j <= 5; j++) { if (j == (6) - i || i == 1 || i == 5) cout << "*"; else cout << " "; } cout << endl; } return 0; }
خمّن ما هذا الشكل؟!
المخرجات:
***** * * * *****
مثال 4:
الآن دعونا نجعل شكلنا أكثر تعقيدًا
********** ** ** * * * * * * * * * ** * * ** * * * * * * * * * ** ** **********
من خلال تطبيق هذا البرنامج الذي لا يختلف عن الأمثلة السابقة نحصل على الشكل الأخير.
#include <iostream> using namespace std; int main() { for (size_t i = 1; i <= 10; i++) { for (size_t j = 1; j <= 10; j++) { if (i == j || j == 11 - i || i == 1 || i == 10 || j == 1 || j == 10) cout << "*"; else cout << " "; } cout << endl; } return 0; }
الدّالّة (1)
الدّالّة في لغة C++ هي مجموعة من التعليمات البرمجية التي تؤدي مهمة محددة. يمكن استخدام الدّوال لتحقيق العمليات الشائعة، مما يجعل التعليمات البرمجية الخاصة بك أكثر قابلية لإعادة الاستخدام وأسهل للفهم. يمكن أيضًا استخدام الدّوال لتحسين أداء البرنامج الخاص بك عن طريق تجنب تكرار الأوامر.
لإنشاء دالّة في C++، يمكنك استخدام بناء الجملة التالي:
return_type function_name(parameter_list) { // Function body }
الـ return_type
تحدد نوع البيانات التي ستعيدها الدالّة. أمّا الـ function_name
هي إسم الدالّة. الـ parameter_list
هي مجموعة المتغيرات التي ستقبلها الدالّة. الـ function_body
هي مجموعة التعليمات البرمجيّة التي ستنفذها الدالّة.
فيما يلي مثال لدالّة بسيطة في لغة C++:
int sum(int x, int y) { int s = 0; s = x + y; return s; }
تأخذ هذه الدالة متغيرين صحيحين، x وy، وتقوم بإرجاع مجموعهما.
لاستدعاء دالة، ما عليك سوى استخدام اسمها متبوعًا بزوج من الأقواس. على سبيل المثال، لإستدعاء دالّة المجموع sum()
، ستكتب:
int s = sum(10, 20);
سيؤدي هذا إلى تعيين القيمة 30 للمتغير s.
يمكن كتابة البرنامج بهذه الطريقة:
#include <iostream> using namespace std; int sum(int x, int y) { int s = 0; s = x + y; return s; } int main() { int s = 0; s = sum(10, 20); cout << "sum = " << s << endl; return 0; }
يأخذ المثال التالي متغيرين عشريين ويرجع مجموعهما:
#include <iostream> using namespace std; float sum(float x, float y) { float s = 0; s = x + y; return s; } int main() { float s = 0; s = sum(20.5, 30.6); cout << "sum = " << s << endl; return 0; }
المخرجات:
sum = 51.1
أهمية إستخدام الإرجاع return:
نحتاج إلى كتابة إرجاع return في دالة في لغة C++ لتحديد القيمة التي سترجعها الدالة إلى دالة الاستدعاء. هذا مهم لأنه يسمح لنا باستخدام الدالة لإجراء العمليات الحسابية ثم استخدام نتيجة تلك الحسابات في البرنامج الخاص بنا.
بدون إستخدام الإرجاع return
، لن تتمكن الدالّة من إرجاع أي قيمة إلى دالّة الاستدعاء. وهذا يعني أننا لن نتمكن من استخدام الدالّة لإجراء العمليات الحسابية ثم استخدام نتيجة تلك الحسابات في البرنامج الخاص بنا.
من خلال إستخدام الإرجاع return
يمكننا إنهاء تنفيذ الدالّة مبكرًا وتجنب الخطأ. وهذا يجعل البرنامج الخاص بنا أكثر قوة وموثوقية.
بشكل عام، الإرجاع return
يعد جزءًا مهمًا من برمجة C++. إذ يسمح لنا بإرجاع القيم من الدوالّ وإنهاء تنفيذ أوامرها مبكرًا. وهذا يجعل البرنامج الخاص بنا أكثر قابلية لإعادة الاستخدام وفعالية وموثوقية.
ولكن هل يمكننا كتابة دالة دون استخدام الإرجاع return؟
إنظر إلى هذا المثال:
#include <iostream> using namespace std; void print() { cout << "myName" << endl; } int main() { print(); return 0; }
يعرّف هذا البرنامج دالّة تسمى print()
والدالّة الرئيسية main. دالّة الطباعة print()
تقوم ببساطة بطباعة النص "myName" إلى شاشة المخرجات. الدالّة الرئيسية main تستدعي دالّة الطباعة print()
ثم تستخدم الإرجاع 0 return.
الـ print()
تمّ تعريفها بإستخدام إرجاع نوع البيانات void
. هذا يعني أن الدالة لا تُرجع أي قيمة. نحن نستخدم نوع الإرجاع void
عندما نريد من إحدى الدوالّ تنفيذ برنامج ما دون إرجاع أي قيمة.
عندما تقوم بكتابة هذا البرنامج وتشغيله، ستتم طباعة الناتج التالي على شاشة المخرجات:
myName
نستخدم نوع البيانات void
لدالّة الطباعة print()
لأننا نريدها فقط لطباعة النص "myName" على شاشة المخرجات. لا نحتاج من الدالة إرجاع أي قيمة.
يمكن أيضًا أن تكون الدوالّ متداخلة، مما يعني أن إحدى الدوالّ يمكنها استدعاء دالّة أخرى. يمكن أن يكون هذا مفيدًا لتقسيم المهام المعقدة إلى وظائف أصغر وأكثر قابلية للإدارة.
تعد الدوالّ أداة قوية في لغة C++ ويمكن استخدامها لتحسين جودة وأداء التعليمات البرمجية الخاصة بك.
فيما يلي بعض النصائح لاستخدام الدوالّ في C++:
- أعط الدوالّ أسماء ذات معنى. وهذا سيجعل التعليمات البرمجية الخاصة بك أكثر قابلية للقراءة وأسهل للصيانة.
- استخدم الدوالّ لتنفيذ العمليات المشتركة. وهذا سيجعل التعليمات البرمجية الخاصة بك أكثر قابلية لإعادة الاستخدام وأسهل للفهم.
- استخدم الدوالّ لتحسين أداء التعليمات البرمجية الخاصة بك عن طريق تجنب التعليمات البرمجية المكررة.
- استخدم الدوالّ لتقسيم المهام المعقدة إلى وظائف أصغر وأكثر قابلية للإدارة.
الدّالّة (2)
مثال 1:
#include <iostream> using namespace std; double avg(int m1, int m2, int m3) { return double(m1 + m2 + m3) / 3; } int main() { int mm1, mm2, mm3; cout << "Enter your marks" << endl; cin >> mm1 >> mm2 >> mm3; cout << "avg = " << avg(mm1, mm2, mm3) << endl; return 0; }
يعرّف هذا البرنامج دالّة تسمى avg()
، والذي يأخذ ثلاث متغيرات عددية (m1
, m2
، و m3
) وإرجاع متوسط هذه المتغيرات الثلاث كقيمة عشريّة.
تعمل الدالة أولاً عن طريق تحويل متغيرات الأعداد الصحيحة إلى قيم عشريّة باستخدام الدالة double()
. يعد ذلك ضروريًا لأن متوسط ثلاثة أعداد صحيحة هو رقم كسري، ونريد أن تقوم الدالة بإرجاع قيمة عشريّة.
بعد ذلك، تحسب الدالة مجموع المتغيرات الثلاثة وتقسم هذا المجموع على 3. وهذا يعطينا متوسط المتغيرات الثلاثة.
وأخيرًا، تقوم الدالة بإرجاع المتوسط الحسابي للقيم.
يعمل البرنامج على النحو التالي:
- يعرّف البرنامج ثلاثة متغيرات صحيحة
mm1
,mm2
، وmm3
لتخزين العلامات التي أدخلها المستخدم. - يطلب البرنامج من المستخدم إدخال علاماته، ثم يقوم البرنامج بقراءة العلامات في المتغيرات الثلاثة.
- يستدعي البرنامج الدالّة
avg()
لحساب المتوسط الحسابي للعلامات الثلاث. - يقوم البرنامج بطباعة المتوسط الحسابي إلى شاشة المخرجات.
مثال 2:
#include <iostream> using namespace std; int max(int n1, int n2, int n3) { int mx = n1; if (mx < n2) mx = n2; if (mx < n3) mx = n3; return mx; } int min(int n1, int n2, int n3) { int mn = n1; if (mn > n2) mn = n2; if (mn > n3) mn = n3; return mn; } int main() { cout << "max = " << max(100, 200, 300) << endl; cout << "min = " << min(100, 200, 300) << endl; return 0; }
يعرّف هذا البرنامج دالّتين max()
و min()
وكلاهما يأخذان ثلاث متغيرات (n1
, n2
، و n3
) وإرجاع الأكبر والأصغر من تلك المتغيرات الثلاثة.
تعمل الدالّة الأولى عن طريق إعطاء متغير يسمى mx
قيمة المتغير الأولى، n1
ثم تتحقق الدالّة ما إذا كان mx
أقل من المتغير الثاني n2
. إذا كان كذلك، تعيّن الدالّة قيمة n2
إلى mx
. ثمّ تتحقق الدالّة ما إذا كان mx
أقل من المتغير الثالث n3
. إذا كان كذلك، تعيّن الدالّة قيمة n3
إلى mx
أخيرًا، ترجع الدالّة قيمة mx
وهو القيمة الأكبر بين المتغيرات الثلاث.
تعمل الدالّة الثانيّة عن طريق إعطاء متغير يسمى mn
قيمة المتغير الأولى، n1
ثم تتحقق الدالّة ما إذا كان mn
أكبر من المتغير الثاني n2
. إذا كان كذلك، تعيّن الدالّة قيمة n2
إلى mn
. ثمّ تتحقق الدالّة ما إذا كان mn
أكبر من المتغير الثالث n3
. إذا كان كذلك، تعيّن الدالّة قيمة n3
إلى mn
أخيرًا، ترجع الدالّة قيمة mn
وهو القيمة الأصغر بين المتغيرات الثلاث.
مخرجات البرنامج:
min = 100 max = 300
هناك طريقتان لتعريف دالة في برنامج C++:
- إعلان الدالّة: هذا نموذج أولي للدالّة التي تخبر المترجم عن اسم الدالّة ونوع الإرجاع والمتغيرات. يجب وضع إعلان الدالّة قبل الاستدعاء الأول لها.
- تعريف الدالّة: هذا هو الكود الفعلي الذي ينفذ الدالّة. يمكن وضع تعريف الدالة في أي مكان في البرنامج، ولكن بشكل عام يتم وضعه في نهاية البرنامج.
على سبيل المثال، إليك مثال يبيّن إعلان الدالّة function declaration وتعريف الدالة function definition:
// Function declaration int add(int a, int b); // Function definition int add(int a, int b) { return a + b; }
يخبر إعلان الدالّة المترجم أن هناك دالّة تسمى add()
وهي تأخذ متغيرين صحيحين وتعيد قيمة عددية. يوفر تعريف الدالّة الكود الفعلي الذي ينفذ وظيفة الدالّة.
يمكنك أيضًا ترتيب الدوالّ المعلنة والدوالّ المعرفّة بطرق مختلفة في التعليمات البرمجية الخاصة بك. على سبيل المثال، يمكنك:
- الإعلان عن جميع الدوالّ في بداية البرنامج ثم تعريفها في نهاية البرنامج.
- إعلان وتعريف الدوالّ بالترتيب الذي يتم استدعاؤها فيه في البرنامج.
- تعريف الدوالّ في ملف header وتعريفها في ملف المصدر source file.
يعتمد أفضل ترتيب للتعليمات البرمجية على الاحتياجات المحددة للبرنامج.
فيما يلي مثال لكيفية استخدام إعلان الدالّة وتعريفها في برنامج C++:
// Function declaration int add(int a, int b); int main() { // Function call int sum = add(1, 2); // Print the sum cout << "The sum is: " << sum << endl; return 0; } // Function definition int add(int a, int b) { return a + b; }
بالطبع يجب عليك أن تولي اهتماما كبيرا للترتيب التسلسلي لبرنامجك.
يعد الترتيب التسلسلي للدوالّ في برنامج C++ مهمًا لسببين رئيسيين:
- سهولة القراءة: البرنامج المنظم جيدًا أسهل في القراءة والفهم. من خلال ترتيب الوظائف بترتيب منطقي، يمكنك تسهيل الأمر على المبرمجين الآخرين لمتابعة تدفق التعليمات البرمجية الخاصة بك.
- قابلية الصيانة: من السهل تحديث البرنامج الذي يتم صيانته جيدًا وإصلاح الأخطاء فيه. ومن خلال ترتيب الدوالّ بترتيب منطقي، يمكنك تسهيل العثور على المشكلات وإصلاحها في التعليمات البرمجية الخاصة بالبرنامج.
ملحوظة !
الدالّة الرئيسية main هي نقطة الدخول لجميع برامج C++. وهي الدالّة الأولى التي يتم استدعاؤها عند تنفيذ البرنامج. الدالّة الرئيسية main مسؤولة عن إبتداء البرنامج واستدعاء الدوالّ الأخرى وإرجاع القيمة إلى نظام التشغيل.
الدالّة الرئيسية main مهمة لعدة أسباب:
- هي نقطة البداية لتنفيذ البرنامج.
- هي المسؤولة عن تهيئة البرنامج.
- تتحكم في تدفق البرنامج عن طريق استدعاء دوالّ أخرى.
- تقوم بإرجاع قيمة إلى نظام التشغيل عند إنهاء البرنامج.
بدون الدالّة الرئيسية main، لن يتمكن البرنامج من بدء التنفيذ.
فيما يلي مثال لدالّة رئيسية main بسيطة في لغة C++:
int main() { // Initialize the program // ... // Call other functions // ... // Return a value to the operating system return 0; }
تقوم الدالّة الرئيسية main بتهيئة البرنامج واستدعاء دوالّ أخرى وإرجاع القيمة 0 إلى نظام التشغيل.
يمكنك استخدام الدالّة الرئيسية main للتحكم في تدفق البرنامج الخاص بك عن طريق استدعاء دوالّ أخرى. على سبيل المثال، يمكنك كتابة دالة لحساب مجموع رقمين ثم استدعاء تلك الدالة من الدالة الرئيسية main.
يمكنك أيضًا استخدام الدالّة الرئيسية main لإرجاع قيمة إلى نظام التشغيل. يتم استخدام هذه القيمة للإشارة إلى ما إذا كان البرنامج قد تم إنهاؤه بنجاح أم لا.
تعد الدالّة الرئيسية main جزءًا مهمًا جدًا من أي برنامج C++. إنها نقطة البداية لتنفيذ البرنامج، وهي التي تتحكم في تدفق البرنامج.
مثال 3:
#include <iostream> using namespace std; int avg(int x, int y, int z) { return (x + y + z) / 3; } void print() { cout << "Hello" << endl; } void xy() { cout << 5 * 10 << endl; } int main() { cout << "Nothing" << endl; return 0; } int f2() { return 5; }
هذا البرنامج يقوم بتعريف أربع دوالّ avg()
, print()
, xy()
، و f2()
. الـ avg()
تأخذ ثلاث معلمات متغيرات وترجع المتوسط الحسابي لها. print()
تقوم بطباعة النص "Hello" إلى شاشة المخرجات. xy()
تقوم بطباعة ناتج ضرب 5 و10 على شاشة المخرجات. f2()
ترجع قيمة العدد الصحيح 5.
الدالّة الرئيسية main في الكود تقوم ببساطة بطباعة السلسلة "Nothing" إلى شاشة المخرجات ثم ترجع 0.
عندما تقوم بكتابة هذا البرنامج وتشغيله، ستتم طباعة الناتج التالي على شاشة المخرجات:
Nothing
وذلك لأن الدالّة الوحيدة التي يتم استدعاؤها من الدالّة الرئيسية main هي cout << "Nothing" << endl;
.
يمكنك استخدام الدوالّ الأخرى في الكود عن طريق استدعائها من الدالّة الرئيسية أو من دوالّ أخرى.
الدّالّة (3) - الدّوال الجاهزة built in functions
الدوال الجاهزة في C++ هي الدوال التي يوفرها الـ compiler وهي متاحة للاستخدام في أي برنامج C++. تُستخدم هذه الدوال عادةً للمهام الشائعة مثل الإدخال/الإخراج والعمليات الرياضية والتلاعب بالكلمات والنصوص.
فيما يلي بعض الدوال الجاهزة الأكثر شيوعًا في لغة C++:
- الدوال الحسابية:
abs()
: إعادة القيمة المطلقة لعدد ما.sqrt()
: حساب الجذر التربيعي.pow()
: رفع العدد إلى قوّة.sin()
: حساب جيب الزاوية بالراديان.cos()
: حساب جيب تمام الزاوية بالراديان.tan()
: حساب ظل الزاوية بالراديان.floor()
: إرجاع أكبر عدد صحيح أقل من أو يساوي عدد ما.ceil()
: إرجاع أصغر عدد صحيح أقل من أو يساوي عدد ما.
- دوالّ التلاعب بالـ strings:
strlen()
: إرجاع طول النص string.strcpy()
: نسخ نص string إلى نص string آخر.strcat()
: جمع نصّين.strcmp()
: يقارن نصين ويعيد قيمة عددية تشير إلى ما إذا كانت متساوية أو أقل أو أكبر من بعضها البعض.
- دوال المدخلات والمخرجات:
cin
: يقرأ البيانات من المدخلات القياسية.cout
: يكتب البيانات إلى المخرجات القياسية.cerr
: يكتب البيانات إلى دفق الخطأ القياسي.fopen()
: فتح ملف للقراءة أو الكتابة.fclose()
إغلاق ملف.
مثال 1:
cout << sqrt(18) << endl;
المخرجات:
4.24264
مثال 2:
cout << abs(-18) << endl;
المخرجات:
18
مثال 3:
cout << mod(10.5,2) << endl;
المخرجات:
0.5
مثال 4:
cout << floor(10.2) << endl;
المخرجات:
10
مثال 5:
cout << floor(-10.2) << endl;
المخرجات:
-11
مثال 6:
cout << ceil(10.2) << endl;
المخرجات:
11
مثال 7:
#include <iostream> using namespace std; int main() { cout << sqrt(abs(pow(-3,2))) << endl; return 0; }
لإستخدام الدوالّ الجاهزة built-in function، فنحن ببساطة بحاجة لتضمين ملفات الـ header المناسبة. قد تواجه بعض المشاكل في تنفيذ هذا البرنامج، وذلك لأنك بحاجة إلى تضمين ملف الـ header <cmath>
.
الـ <cmath>
هذا الملف يحتوي على تعريفات لعدد من دوالّ العمليات الحسابية، مثل sqrt()
, pow()
, sin()
, cos()
، و tan()
. تعتبر هذه الدوالّ مفيدة لإجراء العمليات الحسابية الشائعة، مثل حساب الجذر التربيعي لعدد ما، ورفع العدد إلى قوة.
#include <iostream> #include <cmath> using namespace std; int main() { cout << sqrt(abs(pow(-3,2))) << endl; return 0; }
المخرجات:
3
مثال 8:
#include <iostream> #include <algorithm> #include <cmath> using namespace std; int main() { cout << max(10,5) << endl; return 0; }
ستحتاج إلى تضمين ملف الـ header <algorithm>
لاستخدام أي من الخوارزميات التي توفرها مكتبة C++ القياسية. تتضمن هذه الخوارزميات الفرز والبحث والدمج وغير ذلك الكثير.
المخرجات:
10
مثال 9:
#include <iostream> #include <algorithm> #include <cmath> using namespace std; int main() { cout << max(15,max(10,5)) << endl; return 0; }
المخرجات:
15
مثال 10:
#include <iostream> #include <algorithm> #include <cmath> using namespace std; int main() { int x = 0, y = 10; swap(x,y); cout << "x= " << x << "y= " << y << endl; return 0; }
المخرجات:
x= 10y= 0
يمكن أن تكون الدوالّ الجاهزة built-in functions أداة مفيدة جدًا لمبرمجي C++. باستخدامها يمكنك تجنب الاضطرار إلى كتابة التعليمات البرمجية الخاصة بك للمهام الشائعة.
الدّالّة (4) - الدوالّ العشوائية Random functions
تُستخدم الدوال العشوائية في لغة C++ لإنشاء أرقام عشوائية. هناك دالتان عشوائيتان جاهزتان في لغة C++: rand()
و srand()
.
الـ rand()
تنشئ الدالة عددًا صحيحًا عشوائيًا بين 0 وRAND_MAX، حيث RAND_MAX هو ماكرو تم تعريفه في ملف الـ header cstdlib
. الدالّة srand()
تستخدم لوضع بذرة لمولد الأرقام العشوائية. إذا لم تستدعي الدالة srand()
، سيتم تصنيف مولد الأرقام العشوائية بقيمة افتراضية.
مثال 1:
#include <iostream> #include <cstdlib> using namespace std; int main() { cout << rand() << endl; return 0; }
يطبع هذا البرنامج عددًا صحيحًا عشوائيًا إلى شاشة المخرجات.
مثال 2:
#include <iostream> #include <cstdlib> using namespace std; int main() { for (size_t i = 0; i < 10; i++) { cout << rand() << endl; } return 0; }
هذا البرنامج عبارة عن حلقة دوران تطبع 10 أعداد صحيحة عشوائية على شاشة المخرجات.
مثال 3:
#include <iostream> #include <cstdlib> using namespace std; int main() { for (size_t i = 0; i < 10; i++) { cout << rand()%10 << endl; } return 0; }
ينشئ هذا البرنامج رقمًا عشوائيًا بين 0 و9 ويطبعه على شاشة المخرجات. يقوم معامل القسمة (%) بإرجاع باقي عملية القسمة. إذن التعبير rand()%10
يرجع باقي قسمة العملية الحسابية rand() / 10
مما سيعطينا رقمًا بين 0 و 9.
مثال 4:
#include <iostream> #include <cstdlib> using namespace std; int main() { for (size_t i = 1; i <= 10; i++) { cout << rand()%(30 - 20 + 1) + 20 << endl; } return 0; }
يقوم هذا البرنامج بإنشاء عدد عشوائي بين 20 و30 وطباعته على شاشة المخرجات.
مثال 5:
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; int main() { srand(time(0)); for (size_t i = 1; i <= 10; i++) { cout << rand()%(30 - 20 + 1) + 20 << endl; } return 0; }
الـ srand(time(0));
هي دالة توليد الأرقام العشوائية مع الوقت الحالي. وهذا يضمن أن الأرقام العشوائية مختلفة في كل مرة يتم فيها تشغيل البرنامج.
الـ srand()
تأخذ الدالة عددًا صحيحًا كمتغير لها وتقوم بتهيئة مولد الأرقام العشوائية باستخدام هذا المتغير. ال time(0)
هي دالة تقوم بإرجاع الوقت الحالي كقيمة time_t، وهو عدد صحيح.
من خلال زرع مولد الأرقام العشوائية مع الوقت الحالي، فإننا نستخدم قيمة تتغير باستمرار، مما يضمن اختلاف الأرقام العشوائية في كل مرة يتم فيها تشغيل البرنامج.
من المهم زرع مولد الأرقام العشوائية قبل استخدام rand()
. إذا لم تقم بزرع مولد الأرقام العشوائية، فسيتم بذره بقيمة افتراضية، مما سيؤدي إلى إنشاء نفس التسلسل من الأرقام العشوائية في كل مرة يتم فيها تشغيل البرنامج.
مثال 6:
#include <iostream> #include <cstdlib> #include <ctime> using namespace std; int main() { int x, y; x = time(0); for (size_t i = 0; i < 10000; i++) { cout << time(NULL) << endl; y = time (0); } cout << "The token time:" << y - x << endl; return 0; }
يطبع هذا البرنامج الوقت الحالي 10000 مرة على شاشة المخرجات ثم يطبع إجمالي الوقت المستغرق للقيام بذلك.
يعمل البرنامج على النحو التالي:
- الـ
int x, y;
هو تعريف لمتغيرين صحيحينx
وy
. - الـ
x = time(0);
يعين الوقت الحالي للمتغيرx
. - الـ
for (size_t i = 0; i < 10000; i++)
حلفة دوران يتم تكرارها 10000 مرة. - في كل تكرار للحلقة for، يتم تنفيذ الكود التالي:
- تتم طباعة الوقت الحالي على شاشة المخرجات باستخدام
cout << time(NULL) << endl;
.
- تتم طباعة الوقت الحالي على شاشة المخرجات باستخدام
- الـ
y = time(0);
يعين الوقت الحالي للمتغيرy
. - الـ
cout << "The token time:" << y - x << endl;
يطبع إجمالي الوقت المستغرق لطباعة الوقت الحالي 10000 مرة على شاشة المخرجات.
الـ time(NULL)
هي دالة مرادفة للدالة time(0)
.
يمكنك استخدام هذا البرنامج لقياس أداء التعليمات البرمجية الخاصة بك أو لحساب الوقت المنقضي بين حدثين.
الدّالّة (5) - الإستدعاء حسب المرجع و الإستدعاء حسب القيمة)
يعد الإستدعاء حسب القيمة والإستدعاء حسب المرجع طريقتين مختلفتين لتمرير المتغيرات إلى الدّوال في C++.
الإستدعاء حسب القيمة هي آلية التمرير الافتراضية في C++. عندما يتم استدعاء دالة حسب القيمة، يتم تمرير نسخة من المتغير الفعلي إلى الدالة. تعمل الدالة بعد ذلك على هذه النسخة، وأي تغييرات يتم إجراؤها على النسخة داخل الدالة لن تنعكس في المتغير الفعلي خارج الدالة.
الإستدعاء حسب المرجع يسمح لك بتمرير مرجع المتغير الفعلي إلى الدالة. هذا يعني أن الدالة تعمل مباشرة على المتغير الفعلي خارج الدالة. أي تغييرات يتم إجراؤها على المتغير داخل الدالة سوف تنعكس في المتغير الفعلي خارج الدالة.
لتمرير متغير حسب المرجع، تحتاج إلى استخدام عامل التشغيل &
قبل اسم المتغير.
على سبيل المثال، يوضح البرنامج التالي كيفية إستدعاء دالة حسب القيمة:
#include <iostream> using namespace std; void swap(int x, int y) { int z = x; x = y; y = z; } int main() { int x = 10, y = 20; swap(x, y); cout << "x= " << x << " y= " << y << endl; return 0; } //x= 10 y= 20
يوضح البرنامج التالي كيفية إستدعاء نفس الدالة حسب المرجع:
#include <iostream> using namespace std; void swap(int&x, int&y) { int z = x; x = y; y = z; } int main() { int x = 10, y = 20; swap(x, y); cout << "x= " << x << " y= " << y << endl; return 0; } //x= 20 y= 10
كما ترى، نتيجة استدعاء الدالة swap()
تختلف اعتمادًا على ما إذا كانت تستدعى حسب القيمة أو حسب المرجع.
غالبًا ما يتم استخدام الاستدعاء حسب المرجع لتعديل قيم المتغيرات خارج الدالة. على سبيل المثال، swap()
هذه الدالة تستخدم لمبادلة قيم متغيرين.
يمكن أيضًا استخدام الاستدعاء حسب المرجع لتمرير objects كبيرة إلى الدوال دون الحاجة إلى نسخها. هذا يمكن أن يحسن أداء البرنامج الخاص بك.
ومع ذلك، من المهم استخدام الاستدعاء حسب المرجع بعناية، لأنه قد يؤدي إلى نتائج غير متوقعة إذا لم يتم استخدامه بشكل صحيح. على سبيل المثال، إذا قمت بتمرير مرجع إلى متغير محلي إلى دالة، وعادت الدالة، فسيخرج المتغير المحلي من النطاق، لكن مرجعه سيظل صالحًا. يمكن أن يؤدي هذا إلى تعطل البرنامج إذا حاولت الوصول إلى المتغير المحلي من خلال المرجع.
بشكل عام، من الأفضل استخدام الإستدعاء حسب القيمة إلا إذا كان لديك سبب محدد لاستخدام الإستدعاء حسب المرجع.
على سبيل المثال، يوضح البرنامج التالي كيفية إستدعاء دالة حسب القيمة:
#include <iostream> using namespace std; void fun(int x, int y) { x += 1; y += 2; } int main() { int k = 50, r = 10; fun(k, r); cout << "K= " << k << " R= " << r << endl; return 0; } //K= 50 R= 10
يوضح البرنامج التالي كيفية إستدعاء نفس الدالة حسب المرجع:
#include <iostream> using namespace std; void fun(int&x, int&y) { x += 1; y += 2; } int main() { int k = 50, r = 10; fun(k, r); cout << "K= " << k << " R= " << r << endl; return 0; } //K= 51 R= 12
كما ترى، نتيجة استدعاء الدالة fun()
تختلف اعتمادًا على ما إذا كانت تستدعى حسب القيمة أو حسب المرجع.
الدّالّة (6) - Recursion 1
Recursion هي تقنية برمجة حيث تستدعي الدالة نفسها بشكل مباشر أو غير مباشر. يمكن استخدام هذا لحل المشكلات التي يمكن تقسيمها إلى مشكلات أصغر وأبسط من نفس النوع.
قد يكون من الصعب فهم الـ Recursion في البداية، ولكنها أداة قوية يمكن استخدامها لحل مجموعة واسعة من المشكلات.
فيما يلي مثال لدالّة Recursion بسيطة في لغة C++:
#include <iostream> using namespace std; void f(int n) { if (n < 1) return; else cout << "round:" << n << endl; f(n - 1); } int main() { f(5); return 0; }
الدالة f(int n)
هي دالة recursive تقوم بطباعة السلسلة "round:" متبوعة بالعدد الصحيح n
إلى شاشة المخرجات n
من المرات.
تعمل الدالة عن طريق استدعاء نفسها بشكل متكرر لطباعة السلسلة "round:" متبوعة بالعدد الصحيح n - 1
إلى شاشة المخرجات. تستمر هذه العملية حتى تصبح قيمة n
مساوية للعدد 1، وعند هذه النقطة ترجع الدالة.
المخرجات:
round: 5 round: 4 round: 3 round: 2 round: 1
يمكن أن يكون من الصعب قليلًا فهم دوال الـ Recursive في البداية، ولكنها أداة قوية يمكن استخدامها لحل مجموعة واسعة من المشكلات.
يجب عليك الانتباه إلى هذه المحتويات الثلاثة لدالة الـ recursive (الحالة الأساسية، والمنطق، والمشكلة الفرعية)، والتي تم وضع علامة عليها في الدالة التالية:
void f(int n) { if (n < 1) return; //base case else cout << "round:" << n << endl; //logic f(n - 1); //subproblem }
المحتويات الثلاثة لدالة الـrecursive هي:
- الحالة الأساسية: الحالة الأساسية هي حالة خاصة يتم التعامل معها مباشرة في الدالة دون إجراء أي استدعاءات متكررة. عادةً ما تكون الحالة الأساسية حالة بسيطة يمكن حلها مباشرةً.
- المنطق: منطق دالة الـ recursive هو الكود الذي يتم تنفيذه لتقسيم المشكلة إلى مشاكل أصغر وأبسط من نفس النوع. يتضمن المنطق عادةً إجراء استدعاءات متكررة للدالة ذات قيم إدخال أصغر.
- المشكلة الفرعية: المشكلة الفرعية هي مشكلة أصغر وأبسط من نفس نوع المشكلة الأصلية. تعمل دوال الـ recursive عن طريق تقسيم المشكلة الأصلية إلى مشكلات فرعية ثم حل المشكلات الفرعية باستخدام نفس دالة الـ recursive.
فيما يلي مثال على دالة recursive في لغة C++ تحسب مضروب الرقم:
#include <iostream> using namespace std; int fact(int n) { if (n == 0 || n == 1) return 1; else return n*fact(n - 1); } int main() { cout << fact(5) << endl; return 0; }
الدالة fact(int n)
هي دالة recursive تحسب مضروب العدد. مضروب العدد هو حاصل ضرب جميع الأعداد الصحيحة الموجبة الأصغر من أو تساوي هذا العدد. على سبيل المثال، مضروب 5 هو 120، لأن 120 هو حاصل ضرب 1، 2، 3، 4، و5. لذلك، سيكون ناتج برنامجنا 120.
الدالة fact(int n)
تعمل عن طريق استدعاء نفسها بشكل متكرر لحساب مضروب الرقم واحد أقل من رقم الإدخال. على سبيل المثال، لحساب مضروب 5، ستستدعي الدالة نفسها أولاً لحساب مضروب 4. ومن ثم ستستدعي نفسها لحساب مضروب 3، وهكذا. في النهاية، ستصل الدالة إلى الحالة الأساسية، حيث يكون رقم الإدخال هو 0 أو 1. الحالة الأساسية هي حالة خاصة يتم التعامل معها مباشرة في الدالة دون إجراء أي استدعاءات متكررة. مضروب 0 هو 1 ومضروب 1 هو أيضًا 1، لذا تقوم الدالة ببساطة بإرجاع 1 في هذه الحالات.
سلسلة فيبوناتشي
متسلسة فيبوناتشي هي سلسلة من الأعداد بحيث يمثل كل عدد في السلسة مجموع العددين السابقين له في السلسلة. أول عددين في السلسة هما 0 و 1، وكل عدد لاحق هو مجموع العددين السابقين.
إليك تسلسل فيبوناتشي حتى أول 10 أرقام:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
يمكن إنشاء تسلسل فيبوناتشي باستخدام علاقة التكرار التالية:
F(n) = F(n - 1) + F(n - 2)
بينما F(n)
هو الرقم n في تسلسل فيبوناتشي.
يحتوي تسلسل فيبوناتشي على عدد من الخصائص المثيرة للاهتمام. على سبيل المثال، نسبة أرقام فيبوناتشي المتعاقبة تقترب من النسبة الذهبية عندما تصبح الأرقام أكبر. النسبة الذهبية هي رقم غير منطقي يساوي تقريبًا 1.618.
يظهر تسلسل فيبوناتشي في العديد من مجالات مختلفة في الرياضيات والطبيعة. على سبيل المثال، يمكن استخدامه لنمذجة نمو مجموعة من الأرانب، النمط الحلزوني للصدفة، والنمط المتفرع للشجرة.
فيما يلي بعض الأمثلة على كيفية استخدام تسلسل فيبوناتشي في العالم الحقيقي:
- الموارد المالية: يمكن استخدام تسلسل فيبوناتشي لتحديد مستويات الدعم والمقاومة المحتملة لأسعار الأسهم.
- الموسيقى: يمكن استخدام تسلسل فيبوناتشي لإنشاء مقطوعات موسيقية ذات إيقاع وبنية ممتعة.
- الفن: يمكن استخدام تسلسل فيبوناتشي لإنشاء أعمال فنية ذات تركيبة متوازنة ومتناغمة.
- الهندسة المعمارية: يمكن استخدام تسلسل فيبوناتشي لتصميم المباني والهياكل الأخرى التي تكون مبهجة من الناحية الجمالية وسليمة من الناحية الهيكلية.
تسلسل فيبوناتشي هو تسلسل رياضي رائع ومتعدد الاستخدامات وله العديد من التطبيقات في العالم الحقيقي.
مثال:
#include <iostream> using namespace std; int fib(int n) { if (n == 0 || n == 1) return n; else return fib(n - 1) + fib(n - 2); } int main() { cout << fib(3) << endl; return 0; }
الدالة fib(int n)
يعمل عن طريق استدعاء نفسه بشكل متكرر لحساب أرقام فيبوناتشي للرقمين السابقين. إذا كان رقم الإدخال n
0 أو 1، فإن الدالة تقوم ببساطة بإرجاع الرقم، لأن أول رقمين فيبوناتشي هما 0 و1. وبخلاف ذلك، تقوم الدالة بإرجاع مجموع أرقام فيبوناتشي للرقمين السابقين.
المخرجات:
2
الدّالّة (7) - Recursion 2
مثال 1:
#include <iostream> using namespace std; int sum(int n) { if (n == 1) return 1; else return n + sum(n - 1); } int main() { cout << sum(5) << endl; return 0; }
هذه الدالة تحسب مجموع العدد الطبيعي الأول n
. الأعداد الطبيعية الأولى n
هي الأرقام من 1 لـ n
.
تعمل الدالة عن طريق استدعاء نفسها بشكل متكرر لحساب مجموع الأعداد الطبيعية الأولى n - 1
. إذا كان الرقم المدخل n
هو 1، تقوم الدالة ببساطة بإرجاع الرقم 1، لأن مجموع الرقم الطبيعي الأول هو 1. وبخلاف ذلك، تقوم الدالة بإرجاع مجموع الرقم المدخل n
ومجموع الأعداد الطبيعية الأولى n - 1
.
المخرجات:
15
مثال 2:
#include <iostream> using namespace std; int sum(int x, int y) { if (x == y) return x; else return y + sum(x, y - 1); } int main() { cout << sum(4, 6) << endl; return 0; }
هذه دالة recursive sum
تأخذ متغيرين صحيحين كـ parameters x
و y
، والتي تمثل نطاق الأرقام المطلوب جمعها. إليك كيفية عمل الدالة:
- الحالة الأساسية: إذا كانت
x
تساويy
، فهذا يعني أننا وصلنا إلى نهاية الـ range، فسوف يرجعx
. هذا هو شرط إنهاء الrecursion. - حالة الـ recursive: إذا كانت
x
لا تساويy
، فستضاف القيمة الحالية لـy
إلى نتيجة استدعاءsum
مرة أخرى مع نفسx
وy-1
. يؤدي هذا بشكل فعال إلى تقسيم مجموع الـ range إلى مشاكل فرعية أصغر عن طريق تقليل الحد العلوي (y
بمقدار 1 في كل مرة يتم إستدعاء دالة الـ recursive.
في الدالة الرئيسة main
، يتم إستدعاء الدالة sum
مع الـ (arguments) 4
و 6
ثم يقوم بإخراج النتيجة إلىشاشة المخرجات باستخدام cout
. في هذه الحالة، سيتم حساب مجموع الأعداد الصحيحة من 4 إلى 6 وطباعة النتيجة.
عند تشغيل هذا الكود، فإنه سيخرج مجموع الأعداد الصحيحة من 4 إلى 6، وهو 4 + 5 + 6 = 15. إذن، سيكون ناتج هذا الكود:
15
الدّالّة (8) - Overloading Function
Function overloading أو التحميل الزائد للدوال هو إحدى ميزات لغة C++ التي تتيح لك الحصول على دالتين أو أكثر بنفس الاسم، طالما أن لديهم parameters مختلفة. يمكن أن يكون هذا مفيدًا عندما تريد الحصول على دالة واحدة يمكنها تنفيذ مهام مختلفة، اعتمادًا على نوع البيانات التي يتم تمريرها إليها كـ argument.
على سبيل المثال، هذا البرنامج عبارة عن مجموعة من ثلاث دوال التي تطبع أنواعًا مختلفة من البيانات إلى شاشة المخرجات.
#include <iostream> using namespace std; void print(int a) { cout << "Integer = " << a << endl; } void print(float a) { cout << "Float = " << a << endl; } void print(char c) { cout << "Character = " << c << endl; } int main() { print(1); print('a'); return 0; }
الدالة الأولى، print(int a)
، تأخذ عددًا صحيحًا كـ argument وتطبعه على شاشة المخرجات بالـ (prefix) "Integer =".
الدالة الثانية، print(float a)
، تأخذ رقمًا عشريًّا من نوع float كـargument وتطبعه على شاشة المخرجات بـ(prefix) "Float =".
الدالة الثالثة، print(char c)
، تأخذ حرفًا كـ argument وتطبعه على شاشة المخرجات بـ (prefix) "Character = ".
المخرجات:
Integer = 1 Character = a
الدّالّة (9) - Default arguments
الـ arguments الافتراضية هي إحدى ميزات C++ التي تسمح لك بتحديد قيمة افتراضية لـ parameter الدالة. هذا يعني أنه إذا لم يقدم مستدعي الدالة قيمة لـ parameter معين، سيتم استخدام القيمة الافتراضية بدلاً من ذلك.
يمكن أن تكون الـ arguments الافتراضية مفيدة لتبسيط التعليمات البرمجية التي تستدعي دالة، ولجعل الدالة أكثر مرونة. على سبيل المثال، يمكن أن يكون لديك دالة تأخذ parameters إثنين، ولكن حيث يكون الـ parameter الثاني اختياريًّا. يمكنك بعد ذلك تحديد قيمة افتراضية للـ parameter الثاني، بحيث لا يضطر مستدعي الدالة إلى تقديم قيمة لها إذا كانوا لا يريدون ذلك.
فيما يلي مثال لكيفية استخدام الـ arguments الافتراضية في C++:
#include <iostream> using namespace std; int sum(int a = 5, int b = 5, int c = 5) { return a + b + c; } int main() { cout << sum(10, 55) << endl; return 0; }
يحسب هذا البرنامج مجموع ثلاثة أعداد صحيحة. يستخدم البرنامج الـ
arguments الافتراضية لتوفير القيم الافتراضية للأعداد الصحيحة الثلاثة. وهذا يعني أنه إذا كان المستدعي من sum()
لم يوفر قيمًا للأعداد الصحيحة الثلاثة، سيتم استخدام القيم الافتراضية بدلاً من ذلك.
الـ main()
تستدعي الدالة sum()
مع الـ (arguments) 10 و 55. sum()
ستقوم بإرجاع مجموع الأعداد الصحيحة الثلاثة، وهو 70. main()
ستطبع الناتج إلى شاشة المخرجات.
المخرجات:
70
فيما يلي بعض النصائح لاستخدام الـ arguments الافتراضية:
- لا تكتب الـ argument الافتراضي في منتصف arguments اثنتين مثل هذا (int a، int b = 5، int c)، سيؤدي ذلك إلى حدوث خطأ في الـ compiler.
- احرص على عدم استبدال الarguments الافتراضية بالقيم الغير صالحة للدالة.
- استخدم الـ parameters الافتراضية لتوفير القيم الافتراضية للـ parameters التي ليست ضرورية لوظيفة الدالة.
الدّالّة (10) - Inline Function
الـ Inline Function في لغة C++ هي دالة يتم استبدال تعليماتها البرمجية عند نقطة الاستدعاء. هذا يعني أن الـ compiler ينسخ كود الـ inline function إلى دالة الاستدعاء، بدلاً من إنشاء تعليمات الاستدعاء.
تُستخدم الـ inline function عادةً للدوال الصغيرة التي يتم استدعاؤها بشكل متكرر. وهذا يمكن أن يحسن أداء البرنامج، كما أنه يلغي الحمل الزائد لاستدعاءات الدوال.
للإعلان عن inline function في C++، يمكنك استخدام inline
كـ keyword قبل إعلان الدالة. على سبيل المثال:
#include <iostream> using namespace std; inline void p() { cout << "hello" << endl; } int main() { p(); p(); p(); return 0; }
inline void p()
، يعرف inline function تسمى p()
. لا تأخذ الدالة أي arguments وتقوم ببساطة بطباعة الكلمة "hello" على شاشة المخرجات، متبوعة بحرف السطر الجديد.
الأسطر الثلاث p();
, p();
، و p();
، تستدعي الدالة p()
ثلاث مرات.
عندما تقوم بكتابة هذا البرنامج وتشغيله، ستتم طباعة الناتج التالي على شاشة المخرجات:
hello hello hello
من المهم ملاحظة أن الـ compiler غير ملزم بتضمين دالة، حتى لو تم الإعلان عنها على أنها inlined. قد يختار الـ compiler عدم تضمين دالة إذا كانت ضخمة جدًا أو إذا تم استدعاؤها بشكل غير مباشر.
فيما يلي بعض فوائد استخدام الـ inline functions:
- تحسين الأداء: يمكن للـ inline functions تحسين أداء البرنامج عن طريق التخلص من الحمل الزائد لاستدعاءات الدوال.
- تقليل حجم البرنامج: يمكن للـ inline functions تقليل حجم التعليمات البرمجية للبرنامج عن طريق إزالة الحاجة إلى إنشاء تعليمات برمجية منفصلة لكل استدعاء دالة.
- زيادة إمكانية القراءة: يمكن للـ inline functions أن تجعل التعليمات البرمجية أكثر قابلية للقراءة من خلال التخلص من الحاجة إلى التنقل ذهابًا وإيابًا بين الدوال المختلفة.
ومع ذلك، هناك أيضًا بعض العيوب المحتملة لاستخدام الـ inline functions:
- زيادة وقت الترجمة: يمكن للـ inline functions أن تزيد من وقت الترجمة للبرنامج، حيث يحتاج المترجم إلى نسخ رمز الـ inline functions في كل دالة استدعاء.
- زيادة حجم البرنامج: إذا تم استدعاءinline function بشكل متكرر، فيمكن أن يؤدي ذلك إلى زيادة حجم الكود الخاص بالبرنامج.
- انخفاض قابلية الصيانة: يمكن للـ inline functions أن تجعل صيانة التعليمات البرمجية أكثر صعوبة، حيث أن التعليمات البرمجية الخاصة بالدالة المضمنة منتشرة في جميع أنحاء البرنامج.
بشكل عام، يمكن أن تكون الـ inline function أداة مفيدة لتحسين أداء التعليمات البرمجية وسهولة قراءتها. ومع ذلك، من المهم استخدامها بعناية، حيث يمكنها أيضًا زيادة وقت الترجمة وحجم التعليمات البرمجية للبرنامج.
فيما يلي بعض النصائح لاستخدام الـ inline functions بفعالية:
- تُستخدم الـ inline function عادةً للدوال الصغيرة التي يتم استدعاؤها بشكل متكرر.
- تجنب تضمين الدوال الكبيرة جدًا أو التي يتم استدعاؤها بشكل غير مباشر.
- استخدم
inline
كـ keyword باستمرار، حتى يتمكن الـ compiler من اتخاذ قرارات مستنيرة حول ما إذا كان سيتم تضمين دالة أم لا. - قم بتوثيق التعليمات البرمجية الخاصة بك بوضوح، حتى يفهم المطورون الآخرون كيف ولماذا يتم استخدام الـ inline functions.
المتغير الساكن - Static variable
المتغير الساكن في لغة C++ هو متغير يتم تخصيصه مرة واحدة ويبقى في الذاكرة طوال تنفيذ البرنامج. يمكن الإعلان عن المتغيرات الساكنة في النطاق العالمي، أو نطاق الـ namespace، أو نطاق الـ class، أو نطاق الدالة.
تتم تهيئة المتغيرات الساكنة على النطاق العالمي عند بدء تشغيل البرنامج ويتم تدميرها عند انتهاء البرنامج. تتم تهيئة المتغيرات الساكنة في نطاق الـ namespace عند تحميل الـ namespace ويتم إتلافها عند إلغاء تحميلها. تتم تهيئة المتغيرات الساكنة في الـ classes عند استخدام الـ class لأول مرة ويتم تدميرها عند انتهاء البرنامج. تتم تهيئة المتغيرات الساكنة في الدوال عند استدعاء الدالة لأول مرة ويتم تدميرها عند إرجاع الدالة.
يمكن أن تكون المتغيرات الساكنة مفيدة لمجموعة متنوعة من الأهداف، مثل:
- تخزين معلومات الـ global state: يمكن استخدام المتغيرات الساكنة لتخزين معلومات الـ global state التي يجب الوصول إليها من خلال دوال متعددة في البرنامج.
- تنفيذ المفردات: يمكن استخدام المتغيرات الساكنة لتنفيذ المفردات، وهي classes يمكن أن تحتوي على مثيل واحد فقط.
- تنفيذ الـ lazy initialization: يمكن استخدام المتغيرات الساكنة لتنفيذ الـ lazy initialization، وهي تقنية لتأخير تهيئة المتغير حتى يتم استخدامه لأول مرة.
فيما يلي مثال لمتغير ساكن تم تعريفه في دالة:
#include <iostream> using namespace std; void fun() { static int x = 0; x++; cout << x << endl; } int main() { fun(); fun(); return 0; }
يعرّف هذا البرنامج دالّة تسمى fun()
ثم يستدعيها مرتين من الدالة الرئيسة main()
. الدالة fun()
تعلن عن متغير ساكن يسمى x
ويزيده في كل مرة يتم استدعاؤها. تقوم الدالة بعد ذلك بطباعة قيمة x
إلى شاشة المخرجات.
لأن x
هو متغير ساكن، ويتم الاحتفاظ بقيمته بين استدعاءات الدوال. وهذا يعني أنه عندما يتم إستدعاء الدالة fun()
للمرة الثانية، قيمة المتغير x
ستكون 1 وليس 0. وبالتالي فإن مخرجات البرنامج ستكون كما يلي:
1 2
يوضح هذا البرنامج كيف يمكن استخدام المتغيرات الساكنة لتخزين معلومات الحالة التي يجب الوصول إليها عن طريق استدعاءات متعددة للدالة.
Aliasing & Constant Variable
إنشاء اسم مستعار Aliasing
يحدث الـ Aliasing في C++ عندما يشير اسمان مختلفان أو أكثر إلى نفس موقع الذاكرة. يمكن أن يحدث هذا عند استخدام المؤشرات pointers أو المراجع، أو عند الإعلان عن متغيرين من نفس النوع وتهيئتهما لنفس القيمة.
على سبيل المثال، التعليمة البرمجية التالية بإنشاء اسم مستعار للمتغير x
:
int x = 1; int &y = x;
الآن، كلا المتغيرين x
و y
يشيران إلى نفس موقع الذاكرة. إذا قمت بتغيير قيمة x
، قيمة y
ستتغير أيضًا.
مثال:
#include <iostream> using namespace std; int main() { int x = 1; int &y = x; x = 5; int &z = y; cout << "x = " << x << " " << "y = " << y << " " << "z = " << z << endl; y = 7; cout << "x = " << x << " " << "y = " << y << " " << "z = " << z << endl; y = x + z - 3; cout << "x = " << x << " " << "y = " << y << " " << "z = " << z << endl; return 0; }
يبدأ الكود بإعلان متغير صحيح اسمه x
وتهيئته إلى 1. ثم يعلن عن مرجع عدد صحيح مسمى y
وربطه بالمتغير x
. مما يعني أن y
هو الآن اسم مستعار لـ x
.
بعد ذلك، يقوم الكود بتعيين القيمة 5 للمتغير x
. وهذا يغير أيضًا قيمة المتغير y
، لأنه اسم مستعار لـ x
.
بعد ذلك، يقوم البرنامج بتعريف مرجع عدد صحيح آخر يسمى z
وربطه بالمتغير y
. مما يعني أن z
وهو أيضًا اسم مستعار لـ x
.
الآن، جميع المتغيرات الثلاثة x
, y
، و z
تشير إلى نفس موقع الذاكرة. وهذا يعني أن تغيير قيمة أي من هذه المتغيرات سيؤدي أيضًا إلى تغيير قيمة المتغيرين الآخرين.
ثم يقوم الكود بطباعة قيم المتغيرات x
, y
، و z
إلى شاشة المخرجات والتي ستكون كالتالي:
x = 5 y = 5 z = 5
بعد ذلك، يقوم الكود بتعيين القيمة 7 للمتغير y
. وهذا يغير أيضًا قيم المتغيرات x
و z
، لأنها كلها أسماء مستعارة لنفس موقع الذاكرة.
ثم يقوم الكود بطباعة قيم المتغيرات x
, y
، و z
إلى شاشة المخرجات والتي ستكون كالتالي:
x = 7 y = 7 z = 7
وأخيرًا، يقوم الكود بحساب قيمة x + z - 3
ويعينه للمتغير y
. وهذا يغير أيضًا قيم المتغيرات x
و z
، لأنها كلها أسماء مستعارة لنفس موقع الذاكرة.
ثم يقوم الكود بطباعة قيم المتغيرات x
, y
، و z
إلى شاشة المخرجات والتي ستكون كالتالي:
x = 11 y = 11 z = 11
يوضح هذا الكود كيف يمكن استخدام الاسم المستعار لتغيير قيم متغيرات متعددة في وقت واحد. ويوضح أيضًا كيف يمكن استخدام الأسماء المستعارة لإنشاء أسماء مستعارة للتعبيرات المعقدة.
من المهم ملاحظة أن الاسم المستعار يمكن أن يكون أداة قوية، ولكنه قد يؤدي أيضًا إلى الارتباك والأخطاء إذا تم استخدامه بشكل غير صحيح. من المهم أن تفكر بعناية في الآثار المترتبة على الاسم المستعار قبل استخدامه في التعليمات البرمجية الخاصة بك.
المتغير الثابت Constant Variable
المتغير الثابت في لغة C++ هو متغير لا يمكن تغيير قيمته بعد تهيئته. للإعلان عن متغير ثابت، يمكنك استخدام const
كـ keyword قبل نوع المتغير. على سبيل المثال:
const double Pi = 3.14;
بمجرد الإعلان عن متغير ثابت، لا يمكنك تعيين قيمة جديدة له. إذا حاولت القيام بذلك، سيقوم المترجم بإنشاء خطأ.
مثال:
#include <iostream> using namespace std; int main() { const double Pi = 3.14; int r; cout << "please enter r: "; cin >> r; double a = Pi * r * r; cout << "Area of circle = " << a << endl; return 0; }
هذا الكود يحسب مساحة الدائرة يبدأ البرنامج بإعلان متغير عشري ثابت اسمه Pi
وتهيئته إلى القيمة 3.14. ثم يعلن عن متغير عدد صحيح اسمه r
ويطالب المستخدم بإدخال قيمة له.
بعد ذلك، يقوم البرنامج بحساب مساحة الدائرة باستخدام الصيغة التالية:
- Area = πr²
يقوم البرنامج بعد ذلك بطباعة مساحة الدائرة إلى شاشة المخرجات.
فيما يلي تفصيل للكود:
int main() { // Declare a constant double variable named Pi and initialize it to the value 3.14. const double Pi = 3.14; // Declare an integer variable named r. int r; // Prompt the user to enter a value for r. cout << "please enter r: "; cin >> r; // Calculate the area of the circle. double a = Pi * r * r; // Print the area of the circle to the console. cout << "Area of circle = " << a << endl; return 0; }
المخرجات:
please enter r: 5 Area of circle = 78.5
يمكن أن تتفاعل المتغيرات المستعارة والثابتة بعدة طرق مثيرة للاهتمام:
- إذا كان لديك متغير ثابت وقمت بإنشاء اسم مستعار له، فسيكون الاسم المستعار ثابتًا أيضًا. وذلك لأن الاسم المستعار هو مجرد اسم آخر لنفس المتغير، ولا يمكن تغيير قيمة المتغير.
- إذا كان لديك متغير ثابت وقمت بتمريره إلى دالة كـparameter، فلن تتمكن الدالة من تغيير قيمة المتغير. وذلك لأن الدالة مسموح لها فقط بتعديل النسخة المحلية من المتغير.
- إذا كان لديك مؤشر pointer لمتغير ثابت، فلا يزال بإمكانك استخدام المؤشر لإلغاء الإشارة إلى المتغير وقراءة قيمته. ومع ذلك، لا يمكنك استخدام المؤشر لتغيير قيمة المتغير.
فيما يلي مثال على المتغيرات المستعارة والثابتة في لغة C++:
#include <iostream> using namespace std; int main() { int i = 1; int &j = i; cout << "j = " << j << endl; const int &k = j; cout << "k = " << k << endl; return 0; }
المخرجات:
j = 1 k = 1
المصفوفة أحادية البعد One Dimensional Array (1)
المصفوفة عبارة عن بنية بيانات تقوم بتخزين مجموعة من العناصر من نفس نوع البيانات. يتم تخزين هذه العناصر في مواقع ذاكرة متجاورة، ويمكن الوصول إليها باستخدام index. توفر المصفوفات طريقة لإدارة ومعالجة مجموعة كبيرة من البيانات بكفاءة.
في لغة C++، يمكنك الإعلان عن مصفوفة عن طريق تحديد نوع بياناتها وعدد العناصر التي ستحتوي عليها. إليك بناء الجملة التالي:
data_type array_name[array_size];
المصفوفات في لغة C++ تبدأ فهرستها بصفر، مما يعني أنه يتم الوصول إلى العنصر الأول باستخدام index 0، والعنصر الثاني باستخدام index 1، وهكذا. يمكنك الوصول إلى عناصر المصفوفة وتعديلها باستخدام الأقواس المربعة:
array_name[index] = new_value;
مثال:
#include <iostream> using namespace std; int main() { int x[5]; x[0] = 10; x[1] = 20; cout << x[0] << endl; x[2]; x[3]; x[4]; return 0; }
1. int x[5];
يعلن هذا السطر عن مصفوفة أعداد صحيحة تسمى x بحجم 5. وهذا يعني أن x يمكنها تخزين خمس قيم صحيحة. ومع ذلك، في هذه المرحلة، تكون القيم الموجودة في المصفوفة غير مهيأة، مما يعني أنها تحتوي على بيانات عشوائية.
2. x[0] = 10;
هنا، نقوم بتعيين القيمة 10 للعنصر الأول في المصفوفة x. في لغة C++، تكون المصفوفات مفهرسة بصفر، لذلك يشير x[0] إلى العنصر الأول.
3. x[1] = 20;
وبالمثل، فإننا نخصص القيمة 20 للعنصر الثاني في المصفوفة x، وهو x[1].
4. cout << x[0] << endl;
يطبع هذا السطر قيمة العنصر الأول من المصفوفة x، وهو 10، إلى شاشة المخرجات. ويستخدم (object) cout من مكتبة C++ لإخراج القيمة، متبوعًا بحرف نهاية السطر (endl) للتنسيق.
5. x[2];
يبدو أن هذه الأسطر تصل إلى العنصر الثالث في المصفوفة x، لكنها لا تفعل أي شيء. إنها في الأساس عملية "noop"، وتظل القيمة في x[2] غير مهيأة أو دون تغيير.
6. x[3]; and x[4];
كما هو الحال في السطر السابق، تصل هذه الخطوط إلى العنصرين الرابع والخامس من المصفوفة x، لكنها أيضًا لا تؤدي أي عملية ذات معنى.
المخرجات:
10
مثال إضافي:
#include <iostream> using namespace std; int main() { int x[5]; x[0] = 10; x[1] = 5; x[4] = x[0] + x[1]; cout<< x[4] << endl; return 0; }
المخرجات:
15
المصفوفات تقبل فقط حجم ثابت للمصفوفة، مثال:
#include <iostream> using namespace std; int main() { const int t = 5; int x[t]; return 0; }
يعلن هذا الكود عن مصفوفة أعداد صحيحة `x` بحجم 5، ويتم تحديد الحجم بواسطة الثابت `t`، الذي تم ضبطه على 5. وتضمن الـ(keyword) `const` عدم إمكانية تغيير `t` لاحقًا في البرنامج . لا يقوم هذا الكود بتنفيذ أي عمليات أخرى على المصفوفة؛ إنه يوضح ببساطة كيفية الإعلان عن مصفوفة بحجم يحدده ثابت.
يمكنك أيضًا الإعلان عن حجم المصفوفة الخاصة بك، والإعلان عن القيم الأولية للمصفوفة الخاصة بك:
#include <iostream> using namespace std; int main() { int x[5] = { 1,4,8,7,2 }; cout << x[0] << endl; return 0; }
يعلن هذا الكود عن مصفوفة أعداد صحيحة x بحجم 5 ويقوم بتهيئتها بالقيم 1 و4 و8 و7 و2. ثم يقوم بطباعة العنصر الأول من المصفوفة، وهو 1، إلى شاشة المخرجات. يوضح هذا الكود كيفية إنشاء مصفوفة والوصول إلى عناصرها في لغة C++.
ولكن ماذا لو لم تعلن عن أي قيم أولية في المصفوفة؟
#include <iostream> using namespace std; int main() { int x[5]; cout << x[3] << endl; return 0; }
يعلن هذا الكود عن مصفوفة أعداد صحيحة x بحجم 5 ولكنه لا يقوم بتهيئة عناصره. لذلك، عند محاولة طباعة قيمة العنصر الرابع، سيتم عرض قيمة عشوائية أو غير صحيحة. من المهم تهيئة عناصر المصفوفة قبل استخدامها لتجنب السلوك غير المتوقع في برامجك.
ماذا لو أعلنت قيمة أولية واحدة فقط تساوي 0؟
#include <iostream> using namespace std; int main() { int x[5] = {0}; cout << x[3] << endl; return 0; }
يعلن هذا الكود عن مصفوفة أعادد صحيحة x بحجم 5 ويقوم بتهيئة جميع عناصرها إلى 0. ثم يقوم بطباعة قيمة العنصر الرابع من المصفوفة، وهو 0، إلى شاشة المخرجات. يوضح هذا الكود كيفية إنشاء مصفوفة بقيم أولية محددة في لغة C++.
يمكنك أيضًا الإعلان عن حجم المصفوفة والإعلان عن بعض القيم الأولية الأولى، على سبيل المثال:
#include <iostream> using namespace std; int main() { int x[5] = {1,2,3}; cout << x[3] << endl; return 0; }
يعلن هذا الكود عن مصفوفة أعداد صحيحة x بحجم 5 ويقوم بتهيئة العناصر الثلاثة الأولى بقيم، بينما تتم تهيئة العنصرين المتبقيين تلقائيًا إلى 0. ثم يقوم بطباعة قيمة العنصر الرابع، وهو 0، إلى شاشة المخرجات.
أو يمكنك الإعلان عن عدم وجود حجم لمصفوفتك والإعلان فقط عن القيم الأولية لمصفوفتك:
#include <iostream> using namespace std; int main() { int x[] = {1,2,3,4,5}; cout << x[3] << endl; return 0; }
يعلن هذا الرمز عن مصفوفة أعداد صحيحة x ويقوم بتهيئتها بقيم باستخدام قائمة تهيئة المصفوفة. ثم يقوم بطباعة قيمة العنصر الرابع من المصفوفة، وهو 4، إلىشاشة المخرجات. يوضح هذا الرمز كيفية إنشاء مصفوفة والوصول إلى عناصرها في لغة C++.
كيف يقوم الـ compiler بتخزين القيم في المصفوفة؟
يقوم الـ compiler بتخزين قيم المصفوفة في مواقع الذاكرة المتجاورة. وهذا يعني أنه يتم تخزين قيم عناصر المصفوفة بجانب بعضها البعض في الذاكرة. يقوم المترجم بتخصيص جزء من الذاكرة كبير بما يكفي لتخزين كافة عناصر المصفوفة، ثم يقوم بتخزين قيم العناصر الموجودة في هذا الجزء من الذاكرة.
يقوم الـcompiler بتتبع عنوان العنصر الأول في المصفوفة، ثم يستخدم هذا العنوان للوصول إلى العناصر الأخرى في المصفوفة. على سبيل المثال، للوصول إلى العنصر الموجود في الفهرس i، يضيف الـ(compiler) i إلى عنوان العنصر الأول.
تعتمد الطريقة المحددة التي يقوم بها المترجم بتخزين قيم المصفوفة على نوع بيانات عناصر المصفوفة. على سبيل المثال، إذا كانت عناصر المصفوفة عبارة عن أعداد صحيحة، فسيقوم المترجم بتخزين كل عدد صحيح في موقع ذاكرة واحد. ومع ذلك، إذا كانت عناصر المصفوفة عبارة عن بنيات، فسيقوم المترجم بتخزين كل بنية في كتلة متجاورة من مواقع الذاكرة.
يوضح لك هذا الكود كيفية قراءة وتخزين مدخلات المستخدم في مصفوفة في لغة C++، فهو يسمح للمستخدم بإدخال قيمة عددية للعنصر الأول في المصفوفة، arr[0]، ثم يعرض القيمة التي تم إدخالها.
#include <iostream> using namespace std; int main() { int arr[50]; cout << "Enter arr[0]: " << endl; cin >> arr[0]; cout << arr[0] << endl; return 0; }
لاجتياز عناصر المصفوفة، يمكنك استخدام حلقات الدوران مثل for
أو while
. يتيح لك التكرار عبر المصفوفة تنفيذ العمليات على كل عنصر بشكل منهجي.
مثال:
#include <iostream> using namespace std; int main() { int arr[10]; for (size_t i = 0; i < 10; i++) { cout << "Enter arr[" << i << "]: "; cin >> arr[i]; } for (size_t i = 0; i < 10; i++) { cout << "arr[" << i << "] = " << arr[i] << endl; } return 0; }
يسمح هذا الكود للمستخدم بإدخال 10 قيم صحيحة، واحدة لكل عنصر من عناصر المصفوفة، ثم يعرض القيم مع مؤشراتها. ويوضح كيفية قراءة وتخزين مدخلات المستخدم المتعددة في مصفوفة ومن ثم طباعة محتويات المصفوفة في لغة C++.
المصفوفة أحادية البعد One Dimensional Array (2)
المصفوفات متعددة الاستخدامات بشكل لا يصدق، ويمكنك إجراء عمليات مختلفة عليها، مثل نسخ القيم الأولية لمصفوفة موجودة إلى مصفوفة أخرى.
بالطبع لا يمكننا استخدام السطر التالي:
arr2 = arr1;
لأننا لا نخصص قيمة واحدة. تحتوي كل مصفوفة على مجموعة من القيم، لذلك إذا أردنا تعيين قيمة واحدة في مصفوفة، فيجب علينا تحديد موقعها:
arr2[0] = arr1[0];
ولكن ماذا لو أردنا تعيين جميع قيم المصفوفة إلى مصفوفة أخرى؟
مثال 1:
#include <iostream> using namespace std; int main() { const int s = 4; int arr1[s] = { 10,20,30,40 }; int arr2[s]; for (size_t i = 0; i <= s-1; i++) { arr2[i] = arr1[i]; } for (size_t i = 0; i < s; i++) { cout << arr2[i] << " "; } cout << endl; return 0; }
يقوم هذا الكود بتهيئة المصفوفة arr1 بالقيم، ثم ينسخ محتويات arr1 إلى مصفوفة أخرى arr2. وأخيرا، فإنه يطبع عناصر arr2. يوضح هذا كيفية نسخ محتويات مصفوفة إلى أخرى في لغة C++.
المخرجات:
10 20 30 40
مثال 2:
#include <iostream> using namespace std; int main() { const int s = 5; int arr[s] = { 100,200,200,500,0 }; int sum = 0; for (size_t i = 0; i < s; i++) { //sum += arr[i]; sum = sum + arr[i]; } cout << "sum = " << sum << endl; return 0; }
يحسب هذا الكود مجموع العناصر الموجودة في مجموعة الأعداد الصحيحة ثم يطبع النتيجة على شاشة المخرجات. يوضح كيفية استخدام حلقة للتكرار عبر مصفوفة وتجميع القيم.
المخرجات:
sum = 1000
مثال 3:
#include <iostream> using namespace std; int main() { const int s = 5; int arr[s] = { 100,200,200,500,100 }; long long d = 1; for (size_t i = 0; i < s; i++) { d *= arr[i]; } cout << "multi = " << d << endl; return 0; }
- long long d = 1; :يقوم هذا السطر بتهيئة متغير صحيح long long من d إلى 1. سيتم استخدام هذا المتغير لتجميع ناتج العناصر الموجودة في المصفوفة.
- long long هو نوع بيانات في C++ يمثل عددًا صحيحًا 64 بت. إنه مصمم لتخزين قيم عددية كبيرة جدًا. يمكنه الاحتفاظ بنطاق أوسع من القيم مقارنةً بالأعداد الصحيحة العادية أو الأعداد الصحيحة الطويلة.
- لذلك، العبارة long long d = 1؛ تعلن عن متغير اسمه d بنوع بيانات long long ويقوم بتهيئته بالقيمة 1. ويمكن بعد ذلك استخدام هذا المتغير لتخزين ومعالجة القيم الصحيحة الكبيرة في برنامجك.
باختصار، يحسب هذا الكود حاصل ضرب العناصر الموجودة في مصفوفة الأعداد الصحيحة ثم يطبع النتيجة على وحدة التحكم. يوضح كيفية استخدام حلقة للتكرار عبر مصفوفة وتجميع منتج عناصرها.
المخرجات:
multi = 200000000000
مثال 4:
#include <iostream> using namespace std; int main() { const int s = 5; int arr[s] = { 100,200,200,501,101 }; int sumEven, sumOdd = 0; for (size_t i = 0; i < s; i++) { if (arr[i] % 2 == 0) sumEven += arr[i]; else sumOdd += arr[i]; } cout << "Sum of even numbers = " << sumEven << endl; cout << "Sum of odd numbers = " << sumOdd << endl; return 0; }
يحسب هذا الكود مجموع الأرقام الزوجية والفردية بشكل منفصل في مصفوفة الأعداد الصحيحة ثم يطبع النتائج على شاشة المخرجات.
المخرجات:
Sum of even numbers = 500 Sum of odd numbers = 602
مثال 5:
#include <iostream> using namespace std; int main() { const int s = 6; int marks[s] = { 100,99,98,88,70,90 }; int sum = 0; for (size_t i = 0; i < s; i++) { sum += marks[i]; } cout << "Average = " << sum / s << endl; return 0; }
يحسب هذا الكود متوسط العلامات المخزنة في علامات مصفوفة الأعداد الصحيحة ثم يطبع المتوسط إلى شاشة المخرجات.
المخرجات:
Average = 90
مثال 6:
#include <iostream> using namespace std; int main() { const int s = 6; int arr[s] = { 22,100,95,101,200,90 }; int max = 0; for (size_t i = 0; i < s; i++) { if (arr[i] > max) max = arr[i]; } cout << "Maximum number = " << max << endl; return 0; }
يعثر هذا الكود على الحد الأقصى للقيمة بين الأرقام المخزنة في مجموعة الأعداد الصحيحة ثم يطبع القيمة القصوى إلى شاشة المخرجات.
المخرجات:
Maximum number = 200
مثال 7:
#include <iostream> using namespace std; int main() { int vector[5]; cout << "Enter 5 numbers: "; for (int i = 0; i < 5; i++) cin >> vector[i]; cout << endl; int Element; cout << "What is the element you are looking for? "; cin >> Element; bool Found = false; int i; for (i = 0; i < 5; i++) if (Element == vector[i]) { Found = true; break; } cout << endl; if (Found) cout << Element << " found at position " << i << endl; else cout << Element << " is not in this array!" << endl; return 0; }
في كود C++ هذا، أنت تأخذ مدخلات المستخدم لملء مصفوفة أعداد صحيحة "vector" بالحجم 5 ثم تبحث عن عنصر محدد داخل المصفوفة. دعونا نحلل الكود خطوة بخطوة:
- int vector[5];
- يعلن هذا السطر عن مصفوفة أعداد صحيحة "vector" بحجم 5، والتي يمكنها تخزين ما يصل إلى خمس قيم صحيحة. - Input Loop to Populate the Array:
- يطلب الكود من المستخدم إدخال خمسة أرقام ويخزنها في المصفوفة "vector" باستخدام حلقة "for" التي تتكرر خمس مرات. يتم تعيين كل إدخال إلى عنصر مختلف من المصفوفة. - int Element;
- يعلن هذا السطر عن متغير صحيح "Element"، والذي سيخزن القيمة التي يبحث عنها المستخدم. - Prompt for the Element to Search:
- يطلب الكود من المستخدم إدخال العنصر الذي يريد البحث عنه. - Search Loop:
– يتم استخدام حلقة `for` للبحث عن العنصر داخل المصفوفة. إذا تم العثور على العنصر، تنقطع الحلقة، ويتم تعيين علامة "Found" على "true". المتغير `i` يتتبع الموضع الذي تم العثور على العنصر فيه. - Output the Result:
- يتحقق الكود بعد ذلك من العثور على العنصر (على سبيل المثال، "Found" هو "true"). إذا تم العثور عليه، فإنه يعرض العنصر وموضعه في المصفوفة. إذا لم يتم العثور عليه، فإنه يعلم المستخدم أن العنصر غير موجود في المصفوفة.
باختصار، يسمح هذا الكود للمستخدم بإدخال خمسة أرقام في مصفوفة ثم البحث عن عنصر معين داخل المصفوفة. ويقدم ملاحظات حول ما إذا كان قد تم العثور على العنصر وموضعه في المصفوفة.
المصفوفة أحادية البعد One Dimensional Array (3) - تمرير مصفوفة إلى دالّة
في لغة C++، يمكنك تمرير مصفوفة إلى دالة، وهذا مفهوم أساسي عند العمل مع المصفوفات. عندما تقوم بتمرير مصفوفة إلى دالة، فإنك تسمح لهذه الدالة بالعمل على عناصر المصفوفة أو تعديل المصفوفة إذا لزم الأمر.
فيما يلي شرح لكيفية تمرير مصفوفة إلى دالة في لغة C++:
إعلان مصفوفة في دالة
Parameters المصفوفة: لتمرير مصفوفة إلى دالة، يجب عليك إعلان parameter الدالة كمصفوفة. يمكنك إعلان ذلك بطريقتين:
- استخدام حجم ثابت: إذا كنت تعرف حجم المصفوفة مسبقًا، فيمكنك تحديد الحجم في parameter الدالة، كما يلي:
void functionName(int arr[5]) { // Code to work with the array }
- استخدام مؤشر Pointer: يمكنك أيضًا تعريف parameter الدالة كمؤشر لعناصر المصفوفة دون تحديد الحجم، كما يلي:
void functionName(int *arr, int size) { // Code to work with the array }
في هذه الحالة، يمكنك تمرير حجم مصفوفة كـargument منفصلة.
تمرير المصفوفة إلى الدالة
استدعاء الدالة: عند استدعاء الدالة، يمكنك توفير المصفوفة كـargument. إذا قمت بتعريف parameter الدالة بحجم ثابت، فستقوم بتمرير المصفوفة مباشرة. إذا استخدمت مؤشرًا، فإنك تقوم بتمرير المصفوفة وحجمها كـarguments.
مثال مع parameter لمصفوفة ذات حجم ثابت:
int myArray[5] = {1, 2, 3, 4, 5}; functionName(myArray);
Example with a pointer parameter:
int myArray[5] = {1, 2, 3, 4, 5}; functionName(myArray, 5);
استخدام المصفوفة في الدالة
العمل مع المصفوفة: داخل الدالة، يمكنك الوصول إلى عناصر المصفوفة ومعالجتها حسب الحاجة. يمكنك استخدام مؤشرات المصفوفات (على سبيل المثال، arr[0]، arr[1]) للوصول إلى العناصر الفردية، ويمكنك استخدام الحلقات للتكرار عبر العناصر.
مثال للوصول إلى عناصر المصفوفة:
void functionName(int arr[5]) { for (int i = 0; i < 5; i++) { // Access and manipulate arr[i] } }
فيما يلي توضيح لتمرير مصفوفة إلى دوال لتحديد القيم في المصفوفة ثم طباعة تلك القيم:
#include <iostream> using namespace std; void set(int arr[], int s) { for (size_t i = 0; i < s; i++) { cout << "Enter array value: "; cin >> arr[i]; } } void print(int arr[], int s) { for (size_t i = 0; i < s; i++) { cout << arr[i] << " "; } } int main() { int a[5]; set(a, 5); print(a, 5); return 0; }
تسمح دالة المجموعة للمستخدم بتعيين القيم في المصفوفة، وتقوم دالة الطباعة بطباعة القيم المخزنة في المصفوفة. إنه مثال عملي لكيفية العمل مع المصفوفات والدوال في لغة C++.
تعديل المصفوفة
مثال 1:
يُظهر هذا الرمز تنفيذ خوارزمية "Bubble Sort". إن Bubble Sort عبارة عن خوارزمية فرز بسيطة تتنقل بشكل متكرر عبر القائمة المراد فرزها، وتقارن العناصر المتجاورة، وتبدل مواقعهم إذا كانوا بالترتيب الخاطئ. يتم تكرار المرور عبر القائمة حتى لا تكون هناك حاجة للتبديل، مما يشير إلى أن القائمة قد تم فرزها. إليك تفاصيل الكود:
#include <iostream> using namespace std; int main() { int vector[] = {6,5,4,1,2}; int t = 0; // Bubble Sort algorithm for (size_t i = 0; i < 5 - 1; i++) { for (size_t j = 0; j < 5 - i - 1; j++) { if (vector[j] > vector[j + 1]) { t = vector[j]; vector[j] = vector[j + 1]; vector[j + 1] = t; } } } for (size_t i = 0; i < 5; i++) { cout << vector[i] << endl; } return 0; }
- int vector[] = {6, 5, 4, 1, 2};:
تتم تهيئة vector إلى مصفوفة أعداد صحيحة بقيم غير مصنفة. - int t = 0;:
تم الإعلان عن استخدام المتغير الصحيح t لتبديل العناصر أثناء عملية الفرز. - خوارزمية Bubble Sort :
يتم استخدام حلقات for المتداخلة لتنفيذ خوارزمية Bubble Sort. تتحكم الحلقة الخارجية (i) في عدد التمريرات، بينما تقارن الحلقة الداخلية (j) العناصر المتجاورة وتبدلها إذا كانت تحتاج الترتيب. - إذا كان العنصر الموجود في (index ) j أكبر من العنصر الموجود في (index ) j + 1، فسيتم إجراء مبادلة لوضعهما في ترتيب تصاعدي.
- طباعة المصفوفة التي تم فرزها:
بعد اكتمال الفرز، يستخدم الكود حلقة for لطباعة العناصر التي تم فرزها بترتيب تصاعدي.
سيكون ناتج هذا الكود هو المصفوفة التي تم فرزها بترتيب تصاعدي، وهو نتيجة تطبيق خوارزمية Bubble Sort على المصفوفة الأولية غير المصنفة. لا يعد Bubble Sort خوارزمية الفرز الأكثر كفاءة لمجموعات البيانات الكبيرة، ولكن من السهل فهمها وتنفيذها.
مثال 2:
يقوم هذا الكود بفرز مصفوفة أعداد صحيحة باستخدام خوارزمية Bubble Sort. يقوم بفرز المصفوفة بترتيب تصاعدي ثم طباعة القيم التي تم فرزها. دعونا نحلل الكود خطوة بخطوة:
#include <iostream> using namespace std; const int s = 5; void sort(int arr[]) { int t = 0; for (size_t i = 0; i < 5 - 1; i++) { for (size_t j = 0; j < 5 - i - 1; j++) { if (arr[j] > arr[j + 1]) { t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t; } } } } void print(int vector[]) { for (size_t i = 0; i < 5; i++) { cout << vector[i] << endl; } } int main() { int vector[] = {0,-1,55,-5,-100}; sort(vector); print(vector); return 0; }
سيكون ناتج هذا الكود هو المصفوفة التي تم فرزها بترتيب تصاعدي، وهو نتيجة تطبيق خوارزمية Bubble Sort على المصفوفة الأولية غير المصنفة.
مثال 3:
يوضح هذا الكود كيفية عكس عناصر مصفوفة أعداد صحيحة باستخدام دالة reversArray. يقوم بعكس المصفوفة في مكانها ثم يطبع المصفوفة المعكوسة:
#include <iostream> using namespace std; void reversArray(int arr[], int s) { for (size_t i = 0; i < s / 2; i++) { swap(arr[i], arr[s - i - 1]); } } int main() { int vector[] = {3,2,4,5,6}; reversArray(vector, 5); for (size_t i = 0; i < 5; i++) { cout << vector[i] << " "; } return 0; }
سيكون ناتج هذا الكود هو المصفوفة المعكوسة، حيث يتم عكس العناصر في مكانها. ويتم تحقيق ذلك عن طريق تبديل العنصر الأول بالعنصر الأخير، والعنصر الثاني بالعنصر الثاني إلى الأخير، وهكذا. إنها طريقة بسيطة وفعالة لعكس ترتيب العناصر في المصفوفة.
ملحوظة !
إذا كنت لا ترغب في تعديل المصفوفة الخاصة بك، فيمكنك تهيئة قيمتها كقيمة ثابتة في الدالة. مثال:
void print(const int arr[], int s) { //body of the function }
بهذه الطريقة فإن أي محاولة لتعديل المصفوفة ستؤدي إلى حدوث خطأ.
المصفوفة أحادية البعد One Dimensional Array (4) - مصفوفة الأحرف
غالبًا ما يُشار إلى مجموعة من الأحرف في لغة C++ باسم "string". إنها بنية بيانات أساسية تستخدم لتخزين تسلسلات من الأحرف، مثل الكلمات أو الجمل أو حتى الأحرف الفردية. في لغة C++، يتم تمثيل الـstrings كمصفوفات من الأحرف، تنتهي بحرف فارغ خاص، '\0'، والذي يشير إلى نهاية الـstring.
لبدء حرف نستخدم اقتباسًا واحدًا مثل 'L'.
مثال 1:
char ch[5] = { 'a','b','c','d','e','\0' };
يُستخدم الحرف الفارغ ('\0') أو NULL أو 0 للإشارة إلى نهاية الـstring. تتم إضافة هذا الحرف تلقائيًا إلى نهاية الـstringالحرفي وهو ضروري لوظائف معالجة السلسلة المختلفة.
إذا قمت بإضافة أحرف بعد الحرف الفارغ فلن يقرأها البرنامج.
مثال 2:
#include <iostream> using namespace std; int main() { char ch[5]; cin >> ch; cout << ch << endl; return 0; }
يقرأ هذا الكود سلسلة من الأحرف في مصفوفة char ثم يطبع المصفوفة. ومع ذلك، هناك بعض الأشياء التي يجب ملاحظتها حول المشكلات والتحسينات المحتملة في الكود:
- حجم المصفوفة:
يبلغ حجم مصفوفة (char) 5. إذا قمت بإدخال أكثر من 4 أحرف، فسيؤدي ذلك إلى تجاوز سعة المخزن المؤقت، مما يتسبب في سلوك غير محدد. من الضروري التأكد من أن الإدخال لا يتجاوز حجم المصفوفة. - طريقة الادخال:
عند استخدام cin >> ch، فإنه يقرأ الأحرف حتى يواجه مسافة بيضاء. إذا كنت تريد قراءة سطر كامل (بما في ذلك المسافات)، فيجب عليك استخدام cin.getline(ch, size) أو cin.get(ch, size).
مثال 3:
#include <iostream> using namespace std; int main() { char ch[10]; // Read up to 9 characters into the char array cin.get(ch, 10); // Output the char array cout << ch << endl; return 0; }
يقرأ هذا الكود ما يصل إلى 9 أحرف (بالإضافة إلى الحرف الفارغ) في مصفوفة char باستخدام cin.get() ثم يطبع المصفوفة.
فيما يلي بعض النقاط التي يجب ملاحظتها:
- حجم المصفوفة:
يبلغ حجم مصفوفة (char ch) 10. وبما أن cin.get() يقرأ ما يصل إلى واحد أقل من الحجم المحدد (مع ترك مساحة للحرف الفارغ)، فإنه يقرأ بشكل فعال ما يصل إلى 9 أحرف. - طريقة الادخال:
يقرأ cin.get(ch, 10) الأحرف حتى تتم قراءة 9 أحرف، أو يتم العثور على حرف السطر الجديد، أو الوصول إلى نهاية الملف. ويتوقف عن القراءة بعد الوصول إلى عدد الأحرف المحدد أو مواجهة أحد هذه الشروط. - الحرف الفارغ NULL:
لم يتم إنهاء المصفوفة بشكل صريح بواسطة cin.get(); ومع ذلك، تقوم تدفقات C++ تلقائيًا بإلحاق حرف فارغ في نهاية الـstring عند القراءة في مصفوفة أحرف. لذلك، يتم التعامل مع المصفوفة ch بشكل فعال كـstring من النمط C.
المخرجات:
cout << ch << endl; يطبع محتويات مصفوفة char، ويعاملها كـstring منتهي بقيمة خالية. إذا تم إدخال أقل من 9 أحرف، فسيتم طباعة الأحرف التي تم إدخالها. إذا تم إدخال 9 أحرف، فسيتم طباعة تلك الأحرف التسعة متبوعة بالحرف الفارغ.
محددات المدخلات:
ضع في اعتبارك أن إدخال المستخدم يقتصر على 9 أحرف، وقد يؤدي تجاوز هذا الحد إلى سلوك غير متوقع. من الضروري التعامل مع تجاوزات المدخلات المحتملة بناءً على المتطلبات المحددة لبرنامجك.
مثال 4:
#include <iostream> using namespace std; int main() { char ch1[10], ch2[10]; cin.getline(ch1, 10); cin.getline(ch2, 10); cout << ch1 << endl; cout << ch2 << endl; return 0; }
يقرأ هذا الكود سطرين من الإدخال في مصفوفات char منفصلة ثم يطبع كل مصفوفة على سطر جديد.
فيما يلي بعض النقاط التي يجب ملاحظتها:
- حجم المصفوفة:
كل من ch1 و ch2 عبارة عن مصفوفات char بحجم 10. تقرأ الدالة cin.getline() الأحرف حتى تواجه حرف السطر الجديد أو تصل إلى الحجم المحدد (مع ترك مساحة للحرف الفارغ). - محددات المدخلات:
يقتصر إدخال المستخدم لكل سطر على 9 أحرف، وتتم إضافة الحرف الفارغ تلقائيًا بواسطة cin.getline(). - مساحات التسليم:
على عكس cin >>، يقرأ cin.getline() السطر بأكمله، بما في ذلك المسافات. هذا يسمح لك بإدخال strings بمسافات.
المخرجات:
cout << ch1 << endl; يطبع محتويات مصفوفة الأحرف الأولى، ويتعامل معها على أنها string منتهية بقيمة خالية.
وبالمثل، cout << ch2 << endl; يطبع محتويات مصفوفة char الثانية.
حرف السطر الجديد:
تتم قراءة كل استدعاء إلى cin.getline() حتى يواجه حرف السطر الجديد أو يصل إلى الحجم المحدد. يتم استهلاك حرف السطر الجديد ولكن لا يتم تخزينه في المصفوفات.
يتيح لك هذا الكود إدخال سطرين من النص (يصل كل منهما إلى 9 أحرف) ثم طباعة كل سطر على سطر جديد. إنها مناسبة لقراءة وعرض أسطر متعددة من النص، ومعالجة المسافات، وتجنب مشكلات تجاوز سعة المخزن المؤقت.
هناك بعض الدوال التي تعد جزءًا من مكتبة C القياسية وتستخدم لمعالجة strings نمط C، وهي عبارة عن مصفوفات من الأحرف. وفيما يلي شرح لكل دالة:
- strcpy (نسخ String):
الوصف: يتم استخدام الدالة strcpy لنسخ محتويات string نمط C إلى string آخر.
مثال:
#include <iostream> #include <cstdlib> using namespace std; int main() { char ch1[] = "AAA"; char ch2[4]; strcpy_s(ch2, ch1); cout << ch2 << endl; return 0; }
- دالة strcpy_s:
تعد الدالة strcpy_s جزءًا من مكتبة C القياسية وهي مصممة لتوفير نسخ أكثر أمانًا للـstring من خلال تضمين التحقق من الحجم. - حجم المخزن:
يتم تحديد حجم ch2 بشكل صريح على أنه 4، والذي يتضمن ثلاثة أحرف لمحتوى ch1 ("AAA") ومسافة إضافية واحدة للحرف الفارغ ('\0'). - نسخة الـString الآمنة:
على عكس الدالة strcpy القياسية، تتطلب strcpy_s منك توفير حجم المصفوفة (destsz). يتحقق مما إذا كانت هناك مساحة كافية في المصفوفة لاستيعاب الـstring المصدر والحرف الفارغ. إذا لم تكن هناك مساحة كافية، فلن تقوم الدالة بالنسخ وتقوم بإرجاع رمز خطأ.
المخرجات:
cout << ch2 << endl; تقوم بإخراج محتويات ch2، والتي يجب أن تكون "AAA" في هذه الحالة.
باستخدام strcpy_s، فإنك تتخذ خطوات لمنع مشكلات تجاوز سعة المخزن المؤقت التي قد تحدث إذا لم تكن المصفوفة كبيرة بما يكفي لاستيعاب محتويات الـstring المصدر. هذه ممارسة جيدة لتعزيز أمان ومتانة التعليمات البرمجية الخاصة بك.
- strcat (دمج String):
الوصف: يتم استخدام الدالة strcat لـدمج (إلحاق) محتويات string نمط C بسلسلة أخرى.
مثال:
#include <iostream> #include <cstdlib> using namespace std; int main() { char ch1[] = "AAA"; char ch2[4] = "HH"; strcat(ch2, ch1); cout << ch2 << endl; return 0; }
- دالة strcat:
تعد الدالة strcat جزءًا من مكتبة C القياسية وتُستخدم لدمج محتويات string نمط C بـstring آخر. - حجم المخزن:
يتم تحديد حجم ch2 بشكل صريح على أنه 4، والذي يتضمن حرفين ("HH") ومسافة إضافية واحدة لـ('\0'). - الدمج Concatenation:
strcat(ch2, ch1); يقوم بإلحاق محتويات ch1 بنهاية ch2. يبدأ بنسخ الأحرف من null terminator لـ ch2 إلى نهاية ch1 حتى يواجه الـ null terminator للـ ch1. يتم تحديث الـ null terminator لـ ch2 وفقًا لذلك.
المخرجات:
cout << ch2 << endl; تقوم العبارة بإخراج محتويات ch2 بعد الدمج. نظرًا لأن حجم ch2 محدد بـ 4، فيمكنه الاحتفاظ بـ "HH" والـnull terminator, ولكن يمكن استيعاب حرف إضافي واحد فقط من ch1 ("A").
Result:
ناتج هذا الكود هو "HHA"، وهو نتيجة لدمج "AAA" إلى نهاية المحتوى الموجود في ch2.
من المهم التأكد من أن المصفوفة بها مساحة كافية لاستيعاب الـstring الذي تم دمجه، واستخدام دوال مثل strcat يتطلب إدارة دقيقة لأحجام المخزن المؤقت لمنع تجاوز سعة المخزن المؤقت.
- strcmp (مقارنة String):
الوصف: يتم استخدام الدالة strcmp لمقارنة اثنان من الـ strings من نمط C بشكل معجمي.
ترجع الدالة strcmp قيمة عددية تشير إلى العلاقة المعجمية بين الـ strings الاثنين. يُرجع:
- 0 إذا كانت الـstrings متساوية،
- قيمة سالبة إذا كانت str1 أقل معجميًا من str2،
– قيمة موجبة إذا كانت str1 أكبر من الناحية المعجمية str2.
مثال:
#include <iostream> #include <cstdlib> using namespace std; int main() { cout << strcmp("abc", "abc") << endl; return 0; }
المخرجات:
تقوم عبارة cout بإخراج نتيجة المقارنة باستخدام strcmp. في هذه الحالة، بما أن الـstrings متساوية، فسيكون الناتج 0.
تُستخدم الدالة strcmp بشكل شائع لفرز الـstrings وعمليات القاموس والسيناريوهات الأخرى التي تحتاج فيها إلى تحديد الترتيب المعجمي للـstrings. من المهم التعامل مع النتيجة بشكل مناسب بناءً على متطلباتك المحددة في البرنامج.
- strlen (طول String):
الوصف: يتم استخدام الدالة strlen لتحديد طول string نمط C.
مثال:
#include <iostream> #include <cstdlib> using namespace std; int main() { char ch[] = "jjjjjjjjjjjjjjjjjjjjj"; cout << strlen(ch) << endl; return 0; }
المخرجات:
تقوم عبارة cout بإخراج نتيجة strlen(ch)، وهي طول الـstring النصية “jjjjjjjjjjjjjjjjjjjjj”. نظرًا لوجود 21 حرفًا في الـstring، سيكون الناتج 21.
تعتبر الدالة strlen مفيدة للحصول على طول الـstring النصية، ومن المهم ملاحظة أن الطول لا يتضمن الحرف الفارغ. تأكد من أن الـstring منتهي بشكل صحيح للحصول على نتائج دقيقة باستخدام strlen.
مصفوفة ثنائية الأبعاد
المصفوفة ثنائية الأبعاد في لغة C++ هي بنية تسمح لك بتخزين العناصر في شكل جدولي، منظمة في صفوف وأعمدة. إنها في الأساس مجموعة من المصفوفات.
فيما يلي شرح لمصفوفة ثنائية الأبعاد في لغة C++:
- الإعلان والتهيئة:
// Declaration of a 2D array int matrix[3][4]; // Initialization of a 2D array int anotherMatrix[2][3] = { {1, 2, 3}, {4, 5, 6} };
- البناء:
في المصفوفة ثنائية الأبعاد، يتم ترتيب العناصر في صفوف وأعمدة. يمثل الـindex الأول الصف، ويمثل الـindex الثاني العمود.
- تمثيل الذاكرة:
في الذاكرة، يتم تخزين مصفوفة ثنائية الأبعاد في كتلة متجاورة من الذاكرة. يتم تخزين عناصر كل صف معًا، ويتم تخزين الصفوف واحدًا تلو الآخر.
- الوصول إلى العناصر:
int value = matrix[1][2]; // Accessing the element in the second row and third column
- التهيئة باستخدام الحلقات المتداخلة:
العمليات المشتركة:
– التكرار من خلال العناصر باستخدام الحلقات المتداخلة.
- إجراء العمليات على كل عنصر.
- تمرير مصفوفة ثنائية الأبعاد إلى الدوال.
مثال 1:
#include <iostream> using namespace std; int main() { int a[3][4]; for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 4; j++) { cin >> a[i][j]; } } for (size_t r = 0; r < 3; r++) { for (size_t c = 0; c < 4; c++) { cout << a[r][c] << " "; } cout << endl; } return 0; }
يسمح هذا الكود للمستخدم بإدخال القيم في مصفوفة ثنائية الأبعاد [3] [4] ثم طباعة المصفوفة في شكل جدول.
النقاط الرئيسية:
- إدخال مصفوفة ثنائية الأبعاد:
تُستخدم الحلقات المتداخلة (for Loops) للتكرار على كل عنصر من عناصر المصفوفة ثنائية الأبعاد وإدخال القيم باستخدام cin. - مخرجات مصفوفة ثنائية الأبعاد:
يتم استخدام مجموعة أخرى من الحلقات المتداخلة للتكرار على كل عنصر من عناصر المصفوفة ثنائية الأبعاد وإخراج القيم باستخدام cout.
تطبع الحلقة الداخلية كل صف، وتنتقل الحلقة الخارجية إلى الصف التالي، مما يؤدي إلى إنشاء نموذج جدولي. - مخرجات الجدول:
الـcout << a[r][c] << ” “;i تقوم بطباعة كل عنصر من عناصر المصفوفة ثنائية الأبعاد متبوعة بمسافة.
الك cout << endl; تستخدم للانتقال إلى السطر التالي بعد طباعة كل صف. - تفاعل المستخدم:
يُطلب من المستخدم إدخال قيم لكل عنصر في المصفوفة ثنائية الأبعاد. ومن المتوقع أن يتم توفير المدخلات بطريقة حكيمة. - حجم المصفوفة:
يتم الإعلان عن المصفوفة a بثلاثة صفوف وأربعة أعمدة، مما يؤدي إلى إنشاء مصفوفة 3 × 4.
يسمح هذا الرمز للمستخدم بإدخال القيم بشكل تفاعلي في مصفوفة 3 × 4 ثنائية الأبعاد ثم يعرض المصفوفة بتنسيق جدولي.
مثال 2:
#include <iostream> using namespace std; int main() { // Initialization of a 2D array with predefined values int arr[2][4] = {{1, 1, 1, 1}, {2, 2, 2, 2}}; // Variable to store the sum of array elements int sum = 0; // Nested loops to iterate over each element of the 2D array for (size_t r = 0; r < 2; r++) { for (size_t c = 0; c < 4; c++) { // Accumulate the values to calculate the sum sum += arr[r][c]; } } // Output the sum of array elements cout << "sum = " << sum << endl; return 0; }
المخرجات:
في هذا المثال المحدد، يتم حساب المجموع على النحو التالي: 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 = 12.
سيكون الناتج المجموع = 12.
يعد هذا الكود توضيحًا بسيطًا لكيفية التكرار على العناصر في مصفوفة ثنائية الأبعاد وتنفيذ عملية أساسية، في هذه الحالة، حساب مجموع جميع العناصر.
مثال 3:
#include <iostream> using namespace std; int main() { int marks[10][5]; for (size_t r = 0; r < 10; r++) { for (size_t c = 0; c < 5; c++) { cout << "Enter mark " << c + 1 << " " << "for student " << r + 1 << ": " << endl; cin >> marks[r][c]; } } double s = 0; float avg[10]; for (size_t r = 0; r < 10; r++) { s = 0; for (size_t c = 0; c < 5; c++) { s += marks[r][c]; } avg[r] = s / 5; cout << "Average for student #" << r + 1 << " = "<< avg[r] << endl; } return 0; }
يقوم هذا الكود بجمع علامات 10 طلاب في 5 مواد، وحساب متوسط العلامة لكل طالب، ثم إخراج المتوسط لكل طالب.
مثال 4:
#include <iostream> using namespace std; int main() { char MTXchar[5][5] = {{'*','$','$','$','$'}, {'$','*','$','$','$'}, {'$','$','*','$','$'}, {'$','$','$','*','$'}, {'$','$','$','$','*'}}; for (size_t r = 0; r < 5; r++) { for (size_t c = 0; c < 5; c++) { if (c == r) cout << MTXchar[r][c]; else cout << " "; } cout << endl; } return 0; }
يعرف هذا الكود مصفوفة 5 × 5 (MTXchar) تحتوي على نجوم "على القطر الرئيسي وأحرف "$" في مكان آخر. ثم يقوم بعد ذلك بطباعة النجوم الموجودة على القطر الرئيسي فقط، مما ينتج عنه خط قطري مكون من "نجوم".
المخرجات:
يُخرج الكود سطرًا قطريًا من النجوم على شاشة المخرجات.
* * * * *
يتضمن تمرير مصفوفة ثنائية الأبعاد إلى دالة في لغة C++ تحديد المصفوفة كـparameter في إعلان الدالة. نظرًا لأن المصفوفة ثنائية الأبعاد هي في الأساس مصفوفة من المصفوفات، فأنت بحاجة إلى تحديد حجم الأعمدة (نظرًا لأن الصفوف معروفة ضمنيًا بناءً على عدد المصفوفات).
- إعلان الدالة:
عند الإعلان عن دالة تقبل مصفوفة ثنائية الأبعاد، فإنك تحتاج إلى تحديد parameter المصفوفة بالإضافة إلى حجم الأعمدة. لم يتم تحديد حجم الصفوف بشكل صريح.
void functionName(int arr[][COLS], size_t rows, size_t cols) { // Function logic using arr }
- int arr[][COLS]: تشير إلى أن الدالة تأخذ مصفوفة ثنائية الأبعاد من الأعداد الصحيحة مع عدد محدد من الأعمدة (COLS).
- size_trows: يمكن استخدام هذا الـ parameter لتمرير عدد الصفوف في المصفوفة.
– size_t cols: يمكن استخدام هذا الـparameter لتمرير عدد الأعمدة في المصفوفة
- إستدعاء الدالة:
عند استدعاء الدالة، تحتاج إلى توفير المصفوفة ثنائية الأبعاد الفعلية بالإضافة إلى عدد الصفوف والأعمدة.
int myArray[ROWS][COLS]; functionName(myArray, ROWS, COLS);
– myArray: اسم المصفوفة ثنائية الأبعاد التي تريد تمريرها.
- ROWS: عدد الصفوف في المصفوفة.
- COLS: عدد الأعمدة في المصفوفة.
مثال 5:
#include <iostream> using namespace std; void f(int arr[][5], int r) { for (size_t i = 0; i < r; i++) { for (size_t j = 0; j < 5; j++) { cout << arr[i][j] << " "; } cout << endl; } } int main() { int a[2][5] = {1,2,3,4,5, 10,20,30,40,50}; f(a, 2); return 0; }
تأخذ الدالة f مصفوفة ثنائية الأبعاد (arr) وعدد صفوفها (r) كـparameters.
يستخدم حلقات متداخلة للتكرار على كل عنصر من عناصر المصفوفة وطباعة العناصر في شكل جدول.
تستدعي الوظيفة الرئيسية الدالة f مع المصفوفة ثنائية الأبعاد a وعدد الصفوف (2) كـarguments.
المخرجات:
سيكون ناتج هذا الكود عبارة عن عناصر المصفوفة ثنائية الأبعاد المطبوعة في شكل جدول.
ستبدو المخرجات كما يلي:
1 2 3 4 5 10 20 30 40 50
- تبديل المصفوفة
تبديل المصفوفة هو عملية تقلب المصفوفة، وتبديل مؤشرات الصفوف والأعمدة في المصفوفة. بمعنى آخر، إذا كانت المصفوفة الأصلية تحتوي على عناصر في الموضع (i, j)، فستحتوي المصفوفة المنقولة على العنصر في الموضع (j, i).
مثال 6:
#include <iostream> using namespace std; int main() { int arr[3][3]; for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 3; j++) { cin >> arr[i][j]; } } cout << "Matrix is: \n"; for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 3; j++) { cout << arr[i][j] << " "; } cout << endl; } cout << "Transpose matrix is:\n"; for (size_t i = 0; i < 3; i++) { for (size_t j = 0; j < 3; j++) { cout << arr[j][i] << " "; } cout << endl; } return 0; }
يأخذ هذا الكود مدخلات المستخدم لمصفوفة 3 × 3، ويطبع المصفوفة الأصلية، ثم يطبع تبديلها.
النقاط الرئيسية:
- المدخلات:
تأخذ الحلقات المتداخلة مدخلات المستخدم لمصفوفة 3×3. - مخرجات المصفوفة الأصلية:
بعد أخذ المدخلات، يقوم الكود بإخراج المصفوفة الأصلية. - تبديل مخرجات المصفوفة:
مجموعة أخرى من الحلقات المتداخلة تنتج تبديل المصفوفة. تقوم بتبديل مؤشرات الصفوف والأعمدة.
مثال على المخرجات:
إذا أدخل المستخدم ما يلي:
1 2 3 1 2 3 1 2 3
ستكون المخرجات كالتالي:
Matrix is: 1 2 3 1 2 3 1 2 3 Transpose matrix is: 1 1 1 2 2 2 3 3 3
المؤشر - Pointer
في لغة C++، المؤشر هو متغير يقوم بتخزين عنوان الذاكرة لمتغير آخر. توفر المؤشرات آلية قوية للعمل مع الذاكرة وهياكل البيانات. إنها تسمح بالمعالجة المباشرة لعناوين الذاكرة وتمكن من إدارة الذاكرة بكفاءة.
إعلان المؤشرات
للإعلان عن مؤشر، يمكنك استخدام نوع البيانات متبوعًا بالعلامة النجمية (*) واسم المؤشر. على سبيل المثال:
int *ptr; // Declares a pointer to an integer double *dblPtr; // Declares a pointer to a double
تفعيل المؤشرات
يجب تفعيل المؤشرات قبل الاستخدام. يمكنك تفعيل المؤشر عن طريق تعيين عنوان متغير له. يتم استخدام عنوان الـoperator (&) للحصول على عنوان الذاكرة للمتغير.
int num = 10; int *ptr = # // Initializes ptr with the address of num
عدم مرجعية المؤشرات Dereferencing Pointers
Dereferencing a pointer إلى المؤشر يعني الوصول إلى القيمة المخزنة في عنوان الذاكرة الذي يشير إليه. يتم استخدام عامل الـderefrencing (*) لهذا الغرض.
int num = 10; int *ptr = # cout << *ptr; // Prints the value stored at the address pointed by ptr
مثال 1:
#include <iostream> using namespace std; int main() { // Declaration of a pointer to an integer int *ptr; // Declaration and initialization of an integer variable int val = 5; // Assigning the address of 'val' to the pointer 'ptr' ptr = &val; // Printing the address stored in the pointer cout << ptr << endl; // Printing the value stored at the address pointed by 'ptr' cout << *ptr << endl; // Return 0 to indicate successful execution return 0; }
يعلن هذا الكود عن مؤشر، ويقوم بتفعيل متغير عدد صحيح، ويعين عنوان هذا المتغير للمؤشر، ثم يطبع قيمة المؤشر (عنوان الذاكرة) والقيمة التي يشير إليها.
النقاط الرئيسية:
- الإعلان عن المؤشر:
int *ptr;: يعلن عن مؤشر لعدد صحيح. هذا المؤشر غير مفعل حاليًا. - الإعلان وتفعيل المتغير:
int val = 5;: يعلن عن متغير عدد صحيح يسمى val ويقوم بتفعيله وإعطاؤه القيمة 5. - تعيين عنوان للمؤشر:
ptr = &val;: يعين عنوان الذاكرة للمتغير val للمؤشر ptr. الآن "يشير" ptr إلى موقع الذاكرة الخاص بـ val. - طباعة قيمة المؤشر (عنوان الذاكرة):
cout << ptr << endl;: يطبع عنوان الذاكرة المخزن في المؤشر ptr. يتم تمثيل العنوان عادةً بتنسيق hexadecimal. - طباعة القيمة على العنوان المشار إليه بالمؤشر:
cout << *ptr << endl;: يستخدم عامل الـdereference (*) للوصول إلى القيمة المخزنة في عنوان الذاكرة المشار إليه بواسطة ptr. ويطبع القيمة المخزنة في المتغير val.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كما يلي:
0x7ffee3c86a4c // The memory address (this will vary on each run) 5 // The value stored at the memory address
مثال 2:
#include <iostream> using namespace std; int main() { int *p; int v = 9; p = &v; cout << *p << endl; return 0; }
يطبع هذا الكود عنوان القيمة التي يشير إليها المؤشر p باستخدام التعبيرات &*p.
cout << &*p << endl;: يستخدم عامل الـdereference (*) للوصول إلى القيمة المخزنة في عنوان الذاكرة المشار إليه بـ p. ثم يأخذ عامل التشغيل & عنوان هذه القيمة. بشكل أساسي، &*p يعادل p فقط، لذلك يطبع هذا السطر عنوان الذاكرة المخزن في المؤشر p.
Pointer part 2 (Array vs Pointer & Passing Pointer To Function)
المصفوفات في لغة C++
المصفوفة عبارة عن مجموعة من العناصر من نفس النوع المخزنة في مواقع متجاورة في الذاكرة. في لغة C++، يمكن أن تكون المصفوفات من أنواع بدائية (int، float، إلخ) أو أنواع محددة من قبل المستخدم (structures, classes). المصفوفات لها حجم ثابت، ويمكن الوصول إلى العناصر باستخدام الـindex.
int numbers[5] = {1, 2, 3, 4, 5};
المؤشرات في لغة C++
المؤشر هو متغير يقوم بتخزين عنوان الذاكرة لمتغير آخر. تسمح المؤشرات بتخصيص الذاكرة الديناميكية ومعالجتها. يتم استخدامها غالبًا لإدارة الذاكرة بكفاءة وللوصول إلى العناصر الموجودة في المصفوفات.
int x = 10; int *ptr = &x; // ptr now holds the address of x
المصفوفات والمؤشرات
في العديد من السياقات، تظهر المصفوفات والمؤشرات في لغة C++ سلوكًا مشابهًا. عند استخدام اسم مصفوفة في تعبير، فإنه يتحول إلى مؤشر إلى عنصره الأول. على سبيل المثال:
int arr[3] = {1, 2, 3}; int *ptr = arr; // Equivalent to &arr[0]
ومع ذلك، هناك اختلافات:
- لا يمكن إعادة تعيين متغير المصفوفة للإشارة إلى موقع ذاكرة مختلف، بينما يمكن للمؤشر ذلك.
- تحمل المصفوفات معلومات حول حجمها، في حين أن المؤشرات لا تعرف بطبيعتها حجم الذاكرة التي تشير إليها.
- يمكن استخدام المصفوفات مع عامل التشغيل sizeof لتحديد حجمها، لكن لا يمكن استخدام المؤشرات وحدها.
المؤشر حسابيًا
يمكن زيادة المؤشرات أو إنقاصها للتنقل عبر مصفوفة أو كتلة من الذاكرة.
int arr[5] = {1, 2, 3, 4, 5}; int *ptr = arr; cout << *ptr; // Prints the first element (1) ptr++; // Moves to the next element cout << *ptr; // Prints the second element (2)
مثال 1:
#include <iostream> using namespace std; int main() { int arr[7] = {11,22,33,44,55,66,77}; for (size_t i = 0; i < 7; i++) { cout << *(arr + i) << endl; } return 0; }
يقوم الكود بطباعة عناصر المصفوفة باستخدام المؤشر الحسابي.
cout << *(arr + i) << endl;: يستخدم حساب المؤشر للوصول إلى كل عنصر في المصفوفة. يحسب التعبير arr + i عنوان الذاكرة للعنصر i، ويقوم *(arr + i) بـdereference المؤشر للوصول إلى القيمة المخزنة في هذا العنوان.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
11 22 33 44 55 66 77
مثال 2:
#include <iostream> using namespace std; int main() { int arr[7] = {11,22,33,44,55,66,77}; int *ptr; ptr = arr; for (size_t i = 0; i < 7; i++) { cout << *ptr << " "; ptr++; } return 0; }
يحقق هذا الكود نفس الهدف كما في المثال السابق ولكنه يستخدم المؤشر (ptr) ومؤشرًا حسابيًا للتكرار عبر المصفوفة.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
11 22 33 44 55 66 77
تمرير المؤشرات إلى الدوال
عند تمرير مصفوفة إلى دالة، فإنك في الواقع تقوم بتمرير مؤشر إلى العنصر الأول في المصفوفة. ويرجع ذلك إلى تحول المصفوفة إلى مؤشر في arguments الدالة.
مثال 3:
يوضح هذا الكود استخدام المؤشرات والدالة التي تعدل القيمة التي تشير إليها.
#include <iostream> using namespace std; int fun(int *p) { *p = *p + 1; return *p; } int main() { int x = 1; int *ptr = &x; cout << fun(ptr) << endl; cout << x << endl; cout << fun(&x) << endl; cout << x << endl; return 0; }
النقاط الرئيسية:
- دالة fun:
int fun(int *p): يأخذ المؤشر إلى عدد صحيح كـparameter.
*p = *p + 1;: زيادة القيمة التي يشير إليها المؤشر.
return *p;: إرجاع القيمة التي تم تحديثها. - الدالة الرئيسة main:
int x = 1;: يعلن ويفعل متغيرًا صحيحًا x.
int *ptr = &x;: يعلن عن مؤشر ptr ويعين عنوان x له.
cout << fun(ptr) << endl;: يستدعي الدالة fun باستخدام المؤشر ptr ويطبع النتيجة. قيمة x الآن هي 2.
cout << x << endl;: يطبع القيمة الحالية لـ x بعد استدعاء الدالة. وهي الآن 2.
cout << fun(&x) << endl;: يستدعي الوظيفة fun بعنوان x مباشرة ويطبع النتيجة.
أصبحت قيمة x الآن 3.cout << x << endl;: يطبع القيمة النهائية لـ x. هو الآن 3.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
2 2 3 3
هياكل البيانات (struct)
في عالم البرمجة، تلعب هياكل البيانات دورًا محوريًا في كيفية تنظيم البيانات ومعالجتها بكفاءة. إنها بمثابة اللبنات الأساسية التي تمكننا من إنشاء برامج جيدة البناء والتنظيم. في هذا الشرح، سوف نتعمق في بنية بيانات أساسية واحدة في لغة C++ تُعرف باسم struct.
ما هي بنية البيانات؟
قبل أن نستكشف struct، دعونا نحدد ما هي بنية البيانات. بنية البيانات هي طريقة لتنظيم البيانات وتخزينها في ذاكرة الكمبيوتر لإجراء العمليات على تلك البيانات بكفاءة أكبر. حيث أنها توفر مخططًا لتخزين واسترجاع المعلومات.
مقدمة لـstruct في لغة C++
struct في C++ هي نوع بيانات محدد من قبل المستخدم يسمح لك بتجميع متغيرات أنواع البيانات المختلفة معًا تحت اسم واحد. فكر في الأمر على أنه إنشاء نوع بيانات مخصص مصمم خصيصًا لتلبية الاحتياجات المحددة لبرنامجك. وهذا يجعل التعليمات البرمجية الخاصة بك أكثر نمطية وتنظيمًا وأسهل للفهم.
بناء جملة struct:
بناء الجملة لتعريف struct واضح ومباشر:
struct MyStruct
struct MyStruct
مثال 1:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; }; int main() { car x; x.name = "Rolls-Royce"; x.color = "Red"; x.model = 2019; x.maxSpeed = 270; cout << x.name << endl; return 0; }
يحدد الكود struct مسماة سيارة تمثل معلومات حول سيارة، ويقوم بإنشاء مثيل لهذه البنية تسمى x لتخزين تفاصيل حول سيارة معينة.
النقاط الرئيسية:
- تعريف الـStruct (السيارة):
تحتوي الـstruct المسماة car على أربعة متغيرات للأعضاء: الاسم name واللون color والسرعة القصوى maxSpeed والطراز model. تقوم هذه المتغيرات بتخزين معلومات حول السيارة. - Instance Creation إنشاء المثيل (x):
يتم إنشاء المثيل (instance) لـstruct السيارة المسمى x في الدالة الرئيسية. يمكن لهذا المثيل تخزين معلومات حول سيارة معينة. - تعيين القيم:
يتم تعيين القيم لمتغيرات الأعضاء للمثيل x باستخدام notation النقطة (.). على سبيل المثال، x.name = "Rolls-Royce" يعين الاسم "Rolls-Royce" لمتغير عضو الاسم. - طباعة المعلومات:
يقوم البرنامج بطباعة اسم السيارة باستخدام cout << x.name << endl;. في هذه الحالة، سيتم إخراج "Rolls-Royce" إلى شاشة المخرجات.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
Rolls-Royce
مثال 2:
إليك طريقة أخرى لتفعيل القيم بناءً على المثال السابق:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; }; int main() { car x = {"BMW","Blue",250,2016}; cout << x.maxSpeed << endl; return 0; }
المخرجات:
250
مثال 3:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; }; int main() { car x = {"BMW","Blue",250,2016}; car y = x; cout << y.name << endl; return 0; }
إنشاء المثيل وتفعيله (x وy):
- يتم إنشاء مثيل لـstruct السيارة المسمى x بالقيم الأولية.
- يتم إنشاء مثيل آخر يسمى y وتفعيله بقيم x. هذه نسخة من الأعضاء، وكل عضو في y يحصل على قيمة العضو المقابل في x.
مثال 4:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; }y; int main() { car x = {"BMW","Blue",250,2016}; car y = {"Mercedes","Red",300,2016}; if (x.maxSpeed > y.maxSpeed) cout << "car x is faster than car y"; else cout << "car y is faster than car x"; return 0; }
- إعلان المثيل العالمي (y):
تم الإعلان عن مثيل لـstruct السيارة المسمى y في النطاق العالمي. وهذا يعني أنه يمكن الوصول إليه في جميع أنحاء البرنامج. - إنشاء المثيل وتفعيله (x وy المحلي):
- يتم إنشاء مثيل محلي لـstruct السيارة المسماة x بقيم أولية.
- يتم إنشاء مثيل محلي آخر يسمى y بقيم مختلفة. هذا y محلي للدالة الرئيسية ويغطي على y العام ضمن هذا النطاق. - المقارنة والمخرجات:
يقوم البرنامج بمقارنة السرعات القصوى للسيارات x و y ويطبع رسالة تشير إلى السيارة الأسرع.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستعتمد على القيم المخصصة لـ maxSpeed في x وy. بالنسبة للقيم المقدمة، ستكون المخرجات:
car y is faster than car x
تمرير 'struct' إلى دالّة:
مثال 5:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; }; void f(car f) { cout << "Name = " << f.name << endl; cout << "Color = " << f.color << endl; } int main() { car v = {"No name","Red",160,2000}; f(v); return 0; }
النقاط الرئيسية:
- تعريف الدالة (f):
تأخذ الدالة f السيارة كـparameter وتطبع معلومات حول السيارة، تحديدًا الاسم واللون. - إنشاء المثيل وتفعيله (v):
يتم إنشاء مثيل لبنية السيارة المسماة v بالقيم الأولية. - استدعاء الدالة (f(v)):
يستدعي البرنامج الدالة f مع مثيل v كـargument.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
Name = No name Color = Red
مثال 6:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; }; car read_return(car&s) { cout << "Enter car name:\n"; cin >> s.name; cout << "Enter car color:\n"; cin >> s.color; cout << "Enter car maximum speed:\n"; cin >> s.maxSpeed; cout << "Enter car model:\n"; cin >> s.model; return s; } int main() { car v; read_return(v); car h; h = v; cout << h.name << endl; return 0; }
النقاط الرئيسية:
- تعريف الدالة (read_return):
تأخذ الدالة read_return مرجع السيارة كـparameter، وتقرأ المدخلات لملء حقولها، وترجع struct السيارة المعدلة. - استدعاء الدالة (read_return(v)):
يستدعي البرنامج الدالة read_return مع مثيل v كمرجع، مما يسمح للدالة بتعديل قيم v. - التعيين والطباعة:
- يتم تعيين قيم v إلى h.
- يقوم البرنامج بطباعة اسم h.
المخرجات:
إذا قمت بتشغيل هذا البرنامج وأدخلت قيمًا عند مطالبتك بذلك، فسوف تعتمد المخرجات على الإدخال المقدم. على سبيل المثال:
Enter car name: BMW Enter car color: Blue Enter car maximum speed: 250 Enter car model: 2022 BMW
مثال 7:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; void fun(car n) { cout << n.name << endl; } }; int main() { car v = {"Kia"}; v.fun(v); return 0; }
النقاط الرئيسية:
- تعريف الدالة الضمنية (fun):
يتم تعريف الدالة الضمنية fun داخل هيكل السيارة.
يأخذ السيارة كـparameter ويطبع اسمها.
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
Kia
مثال 8:
#include <iostream> #include <string> using namespace std; struct car { string name; string color; int maxSpeed; int model; void fun(car n) { cout << n.name << endl; } }; int main() { car*h; car b = {"Toyota","Red",170,2008}; h = &b; cout << h->color << endl; return 0; }
النقاط الرئيسية:
- إعلان المؤشر وتفعيله (h):
تم الإعلان عن مؤشر لسيارة اسمها h. - إنشاء المثيل وتفعيله (b):
يتم إنشاء مثيل لبنية السيارة المسماة b وتهيئته بقيم محددة. - تعيين المؤشر (h = &b):
يتم تعيين عنوان b للمؤشر h. - Dereferencing المؤشر (h->color):
يقوم البرنامج بطباعة لون السيارة المشار إليها بالحرف h باستخدام عامل السهم (->).
المخرجات:
إذا قمت بتشغيل هذا البرنامج، فإن المخرجات ستكون كالتالي:
Red
النص String
في لغة C++، النص عبارة عن سلسلة من الأحرف الممثلة باستخدام class النص. على عكس نصوص النمط C، والتي هي عبارة عن مصفوفات من الأحرف، فإن نصوص C++ ديناميكية وتوفر مستوى أعلى من التجريد. يعد class النص جزءًا من مكتبة القوالب القياسية C++ (STL)، مما يجعلها ملائمة وقوية للتعامل مع النصوص.
الإعلان عن النصوص وتفعيلها:
يمكنك الإعلان عن نص باستخدام الـstring keyword وتفعيلها بطرق مختلفة:
// Declare and initialize strings string str1 = "Hello, "; // Initialization using a string literal string str2("world!"); // Initialization using a constructor // Concatenate strings string result = str1 + str2;
عند استخدام نص في لغة C++، يجب عليك تضمين مكتبته بهذه الطريقة:
#include <string>
مثال:
#include <iostream> #include <string> using namespace std; int main() { string x = "Ahmad", y; y.assign(x); cout << "y = " << y << endl; return 0; }
تعيين نص string:
- النص y;: يعلن عن متغير نص آخر y.
- y.assis(x);: يستخدم طريقة التعيين لنسخ محتويات النص x إلى النص y.
المخرجات:
y = Ahmad
Methods النصوص في C++:
فيما يلي شرح methods النص المختلفة في لغة C++، بالإضافة إلى الـparameters والأمثلة الخاصة بها:
- at Method:
الوصف: إرجاع الحرف في مكان محدد في النص.
Parameters: يأخذ مكانًا index كـparameter.
مثال:string x = "Ahmad"; cout << x.at(0) << endl; // A
- length Method:
الوصف: إرجاع عدد الأحرف في النص.
Parameters: لا يوجد parameters.
مثال:string x = "Ahmad"; cout << x.length() << endl; // 5
- size Method:
الوصف: مثل الطول، يُرجع عدد الأحرف في النص.
Parameters: لا يوجد parameters.
مثال:string x = "Ahmad"; cout << x.size() << endl; // 5
- substr Method:
الوصف: إرجاع نص فرعي من النص الأصلي.
Parameters: يأخذ index البداية والطول كـparameters.
مثال:string x = "Ahmad"; cout << x.substr(1,3) << endl; // hma
- swap Method:
الوصف: تبديل محتوى نصين.
Parameters: يأخذ نصًا آخر كـparameter.
مثال:string x = "Ahmad", y = "Ali"; cout << x.swap(y) << endl; // Swaps the content of x and y
- find Method:
الوصف: يبحث عن التواجد الأول لنص فرعي في النص.
Parameters: يأخذ نصًا فرعيًا كـparameter.
مثال:string x = "Ahmad"; cout << x.find('a') << endl; // 3
- rfind Method:
الوصف: يبحث عن التواجد الأخير لنص فرعي في النص.
Parameters: يأخذ نصًا فرعيًا كـparameter.
مثال:string x = "Ahmad"; cout << x.rfind('a') << endl; // 3
- erase Method:
الوصف: مسح أحرف من النص.
Parameters: يأخذ index البداية ويعده كـparameters.
مثال:string x = "Ahmad"; cout << x.erase(0,3) << endl; // ad
- replace Method:
الوصف: استبدال جزء من النص بنص آخر.
Parameters: تأخذ index البداية والطول ونص للاستبدال كـparameters.
مثال:string x = "Ahmad"; cout << x.replace(1,4,"li") << endl; // Ali
- insert Method:
الوصف: يقوم بإدراج أحرف في النص.
Parameters: يأخذ index البداية والنص لإدراجها كـparameters.
مثال:string x = "Ahmad"; cout << x.insert(5," Ali") << endl; // Ahmad Ali
هذه بعض methods النصوص الشائعة الاستخدام في لغة C++. تذكر تضمين لاستخدام هذه الطرق.
البرمجة كائنية التوجه OOP in C++
البرمجة الشيئية (OOP) هي اختصار يرمز إلى البرمجة كائنية التوجه
في البرمجة الإجرائية، يكمن التركيز في كتابة الإجراءات أو الوظائف المصممة لتنفيذ العمليات على البيانات. في المقابل، تدور البرمجة الموجهة للكائنات حول إنشاء كائنات objects تحتوي على البيانات والوظائف.
توفر البرمجة الموجهة للكائنات العديد من المزايا مقارنة بالبرمجة الإجرائية:
- عادةً ما يكون تنفيذ البرمجة كائنية التوجه OOP أسرع وأكثر وضوحًا.
- توفر البرمجة كائنية التوجه OOP بنية محددة جيدًا لتنظيم البرامج.
- تعمل OOP على تعزيز مبدأ "لا تكرر نفسك Don’t Repeat Yourself" (DRY) في لغة C++، مما يقلل من تكرار التعليمات البرمجية ويعزز إمكانية صيانة التعليمات البرمجية وتعديلها وتصحيح الأخطاء.
- تسهل البرمجة كائنية التوجه OOP تطوير التطبيقات القابلة لإعادة الاستخدام بدرجة كبيرة مع تقليل حجم التعليمات البرمجية وجداول زمنية أقصر للتطوير.
كنصيحة مفيدة، يتضمن الالتزام بمبدأ "لا تكرر نفسك" (DRY) تحديد مقاطع التعليمات البرمجية الشائعة واستخراجها داخل التطبيق، ووضعها مركزيًا لإعادة استخدامها بدلاً من تكرارها عبر قاعدة التعليمات البرمجية.
مراجعة الدّالّة
هيكل برنامج C++
#include <iostream> using namespace std; int main() { cout << "Welcome to OOP in C++" << endl; return 0; }
دعونا نحلل بنية كل سطر أوامر والغرض منه في الكود السابق :
#include <iostream>
البنية: يبدأ هذا السطر بتوجيه #include للمعالج المسبق، متبوعًا بملف الـ(heading) المحاط بأقواس زاوية.
الغرض: يتضمن هذا السطر مكتبة دفق الإدخال/الإخراج (iostream)، والتي تعتبر ضرورية لتنفيذ عمليات الإدخال والإخراج في C++. يوفر دوال مثل cin للإدخال و cout للإخراج.
using namespace std;
البنية: استخدام مساحة الاسم std؛ يعلن السطر أن الكود سيستخدم مساحة الاسم std.
الغرض: تحتوي مساحة الاسم std على مكونات مكتبة C++ القياسية، بما في ذلك cout وendl المستخدم في التعليمات البرمجية. ومن خلال تضمين هذا السطر، يمكنك استخدام هذه المكونات دون تحديد مساحة الاسم بشكل صريح في كل مرة تستخدمها فيها.
int main() {
البنية: يمثل هذا السطر بداية الدالة الرئيسية main، وهي نقطة الدخول لكل برنامج C++.
الغرض: الدالة الرئيسية هي المكان الذي يبدأ فيه تنفيذ البرنامج. إنه مطلوب في كل برنامج C++، والكود الموجود داخل الأقواس المتعرجة {} يحدد نص الدالة الرئيسية.
cout << "Welcome to OOP in C++" << endl;
البنية: يستخدم هذا السطر كائن cout لطباعة النص "Welcome to OOP in C++" إلى شاشة المخرجات. يتم استخدام عامل التشغيل << لتمرير النص إلى المخرجات.
الغرض: هذا السطر مسؤول عن عرض رسالة ترحيب على شاشة المخرجات، تشير إلى أن البرنامج يركز على البرمجة كائنية التوجه (OOP) في لغة C++.
return 0; }
البنية: الإسترجاع بقيمة 0؛ يشير السطر إلى نهاية الدالة الرئيسية. يتم إرجاع الرقم 0 إلى نظام التشغيل، مما يشير إلى تنفيذ البرنامج بنجاح.
الغرض: الإسترجاع بقيمة 0؛ يعد السطر طريقة شائعة للإشارة إلى الإنهاء الناجح للبرنامج. عادةً ما يتم إرجاع القيمة 0 إلى نظام التشغيل للإشارة إلى أن البرنامج تم تنفيذه بدون أخطاء.
المكتبات في لغة C++
في لغة C++، المكتبة عبارة عن مجموعة من الدوال والفئات والإجراءات المترجمة مسبقًا والتي يمكن استخدامها بواسطة البرنامج. توفر هذه المكتبات مجموعة من الدوال التي يمكن استخدامها لأداء المهام الشائعة، بدءًا من عمليات الإدخال/الإخراج الأساسية إلى الحسابات الرياضية المعقدة. توفر المكتبات طريقة لنموذجية التعليمات البرمجية، وتعزيز إعادة استخدام التعليمات البرمجية، وتبسيط عملية التطوير من خلال توفير حلول جاهزة لمختلف المهام.
فيما يلي بعض الجوانب الرئيسية للمكتبات في C++:
- مكتبة النماذج القياسية (STL):
تعد مكتبة C++ القياسية، والتي يشار إليها غالبًا باسم مكتبة النماذج القياسية (STL)، جزءًا أساسيًا من لغة C++. وتتضمن نطاقًا واسعًا من الخوارزميات العامة (مثل الفرز والبحث) وهياكل البيانات (مثل المتجهات والقوائم والخرائط) التي يتم تنفيذها باستخدام القوالب. تعمل STL على تبسيط البرمجة من خلال توفير حلول فعالة وعامة للمشاكل الشائعة. - الملفات الرأسية Header Files:
يتم توزيع مكتبات C++ عادةً كملفات رأسية (بامتداد .h أو .hpp) وملفات تنفيذ (بملحق .cpp). تحتوي الملفات الرأسية على إعلانات الدوال والفئات والكيانات الأخرى التي يمكن استخدامها في برنامجك، بينما تحتوي ملفات التنفيذ على الكود الفعلي. - مكتبة IOStream:
تعد مكتبة iostream جزءًا أساسيًا من مكتبة C++ القياسية وتوفر دوال لعمليات الإدخال والإخراج. تتضمن cin (للإدخال) وcout (للإخراج)، من بين فئات الدفق الأخرى. فيما يلي مثال لاستخدام مكتبة iostream:#include <iostream> int main() { std::cout << "Hello, C++!" << std::endl; return 0; }
- مكتبة الـMath:
توفر مكتبة cmath مجموعة من الدوال الرياضية، مثل الدوال المثلثية واللوغاريتمية والدوال الأسية. - المكتبات المعرفة من قبل المستخدم:
وبصرف النظر عن المكتبات القياسية، يمكنك إنشاء مكتباتك الخاصة لتغليف وتنظيم التعليمات البرمجية الخاصة بك. يتضمن ذلك إنشاء ملفات رأسية تعلن عن الدوال والفئات، وملفات التنفيذ التي تحدد سلوكها. يمكنك بعد ذلك تضمين مكتبتك المخصصة في برامج C++ الأخرى.
الدالة في لغة C++
في مجال البرمجة، تلعب الدوال دورًا محوريًا في تنظيم التعليمات البرمجية، وتعزيز إمكانية إعادة الاستخدام، وتعزيز النهج المنظم لحل المشكلات. في لغة C++، الدالة هي وحدة قائمة بذاتها من التعليمات البرمجية مصممة لأداء مهمة محددة أو تحقيق هدف معين. فهي تحتوي على مجموعة من التعليمات، مما يسمح لك بتقسيم البرنامج المعقد إلى أجزاء أصغر وأكثر قابلية للإدارة.
المفاهيم الرئيسية
1. Modularity:
تتيح الدوال تحليل البرنامج إلى وحدات أصغر وأكثر قابلية للإدارة. تتعامل كل دالة مع جانب محدد من الوظيفة العامة، مما يعزز بنية التعليمات البرمجية المعيارية والمنظمة.
2. قابلية إعادة الاستخدام:
بمجرد تعريف الدالة، يمكن إعادة استخدامها في أجزاء مختلفة من البرنامج أو حتى في برامج أخرى. وهذا يعزز مبدأ "لا تكرر نفسك" (DRY)، مما يوفر الوقت والجهد.
3. التجريد:
توفر الدوال مستوى من التجريد، مما يسمح لك بالتركيز على الوظائف عالية المستوى دون التورط في تفاصيل التنفيذ. يعمل هذا التجريد على تحسين إمكانية قراءة التعليمات البرمجية وتبسيط عملية تصحيح الأخطاء.
4. Parameters وقيم الإرجاع Return:
يمكن أن تقبل الدوال parameters الإدخال وقيم الإرجاع، مما يسمح بالتعليمات البرمجية الديناميكية والتفاعلية. توفر الـparameters طريقة لتمرير المعلومات إلى دالة، بينما تسمح قيم الإرجاع للدوال بإرسال النتائج مرة أخرى إلى كود الاستدعاء.
بناء الجملة الأساسي
تتبع دالة C++ النموذجية بناء الجملة الأساسي هذا:
returnType functionName(parameterType1 parameterName1, parameterType2 parameterName2, ...) { // Function body: Code to perform the desired task // Optionally, a return statement to provide a result back to the caller }
دعونا نحلل المكونات:
returnType: يحدد نوع البيانات التي ستعيدها الدالة (إن وجدت).
functionName: المعرف الفريد للدالة.
parameters: قيم الإدخال التي تتلقاها الدالة.
functionBody: مجموعة التعليمات التي تحدد سلوك الدالة.
مثال 1:
#include <iostream> using namespace std; int sum(int x, int y) { return x + y; } int main() { cout << sum(10,20) << endl; return 0; }
برنامج C++ هذا هو برنامج بسيط يقوم بحساب وعرض مجموع رقمين.
- تحديد دالة المجموع:
int sum(int x, int y) { return x + y; }
تقوم مجموعة التعليمات البرمجية التالية بإنشاء دالة تسمى sum. تأخذ رقمين (x و y) وتعيد مجموعهما.
- استخدم الدالة Sum في الجزء الرئيسي main:
int main() { cout << sum(10, 20) << endl; return 0; }
هنا، الجزء الرئيسي main يشبه مدير البرنامج. يقول: "مرحبًا، احسب مجموع 10 و20 باستخدام دالة المجموع، وأرني النتيجة على الشاشة." إن endl يشبه الضغط على Enter على لوحة المفاتيح؛ ينتقل إلى السطر التالي.
- تشغيل البرنامج:
عند تشغيل هذا البرنامج يقوم بعملية الحساب (10 + 20) داخل دالة المجموع، ثم يعرض النتيجة (30) على الشاشة. الإرجاع return 0; يخبر الكمبيوتر أن كل شيء سار على ما يرام.
بالطبع يمكن للمستخدم إدخال البيانات المراد حسابها باستخدام cin باستخدام هذا الأمر:
cin >> x >> y;
يمكن أن يكون هذا الأمر موجودًا داخل الدالة الرئيسية، ولكن من الأفضل استخدامه داخل الدالة نفسها للحصول على كود أكثر وضوحًا.
انظر المثال التالي:
#include <iostream> using namespace std; int sum(int x, int y) { cout << "Enter two numbers:\n"; cin >> x >> y; return x + y; } int main() { int x = 0, y = 0; int z = sum(x, y); cout << "The sum is = " << z << endl; return 0; }
يطلب هذا الكود من المستخدم إدخال رقمين، وجمعهما معًا، ثم عرض النتيجة.
- تحديد دالة المجموع:
int sum(int x, int y) { cout << "Enter two numbers:\n"; cin >> x >> y; return x + y; }
تعرف مجموعة التعليمات البرمجية هذه دالة تسمى sum. يطلب من المستخدم إدخال رقمين، وقراءة هذه الأرقام من لوحة المفاتيح (cin)، ثم إرجاع مجموع تلك الأرقام.
- استخدم الدالة Sum في الجزء الرئيسي main:
int main() { int x = 0, y = 0; int z = sum(x, y); cout << "The sum is = " << z << endl; return 0; }
في الجزء الرئيسي main، يبدأ بإعطاء x وy القيمة 0. ثم يستدعي دالة المجموع، ويمرر x وy كوسائط. تحصل الدالة sum على مدخلات المستخدم، وتضيف الأرقام، وترجع النتيجة. يتم تخزين النتيجة في z. وأخيرًا، يعرض المجموع برسالة باستخدام cout.
لذا، باختصار، هذا البرنامج يشبه آلة حاسبة تفاعلية بسيطة. يطلب منك رقمين، ويجمعهم معًا، ثم يظهر لك النتيجة على الشاشة.
يعد توفير القيم الأولية للمتغيرات أمرًا بالغ الأهمية لتجنب الأخطاء.
مثال 2:
#include <iostream> using namespace std; double avg(double m1, double m2, double m3) { return (m1 + m2 + m3) / 3; } int main() { cout << avg(100, 60, 50); return 0; }
يقوم هذا الكود بحساب وعرض متوسط ثلاثة أرقام.
تأخذ الدالة avg ثلاثة أرقام (m1 وm2 وm3)، وتقوم بجمعها معًا وتقسيمها على 3 وإرجاع النتيجة. هذه هي الطريقة التي تحسب بها متوسط ثلاثة أرقام.
في الجزء الرئيسي، يتم استدعاء الدالة avg مباشرةً باستخدام ثلاثة أرقام (100 و60 و50) كوسيطات. ثم يستخدم cout لعرض النتيجة، وهي متوسط هذه الأرقام الثلاثة.
مثال 3:
#include <iostream> using namespace std; int max(int n1, int n2, int n3) { int m = n1; if (m < n2) m = n2; if (m < n3) m = n3; return m; } int main() { cout << max(100, 20, 800); return 0; }
يقوم هذا الكود بعرض ثلاث أرقام وطباعة الرقم الأكبر بينهم.
تأخذ الدالة max ثلاثة أرقام (n1 وn2 وn3) وتستخدم العبارات الشرطية (if) لتحديد العدد الأكبر قيمة فيما بينها. يبدأ باعتبار n1 القيمة الكبرى الأولية ثم يستخدم العبارات الشرطية (if) لمقارنتها بالقيم الأخرى. كلما تم استيفاء الشرط، يتم تحديث القيمة القصوى. ثم يتم إرجاع القيمة الكبرى.
في الجزء الرئيسي، يستدعي مباشرة الدالة max بثلاثة أرقام (100، 20، و800) كوسيطات. ثم يستخدم cout لعرض النتيجة، وهي القيمة الأكبر بين هذه الأرقام الثلاثة.
يمكننا إيجاد قيمة صُغرى بين القيم الأخرى باستخدام نفس المنطق ولكن مع تغيير الشروط.
مثال 4:
#include <iostream> using namespace std; void p() { cout << "myName\n"; } int main() { p(); return 0; }
يعرف هذا الكود دالة ثم يستدعي تلك الدالة في الجزء الرئيسي من البرنامج.
تستخدم الدالة المسماة p كلمة أساسية void قبل اسم الدالة مما يعني أن هذه الدالة لا تُرجع أي قيمة. داخل الدالة، يستخدم cout لعرض النص "myName" على الشاشة.
في الجزء الرئيسي، يتم إستدعاء الدالة p. عند استدعاء دالة، ينتقل البرنامج إلى تلك الدالة، وينفذ المهام الموجودة بداخلها، ثم يعود إلى حيث تم استدعاؤها. في هذه الحالة، ينتقل إلى الدالة p، ويطبع "myName" على الشاشة، ثم يعود إلى الدالة الرئيسية.
- الآن دعونا نتحدث عن دالة void:
الدالة void هي دالة لا تُرجع قيمة. في هذا المثال، تعتبر الدالة p دالة void لأنها تحتوي على الكلمة الأساسية void قبل اسمها.
تكون دوال void مفيدة عندما تريد من إحدى الدوال تنفيذ مهمة أو مجموعة من المهام دون الحاجة إلى تقديم نتيجة إلى جزء البرنامج الذي يستدعيها.
مثال 5:
#include <iostream> using namespace std; int mul(int x = 10, int y = 50) { return x*y; } int main() { cout << mul(10) << endl; cout << mul(10, 2) << endl; cout << mul() << endl; return 0; }
يوضح هذا الرمز استخدام الـarguments الافتراضية في دالة.
تقوم الدالة المسماة mul بضرب رقمين (x وy). الجزء المثير للاهتمام هو أن كلا من x وy لهما قيم افتراضية معينة (10 و50 على التوالي). إذا لم يتم توفير هذه القيم عند استدعاء الدالة، فسيتم استخدام هذه القيم الافتراضية.
في الجزء الرئيسي، يتم استدعاء الدالة mul عدة مرات باستخدام مجموعات مختلفة من الـarguments:
mul(10): يستخدم القيمة الافتراضية 50 لـ y، لذلك يتم ضرب 10 في 50.
mul(10, 2): يستخدم كلا من 10 و2 المقدمين كـarguments، لذلك يقوم بضرب 10 في 2.
mul(): نظرًا لعدم توفير أي وسائط، فإنه يستخدم كلا القيمتين الافتراضيتين (10 و50)، لذلك يقوم بضرب 10 في 50.
- الآن دعونا نتحدث عن الـarguments الافتراضية:
تسمح لك الـarguments الافتراضية بتوفير قيم للـparameters في إعلان الدالة، لذلك إذا لم يقدم المتصل قيمًا، فسيتم استخدام القيم الافتراضية.
في هذا المثال، تحتوي الدالة mul على قيم افتراضية لكل من x وy، مما يجعلها مرنة عند استدعاء الدالة بأعداد مختلفة من الـarguments.
مثال 6:
#include <iostream> using namespace std; void ref(int &x, int &y) { x += 1; y += 1; } int main() { int p = 0, t = 0; ref(p, t); cout << "p = " << p << endl; cout << "t = " << t << endl; return 0; }
يوضح هذا الرمز مفهوم "الاستدعاء حسب المرجع".
تحتوي الدالة المسماة ref على جزء مثير للاهتمام وهو أنها تأخذ المراجع (&x و&y) كـparameters، مما يشير إلى أنها ستقوم مباشرة بتعديل قيم المتغيرات التي تم تمريرها إليها.
في الجزء الرئيسي، يعلن عن متغيرين (p وt) ويقوم بتهيئتهما إلى 0. ثم يستدعي الدالة ref مع p وt كوسيطتين. داخل الدالة ref، يتم زيادة كل من p وt بمقدار 1. وأخيرًا، يتم طباعة قيم p وt بعد استدعاء الدالة باستخدام cout.
- الآن دعونا نتحدث عن الإستدعاء حسب المرجع:
في لغة C++، عندما تقوم بتمرير parameters إلى دالة حسب المرجع (باستخدام &)، تتلقى الدالة عناوين الذاكرة الخاصة بالمتغيرات الفعلية، وليس فقط نسخًا من قيمها.
وهذا يعني أن أي تغييرات يتم إجراؤها على الـparameters داخل الدالة تؤثر بشكل مباشر على المتغيرات الأصلية خارج الدالة.
في هذا المثال، تقوم الدالة ref بتعديل قيم p وt مباشرة لأنه يتم تمريرهما حسب المرجع.
الدوال الجاهزة Built-in functions
الدوال الجاهزة في C++ هي دوال محددة مسبقًا توفرها لغة برمجة C++. تعد هذه الدوال جزءًا من مكتبة C++ القياسية وتقدم حلولاً جاهزة للمهام الشائعة. وهي مصممة لتنفيذ عمليات محددة ويمكن للمبرمجين استخدامها دون الحاجة إلى كتابة التعليمات البرمجية الكاملة لتلك العمليات.
إليك شرح بسيط:
- أدوات جاهزة:
تشبه الدوال الجاهزة الأدوات المتوفرة بالفعل لتستخدمها. بدلاً من إنشاء هذه الأدوات من الصفر، يمكنك ببساطة استخدام الدوال الجاهزة لتنفيذ مهام متنوعة. - جزة من مكتبة C++ القياسية:
هذه الدوال هي جزء من مكتبة C++ القياسية، وهي عبارة عن مجموعة من الأدوات الدوال التي تأتي مع كل C++ compiler. لا تحتاج إلى إنشاء أو تثبيت أي شيء إضافي لاستخدامها. - العمليات المشتركة:
تم تصميم الدوال الجاهزة لتنفيذ عمليات شائعة مثل الإدخال/الإخراج، والحسابات الرياضية، ومعالجة السلسلة، والمزيد. تتضمن الأمثلة cout وcin للطباعة والقراءة من شاشة النتائج، وsqrt للجذر التربيعي، وstrlen للحصول على طول السلسلة. - توفير الوقت:
باستخدام الدوال الجاهزة، يمكنك توفير الكثير من الوقت والجهد. لا تحتاج إلى كتابة تعليمات برمجية معقدة للمهام التي يتم التعامل معها بالفعل بواسطة هذه الدوال. - الاتساق عبر المنصات:
نظرًا لأن هذه الدوال جزء من المكتبة القياسية، فإنها توفر طريقة متسقة لتنفيذ العمليات عبر الأنظمة الأساسية والمترجمين المختلفين. وهذا يجعل التعليمات البرمجية الخاصة بك أكثر قابلية للنقل ويضمن أنها تعمل بشكل مماثل على أنظمة مختلفة.
في جوهرها، الدوال الجاهزة في لغة C++ تشبه صندوق الأدوات الذي يأتي مع اللغة. أنها توفر مجموعة من الأدوات التي يمكنك استخدامها لجعل مهام البرمجة الخاصة بك أسهل وأسرع وأكثر توحيدًا. عندما تصبح أكثر دراية بـ C++، ستكتشف هذه الدوال وتستفيد منها لتبسيط التعليمات البرمجية الخاصة بك.
فيما يلي بعض الدوال الجاهزة الأكثر شيوعًا في لغة C++:
- دوال الإدخال والإخراج Input/Output Functions:
cout: يستخدم لعرض المخرجات على شاشة النتائج.
cin: يستخدم لأخذ المدخلات من المستخدم من خلال شاشة المخرجات. - دوال العمليات الرياضية:
()sqrt: حساب الجذر التربيعي.
()abs: إعادة القيمة المطلقة لعدد ما.
()pow: رفع العدد إلى قوّة. - دوال النصوص string:
()strlen: إرجاع طول السلسلة.
()strcpy: نسخ نص string إلى نص string آخر.
()strcat: جمع نصّين. - دوال الأحرف:
()isalpha: التحقق مما إذا كان الحرف هو حرف أبجدي.
()isdigit: يتحقق مما إذا كان الحرف رقمًا.
()toupper: تحويل الحرف إلى أحرف كبيرة. - دوال المصفوفات:
()sizeof: إرجاع حجم نوع البيانات أو المصفوفة.
()فرز: فرز العناصر في مصفوفة بترتيب تصاعدي. - دوال الذاكرة:
()malloc: يخصص مقدارًا محددًا من الذاكرة ديناميكيًا.
()free: لتحرير الذاكرة التي تم تخصيصها مسبقًا باستخدام malloc. - دوال الوقت والتاريخ:
()time: إرجاع الوقت الحالي بالثواني.
()ctime: تحويل قيمة زمنية إلى تمثيل نص.
مثال 7:
#include <iostream> #include <cmath> #include <algorithm> using namespace std; int main() { cout << abs(-10) << endl; cout << max(10, 20) << endl; cout << max(max(10, 20), 30) << endl; int x = 10, y = 20; swap(x,y); cout << "x = " << x << endl; cout << "y = " << y << endl; return 0; }
يوضح هذا الكود استخدام العديد من الدوال الجاهزة من مكتبات و.
- abs(-10): حساب القيمة المطلقة لـ -10 وطباعة النتيجة (10) على شاشة المخرجات.
- (10، 20)max: يبحث عن القيمة الأكبر بين عددين (10 و 20) ويطبع النتيجة (20) على شاشة المخرجات.
- max(max(10, 20), 30): يقارن بين ثلاثة أرقام (10، 20، و30) ويجد العدد الأكبر بينها. يقوم بطباعة النتيجة (30) إلى شاشة المخرجات.
- (x، y)swap: مبادلة قيم x و y. بعد هذا السطر، x سيكون 20، و y سيكون 10.
المخرجات:
10 20 30 x = 20 y = 10
مراجعة المصفوفات
ما هي المصفوفة؟
المصفوفة عبارة عن بنية بيانات أساسية في لغة C++ تسمح لك بتخزين عناصر متعددة من نفس نوع البيانات تحت اسم واحد. يمكن الوصول إلى هذه العناصر باستخدام الـindex، مما يجعل المصفوفات أداة قوية للتعامل مع مجموعات البيانات بكفاءة.
الإعلان والتهيئة:
في لغة C++، يتضمن الإعلان عن المصفوفة تحديد نوع بيانات عناصرها واسم المصفوفة. يمكن إجراء التهيئة أثناء الإعلان أو لاحقًا باستخدام بيانات الدالة. دعونا نلقي نظرة على بناء الجملة:
// Declaration int myArray[5]; // Initialization during declaration int anotherArray[] = {1, 2, 3, 4, 5}; // Accessing elements cout << anotherArray[2]; // Output: 3
Array Indexing فهرسة المصفوفة:
تبدأ فهرسة المصفوفات من 0، مما يعني أنه يتم الوصول إلى العنصر الأول باستخدام الـindex 0، والثاني باستخدام الـindex 1، وهكذا. كن حذرًا حتى لا تصل إلى عناصر خارج حدود المصفوفة، حيث قد يؤدي ذلك إلى سلوك غير محدد.
التلاعب بالمصفوفات:
الدوران من خلال مصفوفة:
يعد التكرار عبر المصفوفة عملية شائعة. يمكنك استخدام حلقة for للوصول إلى كل عنصر:
for (int i = 0; i < 5; ++i) { cout << myArray[i] << " "; }
حجم المصفوفة:
يعد تحديد حجم المصفوفة أمرًا ضروريًا لتجنب أخطاء الفهرس خارج الحدود. يمكنك استخدام عامل التشغيل sizeof أو حساب الحجم باستخدام عدد العناصر وحجم نوع البيانات:
int size = sizeof(anotherArray) / sizeof(anotherArray[0]);
مثال 1:
#include <iostream> using namespace std; int main() { int arr[7]; for (int i = 0; i < 7; i++) { cin >> arr[i]; } for (int i = 0; i < 7; i++) { cout << arr[i] << " "; } return 0; }
يأخذ هذا البرنامج مدخلات لمجموعة من الأعداد الصحيحة ثم يطبع القيم المدخلة.
مثال 2:
#include <iostream> using namespace std; int main() { int arr[7]; for (int i = 0; i < 7; i++) { cin >> arr[i]; } for (int i = 0; i < 7; i++) { cout << "index" << i << " = " << arr[i]; cout << endl; } cout << endl; return 0; }
هذا البرنامج عبارة عن تطبيق بسيط للـconsole يأخذ مدخلات لمجموعة من 7 أعداد صحيحة من المستخدم ثم يعرض كل عنصر مع موقعه.
مثال 3:
#include <iostream> using namespace std; int main() { char g[5]; cin.get(g, 5); cout << g << endl; return 0; }
يوضح هذا البرنامج قراءة مصفوفة أحرف من إدخال المستخدم باستخدام cin.get().
char g[5];
يعلن هذا السطر عن مصفوفة أحرف تسمى g يمكنها استيعاب 5 أحرف.
cin.get(g, 5);
يستخدم هذا السطر cin.get() لقراءة ما يصل إلى 4 أحرف (نظرًا لأن حجم المصفوفة هو 5، يتم حجز مساحة واحدة للـnull terminator \0) من المستخدم ويخزنها في مصفوفة الأحرف g. ويتوقف عن القراءة عندما يواجه حرف السطر الجديد، أو نهاية الدفق، أو عند قراءة العدد المحدد من الأحرف.
باختصار، يطالب هذا البرنامج المستخدم بإدخال ما يصل إلى 4 أحرف (باستثناء الـnull terminator) ثم يطبع الأحرف المدخلة باستخدام cout.
The null terminator
غالبًا ما يتم تمثيله بالحرف \0 (شرطة مائلة عكسية متبوعة بصفر)، وهو حرف خاص يستخدم في C وC++ للإشارة إلى نهاية النص. من الضروري لوظائف معالجة النص تحديد مكان انتهاؤه في الذاكرة.
في الذاكرة، يتم تمثيل النص على شكل مجموعة من الأحرف. يتم وضع الـnull terminator في نهاية هذا المصفوفة للإشارة إلى حدود النص. عندما يقوم برنامج ما بمعالجة نص، يمكنه الاستمرار في قراءة الأحرف حتى يواجه الـnull terminator، مما يدل على نهاية بيانات النص.
على سبيل المثال:
const char myString[] = "Hello";
سيتم تخزين هذا في الذاكرة على النحو التالي:
H e l l o \0
هنا، يتبع الـ(null terminator) \0 أحرف النص، مما يشير إلى نهايته. تستخدم الدوال التي تعمل على النصوص، مثل تلك الموجودة في مكتبة C القياسية أو مكتبة C++ القياسية، الـ(null terminator) لتحديد طول النص والتعرف على نهايته عند إجراء العمليات.
مثال 4:
#include <iostream> using namespace std; int main() { char g1[10]; char g2[10]; cin.getline(g1, 10); cin.getline(g2, 10); cout << g1 << endl; cout << g2 << endl; return 0; }
يوضح هذا البرنامج استخدام الدالة cin.getline() لقراءة أسطر الإدخال في مصفوفات الأحرف ثم طباعتها.
char g1[10]; char g2[10];
تعلن هذه السطور عن مصفوفتين من الأحرف، g1 وg2، كل منهما قادر على استيعاب ما يصل إلى 9 أحرف بالإضافة إلى الـ( null terminator) \0.
cin.getline(g1, 10); cin.getline(g2, 10);
تستخدم هذه السطور cin.getline() لقراءة أسطر الإدخال من المستخدم في مصفوفات الأحرف g1 وg2. تأخذ الدالة 2 arguments: المصفوفة لتخزين الإدخال (g1 أو g2) والحد الأقصى لعدد الأحرف المراد قراءتها (9 في هذه الحالة، مع ترك مسافة واحدة للـnull terminator). تقوم دالة getline بقراءة الأحرف حتى تواجه حرف السطر الجديد ('\n') أو تصل إلى الحد المحدد.
باختصار، يتيح هذا البرنامج للمستخدم إدخال سطرين من النص، يصل طول كل منهما إلى 9 أحرف، ثم يقوم بطباعة الأسطر المدخلة. استخدام cin.getline() يضمن قراءة الإدخال كخط، ويتجنب البرنامج المشكلات المحتملة مع المخزن المؤقت للإدخال.
مثال 5:
#include <iostream> #include <cstring> using namespace std; int main() { char a1[] = "A"; char a2[] = "B"; cout << strcmp(a1, a2); return 0; }
يقوم برنامج C++ هذا بمقارنة نصين باستخدام الدالة strcmp من مكتبة C القياسية.
يتضمن هذا الرمز ملفات الـheader الضرورية لعمليات الإدخال/الإخراج (iostream) والدوال المرتبطة بالنص(cstring). يتم تضمين cstring للدالة strcmp.
char a1[] = "A"; char a2[] = "B";
تعلن هذه الأسطر وتهيئ مصفوفتين من الأحرف، a1 وa2. يحتوي كل مصفوفة على حرف واحد ويتم إنهاؤه تلقائيًا بواسطة المترجم. تذكر أن النص في C/C++ هو في الأساس مجموعة من الأحرف المنتهية بالحرف الفارغ ('\0').
cout << strcmp(a1, a2);
يستخدم هذا السطر الدالة strcmp لمقارنة النصوص المخزنة في a1 وa2. تتم طباعة النتيجة على شاشة المخرجات. ترجع الدالة strcmp قيمة عددية:
- إذا كانت النصوص متساوية، فإنها ترجع 0.
- إذا كانت a1 أقل من a2 من الناحية المعجمية، فإنها تُرجع قيمة سالبة.
- إذا كانت a1 أكبر من a2 من الناحية المعجمية، فإنها تُرجع قيمة موجبة.
تتم طباعة نتيجة المقارنة على شاشة المخرجات. سيكون هذا إما 0 إذا كانت النصوص متساوية، أو قيمة سالبة إذا كانت a1 أقل من a2، أو قيمة موجبة إذا كانت a1 أكبر من a2.
مصفوفات متعددة الأبعاد:
C++ supports multi-dimensional arrays, which can be thought of as arrays of arrays. A 2D array, for example, can be declared and accessed like this:
int matrix[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
نقاط إضافيّة:
تمرير المصفوفات إلى الدوالّ:
عند تمرير مصفوفة إلى دالة، تحتاج إلى استخدام المؤشرات. إليك مثال سريع:
void printArray(int arr[], int size) { for (int i = 0; i < size; ++i) { cout << arr[i] << " "; } }
مصفوفات مكتبة النماذج القياسية (STL):
للحصول على المزيد من الوظائف المتقدمة، فكر في استخدام فئة المصفوفة من مكتبة النماذج القياسية C++ (STL). ويوفر ميزات إضافية وفحوصات السلامة.
#include <array> std::array<int, 5> stlArray = {1, 2, 3, 4, 5};
مثال 6:
#include <iostream> #include <cstring> using namespace std; void search(int arr[], int s, int k) { bool g = false; for (size_t i = 0; i < s; i++) { if (arr[i] == k) g = true; } if (g) cout << "Found\n"; else cout << "Not Found\n"; } int main() { int arr[] = {22, 55, 88, 99, 1, 0, 7}; search(arr, 7, 55); return 0; }
يحدد هذا البرنامج دالة تسمى search تبحث عن قيمة محددة في مصفوفة أعداد صحيحة. تقوم الدالة الرئيسية بعد ذلك باستدعاء دالة search هذه للتحقق من وجود قيمة معينة في المصفوفة المحددة.
تأخذ الدالة المسماة search ثلاث( parameters): arr (مصفوفة أعداد صحيحة)، s (حجم المصفوفة)، وk (القيمة المطلوب البحث عنها).
يقوم بتهيئة المتغير المنطقي g إلى false، والذي سيتم استخدامه لتتبع ما إذا كانت القيمة k موجودة في المصفوفة.
ثم تتكرر الدالة عبر المصفوفة باستخدام حلقة for. إذا كان العنصر الحالي يساوي القيمة المستهدفة k، فإنه يضبط g على true.
بعد تنفيذ الحلقة، تتحقق من قيمة g وتطبع إما “Found” أو “Not Found” بناءً على ما إذا تم العثور على القيمة المستهدفة في المصفوفة.
تقوم الدالة الرئيسية بتهيئة مصفوفة عددية تحتوي على قيم.
ثم يستدعي دالة search، ويمرر المصفوفة، وحجم المصفوفة (7)، والقيمة المطلوب البحث عنها (55).
اعتمادًا على ما إذا كانت القيمة 55 موجودة في المصفوفة، سيقوم البرنامج بإخراج إما "Found" أو "Not Found".
مثال 7:
#include <iostream> using namespace std; void sort(int arr[], int s) { int t; for (size_t i = 0; i < s - 1; i++) { for (size_t j = 0; j < s - 1; j++) { if (arr[j] > arr[j+1]) { t = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = t; } } } } int main() { int t; int arr[] = {22, 55, 88, 99, 1, 0, 7}; sort(arr, 7); for (size_t i = 0; i < 7; i++) { cout << arr[i] << " "; } return 0; }
يطبق هذا البرنامج خوارزمية فرز بسيطة تُعرف باسم Bubble Sort.
تطبق دالة الفرز خوارزمية فرز الفقاعات لفرز مجموعة من الأعداد الصحيحة.
يأخذ (two parameters): arr (المصفوفة الصحيحة التي سيتم فرزها) وs (حجم المصفوفة).
تستخدم الدالة حلقات for متداخلة للتكرار عبر المصفوفة. تقارن الحلقة الداخلية العناصر المتجاورة وتتبادلها إذا كانت في ترتيب خاطئ، مما يدفع العنصر الأكبر نحو نهاية المصفوفة.
تتكرر العملية حتى يتم فرز المصفوفة بأكملها بترتيب تصاعدي.
تقوم الدالة الرئيسية بتهيئة مصفوفة عددية تحتوي على قيم.
ثم تقوم بعد ذلك باستدعاء وظيفة الفرز، وتمرير المصفوفة وحجمها (7)، والتي تقوم بفرز المصفوفة باستخدام خوارزمية فرز الفقاعات.
وأخيرًا، تتم طباعة المصفوفة التي تم فرزها باستخدام حلقة for.
سيقوم البرنامج بإخراج المصفوفة التي تم فرزها: 0 1 7 22 55 88 99
الخلاصة:
يعد إتقان المصفوفات في لغة C++ خطوة حاسمة لكي تصبح مبرمجًا ماهرًا. إنها بمثابة اللبنات الأساسية لهياكل البيانات والخوارزميات الأكثر تعقيدًا. إذا وجدت هذه المراجعة مفيدة، فلا تنس مراجعة مقاطع الفيديو التعليمية الخاصة بنا على [Adel Nasim] للحصول على أمثلة عملية وأفكار عملية.
الفئات Classes
ما هو الـclass ؟
في لغة C++، الـclass هو نوع بيانات محدد من قبل المستخدم يقوم بتغليف البيانات والدوال التي تعمل على تلك البيانات. إنه بمثابة مخطط لإنشاء الـobjects، وهي خصائص للـclass. تخيل الـclass كقالب يحدد بنية الـobjects وسلوكها.
للإعلان عن class في C++، يمكنك استخدام الكلمة المفتاحية class.
بمجرد تعريف class، يمكنك إنشاء objects منه. الـobjects تمثل خصائص الـclass، ولكل منها مجموعة البيانات الخاصة بها.
في لغة C++، تدعم الـclasses الصفات التعريفية للوصول مثل public, private, وprotected. تتحكم هذه الصفات في إمكانية رؤية الـclass members وإمكانية الوصول إليهم.
مثال 1:
#include <iostream> #include <cstring> #include <algorithm> using namespace std; class car{ private: char name[15]; char color[10]; int maxSpeed; int model; public: void setName(char n[]) { strcpy_s(name, n); } void setColor(char n[]) { strcpy_s(color, n); } void setMaxSpeed(int m) { maxSpeed = m; } void setModel(int m) { model = m; } char* getName() { return name; } char* getColor() { return color; } int getMaxSpeed() { return maxSpeed; } int getModel() { return model; } void print() { cout << "name = " << name << "\n" << "color = " << color << "\n" << "maxspeed = " << maxSpeed << "\n" << "model = " << model << "\n"; } }; int main() { car x; x.setName("kia"); x.setColor("red"); x.setMaxSpeed(300); x.setModel(2017); x.print(); return 0; }
يحدد كود C++ هذا class بسيط يسمى سيارة يمثل معلومات حول السيارة، مثل اسمها ولونها وسرعتها القصوى وطرازها. يقوم البرنامج بإنشاء خصائص لهذا الـclass، ويحدد سماته، ويطبع التفاصيل باستخدام دوال الأعضاء. دعونا نحلل الكود خطوة بخطوة:
- تعريف الـclass المسمى بـcar
class car { private: char name[15]; char color[10]; int maxSpeed; int model; public: // Member functions for setting attributes void setName(char n[]) { strcpy_s(name, n); } void setColor(char n[]) { strcpy_s(color, n); } void setMaxSpeed(int m) { maxSpeed = m; } void setModel(int m) { model = m; } // Member functions for retrieving attributes char* getName() { return name; } char* getColor() { return color; } int getMaxSpeed() { return maxSpeed; } int getModel() { return model; } // Member function to print details void print() { cout << "name = " << name << "\n" << "color = " << color << "\n" << "maxspeed = " << maxSpeed << "\n" << "model = " << model << "\n"; } };
- Private Members: يحتوي الـclass على متغيرات الـprivate member (الاسم واللون والحد الأقصى للسرعة والطراز)، والتي لا يمكن الوصول إليها إلا داخل الـclass.
- Public Members: يوفر الـclass دوال الـpublic member (setName، وsetColor، وsetMaxSpeed، وما إلى ذلك) لتعيين واسترجاع قيم هذه المتغيرات الخاصة.
- الدالة الرئيسة main:
int main() { car x; // Setting attributes of the car x.setName("kia"); x.setColor("red"); x.setMaxSpeed(300); x.setModel(2017); // Printing the details using the print() member function x.print(); return 0; }
– إنشاء الكائن object: يتم إنشاء كائن x من نوع car.
- سمات الإعداد: يتم تعيين سمات السيارة (الاسم واللون والسرعة القصوى والطراز) باستخدام وظائف الأعضاء (setName وsetColor وsetMaxSpeed وsetModel).
– تفاصيل الطباعة: تتم طباعة تفاصيل السيارة باستخدام دالة عضو الطباعة التي تعرض قيم سمات السيارة.
مخرجات هذا البرنامج ستكون كالتالي:
name = kia color = red maxspeed = 300 model = 2017
يوضح هذا الاستخدام البسيط لـclass في لغة C++، والتي تتضمن البيانات والسلوك المتعلق بالسيارة. يوفر الـclass طرقًا لتعيين واسترجاع السمات، وتعزيز التغليف وإخفاء البيانات. هذا مفهوم أساسي في البرمجة كائنية التوجه.
مثال 2:
#include <iostream> using namespace std; class triangle{ private: float base; float height; public: void setBase_height(float b, float h) { base = b; height = h; } float area() { return 0.5 * base * height; } void print() { cout << "base = " << base << endl << "height = " << height << endl << "area = " << area() << endl; } }; int main() { triangle ob; ob.setBase_height(5, 10); ob.print(); return 0; }
يحدد هذا الرمز class يسمى triangle الذي يمثل مثلثًا. يحتوي الـclass على متغيرات أعضاء خاصة لقاعدة المثلث وارتفاعه، ويوفر دوال الأعضاء لإعداد هذه القيم وحساب المساحة وطباعة تفاصيل المثلث. دعونا نحلل الكود خطوة بخطوة:
- تعريف الـclass المسمى بـtriangle:
class triangle { private: float base; float height; public: // Member function to set base and height void setBase_height(float b, float h) { base = b; height = h; } // Member function to calculate the area of the triangle float area() { return 0.5 * base * height; } // Member function to print details of the triangle void print() { cout << "base = " << base << endl << "height = " << height << endl << "area = " << area() << endl; } };
- Private Members: يحتوي الـclass على متغيرات الـprivate member (القاعدة والإرتفاع)، والتي لا يمكن الوصول إليها إلا داخل الـclass.
- Public Members: يوفر الـclass دوال public member (setBase_height, area, وprint) لتعيين القاعدة والارتفاع، وحساب المساحة، وطباعة تفاصيل المثلث.
- الدالة الرئيسة main:
int main() { triangle ob; // Setting base and height of the triangle ob.setBase_height(5, 10); // Printing the details of the triangle ob.print(); return 0; }
- إنشاء الكائن Object : يتم إنشاء كائن ob من النوع triangle.
- ضبط القاعدة والارتفاع: يتم استدعاء دالة العضو setBase_height لتعيين القاعدة على 5 والارتفاع على 10 للمثلث.
- تفاصيل الطباعة: يتم استدعاء دالة عضو الطباعة لطباعة تفاصيل المثلث بما في ذلك القاعدة والارتفاع والمساحة المحسوبة.
مخرجات هذا البرنامج ستكون كالتالي:
base = 5 height = 10 area = 25
يوضح هذا الاستخدام الأساسي لـclass في لغة C++ لتمثيل البيانات المتعلقة بالمثلث ومعالجتها. يقوم الـclass بتغليف البيانات والسلوك المتعلق بالمثلث، مما يعزز التغليف ويجعل الكود أكثر تنظيماً وقابلية لإعادة الاستخدام. يقوم البرنامج بحساب وعرض مساحة المثلث باستخدام دوال الأعضاء المتوفرة.
أداة البناء Constructors
ما هو الـConstructor؟
في لغة C++، الـconstructor هو دالة عضو خاصة داخل فئة class يتم استدعاؤها تلقائيًا عند إنشاء كائن object من تلك الفئة. والغرض الأساسي منه هو تهيئة أعضاء بيانات الكائن object أو إجراء أي إعداد ضروري للكائن. يتأكد المنشئون من أن الكائن يبدأ بحالة صالحة.
- اسم الـConstructor:
الـConstructors لديهم نفس اسم الـclass الذي ينتمون إليه. - لا توجد قيمة إرجاع:
لا يُرجع الـConstructors أي قيمة، ولا حتى void. - إعطاء قيمة للـobject:
يتم استخدام الـConstructors لإنشاء object جديد وإعداده.
أنواع الـConstructors:
- Default/ Empty Constructor:
يتم إنشاؤه تلقائيًا بواسطة المترجم compiler إذا لم يتم تعريف أي constructor بشكل صريح.
إنشاء أعضاء الـobject بإستخدام قيم إفتراضية.
بناء الجملة: ClassName() { /* Initialization code */ }
مثال 1:#include <iostream> #include <cstdlib> using namespace std; class Triangle { private: int base; int height; public: //empty constructor, no arguments are required Triangle() { cout << "first constructor\n"; } void setBase_Height(int b, int h) { base = b; height =h; } float area() { return 0.5*base*height; } void print() { cout << "base = " << base << endl << "height = " << height << endl << "area = " << area() << endl; } }; int main() { Triangle ob; ob.setBase_Height(10, 5); ob.print(); return 0; }
تم تعريف (empty constructor) (Triangle()). يتم استدعاؤه تلقائيًا عند إنشاء object وطباعة "first constructor".
- Parameterized Constructor:
يقبل parameters أثناء إنشاء الـobject لتخصيص الحالة الأولية.
يوفر المرونة للـobjects للبدء بقيم مختلفة.
بناء الجملة: ClassName(type param1, type param2, …) { /* Initialization code */ }
مثال 2:#include <iostream> #include <cstdlib> using namespace std; class Triangle { private: int base; int height; public: //empty constructor, no arguments are required Triangle() { cout << "first constructor\n"; base = 0; height = 0; } //parameterized constructor Triangle(int b, int h) { base = b; height = h; cout << "parameterized constructor\n"; } void setBase_Height(int b, int h) { base = b; height = h; } float area() { return 0.5*base*height; } void print() { cout << "base = " << base << endl << "height = " << height << endl << "area = " << area() << endl; } }; int main() { Triangle ob(5, 10); //ob.setBase_Height(10, 5); ob.print(); return 0; }
Parameterized Constructor:
يقبل (parameters) (int b وint h) للتهيئة المخصصة.
يطبع “parameterized constructor” عندما يتم إستدعاؤه.
يتم استخدام The parameterized constructor أثناء إنشاء الكائن object، مما يوفر قيمًا أولية لأعضاء القاعدة والارتفاع.
مثال 3:
#include <iostream> #include <cstdlib> using namespace std; class student { private: char name[20]; int id; public: student() { strcpy_s(name, "no name"); id = 0; } student(char n[], int i) { cout << "parameterized constructor\n"; strcpy_s(name, n); id = i; } student(char n[]) { strcpy_s(name, n); } void print() { cout << "name = " << name << endl; cout << "id = " << id << endl; } }; int main() { student ob("Mohammed", 1450902032); ob.print(); return 0; }
يحدد هذا الرمز طالبًا في الفصل يمثل طالبًا له اسم ومعرف متغيرات الأعضاء الخاصة. تتضمن الفئة ثلاثة (constrctors): empty constructor, parameterized constructor بالاسم والمعرف، وparameterized constructor بالاسم فقط. دعونا نحلل الكود خطوة بخطوة:
– تعريف الـ(Class): student
class student { private: char name[20]; int id; public: // Empty Constructor student() { strcpy_s(name, "no name"); id = 0; } // Parameterized Constructor (with name and id) student(char n[], int i) { cout << "parameterized constructor\n"; strcpy_s(name, n); id = i; } // Parameterized Constructor (with name only) student(char n[]) { strcpy_s(name, n); } // Member Function to print student details void print() { cout << "name = " << name << endl; cout << "id = " << id << endl; } };
- Empty Constructor:
تهيئة الاسم إلى "no name" والمعرف إلى 0. - Parameterized Constructor (بالإسم والمعرف):
يقبل parameters (char n[] and int i).
يطبع “parameterized constructor” عندما يتم إستدعاؤه.
يعين الاسم والمعرف بناءً على القيم المُدخلة. - Parameterized Constructor (بالإسم فقط):
يقبل parameter (char n[]).
تهيئة سمة الاسم فقط. - أعضاء الـFunction:
print: يعرض اسم الطالب وهويته.
الدالة الرئيسة main:
int main() { // Object of class student created using the parameterized constructor with name and id student ob("Mohammed", 1450902032); // Printing the details of the student ob.print(); return 0; }
- إنشاء الكائن object:
يتم إنشاء كائن ob من النوع Student باستخدام الـparameterized constructor بكل من الاسم والمعرف. - تفاصيل الطباعة:
يتم استدعاء دالة عضو الطباعة لعرض تفاصيل الطالب بما في ذلك الاسم والمعرف.
مخرجات هذا البرنامج ستكون كالتالي:
parameterized constructor name = Mohammed id = 1450902032
يوضح هذا الكود استخدام constructors متعددة في فئة C++. اعتمادًا على الـarguments المتوفرة أثناء إنشاء الكائن، يمكن استدعاء constructors مختلفة لتهيئة الكائن وفقًا لذلك.
3. Copy Constructor:
مثال 4:
#include <iostream> using namespace std; class Copy { private: int a1, a2, a3, a4, a5, a6, a7, a8; public: Copy(int aa1, int aa2, int aa3, int aa4, int aa5, int aa6, int aa7, int aa8) { a1 = aa1; a2 = aa2; a3 = aa3; a4 = aa4; a5 = aa5; a6 = aa6; a7 = aa7; a8 = aa8; } Copy(const Copy &a) { a1 = a.a1; a2 = a.a2; a3 = a.a3; a4 = a.a4; a5 = a.a5; a6 = a.a6; a7 = a.a7; a8 = a.a8; } void print() { cout << a1 << " " << a2 << " " << a3 << " " << a4 << " " << a5 << " " << a6 << " " << a7 << " " << a8 << " " << endl; } }; int main() { Copy g(1, 2, 3, 4, 5, 6, 7, 8); g.print(); Copy h(g); h.print(); return 0; }
يحدد هذا الكود نسخة فئة مع constructors اثنين - أحدهما لتهيئة كائن بقيم محددة والآخر لإجراء نسخة عميقة من كائن موجود. دعونا نحلل الكود:
– تعريف الـ(Class): Copy
class Copy { private: int a1, a2, a3, a4, a5, a6, a7, a8; public: // Parameterized Constructor for Initialization Copy(int aa1, int aa2, int aa3, int aa4, int aa5, int aa6, int aa7, int aa8) { a1 = aa1; a2 = aa2; a3 = aa3; a4 = aa4; a5 = aa5; a6 = aa6; a7 = aa7; a8 = aa8; } // Copy Constructor for Deep Copy Copy(const Copy &a) { a1 = a.a1; a2 = a.a2; a3 = a.a3; a4 = a.a4; a5 = a.a5; a6 = a.a6; a7 = a.a7; a8 = a.a8; } // Member Function to Print Object Details void print() { cout << a1 << " " << a2 << " " << a3 << " " << a4 << " " << a5 << " " << a6 << " " << a7 << " " << a8 << " " << endl; } };
- Parameterized Constructor:
تهيئة الكائن بقيم محددة مقدمة كـarguments. - Copy Constructor:
إجراء نسخة عميقة من كائن موجود (Copy &a).
نسخ كل متغير عضو للكائن المصدر إلى الكائن الجديد. - دالة Print:
يعرض قيم متغيرات أعضاء الكائن.
الدالة الرئيسة main:
int main() { // Creating an object 'g' and initializing it with specific values Copy g(1, 2, 3, 4, 5, 6, 7, 8); // Printing the details of object 'g' g.print(); // Creating another object 'h' and initializing it by performing a deep copy of 'g' Copy h(g); // Printing the details of object 'h' h.print(); return 0; }
- إنشاء الكائن (g):
يتم إنشاء كائن g من النوع Copy وتهيئته بقيم محددة. - تفاصيل الطباعة (g):
يتم استدعاء دالة عضو الطباعة لعرض تفاصيل الكائن g. - إنشاء الكائن (h):
يتم إنشاء كائن h آخر وتهيئته عن طريق إجراء نسخة عميقة من الكائن g باستخدام copy constructor. - تفاصيل الطباعة (h):
يتم استدعاء دالة عضو الطباعة لعرض تفاصيل الكائن h.
مخرجات هذا البرنامج ستكون كالتالي:
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
يوضح هذا الكود استخدام copy constructor لإنشاء كائن جديد بنفس قيم كائن موجود، مما يضمن نسخة عميقة من البيانات.
أداة الهدم Destructor
ما هو الـDestructor؟
في لغة C++، تعتبر أداة الهدم destructor دالة عضو خاصة يتم استدعاؤها تلقائيًا عندما يخرج كائن عن النطاق أو يتم حذفه بشكل صريح. والغرض الأساسي منه هو تنظيف الموارد أو تنفيذ الإجراءات قبل إلغاء تخصيص ذاكرة الكائن.
خصائص الـDestructor :
- الإسم والبناء:
الاسم: الـDestructors لها نفس اسم الفئة class، مسبوقة بعلامة التلدة (~).
بناء الجملة: ~ClassName() - الاستدعاء التلقائي:
يتم استدعاء أدوات الهدم تلقائيًا عندما يخرج كائن عن النطاق أو يتم حذفه بشكل صريح.
أداة الهدم تتعامل مع تنظيف وإلغاء تخصيص الموارد التي يحتفظ بها الكائن. - لا يوجد نوع إرجاع أو Parameters:
لا تحتوي أدوات الهدم على الإرجاع، ولا حتى void.
لا تقبل أي parameters. - أداة هدم واحدة لكل class:
يمكن أن يحتوي كل class على أداة هدم واحدة فقط.
وهو مسؤول عن تنظيف الموارد المخصصة خلال عمر الكائن. - الحذف اليدوي:
بالنسبة للكائنات التي تم إنشاؤها ديناميكيًا باستخدام new، يتم استدعاء أداة الهدم destructor بشكل صريح باستخدام الحذف.
يعد هذا ضروريًا لتحرير الذاكرة المخصصة في الـheap. - تنظيف الموارد:
غالبًا ما يتم استخدام أدوات الهدم لتحرير الموارد مثل الذاكرة الديناميكية وhandles الملفات واتصالات قاعدة البيانات وما إلى ذلك.
يضمن التنظيف المناسب قبل تدمير الكائن.
لماذا نستخدم الـDestructors؟
تعد أدوات الهدم ضرورية لإدارة الموارد بشكل سليم والحفاظ على سلامة برنامجك. وهي مفيدة بشكل خاص عند التعامل مع تخصيص الذاكرة الديناميكية أو معالجة الملفات أو أي موقف يكون فيه التنظيف ضروريًا.
بناء جملة الـDestructor:
بناء جملة الهدم واضح ومباشر. له نفس اسم class ال`ي يسبقه الإشارة (~). هنا مثال:
class MyClass { public: // Constructor MyClass() { // Constructor code } // Destructor ~MyClass() { // Destructor code } };
مثال 1:
#include <iostream> using namespace std; class Rectangle { private: int W, H; public: Rectangle(int a, int b) { W = a; H = b; cout << "A rectangle has been created\n"; } ~Rectangle() { cout << W << " " << H << " A rectangle has been deleted\n"; } }; int main() { Rectangle R1(3, 4), R2(5, 6); return 0; }
يحدد هذا الكود class يسمى rectangle تمثل مستطيلاً بمتغيرات الأعضاء الخاصة W (العرض) وH (الارتفاع). تتضمن الفئة أداة بناء constructor لإنشاء مستطيل وأداة هدم destructor لتنظيف الموارد عند حذف المستطيل. دعونا نحلل الكود:
– تعريف الـ(Class): Rectangle
class Rectangle { private: int W, H; public: // Constructor Rectangle(int a, int b) { W = a; H = b; cout << "A rectangle has been created\n"; } // Destructor ~Rectangle() { cout << W << " " << H << " A rectangle has been deleted\n"; } };
- أداة البناء Constructor:
تهيئة متغيرات الأعضاء W (العرض) وH (الارتفاع) مع القيم التي تم تمريرها كـarguments.
طباعة رسالة تشير إلى إنشاء rectangle . - أداة الهدم Destructor:
طباعة رسالة بأبعاد المستطيل عند حذفها.
الدالة الرئيسة main:
int main() { // Creating two objects of class Rectangle Rectangle R1(3, 4), R2(5, 6); return 0; }
- إنشاء الكائن object:
يتم إنشاء كائنين، R1 وR2، من النوع Rectangle في الدالة الرئيسية. - تنفيذ أداة البناء Constructor :
يتم تنفيذ constructor فئة Rectangle لكل كائن، مع تهيئة عرضه وارتفاعه.
تتم طباعة رسائل تشير إلى إنشاء المستطيلات. - هدم الكائن Object Destruction:
عند خروج البرنامج، أو عند ترك نطاق الدالة الرئيسة، يتم استدعاء أدوات الهدم لـ R1 وR2 تلقائيًا.
تتم طباعة رسائل تشير إلى حذف المستطيلات وأبعادها.
مخرجات هذا البرنامج ستكون كالتالي:
A rectangle has been created A rectangle has been created 5 6 A rectangle has been deleted 3 4 A rectangle has been deleted
يوضح هذا الإخراج الاستدعاء التلقائي للـconstructor عند إنشاء الكائنات والاستدعاء للـdestructor عندما تخرج الكائنات عن النطاق (في نهاية البرنامج في هذه الحالة). تقوم أداة الهدم بطباعة رسائل تشير إلى حذف المستطيلات مع أبعادها.
مثال 2:
#include <iostream> using namespace std; class Rectangle { private: int W, H; public: Rectangle(): W(0), H(0) { cout << "W = " << W << " H = " << H << endl; } Rectangle(int a, int b): W(0), H(0) { W = a; H = b; cout << "A rectangle has been created\n"; Rectangle ob; } ~Rectangle() { cout << W << " " << H << " A rectangle has been deleted\n"; } }; int main() { Rectangle R1(3, 4), R2(5, 6); Rectangle R3; return 0; }
- Default Constructor:
تهيئة W وH إلى 0.
يطبع القيم الأولية لـ W وH عند إنشاء كائن باستخدام هذا الـconstructor. - Parameterized Constructor:
يقبل الـ(parameters) a وb لتعيين أبعاد المستطيل.
تهيئة W وH إلى 0 قبل تعيين القيم المُدخلة.
طباعة رسالة تشير إلى إنشاء rectangle .
ينشئ كائنًا آخر داخل الـconstructor (لا ينصح به). - أداة الهدم Destructor:
طباعة رسالة بأبعاد المستطيل عند حذفها.
مخرجات هذا البرنامج ستكون كالتالي:
A rectangle has been created W = 0 H = 0 0 0 A rectangle has been deleted A rectangle has been created W = 0 H = 0 0 0 A rectangle has been deleted W = 0 H = 0 0 0 A rectangle has been deleted 5 6 A rectangle has been deleted 3 4 A rectangle has been deleted
مثال 3:
#include <iostream> using namespace std; class phone { private: char name[10]; char model[10]; int price; public: phone() {} phone(char n[], char m[], int p) { strcpy(name, n); strcpy(model, m); price = p; } void print(); ~phone(); }; phone::~phone() { cout << "object destructed\n" } void phone::print() { cout << "Name = " << name << endl; cout << "Model = " << model << endl; cout << "Price = " << price << endl; } int main() { phone ob1, ob2("HUAWI", "MATE 9", 400); ob2.print(); return 0; }
يحدد هذا الكود class phoneيمثل هاتفًا يحتوي على متغيرات الأعضاء الخاصة، الاسم والطراز والسعر. تتضمن الفئة default constructor، وparameterized constructor، ودالة عضو (print)، وdestructor. دعونا نحلل الكود:
– تعريف الـ(Class): phone
class phone { private: char name[10]; char model[10]; int price; public: // Default Constructor phone() {} // Parameterized Constructor phone(char n[], char m[], int p) { strcpy(name, n); strcpy(model, m); price = p; } // Member Function to Print Phone Details void print(); // Destructor ~phone(); };
- Default Constructor:
يتم توفير default constructor فارغ. - Parameterized Constructor:
يقبل الـ(parameters ) n (name)، وm (model)، وp (price) لتهيئة متغيرات أعضاء الكائن. - دالة العضو (print):
يعرض تفاصيل الهاتف بما في ذلك اسمه وموديله وسعره. - أداة الهدم Destructor:
يخرج رسالة عند هدم كائن من الفئة.
– تنفيذ أداة الهدم Destructor:
phone::~phone() { cout << "object destructed\n"; }
يتم تعريف الـdestructor خارج الفئة ويطبع رسالة عند هدم كائن من الفئة.
الدالة الرئيسة main:
int main() { phone ob1, ob2("HUAWI", "MATE 9", 400); ob2.print(); return 0; }
- إنشاء الكائن object:
يتم إنشاء كائنين، ob1 وob2، من النوع phone.
تتم تهيئة ob2 باستخدام parameterized constructor. - تفاصيل الطباعة:
يتم استدعاء دالة عضو الطباعة لـ ob2 لعرض تفاصيلها. - هدم الكائن Object Destruction:
عند خروج البرنامج، أو عند ترك نطاق الدالة الرئيسية، يتم استدعاء أدوات الهدم لـ ob1 وob2 تلقائيًا.
تتم طباعة رسالة تشير إلى أنه تم إتلاف الكائن.
مخرجات هذا البرنامج ستكون كالتالي:
Name = HUAWI Model = MATE 9 Price = 400 object destructed
توضح المخرجات إنشاء كائنات الهاتف وطباعتها وتدميرها، بما في ذلك استخدام parameterized constructor وdestructor.
مثال 4:
#include <iostream> #include <cstring> using namespace std; class Student { private: char name[20]; int ID; public: Student() { cout << "Object created\n"; } ~Student() { cout << "Object destructed\n"; } void Set_Name_ID(char n[], int id) { strcpy(name, n); ID = id; } void print(void) { cout << name << "\t" << ID << endl; } }; //end of class definition void F(Student S) { Student S1; S1 = S; S.Set_Name_ID("Sami", 12345); S.print(); S1.print(); } int main() { Student St1, St2; St1.Set_Name_ID("Ahmad", 11111); St2.Set_Name_ID("Ali", 22222); cout << "Going to Function\n"; F(St1); cout << "Back from Funcrion\n"; St1.print(); return 0; }
يحدد هذا الكودclass Student الذي يمثل الطالب مع اسم ومعرف متغيرات الأعضاء الخاصة. يتضمن الـ(class) default constructor، وdestructor، ودالة عضو لتعيين الاسم والمعرف، ودالة عضو أخرى لطباعة تفاصيل الطالب. بالإضافة إلى ذلك، هناك دالة F تأخذ Student object كـparameter وتوضح إنشاء الكائن وتعيينه وتعديله وهدمه داخل الدالة. دعونا نحلل الكود:
– تعريف الـ(Class): Student
class Student { private: char name[20]; int ID; public: // Default Constructor Student() { cout << "Object created\n"; } // Destructor ~Student() { cout << "Object destructed\n"; } // Member Function to Set Name and ID void Set_Name_ID(char n[], int id) { strcpy(name, n); ID = id; } // Member Function to Print Student Details void print(void) { cout << name << "\t" << ID << endl; } };
- Default Constructor:
طباعة رسالة تشير إلى إنشاء كائن. - أداة الهدم Destructor:
طباعة رسالة تشير إلى أن الكائن قد تم إتلافه. - دالة Set_Name_ID:
يقبل الـ(parameters) n (name) والـid (ID) لتعيين متغيرات الأعضاء. - دالة الطباعة print:
طباعة اسم وهوية الطالب.
– الدالة F:
void F(Student S) { Student S1; S1 = S; S.Set_Name_ID("Sami", 12345); S.print(); S1.print(); }
- يقوم بإنشاء Student object S1 داخل الدالة.
- نسخ محتوى الـ(parameter) S إلى S1.
- يعدل محتوى S باستخدام الدالة Set_Name_ID.
- يطبع تفاصيل كل من S وS1.
الدالة الرئيسة main:
int main() { Student St1, St2; St1.Set_Name_ID("Ahmad", 11111); St2.Set_Name_ID("Ali", 22222); cout << "Going to Function\n"; F(St1); cout << "Back from Function\n"; St1.print(); return 0; }
- إنشاء الكائن وتعديله:
إنشاء كائنين للطالب، St1 وSt2.
يقوم بتعيين أسمائهم ومعرفاتهم باستخدام الدالة Set_Name_ID. - إستدعاء الدالة:
يستدعي الدالة F بتمرير St1 كـparameter. - هدم الكائن Object Destruction:
طباعة رسائل تشير إلى هدم الكائنات عند خروجها عن النطاق (نهاية الدالة أو البرنامج).
مخرجات هذا البرنامج ستكون كالتالي:
Object created Object created Going to Function Object created Sami 12345 Ahmad 11111 Object destructed Object destructed Back from Funcrion Ahmad 11111 Object destructed Object destructed
يوضح هذه المخرجات إنشاء كائنات الطالب وتعديلها وتدميرها، بما في ذلك سلوك الـconstructor والـdestructor الافتراضيين. توضح الدالة F نسخ الكائنات وتعديلها داخل الدالة.
هياكل البيانات Structure
توفر الهياكل في C++ طريقة قوية لتنظيم البيانات ذات الصلة وتخزينها تحت اسم واحد. إنها مفهوم أساسي في البرمجة كائنية التوجه (OOP) التي تسمح لك بإنشاء أنواع بيانات مخصصة لتناسب احتياجات برنامجك. في هذا الشرح، سنتعمق في أساسيات الهياكل في لغة C++.
ما هي هياكل البيانات؟
الـStructure عبارة عن نوع بيانات محدد من قبل المستخدم في لغة C++، وهو يمكّنك من تجميع أنواع بيانات مختلفة معًا تحت اسم واحد. وهذا يجعل من السهل إدارة وتنظيم المعلومات ذات الصلة. على عكس أنواع البيانات البدائية، يمكن للهياكل أن تحتوي على مجموعة من المتغيرات بأنواع بيانات مختلفة.
تعريف هياكل البيانات structure:
لإنشاء structure، يمكنك استخدام الكلمة المفتاحية structure، متبوعة باسم البنية والمتغيرات داخل الأقواس المتعرجة. إليك مثال بسيط:
// Define a structure named 'Person' struct Person { char name[50]; int age; float height; };
في هذا المثال، قمنا بإنشاء structure تسمى "Person" مع ثلاثة أعضاء: مصفوفة أحرف للاسم، وعدد صحيح للعمر، ,وعدد عشري للارتفاع.
الوصول إلى أعضاء الـstructure:
نستطيع الوصول إلى أعضاء الـstructure بإستخدام معامل النقطة (.).
مثال 1:
#include <iostream> using namespace std; struct car { string name; string color; int maxspeed; int model; }; int main() { car g; g.name = "Kia"; g.color = "red"; cout << g.name << endl; //Kia cout << g.color << endl; //red return 0; }
يعرف هذا الكود برنامجًا بسيطًا يستخدم structure يسمى car. يحتوي هيكل البيانات car على أربعة أعضاء: الاسم (نص)، واللون (نص)، والسرعة القصوى (عدد صحيح)، والطراز (عدد صحيح). يقوم البرنامج بعد ذلك بالإعلان عن متغير g من النوع car ويقوم بتعيين قيم لاسمه وأعضاء اللون. وأخيرًا، يقوم بطباعة قيم الاسم واللون على شاشة المخرجات.
إنشاء متغيرات للـstructure:
يمكنك إنشاء متغيرات لـstructure في وقت الإعلان، على غرار كيفية إنشاء المتغيرات الأولية:
// Initialize structure variables Person person3 = {"Alice", 30, 5.5};
مثال 2:
#include <iostream> using namespace std; struct car { string name; string color; int maxspeed; int model; }g, k; int main() { k = {"aa", "black", 300, 97}; g = {"kia", "red", 250, 96}; cout << g.maxspeed << endl; return 0; }
يعؤف هذا الكود هيكلًا يسمى car مكونة من أربعة أعضاء (الاسم واللون والسرعة القصوى والطراز). بالإضافة إلى ذلك، فإنه يعلن عن متغيرين، g وk، من النوع car. في الدالة الرئيسية، يقوم بتعيين قيم لمتغيرات الهيكل هذا باستخدام بناء جملة التهيئة ثم يطبع قيمة عضو maxspeed للمتغير g. وهنا شرح مفصل:
– تعريف الـstructure وإعلان المتغير:
struct car { string name; string color; int maxspeed; int model; } g, k; // Declaration of structure variables g and k of type car
يحدد structure مسمى car ويعلن عن متغيرين للهيكل g وk من النوع car.
- الدالة الرئيسة:
int main() { // Initialization of structure variable k k = {"aa", "black", 300, 97}; // Initialization of structure variable g g = {"kia", "red", 250, 96}; // Print the value of the maxspeed member of the g structure variable cout << g.maxspeed << endl; return 0; }
مسار التنفيذ:
- إنشاء المتغيرات:
k = {“aa”, “black”, 300, 97}؛ إنشاء أعضاء متغير الهيكل k بهذه القيم.
g = {“kia”, “red”, 250, 96}؛ إنشاء أعضاء متغير الهيكل k بهذه القيم. - طباعة إلى شاشة المخرجات:
cout << g.maxspeed << endl; يطبع قيمة عضو maxspeed للمتغير g إلى شاشة المخرجات، متبوعًا بسطر جديد. - جملة الإرجاع:
return 0؛ يشير إلى أن البرنامج قد تم تنفيذه بنجاح.
مخرجات هذا البرنامج ستكون كالتالي:
250
الدوال وهياكل البيانات Structures:
يمكن تمرير الهياكل كـarguments للدوال، مما يسمح لك بإنشاء تعليمات برمجية أكثر تنظيمًا.
مثال 3:
#include <iostream> #include <string> using namespace std; struct Distance { int feet; float inches; }; Distance add_distance(Distance d1, Distance d2) { Distance result; result.feet = d1.feet + d2.feet; result.inches = d1.inches + d2.inches; return result; } int main() { Distance x, y, z; cout << "Enter feet value \n"; cin >> x.feet >> y.feet; cout << "Enter inches value \n"; cin >> x.inches >> y.inches; z = add_distance(x, y); cout << "z.feet = " << z.feet << " z.inches = " << z.inches << endl; return 0; }
يوضح هذا الكود استخدام الهياكل لتمثيل المسافات بالأقدام والبوصات. يحدد مسافة الهيكل بعضوين: feet (عدد صحيح) وinches (عدد عشري). يتضمن البرنامج بعد ذلك دالة add_distance لإضافة هيكلين للمسافة، وتأخذ الدالة الرئيسية مدخلات المستخدم لمسافتين، وتضيفهما باستخدام دالة add_distance، وتطبع النتيجة. فيما يلي شرح خطوة بخطوة:
– تعريف الـstructure:
struct Distance { int feet; float inches; };
يعرف هيكل يسمى Distance مع عضوين: feet (عدد صحيح) وinches (عدد عشري).
- دالة لإضافة المسافات Distances:
Distance add_distance(Distance d1, Distance d2) { Distance result; result.feet = d1.feet + d2.feet; result.inches = d1.inches + d2.inches; return result; }
تحدد دالة add_distance التي تأخذ هيكلين للمسافة كـparameters، وتضيف أعضاء الـfeet والـinches المقابلة لهما، وترجع هيكل مسافة جديدة تمثل المجموع.
- الدالة الرئيسة:
int main() { Distance x, y, z; // User input for the first distance (x) cout << "Enter feet value \n"; cin >> x.feet; cout << "Enter inches value \n"; cin >> x.inches; // User input for the second distance (y) cout << "Enter feet value \n"; cin >> y.feet; cout << "Enter inches value \n"; cin >> y.inches; // Add the two distances using the add_distance function z = add_distance(x, y); // Print the result cout << "z.feet = " << z.feet << " z.inches = " << z.inches << endl; return 0; }
مسار التنفيذ:
- مدخلات المستخدم:
يُطلب من المستخدم إدخال قيم الـfeet والـinches لمسافتين (x وy). - إستدعاء الدالة:
يتم استدعاء الدالة add_distance باستخدام هياكل x وy كـarguments، ويتم تخزين النتيجة في الهيكل z. - طباعة إلى شاشة المخرجات:
يقوم البرنامج بطباعة النتيجة، ويعرض مجموع المسافات بالـfeet والـinches. - جملة الإرجاع:
return 0؛ يشير إلى أن البرنامج قد تم تنفيذه بنجاح.
مثال عن التنفيذ:
إذا أدخل المستخدم ما يلي:
Enter feet value 3 Enter inches value 6.5 Enter feet value 2 Enter inches value 3.2
ستكون المخرجات كالتالي:
z.feet = 5 z.inches = 9.7
مثال 4:
#include <iostream> #include <string> #include <cstring> #include <cstdlib> using namespace std; struct exam { float first; float second; float final; }; class subject { char name[10]; exam Exam; public: subject() { strcpy(name, "no name"); Exam = {0, 0, 0}; } subject(char n[], float fa, float s, float fi) { strcpy(name, n); Exam = {fa, s, fi}; } float total() { return Exam.first + Exam.second + Exam.final; } void print() { cout << "The subject = " << name << endl << "First Exam = " << Exam.first << endl << "Second Exam = " << Exam.second << endl << "Final Exam = " << Exam.final << endl << "The Total is = " << total() << endl; } }; int main() { subject e("OOP", 25, 24, 49); e.print(); return 0; }
يحدد هذا الكود برنامجًا يستخدم مجموعة من الهياكل structures والفئات classes لتصميم subject باستخدام درجات الاختبار. دعونا نحلل الكود خطوة بخطوة:
– تعريف الـstructure:
struct exam { float first; float second; float final; };
يعرف structure يسمى exam يضم ثلاثة أعضاء يمثلون درجات الاختبار: الأول والثاني والنهائي.
- تعريف الفئة class:
class subject { char name[10]; exam Exam; public: // Default constructor subject() { strcpy(name, "no name"); Exam = {0, 0, 0}; } // Parameterized constructor subject(char n[], float fa, float s, float fi) { strcpy(name, n); Exam = {fa, s, fi}; } // Member function to calculate the total exam score float total() { return Exam.first + Exam.second + Exam.final; } // Member function to print subject details void print() { cout << "The subject = " << name << endl << "First Exam = " << Exam.first << endl << "Second Exam = " << Exam.second << endl << "Final Exam = " << Exam.final << endl << "The Total is = " << total() << endl; } };
يحدد فئة class تسمى الـsubject مع private members:
- name: مجموعة من الأحرف التي تمثل اسم الـsubject.
- Exam: مثال على هيكل الامتحان الذي يمثل درجات الامتحان.
يتضمن هذا الـclass:
- A default constructor (subject()) يقوم بتهيئة الاسم إلى "no name" والـExam لجميع الأصفار.
- A parameterized constructor (subject(char n[], float fa, float s, float fi)) تهيئة الاسم والاختبار بالقيم التي تم توفيرها.
- دالة عضو Total() تحسب مجموع درجات الامتحان.
- تقوم دالة العضو print() بطباعة تفاصيل الموضوع.
- الدالة الرئيسة:
int main() { // Create an instance of the subject class subject e("OOP", 25, 24, 49); // Call the print() member function to display subject details e.print(); return 0; }
في الدالة الرئيسة:
- يتم إنشاء مثيل e لفئة الموضوع باستخدام parameterized constructor.
- يتم استدعاء دالة عضو print() على الكائن e، وتعرض تفاصيل الموضوع ودرجات الامتحان الخاص به.
مثال على المخرجات:
The subject = OOP First Exam = 25 Second Exam = 24 Final Exam = 49 The Total is = 98
تمثل هذه المخرجات تفاصيل “OOP” subject,، بما في ذلك درجات الامتحانات الأولى والثانية والنهائية، بالإضافة إلى الدرجة الإجمالية.
أداة New وأداة Delete
في البرمجة الكائنية (OOP) مع لغة C++، تلعب المؤشرات Pointers دورًا حاسمًا في إدارة الذاكرة والوصول إلى الكائنات ديناميكيًا. فيما يلي شرح موجز للمؤشرات في سياق OOP في C++:
تعريف:
المؤشر Pointer هو متغير يحمل عنوان الذاكرة لمتغير آخر. في لغة C++، يمكن استخدام المؤشرات لتخزين عناوين الكائنات التي تم إنشاؤها ديناميكيًا على الـheap.
مثال 1:
int main() { int var1 = 11; int var2 = 22; cout << &var1 << endl; cout << &var2 << endl; int *ptr; ptr = &var2; *ptr = 5000; cout << *ptr << endl; cout << var2 << endl; }
يوضح هذا الكود استخدام المؤشرات لمعالجة المتغيرات والوصول إلى عناوين الذاكرة الخاصة بها.
- الإعلان وتفعيل المتغير:
int var1 = 11; int var2 = 22;
تم الإعلان عن متغيرين صحيحين، var1 وvar2، وإعطائهما القيمتين 11 و22، على التوالي.
- طباعة عناوين الذاكرة:
cout << &var1 << endl; cout << &var2 << endl;
تتم طباعة عناوين الذاكرة var1 وvar2 باستخدام عامل تشغيل عنوان (&). يكشف هذا عن المواقع في الذاكرة حيث يتم تخزين هذه المتغيرات.
- إعلان المؤشر وتعيين قيمته:
int *ptr; ptr = &var2;
تم الإعلان عن متغير المؤشر ptr من النوع int. وتم تعيين عنوان var2 للمؤشر ptr.
- استخدام المؤشر لتعديل المتغير:
*ptr = 5000;
يتم تغيير القيمة التي يشير إليها ptr إلى 5000. وبما أن ptr يشير إلى عنوان var2، فإن هذا يعدل أيضًا قيمة var2.
- طباعة قيمة المؤشر والمتغير الذي تم تعديله:
cout << *ptr << endl; cout << var2 << endl;
تتم طباعة القيمة المشار إليها بواسطة ptr (5000) والقيمة المعدلة لـ var2. يجب أن تكون كلتا القيمتين متماثلتين، حيث تم استخدام المؤشر لتعديل محتوى var2.
قد تبدو مخرجات الكود كما يلي:
0x7fff5fbff628 // Memory address of var1 0x7fff5fbff624 // Memory address of var2 5000 // Value pointed to by ptr 5000 // Modified value of var2
قد تختلف عناوين وقيم الذاكرة الدقيقة وفقًا للنظام والمترجم.
تخصيص الذاكرة الديناميكية:
غالبًا ما يتضمن OOP إنشاء كائنات باستخدام عامل التشغيل new لتخصيص الذاكرة الديناميكية. تُستخدم المؤشرات لتخزين عنوان الكائنات المخصصة ديناميكيًا.
حذف الكائنات الديناميكية:
عندما لا تكون هناك حاجة للكائنات الديناميكية، يجب حذفها بشكل صريح لتجنب تسرب الذاكرة. يتم استخدام العامل delete.
مثال 2:
#include <iostream> using namespace std; int main() { int *p; p = new int; *p = 10; cout << p << endl; delete p; }
يوضح هذا الكود تخصيص الذاكرة الديناميكية وإلغاء التخصيص لمتغير عدد صحيح. وفيما يلي شرح للكود:
#include <iostream> using namespace std; int main() { // Declare a pointer variable int *p; // Allocate memory for an integer on the heap p = new int; // Assign a value to the dynamically allocated integer *p = 10; // Print the address of the dynamically allocated memory cout << p << endl; // Deallocate the dynamically allocated memory delete p; return 0; }
شرح:
- الإعلان عن المؤشر:
int *p;
يعلن عن متغير المؤشر p الذي سيتم استخدامه لتخزين عنوان عدد صحيح مخصص ديناميكيًا.
- تخصيص الذاكرة الديناميكية:
p = new int;
يخصص الذاكرة على الـheap لتخزين عدد صحيح ويعين عنوان الذاكرة المخصصة للمؤشر p.
- تعيين قيمة:
*p = 10;
يتم إلغاء الإشارة إلى المؤشر p وتعيين القيمة 10 للعدد الصحيح المخصص ديناميكيًا.
- طباعة العنوان:
cout << p << endl;
يطبع عنوان الذاكرة المخصصة ديناميكيًا. هذا العنوان موجود في الـheap وقد يختلف في كل مرة يتم فيها تشغيل البرنامج.
- إلغاء تخصيص الذاكرة:
delete p;
إلغاء تخصيص الذاكرة المخصصة مسبقًا بأخرى جديدة. هذه الخطوة ضرورية لمنع تسرب الذاكرة.
مثال 3:
#include <iostream> using namespace std; int main() { // Declare two pointer variables int *p1, *p2; // Allocate memory for the first integer on the heap p1 = new int; *p1 = 10; // Print the location of the pointer and the content of the allocated memory cout << &p1 << endl; // location of the pointer cout << "Memory location " << p1 << "\n"; cout << " contains " << *p1 << endl; // Allocate memory for the second integer on the heap p2 = new int; *p2 = 10; // Print the location of the second pointer and the content of the allocated memory cout << &p2 << endl; // location of the pointer cout << "Memory location " << p2 << "\n" << " contains " << *p2 << endl; // Deallocate the dynamically allocated memory delete p1; delete p2; return 0; }
يوضح هذا الكود تخصيص الذاكرة الديناميكية لمتغيرين صحيحين باستخدام المؤشرات. وفيما يلي شرح للكود:
- الإعلان عن المؤشر:
int *p1, *p2;
يعلن عن متغيري المؤشر، p1 وp2، اللذين سيتم استخدامهما لتخزين عناوين الأعداد الصحيحة المخصصة ديناميكيًا.
- تخصيص الذاكرة الديناميكية (p1):
p1 = new int; *p1 = 10;
يخصص الذاكرة في الـheap لعدد صحيح باستخدام new، ويعين العنوان إلى p1، ويعين القيمة 10 للعدد الصحيح المخصص ديناميكيًا.
- طباعة المعلومات (p1):
cout << &p1 << endl; // location of the pointer cout << "Memory location " << p1 << "\n"; cout << " contains " << *p1 << endl;
يطبع موقع متغير المؤشر p1 ومحتوى الذاكرة التي يشير إليها.
- تخصيص الذاكرة الديناميكية (p2):
p2 = new int; *p2 = 10;
يخصص الذاكرة في الـheap لعدد صحيح آخر باستخدام new، ويعين العنوان لـ p2، ويعين القيمة 10 للعدد الصحيح المخصص ديناميكيًا.
- طباعة المعلومات (p2):
cout << &p2 << endl; // location of the pointer cout << "Memory location " << p2 << "\n" << " contains " << *p2 << endl;
يطبع موقع متغير المؤشر p2 ومحتوى الذاكرة التي يشير إليها.
- إلغاء تخصيص الذاكرة:
delete p1; delete p2;
إلغاء تخصيص الذاكرة المخصصة ديناميكيًا باستخدام delete لتجنب تسرب الذاكرة.
مثال 4:
#include <iostream> using namespace std; class CRectangle { int *width, *height; public: CRectangle(int, int); //constructor ~CRectangle(); //destructor int area() { return (*width * *height); } }; CRectangle::CRectangle(int a, int b) { width = new int; height = new int; *width = a; *height = b; } CRectangle::~CRectangle() { delete width; delete height; } int main() { CRectangle rect(3, 4), rectb(5, 6); cout << "rect area: " << rect.area() << endl; cout << "rectb area: " << rectb.area() << endl; return 0; }
يحدد هذا الرمز فئة CRectangle للمستطيلات باستخدام تخصيص الذاكرة الديناميكية للعرض والارتفاع. إليك تفاصيل الكود:
- تعريف الفئة (CRectangle):
تمثل الفئة CRectangle مستطيلات، مع تخزين عرض وارتفاع الأعضاء الخاصين كمؤشرات أعداد صحيحة ديناميكية. - أداة البناء Constructor (CRectangle::CRectangle):
يخصص الـconstructor الذاكرة للعرض والارتفاع باستخدام new ويقوم بتهيئتهم بالقيم المقدمة. - أداة الهدم Destructor (CRectangle::~CRectangle):
يقوم الـdestructor بإلغاء تخصيص الذاكرة للعرض والارتفاع باستخدام delete. - دالة العضو (area):
تقوم دالة area بحساب مساحة المستطيل وإرجاعها باستخدام القيم المخزنة في العرض والارتفاع. - الدالة الرئيسة main:
ينشئ مثيلات CRectangle (rect and rectb) مع تخصيص الذاكرة الديناميكية للعرض والارتفاع.
يستدعي دالة area لحساب وعرض مساحة كل مستطيل.
يتم استدعاء أدوات التدمير تلقائيًا عندما تخرج (rect and rectb) عن النطاق، مما يؤدي إلى تحرير الذاكرة المخصصة ديناميكيًا.
مصفوفة من الكائنات object ومؤشرات للكائنات objects
في عالم البرمجة الكائنية (OOP) مع لغة C++، تعد إدارة كائنات متعددة بكفاءة أمرًا بالغ الأهمية. هناك مفهومان قويان يساعدان في تحقيق ذلك مصفوفة الكائنات Arrays of Objects و مؤشرات للكائنات Pointers to Objects. سنوضح كلا المفهومين وكيفية توظيفهما في لغة C++.
مثال 1:
#include <iostream> using namespace std; int main() { int arr[5] = {10, 50, 40, 77, 33}; // Printing the memory address of the array (address of the first element) cout << arr << endl; // Printing the value at the first element of the array cout << *arr << endl; // Printing the memory address of the second element of the array cout << arr + 1 << endl; // Printing the memory address of the last element of the array cout << arr + 4 << endl; // Printing the value at the last element of the array cout << *(arr + 4) << endl; // Using a loop to print all elements of the array using pointer arithmetic for (size_t i = 0; i < 5; i++) { cout<< *(arr + i) << endl; } return 0; }
يوضح هذا البرنامج استخدام المؤشرات مع المصفوفات.
ستعرض المخرجات عناوين الذاكرة وقيمها وعناصر المصفوفة بناءً على حساب المؤشر وتكرارات الحلقة.
مثال 2:
#include <iostream> using namespace std; int main() { int arr[5] = {10, 50, 40, 77, 33}; int *p; p = arr; for (size_t i = 0; i < 5; i++) { cout << *p << endl; p++; } return 0; }
يوضح برنامج C++ هذا استخدام المؤشر للتكرار عبر مصفوفة.
ستعرض المخرجات قيم كل عنصر في المصفوفة، مطبوعًا واحدًا تلو الآخر، حيث يتم زيادة المؤشر p داخل الحلقة.
10 50 40 77 33
ملحوظة !
- يتم تعيين المؤشر p مبدئيًا على عنوان العنصر الأول في المصفوفة.
- زيادة المؤشر (p++) يجعله يشير إلى العنصر التالي في المصفوفة.
- يوفر هذا الأسلوب طريقة بديلة للتكرار عبر عناصر المصفوفة باستخدام المؤشرات.
مثال 3:
#include <iostream> #include <cstdlib> #include <cstring> using namespace std; class Student { char name[20]; int ID; public: Student() //empty constructor { cout << "empty\n"; strcpy(name, "No name"); ID = 0; } Student(char n[], int id) //constructor overloading { cout << "parameterize\n"; strcpy(name, n); ID = id; } void print(void) { cout << name << "\t" << ID << endl; } }; int main() { Student S1("Ali", 123), S2("Ahmad", 456); Student arr[3]; for (int i = 0; i < 3; i++) arr[i].print(); return 0; }
يوضح هذا البرنامج استخدام constructor overloading وdefault constructors في الـclass. دعونا نحلل الكود:
- Class Student:
يحدد class يسمى الطالب مع اسم ومعرف أعضاء البيانات الخاصة.
يحتوي على constructors اثنين: constructor فارغ وparameterized constructor.
يوفر دالة عضو وهي دالة الطباعة لعرض تفاصيل الطالب. - Empty Constructor:
Student() { cout << "empty\n"; strcpy(name, "No name"); ID = 0; }
تهيئة الاسم بـ "No name" والمعرف بـ 0.
طباعة "empty" عند إنشاء كائن باستخدام هذا الـconstructor. - Constructor Overloading:
Student(char n[], int id) { cout << "parameterize\n"; strcpy(name, n); ID = id; }
تهيئة الاسم والمعرف باستخدام الـarguments المتوفرة.
طباعة "parameterize" عند إنشاء كائن باستخدام هذا الـconstructor. - print() Function:
void print(void) { cout << name << "\t" << ID << endl; }
طباعة اسم الطالب وهويته.
- إنشاء كائنات:
يتم إنشاء الكائنين S1 وS2 باستخدام parameterized constructor، مع عرض "parameterize" لكل كائن.
يتم إنشاء مصفوفة من كائنات الطالب باستخدام default constructor، مع عرض "empty" لكل كائن. - طباعة تفاصيل الطالب:
يتم استدعاء الدالة print() لكل كائن في المصفوفة لعرض تفاصيله.
المخرجات:
ستعرض المخرجات رسائل الـconstructor وتفاصيل كل طالب في المصفوفة.
المتغيرات الرقمية
المقدمة
في لغة C++، المتغيرات الرقمية، المعروفة أيضًا بـenumerated type، هو نوع بيانات محدد من قبل المستخدم ويتكون من مجموعة من الثوابت الصحيحة المسماة. هذه الثوابت هي في الأساس أسماء تمثيلية مخصصة لقيم عددية.
طريقة الإعلان
- يتم استخدام الكلمة الأساسية enum للإعلان عن المتغيرات الرقمية.
- يمكنك إدراج الأسماء الثابتة مفصولة بفواصل، ومحاطة بشكل اختياري بأقواس متعرجة.
- افتراضيًا، تبدأ الثوابت بقيمة 0 وتزداد بمقدار 1 لكل ثوابت لاحقة.
- يمكنك تعيين قيم عددية محددة بشكل صريح للثوابت الفردية.
مثال 1:
#include <iostream> #include <string> #include <cstring> #include <algorithm> using namespace std; enum days {sat = 1, sun, mon, tue, wed, thu, fri}; int main() { string d[7] = {"sat", "sun", "mon", "tue", "wed", "thu", "fri"}; days m[7] = {sat, sun, mon, tue, wed, thu, fri}; for (size_t i = 0; i < 7; i++) { cout << m[i] << " - " << d[i] << endl; } return 0; }
الأيام كمتغيرات رقمية:
- يعلن متغيرات رقمية تم تسميتها days لتمثيل أيام الأسبوع.
- يعين قيمًا لكل يوم: sat = 1, sun, mon, وما إلى ذلك (الأيام اللاحقة تحصل على قيم ضمنية متزايدة من اليوم السابق).
مصفوفة النص d:
- إنشاء مصفوفة من النصوص باسم d بحجم 7، تحمل أسماء اليوم بالكامل ("sat"، "sun"، ...، "fri").
المتغيرات الرقمية للمصفوفة m:
- إنشاء مصفوفة من قيم المتغيرات الرقمية المسماة m بحجم 7، تحتوي على متغيرات رقمية للأيام (sat, sun, …, fri).
حلقة الدوران for:
- الدوران خلال عناصر كل من d وm باستخدام index مفرد i.
- لكل حلقة تكرار:
- طباعة قيمة المتغير الرقمي الحالية من m[i] متبوعة بواصلة ("-").
- طباعة اسم اليوم المقابل من d[i] على نفس السطر.
مثال 2:
#include <iostream> #include <string> #include <cstring> #include <algorithm> using namespace std; string da[7] = {"sat", "sun", "mon", "tue", "wed", "thu", "fri"}; enum Days {sat = 1, sun, mon, tue, wed, thu, fri}; class week { Days d[7]; public: void setday(Days w[]) { for (size_t i = 0; i < 7; i++) d[i] = w[i]; } void p() { for (size_t i = 0; i < 7; i++) cout << "The number of day " << da[i] << " = " << d[i] << endl; } }; int main() { Days d[7] = {sat, sun, mon, tue, wed, thu, fri}; week g; g.setday(d); g.p(); return 0; }
المصفوفة da:
- يعلن عن مصفوفة نص عالمية النطاق da بحجم 7، تخزن أسماء الأيام الكاملة ("sat"، "sun"، ...، "fri").
الأيام كمتغيرات رقمية:
- يعرف متغيرات رقمية تم تسميتها "Days " لتمثيل أيام الأسبوع بقيم عددية:
– sat = 1
– sun (ضمنيًا 2)
– mon (ضمنيًا 3)
– …
– fri (ضمنيًا 7)
Class week:
- يمثل مفهوم "week".
- متغيرات الأعضاء:
* d عبارة عن مصفوفة من المتغيرات الرقمية للأيام، تحتفظ بقيم اليوم الرقمية لمدة أسبوع (على سبيل المثال، sat, sun, …).
- طرق الأعضاء:
* setday(Days w[]): يأخذ مصفوفة من قيم المتغيرات الرقمية للأيام كمدخلات ويعينها لمصفوفة العضو d، وينسخ قيم اليوم إلى الكائن.
* p(): طباعة أسماء الأيام والقيم الرقمية المقابلة لها من المصفوفة d. يكون التكرار من خلال المصفوفة، ويصل إلى كل من سلسلة da الحالية وقيمة d في نفس الـindex لطباعة الزوج.
الدالة الرئيسة main:
- إنشاء مصفوفة d من نوع الأيام وتهيئتها باستخدام sat وsun و... وfri.
- إنشاء كائن للأسبوع اسمه g.
- يستدعي طريقة setday الخاصة بـ g لتمرير المصفوفة d (التي تحتوي على قيم اليوم) إلى الكائن.
- يستدعي الطريقة p الخاصة بـ g، التي تطبع معلومات اليوم بالتنسيق "The number of day = ".
يوضح الكود استخدام المتغيرات الرقمية والفئات لتمثيل وإدارة معلومات يوم الأسبوع في C++.
شرح لـ Static Class Member و Static Member Function
المقدمة
في لغة C++، يقدم "static class members" و"static member functions" عناصر على مستوى الفئة غير مرتبطة بالكائنات الفردية للفئة.
#include <iostream> #include <string> using namespace std; void f() { static int x = 0; x++; cout << x << endl; } int main() { f(); f(); return 0; }
يعرّف برنامج C++ هذا الدالة f() التي تزيد متغيرًا صحيحًا ثابتًا x في كل مرة يتم استدعاؤها، ثم تطبع قيمة x إلى المخرجات القياسية. الدالة main() تستدعي f() مرتين.
إليك ما يفعله البرنامج:
- تم تعريف الدالة f(). أنه يحتوي على متغير static x تمت تهيئته إلى 0.
- في كل مرة يتم استدعاء الدالة f()، فإنها تزيد x بمقدار 1 وتطبع القيمة الجديدة لـ x.
- في الدالة main()، يتم استدعاء f() مرتين.
- عندما يتم استدعاء f() للمرة الأولى، تتم زيادة x من 0 إلى 1، ويتم طباعة 1.
- عندما يتم استدعاء f() للمرة الثانية، تتم زيادة x من 1 إلى 2، ويتم طباعة 2.
ولذلك، عند تشغيل البرنامج، يجب أن تكون المخرجات كالتالي:
1 2
إليك ما يفعله static في هذا السياق:
- الحفاظ على القيمة بين استدعاءات الدوال: عندما يتم تعريف متغير على أنه static داخل دالة، فإن قيمته تستمر بين استدعاءات الدوال. وهذا يعني أن المتغير يحتفظ بقيمته حتى بعد خروج الدالة f(). في المثال، يحتفظ المتغير x بقيمته بين استدعاءات f().
- التهيئة: تتم تهيئة المتغيرات الثابتة Static مرة واحدة فقط، في بداية تنفيذ البرنامج. في هذه الحالة، تتم تهيئة المتغير Static x إلى 0 في المرة الأولى التي يتم فيها استدعاء الدالة f(). لا تؤدي الاستدعاءات اللاحقة إلى f() إلى إعادة تهيئة x.
- مدة التخزين: المتغيرات الثابتة Static لها "مدة تخزين ثابتة"، مما يعني أنها مخصصة للذاكرة طوال مدة تنفيذ البرنامج. يتم تخصيص هذه الذاكرة في جزء البيانات من البرنامج.
- النطاق: يقتصر نطاق المتغير الثابت static المعلن داخل الدالة على تلك الدالة. ولا يمكن الوصول إليه من خارج الدالة. في هذا المثال، لا يمكن الوصول إلى x إلا من خلال الدالة f().
Static Class Member
#include <iostream> #include <cstring> #include <string> using namespace std; //define how many objects are created class Student { static int count; char name[20]; int first, second, final, total, ID; public: Student() //constructor { strcpy(name, "No name"); ID = 0; first = second = final = 0; count++; cout << "Numbers of students constructed: " << count << endl; } }; //end of class int Student::count = 0; int main() { cout << "\nConstruct 2 objects\n"; Student S1, S2; cout << "\nConstruct 3 objects\n"; Student St[3]; return 0; } //end of main
– تعريف الـ(Class): Student
- يعرف class Student بأسماء أعضاء البيانات الخاصة، first, second, final, total, وID
- تم تعريف متغير العدد على أنه static داخل الكلاس. وهذا يعني أنه مشترك بين جميع كانات Student class، وتستمر قيمتها عبر كائنات Student المتعددة.
أداة البناء Constructor:
- يحتوي الكلاس على أداة البناء الافتراضية Student() الذي يقوم بتهيئة أعضاء البيانات بالقيم الافتراضية.
- في constructor، يتم تهيئة name بـ"No name"، ويتم تهيئة ID بـ 0، ويتم تهيئة exam scores بـ 0.
- في كل مرة يتم إنشاء Student object، تتم زيادة متغير العدد، وتتم طباعة العدد الإجمالي للطلاب الذين تم إنشاؤهم.
تهيئة الـ Static Member:
- تتم تهيئة static member خارج تعريف class.
الدالة الرئيسة main:
- في الدالة main()، يتم إنشاء كائنين للـStudent (S1 وS2) باستخدام الـdefault constructor. يقوم البرنامج بطباعة عدد الطلاب الذين تم إنشاؤهم بعد إنشاء كل كائن.
- بعد ذلك، يتم إنشاء مصفوفة مكونة من ثلاثة كائنات للـStudent. مرة أخرى، يقوم البرنامج بطباعة عدد الطلاب الذين تم إنشاؤهم بعد إنشاء هذه المصفوفة.
المخرجات:
Construct 2 objects Numbers of students constructed: 1 Numbers of students constructed: 2 Construct 3 objects Numbers of students constructed: 3 Numbers of students constructed: 4 Numbers of students constructed: 5
يتم استخدام متغير الـ static member لتتبع العدد الإجمالي لكائنات الـStudent التي تم إنشاؤها. في كل مرة يتم فيها إنشاء كائن الطالب (أي عند استدعاء الـconstructor)، يزداد العدد. يسمح هذا للبرنامج بتتبع عدد كائنات الطالب التي تم إنشاء مثيل لها عبر تنفيذ البرنامج بأكمله.
Static Member Function
#include <iostream> #include <cstring> #include <string> using namespace std; class Student { static int count; char name[20]; int first, second, final, total, ID; public: static void print_count() { cout << "Students constructed: " << count << endl; } Student() //constructor { strcpy(name, "No name"); ID = 0; first = second = final = 0; count++; print_count(); } }; //end of class int Student::count = 0; int main() { Student::print_count(); cout << "\nConstruct 2 objects\n"; Student S1, S2; cout << "\nConstruct 3 objects\n"; Student St[3]; return 0; } //end of main
في هذا البرنامج الذي تم تحديثه، تمت إضافة وظيفة العضو (static ) print_count() إلى فئة الطالب. إليك ما تفعله الكلمة الأساسية static في هذا السياق:
- Static Member Function: تم تعريف دالة العضو print_count() على أنها static. وهذا يعني أنه ينتمي إلى الفئة نفسها، وليس إلى المثيلات الفردية (الكائنات) للفئة. لا تتمتع دوال الأعضاء الـstatic بإمكانية الوصول إلى متغيرات أو دوال الأعضاء غير الـstatic مباشرة لأنها غير مرتبطة بأي كائن معين.
- الوصول إلى متغيرات الأعضاء الـStatic: داخل دالة العضو الثابت print_count()، يتم الوصول إلى عدد متغيرات الأعضاء الــstatic مباشرة. وبما أن العد هو أيضًا static، فيمكن الوصول إليه دون الحاجة إلى مثيل للفئة.
- الوصول إلى دالة العضو الـStatic: يمكن استدعاء دوال العضو الـStatic باستخدام اسم الكلاس متبوعًا بعامل تحليل النطاق (::). على سبيل المثال، يستدعي Student::print_count() الدالة print_count() الخاصة بكلاس Student .
- الاستخدام: في الدالة main()، يتم استدعاء Student::print_count() قبل إنشاء أي كائنات Student. يوضح هذا أنه يمكن استدعاء دوال الأعضاء الـstatic دون الحاجة إلى إنشاء مثيل للكائن.
- التهيئة والزيادة: داخل Student constructor، تتم زيادة متغير العدد في كل مرة يتم فيها إنشاء كائن طالب جديد. يتم استدعاء الدالة print_count() داخل المنشئ لعرض العدد المحدث بعد كل إنشاء للكائن.
يوضح هذا البرنامج استخدام دالة عضو static للوصول إلى متغير عضو static وتوفير الدوال المرتبطة بالفئة نفسها، بدلاً من الكائنات الفردية للفئة.
الكائن الثابت Constant Object ودالة العضو الثابتة Constant Member Function
المقدمة
في لغة C++، المتغير الثابت هو متغير لا يمكن تغيير قيمته بمجرد تهيئته. تظل القيمة ثابتة طوال تنفيذ البرنامج. يتم الإعلان عن المتغيرات الثابتة باستخدام الكلمة الأساسية const.
ثابت الكائن Constant Object
- الإعلان: يتم الإعلان عن كائن ثابت باستخدام الكلمة الأساسية const مع نوع الكائن.
- غير قابل للتغيير: بمجرد إعلانه كـ const، لا يمكن تغيير متغيرات أعضاء الكائن.
- استدعاء دوال الأعضاء: يمكن للكائن الثابت فقط استدعاء دوال الأعضاء الثابتة من فئاتها. وهذا يمنع أية عمليات قد تؤدي إلى تعديل الحالة الداخلية للكائن.
دالة العضو الثابتة Constant Member Function
- الإعلان: يتم الإعلان عن دالة العضو على أنها ثابتة عن طريق إلحاق الكلمة الأساسية const بإعلانها وتعريفها.
- ضمان عدم التعديل: تضمن دالة العضو الثابت ضمنيًا بعدم تعديل أي من أعضاء البيانات غير الثابتة في فئتها.
- فرض الصحة: وهذا يساعد المترجم على فرض الصحة ويمنع الآثار الجانبية غير المقصودة.
مثال
#include <iostream> using namespace std; class time { private: int h, m, s; public: void print() const //constant function { cout << "Time = " << h << ":" << m << s << endl; } time(int i, int j, int k) { h = i; m = j; s = k; } }; int main() { const time noon(12, 0, 0); //constant object noon.print(); return 0; }
في الكود المقدم، تلعب كل من الدوال الثابتة والكائنات الثابتة أدوارًا مهمة:
الدالة الثابتة (print() const):
- تم تعريف دالة العضو print() على أنها const، مما يجعلها دالة عضو ثابتة.
- تضمن دالة العضو الثابت عدم تعديل حالة الكائن الذي يتم استدعاؤه عليه. يعد بعدم تعديل أي متغيرات أعضاء غير ثابتة للفئة.
- في هذا الكود، تكون الدالة print() مسؤولة عن عرض الوقت المخزن في كائن الوقت. نظرًا لأنه لا يعدل حالة الكائن، يتم تعريفه على أنه const.
- من خلال الإعلان عن print() كدالة عضو ثابتة، فإنها تسمح للكائنات الثابتة باستدعاء هذه الدالة.
الكائن الثابت (const time noon(12, 0, 0)):
- يتم إنشاء كائن يسمى noon للـclass time وإعلانه على أنه const. وهذا يجعل noon كائنًا ثابتًا.
- الكائن الثابت هو كائن لا يمكن تعديل حالته بعد التهيئة. أي محاولة لتعديل حالته ستؤدي إلى خطأ في الترجمة.
- في هذا الكود، يمثل كائن noon وقتًا محددًا، 12:00:00، ولا يمكن تغييره بمجرد إنشائه.
- تكون الكائنات الثابتة مفيدة عندما تريد التأكد من بقاء حالة الكائن دون تغيير طوال عمره، مما يوفر ضمانات بالثبات والأمان.
- من خلال الإعلان عن noon ككائن ثابت، فإنه يضمن إمكانية استدعاء الدالة print()، والتي تم وضع علامة عليها أيضًا على أنها const، عليه. يسمح هذا للدالة print() بالوصول إلى حالة كائن الظهيرة دون خوف من التعديل.
باختصار، تضمن الدالة الثابتة (print() const) عدم تعديل حالة الكائن، ويضمن الكائن الثابت (const time noon(12, 0, 0)) بقاء حالته دون تغيير طوال حياته. تعمل هذه المفاهيم معًا لفرض الثبات والسلامة في الكود.
دالة الصديق Friend Function/ وفئة الصديق Friend class
في لغة (C++)، friend function أو friend class هي ميزة تسمح لدالة أو فئة بالوصول إلى الأعضاء الخاصين والمحميين في فئة أخرى. تعمل هذه الميزة على كسر التغليف إلى حد ما، ولكنها قد تكون مفيدة في سيناريوهات محددة حيث يكون الاقتران المحكم بين الفئات ضروريًا أو عند تنفيذ أنماط تصميم معينة.
Friend Function:
دالة الصديق هي دالة عادية تُمنح حق الوصول إلى أعضاء خاصين ومحميين في الفئة. لإعلان دالة كصديق لفئة ما، يجب عليك الإعلان عنها ضمن إعلان الفئة المسبوق بالكلمة الأساسية "friend".
مثال 1:
#include <iostream> using namespace std; class myClass { int a, b; public: myClass(int i, int j) { a = i; b = j; } friend int sum(myClass ob); }; int sum(myClass ob) { return ob.a + ob.b; } int main() { myClass o(10, 20); cout << sum(o) << endl; return 0; }
في مقتطف كود C++ هذا، يتم تعريف فئة تسمى myClass مع عضوين صحيحين خاصين a وb. يحتوي الـclass أيضًا على مُنشئ لتهيئة هؤلاء الأعضاء. بالإضافة إلى ذلك، هناك مجموع دالة friend معلنة داخل الـclass.
تأخذ الدالة sum كائنًا من النوع myClass كوسيطة لها وترجع مجموع الأعضاء الخاصين a وb لهذا الكائن.
في الوظيفة الرئيسية، يوجد كائن o من النوع myClass بقيم أولية 10 و20، ثم يتم استدعاء دالة المجموع لتمرير هذا الكائن o كوسيطة. وأخيرًا، إخراج نتيجة دالة المجموع باستخدام cout.
فيما يلي تفاصيل تنفيذ التعليمات البرمجية:
- يتم إنشاء كائن o من النوع myClass بالقيمتين 10 و20.
- يتم استدعاء الدالة sum باستخدام الكائن o كوسيطة.
- داخل الدالة sum، يتم الوصول إلى العضوين الخاصين a وb للكائن ob من خلال إعلان friend، ويتم إرجاع مجموعهما.
- ثم تتم طباعة sum باستخدام cout.
لذلك، عند تشغيل هذا البرنامج، تكون المخرجات:
30
مثال 2:
#include <iostream> using namespace std; class CRectangle { private: int width, height; friend CRectangle duplicate(CRectangle); public: void set_values(int, int); int area(void) { return (width * height); } }; void CRectangle::set_values(int a, int b) { width = a; height = b; } CRectangle duplicate(CRectangle R) { CRectangle T; T.width = R.width * 2; T.height = R.height * 2; return T; } int main() { CRectangle rect, rectb; rect.set_values(2, 3); cout << "The area before duplicate = " << rect.area() << endl; rectb = duplicate(rect); cout << "The area after duplicate = " << rectb.area() << endl; return 0; }
يحدد كود C++ هذا فئة CRectangle تمثل مستطيلاً. يحتوي الكلاس على عرض وارتفاع أعضاء البيانات الخاصة، ودالتين عضويتين هما set_values وarea.
في القسم private، العرض والارتفاع هما أبعاد المستطيل. يتم استخدام الدالة set_values لتعيين قيم العرض والارتفاع. تقوم دالة المساحة بحساب مساحة المستطيل وإرجاعها.
تم الإعلان عن الدالة المكررة كصديق لـ CRectangle. وهذا يعني أن لديه إمكانية الوصول إلى أعضاء CRectangle الخاصين.
تقوم الدالة set_values بتعيين عرض المستطيل وارتفاعه بناءً على المعلمات المتوفرة.
تأخذ الدالة المكررة كائن CRectangle كمعلمة، وتنشئ كائن CRectangle جديد T، وتعين عرض وارتفاع T ليكون ضعف عرض وارتفاع الإدخال R. ثم يقوم بإرجاع المستطيل الجديد T.
في الدالة الرئيسة:
- يتم إنشاء كائنات rect وrectb لـCRectangle.
- يتم استدعاء الدالة set_values في rect لتعيين أبعادها على 2×3.
- سيتم طباعة مساحة rect قبل مضاعفتها.
- يتم استدعاء الدالة المكررة باستخدام rect كوسيطة، ويتم تعيين المستطيل الذي تم إرجاعه إلى rectb.
- تتم طباعة rectb بعد مضاعفتها.
مخرجات هذا البرنامج تكون كالتالي:
The area before duplicate = 6 The area after duplicate = 24
مثال 3:
#include <iostream> using namespace std; class Tri; class CRectangle { int width, height; public: void set_values(int a, int b) { width = a; height = b; } friend int Sum(Tri T, CRectangle R); }; class Tri { int W, H; public: Tri(int a, int b) { W = a; H = b; } friend int Sum(Tri T, CRectangle R); }; int Sum(Tri T, CRectangle R) { return T.W + R.width; } int main() { CRectangle r; r.set_values(2, 3); Tri l(5, 10); cout << Sum(l, r) << endl; return 0; }
يوضح برنامج C++ هذا استخدام دوال friend عبر فئات مختلفة. إليك تفاصيل الكود:
إعلان الكلاس:
- تم الإعلان عن فئتين: CRectangle وTri.
- يحتوي CRectangle على عضوين خاصين بالعرض والارتفاع، ووظيفة العضو set_values لتعيين هذه القيم.
- يحتوي Tri على عضوين خاصين من البيانات W وH، ومُنشئ لتهيئتهما.
الإعلان عن دالة Friend:
- يعلن كلا الفئتين عن وظيفة صديق تسمى Sum. هذا يعني أن الدالة Sum يمكنها الوصول إلى الأعضاء الخاصين في كل من CRectangle وTri.
تنفيذ دالة Friend:
- تأخذ الدالة Sum كائنات من النوعين Tri وCRectangle كوسائط وتقوم بإرجاع مجموع أعضائها الخاصين.
- في هذه الحالة، تقوم بإرجاع مجموع W من الكائن Tri والعرض من الكائن CRectangle.
الدالة الرئيسة main:
- في الدالة الرئيسية، يتم إنشاء كائن r من النوع CRectangle ويتم استدعاء دالة set_values الخاصة به لتعيين أبعاده إلى 2×3.
- تم إنشاء كائن آخر l من النوع Tri بأبعاد 5×10.
- يتم استدعاء الدالة Sum باستخدام الكائنين l وr كوسائط، ويتم طباعة النتيجة.
المخرجات:
- نظرًا لأن الدالة Sum تُرجع مجموع W من Tri والعرض من CRectangle، فسيكون الناتج 5 + 2 = 7.
Friend Class:
فئة Friend هي فئة يتم منحها حق الوصول إلى الأعضاء الخاصين والمحميين في فئة أخرى. يمكنك الإعلان عن فئة Friend من خلال سبق الإعلان عنها بالكلمة الأساسية "Friend" داخل الفئة التي تعتبر صديقًا لها.
مثال 4:
#include <iostream> using namespace std; class CSquare; class CRectangle { int width, height; public: int area(void) { return (width * height); } void convert(CSquare); }; class CSquare { private: int side; public: void set_side(int x) { side = x; } friend class CRectangle; }; void CRectangle::convert(CSquare a) { width = a.side; height = a.side; } int main() { CSquare sqr; CRectangle rect; sqr.set_side(4); rect.convert(sqr); cout << rect.area() << endl; return 0; }
يوضح برنامج C++ هذا استخدام فئات الأصدقاء ووظائف الأعضاء، خاصة في سياق فئة CRectangle وفئة CSquare.
إعلان الكلاس:
- تم الإعلان عن فئتين: CRectangle و CSquare.
- يحتوي CRectangle على عضوين خاصين بالعرض والارتفاع، ومنطقة دالة للعضو لحساب مساحتها. كما أن لديها دالة تحويل العضو، والتي تم تعريفها لاحقًا خارج الكلاس.
- يحتوي CSquare على جانب واحد من أعضاء البيانات الخاصة ودالة عضو set_side لتعيين قيمته. يعلن عن CRectangle كفئة صديق.
الإعلان عن الكلاس Friend:
- تعلن CSquare عن CRectangle كفئة أصدقاء، مما يسمح لـ CRectangle بالوصول إلى أعضائها الخاصين.
تعريف دالة العضو:
- يتم تعريف دالة عضو التحويل لـ CRectangle خارج الفئة.
- يأخذ كائن CSquare كمعلمة ويعين جانبه لكل من عرض وارتفاع المستطيل.
الدالة الرئيسة main:
- في الدالة الرئيسية، يتم إنشاء الكائنات sqr من النوع CSquare والمستطيل من النوع CRectangle.
- تقوم الدالة set_side لـ sqr بتعيين side على 4.
- يتم استدعاء دالة تحويل rect باستخدام sqr كوسيطة، وتحويل المربع إلى مستطيل.
- ثم تتم طباعة مساحة rect الناتج.
المخرجات:
- بما أن طول ضلع المربع هو 4، فبعد تحويله إلى مستطيل، يصبح كل من عرض المستطيل وارتفاعه 4. وبالتالي فإن مساحة المستطيل الناتج هي 4 * 4 = 16.
التحميل الزائد
إن التحميل الزائد للمشغل في لغة C++ هو ميزة تسمح لك بإعادة تعريف سلوك المشغلين (مثل +، -، *، /، وما إلى ذلك) للأنواع المحددة من قبل المستخدم. فهو يمكّنك من توسيع وظائف المشغلين للعمل مع كائنات من فئاتك الخاصة.
مثال 1:
#include <iostream> using namespace std; class triangle { private: float width, height; public: triangle(float a = 0, float b = 0) { width = a; height = b; } void getdata() { cout << "Enter width \n"; cin >> width; cout << "Enter height \n"; cin >> height; } void showdata() { cout << "width and height = (" << width << "," << height << ")" << endl; } void add(triangle c1, triangle c2) { width = c1.width + c2.width; height = c1.height + c2.height; } }; int main() { triangle c1, c2(3.5, 1.5), c3; c1.getdata(); c3.add(c1, c2); c3.showdata(); return 0; }
يحدد برنامج C++ هذا الفئة triangle التي تمثل مثلثًا. تحتوي على عرض وارتفاع أعضاء البيانات الخاصة، ومنشئ لتهيئتهم، ودوالالأعضاء getdata لإدخال قيم العرض والارتفاع، وإظهار البيانات لعرض العرض والارتفاع، والإضافة لإضافة مثلثين معًا.
تعريف الـclass:
- يتم تعريف triangle class بعرض وارتفاع أعضاء البيانات الخاصة.
- يحتوي على constructor يقوم بتهيئة العرض والارتفاع بالقيم الافتراضية 0.
- يحتوي على دوال الأعضاء getdata، showdata، والإضافة.
الدالة الرئيسة main:
- في الدالة الرئيسية، يتم إنشاء ثلاثة (triangle objects): c1 وc2 وc3.
- تتم تهيئة c1 بالقيم الافتراضية باستخدام default constructor.
- تتم تهيئة c2 بالقيمتين 3.5 و1.5 باستخدام parameterized constructor.
- يتم إنشاء c3 دون أي تعريف مسبق.
- يتم استدعاء getdata لـ c1 لإدخال قيم العرض والارتفاع.
- يتم استدعاء الإضافة لـ c3 مع c1 وc2 كـarguments، مما يضيف العرض والارتفاع المقابلين لـ c1 وc2 ويعين النتيجة إلى c3.
- يتم استدعاء showdata لـ c3 لعرض العرض والارتفاع الناتج بعد الإضافة.
مثال 2:
#include <iostream> using namespace std; class triangle { private: float width, height; public: triangle(float a = 0, float b = 0) { width = a; height = b; } void getdata() { cout << "Enter width \n"; cin >> width; cout << "Enter height \n"; cin >> height; } void showdata() { cout << "width and height = (" << width << "," << height << ")" << endl; } triangle add(triangle c2) { triangle c3; c3.width = width + c2.width; c3.height = height + c2.height; return c3; } }; int main() { triangle c1, c2(3.5, 1.5), c3; c1.getdata(); c3 = c1.add(c2); c3.showdata(); return 0; }
في هذا المثال، يتم أيضًا إجراء عملية إضافة مثلثين باستخدام دالة عضو تسمى add. ومع ذلك، بدلاً من تعديل الكائن الذي يتم استدعاء الدالة عليه، يتم إنشاء كائن مثلث جديد داخل دالة الإضافة لتخزين نتيجة الإضافة. يتم بعد ذلك إرجاع هذا الكائن الجديد من الدالة، مع ترك الكائنات الأصلية دون تغيير.
مثال 3:
#include <iostream> using namespace std; class triangle { private: float width, height; public: triangle(float a = 0, float b = 0) { width = a; height = b; } void getdata() { cout << "Enter width \n"; cin >> width; cout << "Enter height \n"; cin >> height; } void showdata() { cout << "width and height = (" << width << "," << height << ")" << endl; } triangle operator+(triangle c2) // Overloading the + operator { triangle c3; c3.width = width + c2.width; c3.height = height + c2.height; return c3; } }; int main() { triangle c1, c2(3.5, 1.5), c3; c1.getdata(); c3 = c1 + c2; // Using the overloaded + operator c3.showdata(); return 0; }
يوضح برنامج C++ هذا استخدام التحميل الزائد للمشغل لإجراء عملية الجمع بين كائنين مثلثين. دعنا نحلل الكود ونسلط الضوء على الاختلافات مقارنة بالأمثلة السابقة:
الاختلافات مقارنة بالأمثلة السابقة:
- التحميل الزائد:
في هذا المثال، يتم تحميل عامل التشغيل + بشكل زائد باستخدام دالة عضو تسمى operator+. يتيح لنا ذلك إجراء عملية الجمع بين كائنين مثلثين باستخدام العامل + بطريقة مشابهة للـbuilt-in types.
في المقابل، استخدمت الأمثلة السابقة دوال الأعضاء (add) لإجراء عملية الجمع بين الكائنات. - الإستخدام في الدالة الرئيسة main:
في الدالة الرئيسية، يتم تنفيذ إضافة c1 وc2 باستخدام + operator: c3 = c1 + c2;.
يعد بناء الجملة هذا أكثر سهولة وإيجازًا مقارنة باستدعاء دالة عضو منفصلة (add). - نوع الإسترجاع return:
تقوم الدالة operator+ بإرجاع triangle object يمثل نتيجة عملية الإضافة.
في الأمثلة السابقة، لم تُرجع دوال الإضافة أي قيمة، وبدلاً من ذلك قامت بتعديل أحد الـobjects مباشرةً.
المؤشر this
في لغات البرمجة الكائنية (OOP) مثل C++، يكون المؤشر this عبارة عن كلمة أساسية تُستخدم للإشارة إلى الكائن الحالي داخل دالة العضو. إنه مؤشر خاص يحمل عنوان الذاكرة لمثيل الكائن الحالي.
مثال 1:
#include <iostream> using namespace std; class stud { public: void address() { cout << this; } }; int main() { stud a, b, c; cout << "The adress of a\t"; a.address(); cout << endl << "The adress of b\t"; b.address(); cout << endl << "The adress of c\t"; c.address(); cout << endl; return 0; }
في هذا البرنامج، لدينا class يسمى Stu، والذي يحتوي على عنوان دالة العضو address(). تقوم دالة العضو هذه ببساطة بطباعة عنوان الذاكرة للكائن الحالي باستخدام المؤشر this.
وظيفة المؤشر this:
- في دالة address() لـstud class، يشير this إلى الكائن الحالي الذي يتم استدعاء دالة العضو من أجله.
- داخل الدالة (address()) يقوم cout << this; بطباعة عنوان الذاكرة للكائن الحالي.
- في الدالة main()، نقوم بإنشاء ثلاثة كائنات a وb وc من class stud.
- نقوم بعد ذلك باستدعاء الدالة address() على كل كائن، والتي تطبع عنوان الذاكرة لكل كائن.
- يضمن المؤشر this طباعة عنوان الذاكرة الصحيح للكائن الحالي عند استدعاء دالة address() لكل كائن.
مثال 2:
#include <iostream> using namespace std; class Student { int id; public: void set_id(int id) { this->id = id; } void print_id() { cout << "ID is " << id << endl; } }; int main() { Student St; St.set_id(10); St.print_id(); return 0; }
في هذا البرنامج، لدينا class اسمه Student مع معرف متغير عضو خاص يمثل id الطالب. يوفر الـclass دالتين للعضو: set_id() وprint_id().
وظيفة المؤشر this:
- في دالة العضو set_id()، معرف المعلمة الذي تم تمريره إلى الدالةله نفس اسم معرف متغير العضو للفئة. للتمييز بينهما، يتم استخدام المؤشر this.
- داخل الدالة set_id()، this->id = id; يعين قيمة معرف المعلمة لمعرف متغير العضو للكائن الحالي.
- يشير المؤشر this إلى الكائن الحالي الذي يتم استدعاء دالة العضو من أجله. يتم استخدامه للوصول إلى أعضاء الكائن.
- في الدالة main()، يتم إنشاء كائن St من فئة Student. يتم استدعاء الدالة set_id() لتعيين معرف الكائن St إلى 10.
- ثم يتم استدعاء الدالة print_id() لطباعة معرف الكائن St.
ستكون مخرجات البرنامج كالتالي:
ID is 10
التحميل الزائد للمعاملات الاحادية
في لغة C++، يشير التحميل الزائد للمشغل إلى القدرة على إعادة تعريف سلوك العوامل مثل العوامل الحسابية والمنطقية للأنواع المحددة من قبل المستخدم. العوامل الأحادية هي تلك العوامل التي تعمل على معامل واحد. يسمح لك التحميل الزائد للعوامل الأحادية بتخصيص سلوكها عند تطبيقها على كائنات من فئاتك الخاصة.
الغرض من التحميل الزائد للمعامل:
- توفير بناء الجملة الطبيعي: يتيح لك التحميل الزائد للمعامل استخدام عوامل التشغيل مع الأنواع المعرفة من قبل المستخدم بطريقة تحاكي استخدامها مع الأنواع المضمنة. على سبيل المثال، يمكنك إضافة كائنين من فئة باستخدام عامل التشغيل +، أو ربط السلاسل باستخدام عامل التشغيل +، أو زيادة كائن باستخدام عامل التشغيل ++.
- تحسين إمكانية القراءة: من خلال التحميل الزائد على المعاملات، يمكنك جعل التعليمات البرمجية الخاصة بك أكثر سهولة وقابلية للقراءة، حيث تتيح لك التعبير عن العمليات بطريقة طبيعية وموجزة.
مثال 1:
يوضح برنامج C++ هذا التحميل الزائد لمعامل unary ++ لفئة تسمى Unary. دعونا نحلل الكود:
#include <iostream> using namespace std; class Unary { int x, y; public: Unary(int i = 0, int j = 0) { x = i; y = j; } void show() { cout << x << " " << y << endl; } // Overloading the unary ++ operator as a member function void operator++() { x++; y++; } }; int main() { Unary v(10, 20); v++; // Incrementing the object 'v' using overloaded ++ operator v.show(); // Displaying the updated values of 'x' and 'y' return 0; }
شرح:
- تحتوي الفئة Unary على متغيرين خاصين للعضو x وy، ومُنشئ لتهيئتهما. بالإضافة إلى ذلك، يحتوي على دالة عضو show() لعرض قيم x وy.
- داخل تعريف الفئة، يتم تحميل المعامل الأحادي ++ بشكل زائد باستخدام دالة operator++(). تعمل هذه الدالة على زيادة قيم x وy.
- في الدالة main()، يتم إنشاء كائن v من الفئة Unary بقيم أولية 10 لـ x و20 لـ y.
- يتم تطبيق operator ++ على الكائن v باستخدام v++. وبما أنه عامل أحادي، فإنه لا يتطلب أي معامل.
- تعمل الدالة operator++() المحملة بشكل زائد على زيادة قيم x وy بمقدار واحد.
- بعد عملية الزيادة، يتم استدعاء الدالة show() لعرض القيم المحدثة لـ x وy.
ستكون مخرجات البرنامج كالتالي:
11 21
وذلك لأن قيم x وy تتزايد بمقدار 1 بعد تطبيق operator ++ على الكائن v. وبالتالي، يتم عرض القيم المحدثة كـ 11 لـ x و21 لـ y.
مثال 2:
#include <iostream> using namespace std; class Unary { int x, y; public: Unary(int i = 0, int j = 0) { x = i; y = j; } void show() { cout << x << " " << y << endl; } Unary operator++() { x++; y++; return *this; } Unary operator++(int) { Unary t; t = *this; x++; y++; return t; } }; int main() { Unary v(10, 20), k; v++; k = v++; k.show(); v.show(); return 0; }
يوضح برنامج C++ هذا التحميل الزائد للعامل unary ++ لفئة تسمى Unary، بالإضافة إلى عامل زيادة postfix ++ الذي يأخذ معلمة عدد صحيح وهمي إضافية. دعونا نحلل الكود:
- تحتوي الفئة Unary على متغيرين خاصين للعضو x وy، ومُنشئ لتهيئتهما. بالإضافة إلى ذلك، يحتوي على دالة عضو show() لعرض قيم x وy.
- داخل تعريف الفئة، يتم تحميل عامل التشغيل الأحادي ++ مرتين:
1. تقوم دالة operator++() بتحميل عامل التشغيل البادئة ++ بشكل زائد. فهو يزيد قيم x وy ويعيد الكائن المحدث.
2. تقوم دالة عامل التشغيل ++(int) بتحميل عامل تشغيل postfix ++ بشكل زائد بمعلمة عدد صحيح وهمي إضافية. يقوم بإنشاء كائن مؤقت t وتعيين قيم الكائن الحالي له. ثم يقوم بزيادة قيم x و y للكائن الحالي وإرجاع الكائن المؤقت t. - في الدالة main()، يتم إنشاء كائنين v وk من الفئة Unary، مع تهيئة v بالقيم 10 لـ x و20 لـ y.
- يتم تطبيق v++ على الزيادة v باستخدام عامل زيادة postfix. سيؤدي هذا إلى زيادة قيم x وy لـ v وإرجاع كائن مؤقت بالقيم الأصلية.
- k = v++ سيقوم أولاً بتعيين الكائن المؤقت (بالقيم الأصلية) إلى k ثم يزيد v باستخدام عامل زيادة postfix.
- يتم استدعاء الدالة show() لعرض قيم k وv بعد عمليات الزيادة.
ستكون مخرجات البرنامج كالتالي:
11 21 12 22
مثال 3:
#include <iostream> using namespace std; class Unary { int x, y; public: Unary(int i = 0, int j = 0) { x = i; y = j; } void show() { cout << x << " " << y << endl; } // Overloading the prefix ++ operator Unary operator++() { x++; y++; return *this; } // Overloading the postfix ++ operator with an additional dummy integer parameter Unary operator++(int) { Unary t; t = *this; x++; y++; return t; } // Overloading the unary - operator Unary operator-() { x = -x; y = -y; return *this; } }; int main() { Unary k(1, 2); // Create an object of the class Unary with initial values 1 and 2 -k; // Applying unary - operator to object k k.show(); // Display the updated values of k return 0; }
- تعمل دالة operator-() على زيادة التحميل على عامل التشغيل الأحادي. فهو يلغي قيم x وy بضربهما في -1 ويعيد الكائن المحدث.
- في الدالة main()، يتم إنشاء كائن k من الفئة Unary بالقيم الأولية 1 لـ x و2 لـ y.
- يتم تطبيق العامل الأحادي – على الكائن k باستخدام -k. سيؤدي هذا إلى إلغاء قيم x و y لـ k.
- أخيرًا، يتم استدعاء الدالة show() لعرض القيم المحدثة لـ x وy.
يشير هذا إلى أن قيم x وy للكائن k قد تم إبطالها من خلال تطبيق العامل الأحادي. وبالتالي، تصبح x -1 وy تصبح -2.
مثال 4:
#include <iostream> using namespace std; class Unary { int x, y; public: Unary(int i = 0, int j = 0) { x = i; y = j; } void show() { cout << x << " " << y << endl; } Unary operator++() { x++; y++; return *this; } Unary operator++(int) { Unary t; t = *this; x++; y++; return t; } Unary operator-() { x = -x; y = -y; return *this; } bool operator!() { return (x == 0 && y == 0); } }; int main() { Unary x(1, 1); if (!x) cout << "true"; else cout << "false"; return 0; }
- تقوم الدالة operator!() بتحميل عامل النفي المنطقي بشكل زائد!. تُرجع القيمة true إذا كان كل من x وy يساوي 0، مما يشير إلى أن الكائن منطقيًا false. وإلا فإنها ترجع false.
- في الدالة main()، يتم إنشاء كائن x من الفئة Unary بالقيم الأولية 1 لـ x و1 لـ y.
- عامل النفي المنطقي operator ! يتم تطبيقه على الكائن x باستخدام !x. إذا كانت x خاطئة منطقيًا (أي أن كلاً من x وy يساوي 0)، فسيتم طباعة "true". وإلا فإنه يطبع "false".
ستكون مخرجات البرنامج كالتالي:
false
مثال 5:
#include <iostream> using namespace std; class Unary { int x, y; public: Unary(int i = 0, int j = 0) { x = i; y = j; } void show() { cout << x << " " << y << endl; } Unary operator++() { x++; y++; return *this; } Unary operator++(int) { Unary t; t = *this; x++; y++; return t; } Unary operator-() { x = -x; y = -y; return *this; } bool operator!() { return (x == 0 && y == 0); } Unary operator+=(Unary b2) { x += b2.x; y += b2.y; return *this; } }; int main() { Unary b(1, 2), b2(3, 3); b += b2; b.show(); return 0; }
تقوم الدالة operator+=() بتحميل عامل التعيين المركب += بشكل زائد. يقوم بإضافة متغيرات الأعضاء المقابلة للكائن الحالي (this) مع متغيرات الأعضاء للكائن الذي تم تمريره (b2) وإرجاع الكائن المحدث.
ستكون مخرجات البرنامج كالتالي:
4 5
التحميل الزائد لمعاملات المقارنة
في C++، يسمح لك التحميل الزائد للمعامل بإعادة تعريف سلوك العوامل مثل العوامل الحسابية والمنطقية للأنواع المعرفة من قبل المستخدم. يتم استخدام عوامل الـRelational لمقارنة القيم بين معاملين وتحديد العلاقة بينهما. يسمح لك التحميل الزائد للمشغل لعوامل الـRelational بتحديد سلوكيات المقارنة المخصصة للكائنات من فئاتك الخاصة.
الهدف من إستخدام التحميل الزائد لمعاملات المقارنة:
- المقارنة المخصصة: مع التحميل الزائد للمشغل، يمكنك تحديد سلوكيات المقارنة المخصصة لكائنات فئاتك. يتيح لك ذلك مقارنة الكائنات بناءً على معايير محددة ذات صلة بمجال التطبيق الخاص بك.
- بناء الجملة الطبيعي: تتيح لك عوامل الـrelational المحملة بشكل زائد استخدام البنية المألوفة لعوامل الـrelational (<، ، >=، ==، !=) مع الأنواع المعرفة من قبل المستخدم. يؤدي ذلك إلى تحسين إمكانية قراءة التعليمات البرمجية ويجعل التعليمات البرمجية الخاصة بك أكثر سهولة.
مثال:
#include <iostream> using namespace std; class Relational { int x, y, z; public: Relational() { x = y = z = 0; } Relational(int i, int j, int k) { x = i; y = j; z = k; } int operator==(Relational b) { return(x == b.x && y == b.y && z == b.z); } }; int main() { Relational a(10, 10, 10), b(10, 10, 10); if (a == b) cout << "The two objects are equal\n"; else cout << "The two objects are not equal\n"; return 0; }
يوضح برنامج C++ هذا التحميل الزائد للمعامل لمعامل المساواة (==) في الفئة الـRelational.
- تمثل الفئة Relational كائنات ذات ثلاث attributes صحيحة x وy وz.
- توفر الفئة مُنشئين constructors: مُنشئ افتراضي واحد يقوم بتهيئة جميع الـattributes إلى 0، ومنشئ آخر يسمح بتهيئة الـattributes بالقيم المقدمة.
- عامل operator= = تم تحميل الدالة بشكل زائد داخل الفئة. يأخذ كائن Relational آخر b كمعلمة ويعيد عددًا صحيحًا يشير إلى ما إذا كانت سمات الكائنين متساوية. يتحقق مما إذا كانت سمات x وy وz لكلا الكائنين متساوية ويعيد 1 إذا كانت كذلك، و0 بخلاف ذلك.
- في الدالة main()، يتم إنشاء كائنين (Relational) a وb، وتم تهيئتهما بالقيم (10، 10، 10).
- يتم استخدام عامل المساواة المحمل بشكل زائد == لمقارنة الكائنات a وb. إذا كانت سمات كلا الكائنين متساوية، فستتم طباعة الرسالة "The two objects are equal"؛ وإلا فستتم طباعة الرسالة "The two objects are not equal".
مكتبة النماذج القياسية STL في لغة C++
تشتمل مكتبة النماذج القياسية (STL) على مجموعة من فئات قوالب (template classes) C++ المصممة لتوفير هياكل ودوال بيانات البرمجة الأساسية، بما في ذلك lists وstacks وarrays والمزيد. تشتمل هذه المكتبة على library comprises container classes, algorithms, وiterators، مما يجعلها مجموعة أدوات متعددة الاستخدامات. يتم تحديد مكونات STL، مما يساهم في عموميتها. يعد الكفاءة في العمل مع template classes شرطًا أساسيًا لاستخدام STL بشكل فعال.
مقدمة إلى مكتبة النماذج القياسية STL
مكتبة النماذج القياسية (STL) عبارة عن مجموعة قوية من الفئات والدوال في لغة C++ والتي توفر خوارزميات وحاويات ومكررات عامة وقابلة لإعادة الاستخدام. إنه مكون أساسي في برمجة C++ الحديثة ويوفر مجموعة كبيرة من الوظائف لتبسيط التعليمات البرمجية وتحسينها.
المكونات الرئيسية لمكتبة النماذج القياسية STL:
- الحاويات Containers:
الـContainers هي objects تقوم بتخزين مجموعات من الـobjects الأخرى. أنها توفر وسيلة لتنظيم ومعالجة البيانات بكفاءة. توفر STL العديد من فئات الحاويات، ولكل منها خصائصها الفريدة:
- Vector: مصفوفة ديناميكية تقوم بضبط حجمها تلقائيًا.
- List: قائمة مرتبطة بشكل مزدوج تسمح بالإدراج والحذف السريع في أي مكان في القائمة.
- Deque: قائمة انتظار ذات نهاية مزدوجة تدعم الإدراج والحذف السريع في كلا الطرفين.
- Set: مجموعة من العناصر الفريدة والمرتبة.
- Map: مجموعة من key-value pairs، حيث يكون كل مفتاح فريدًا.
- Stack: عبارة عن Container مع إمكانية الوصول إلى آخر ما يدخل هو أول ما يخرج (LIFO).
- Stack: عبارة عن Container مع إمكانية الوصول إلى أول ما يدخل هو أول ما يخرج (LIFO). - التكرارات Iterators:
التكرارات هي objects تسمح بالاجتياز عبر عناصر الـcontainer. تعمل كمؤشرات وتوفر طريقة موحدة للوصول إلى العناصر بغض النظر عن تطبيق الـcontainer الأساسية. هناك أنواع مختلفة من التكرارات:
- Input iterators: السماح بقراءة العناصر من الـcontainer.
- Output iterators: السماح بكتابة العناصر في الـcontainer.
- Forward iterators: دعم الاجتياز للأمام.
- Bidirectional iterators: تدعم الاجتياز ثنائي الاتجاه (للأمام والخلف).
- Random access iterators: دعم الوصول العشوائي إلى العناصر (على سبيل المثال، الوصول إلى العناصر عن طريق index). - الخوارزميات Algorithms:
الخوارزميات هي وظائف تعمل على الـcontainers من خلال التكرارات. يقومون بعمليات مختلفة مثل البحث عن العناصر الموجودة في الحاويات وفرزها وتعديلها ومعالجتها. توفر STL مجموعة واسعة من الخوارزميات للمهام الشائعة، بما في ذلك:
- Sorting: فرز العناصر في container.
- Searching: البحث عن العناصر الموجودة في الـcontainer.
- Transforming: تعديل العناصر في الـcontainer بناءً على معايير معينة.
- Copying: نسخ العناصر بين الـcontainers.
- الدمج: دمج عناصر من containers متعددة في container واحدة مرتبة.
والكثير بعد من الخوارزميات ...
أنواع الحاويات Container
- Simple
– Pair - Sequence
– Array
– Vector
– Deque
– List
– Forward list - Associative
– Map
– Multimap
– Set
– Multiset - Unordered
– Unordered set
– Unordered multiset
– Unordered map
– Unordered multimap - Adapter
– Stack
– Queue
– Priority queue
المتجه (Vector) 1
Sequence containers في C++ هي فئة من الـcontainers الموجودة في مكتبة النماذج القياسية (STL) التي تخزن العناصر بترتيب تسلسلي. إنها توفر تخصيصًا ديناميكيًا للتخزين وتدعم العمليات المختلفة للوصول إلى العناصر وإدراجها وإزالتها. يتم تصنيف Sequence containers بشكل أساسي بناءً على كيفية تنظيم عناصرها وإدارتها. فيما يلي شرح لـSequence containers شائعة الاستخدام:
المصفوفات Arrays
تعتبر المصفوفات بمثابة نوع من sequence container في لغة C++. ومع ذلك، يتم تصنيفها عادةً بشكل منفصل عن sequence containers الأخرى مثل الـvectors والـlists وdeques بسبب طبيعة حجمها الثابت وغياب إمكانيات تغيير الحجم الديناميكي.
المتجه Vector
الخصائص:
- إدراج سريع/إزالة سريعة في النهاية
- إدراج بطيء/إزالة بطيئة في البداية أو في المنتصف
- بحث بطيء
مثال 1:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v(4); v[0] = 10; v.push_back(50); return 0; }
يحاول مقتطف التعليمات البرمجية هذا إنشاء متجه للأعداد الصحيحة بحجم أولي مكون من 4 عناصر، ويقوم بتعيين قيمة للعنصر الأول، ثم يحاول إضافة عنصر إضافي باستخدام دالة Push_back. ومع ذلك، هناك مشكلة محتملة في استخدام Push_back بعد تحديد الحجم الأولي.
دعونا نحلل الكود ونناقش الآثار المترتبة:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(4); // Creates a vector with 4 elements, all initialized to 0 v[0] = 10; // Assigns a value of 10 to the first element of the vector v.push_back(50); // Attempts to add an additional element with the value 50 // This line may lead to undefined behavior because it's // appending an element beyond the originally specified size return 0; }
شرح:
- السطر vector v(4); ينشئ متجهًا v بحجم أولي مكون من 4 عناصر، تمت تهيئتها جميعًا إلى القيمة الافتراضية للأعداد الصحيحة (وهي 0).
- ثم، v[0] = 10؛ يعين قيمة 10 للعنصر الأول من المتجه.
- ومع ذلك، السطر التالي v.push_back(50); يحاول إضافة عنصر إضافي إلى المتجه باستخدام Push_back. نظرًا لأن حجم المتجه كان في البداية يحتوي على 4 عناصر فقط، فإن إلحاق عنصر يتجاوز هذا الحجم قد يؤدي إلى سلوك غير محدد. قد تؤدي عملية Push_back إلى إعادة تخصيص وحدة التخزين الأساسية للمتجه، مما قد يؤدي إلى تلف الذاكرة أو أي سلوك آخر غير متوقع.
مثال 2:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {1, 2, 3, 4}; // Initialize vector v with values 1, 2, 3, 4 vector<int>v2(v); // Initialize vector v2 as a copy of v cout << v2[0]; // Output the first element of v2 return 0; }
يقوم مقتطف الكود هذا بتهيئة المتجه v بأربعة أعداد صحيحة {1، 2، 3، 4}، ثم يحاول تهيئة المتجه v2 الآخر كنسخة من v.
سيكون الناتج 1، لأنه يطبع العنصر الأول من v2، وهو نسخة من v. لذلك، سيطبع العنصر الأول من v، وهو 1.
مثال 3:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(5, 100); // Initialize vector v with 5 elements, each set to the value 100 vector<int> v2(6, 200); // Initialize vector v2 with 6 elements, each set to the value 200 cout << v2[0] << endl; // Output the first element of v2 return 0; }
في هذا البرنامج:
- vector v(5, 100); يقوم بإنشاء متجه v مع 5 عناصر، كل منها تمت تهيئته بالقيمة 100. يقوم هذا المُنشئ بتهيئة v بخمسة عناصر، كل منها له القيمة 100.
- vector v2(6, 200); ينشئ متجه v2 مع 6 عناصر، كل منها تمت تهيئته بالقيمة 200. يقوم هذا المنشئ بتهيئة v2 بـ 6 عناصر، كل منها له القيمة 200.
- cout << v2[0] << endl; يطبع العنصر الأول من v2، وهو 200، متبوعًا بحرف السطر الجديد.
وبالتالي فإن مخرجات هذا الكود ستكون:
200
مثال 4:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(5, 100); // Initialize vector v with 5 elements, each set to the value 100 vector<int> v2(6, 200); // Initialize vector v2 with 6 elements, each set to the value 200 v.swap(v2); // Swap the contents of v and v2 cout << v2[0] << endl; // Output the first element of v2 return 0; }
في هذا البرنامج:
- vector v(5, 100); ينشئ متجهًا v مكونًا من 5 عناصر، تمت تهيئ كل منها إلى القيمة 100.
- vector v2(6, 200); ينشئ متجهًا v2 مكونًا من 6 عناصر، تمت تهيئ كل منها إلى القيمة 200.
- v.swap(v2); يتبادل محتويات v وv2، ويتبادل بشكل فعال عناصر وأحجام المتجهين.
- cout << v2[0] << endl; يحاول طباعة العنصر الأول من v2.
ومع ذلك، بعد عملية المبادلة، يحتوي v2 الآن على العناصر الموجودة في الأصل في v، ويحتوي v على العناصر الموجودة في الأصل في v2. لذلك، فإن محاولة الوصول إلى v2[0] ستؤدي فعليًا إلى طباعة العنصر الأول من المتجه الذي كان في البداية v.
وبالتالي فإن مخرجات هذا الكود ستكون:
100
مثال 5:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v; // Create an empty vector v.push_back(10); // Add 10 to the end of the vector v.push_back(20); // Add 20 to the end of the vector v.push_back(30); // Add 30 to the end of the vector cout << v.front() << endl; // Output the first element of the vector cout << v.back() << endl; // Output the last element of the vector cout << v.at(0) << endl; // Output the element at index 0 of the vector return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v;: يعلن عن متجه فارغ للأعداد الصحيحة.
- v.push_back(10);: إلحاق القيمة 10 بنهاية المتجه v.
- v.push_back(20);: إلحاق القيمة 20 بنهاية المتجه v.
- v.push_back(30);: إلحاق القيمة 30 بنهاية المتجه v.
- cout << v.front() << endl;: يُخرج العنصر الأول من المتجه v باستخدام دالة العضو front(). في هذه الحالة، فإنه يطبع 10.
- cout << v.back() << endl;: يُخرج العنصر الأخير من المتجه v باستخدام دالة العضو back(). في هذه الحالة، فإنه يطبع 30.
- cout << v.at(0) << endl;: إخراج العنصر عند الـ index 0 للمتجه v باستخدام دالة العضو at(). في هذه الحالة، فإنه يطبع 10.
وبالتالي فإن مخرجات هذا الكود ستكون:
10 30 10
مثال 6:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(100); // Create a vector with 100 elements, all initialized to 0 v.push_back(5); // Append the value 5 to the end of the vector cout << "Size = " << v.size() << endl; // Output the current size of the vector cout << "Capacity = " << v.capacity() << endl; // Output the current capacity of the vector return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v(100);: إنشاء متجه v بحجم أولي مكون من 100 عنصر، تمت تهيئتها جميعًا إلى القيمة الافتراضية لـ int، وهي 0.
- v.push_back(5);: إلحاق القيمة 5 بنهاية المتجه. نظرًا لأنه تمت تهيئة المتجه بحجم 100، ولكن تمت إضافة عنصر واحد فقط، فسيقوم المتجه بتغيير حجم نفسه حسب الحاجة لاستيعاب العنصر الجديد.
- cout << “Size = ” << v.size() << endl;: إخراج الحجم الحالي للمتجه باستخدام دالة العضو size(). في هذه الحالة، يتم طباعة 101، حيث يوجد الآن 101 عنصر في المتجه.
- cout << “Capacity = ” << v.capacity() << endl;: إخراج السعة الحالية للمتجه باستخدام دالة عضو السعة (). تمثل السعة الحد الأقصى لعدد العناصر التي يمكن أن يحتفظ بها المتجه دون إعادة تخصيص الذاكرة. في هذه الحالة، يعتمد ذلك على التنفيذ، ولكن من المحتمل أن يكون أكبر من أو يساوي 101، حيث قد يخصص المتجه ذاكرة إضافية لتجنب عمليات إعادة التخصيص المتكررة عند إضافة المزيد من العناصر.
مثال 7:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {1, 2, 3, 4, 5}; for (size_t i = 0; i < v.size(); i++) { cout << v[i] << " "; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {1, 2, 3, 4, 5};: تهيئة المتجه v بخمسة عناصر، يحتوي كل منها على القيم 1، 2، 3، 4، و5، على التوالي.
- for (size_t i = 0; i < v.size(); i++) { … }: هذه حلقة for تقليدية تتكرر فوق كل عنصر من عناصر المتجه. يبدأ بفهرس i يساوي 0 ويستمر حتى يصبح i أقل من حجم المتجه (v.size()).
- size_t هو نوع عدد صحيح غير موقّع يستخدم لتمثيل أحجام الكائنات. يُستخدم بشكل شائع للمؤشرات وأحجام الحاويات مثل المتجهات.
- cout << v[i] << ” “;: داخل الحلقة، يتم طباعة قيمة العنصر عند index i للمتجه، متبوعًا بمسافة.
وبالتالي فإن مخرجات هذا الكود ستكون:
1 2 3 4 5
يقوم بطباعة كل عنصر من عناصر المتجه v مفصولاً بمسافة.
Deque
الخصائص:
- إدراج سريع/إزالة سريعة في البداية والنهاية
- إدراج سريع/إزالة بطيئة في المنتصف
- بحث بطيء
List
الخصائص:
- سرعة إدخال وإزالة العناصر من أي مكان في الـcontainer
- الوصول العشوائي السريع غير مدعوم. إنه بطيء في الوصول إلى العناصر من المنتصف
- بحث بطيء
- قائمة مرتبطة بشكل مزدوج
Forward List
الخصائص:
- إدراج وإزالة سريعين للعناصر من أي مكان في الـcontainer.
- الوصول العشوائي السريع غير مدعوم
- قائمة مرتبطة بشكل منفرد
المتجه (Vector) 2
التكرارات Iterators
في لغة C++، التكرارات عبارة عن كائنات توفر طريقة لاجتياز عناصر الحاوية (مثل المتجهات) بطريقة تسلسلية. تعمل التكرارات كمؤشرات للعناصر الموجودة في الحاوية، مما يسمح لك بالوصول إلى العناصر ومعالجتها بكفاءة. إنها بمثابة جسر بين الخوارزميات وهياكل البيانات، مما يتيح لك تنفيذ عمليات متنوعة على عناصر الحاوية دون الحاجة إلى معرفة تفاصيل التنفيذ الأساسية.
بالنسبة للمتجهات على وجه التحديد، توفر التكرارات العديد من الوظائف:
- الوصول إلى العناصر: يمكنك استخدام التكرارات للوصول إلى العناصر الفردية للمتجه. تدعم المُكرِّرات إلغاء المرجعية dereferencing، مما يسمح لك باسترداد قيمة العنصر الذي تشير إليه.
- الاجتياز: توفر التكرارات آليات للعبور عبر عناصر المتجه بشكل تسلسلي. يمكنك زيادة أو إنقاص المكرر للانتقال إلى العنصر التالي أو السابق، على التوالي.
- العمليات المستندة إلى النطاق: يمكن للتكرارات تحديد النطاقات داخل المتجه، مما يسمح لك بتنفيذ عمليات على مجموعات فرعية من العناصر. على سبيل المثال، يمكنك تحديد نطاق من التكرارات للإشارة إلى مجموعة فرعية من العناصر لفرزها أو نسخها إلى حاوية أخرى.
- فئات التكرار: يتم تصنيف التكرارات في لغة C++ إلى فئات مختلفة بناءً على قدراتهم وسلوكهم. بالنسبة للمتجهات، فئة التكرار الأكثر استخدامًا هي مكرر الوصول العشوائي، الذي يدعم الوصول العشوائي الفعال إلى العناصر باستخدام العمليات الحسابية مثل الجمع والطرح.
- إبطال المكرر: يمكن أن يؤدي تعديل المتجه (على سبيل المثال، إدراج عناصر أو مسحها) إلى إبطال المكررات التي تشير إلى عناصر داخل المتجه. من الضروري أن تكون على دراية بقواعد إبطال المكرر لتجنب السلوك غير المحدد عند العمل مع المكررات.
مثال 1:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; vector<int>::iterator it = v.begin(); cout << *it; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {10, 20, 30, 40};: تهيئة المتجه v بأربعة أعداد صحيحة {10, 20, 30, 40}.
- Vector::iterator it = v.begin();: يعلن عن مكرر من النوع Vector::iterator ويقوم بتهيئته للإشارة إلى بداية المتجه v باستخدام دالة العضو begin().
- cout << *it;: يقوم بإلغاء الإشارة إلى المكرر للوصول إلى القيمة التي يشير إليها (العنصر الأول في المتجه) وطباعتها باستخدام cout.
يطبع قيمة العنصر الأول من المتجه v، وهو 10.
مثال 2:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; vector<int>::iterator it = v.end()-1; cout << *it; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector::iterator it = v.end() – 1;: يعلن عن مكرر من النوع Vector::iterator ويقوم بتهيئته للإشارة إلى العنصر الأخير في المتجه v عن طريق طرح 1 من النتيجة من دالة العضو end(). لاحظ أن end() تُرجع مكررًا يشير إلى الموضع بعد العنصر الأخير، لذا فإن طرح 1 يوجهه إلى العنصر الأخير.
- cout << *it;: يقوم بإلغاء الإشارة إلى المكرر للوصول إلى القيمة التي يشير إليها (العنصر الأخير في المتجه) وطباعته باستخدام cout.
يطبع قيمة العنصر الأخير من المتجه v، وهو 40.
مثال 3:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::iterator it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر لكل عنصر من عناصر المتجه باستخدام مكرر it. يبدأ بالمكرر الذي يشير إلى بداية المتجه (v.begin()) ويستمر حتى يساوي المكرر v.end()، والذي يمثل موضعًا واحدًا بعد نهاية المتجه. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
10 20 30 40
مثال 4:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::reverse_iterator it = v.rbegin(); it != v.rend(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::reverse_iterator it = v.rbegin(); it != v.rend(); it++) { … }: هذه حلقة for تتكرر على العناصر المتجهة بترتيب عكسي باستخدام التكرارات العكسية. يبدأ بالمكرر العكسي الذي يشير إلى العنصر الأخير في المتجه (v.rbegin()) ويستمر حتى يصل المكرر العكسي إلى الموضع قبل العنصر الأول (v.rend())، حصريًا. في كل تكرار، تتم زيادة المكرر العكسي للإشارة إلى العنصر السابق (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر العكسي للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
40 30 20 10
مثال 5:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::const_reverse_iterator it = v.crbegin(); it != v.crend(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::const_reverse_iterator it = v.crbegin(); it != v.crend(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة بترتيب عكسي باستخدام مكررات const العكسية . يبدأ بالمكرر العكسي const الذي يشير إلى العنصر الأخير في المتجه (v.crbegin()) ويستمر حتى يصل المكرر العكسي const إلى الموضع قبل العنصر الأول (v.crend()). في كل تكرار، تتم زيادة المكرر العكسي const للإشارة إلى العنصر السابق (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء الإشارة إلى المكرر العكسي const للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
يقوم بطباعة كل عنصر من عناصر المتجه v بترتيب عكسي، بدءًا من 40 وانتهاءً بالرقم 10.
مثال 6:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {10, 20, 30, 40}; for (vector<int>::const_iterator it = v.cbegin(); it != v.cend(); it++) { cout << *it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- for (vector::const_iterator it = v.cbegin(); it != v.cend(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة باستخدام مكررات const. يبدأ بمكرر const الذي يشير إلى العنصر الأول في المتجه (v.cbegin()) ويستمر حتى يصل مكرر const إلى الموضع بعد العنصر الأخير (v.cend()). في كل تكرار، تتم زيادة مكرر const للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية مكرر const للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
يقوم بطباعة كل عنصر من عناصر المتجه v على سطر جديد، ويتكرر خلال المتجه بأكمله.
Auto
في لغة C++، يتم استخدام الكلمة المفتاحية auto لاستدلال النوع، مما يسمح للمترجم باستنتاج نوع المتغير تلقائيًا بناءً على مُهيئه. عند استخدامه مع المتجهات، يمكن لـ auto تبسيط التعليمات البرمجية عن طريق تحديد النوع الصحيح من التكرارات أو العناصر تلقائيًا دون تحديدها بشكل صريح.
يمكن أن يؤدي استخدام auto مع المتجهات إلى جعل التعليمات البرمجية أكثر إيجازًا وقابلية للقراءة، خاصة عندما يكون المكرر أو نوع العنصر معقدًا أو عندما يتم تغييره بشكل متكرر. ومع ذلك، من الضروري استخدام auto بحكمة والتأكد من أن النوع المستنتج واضح ولا لبس فيه.
مثال 7:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v = {5, 10, 15, 20}; // Initialize vector v with four integers auto it = v.begin(); // Use auto to deduce the type of the iterator // Iterate over the vector elements using the deduced iterator type for (; it != v.end(); it++) { cout << *it << endl; // Output the value pointed to by the iterator, followed by a newline } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {5, 10, 15, 20};: تهيئة المتجه v بأربعة أعداد صحيحة {5, 10, 15, 20}.
- auto it = v.begin();: يستخدم auto لاستنتاج نوع المكرر. في هذه الحالة، يستنتج أنه من النوع Vector::iterator، يشير إلى بداية المتجه v.
- for (; it != v.end(); it++) { … }: هذه حلقة for تتكرر على العناصر المتجهة باستخدام نوع المكرر المستنتج. يبدأ بالمكرر الذي يشير إلى بداية المتجه (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
5 10 15 20
مثال 8:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {5, 10, 15, 20}; v.insert(v.begin()+1, 12); v.insert(v.end()-1, 17); for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- v.insert(v.begin() + 1, 12);: يُدرج القيمة 12 في المتجه v في الموضع بعد العنصر الأول (index 1).
- v.insert(v.end() – 1, 17);: يُدرج القيمة 17 في المتجه v في الموضع قبل العنصر الأخير. نظرًا لأن v.end() يشير إلى العنصر الأخير، فإن طرح 1 يضع نقطة الإدراج قبل العنصر الأخير.
- for (auto it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة باستخدام auto لاستدلال النوع. يبدأ بالمكرر الذي يشير إلى بداية المتجه (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
5 12 10 15 17 20
مثال 9:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {5, 10, 15, 20}; v.erase(v.begin()); for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- v.erase(v.begin());: إزالة العنصر الأول من المتجه v باستخدام طريقة المسح (). بما أن v.begin() يشير إلى العنصر الأول، فإن هذه العملية تزيل هذا العنصر من المتجه.
- for (auto it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر عبر العناصر المتجهة باستخدام auto لاستدلال النوع. يبدأ بالمكرر الذي يشير إلى بداية المتجه المعدل (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
10 15 20
مثال 10:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v = {5, 10, 15, 20}; v.erase(v.begin()+1, v.end()); for (auto it = v.begin(); it != v.end(); it++) cout << *it << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- v.erase(v.begin() + 1, v.end());: إزالة العناصر من العنصر الثاني (index 1) حتى نهاية المتجه باستخدام طريقة المسح () مع التكرارات التي تحدد النطاق. يتم تحديد النطاق من v.begin() + 1 (المكرر الذي يشير إلى العنصر الثاني) إلى v.end() (المكرر الذي يشير إلى العنصر الأخير).
- for (auto it = v.begin(); it != v.end(); it++) { … }: هذه حلقة for تتكرر على العناصر المتجهة المتبقية باستخدام auto لاستدلال النوع. يبدأ بالمكرر الذي يشير إلى بداية المتجه المعدل (v.begin()) ويستمر حتى يصل المكرر إلى الموضع بعد العنصر الأخير المتبقي (v.end())، حصريًا. في كل تكرار، تتم زيادة المكرر للإشارة إلى العنصر التالي (it++).
- cout << *it << endl;: داخل الحلقة، يقوم بإلغاء مرجعية المكرر للوصول إلى قيمة العنصر المتبقي الذي يشير إليه ويطبعه باستخدام cout، متبوعًا بسطر جديد (endl).
وبالتالي فإن مخرجات هذا الكود ستكون:
5
مثال 11:
#include <iostream> #include <vector> using namespace std; int main() { vector<int>v(10); for (int i = 0; i <10; i++) v[i] = i; cout << "Vector size initially: " << v.size(); cout << "\nVector elemens are: "; for (int i = 0; i <10; i++) cout << v[i] << " "; v.resize(5); cout << "\n\nVector size after resize(5): " << v.size(); v.shrink_to_fit(); return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v(10);: تهيئة المتجه v بالحجم 10، والذي يحتوي على عناصر تمت تهيئتها افتراضيًا (تتم تهيئة جميع العناصر إلى الصفر في هذه الحالة).
- يعين القيم لعناصر المتجه باستخدام حلقة.
- يُخرج الحجم الأولي للمتجه باستخدام v.size().
- إخراج عناصر المتجه باستخدام حلقة.
- يتم تغيير حجم المتجه إلى الحجم 5 باستخدام v.resize(5). تعمل هذه العملية على تقليل حجم المتجه إلى 5، وتتم إزالة أي عناصر زائدة من المتجه.
- إخراج حجم المتجه بعد تغيير الحجم.
- يستدعي v.shrink_to_fit() لتقليل قدرة المتجه ليتناسب مع حجمه.
ملحوظة:Shrink_to_fit() هو طلب غير ملزم لتقليل سعة المتجه ليناسب حجمه ولكنه لا يضمن أن السعة سيتم تقليلها فعليًا. قد يختار التنفيذ تجاهل الطلب.
المتجه (Vector) 3
مثال 1:
#include <iostream> #include <vector> using namespace std; int main() { vector<int> v(5); // Initialize vector v with size 5 int n = 0; // Prompt the user to enter vector elements cout << "Enter Vector Elements:\n"; for (size_t i = 0; i < v.size(); i++) { cin >> v[i]; // Read input into vector element at index i // Check if we're at the end of the vector if (i == v.size() - 1) { cout << "If you want to resize the list, enter the new size. Enter -1 to finish: "; cin >> n; if (n == -1) break; else v.resize(n); // Resize the vector to the new size entered by the user } } // Output the elements of the vector cout << "Vector elements are:\n"; for (size_t i = 0; i < v.size(); i++) { cout << v[i] << endl; // Output each element followed by a newline } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v(5);: تهيئة المتجه v بالحجم 5، والذي يحتوي على عناصر تمت تهيئتها افتراضيًا (تتم تهيئة جميع العناصر إلى الصفر في هذه الحالة).
- يطالب المستخدم بإدخال عناصر المتجهات.
- يقرأ المدخلات من المستخدم إلى العناصر المتجهة باستخدام حلقة. بعد قراءة العنصر الأخير، يطلب الكود من المستخدم إما تغيير حجم المتجه أو الخروج من الحلقة عن طريق إدخال -1.
- إذا أدخل المستخدم حجمًا جديدًا، فسيتم تغيير حجم المتجه وفقًا لذلك باستخدام v.resize(n).
- إخراج عناصر المتجه بعد انتهاء الحلقة.
يسمح هذا الرمز للمستخدم بتغيير حجم المتجه ديناميكيًا أثناء الإدخال ثم يعرض عناصر المتجه.
مثال 2:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v = {5, 1, 2, 7, 0, 3}; // Initialize vector v with some unsorted integers sort(v.begin(), v.end()); // Sort the elements of the vector in ascending order // Output the sorted elements of the vector for (auto it : v) { cout << it << endl; // Output each sorted element followed by a newline } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {5, 1, 2, 7, 0, 3};: تهيئة المتجه v بستة أعداد صحيحة بترتيب غير مصنف.
- Sort(v.begin(), v.end());: يفرز عناصر المتجه v بترتيب تصاعدي باستخدام خوارزمية std::sort. تتطلب وظيفة الفرز مكررين يحددان النطاق المراد فرزه، وهما v.begin() (يشير إلى العنصر الأول) و v.end() (يشير إلى عنصر واحد بعد العنصر الأخير).
- for (auto it : v) { … }: هذه حلقة for تعتمد على النطاق وتتكرر عبر المتجه المصنف v. وتتكرر عبر كل عنصر من عناصر المتجه v بترتيب تصاعدي.
- cout << it << endl;: داخل الحلقة، يتم طباعة كل عنصر من عناصر المتجه متبوعًا بخط جديد باستخدام cout.
وبالتالي فإن مخرجات هذا الكود ستكون:
0 1 2 3 5 7
يقوم بطباعة كل عنصر من عناصر المتجه المصنف v بترتيب تصاعدي، عنصر واحد في كل سطر.
مثال 3:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int>v = {5, 1, 2, 7, 0, 3}; sort(v.rbegin(), v.rend()); for (auto it:v) { cout << it << endl; } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Sort(v.rbegin(), v.rend());: يفرز عناصر المتجه v بترتيب تنازلي باستخدام خوارزمية std::sort مع التكرارات العكسية. تتطلب دالة الفرز مكررين يحددان النطاق المراد فرزه، وهما v.rbegin() (يشير إلى العنصر الأخير، ويعامل على أنه الأول للفرز) وv.rend() (يشير إلى عنصر قبل العنصر الأول، ويعامل كآخر فرز).
إذن مخرجات هذا الكود ستكون:
7 5 3 2 1 0
يقوم بطباعة كل عنصر من عناصر المتجه المصنف v بترتيب تنازلي، عنصر واحد في كل سطر.
مثال 4:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int>v = {5, 1, 2, 7, 0, 3}; reverse(v.begin(), v.end()); for (auto it:v) { cout << it << endl; } return 0; }
- Reverse(v.begin(), v.end());: عكس عناصر المتجه v باستخدام الخوارزمية std::reverse. تتطلب الدالة العكسية مُكرِّرين يحددان النطاق المراد عكسه، وهما v.begin() (يشير إلى العنصر الأول) وv.end() (يشير إلى عنصر واحد بعد العنصر الأخير).
وبالتالي فإن مخرجات هذا الكود ستكون:
3 0 7 2 1 5
يقوم بطباعة كل عنصر من عناصر المتجه المعكوس v، عنصر واحد في كل سطر.
مثال 5:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v = {5, 1, 2, 7, 0, 3}; // Initialize vector v with some integers // Find and output the minimum element in the vector cout << *min_element(v.begin(), v.end()) << endl; // Find and output the maximum element in the vector cout << *max_element(v.begin(), v.end()) << endl; // Find and output the minimum element in the vector excluding the last two elements cout << *min_element(v.begin(), v.end() - 2) << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector v = {5, 1, 2, 7, 0, 3};: تهيئة المتجه v بستة أعداد صحيحة بالترتيب المحدد.
- cout << *min_element(v.begin(), v.end()) << endl;: ابحث عن الحد الأدنى للعنصر في المتجه v باستخدام خوارزمية std::min_element. تقوم الدالة min_element بإرجاع مكرر يشير إلى الحد الأدنى للعنصر، و* تلغي الإشارة إلى هذا المكرر للحصول على قيمة الحد الأدنى للعنصر. ثم تتم طباعة الحد الأدنى للعنصر متبوعًا بسطر جديد.
- cout << *max_element(v.begin(), v.end()) << endl;: كما هو مذكور أعلاه، يبحث هذا السطر عن الحد الأقصى للعنصر في المتجه v ويطبعه.
- cout << *min_element(v.begin(), v.end() – 2) << endl;: يبحث عن الحد الأدنى للعنصر في المتجه v باستثناء العنصرين الأخيرين. النطاق المستخدم للعثور على الحد الأدنى للعنصر هو من v.begin() إلى v.end() – 2 (باستثناء العنصرين الأخيرين). ثم تتم طباعة الحد الأدنى للعنصر متبوعًا بسطر جديد.
وبالتالي فإن مخرجات هذا الكود ستكون:
0 7 1
يقوم بطباعة الحد الأدنى للعنصر والحد الأقصى للعنصر والحد الأدنى للعنصر باستثناء العنصرين الأخيرين من المتجه v، متبوعًا كل منهما بسطر جديد.
مثال 6:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int>v = {5, 1, 2, 7, 0, 3}; auto pair = minmax_element(v.begin(), v.end()); cout << *pair.first << endl; cout << *pair.second << endl; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- auto Pair = minmax_element(v.begin(), v.end());: يبحث عن الحد الأدنى والحد الأقصى للعناصر في المتجه v باستخدام خوارزمية std::minmax_element. تقوم هذه الدالة بإرجاع زوج من التكرارات، حيث يشير المكرر الأول إلى الحد الأدنى للعنصر ويشير المكرر الثاني إلى الحد الأقصى للعنصر.
- cout << *pair.first << endl;: يُخرج قيمة الحد الأدنى للعنصر عن طريق إلغاء مرجعية pair.first.
- cout << *pair.sec << endl;: إخراج قيمة الحد الأقصى للعنصر عن طريق إلغاء pair.second.
وبالتالي فإن مخرجات هذا الكود ستكون:
0 7
يقوم بطباعة الحد الأدنى والحد الأقصى من عناصر المتجه v، متبوعًا بكل منها بخط جديد.
مثال 7:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v = {5, 1, 2, 7, 0, 3}; // Initialize vector v with some integers // Find the minimum element in the vector auto it = min_element(v.begin(), v.end()); // Sort the elements of the vector up to (but not including) the minimum element sort(v.begin(), it); // Output the sorted elements of the vector for (auto i : v) { cout << i << endl; // Output each sorted element followed by a newline } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- auto it = min_element(v.begin(), v.end());: ابحث عن الحد الأدنى للعنصر في المتجه v باستخدام خوارزمية std::min_element. تقوم هذه الدالة بإرجاع مكرر يشير إلى الحد الأدنى من العناصر.
- Sort(v.begin(), it);: يفرز عناصر المتجه v حتى (ولكن لا يشمل) الحد الأدنى للعنصر الذي تم العثور عليه بواسطة min_element. يستخدم خوارزمية std::sort مع نطاق محدد بواسطة التكرارات، من v.begin() إليها. يؤدي هذا إلى فرز العناصر قبل العنصر الأدنى.
- for (auto i : v) { … }: هذه حلقة for تعتمد على النطاق وتتكرر عبر المتجه المصنف v. وتتكرر عبر كل عنصر من عناصر المتجه v بترتيب مفروز.
cout << i << endl;: داخل الحلقة، يتم طباعة كل عنصر مفروز i من المتجه متبوعًا بخط جديد باستخدام cout.
وبالتالي فإن مخرجات هذا الكود ستكون:
0 1 2 5 7
يقوم بطباعة كل عنصر من عناصر المتجه v حتى الحد الأدنى للعنصر، ويتم فرزه بترتيب تصاعدي، ويتبع كل منها سطر جديد.
مثال 8:
#include <iostream> #include <vector> #include <algorithm> using namespace std; bool GreaterThanThree(int i) { return i > 3; } int main() { vector<int> v = {5, 1, 2, 7, 0, 3}; // Initialize vector v with some integers // Sort the elements of the vector in ascending order sort(v.begin(), v.end()); // Find the first element greater than 3 using find_if with GreaterThanThree as the predicate auto it = find_if(v.begin(), v.end(), GreaterThanThree); // Output all elements greater than 3 for (; it != v.end(); it++) { cout << *it << endl; // Output each element greater than 3 followed by a newline } return 0; }
إليك ما يفعله كل سطر من البرنامج:
- bool GreaterThanThree(int i): يحدد دالة أصلية GreaterThanThree تُرجع صحيحًا إذا كان عدد الإدخال الصحيح i أكبر من 3.
- Vector v = {5, 1, 2, 7, 0, 3};: تهيئة المتجه v بستة أعداد صحيحة.
- Sort(v.begin(), v.end());: يفرز عناصر المتجه v بترتيب تصاعدي باستخدام خوارزمية std::sort.
- auto it = find_if(v.begin(), v.end(), GreaterThanThree);: يبحث عن العنصر الأول الأكبر من 3 في المتجه المصنف v باستخدام خوارزمية std::find_if مع الدالة الأصلية GreaterThanThree.
- for (; it != v.end(); it++) { … }: هذه حلقة for تتكرر على العناصر من العنصر الأول الأكبر من 3 حتى نهاية المتجه. يقوم بإخراج كل عنصر أكبر من 3 متبوعًا بسطر جديد باستخدام cout.
وبالتالي فإن مخرجات هذا الكود ستكون:
5 7
يقوم بطباعة كل عنصر من عناصر المتجه v أكبر من 3، متبوعًا بكل عنصر بسطر جديد.
مثال 9:
#include <iostream> #include <vector> #include <algorithm> using namespace std; bool GreaterThanThree(int i) { return i > 3; } int main() { int arr[] = {10, 20, 30, 40, 50, 60, 70}; vector<int> v(7); copy(arr, arr + 7, v.begin()); cout << "myvector contains: "; for (auto it:v) cout << it << " "; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- int arr[] = {10, 20, 30, 40, 50, 60, 70};: تهيئة مصفوفة على النمط C تحتوي على سبعة أعداد صحيحة.
- Vector v(7);: تهيئة المتجه v بالحجم 7.
- Copy(arr, arr + 7, v.begin());: نسخ العناصر من مصفوفة النمط C إلى المتجه v باستخدام خوارزمية std::copy. تتطلب وظيفة النسخ ثلاث معلمات: مكررات البداية والنهاية للنطاق المصدر (هنا، arr و arr + 7 يمثلان المصفوفة بأكملها)، ومكررات البداية للنطاق الوجهة (هنا، v.begin()).
- cout << "myvector contains: "; يحتوي على رسالة تشير إلى بداية الإخراج.
- for (auto it : v) cout << it << ” “;: هذه حلقة for تعتمد على النطاق وتتكرر لكل عناصر المتجه v. وتقوم بإخراج كل عنصر من عناصر المتجه متبوعًا بمسافة.
وبالتالي فإن مخرجات هذا الكود ستكون:
myvector contains: 10 20 30 40 50 60 70
مثال 10:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> from_vector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; vector<int> to_vector(15); copy_backward(from_vector.begin(), from_vector.end(), to_vector.end()); cout << "to_vector contains: "; for (auto i: to_vector) cout << i << " "; return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Vector from_vector = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};: تهيئة ناقل المصدر from_vector بعشرة أعداد صحيحة.
- Vector to_vector(15);: تهيئة المتجه المستهدف to_vector بالحجم 15.
- Copy_backward(from_vector.begin(), from_vector.end(), to_vector.end());: نسخ العناصر من المتجه المصدر from_vector إلى المتجه الهدف to_vector بترتيب عكسي باستخدام خوارزمية std::copy_backward. تتطلب وظيفة Copy_backward ثلاث معلمات: مكررات البداية والنهاية للنطاق المصدر (هنا، from_vector.begin() وfrom_vector.end() يمثلان المتجه بالكامل)، ومكرر النهاية لنطاق الوجهة (هنا، to_vector.end ()).
- cout << “to_vector contains: “;:يحتوي على رسالة تشير إلى بداية الإخراج.
- for (auto i : to_vector) cout << i << ” “;: هذه حلقة for تعتمد على النطاق وتتكرر عبر عناصر المتجه المستهدف to_vector. يقوم بإخراج كل عنصر من عناصر المتجه المستهدف متبوعًا بمسافة.
وبالتالي فإن مخرجات هذا الكود ستكون:
to_vector contains: 0 0 0 0 0 1 2 3 4 5 6 7 8 9 10
يقوم بطباعة كل عنصر من عناصر المتجه الهدف to_vector، والذي يحتوي على عناصر المتجه المصدر from_vector المنسوخة بترتيب عكسي، متبوعة بمسافة. يتم ترك العناصر الأولية لـ to_vector التي لم تتم الكتابة فوقها بواسطة عملية النسخ دون تغيير (تتم تهيئتها افتراضيًا إلى 0 في هذه الحالة).
مثال 11:
#include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; int main() { vector<int> v = {1, 2, 3, 4, 5}; // Initialize a vector v with five integers // Copy elements from the vector v to the standard output (cout) copy(v.begin(), v.end(), ostream_iterator<int>(cout, " ")); return 0; }
إليك ما يفعله كل سطر من البرنامج:
- Copy(v.begin(), v.end(), ostream_iterator(cout, ” "));: نسخ العناصر من المتجه v إلى الإخراج القياسي (cout) باستخدام خوارزمية std::copy وstd: :ostream_iterator. تتطلب وظيفة النسخ ثلاث معلمات: مكررات البداية والنهاية للنطاق المصدر (هنا، v.begin() وv.end() يمثلان المتجه بأكمله)، ومكرر الإخراج (هنا، ostream_iterator(cout, ” “)) الذي يكتب كل عنصر منسوخ إلى الإخراج القياسي باستخدام فاصل مسافة.
وبالتالي فإن مخرجات هذا الكود ستكون:
1 2 3 4 5
خصائص المتجه
- Add -> Back -> O(1):
يشير هذا الخط إلى أن إضافة عنصر إلى الجزء الخلفي (النهاية) للمتجه له تعقيد زمني قدره O(1)، مما يعني أنها عملية في وقت ثابت. تمتلك المتجهات تخصيصًا ديناميكيًا للذاكرة، وعادةً ما تتضمن إضافة عنصر إلى الخلف إلحاق العنصر بنهاية كتلة الذاكرة المتجاورة الأساسية، وهو ما يمكن القيام به في وقت ثابت. - Delete -> Back -> O(1):
حذف عنصر من الجزء الخلفي (النهاية) للمتجه له أيضًا تعقيد زمني قدره O(1)، مما يعني أنها عملية في وقت ثابت. عادةً ما تتضمن إزالة العنصر الأخير تقليل حجم المتجه، ولا يلزم إعادة تخصيص أو تغيير العناصر. - Add -> Any Where -> O(N):
إن إضافة عنصر في أي مكان باستثناء الجزء الخلفي من المتجه، كما هو الحال في المقدمة أو في المنتصف، له تعقيد زمني قدره O(N). وذلك لأن إدراج عنصر في موضع عشوائي قد يتطلب نقل جميع العناصر اللاحقة لإفساح المجال للعنصر الجديد، الأمر الذي يستغرق وقتًا خطيًا يتناسب مع عدد العناصر المُزاحة. - Delete -> Any Where -> O(N):
وبالمثل، فإن حذف عنصر من أي مكان باستثناء الجزء الخلفي من المتجه له أيضًا تعقيد زمني قدره O(N). قد تتطلب إزالة عنصر من موضع عشوائي نقل جميع العناصر اللاحقة لملء الفجوة التي خلفها العنصر المحذوف، الأمر الذي يستغرق أيضًا وقتًا خطيًا يتناسب مع عدد العناصر المزاحة. - Access -> [] – at() -> O(1):
الوصول إلى عنصر في متجه بواسطة الفهرس باستخدام عامل الأقواس المربعة ([]) أو وظيفة العضو at() له تعقيد زمني قدره O(1). وهذا يعني أن الوصول إلى أي عنصر في المتجه يستغرق وقتًا ثابتًا بغض النظر عن حجم المتجه. توفر المتجهات وصولاً عشوائيًا في الوقت الثابت لأنها تخزن العناصر في كتلة ذاكرة متجاورة، مما يسمح بالوصول المباشر إلى أي عنصر من خلال فهرسه. - Search -> find() -> O(log N):
البحث عن عنصر في متجه مفروز باستخدام خوارزمية std::find() له تعقيد زمني قدره O(log N) عندما يتم فرز المتجه. وذلك لأن std::find() يقوم بإجراء بحث ثنائي على المتجه الذي تم فرزه، والذي يحتوي على تعقيد زمني لوغاريتمي. ومع ذلك، من المهم ملاحظة أنه إذا لم يتم فرز المتجه، فإن التعقيد الزمني للعثور على عنصر باستخدام std::find() سيكون O(N)، لأنه يقوم بإجراء بحث خطي.
الإيجابيات:
يتم تنفيذها كمصفوفات ديناميكية
السلبيات:
- إعادة توزيع مكلفة
- يتطلب الذاكرة المتجاورة
هياكل البيانات
يعمل هيكل البيانات بمثابة مستودع مصمم لتخزين البيانات وتنظيمها بشكل منظم، مما يتيح الوصول الفعال والتحديثات عند استخدامها على جهاز كمبيوتر.
التعقيد الزمني والمكاني
مقدمة:
يعد الـcomplexity في هياكل البيانات مفهومًا أساسيًا في علوم الكمبيوتر يساعدنا في تحليل أداء وكفاءة الخوارزميات. فهو يسمح لنا بقياس الموارد (مثل الوقت والذاكرة) التي تتطلبها الخوارزمية لحل مشكلة ما مع نمو حجم الإدخال. في هذه المقالة، سنستكشف أساسيات التعقيد في هياكل البيانات، بما في ذلك التعقيد الزمني والتعقيد المكاني، وكيفية تأثيرها على تصميم الخوارزمية وتحليلها.
في خوارزميات البحث، يتطلب تحليل الكفاءة النظر في سيناريوهات محتملة مختلفة. وهذا يساعدنا على فهم كيفية أداء الخوارزمية في ظل ظروف مختلفة. سنستكشف هنا الحالة الأساسية والحالة المتوسطة والحالة الأسوأ بالإضافة إلى تدوين الـcomplexity الخاص بها:
- الحالة الأساسية (Omega notation):
يشير إلى أبسط إدخال حيث تنتهي الخوارزمية بأقل عدد من الخطوات.
ملاحظة: يختلف باختلاف الخوارزمية. غالبًا ما يُشار إليه بالرمز O(1) الذي يشير إلى وقت ثابت، بغض النظر عن حجم الإدخال. - الحالة المتوسطة (Theta notation):
يمثل الأداء المتوقع في المتوسط عند النظر في جميع المدخلات الممكنة باحتمالات متساوية.
ملاحظة: يعتمد على تصميم الخوارزمية وتوزيع البيانات. على سبيل المثال، يحتوي البحث الخطي على حالة متوسطة لـ O(n/2)، مما يعني أنه يستغرق نصف المقارنات، في المتوسط، للعثور على عنصر في القائمة. - الحالة الأسوأ (Big O notation):
يمثل السيناريو الأكثر تحديًا، حيث يتطلب الحد الأقصى لعدد الخطوات.
ملاحظة: مرة أخرى، يعتمد على الخوارزمية. أسوأ حالة للبحث الخطي هي O(n)، مما يدل على أنه قد يحتاج إلى مقارنة جميع العناصر إذا لم يكن الهدف موجودًا أو في النهاية.
- ملاحظة أخرى:
– Complexity notation uses symbols like O, Ω, and Θ to represent how the execution time grows with input size (Big O Notation, Big Omega Notation, and Big Theta Notation respectively).
– These representations provide an idealized theoretical understanding of the algorithm’s efficiency, not always guaranteeing exact execution times.
Big O notation
Big O notation هو أداة رياضية تستخدم في علوم الكمبيوتر لوصف الحد الأعلى لكيفية نمو وقت تنفيذ الخوارزمية أو تعقيد المساحة مع زيادة حجم الإدخال. بعبارات أبسط، فهو يساعدنا على فهم مدى كفاءة أداء الخوارزمية أثناء تعاملها مع مجموعات بيانات أكبر وأكبر.
فيما يلي بعض النقاط الأساسية حول Big O notation:
ما الذي تمثله:
يركزBig O notation على السلوك المحدود للوظيفة (عادةً ما يمثل تعقيد الخوارزمية) حيث يميل حجم الإدخال نحو اللانهاية. فهو يتجاهل الثوابت والمصطلحات ذات الترتيب الأدنى، مما يوفر فكرة عامة عن كفاءة الخوارزمية، وليس وقت التنفيذ الدقيق.
الـnotations الرئيسية:
- O(n): هذا هو الترميز الأكثر شيوعًا، مما يعني أن الدالة تنمو خطيًا مع حجم الإدخال (n). يؤدي مضاعفة الإدخال إلى مضاعفة وقت التنفيذ تقريبًا. تتضمن الأمثلة البحث في قائمة غير مصنفة (البحث الخطي) والتكرار عبر جميع عناصر المصفوفة.
- O(log n): يشير هذا إلى النمو اللوغاريتمي، وهو أسرع بكثير من النمو الخطي. مضاعفة المدخلات تؤدي فقط إلى زيادة وقت التنفيذ بمقدار ثابت. البحث الثنائي هو مثال كلاسيكي.
- O(1): يمثل هذا تعقيدًا زمنيًا ثابتًا، مما يعني أن وقت التنفيذ مستقل عن حجم الإدخال. الوصول إلى عنصر مباشرة في مصفوفة بواسطة موقعه هو O(1).
- O(n^2): يشير هذا إلى النمو التربيعي، حيث يزيد وقت التنفيذ بشكل تربيعي مع حجم الإدخال. يمكن أن تؤدي الحلقات المتداخلة غالبًا إلى تعقيد O(n^2).
- O(k^n): يمثل هذا النمو الأسي، وهو أمر غير مرغوب فيه بشكل عام بسبب الزيادة السريعة في وقت التنفيذ حتى مع حجم الإدخال الصغير.
تفسير للـ Big O:
- تعتبر القيم الأقل (O(1)، O(log n)) أفضل بشكل عام لأنها تشير إلى خوارزميات أسرع تتكيف بشكل جيد مع المدخلات الأكبر.
- يمكن أن تمثل القيم الأعلى (O(n^2)، O(k^n)) مشكلة بالنسبة لمجموعات البيانات الكبيرة لأنها تؤدي إلى مشاكل كبيرة في الأداء.
Big O ليس مقياس التعقيد الوحيد:
بينما يركز Big O على الحدود العليا، هناك رموز أخرى مثل أوميغا (Ω) للحدود الدنيا وثيتا (Θ) للحدود الدقيقة.
مثال 1:
sum; // n = 5 for (i = 1; i <= n; i++) sum = sum + 1;
يحسب هذا الكود مجموع الأرقام من 1 إلى n باستخدام حلقة for. تتكرر الحلقة n مرات، وفي كل تكرار، تنفذ عملية إضافة ثابتة للوقت (sum = sum + 1).
ولذلك، فإن التعقيد الزمني لهذا الرمز هو O(n). هذا يعني أن وقت تنفيذ الكود ينمو خطيًا مع حجم الإدخال n. بمعنى آخر، مع زيادة قيمة n، فإن الوقت الذي يستغرقه تشغيل التعليمات البرمجية سيزداد أيضًا، ولكن بمعدل متناسب.
فيما يلي جدول يلخص تحليل التعقيد الزمني:
الخطوة | الوصف | التعقيد الزمني |
---|---|---|
التهيئة | تعرّف متغيرين sum و i |
O(1) |
حلقة الدوران | تتكرر n من المرات |
O(n) |
الزيادة | إضافة 1 إلى sum في كل تكرار |
O(n) |
المجموع | O(n) |
بشكل عام، يحتوي الكود على تعقيد زمني خطي، والذي يعتبر فعالاً للعديد من الخوارزميات.
مقدمة الى الـStack
مقدمة:
في مجال علوم الكمبيوتر والبرمجة، يعد فهم هياكل البيانات أمرًا أساسيًا. أحد هياكل البيانات الأساسية هذه هو الستاك. في هذه المقالة، سوف نتعمق في ماهية الستاك، وكيفية عمله، وعملياته، وتطبيقاته في البرمجة.
ما هو الـStack؟
الستاكعبارة عن بنية بيانات خطية تتبع مبدأ Last-In-First-Out (LIFO). تخيل كومة من الأطباق في الكافيتيريا؛ يمكنك فقط إزالة اللوحة العلوية من المكدس. وبالمثل، في بنية بيانات الستاك، يمكن فقط إضافة العناصر أو إزالتها من الأعلى.
كيف يعمل الـStack ؟
يحتوي الستاك على عمليتين أساسيتين: push وpop.
- Push: إضافة عنصر إلى أعلى الستاك.
- Pop: إزالة العنصر العلوي من الستاك.
بالإضافة إلى ذلك، تدعم الستاك عادةً عمليات أخرى مثل peek (لعرض العنصر العلوي دون إزالته) وisEmpty (للتحقق مما إذا كانت الستاك فارغ).
تنفيذ الـStack:
يمكن تنفيذ الستاك باستخدام المصفوفات أو القوائم المرتبطة.
- تطبيق المصفوفة: في هذا الأسلوب، يتم استخدام مصفوفة ذات حجم ثابت لتخزين عناصر الستاك. تعمل عمليات Push وpop على تعديل الفهرس العلوي للمصفوفة.
- تنفيذ القائمة المرتبطة: هنا، يتم استخدام قائمة مرتبطة لتنفيذ الستاك. تمثل كل عقدة في القائمة المرتبطة عنصرًا، والجزء العلوي من الستاكهو رأس القائمة.
عمليات الـStack:
دعونا نلقي نظرة على عمليات الستاك بالتفصيل:
- Push: إضافة عنصر إلى أعلى الستاك. يصبح العنصر الجديد هو العنصر العلوي، ويزداد حجم الستاك بمقدار واحد.
- Pop: إزالة العنصر العلوي من الستاك. يتم إرجاع العنصر (أو إزالته ببساطة)، ويتم تقليل حجم الستاك بمقدار واحد.
- getTop: إرجاع العنصر العلوي للستاك دون إزالته.
- isEmpty: يتحقق مما إذا كان الستاك فارغًا. يُرجع صحيحًا إذا كان الستاك فارغًا، ويُرجع خطأً إذا كان خلاف ذلك.
تطبيقات الـStack:
يجد الستاك تطبيقات في مجالات مختلفة من علوم الكمبيوتر والبرمجة، بما في ذلك:
- Function Call Stack: يستخدم لإدارة استدعاءات الدوال والمتغيرات المحلية في لغات البرمجة.
- تقييم التعبير: يستخدم لتقييم التعبيرات الحسابية، والتحويل من infix إلى postfix، وحل التعبيرات اللاحقة.
- آلية التراجع: تدعم عملية التراجع في برامج تحرير النصوص والتصميم الجرافيكي.
- Backtracking: يستخدم في خوارزميات مثل بحث العمق الأول (DFS) لاستكشاف جميع المسارات الممكنة في الرسم البياني.
الخلاصة:
في الختام، الستاك هي هياكل بيانات بسيطة ولكنها قوية تلعب دورًا حاسمًا في العديد من مهام البرمجة. إن فهم كيفية عمل الستاك وتطبيقاتها يمكن أن يعزز بشكل كبير قدرات المبرمج على حل المشكلات. سواء كنت مبتدئًا أو مبرمجًا ذو خبرة، فإن إتقان الستاك أمر ضروري لبناء أنظمة برمجية فعالة وقوية.
تنفيذ الـStack
يعرف هذا البرنامج stack class بسيطة مع عمليات أساسية مثل push, pop, getTop, وprint. إليك تفاصيل البرنامج:
#include <iostream> using namespace std; const int MAX_SIZE = 100; class Stack { private: int top; // Index of the top element in the stack int item[MAX_SIZE]; // Array to store stack elements public: // Constructor: Initializes top to -1 (empty stack) Stack() : top(-1) {} // Function to push an element onto the stack void push(int Element) { if (top >= MAX_SIZE - 1) { cout << "Stack is full on push" << endl; } else { top++; item[top] = Element; } } // Function to check if the stack is empty bool isEmpty() { return top < 0; } // Function to pop an element from the stack void pop() { if (isEmpty()) { cout << "Stack is empty on pop" << endl; } else { top--; } } // Function to get the top element of the stack void getTop(int& stackTop) { if (isEmpty()) { cout << "Stack is empty on getTop" << endl; } else { stackTop = item[top]; cout << stackTop << endl; } } // Function to print the elements of the stack void print() { cout << "["; for (int i = top; i >= 0; i--) { cout << item[i] << " "; } cout << "]" << endl; } }; int main() { Stack s; // Push elements onto the stack s.push(5); s.push(10); s.push(15); s.push(20); // Print the stack s.print(); // Get and print the top element of the stack int y = 0; s.getTop(y); // Pop an element from the stack s.pop(); // Push another element onto the stack s.push(7); // Print the stack again s.print(); return 0; }
شرح:
- تنفذ Stack class باستخدام عنصر مصفوفة مسماة item . يحتوي على دوال الأعضاء push, pop, getTop, isEmpty, وprint.
- في دالة push ، إذا لم تكن الـstack ممتلئة، فإنها تزيد الـ top index وتضيف العنصر إلى مصفوفة العناصر في الـ top index.
- تتحقق الدالة isEmpty مما إذا كان الـ stack فارغًا عن طريق مقارنة الـ top index بـ -1.
- تقوم الدالة pop بتقليل الـ top index إذا لم يكن الـ stack فارغًا.
- تقوم الدالة getTop باسترداد الـ top element للستاك وتخزينه في stackTop.
- تقوم دالة الطباعة بطباعة عناصر الستاك من الأعلى إلى الأسفل.
- في الدالة الرئيسية، يتم إنشاء كائن الستاك وتنفيذ عمليات مختلفة مثل push, pop, getTop, وprint.
الـStack بإستخدام القوائم المتصلة (ربط الـStack) 1
المقدمة
لقد قمنا بتغطية الـstack والـstack الذي تم تنفيذه باستخدام المصفوفات. الآن، دعونا نستكشف تنفيذ الـstack باستخدام المؤشرات ونناقش سبب اعتباره نهجًا متميزًا.
قد تتساءل عن سبب حاجتنا إلى مناقشة هذا البديل عندما نظرنا بالفعل إلى تطبيقات الـstack باستخدام المصفوفات. ألا يمكننا إستخدام بالمصفوفات وحسب؟
السبب وراء استكشاف تنفيذ الـstack باستخدام المؤشرات هو معالجة القيود المحتملة التي قد نواجهها مع التطبيقات المستندة إلى المصفوفة، خاصة فيما يتعلق بقيود الحجم. ومن خلال استخدام المؤشرات، يمكننا التحايل بشكل فعال على مثل هذه المشكلات، مما يضمن حلاً أكثر مرونة وقوة لإدارة البيانات داخل بنية الـstack.
يتضمن استخدام المؤشرات لتنفيذ الـstack تخصيص الذاكرة ديناميكيًا لعناصر الـstack ومعالجة المؤشرات لإدارة بنيته. يعد هذا الأسلوب مفيدًا بشكل خاص عندما لا يكون حجم الـstack معروفًا مسبقًا أو عندما تكون كفاءة الذاكرة موضع اهتمام.
بناء العقدة Node
تشير "العقدة node" إلى وحدة البناء الأساسية المستخدمة في العديد من هياكل البيانات، بما في ذلك القوائم المرتبطة والأشجار والرسوم البيانية والمزيد. العقدة node هي بنية تحتوي على مكونين رئيسيين:
- البيانات Data: يحمل هذا المكون القيمة الفعلية أو العنصر الذي تمثله العقدة. يمكن أن يكون من أي نوع بيانات اعتمادًا على التطبيق.
- Next Pointer المؤشر التالي (أو الرابط Link): هذا المكون هو مرجع أو مؤشر للعقدة التالية في التسلسل. يقوم بإنشاء الاتصال بين العقد في بنية البيانات.
تحدد مقتطفات التعليمات البرمجية هذه بنية العقدة الأساسية بلغات برمجة مختلفة:
- C++:
struct Node { Type item; Node* next; };
في C++، يتم استخدام struct لتعريف بنية تسمى Node. وفيه عضوان:
– item: متغير من النوع Type لتخزين البيانات.
- next: مؤشر إلى عقدة أخرى، يشير إلى العقدة التالية في التسلسل. - Java:
class Node { Type item; Node next; }
في Java، يتم استخدام class لتعريف فئة تسمى Node. وكذلك فهو يضم عضوين:
– item: متغير من النوع Type لتخزين البيانات.
- next: وهو object من نوع Node، يمثل العقدة التالية في التسلسل. - C#:
class Node { Type item; Node next; }
وهذا مشابه لجافا. في لغة C#، يتم استخدام class لتعريف فئة تسمى Node، وهي تحتوي أيضًا على عضوين:
– item: متغير من النوع Type لتخزين البيانات.
- next: وهو object من نوع Node، يمثل العقدة التالية في التسلسل. - Python:
class Node: def __init__(self): self.item = None self.next = None
في بايثون، يتم تعريف class يسمى Node. الأسلوب __init__ هو constructor يستخدم لتهيئة كائنات الـclass. داخل الـconstructor :
- تتم تهيئة self.item إلى None، مما يشير إلى أنه يقوم بتخزين البيانات.
- تتم تهيئة self.next إلى None، مما يشير إلى أنه يشير إلى العقدة التالية في التسلسل.
stackTop
يشير StackTop عادةً إلى العنصر العلوي في بنية بيانات الـstack. في سياق الـstack، هو متغير أو مؤشر يشير إلى العنصر العلوي للـstack، أو بمعنى آخر، العنصر المضاف حديثًا.
لتعيين قيمة stackTop للعضو التالي في الـnode:
- C++:
newNode هو مؤشر للعقدة، ومن المفترض أن يكون stackTop مؤشرًا آخر للعقدة. يقوم هذا السطر بتعيين قيمة stackTop للمؤشر التالي للعقدة الجديدة، مما يؤدي بشكل فعال إلى ربط العقدة الجديدة بالعقدة المشار إليها بواسطة stackTop.newNode->next = stackTop;
- Java, C#, Python:
newNode هو object من النوع Node، وstackTop هو أيضًا object من النوع Node. يقوم هذا السطر بتعيين مرجع الكائن المشار إليه بواسطة StackTop إلى الحقل التالي للعقدة الجديدة.newNode.next = stackTop; // Java and C# newNode.next = stackTop // Python
لتعيين قيمة newNode إلى StackTop:
C++, Java, C#, Python:
stackTop = newNode
StackTop هو مؤشر للعنصر العلوي للـstack الذي يتم تنفيذه باستخدام المؤشرات أو تخصيص الذاكرة الديناميكية. StackTop = newNode سيقوم بتحديث StackTop للإشارة إلى العقدة المنشأة حديثًا newNode. وهذا يجعل newNode العنصر العلوي الجديد في الـstack بشكل فعال.
لإنشاء نسخة من StackTop نستخدم الجملة:
temp = stackTop
لماذا نفعل هذا؟
يتيح لك إنشاء نسخة من stackTop (temp) التعامل بأمان مع العنصر العلوي من المكدس والوصول إليه دون التأثير على المؤشر أو المرجع أو القيمة الأصلية. فهو يوفر المرونة ويضمن بقاء البيانات الأصلية سليمة لاستخدامها في المستقبل.
الدوال الرئيسية لتطبيقات ADT stack (ربط الـStack) المرتكزة على المؤشر:
- push(Type newItem)
- pop()
- pop(Type& stackTop)
- getTop()
- display()
- isEmpty()
جافا
Java هي لغة برمجة عالية المستوى ومتعددة الاستخدامات ومستخدمة على نطاق واسع ومعروفة بسهولة النقل والمرونة. تم تطوير Java في الأصل بواسطة Sun Microsystems وتتم صيانتها الآن بواسطة Oracle Corporation، وتمتلك Java نظامًا بيئيًا واسعًا من المكتبات والأدوات التي تجعلها خيارًا شائعًا لتطبيقات البرامج المختلفة، بدءًا من تطوير الويب وحتى تطبيقات الأجهزة المحمولة والأنظمة على مستوى المؤسسات. تحظى Java بالتقدير لقدرتها على العمل على منصات متعددة، وميزاتها الأمنية القوية، ودعمها للبرمجة الموجهة للكائنات. إنها تقنية أساسية لبناء مواقع الويب الديناميكية وتطبيقات الويب التفاعلية وتطبيقات الهاتف المحمول، مما يجعلها أداة حيوية في عالم تطوير البرمجيات.
المقدمة
لغة البرمجة جافا Java
Java هي لغة برمجة مرتكزة على الـClass كما أنها كائنية التوجه. يتم استخدامها على نطاق واسع لتطوير أنواع مختلفة من التطبيقات، بدءًا من أدوات سطح المكتب البسيطة وحتى أنظمة المشاريع المعقدة.
Java متاحة لمعظم أنظمة التشغيل (اكتبها مرة واحدة وقم بتشغيلها في أي مكان Write once run anywhere).
تطبيقات لغة جافا مع أمثلة من العالم الحقيقي
- تطبيقات الموبايل.
- تطبيقات واجهة المستخدم الرسومية GUI لسطح المكتب.
- تطبيقات الويب.
- تطبيقات الألعاب.
- تطبيقات المشاريع.
- الحماية.
- التطبيقات العلمية.
- الأنظمة المضمنة Embedded.
- تقنيات البيانات الضخمة Big Data.
- التطبيقات الموزعة
- التطبيقات المستندة إلى السحابة الإلكترونية Cloud-based.
- خوادم الويب وخوادم التطبيقات.
- أدوات البرمجيات.
لماذا نحتاج تعلم لغات البرمجة؟
هناك العديد من الأسباب التي تجعل تعلم لغة برمجة مثل Java أمرًا ذا قيمة، حتى لو كنت لا تخطط لأن تصبح مبرمجًا محترفًا. نذكر بعضها:
- تشكيل المشهد الرقمي بشكل مباشر: يتم استخدام Java لإنشاء عدد لا يحصى من مواقع الويب وتطبيقات الأجهزة المحمولة، بدءًا من منصات الوسائط الاجتماعية المفضلة لديك وحتى الأدوات المصرفية عبر الإنترنت. إن تعلم Java يمكّنك من أن تكون أكثر من مجرد مستخدم؛ يمكنك أن تصبح مبدعًا، وتشكل التجارب الرقمية للآخرين.
- يعزز حل المشكلات والتفكير النقدي: تساعدك البرمجة على تطوير مهارات التفكير المنطقي والقدرة على تقسيم المشكلات المعقدة إلى خطوات أصغر قابلة للحل. وهذا يترجم بشكل جيد إلى مجالات أخرى من الحياة، من معالجة الواجبات المدرسية إلى اتخاذ القرارات اليومية.
- يفتح أبوب المهنة: على الرغم من أن مهارات البرمجة ليست ضرورية لكل وظيفة، إلا أنها مطلوبة بشكل متزايد في مختلف الصناعات. حتى المعرفة الأساسية يمكن أن تمنحك ميزة في العديد من المجالات، بدءًا من التسويق والتمويل وحتى علوم البيانات والتعليم.
- إنها ببساطة ممتعة ومجزية! هناك متعة فريدة من نوعها عند رؤية الكود الخاص بك ينبض بالحياة وإنشاء شيء عملي أو حتى جميل. يمكن أن تكون عملية التعلم نفسها صعبة ومجزية، مما يعزز الشعور بالإنجاز والنمو.
نحتاج أيضًا إلى معرفة كيفية التواصل مع أجهزة الكمبيوتر، وللقيام بذلك لدينا ما يسمى النظام الثنائي الذي يمثل البيانات والتعليمات باستخدام رمزين فقط: 0 و1. هذا النظام هو أساس الحوسبة الرقمية لأن الأجهزة الإلكترونية، مثل أجهزة الكمبيوتر والمعالجات الدقيقة، تفسر وتعالج المعلومات بتنسيق ثنائي.
كمبرمج، تقوم بكتابة التعليمات البرمجية المصدرية source code لتنفيذ وظائف محددة، مثل إنشاء النص. تتم كتابة كود المصدر source code هذا باستخدام بناء جملة محدد. على سبيل المثال، في Java، يمكنك كتابة System.out.print("Java"). هذا السطر من التعليمات البرمجية مفهوم للبشر ويعتبر تمثيلاً لغويًا عالي المستوى high-level language.
ومع ذلك، قبل أن يتمكن الجهاز من فهم هذا الكود وتنفيذه، يجب أن يخضع لعدة خطوات لترجمته إلى نموذج يمكنه معالجته. أولاً، يتم تمرير التعليمات البرمجية من خلال مترجم Java (javac)، الذي يتحقق من بناء الجملة ويحول التعليمات البرمجية المصدر إلى كود بايت. Bytecode هو تمثيل منخفض المستوى low-level للتعليمات البرمجية المستقلة عن النظام الأساسي ويمكن تنفيذها بواسطة أي نظام باستخدام Java Virtual Machine (JVM).
بعد ذلك، يتم تفسير الـbytecode بواسطة JVM، الذي يعمل كمترجم. يقوم JVM بترجمة الـbytecode إلى تعليمات كود الجهاز التي يمكن لنظام التشغيل الأساسي فهمها وتنفيذها. تسمح هذه العملية لبرامج Java بالعمل على أنظمة تشغيل مختلفة دون تعديل، مما يجعلها سمة أساسية في لغة Java.
منصات لغة البرمجة جافا
- منصة جافا، الإصدار القياسي (Java SE)
- تطبيقات سطح المكتب. - منصة جافا، إصدار المؤسسات (Java EE)
- تطبيقات الويب، تطبيقات الخادم. - منصة جافا، إصدار المايكرو (Java ME)
– تطبيقات الهاتف المحمول، تطبيقات الألعاب. - Java FX
- منصة للأنظمة المكتبية والمحمولة والأنظمة المدمجة. مبني على Java ومجموعة أدوات مميزة بالكامل لتطوير تطبيقات العملاء الغنية.
تحميل وتثبيت أدوات تطوير جافا
تحميل مجموعة تطوير جافا
لتنزيل Java Development Kit (JDK) لبرمجة Java، اتبع الخطوات التالية:
- افتح متصفح الويب الخاص بك وابحث عن "تنزيل JDK for Java" على Google. قم بزيارة موقع Oracle الرسمي لأن Java مملوكة لشركة Oracle. سيتم توجيهك إلى صفحة تنزيلات Java SE.
- في صفحة تنزيلات Java SE، اختر إصدار JDK الذي يتوافق مع نظام التشغيل الخاص بك. تتوفر الخيارات عادةً لأنظمة التشغيل Windows وmacOS وLinux.
- اقبل اتفاقية ترخيص Oracle Technology Network لـ Oracle Java SE لمتابعة التنزيل. اقرأ الشروط والأحكام واقبلها إذا وافقت عليها.
- انقر على رابط التنزيل لبدء تنزيل ملف تثبيت JDK. سيكون تنسيق الملف هو .exe لنظام التشغيل Windows، و.dmg لنظام التشغيل macOS، و.tar.gz لنظام التشغيل Linux.
- بمجرد اكتمال التنزيل، حدد موقع ملف التثبيت الذي تم تنزيله وقم بتشغيله. اتبع التعليمات التي تظهر على الشاشة لتثبيت JDK على نظامك.
إعداد بيئة التطوير
الآن، لإعداد بيئة التطوير الخاصة بك:
- قم بتنزيل بيئة التطوير المتكاملة (IDE) لـ Java. مرة أخرى، قم بإجراء بحث في Google عن "IDE for Java" وفكر في تنزيل Apache NetBeans، وهو IDE سهل الاستخدام لتطوير Java. إذا كنت لا ترغب في تنزيل ملف ide، فيمكنك العمل معنا الآن باستخدام مترجم عبر الإنترنت ونحن نوصي بـ ideone.com.
- ابحث عن "تنزيل NetBeans for Java" على Google. ابحث عن إصدار Apache NetBeans وقم بتنزيله.
- بمجرد اكتمال التنزيل، يمكنك العثور على NetBeans IDE الذي تم تنزيله على جهازك. انتقل إلى قائمة "ابدأ" وانقر عليها لفتح NetBeans IDE.
- لبدء مشروعك الأول في NetBeans، انقر فوق "ملف" > "مشروع جديد" > "Java" > "تطبيق Java". اختر اسمًا لمشروعك (على سبيل المثال، "FirstProject") وحدد الموقع الذي تريده. انقر فوق "إنهاء" لإنشاء المشروع.
- في نافذة المشروع، انتقل إلى مجلد "Source Packages". انقر بزر الماوس الأيمن على محتوى المجلد وحدد "جديد" > Java Class". قم بتسميته باستخدام الحروف الكبيرة لكل كلمة بدون مسافات (على سبيل المثال، "NewClass"). تأكد من أن اسم الـclass يطابق اسم ملف Java. انقر فوق "إنهاء" لإنشاء الـclass.
باتباع هذه الخطوات، ستكون قد قمت بتنزيل وتثبيت JDK لتطوير Java بنجاح، وإعداد Apache NetBeans IDE، وإنشاء مشروع Java الأول. أنت الآن جاهز لبدء البرمجة بلغة Java!
قواعد بناء الجملة Syntax
فيما يلي قواعد بناء جملة Java:
- الكلمات المحجوزة: بعض الكلمات في Java محجوزة لأغراض محددة ولا يمكن استخدامها كمعرفات (على سبيل المثال، class، public، static). تُعرف هذه الكلمات بالكلمات المحجوزة أو الكلمات الرئيسية ويتم تمييزها باللون الأزرق.
- التعليمات البرمجية: يجب وضع جميع كتل التعليمات البرمجية في Java بين قوسين متعرجين {}. يتضمن ذلك تعريفات الفئة وتعريفات الأساليب والحلقات والعبارات الشرطية.
- Main Method: نقطة البداية لبرنامج Java هي Main Method. يجب الإعلان عنه باعتباره public static void main(String[] args) ويجب كتابته دائمًا كما هو موضح أدناه:
public static void main(String[] args) { // Code goes here }
- الفواصل المنقوطة: تستخدم الفواصل المنقوطة (؛) لإنهاء البيانات في Java. يجب أن تنتهي كل عبارة بفاصلة منقوطة.
- مثال:
public class MyFirstProgram { public static void main(String[] args) { System.out.print("Hello"); } }
- طريقة System.out.print() هي دالة في Java يتم استخدامها لعرض المخرجات. وفيما يلي تفصيل لمكوناته:
- System: عبارة عن class في حزمة Java.lang الخاصة بـ Java. يوفر الوصول إلى تدفقات الإدخال والإخراج والخطأ القياسية للنظام.
- out: out هو عضو ثابت في System class. إنه يمثل دفق الإخراج القياسي، والذي عادة ما يكون شاشة المخرجات أو المحطة الطرفية حيث ترى إخراج النص عند تشغيل برنامج Java.
- print(): هي طريقة للـ out object. يتم استخدامه لعرض النص أو البيانات الأخرى على دفق الإخراج القياسي دون التقدم إلى السطر التالي. سيتم عرض كل ما يتم تمريره كوسيطة إلى طريقة الطباعة () على شاشة المخرجات.
على سبيل المثال:System.out.print("Hello");
سيعرض سطر التعليمات البرمجية هذا "Hello" على شاشة المخرجات.
- حساسية حالة الأحرف: Java حساسة لحالة الأحرف، مما يعني أنه يتم التعامل مع الأحرف الكبيرة والصغيرة على أنها مختلفة. على سبيل المثال، يعتبر "hello" و"Hello" معرفين مختلفين.
أنواع الأخطاء
في Java، يمكن تصنيف الأخطاء بشكل عام إلى ثلاثة أنواع رئيسية:
- خطأ في بناء الجملة: تحدث أخطاء في بناء الجملة عند انتهاك قواعد بناء جملة Java. تم اكتشاف هذه الأخطاء بواسطة المترجم أثناء عملية الترجمة. تتضمن أخطاء بناء الجملة الشائعة الكلمات الأساسية التي بها أخطاء إملائية والفواصل المنقوطة المفقودة والاستخدام غير الصحيح لعوامل التشغيل. تمنع الأخطاء النحوية ترجمة البرنامج بنجاح.
- خطأ وقت التشغيل: تحدث أخطاء وقت التشغيل، المعروفة أيضًا باسم الاستثناءات، أثناء تنفيذ البرنامج. تحدث هذه الأخطاء عند ظهور ظروف أو مواقف غير متوقعة أثناء تشغيل البرنامج. تتضمن الأمثلة الشائعة القسمة على صفر، والوصول إلى عنصر مصفوفة خارج الحدود، ومحاولة استخدام مرجع object فارغ. تتسبب أخطاء وقت التشغيل في إنهاء البرنامج فجأة ما لم تتم معالجتها باستخدام آليات معالجة الاستثناءات مثل try-catch blocks.
- خطأ منطقي: تحدث الأخطاء المنطقية عندما لا ينتج البرنامج المخرجات المتوقعة بسبب أخطاء منطقية أو أخطاء خوارزمية غير صحيحة في التعليمات البرمجية. على عكس أخطاء بناء الجملة وأخطاء وقت التشغيل، لا تتسبب الأخطاء المنطقية في إنهاء البرنامج أو ظهور رسائل خطأ. وبدلاً من ذلك، فإنها تؤدي إلى سلوك أو مخرجات غير صحيحة. غالبًا ما يتضمن تصحيح الأخطاء المنطقية تحليلًا دقيقًا لمنطق التعليمات البرمجية والخوارزمية لتحديد المشكلة الأساسية وإصلاحها.
سلاسل الهروب والتعليقات
التعليقات
في Java، التعليقات عبارة عن عبارات غير قابلة للتنفيذ ويتم تجاهلها بواسطة المترجم. يتم استخدامها لتقديم التوضيحات والوثائق والشروح داخل التعليمات البرمجية. هناك ثلاثة أنواع من التعليقات في Java:
- التعليقات ذات السطر الواحد: تبدأ التعليقات ذات السطر الواحد بخطين مائلين للأمام (//). كل شيء بعد // على نفس السطر يعتبر تعليقًا ويتم تجاهله بواسطة المترجم. تُستخدم التعليقات المكونة من سطر واحد عادةً للتفسيرات القصيرة أو التعليقات على سطر واحد من التعليمات البرمجية.
مثال:// This is a single-line comment
- التعليقات متعددة الأسطر: التعليقات متعددة الأسطر، والمعروفة أيضًا باسم التعليقات الجماعية، تكون محاطة بين /* و*/. كل ما بين هذه الرموز، بما في ذلك الأسطر الجديدة، يعتبر تعليقًا. غالبًا ما تُستخدم التعليقات متعددة الأسطر للتفسيرات الأطول أو التعليقات التي تمتد إلى عدة أسطر من التعليمات البرمجية.
مثال:/* This is a multi-line comment */
- تعليقات Javadoc: تعليقات Javadoc هي نوع خاص من التعليقات يُستخدم لإنشاء الوثائق. تبدأ بـ /** وتنتهي بـ */. تُستخدم تعليقات Javadoc لوصف الفئات والأساليب والحقول، ويمكن أن تتضمن علامات خاصة لتوفير معلومات إضافية مثل المعلمات وقيم الإرجاع والاستثناءات.
مثال:/** * This is a Javadoc comment for the MyClass class. */ public class MyClass { /** * This is a Javadoc comment for the myMethod method. * @param x This is a parameter of the method. * @return This method returns a value. */ public int myMethod(int x) { return x * x; } }
سلاسل الهروب:
في Java، تسلسلات الهروب عبارة عن مجموعات أحرف خاصة تستخدم لتمثيل الأحرف التي يصعب أو يستحيل تمثيلها مباشرة في سلسلة حرفية. تُسبق تسلسلات الهروب بحرف شرطة مائلة عكسية (). فيما يلي بعض تسلسلات الهروب الشائعة المستخدمة في Java:
- n\: يمثل حرف السطر الجديد. عند استخدامها ضمن سلسلة حرفية، فإنها تنقل المؤشر إلى بداية السطر التالي.
- t\: يمثل حرف علامة التبويب. عند استخدامها ضمن سلسلة حرفية، فإنها تُدرج علامة تبويب أفقية.
- '\: يمثل حرف اقتباس واحد. يستخدم لتضمين علامة اقتباس مفردة ضمن سلسلة حرفية.
- "\: يمثل حرف الاقتباس المزدوج. يستخدم لتضمين علامات الاقتباس المزدوجة داخل سلسلة حرفية.
- \\: يمثل حرف الخط المائل العكسي. تستخدم لتضمين شرطة مائلة عكسية داخل سلسلة حرفية.
- r\: يمثل حرف إرجاع.
- b\: يمثل حرف مسافة للخلف.
- f\: يمثل حرف تغذية النموذج.
مثال:
public class EscapeSequencesExample { public static void main(String[] args) { // Newline (\n) escape sequence System.out.println("Hello\nWorld"); // Output: Hello // World // Tab (\t) escape sequence System.out.println("Java\tProgramming"); // Output: Java Programming // Double quote (\") escape sequence System.out.println("She said, \"Hello!\""); // Output: She said, "Hello!" // Backslash (\\) escape sequence System.out.println("C:\\Users\\John\\Documents"); // Output: C:\Users\John\Documents // Carriage return (\r) escape sequence System.out.println("Overwritten text\r123"); // Output: 123written text // Backspace (\b) escape sequence System.out.println("Back\bspace"); // Output: Backspace // Form feed (\f) escape sequence System.out.println("Hello\fWorld"); // Output: Hello // World } }
أنواع البيانات
تسمية المتغيرات
في Java، يجب أن تلتزم أسماء المتغيرات بقواعد قياسية معينة لضمان الوضوح وسهولة القراءة والتوافق مع بناء جملة اللغة. فيما يلي القواعد القياسية لتسمية المتغيرات في Java:
- طول الاسم المتغير: يمكن أن تكون الأسماء المتغيرة بأي طول. ومع ذلك، يوصى باستخدام أسماء ذات معنى ووصفية مع إبقائها موجزة وسهلة القراءة.
- الأحرف المسموحة: يمكن أن تتكون الأسماء المتغيرة من أحرف (كبيرة وصغيرة)، وأرقام، والشرطة السفلية (_)، وعلامة الدولار ($). يمكن أن تبدأ بحرف أو شرطة سفلية أو علامة الدولار.
- الكلمات المحجوزة: لا يمكن أن تكون أسماء المتغيرات مماثلة لكلمات Java الأساسية أو الكلمات المحجوزة. على سبيل المثال، لا يمكنك تسمية متغير "class" أو "int" حيث أنهما كلمات محجوزة في Java.
- حساسية حالة الأحرف: Java حساسة لحالة الأحرف، مما يعني أنه يتم التعامل مع الأحرف الكبيرة والصغيرة كأحرف مختلفة. لذلك، يتم اعتبار "myVar" و"myvar" كمتغيرين مختلفين.
- طريقة CamelCase: إنها طريقة شائعة في Java لاستخدام CamelCase لتسمية المتغيرات. يتضمن CamelCase كتابة كلمات أو عبارات مركبة بحيث تبدأ كل كلمة أو اختصار بحرف كبير، دون مسافات أو علامات ترقيم بينهما. على سبيل المثال: myVariableName، TotalAmount، StudentAge.
- الأسماء ذات المعنى: يجب أن تكون أسماء المتغيرات وصفية وتنقل غرض أو معنى المتغير. تجنب استخدام أسماء المتغيرات المكونة من حرف واحد (باستثناء عدادات الحلقات) أو الاختصارات المبهمة بشكل مفرط.
- البدء بأحرف صغيرة: من المعتاد في لغة Java بدء أسماء المتغيرات بأحرف صغيرة، باستثناء الثوابت، والتي تتم كتابتها عادةً بأحرف كبيرة.
- الأحرف الخاصة: بالإضافة إلى الأحرف والأرقام والشرطة السفلية، يمكنك استخدام علامة الدولار ($) كحرف خاص في أسماء المتغيرات. تجنب استخدام أحرف خاصة أخرى مثل @ و% وما إلى ذلك.
من خلال الالتزام بهذه القواعد القياسية، يمكنك إنشاء أسماء متغيرات واضحة وموجزة وذات معنى في كود Java، مما يعزز سهولة القراءة وقابلية الصيانة.
أنواع البيانات
في Java، تحدد أنواع البيانات نوع البيانات التي يمكن تخزينها في متغير. تحتوي Java على فئتين من أنواع البيانات: أنواع البيانات البدائية وأنواع البيانات المرجعية.
أنواع البيانات البدائية:
أنواع البيانات البدائية هي أنواع البيانات الأساسية في Java. إنها تمثل قيمًا فردية ويتم تحديدها مسبقًا بواسطة اللغة. هناك ثمانية أنواع من البيانات البدائية في Java:
- byte: نوع بيانات البايت هو عدد صحيح مكمل مكون من 8 بتات. الحد الأدنى لقيمته -128 والحد الأقصى لقيمته 127.
- short: نوع البيانات القصيرة عبارة عن عدد صحيح مكمل مكون من 16 بت. الحد الأدنى لقيمته -32,768 والحد الأقصى لقيمته 32,767.
- int: نوع البيانات Int عبارة عن عدد صحيح مكمل مكون من 32 بت. الحد الأدنى لقيمته -2^31 والحد الأقصى لقيمته 2^31-1.
- long: نوع البيانات long هو عدد صحيح مكمل مكون من 64 بت. الحد الأدنى لقيمته -2^63 والحد الأقصى لقيمته 2^63-1.
- float: نوع البيانات float عبارة عن نقطة عشرية ذات دقة واحدة 32 بت IEEE 754. ولا ينبغي أبدًا استخدامه للقيم الدقيقة، مثل العملة.
- double: نوع البيانات double عبارة عن فاصلة عشرية IEEE 754 مزدوجة الدقة 64 بت. يتم استخدامه للقيم العشرية التي تتطلب المزيد من الدقة.
- boolean: يمثل نوع البيانات boolean وحدة بت واحدة من المعلومات. لها قيمتان محتملتان فقط: true وfalse.
- char: نوع بيانات Char هو حرف Unicode واحد بطول 16 بت.
مثال:
byte age = 25; short distance = 10000; int population = 1500000; long nationalDebt = 20202020202020L; // Use 'L' suffix for long literals float temperature = 32.5f; // Use 'f' suffix for float literals double pi = 3.14159265359; boolean isJavaFun = true; char grade = 'A';
تعد أنواع البيانات البدائية هذه بمثابة اللبنات الأساسية لبرامج Java، مما يسمح للمطورين بتخزين أنواع مختلفة من البيانات ومعالجتها بكفاءة. يعد فهم خصائصها واستخدامها المناسب أمرًا ضروريًا لكتابة كود Java الفعال.
ملحوظة ! هناك srt الذي يشير إلى string، لكنه يعتبر object data type.
البرمجة كائنية التوجه في لغة Java
البرمجة كائنية التوجه (OOP) هي نموذج برمجة أساسي يستخدم على نطاق واسع في Java. في OOP، المفهوم الأساسي هو تنظيم التعليمات البرمجية في "كائنات"، وهي أمثلة لفئات تمثل كيانات أو مفاهيم أو هياكل بيانات في العالم الحقيقي.
المقدمة
البرمجة كائنية التوجه
البرمجة كائنية التوجه هي منهجية أو نموذج لتصميم برنامج باستخدام الفئات classes والكائنات objects. وله العديد من المزايا مثل:
- إعادة الاستخدام.
- سهولة القراءة.
- أداء عالي.
- توفير المساحة.
البرمجة كائنية التوجه (OOP) هي نموذج برمجة يدور حول مفهوم الكائنات. Java هي لغة برمجة قوية ومستخدمة على نطاق واسع وتدعم بشكل كامل مبادئ OOP.
اللغات الأخرى التي تدعم البرمجة كائنية التوجه : Python اC++ وVisual Basic وNET وRuby.
وهناك منهجيات أخرى مثل:
- البرمجة الإجرائية التي تستخدمها لغة C، Pascal، COBOL، FORTRAN، Java، Python، C++..إلخ.
- Event-driven وهي تعتمد على الأحداث وتستخدم بواسطة C# وVisual Basic وVisual C++ وJava.
الكائنات هي لبنات بناء أساسية في البرمجة الكائنية (OOP) وتلعب دورًا حاسمًا في تصميم الأنظمة وتنفيذها. لتوضيح هذا المفهوم، دعونا نفكر في مثال تصميم نظام المتجر.
عندما نفكر في بناء نظام متجر، فإن أول ما يتبادر إلى ذهننا هو العناصر المعنية. في هذه الحالة، يمكننا تحديد ثلاثة أشياء رئيسية: المنتجات والعملاء وطرق الدفع.
المنتجات: تمثل المنتجات العناصر التي يبيعها المتجر. يمكن أن تكون هذه أي شيء من البقالة إلى الإلكترونيات أو الملابس. يتميز كل منتج في المتجر بخصائص فريدة مثل الاسم والسعر والكمية والفئة. في OOP، يمكن تمثيل المنتجات ككائنات ذات خصائص وسلوكيات. على سبيل المثال، يمكن أن يكون لدينا فئة منتج بسمات مثل الاسم والسعر والكمية وطرق لتحديث معلومات المنتج أو التحقق من التوفر.
العملاء: العملاء هم الأفراد أو الكيانات الذين يزورون المتجر لشراء المنتجات. يتفاعلون مع المتجر من خلال تصفح المنتجات واختيار العناصر للشراء وإكمال المعاملات. في OOP، يمكن تصميم العملاء ككائنات ذات سمات مثل الاسم والعنوان ومعلومات الاتصال وسجل الشراء. يمكن أن يكون لدينا فئة العملاء مع طرق تقديم الطلبات أو عرض سجل الطلبات أو تحديث التفاصيل الشخصية.
طريقة الدفع: طرق الدفع هي آليات يستخدمها العملاء لدفع ثمن مشترياتهم. يمكن أن يشمل ذلك النقد أو بطاقات الائتمان/الخصم أو محافظ الهاتف المحمول أو بوابات الدفع عبر الإنترنت. تحتوي كل طريقة دفع على مجموعة القواعد والإجراءات الخاصة بها لمعالجة المعاملات. في OOP، يمكن تمثيل طرق الدفع ككائنات ذات خصائص وسلوكيات تتعلق بمعالجة المدفوعات. يمكننا تحديد فئات مختلفة لطرق الدفع باستخدام طرق للسماح بالمدفوعات أو التعامل مع المبالغ المستردة أو إنشاء إيصالات المعاملات.
من خلال تحديد هذه الأشياء والعلاقات بينها، يمكننا تصميم نظام متجر متماسك ومعياري يشمل وظائف إدارة المنتجات وخدمة العملاء ومعالجة المدفوعات. يقوم كل كائن في النظام بتغليف حالته وسلوكه، مما يعزز التغليف والنمطية وإعادة استخدام التعليمات البرمجية، وهي المبادئ الأساسية للبرمجة الموجهة للكائنات.
الكائنات Objects
تتكون الكائنات في البرمجة كائنية التوجه (OOP) من عضوين أساسيين: البيانات (الخصائص والسمات) والعمليات (الطرق والوظائف والسلوكيات). تقوم هذه العناصر بتغليف حالة الكائن وسلوكه، على التوالي، وتعمل معًا لتمثيل كيانات العالم الحقيقي داخل نظام برمجي.
دعنا نستكشف مثال السيارة لفهم هذه المفاهيم بشكل أفضل:
- صفات:
الاسم: يمثل العلامة التجارية أو الشركة المصنعة للسيارة.
السرعة القصوى: تشير إلى السرعة القصوى التي يمكن للسيارة تحقيقها.
السعر: يدل على تكلفة أو سعر السيارة.
الطراز: يمثل الطراز أو الإصدار المحدد من السيارة. - العمليات:
move(): يمثل عملية تحريك السيارة. قد تتضمن هذه الطريقة تشغيل المحرك، والتسريع، والفرملة، والتوجيه.
CalculateVehicleAge(): حساب عمر السيارة بناءً على سنة التصنيع أو سنة الطراز. - Getters and Setters:
get(): تقوم أساليب Getter باسترداد القيمة الحالية للسمة.
set (): تقوم طرق الضبط بتعديل أو تحديث قيمة السمة.
على سبيل المثال، في فئة السيارة، قد يكون لدينا أساليب getter وsetter لكل سمة، مثل getName() وsetName() لسمة الاسم، وgetMaxSpeed() وsetMaxSpeed() لسمة السرعة القصوى، وما إلى ذلك. تسمح هذه الأساليب للتعليمات البرمجية الخارجية بالوصول إلى حالة كائن السيارة وتعديلها بطريقة خاضعة للرقابة، مما يعزز التغليف وسلامة البيانات.
بالإضافة إلى ذلك، فإن مخطط الفئة هو تمثيل رسومي لبنية وعلاقات الفئات في نظام برمجي. إنه يصور بصريًا الطبقات وسماتها وأساليبها وارتباطاتها بالفئات الأخرى. توفر الرسوم البيانية للفصل نظرة عامة عالية المستوى على بنية النظام وتساعد المطورين على فهم العلاقات بين المكونات المختلفة.
الفئات Classes
في البرمجة كائنية التوجه (OOP)، الفئة عبارة عن مخطط أو قالب يحدد بنية الكائنات وسلوكها. إنه بمثابة مخطط لإنشاء كائنات ذات خصائص ووظائف مماثلة. تقوم الفئات بتغليف البيانات (السمات) والعمليات (الطرق) في وحدة واحدة، مما يوفر طريقة معيارية وقابلة لإعادة الاستخدام لنمذجة كيانات العالم الحقيقي داخل نظام برمجي.
الفئات Classes والكائنات Objects
إنشاء فئة في Java باستخدام NetBeans:
تشبه الفئات المخططات التي تحدد بنية الكائنات وسلوكها في Java. فكر فيها كنماذج لبناء كائنات محددة ذات خصائص وقدرات فريدة.
في Apache NetBeans، اتبع الخطوات التالية لإنشاء class جديد:
إعداد مشروع:
- إذهب إلى File >> New Project >> Java Application
- قم بتسمية مشروعك (على سبيل المثال، JavaOOP) وانقر فوق "Finish".
إنشاء Package:
- انتقل إلى قسم Source Package.
- انقر فوق علامة + واختر جديد New > حزمة Java Package.
- قم بتسمية الـpackage الخاصة بك (على سبيل المثال، com.mycompany.javaoop) وانقر فوق "Finish".
إنشاء Class:
- انقر بزر الماوس الأيمن على package التي قمت بإنشائها.
- حدد جديد New > فئة Java Class.
- قم بتسمية الـclass (على سبيل المثال، Main) وانقر فوق "Finish".
الكود:
package com.mycompany.javaoop; public class Main { public static void main(String[] args) { System.out.println("JAVA"); } }
شرح الكود:
- package com.mycompany.javaoop;: يحدد هذا السطر الحزمة التي تنتمي إليها فئتك الرئيسية. يساعد تنظيم الفئات في حزم في الحفاظ على بنية التعليمات البرمجية وتجنب تعارض الأسماء.
– public class Main: هذا يعلن عن فئة عامة تسمى Main. يمكن الوصول إلى الـclasses العامة من أجزاء أخرى من مشروعك.
– public static void main(String[] args): هذه هي الطريقة الرئيسية main، وهي نقطة الدخول حيث يبدأ تنفيذ برنامجك.
- System.out.println("JAVA");: يطبع هذا السطر النص "JAVA" على شاشة المخرجات.
استكشاف الأخطاء وإصلاحها:
: إذا واجهت هذا الخطأ، فتأكد من تعيين حقل Main Class فيRun configuration على com.mycompany.javaoop.Main.
فيما يلي كيفية معالجة الخطأ "No main classes found" في NetBeans:
- انقر بزر الماوس الأيمن على اسم المشروع (في هذه الحالة، "JavaOOP") في نافذة المشاريع.
- حدد خصائص Properties.
- انتقل إلى فئة Run.
- في حقل Main Class، أدخل الاسم المؤهل بالكامل لmain class، بما في ذلك اسم الـ package. على سبيل المثال، إذا كان اسم الفئة "Main" وكان موجودًا في الحزمة "com.mycompany.javaoop"، فيمكنك إدخال com.mycompany.javaoop.Main.
- انقر OK لحفظ التغييرات.
الآن حاول تشغيل البرنامج مرة أخرى. يجب أن يتم تنفيذه دون ظهور الخطأ "No main classes found".
إنشاء الفئات Classes والكائنات Objects:
ملفات منفصلة لفئات منفصلة: تحتاج كل فئة في Java إلى ملف خاص بها، يختلف عن الفئة الرئيسية Main. وهذا يعزز التنظيم والنمطية في التعليمات البرمجية الخاصة بك.
إنشاء Car Class:
- انقر بزر الماوس الأيمن على الحزمة التي تريد إنشاء الفئة الجديدة فيها (على سبيل المثال، com.mycompany.javaoop).
- حدد جديد New > فئة Java Class.
- قم بتسمية الفئة Car (كتابة الحرف الأول بالأحرف الكبيرة هو اصطلاح تسمية Java).
- انقر فوق Finish. يؤدي هذا إلى إنشاء ملف جديد باسم Car.java ضمن الحزمة الخاصة بك.
طريقة تسمية الـ Classes:
- يبدأ اسم الـclass دائمًا بحرف كبير.
- استخدم أسماء وصفية تعكس غرض الـclass(على سبيل المثال، Car, Person, Account).
- تجعل هذه الطريق الكود أكثر قابلية للقراءة وأسهل للفهم.
مثال 1:
class Car { String name; int maxSpeed; float price; int model; }
إعلان الـClass:
- فئة Car – يعلن هذا السطر عن فئة جديدة تسمى Car.
صفات:
- String name؛ - يحدد attribute تسمى name من النوع String. سيتم تخزين اسمcar object.
- int maxSpeed؛ - يحدد attribute تسمى maxSpeed من النوع int. سيتم تخزين السرعة القصوى لجسم السيارة بالكيلومترات في الساعة.
- float price - يحدد attribute تسمى price من النوع float. سيتم تخزين سعر كائن السيارة بتنسيق floating-point.
- int model؛ - يحدد attribute تسمى model من النوع int. سيتم تخزين سنة طراز جسم السيارة.
الآن سنقوم بإنشاء مثيلاتنا لفئة Car في الفئة Main، لأنها نقطة الدخول للبرنامج حيث يبدأ التنفيذ.
public class Main { public static void main(String args[]) { Car c1 = new Car(); c1.name = "Tesla"; c1.maxSpeed = 210; Car c2 = new Car(); c2.name = "Kia"; System.out.println(c1.name); System.out.println(c1.maxSpeed); System.out.println(c2.name); } }
لا يزال هذا الكود يستخدم Car class مع attributes مثل name, maxSpeed, price, وmodel.
Main Class:
- public static void main(String args[]): هذا السطر هو نقطة الدخول لبرنامجك.
- Car c1 = new Car();: يقوم هذا السطر بإنشاء instance جديد لفئة السيارة وتخصيصه للمتغير c1.
- c1.name = "Tesla"؛: يقوم هذا السطر بتعيين name attribute لكائن c1 على "Tesla".
- c1.maxSpeed = 210;: يقوم هذا السطر بتعيين maxSpeed attribute للكائن c1 على 210 كيلومترًا في الساعة.
- Car c2 = new Car();: يقوم هذا السطر بإنشاء instance جديد آخر لفئة السيارة وتخصيصه للمتغير c2.
- c2.name = "Kia"؛: يقوم هذا السطر بتعيين name attribute لكائن c2 على "Kia".
- System.out.println(c1.name);: يطبع هذا السطر قيمة name attribute للكائن c1 إلى شاشة المخرجات، والتي يجب أن تطبع "Tesla".
- System.out.println(c1.maxSpeed);: يطبع هذا الخط قيمة سمة maxSpeed للكائن c1 إلى شاشة المخرجات، والتي يجب أن تطبع "210".
- System.out.println(c2.name);: يطبع هذا السطر قيمة name attribute للكائن2c1 إلى شاشة المخرجات، والتي يجب أن تطبع "Kia".
مثال 2:
Car Class:
class Car { String name; int maxSpeed; float price; int model; void setName(String n) { name = n; } String getName() { return name; } }
- وله أربع سمات: name, maxSpeed, price, and model. لا تظهر هذه السمات في هذا المثال، لكن يمكنها تخزين معلومات حول اسم السيارة والسرعة القصوى والسعر وسنة الطراز.
- وله طريقتان أيضًا:
- setName(String n): تأخذ هذه الطريقة string argument n وتقوم بتعيين سمة اسم السيارة على تلك القيمة.
- getName(): تقوم هذه الطريقة بإرجاع القيمة الحالية لـname attribute السيارة كنص.
Main Class:
public class Main { public static void main(String args[]) { Car c1 = new Car(); c1.name = "Tesla"; c1.setName("KIA"); System.out.println(c1.getName()); } }
- يقوم بإنشاء instance جديد لفئة السيارة ويعينه للمتغير c1.
- يقوم بتعيين سمة اسم السيارة إلى "Tesla" باستخدام التعيين المباشر (c1.name = "Tesla").
- ثم يقوم بالكتابة فوق الاسم عن طريق استدعاء الأسلوب setName باستخدام الوسيطة "KIA". تقوم هذه الطريقة بتغيير سمة الاسم الداخلي للكائن c1.
- وأخيرًا، يقوم بطباعة اسم السيارة باستخدام طريقة getName، التي تسترد القيمة الحالية ("KIA") وتطبعها على شاشة المخرجات.
مثال 3:
Class Car:
class Car { String name; int maxSpeed; float price; int model; void setName(String n) { // Setters/ Mutators name = n; } String getName() { // Getters/ Accessors return name; } void setModel(int m) { if(m >= 2015) model = m; else System.out.println("Sorry, we do not accept this model"); } int getModel() { return model; } }
- Attributes: لا يزال يحتوي على name, maxSpeed, price, and model.
- الطرق Methods:
- Setters: تسمح هذه الطرق بتعيين قيم attribute محددة.
- setName(String n): يضبط سمة الاسم.
- setModel(int m): يضبط سمة النموذج مع التحقق من الصحة، ويقبل فقط الأعوام 2015 أو الأحدث.
- Getters: تسترد هذه الطرق قيم attribute محددة.
- getName (): إرجاع قيمة سمة الاسم.
- getModel (): إرجاع قيمة سمة النموذج.
Main Class:
public class Main { public static void main(String args[]) { Car c1 = new Car(); c1.name = "Tesla"; c1.setModel(2021); System.out.println(c1.getModel()); // 2021 } }
- Car c1 = new Car();: إنشاء كائن سيارة جديد.
- c1.name = "Tesla";: يضبط سمة الاسم لـ c1 باستخدام السلوك الافتراضي (لا حاجة إلى أداة ضبط setter).
- c1.setModel(2021);: تعيين سمة النموذج لـ c1 باستخدام أداة الضبط. نظرًا لأن عام 2021 صالح، فإنه يقوم بتحديث النموذج.
- System.out.println(c1.getModel());: طباعة سمة النموذج لـ c1، والتي يجب أن تنتج "2021".
معدّلات مستوى الوصول Access Level Modifiers في Java
(التحكم في Visibility وEncapsulation)
معدّلات مستوى الوصول هي بنيات أساسية في Java تحدد إمكانية رؤية أعضاء الـclass وإمكانية الوصول إليهم (fields, methods, constructors). إنهم يلعبون دورًا حاسمًا في تعزيز الـencapsulation، وهو مبدأ أساسي للبرمجة الموجهة للكائنات والذي يضمن حماية البيانات والنمطية.
نقاط أساسية:
- Visibility: تشير إلى ما إذا كان عضو الفصل مرئيًا بشكل مباشر وقابل للاستخدام من أجزاء أخرى من الكود.
- Accessibility: تشير إلى القدرة على الوصول مباشرة إلى عضو الفصل دون استخدام آليات خاصة.
أنواع معدّلات مستوى الوصول Access Level Modifiers:
- Public:
الأعضاء المعلنون للعامة يكونون مرئيين ويمكن الوصول إليهم من أي مكان في برنامجك.
استخدم هذا المعدل بحكمة للعناصر التي تحتاج إلى استخدامها على نطاق واسع، ولكن كن حذرًا حتى لا تكشف تفاصيل التنفيذ دون داع. - Private:
الأعضاء الذين تم الإعلان عن خصوصيتهم يكونون مرئيين فقط ويمكن الوصول إليهم داخل الـclass الذي تم تعريفهم فيه.
وهذا يعزز الـencapsulation عن طريق تقييد الوصول المباشر إلى البيانات الداخلية، وتشجيع استخدام الأساليب العامة للتحكم في التفاعلات مع الـclass. - Protected:
الأعضاء الذين تم الإعلان عن حمايتهم يكونون مرئيين ويمكن الوصول إليهم داخل الفئة التي تم تعريفهم فيها، وفئاتها الفرعية في نفس الحزمة، والفئات الفرعية في حزم مختلفة (ولكن ليس مباشرة من الفئات الأخرى في حزم مختلفة).
يعد هذا المعدل مفيدًا لإنشاء فئة أساسية (superclass) مع أعضاء محميين يمكن الوصول إليهم ومن المحتمل أن يتم تجاوزهم بواسطة فئاتها الفرعية، مما يعزز إعادة استخدام التعليمات البرمجية والـinheritance. - Default (Package-Private):
الأعضاء المعلنون دون أي معدّل وصول يكونون مرئيين ويمكن الوصول إليهم ضمن نفس الحزمة (الحزمة التي تحتوي على تعريف الفئة).
وهذا يوفر التوازن بين الرؤية والتحكم في الوصول داخل الحزمة.
مثال 4:
يوضح هذا الكود بشكل فعال المفاهيم الأساسية الموجهة للكائنات مثل التغليف ومعدلات الوصول وتفاعل الكائنات في Java. فهو يوفر أساسًا متينًا لبناء تطبيقات Java أكثر تعقيدًا وقوة.
Car Class:
public class Car { private String name; private int maxSpeed; private float price; private int model; public void setName(String n) { // Setters/ Mutators name = n; } public String getName() { // Getters/ Accessors return name; } public void setModel(int m) { if(m >= 2015) model = m; else System.out.println("Sorry, we do not accept this model"); } public int getModel() { return model; } }
- صفات:
- name: نص خاص يخزن اسم السيارة.
- maxSpeed: خاص لتخزين السرعة القصوى للسيارة.
– price: خاص تخزن سعر السيارة.
- model: خاص بتخزين سنة موديل السيارة. - الطرق Methods:
-setName(String n): أداة ضبط عامة لتعيين اسم السيارة.
- getName(): أداة عامة لاسترداد اسم السيارة.
– setModel(int m): أداة ضبط عامة لتعيين سنة طراز السيارة، بما في ذلك التحقق من الصحة لسنوات 2015 أو ما بعده.
- getModel(): أداة عامة لاسترداد سنة طراز السيارة.
Main Class:
public class Main { public static void main(String args[]) { Car c1 = new Car(); c1.setModel(2021); System.out.println(c1.getModel()); // 2021 } }
- Car c1 = new Car();: إنشاء كائن سيارة جديد باسم c1.
- c1.setModel(2021);: يحاول تعيين نموذج c1 على 2021. نجح هذا لأن 2021 سنة صالحة.
- System.out.println(c1.getModel());: طباعة نموذج c1 على شاشة المخرجات، مما يؤدي إلى طباعة "2021".
نقاط أساسية:
- Encapsulation: تقوم فئة السيارة بتغليف بياناتها باستخدام سمات خاصة، مما يضمن الوصول المتحكم فيه من خلال الطرق العامة.
- التحقق من صحة البيانات: تتضمن طريقة setModel التحقق من الصحة لمنع سنوات النموذج غير الصالحة، وتعزيز سلامة البيانات.
- تفاعل الكائن: توضح الفئة الرئيسية كيفية إنشاء كائن سيارة والتفاعل معه باستخدام أساليبه العامة.
- Setters and Getters: توفر هذه الأساليب طريقة يمكن التحكم فيها لتعديل واسترجاع سمات الكائن، مع الحفاظ على مبادئ التغليف.
إخفاء البيانات
إخفاء البيانات هو أسلوب تطوير برمجيات يستخدم خصيصًا في البرمجة الشيئية (OOP). يضمن إخفاء البيانات الوصول الحصري للبيانات إلى أعضاء الفصل ويحمي سلامة الكائن عن طريق منع التغييرات غير المقصودة أو المقصودة.
لماذا نستخدم هذه التقنية؟
لأنه يمكن أن يكون لدينا العديد من الكائنات التي ستتصل عبر الرسائل وتستخدم طرقًا أخرى.
Encapsulation
التغليف هو آلية لتغليف البيانات (المتغيرات) والتعليمات البرمجية التي تعمل على البيانات (الطرق) معًا كوحدة واحدة. في التغليف، سيتم إخفاء متغيرات الفئة عن الفئات الأخرى، ولا يمكن الوصول إليها إلا من خلال أساليب الفئة الحالية. لذلك، يُعرف أيضًا باسم إخفاء البيانات.
لتحقيق التغليف في جافا:
- قم بتعريف متغيرات الفئة على أنها خاصة.
- توفير أساليب الضبط العامة و getter لتعديل وعرض قيم المتغيرات.
الـConstructor وأنواعه (No-Arg, Parameterized, Default)
ما هو الـConstructor؟
- إنها طريقة خاصة في فئة Java يتم استدعاؤها تلقائيًا عند إنشاء كائن جديد من تلك الفئة.
- والغرض الأساسي منه هو تهيئة سمات الكائن (الحقول أو المتغيرات) للقيم الأولية المناسبة.
- فهو يضمن إنشاء الكائنات في حالة صالحة ومتسقة.
قواعد إنشاء Constructors:
- نفس اسم الفئة: يجب أن يكون اسم المنشئ مطابقًا تمامًا لاسم الفئة.
- لا يوجد نوع إرجاع صريح: ليس لدى المنشئين نوع إرجاع، ولا حتى باطل.
- لا يمكن أن تحتوي على Modifiers: لا يمكن الإعلان عن المنشئات على أنها مجردة أو ثابتة أو نهائية أو متزامنة.
أنواع الـConstructor:
- No-Arg Constructor: مُنشئ لا يقبل أي arguments.
public class Product { private String name; private String description; private float price; private int quantity; private float discount; public Product() { this.name = "No name"; this.description = "No description"; this.price = 0.0f; this.quantity = 0; this.discount = 0; } }
- Parameterized Constructor: مُنشئ يقبل arguments.
public Product(String n, String d, float p, int q, float dis) { this.name = n; this.description = d; this.price = p; this.quantity = q; this.discount = dis; }
- Default Constructor: المُنشئ الذي يتم إنشاؤه تلقائيًا بواسطة مترجم Java إذا لم يتم تعريفه بشكل صريح.
Constructor Chaining
تسلسل المُنشئ: عندما يستدعي المُنشئ مُنشئًا آخر من نفس الفئة، يُسمى ذلك تسلسل المُنشئ.
الكود:
Product class:
public class Product { private String name; private String description; private float price; private int quantity; private float discount; private String color; public Product() { this.name = "No name"; this.description = "No description"; this.price = 0.0f; this.quantity = 0; this.discount = 0; } public Product(String n, String d, float p, int q, float dis) { this.name = n; this.description = d; this.price = p; this.quantity = q; this.discount = dis; System.out.println("constructor: 5"); } public Product(String n, String d, float p, int q, float dis, String c) { this (n,d,p,q,dis); this.color = c; System.out.println("constructor: 6"); } public void display() { System.out.println("Name = " + name); System.out.println("description = " + description); System.out.println("Price = " + price); System.out.println("Quantity = " + quantity); System.out.println("Discount = " + Discount); } }
صفات:
- name: نص يمثل اسم المنتج.
- description: نص يصف المنتج.
- price: فاصلة عشرية تخزن سعر المنتج.
- quantity: عدد صحيح يمثل كمية المنتج.
- discount: فاصلة عشرية تمثل نسبة الخصم.
- color: سلسلة (اختيارية) للون المنتج (تمت إضافتها في المُنشئ الثالث).
Constructors:
- Product() (المنشئ الافتراضي): تهيئة جميع السمات بالقيم الافتراضية (“No name”, “No description”، وما إلى ذلك).
- المنتج (String n، String d، float p، int q، float dis): مُنشئ ذو معلمات يأخذ وسيطات للاسم والوصف والسعر والكمية والخصم. يطبع "constructor: 5" عند الاستدعاء.
- المنتج (String n، String d، float p، int q، float dis، String c): مُنشئ ذو معلمات يأخذ الوسائط لجميع السمات، بما في ذلك اللون. يستدعي المُنشئ الثاني داخليًا (“constructor: 5”) ثم يقوم بتعيين اللون. يطبع "constructor: 6" عند الاستدعاء.
- display(): طباعة معلومات المنتج بتنسيق سهل الاستخدام.
Main Class:
public class Main { public static void main(String args[]) { Product p1 = new Product("Camera", "Auto focus", 99, 10, 5, "red"); Product p2 = new Product(); p.display(); } }
يقوم بإنشاء كائنين:
- p1 باستخدام المُنشئ الثالث، مما يوفر قيمًا لجميع السمات.
- p2 باستخدام المُنشئ الافتراضي (لم يتم توفير وسائط).
يحاول استدعاء display() على المتغير المشار إليه بشكل غير صحيح p (يجب أن يكون p2) لعرض القيم الافتراضية.