C++

التعامل مع النصوص في السي بلاس بلاس

C++ التعامل مع النصوص

مفهوم النصوص في C++

في عالم البرمجة, نقول للنص "String" سواء كان يتألف من حرف واحد, كلمة, جملة أو نص كبير جداً.
و من هذا المنطلق نستنتج أن النص عبارة عن سلسلة من الأحرف ليس لها حجم محدد.


طريقة تعريف متغير نصي

في لغة C++ تم إنشاء النوع string خصيصاً لتخزين القيم النصية و هو موجود في المل std.
لذلك إن لم تقم بتضمين الحزمة std في برنامجك, يجب أن تعرّف النص بهذا الشكل std::string.
بالإضافة إلى ذلك, القيمة النصية يجب وضعها بين علامة التنصيص "" كما في المثال التالي.

مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// "Hello World!" و قيمته النص s هنا قمنا بتعريف متغير نصي إسمه
		string s = "Hello world!";

		// s هنا قمنا بطباعة قيمة المتغير
		cout << s;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Hello world!
	  

معلومة تقنية

النوع string في الأساس عبارة عن كلاس ( Class ) و ليس كباقي أنواع البيانات الأولية التي تعرفنا عليها سابقاً.
أيضاً, عندما تقوم بتعريف متغير نوعه string فأنت في الواقع تقوم بإنشاء كائن ( Object ) منه.

لا تقلق بتاتاً إن لم تفهم معنى كلاس و كائن الآن لأنك ستتعلم ذلك بتفصيل ممل لاحقاً في الدورة.

مفهوم دمج النصوص في C++

دمج النصوص ( Concatenation ) يقصد به وضع سلسلة من النصوص بجانب بعضها لعرضها كنص واحد. و هذا الأمر ستحتاجه في أي تطبيق.

فمثلاً, في البرامج أو المواقع التي تستخدمها تلاحظ أنه عند إنشاء حساب جديد يطلب منك أن تدخل إسمك على مرحلتين كالتالي:
- الإسم ( First Name ).
- إسم العائلة ( Last Name ).

بعد أن تنشئ حسابك تلاحظ أنه قام بعرض إسمك الكامل ( الإسم + إسم العائلة ).
عند وضع الإسمين بجانب بعضهما و كأنهما نص واحد يكون المبرمج فعلياً قد قام بدمجهما فقط و ليس إعادة كتابتهما من جديد.


في المثال التالي سنقوم بتعريف المتغير firstName لنضع فيه الإسم, و المتغير lastName لنضع فيه إسم العائلة, و المتغير fullName لنضع فيه الإسم و إسم العائلة.

مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// و وضعنا فيه نص يمثل الإسم firstName هنا قمنا بإنشاء المتغير
		string firstName = "Mhamad";

		// و وضعنا فيه نص يمثل إسم العائلة lastName هنا قمنا بإنشاء المتغير
		string lastName = "Harmush";

		// و أضفنا بينهما مسافة فارغة lastName و إسم العائلة الموجود في المتغير firstName و وضعنا فيه الإسم الموجود في المتغير fullName هنا قمنا بإنشاء المتغير
		string fullName = firstName + " " + lastName;

		// و يالتالي سيتم عرض الإسم الكامل الذي قمنا بدمجه و وضعه فيه fullName هنا عرضنا قيمة المتغير
		cout << fullName;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Mhamad Harmush
	  

كيف يتم تخزين النص في C++

لنفترض أننا قمنا بتعريف string إسمه s و أعطيناه قيمة.

مثال

		string s = "welcome to harmash.com";
	  

بما أننا نستخدم أحرف إنجليزية, سيتم تخزين نص المتغير s في الذاكرة حرفاً حرفاً و بالترتيب كما في هذه الصورة التالية.



مصطلحات تقنية

  • عدد أحرف النص يسمى Length.

  • رقم الخانة يسمى index.

  • أرقام الخانات يقال لها indices أو indexes.

  • إذا قمنا بأخذ جزء من النص فهذا الجزء يسمى substring.


أنت كمبرمج يمكنك إستغلال أرقام الخانات لتصل لمحتوى النص كالتالي.


معلومة تقنية

التعامل مع أحرف اللغات الأخرى كالأحرف العربية ليس بسلاسة التعامل مع الأحرف الإنجليزية و سبب ذلك أن كل حرف عربي يحتاج 2Byte حتى يتم تخزينه في الذاكرة, بينما الحرف الإنجليزي يحتاج 1Byte فقط.

كمثال عملي, كلمة Hello تعتبر تتألف من 5Byte لأنها تتألف من 5 أحرف إنجليزية.
بينما سلام تعتبر تتألف من 8Byte لأنها تتألف من 4 أحرف و كل حرف يحتاج إلى 2Byte.

الوصول لأحرف النص في C++

في حال أردت المرور على أحرف أي نص, سيتم إعتبار أن الخانات قد تم ترقيمها من اليسار إلى اليمين و إبتداءاً من الرقم 0 كما رأينا قبل قليل.


الآن إذا أردنا تحديد رقم الخانة التي نريد الوصول إليها سواء لعرض الحرف الموجود فيها أو لتبديله, فيجب أن نضع إسم المتغير الذي يحتوي على النص, ثم الرمز [] و فيه نحدد رقم الخانة التي نريد الوصول إليها.


في المثال التالي, قمنا بعرض أول خمسة أحرف موجودة في النص.

مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// s هنا قمنا بتعريف متغير نصي إسمه
		string s = "Where is my book?";

		// s هنا قمنا بطباعة أول 5 أحرف موجودة في المتغير
		cout << s[0] << s[1] << s[2] << s[3] << s[4];

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Where
	  

فكرة الثابت string::npos في C++

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

إنتبه لنقطة مهمة و هي أنك لست مضطر لحفظ قيمته, بل يكفي فقط أن تعرف كيف يتم إستخدامه في الكود.


في المثال التالي, قمنا بإنشاء متغير نوعه string و إسمه s و وضعنا فيه نص.
بعدها إستخدمنا الدالة find() للبحث عن كلمة في النص الموضوع في المتغير s.

لمعرفة ما إن تم إيجاد الكلمة المراد البحث عنها في النص أم لا, قمنا بمقارنة القيمة التي أرجعتها الدالة find() بعد أن إنتهت من البحث مع قيمة المتغير string::npos.
إذا كانت القيمة التي أرجعتها الدالة find() تساوي قيمة المتغير string::npos فهذا يعني أنه لم يتم إيجاد الكلمة المراد البحث عنها. إذا لم تكن تساويها فهذا يعني أنه تم إيجادها.

مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// s هنا قمنا بتعريف متغير نصي إسمه
		string s = "I'm learning c++ from harmash.com";

		// result و تخزين نتيجة البحث في المتغير "java" عن الكلمة s هنا قمنا بالبحث في النص الموجود في المتغير
		size_t result = s.find("java");

		// string::npos مع قيمة result هنا قمنا بمقارنة قيمة
		// إذا كانت متساويتين فهذا يعني أنه لم يتم إيجاد الكلمة التي بحثنا عنها 
		if (result == string::npos)
		{
		cout << "Word not found!";
		}
		// إن لم يكونا متساويتين, فهذا يعني أنه تم إيجاده الكلمة التي بحثنا عنها و سيتم طباعة رقم أول خانة تم عنده إيجاد الكلمة
		else
		{
		cout << "Word is found at index " << result;
		}

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Word not found!
	  

دوال الكلاس string في C++

الجدول التالي يحتوي على دوال الكلاس string الأكثر استخداماً.

إسم الدالة مع تعريفها
int length() ترجع رقم يمثل عدد أحرف النص الذي قام باستدعائها.
للدقة, فإن الرقم الذي ترجعه يمثل من كم Byte يتألف النص.
شاهد المثال »
int size() ترجع رقم يمثل عدد أحرف النص الذي قام باستدعائها تماماً مثل الدالة length().
شاهد المثال »
string substr(int pos=0, int len = npos) ترجع نص عبارة عن جزء من النص (Substring) الذي قام باستدعائها.
مكان الباراميتر pos نمرر index الحرف الذي نريد البدء بنسخ النص من عنده.
مكان الباراميتر len يمكنك تمرير رقم يمثل كم حرف تريد أن تنسخ من عند index الباراميتر pos إن لم ترد نسخ كل النص الموجود.
شاهد المثال »
int find(string str) تبحث في النص الذي قام بإستدعائها عن أول index يوجد إبتداءاَ من عنده نفس النص الذي نمرره لها مكان الباراميتر str و ترجعه.
شاهد المثال »
int rfind(string str) تبحث في النص الذي قام بإستدعائها عن آخر index يوجد إبتداءاَ من عنده نفس النص الذي نمرره لها مكان الباراميتر str و ترجعه.
شاهد المثال »
string replace(int pos, int len, string replacement) تستخدم لتعديل جزء محدد من النص الذي قام باستدعائها.
مكان الباراميتر pos نمرر index الحرف الذي نريد البدء بتغيير النص من عنده.
مكان الباراميتر len نمرر رقم يمثل كم حرف تريد أن تبدل إعتباراً من عند index الباراميتر pos.
مكان الباراميتر replacement نمرر النص الذي سيتم إضافته مكان الجزئية التي سيتم حذفها.
شاهد المثال »
void swap(string str) تبدل محتوى النص الذي قام بإستدعائها بمحتوى النص الذي نمرره مكان الباراميتر str.
كما أنها تبدل محتوى المتغير الذي نمرره مكان الباراميتر str بمحتوى النص الذي قام باستدعائها.
شاهد المثال »
string append (const string str) تضيف قيمة النص الذي نمرره مكان الباراميتر str في آخر النص الذي قام باستدعائها و ترجع النص الناتج عند دمجهمها معاً.
شاهد المثال »
bool empty() ترجع true إذا كان النص الذي قام باستدعائها غير فارغ (أي يوجد فيه حرف على الأقل).
و ترجع false إن لم يكن كذلك.
شاهد المثال »
void clear() تمسح كل الأحرف الموجودة في النص الذي قام باستدعائها.
شاهد المثال »
int compare (const string str) تقارن حجم النص الذي قام باستدعائها مع حجم النص الذي نمرره لها مكان الباراميتر str.
ترجع 0 في حال كان حجمهم متساوي.
ترجع 1 في حال كان عدد أحرف النص الذي قام باستدعائها أكبر.
ترجع -1 في حال كان عدد أحرف النص الذي قام باستدعائها أصغر.
شاهد المثال »
string insert (int pos, const string str) تضيف قيمة النص الذي نمرره مكان الباراميتر str في النص الذي قام باستدعائها عند Index الخانة التي نحددها مكان الباراميتر pos و ترجع النص الناتج عند دمجهمها معاً.
شاهد المثال »

العوامل التي تستخدم لمقارنة النصوص في C++

الجدول التالي يحتوي على العوامل التي يمكن استخدامها لمقارنة أحجام النصوص.

إسم العامل رمزه مثال شرح الكود
Equal to == (a == b) هل قيمة a تساوي قيمة b ؟
إذا كان الجواب نعم فإنها ترجع true
Not equal to != (a != b) هل قيمة a لا تساوي قيمة b ؟
إذا كان الجواب نعم فإنها ترجع true
Greater than > (a > b) هل قيمة a أكبر من قيمة b ؟
إذا كان الجواب نعم فإنها ترجع true
Less than < (a < b) هل قيمة a أصغر من قيمة b ؟
إذا كان الجواب نعم فإنها ترجع true
Greater than
or Equal to
>= (a >= b) هل قيمة a أكبر أو تساوي قيمة b ؟
إذا كان الجواب نعم فإنها ترجع true
Less than
or Equal to
<= (a <= b) هل قيمة a أصغر أو تساوي قيمة b ؟
إذا كان الجواب نعم فإنها ترجع true

مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// s2 و متغير نصي إسمه s1 هنا قمنا بتعريف متغير نصي إسمه
		string s1 = "harmash";
		string s2 = "google";

		// سيتم تنفيذ أمر الطباعة s2 يساوي عدد أحرف s1 إذا كان عدد أحرف
		if (s1 == s2)
		cout << "s1 == s2" << endl;

		// سيتم تنفيذ أمر الطباعة s2 أكبر من عدد أحرف s1 إذا كان عدد أحرف
		if (s1 > s2)
		cout << "s1 > s2" << endl;

		// سيتم تنفيذ أمر الطباعة s2 أكبر أو يساوي عدد أحرف s1 إذا كان عدد أحرف
		if (s1 >= s2)
		cout << "s1 >= s2" << endl;

		// سيتم تنفيذ أمر الطباعة s2 أصغر من عدد أحرف s1 إذا كان عدد أحرف
		if (s1 < s2)
		cout << "s1 < s2" << endl;

		// سيتم تنفيذ أمر الطباعة s2 أصغر أو يساوي عدد أحرف s1 إذا كان عدد أحرف
		if (s1 <= s2)
		cout << "s1 <= s2" << endl;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		s1 > s2
		s1 >= s2
	  

C++ الدالة length()

تعريفها

ترجع رقم يمثل عدد أحرف النص الذي قام باستدعائها.
للدقة, فإن الرقم الذي ترجعه يمثل من كم Byte يتألف النص.



بناؤها

	  size_t length() const
	
	  size_t length() const noexcept
	


باراميترات

لا تقبل أي باراميتر.



قيمة الإرجاع

ترجع رقم يمثل من كم Byte يتألف النص الذي قام باستدعائها.



المثل الأول

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "Welcome to harmash.com";

		// str يتألف نص المتغير Byte هنا قمنا بطباعة من كم
		// 1Byte الجواب سيكون 22 لأن كل حرف إنجليزي يتم تخزينه في
		cout << "Length of str is " << str.length() << " bytes";

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		Length of str is 22 bytes
	  


المثل الثاني

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "محمد";

		// str يتألف نص المتغير Byte هنا قمنا بطباعة من كم
		// 2Byte الجواب سيكون 8 لأن كل حرف عربي يتم تخزينه في
		cout << "Length of str is " << str.length() << " bytes";

		return 0;
		}

	  

سنحصل على النتيجة التالية عند التشغيل.

		Length of str is 8 bytes
	  

C++ الدالة size()

تعريفها

ترجع رقم يمثل عدد أحرف النص الذي قام باستدعائها.
للدقة, فإن الرقم الذي ترجعه يمثل من كم Byte يتألف النص.



بناؤها

	  size_t size() const
	
	  size_t size() const noexcept
	


باراميترات

لا تقبل أي باراميتر.



قيمة الإرجاع

ترجع رقم يمثل من كم Byte يتألف النص الذي قام باستدعائها.



المثل الأول

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "Welcome to harmash.com";

		// str يتألف نص المتغير Byte هنا قمنا بطباعة من كم
		// 1Byte الجواب سيكون 22 لأن كل حرف إنجليزي يتم تخزينه في
		cout << "Size of str is " << str.size();

		return 0;
		}

	  

سنحصل على النتيجة التالية عند التشغيل.

		Size of str is 22
	  


المثل الثاني

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "محمد";

		// str يتألف نص المتغير Byte هنا قمنا بطباعة من كم
		// 2Byte الجواب سيكون 8 لأن كل حرف عربي يتم تخزينه في
		cout << "Size of str is " << str.size();

		return 0;
		}

	  

سنحصل على النتيجة التالية عند التشغيل.

		Size of str is 8
	  

C++ الدالة substr()

تعريفها

ترجع نص عبارة جزء من النص (Substring) الذي قام باستدعائها.

عند إستدعاء هذه الدالة عندنا خيارين:

  • تحديد من أين تبدأ.

  • تحديد من أين تبدأ و كم حرف نريد أن نأخذ بالضبط.

إذا لم نقم بتحديد كم حرف نريد أن نأخذ من النص, سترجع كل نص الموجود إبتداءاً من رقم الـ index الذي مررناه مكان الباراميتر pos إلى آخر index موجود في النص الذي قام باستدعائها.



بناؤها

هناك شكلين من هذه الدالة. أي يمكن إستدعاءها بطريقتين مختلفتين.

	  string substr (size_t pos = 0, size_t len = npos) const
	


باراميترات

  • مكان الباراميتر pos نمرر index الحرف الذي نريد البدء بنسخ النص من عنده.

  • مكان الباراميتر len يمكنك تمرير رقم يمثل كم حرف تريد أن تنسخ من عند index الباراميتر pos إن لم ترد نسخ كل النص الموجود.



قيمة الإرجاع

ترجع String عبارة عن substr من الـ String الذي قام باستدعائها.



أخطاء محتملة

std::out_of_range: في حال قمت بتمرير رقم index غير موجود في النص مكان الباراميتر pos سيظهر لك الخطأ التالي عند تشغيل البرنامج.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "welcome to harmash.com, best site for learning";

		cout << str.substr(11) << endl;       // رقم 11 index كل النص الموجود إبتداءاً من الحرف الموجود على الـ substr() هنا سترجع الدالة
		cout << str.substr(11, 7) << endl;    // رقم 11 index أول 7 أحرف موجودة في النص إبتداءاً من الـ substr() هنا سترجع الدالة

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		harmash.com, best site for learning
		harmash
	  

C++ الدالة find()

تعريفها

تبحث في النص الذي قام بإستدعائها عن أول index يوجد إبتداءاَ من عنده نفس النص أو الحرف الذي نمرره لها مكان الباراميتر str و ترجعه.
بمعنى آخر, نعطيها أي نص أو حرف, فترجع لنا رقم أول خانة وجد عندها.

إفتراضياً هذه الدالة تبدأ عملية البحث من الـ index رقم 0 إلى آخر index موجود في النص الذي قام باستدعائها.
و في حال لم يتم العثور على النص المراد البحث عنه ترجع قيمة الثابت string::npos.



بناؤها

	  size_t find (const string& str, size_t pos = 0) const
	
	  size_t find (const string& str, size_t pos = 0) const noexcept
	


باراميترات

  • مكان الباراميتر str نمرر النص الذي ستبحث عنه الدالة في النص الذي قام بإستدعائها.

  • الباراميتر pos هو باراميتر إختياري, نضع مكانه رقم في حال أردنا جعل الدالة تبدأ عملية البحث من رقم index محدد و ليس من الـ index رقم 0.



قيمة الإرجاع

ترجع رقم يمثل أول index وجد عنده النص أو الحرف الذي مررناه لها, و ترجع قيمة الثابت string::npos في حال لم يتم إيجاد تطابق.



المثال الأول

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "I have one cat. My cat like milk.";

		// و بالتالي سيتم طباعة 11 'c' يوجد عندها النص str هنا قمنا بطباعة رقم أول خانة في المتغير
		cout << str.find("c") << endl;

		// و بالتالي سيتم طباعة 11 'cat' يوجد عندها النص str هنا قمنا بطباعة رقم أول خانة في المتغير
		cout << str.find("cat") << endl;

		// مع الإشارة إلى أننا بدأنا عملية البحث من الخانة رقم 14. و بالتالي سيتم طباعة 19 'cat' يوجد عندها النص str هنا قمنا بطباعة رقم أول خانة في المتغير
		cout << str.find("cat", 14) << endl;

		// string::npos و بما أنه لا يوجد هذه الكلمة, سيتم طباعة قيمة الثابت .'dog' يوجد عندها النص str هنا قمنا بطباعة رقم أول خانة في المتغير
		cout << str.find("dog") << endl;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		11
		11
		19
		18446744073709551615
	  


في البرامج الفعلية, قد يكون أسلوب التشييك المستخدم لمعرفة ما إن تم إيجاد القيمة أم لا هو الأنسب لك.

المثال الثاني

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// keyword و الكلمة التي سنبحث عنها في المتغير str هنا قمنا بتعريف النص الأساسي في المتغير
		string str = "I have one cat. My cat like milk.";
		string keyword = "cat";

		// result و تخزين نتيجة البحث في المتغير keyword الموجودة في المتغير "cat" عن الكلمة str هنا قمنا بالبحث في النص الموجود في المتغير
		size_t result = str.find(keyword);

		// string::npos مع قيمة result هنا قمنا بمقارنة قيمة
		// إن لم تكونا متساويتين, فهذا يعني أنه تم إيجاد الكلمة التي بحثنا عنها و سيتم طباعة رقم أول خانة تم عنده إيجاد الكلمة
		if (result != string::npos)
		{
		cout << "'" << keyword << "' is found at index " << result;
		}
		// إذا كانت متساويتين فهذا يعني أنه لم يتم إيجاد الكلمة التي بحثنا عنها و سيتم طباعة أنه لم يتم العثور عليها
		else
		{
		cout << "'" << keyword << "' is not found!";
		}

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		'cat' is found at index 11
	  

C++ الدالة rfind()

تعريفها

تبحث في النص الذي قام بإستدعائها عن آخر index يوجد إبتداءاَ من عنده نفس النص أو الحرف الذي نمرره لها مكان الباراميتر str و ترجعه.
بمعنى آخر, نعطيها أي نص أو حرف, فترجع لنا رقم آخر خانة وجد عندها.

إفتراضياً هذه الدالة تبدأ عملية البحث من الـ index الأخير في النص الذي قام باستدعائها إلى الـ index رقم 0.
و في حال لم يتم العثور على النص المراد البحث عنه ترجع قيمة الثابت string::npos.



بناؤها

	  size_t rfind (const string& str, size_t pos = 0) const
	
	  size_t rfind (const string& str, size_t pos = 0) const noexcept
	


باراميترات

  • مكان الباراميتر str نمرر النص الذي ستبحث عنه الدالة في النص الذي قام بإستدعائها.

  • الباراميتر pos هو باراميتر إختياري, نضع مكانه رقم في حال أردنا جعل الدالة توقف عملية البحث قبل الوصول لرقم index محدد.



قيمة الإرجاع

ترجع رقم يمثل آخر index وجد عنده النص أو الحرف الذي مررناه لها, و ترجع قيمة الثابت string::npos في حال لم يتم إيجاد تطابق.



المثال الأول

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "I have one cat. My cat like milk.";

		// و بالتالي سيتم طباعة 19 'c' يوجد عندها النص str هنا قمنا بطباعة رقم آخر خانة في المتغير
		cout << str.rfind("c") << endl;

		// و بالتالي سيتم طباعة 19 'cat' يوجد عندها النص str هنا قمنا بطباعة رقم آخر خانة في المتغير
		cout << str.rfind("cat") << endl;

		// مع الإشارة إلى أننا أوقفنا عملية البحث عند الخانة رقم 14. و بالتالي سيتم طباعة 11 'cat' يوجد عندها النص str هنا قمنا بطباعة رقم آخر خانة في المتغير
		cout << str.rfind("cat", 14) << endl;

		// string::npos و بما أنه لا يوجد هذه الكلمة, سيتم طباعة قيمة الثابت .'dog' يوجد عندها النص str هنا قمنا بطباعة رقم آخر خانة في المتغير
		cout << str.rfind("dog") << endl;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		19
		19
		11
		18446744073709551615
	  


في البرامج الفعلية, قد يكون أسلوب التشييك المستخدم لمعرفة ما إن تم إيجاد القيمة أم لا هو الأنسب لك.

المثال الثاني

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// keyword و الكلمة التي سنبحث عنها في المتغير str هنا قمنا بتعريف النص الأساسي في المتغير
		string str = "I have one cat. My cat like milk.";
		string keyword = "cat";

		// result و تخزين نتيجة البحث في المتغير keyword الموجودة في المتغير "cat" عن الكلمة str هنا قمنا بالبحث في النص الموجود في المتغير
		size_t result = str.rfind(keyword);

		// string::npos مع قيمة result هنا قمنا بمقارنة قيمة
		// إن لم تكونا متساويتين, فهذا يعني أنه تم إيجاد الكلمة التي بحثنا عنها و سيتم طباعة رقم آخر خانة تم عنده إيجاد الكلمة
		if (result != string::npos)
		{
		cout << "'" << keyword << "' is found at index " << result;
		}
		// إذا كانت متساويتين فهذا يعني أنه لم يتم إيجاد الكلمة التي بحثنا عنها و سيتم طباعة أنه لم يتم العثور عليها
		else
		{
		cout << "'" << keyword << "' is not found!";
		}

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		'cat' is found at index 19
	  

C++ الدالة replace()

تعريفها

تستخدم لتعديل جزء محدد من النص الذي قام باستدعائها.



بناؤها

	  string& replace (size_t pos, size_t len, const string& replacement);
	


باراميترات

  • مكان الباراميتر pos نمرر index الحرف الذي نريد البدء بتغيير النص من عنده.

  • مكان الباراميتر len نمرر رقم يمثل كم حرف تريد أن تبدل إعتباراً من عند index الباراميتر pos.

  • مكان الباراميتر replacement نمرر النص الذي سيتم إضافته مكان الجزئية التي سيتم حذفها.



قيمة الإرجاع

ترجع النص الناتج من عملية التبديل.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "welcome to youtube.com";

		// "harmash" رقم 11 بالنص index إبتداءاً من الحرف الموجود في الـ str هنا قمنا بتبديل أول 7 أحرف موجودة في النص
		str.replace(11, 7, "harmash");

		// بعد أن قمنا بإجراء تعديل عليها str هنا قمنا بطباعة قيمة المتغير
		cout << str;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		welcome to harmash.com
	  

C++ الدالة swap()

تعريفها

تبدل محتوى النص الذي قام بإستدعائها بمحتوى النص الذي نمرره مكان الباراميتر str.
كما أنها تبدل محتوى المتغير الذي نمرره مكان الباراميتر str بمحتوى النص الذي قام باستدعائها.



بناؤها

	  void swap(string& str)
	


باراميترات

  • مكان الباراميتر str نمرر المتغير النص الذي سيتم تبادل المحتوى معه.



قيمة الإرجاع

لا ترجع قيمة.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// s2 و متغير نصي آخر إسمه s1 هنا قمنا بتعريف متغير نصي إسمه
		string s1 = "harmash";
		string s2 = "youtube";

		// s2 و s1 هنا قمنا بتبديل قيمة المتغيرين
		s1.swap(s2);

		// بعد أن قمنا بتديل قيمهما s2 و s1 هنا قمنا بطباعة قيمة
		cout << "s1 = " << s1 << endl;
		cout << "s2 = " << s2 << endl;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		s1 = youtube
		s2 = harmash
	  

C++ الدالة append()

تعريفها

تضيف قيمة النص الذي نمرره مكان الباراميتر str في آخر النص الذي قام باستدعائها و ترجع النص الناتج عند دمجهمها معاً.



بناؤها

	  string& append (const string& str)
	


باراميترات

  • مكان الباراميتر s نمرر النص الذي سيضاف في آخر النص الذي قام بإستدعاء الدالة.



قيمة الإرجاع

ترجع نص جديد يحتوي على قيمة النص الذي قام باستدعاء الدالة مضافاً إليه قيمة النص الذي نمرره للدالة.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// هنا قمنا بتعريف متغيرين يحتويان على نصوص
		string s1 = "This part is from s1. ";
		string s2 = "This part is from s2";

		// s1 في آخر المتغير s2 قيمة المتغير append() ستضيف الدالة 
		// s3 بعدها, الجواب الذي سيرجع سيتم تخزينه في
		string s3 = s1.append(s2);

		// s3 هنا قمنا بطباعة قيمة المتغير
		cout << s3;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		This part is from s1. This part is from s2
	  


لعلك تتسائل حتماً ما الفرق بين استخدام الدالة append() و بأن أن نكتب التالي.

	  string s3 = s1 + s2;
	

الفرق فقط أن الدالة append() تتنفذ بشكل أسرع.

C++ الدالة empty()

تعريفها

تستخدم لمعرفة ما إن كان النص الذي قام باستدعائها, فارغاً أم لا.



بناؤها

	  bool empty() const
	
	  bool empty() const noexcept
	


باراميترات

لا تقبل أي باراميتر.



قيمة الإرجاع

ترجع 1 - و الذي يعني true - إذا كان النص الذي قام باستدعائها غير فارغ (أي يوجد فيه حرف على الأقل).
و ترجع 0 - و الذي يعني false - إن لم يكن كذلك.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// لا يحتوي على أي نص s هنا قمنا بتعريف متغير إسمه
		string s;

		// فارغ s يحتوي على قيمة أم لا. و سيتم طباعة 0 لأن s هنا قمنا بطباعة ما إن كان المتغير
		cout << s.empty() << endl;

		// s هنا قمنا بوضع نص في المتغير
		s = "I like cats";

		// غير فارغ s يحتوي على قيمة أم لا. و سيتم طباعة 1 لأن s هنا قمنا بطباعة ما إن كان المتغير
		cout << s.empty();

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		0
		1
	  

C++ الدالة clear()

تعريفها

تمسح كل الأحرف الموجودة في النص الذي قام باستدعائها.



بناؤها

	  bool clear() const
	
	  bool clear() const noexcept
	


باراميترات

لا تقبل أي باراميتر.



قيمة الإرجاع

لا ترجع قيمة.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// يحتوي على نص s هنا قمنا بتعريف متغير إسمه
		string s = "I like cats";

		// s هنا قمنا بمسح النص الموجود في المتغير
		s.clear();

		// و بالطبع لن يتم طباعة أي شيء لأنه فارغ s هنا قمنا بطباعة محتوى المتغير
		cout << s;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.


	  

C++ الدالة compare()

تعريفها

تقارن حجم النص الذي قام باستدعائها مع حجم النص الذي نمرره لها مكان الباراميتر str.
و ترجع رقم يمثل فرق الحجم بينهما.



بناؤها

هناك شكلين من هذه الدالة. أي يمكن إستدعاءها بطريقتين مختلفتين.

	  int compare (const string& str) const
	
	  int compare (const string& str) const noexcept
	
	  int compare (const string& str) const noexcept
	


باراميترات

مكان الباراميتر str نمرر النص الذي ستتم مقارنته مع النص الذي قام باستدعاء الدالة.



قيمة الإرجاع

  • ترجع 0 في حال كان حجمهم متساوي.

  • ترجع 1 في حال كان عدد أحرف النص الذي قام باستدعائها أكبر.

  • ترجع -1 في حال كان عدد أحرف النص الذي قام باستدعائها أصغر.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		string s1 = "I like cats.";
		string s2 = "I'm learning c++ from harmash.com";
		string s3 = s1;

		cout << s1.compare(s2) << endl;    // s2 أقل من عدد أحرف s1 هنا سيتم طباعة 1- لأن عدد أحرف
		cout << s2.compare(s1) << endl;    // s1 أكبر من عدد أحرف s2 هنا سيتم طباعة 1 لأن عدد أحرف
		cout << s1.compare(s3) << endl;    // s3 يساوي عدد أحرف s1 هنا سيتم طباعة 0 لأن عدد أحرف

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		-1
		1
		0
	  

C++ الدالة insert()

تعريفها

تضيف قيمة النص الذي نمرره مكان الباراميتر str في النص الذي قام باستدعائها عند Index الخانة التي نحددها مكان الباراميتر pos و ترجع النص الناتج عند دمجهمها معاً.



بناؤها

	  string& insert (size_t pos, const string& str)
	


باراميترات

  • مكان الباراميتر str نمرر النص الذي سيضاف في النص الذي قام بإستدعاء الدالة.

  • مكان الباراميتر pos نمرر رقم الـ index الذي نريد إضافة النص عنده.



قيمة الإرجاع

ترجع نص جديد يحتوي على قيمة النص الذي قام باستدعاء الدالة مضافاً إليه قيمة النص الذي نمرره للدالة.



مثال

Main.cpp
		#include <iostream>

		using namespace std;

		int main()
		{
		// str هنا قمنا بتعريف متغير نصي إسمه
		string str = "welcome harmash.com";

		// str رقم 8 في النص index عند الـ "to " هنا قمنا بإضافة الكلمة
		str.insert(8, "to ");

		// بعد أن قمنا بإجراء تعديل عليها str هنا قمنا بطباعة قيمة المتغير
		cout << str;

		return 0;
		}
	  

سنحصل على النتيجة التالية عند التشغيل.

		welcome to harmash.com
	  

C++ التعامل مع المصفوفات

مفهوم المصفوفات في C++

المصفوفة ( Array ) عبارة عن متغير واحد يتألف من عدة عناصر ( Elements ) من نفس النوع.
و كل عنصر في المصفوفة يمكن تخزين قيمة واحدة فيه.

عناصر المصفوفة تتميز عن بعضها من خلال رقم محدد يعطى لكل عنصر يسمى index.
أول عنصر في المصفوفة دائماً يكون رقمه 0.

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


فوائد المصفوفات

  • تقليل عدد المتغيرات المتشابهة, فمثلاً إذا كنا نريد تعريف 10 متغيرات نوعهم int, نقوم بتعريف مصفوفة واحدة تتألف من 10 عناصر.

  • التعامل مع الكود يصبح أسهل, لأنك إذا قمت بتخزين المعلومات داخل مصفوفة, تستطيع تعديلهم, مقارنتهم أو جلبهم كلهم دفعة واحدة بكود صغير جداً باستخدام حلقة.

  • تستطيع الوصول لأي عنصر من خلال رقم الـ index الخاص به.

تعريف مصفوفة في C++

هناك ثلاث طرق يمكنك اتباعها لتعريف مصفوفة (Declare Array) جديدة سنتعرف عليها تباعاً.

// الأسلوب التالي يستخدم لتعريف مصفوفة مع تحديد عدد عناصرها
datatype[size] arrayName;

// الأسلوب التالي يستخدم لتعريف مصفوفة مع تحديد قيمها الأولية
datatype[] arrayName = {value1, value2, ..};

// الأسلوب التالي يستخدم لتعريف مصفوفة مع تحديد عدد عناصرها و قيمة بعض عناصرها
datatype[size] arrayName = {value1, value2, ..};


  • datatype: هو نوع القيم التي يمكن تخزينها في عناصر المصفوفة.

  • size: هو عدد عناصر المصفوفة.

  • arrayName: هو إسم المصفوفة.

  • []: هذا الرمز يمثل من كم بعد تتألف المصفوفة.



أمثلة حول طريقة تعريف مصفوفة أحادية ( One Dimensional Array ).

أمثلة

	  // و تتألف من 5 عناصر int نوعها ,arr هنا قمنا بتعريف مصفوفة ذات بعد واحد إسمها
	  int arr[5];

	  // و وضعنا فيها 6 عناصر, و هذا يعني أن عدد عناصرها أصبح 6 لأننا لم نحدد عدد عناصرها int نوعها ,arr هنا قمنا بتعريف مصفوفة ذات بعد واحد إسمها
	  int arr[] = {1, 2, 3, 4, 5, 6};

	  // و تتألف من 5 عناصر, و قمنا بوضع قيم أولية في أول 3 عناصر فيها int نوعها ,arr هنا قمنا بتعريف مصفوفة ذات بعد واحد إسمها
	  int arr[5] = {1, 2, 3};
	


أمثلة حول طريقة تعريف مصفوفة ثنائية ( Two Dimensional Array ).

أمثلة

	  // و تتألف من 3 × 4 عناصر int نوعها ,arr هنا قمنا بتعريف مصفوفة ذات بعدين إسمها
	  int arr[4][3];

	  // وضعنا فيها مصفوفتين تتألف كل واحدة منهما من 3 عناصر, هذا يعني أن المصفوفة تتألف من 2 × 3 عناصر int نوعها ,arr هنا قمنا بتعريف مصفوفة ذات بعدين إسمها
	  int arr[][] = {
	  {1, 2, 3},
	  {4, 5, 6}
	  };

	  // و تتألف من 3 × 4 عناصر, وقمنا بوضع 6 قيم أولية فيها int نوعها ,arr هنا قمنا بتعريف مصفوفة ذات بعدين إسمها
	  int arr[4][3] = {
	  {1, 2, 3},
	  {4, 5, 6}
	  };
	

الوصول لعناصر المصفوفة في C++

لنفترض الآن أننا قمنا بتعريف مصفوفة نوعها int, إسمها a, و تتألف من 5 عناصر.

	int a[] = { 10, 20, 30, 40, 50 }; 
  

يمكنك تصور شكل المصفوفة a في الذاكرة كالتالي.

بما أن المصفوفة تتألف من 5 عناصر, تم إعطاء العناصر أرقام indexes بالترتيب من 0 إلى 4.

إذاً هنا أصبح عدد عناصر المصفوفة يساوي 5 و هو ثابت لا يمكن تغييره لاحقاً في الكود.
و للوصول لقيمة أي عنصر نستخدم index العنصر الذي تم إعطاؤه له.


في المثال التالي, قمنا بتعريف مصفوفة, ثم غيرنا قيمة العنصر الأول فيها, و من ثم عرضنا قيمة جميع العناصر.

مثال

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بتعريف مصفوفة تتألف من 5 عناصر
	  int arr[] = {10, 20, 30, 40, 50};

	  // هنا قمنا بتغيير قيمة العنصر الأول و العنصر الأخير في المصفوفة
	  arr[0] = 1;
	  arr[4] = 5;

	  // هنا قمنا بعرض قيم جميع عناصر المصفوفة
	  cout << "arr[0] = " << arr[0] << endl;
	  cout << "arr[1] = " << arr[1] << endl;
	  cout << "arr[2] = " << arr[2] << endl;
	  cout << "arr[3] = " << arr[3] << endl;
	  cout << "arr[4] = " << arr[4] << endl;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل

	  arr[0] = 1
	  arr[1] = 20
	  arr[2] = 30
	  arr[3] = 40
	  arr[4] = 5
	

طريقة وضع قيم أولية لعناصر المصفوفة في C++

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

لذا في حال أردت إنشاء مصفوفة جديدة مع حذف أي قيم إفتراضية قد تكون موجودة فيها, يجب أن تقوم بتمرير القيمة الإفتراضية التي تريد وضعها لعناصر المصفوفة لحظة إنشائها.


في المثال التالي, قمنا بتعريف مصفوفة تتألف من 5 عناصر و لم نعطها قيم أولية, ثم قمنا بعرض القيم الإفتراضية الموجودة فيها.

المثال الأول

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بتعريف مصفوفة تتألف من 5 عناصر
	  int arr[5];

	  // هنا قمنا بعرض القيم الإفتراضية الموجودة في عناصر المصفوفة
	  cout << "arr[0] = " << arr[0] << endl;
	  cout << "arr[1] = " << arr[1] << endl;
	  cout << "arr[2] = " << arr[2] << endl;
	  cout << "arr[3] = " << arr[3] << endl;
	  cout << "arr[4] = " << arr[4] << endl;

	  return 0;
	  }
	

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

	  arr[0] = 8
	  arr[1] = 0
	  arr[2] = 42
	  arr[3] = 0
	  arr[4] = 15275776	
	


في المثال التالي قمنا بوضع القيمة 0 كقيمة أولية لجميع عناصر المصفوفة, ثم قمنا بعرض قيمها.

المثال الثاني

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بتعريف مصفوفة تتألف من 5 عناصر مع تحديد أن القيمة الإفتراضية في جميع عناصرها هي 0
	  int arr[5] = {0};

	  // هنا قمنا بعرض القيم الإفتراضية الموجودة في عناصر المصفوفة
	  cout << "arr[0] = " << arr[0] << endl;
	  cout << "arr[1] = " << arr[1] << endl;
	  cout << "arr[2] = " << arr[2] << endl;
	  cout << "arr[3] = " << arr[3] << endl;
	  cout << "arr[4] = " << arr[4] << endl;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند تشغيل البرنامج.

	  arr[0] = 0
	  arr[1] = 0
	  arr[2] = 0
	  arr[3] = 0
	  arr[4] = 0	
	

طريقة معرفة عدد عناصر المصفوفة في C++

إذا أردت معرفة عدد عناصر أي مصفوفة, يمكنك الحصول عليه من خلال قسمة حجم المصفوفة, على نوع العناصر المخزنة فيها.


مثال

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بتعريف مصفوفة تتألف من 5 عناصر مع تحديد أن القيمة الإفتراضية في جميع عناصرها هي 0
	  int arr[] = {1, 2, 3, 4, 5};

	  // n على حجم نوع أول عنصر فيها, و من ثم قمنا بتخزين الناتج في المتغير arr هنا قمنا بقسمة عدد عناصر المصفوفة
	  int n = sizeof(arr) / sizeof(arr[0]);

	  // n الذي قمنا بتخزينه في المتغير arr هنا قمنا بطباعة عدد عناصر المصفوفة
	  cout << "Number of elements in the array is: " << n;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند تشغيل البرنامج.

	  Number of elements in the array is: 5
	

و هذه بعض الطرق الأخرى التي قد تجد أنها تستخدم لمعرفة أحجام المصفوفات.

	// يمكنك الحصول على عدد عناصر المصفوفة مهما كان نوعها من خلال الأسلوب التالي
	sizeof(arrayName) / sizeof(arrayElement[0])

	// يمكنك أن تستخدم الأسلوب التالي int إذا كان نوع المصفوفة هو
	sizeof(arrayName) / sizeof(int)

	// يمكنك أن تستخدم الأسلوب التالي string إذا كان نوع المصفوفة هو
	sizeof(arrayName) / sizeof(string)
  



ملاحظة

الأسلوب الذي استخدمناه لمعرفة عدد عناصر المصفوفة يمكن تطبيقه على أي نوع بيانات آخر و لكن لا يمكن استخدامه مع المؤشرات ( Pointers ) و التي سنتعرف عليها في دروس لاحقة.

التعامل مع المصفوفة بواسطة حلقة في C++

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


في المثال التالي إفترضنا أن عدد عناصر المصفوفة التي سنعرض قيمها معروف.

المثال الأول

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بإنشاء مصفوفة تحتوي على 3 قيم نصية
	  string fruits[3] = {"Apple", "Banana", "Orange"};

	  // على سطر جديد fruits هنا قمنا بإنشاء حلقة, في كل دورة تقوم بعرض قيمة من القيم الموجودة في المصفوفة
	  for (int i=0; i<3; i++)
	  {
	  cout << fruits[i] << endl;
	  }

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل

	  Apple
	  Banana
	  Orange
	

في المثال التالي إفترضنا أن عدد عناصر المصفوفة التي سنعرض قيمها غير معروف.

المثال الثاني

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بإنشاء مصفوفة تحتوي على 3 قيم نصية
	  string fruits[] = {"Apple", "Banana", "Orange"};

	  // n و من ثم تخزينه في المتغير fruits هنا قمنا بحساب عدد عناصر المصفوفة
	  int n = sizeof(fruits) / sizeof(fruits[0]);

	  // على سطر جديد fruits هنا قمنا بإنشاء حلقة, في كل دورة تقوم بعرض قيمة من القيم الموجودة في المصفوفة
	  for (int i=0; i<n; i++)
	  {
	  cout << fruits[i] << endl;
	  }

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل

	  Apple
	  Banana
	  Orange
	

الحلقة foreach في C++

إبتداءاً من إصدار المترجم C++ 11 تم إضافة حلقة for جديدة إسمها Foreach Loop.
هذه الحلقة تسمح لك بالمرور على جميع عناصر المصفوفة دون الحاجة لتعريف عداد و تحديد أين يبدأ و أين ينتهي.


طريقة تعريف الحلقة Foreach

for (element: array)
{
    // statements
}

  • element: هو متغير عادي نقوم بتعريفه بداخل الحلقة و نعطيه نفس نوع المصفوفة التي نضعها بعد النقطتين, لأنه في كل دورة سيقوم بتخزين قيمة عنصر من عناصرها, لذلك يجب جعل نوعه مثل نوعها.

  • array: هي المصفوفة التي نريد الوصول لجميع عناصرها.

  • statements: هي جميع الأوامر الموضوعة في الحلقة و هي تتنفذ في كل دورة.

إذاً هنا تقوم الحلقة بالمرور على جميع عناصر المصفوفة بالترتيب من العنصر الأول إلى العنصر الأخير, و في كل دورة تقوم بتخزين قيمة العنصر في المتغير الذي قمنا بتعريفه.


سنقوم الآن بكتابة برنامج بسيط يعرض قيم جميع عناصر مصفوفة باستخدام الحلقة Foreach.

مثال

	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // هنا قمنا بإنشاء مصفوفة تحتوي على 3 قيم نصية
	  string fruits[] = {"Apple", "Banana", "Orange"};

	  // element في المتغير fruits هنا في كل دورة سيتم تخزين قيمة عنصر من عناصر المصفوفة
	  for (string element: fruits)
	  {
	  // element هنا سيتم عرض القيمة التي تخزنت في المتغير
	  cout << element << endl;
	  }

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل

	  Apple
	  Banana
	  Orange
	

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

C++ إدخال بيانات من المستخدم

مقدمة

في الدروس السابقة, كنا نكتب الكود ثم نقوم بتجربته فيتنفذ كما هو, بمعنى أننا كنا أصلاً نعرف ما سيظهر عند تشغيل الكود لأننا كنا نقوم بتحديد قيم المتغيرات قبل تشغيل البرنامج.

في هذا الدرس ستتعلم كيف تنشئ برنامح يتفاعل مع المستخدم, حيث أنك عندما تقوم بتشغيله سيطلب من المستخدم إدخال بيانات, و بعد إدخالها سيقوم البرنامج بمعالجتها و فعل شيء معين بها.

أمر الإدخال cin في C++

لجعل المستخدم قادر على إدخال بيانات في البرنامج أثناء اشتغاله نستخدم أمر الإدخال cin >>.
الأمر cin موجود في الحزمة std و لهذا يجب ان تكتب std::cin إن لم تكن تريد تضمين الحزمة std في برنامجك.

في كل مرة تقوم فيها باستدعاء هذا الأمر يقوم المترجم بانتظارك لإدخال عدد أو كلمة واحدة من لوحة المفاتيح ( Keyboard ).
بعد الإنتهاء من الإدخال و النقر على الزر Enter سيتم إرجاع الشيء الذي قمت بإدخاله في المكان الذي تم منه إستدعاء الأمر cin >>.


مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // لتخزين المعلومات التي سيدخلها المستخدم لاحقاً age و name قمنا بتجهيز المتغيرين
	  string name;
	  int age;

	  // name هنا سيطلب من المستخدم إدخال إسمه و من ثم سيتم تخزين الإسم الذي يدخله في المتغير
	  cout << "What's your name? ";
	  cin >> name;

	  // age هنا سيطلب من المستخدم إدخال عمره و من ثم سيتم تخزين الرقم الذي يدخله في المتغير
	  cout << "How old are you? ";
	  cin >> age;

	  // في الأخير قمنا بعرض المعلومات التي يدخلها المستخدم بشكل مرتب
	  cout << "Your name is " << name << ", and your age is " << age << " years.";

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل مع الإشارة إلى أننا قمنا بتعليم البيانات التي إنتظرنا البرنامج لإدخالها من لوحة المفاتيح باللون الأصفر.

What's your name? Mhamad
How old are you? 25

Your name is Mhamad, and your age is 25 years.

الفرق بين الأمر cin و الأمر cout

في البداية معرفة سبب تسمية الأوامر بهذه الكلمات سيسهل عليك تذكر الفرق بينهما, لذا تذكر دائماً أصلهما:

  • cin يقصد بها كلمة See In و التي بدورها تستخدم لإدخال قيمة في المتغير.

  • cout يقصد بها كلمة See Out و التي بدورها تستخدم للحصول على قيمة المتغير.


الأمر cin نضع بعده إسم المتغير الذي سنخزن القيمة التي يدخلها المستخدم فيها و تكون الأسهم باتجاه اليمين كالتالي.

	// x قم بتخزين ما سيدخله المستخدم في المتغير
	cin >> x;
  

الأمر cout نضع بعده أي شيء نريد عرضه أو أي متغير نريد عرض قيمته و تكون الأسهم باتجاه اليسار كالتالي.

	// x قم بعرض القيمة المدخلة في المتغير
	cout << "x = " < < a;
  

المشاكل التي يسببها إدخال مسافات فارغة

تذكر دائماً أن الأمر cin >> يستخدم لإدخال رقم أو كلمة واحدة فقط بمعنى أنه لا يجب أن يحتوي ما تدخله على مسافات فارغة.

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


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

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // لتخزين المعلومات التي سيدخلها المستخدم لاحقاً email و name قمنا بتجهيز المتغيرين
	  string name;
	  string email;

	  // name هنا سيطلب من المستخدم إدخال إسمه و من ثم سيتم تخزين الإسم الذي يدخله في المتغير
	  cout << "Enter your name: ";
	  cin >> name;

	  // email هنا سيطلب من المستخدم إدخال بريده الإلكتروني و من ثم سيتم تخزين البريد الذي يدخله في المتغير
	  cout << "Enter your email: ";
	  cin >> email;

	  // في الأخير قمنا بعرض المعلومات التي يدخلها المستخدم بشكل مرتب
	  cout << "\n\n---------------------";
	  cout << "\nName = " << name;
	  cout << "\nEmail = " << email;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل مع الإشارة إلى أننا قمنا بتعليم البيانات التي إنتظرنا البرنامج لإدخالها من لوحة المفاتيح باللون الأصفر.

Enter your name: Mhamad Harmush
Enter your email:               <- لاحظ أنه لم ينتظرنا لندخل البريد الإلكتروني بل إعتبر أنه تم إدخاله

---------------------
Name = Mhamad                   <- name فقط في المتغير Mhamad لاحظ أنه قام بوضع
Email = Harmush                 <- بشكل تلقائي email في المتغير Harmush لاحظ أنه قام بوضع

إدخال نص بواسطة الدالة getline()

إذا أردت جعل المستخدم قادر على إدخال أكثر من كلمة و تخزين كل ما يدخله في متغير نصّي واحد, يمكنك إستخدام الأمر cin لجعل المترجم ينتظر المستخدم حتى يدخل ما يشاء, و من ثم تقوم بتغليف ما سيتم إدخاله بواسطة الدالة getline() و التي ستقوم باعتبار كل ما تم إدخاله عبارة عن نص واحد.

إذاً الأمر التالي هو ما تحتاجه لإستقبال نص من المستخدم.

	// x قم بتخزين كل النص الذي سيدخله المستخدم في المتغير
	getline(cin, x);
  

ملاحظة: الدالة getline() موجودة في الحزمة std و لهذا يجب ان تكتب std::getline(std::cin, x) إن لم تكن تريد تضمين الحزمة std في برنامجك.


في المثال التالي قمنا بتعريف مثال يطلب من المستخدم إدخال إسمه الكامل (إسمه و إسم عائلته) و من ثم يعرض له رسالة ترحيب.

مثال

main.cpp
	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // لتخزين الإسم الذي سيدخله المستخدم لاحقاً name قمنا بتجهيز المتغير
	  string name;

	  // name هنا سيطلب من المستخدم إدخال إسمه الكامل و من ثم سيتم تخزين الإسم الذي يدخله في المتغير
	  cout << "Enter your name: ";
	  getline(cin, name);

	  // في الأخير قمنا بعرض المعلومات التي يدخلها المستخدم بشكل مرتب
	  cout << "Welcome " << name;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل مع الإشارة إلى أننا قمنا بتعليم البيانات التي إنتظرنا البرنامج لإدخالها من لوحة المفاتيح باللون الأصفر.

Enter your name: Mhamad Harmush
Welcome Mhamad Harmush

C++ الدوال

مفهوم الدوال في C++

الدالة ( Function ) عبارة عن مجموعة أوامر مجمعة في مكان واحد و تتنفذ عندما نقوم باستدعائها.
في الدروس السابقة تعرفنا على الكثير من العديد من الدوال الجاهزة في C++ و التي تستخدم للتعامل مع النصوص و الأرقام.

في هذا الدرس سنتعلم كيفية إنشاء دوال جديدة و كيفية استخدامها.


أمثلة حول الدوال الجاهزة

أسماء بعض الدوال التي قمنا باستخدامها في الدروس السابقة.

	length();
	insert();
	replace();
	fmax();
	floor();
  

مصطلحات تقنية

الدوال الجاهزة في C++ يقال لها Built-in Functions.
الدوال التي يقوم المبرمج بتعريفها يقال لها User-defined Functions.

بناء الدوال في C++

عند تعريف أي دالة في C++ عليك إتباع الشكل التالي:

returnType functionName(Parameter List)
{
    // Function Body
}

returnType: يحدد النوع الذي سترجعه الدالة عندما تنتهي أو إذا كانت لن ترجع أي قيمة.
functionName: يمثل الإسم الذي نعطيه للدالة, و الذي من خلاله يمكننا استدعاءها.
Parameter List: المقصود بها الباراميترات ( وضع الباراميترات إختياري ).
Function Body: تعني جسم الدالة, و المقصود بها الأوامر التي نضعها في الدالة.


نوع الإرجاع ( returnType ) في الدالة يمكن أن يكون أي نوع من أنواع البيانات الموجودة في C++ ( int - double - bool - string إلخ.. ).
و يمكن وضع إسم لكلاس معين, و هنا يكون القصد أن الدالة ترجع كائن من هذا الكلاس ( لا تقلق ستتعلم هذا في دروس لاحقة ).

في حال كانت الدالة لا ترجع أي قيمة, يجب وضع الكلمة void مكان الكلمة returnType.

أمثلة حول تعريف دوال جديدة في C++

في المثال التالي قمنا بتعريف دالة إسمها myFunction, نوعها void, و تحتوي على أمر طباعة فقط.
بعدها قمنا باستدعائها في الدالة main() حتى يتم تنفيذ أمر الطباعة الموضوع فيها.

المثال الأول

Main.cpp
	  #include <iostream>

	  using namespace std;

	  // عند استدعاءها تقوم بطباعة جملة myFunction هنا قمنا بتعريف دالة إسمها
	  void myFunction() {
	  cout << "My first function is called";
	  }

	  int main()
	  {
	  // حتى يتنفذ الأمر الموضوع فيها myFunction() هنا قمنا باستدعاء الدالة
	  myFunction();

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  My first function is called
	

هنا قمنا بتعريف دالة إسمها greeting, عند إستدعاءها نمرر لها إسم فتطبع رسالة ترحيب للإسم الذي تم تمريره لها.

المثال الثاني

Main.cpp
	  #include <iostream>

	  using namespace std;

	  // عند استدعاءها تقوم بطباعة جملة greeting هنا قمنا بتعريف دالة إسمها
	  void greeting(string name)
	  {
	  cout << "Hello " << name << ", welcome to our company.";
	  }

	  int main()
	  {
	  // حتى يتنفذ الأمر الموضوع فيها greeting() هنا قمنا باستدعاء الدالة
	  greeting("Mhamad");

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  Hello Mhamad, welcome to our company.
	

هنا قمنا بتعريف دالة إسمها getSum, عند إستدعاءها نمرر لها عددين فترجع لنا ناتج جمعهما.

المثال الثالث

Main.cpp
	  #include <iostream>

	  using namespace std;

	  // عند إستدعاءها نمرر لها عددين فتقوم بإرجاع ناتج جمعهما get_sum هنا قمنا بتعريف دالة إسمها
	  int getSum(int a, int b)
	  {
	  return a + b;
	  }

	  int main()
	  {
	  // x في المتغير get_sum() هنا قمنا بتخزين ناتج العددين 3 و 5 الذي سترجعه الدالة
	  int result = getSum(3, 7);

	  // و التي ستساوي 10 result هنا قمنا بعرض قيمة المتغير
	  cout << "Result = " << result;

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  Result = 10
	

إعطاء قيمة إفتراضية للباراميترات في C++

C++ تتيح لك وضع قيم إفتراضية للباراميترات مما يجعلك عند إستدعاء الدالة مخيّر على تمرير قيم مكان الباراميترات بدل أن تكون مجبراً على ذلك.


مصطلحات تقنية

القيمة الإفتراضية التي نضعها للباراميتر يقال لها Default Argument.


في المثال التالي قمنا بتعريف دالة إسمها printLanguage.
هذه الدالة فيها باراميتر واحد إسمه language يملك النص "English" كقيمة إفتراضية.
كل ما تفعله هذه الدالة عند إستدعاءها هو طباعة قيمة الباراميتر language.

ملاحظة: بما أن الباراميتر language يملك قيمة بشكل إفتراضية, فهذا يعني أنك لم تعد مجبر على تمرير قيمة له عند إستدعاء الدالة لأنه أصلاً يملك قيمة.

مثال

Test.cpp
	  #include <iostream>

	  using namespace std;

	  // و يمكنك عدم تمرير قيمة لأنه أصلاً يملك قيمة language عند إستدعاءها يمكنك تمرير قيمة لها مكان الباراميتر .printLanguage هنا قمنا بتعريف دالة إسمها 
	  void printLanguage(string language="English")
	  {
	  cout << "Your language is " << language << endl;
	  }

	  int main()
	  {
	  // "English" و بالتالي ستظل قيمته language بدون تمرير قيمة مكان الباراميتر printLanguage() هنا قمنا باستدعاء الدالة
	  printLanguage();

	  // "Arabic" و بالتالي ستصبح قيمته language للباراميتر 'Arabic' مع تمرير القيمة printLanguage() هنا قمنا باستدعاء الدالة
	  printLanguage("Arabic");

	  return 0;
	  }
	

سنحصل على النتيجة التالية عند التشغيل.

	  Your language is English
	  Your language is Arabic
	

إنتبه

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


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

شاهد الأمثلة »

أين يجب تعريف الدوال في C++

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


في المثال التالي, قمنا بوضع الدالة myFunction() بعد الدالة التي قمنا باستدعائها منها.
المشكلة التي ستحدث عند التشغيل هنا سببها أن المترجم سيكون لا يعرف ما هي myFunction() حيث أنه تم استدعاءها قبل أن يقوم المترجم قد سبق و قرأها.

المثال الأول

Main.cpp
	  #include <iostream>

	  using namespace std;

	  int main()
	  {
	  // myFunction() هنا قمنا باستدعاء الدالة
	  myFunction();

	  return 0;
	  }

	  // التي تحتوي على أمر طباعة فقط myFunction هنا قمنا بتعريف الدالة
	  void myFunction()
	  {
	  cout << "My first function is called";
	  }
	

سيظهر الخطأ التالي عند التشغيل و الذي يعني أن المترجم لم يعرف ما هي myFunction التي تحاول استدعاءها في السطر الثامن.

main.cpp|8|error: 'myFunction' was not declared in this scope|


حل مشكلة عدم التعرف على الدالة

لحل مشكلة عدم التعرف على الدالة التي حدثت في المثال السابق عندنا خيارين:

  • إبقاء الدالة myFunction() مكانها و ذكر تعريفها ( Function Declartion ) في أول الملف فقط, و هذه الطريقة تعتبر الأكثر تفضيلاّ.

  • وضع الدالة myFunction() فوق الدالة main() حتى يقوم المترجم بقرائها و التعرف عليها و تصبح قادر على استدعاءها في الدالة main() الموجودة بعدها.



في المثال التالي, قمنا بإبقاء الدالة myFunction() مكانها و ذكر تعريفها ( Function Declartion ) قبل أن يتم استدعاءها في الدالة main().
إذاً لن يحدث أي مشكلة عند استدعاء الدالة myFunction() من الدالة main() لأن المترجم سيكون لديه علم بأن الدالة myFunction() موجودة فعلاً.

المثال الثاني

Main.cpp
	  #include <iostream>

	  using namespace std;

	  // حتى يقوم المترجم بالتعرف عليها و نصبح قادرين على استخدامها myFunction الدالة ( Declartion ) هنا قمنا بوضع تعريف
	  void myFunction();

	  int main()
	  {
	  // myFunction() هنا قمنا باستدعاء الدالة
	  myFunction();

	  return 0;
	  }

	  // أو بمعنى آخر تعريف ما سيحدث عندما يتم استدعاءها ,myFunction الدالة ( body ) هنا قمنا بتعريف جسم
	  void myFunction()
	  {
	  cout << "My first function is called";
	  }
	

في الدالة main() سيتم استدعاء و تتنفذ الدالة myFunction() بدون أي مشاكل و سنحصل على النتيجة التالية عند التشغيل.

	  My first function is called
	

نصيحة

الأفضل دائماً وضع تعريفات الدوال ( Functions Declartions ) قبل الدالة main() و تعريف محتوى هذه الدوال بعدها كالتالي لأن قراءة الكود ستصبح أسهل بالنسبة لك.


المثال التالي يريك فقط كيف تقوم بترتيب الكود إذا كنت تنوي تعريف العديد من الدوال في الملف.

المثال الثالث

	  #include <iostream>

	  using namespace std;

	  // نقوم بذكر تعريف جميع الدوال التي سنقوم بإنشائها لاحقاً main() قبل الدالة
	  void printMessage();
	  void greeting(string name);

	  // و فيها يمكننا استدعاء أي دالة قمنا بذكر تعريفها سابقاً بدون أي مشاكل main() هنا نقوم بتعريف الدالة
	  int main()
	  {
	  // greeting() و printMessage() هنا يمكننا استدعاء
	  return 0;
	  }

	  // لأنه قمنا بذكر أنها موجودة من قبل printMessage() هنا نقوم بتعريف الدالة
	  void printMessage()
	  {
	  // هنا نكتب ما سيحدث عند استدعاءها 
	  }

	  // لأنه قمنا بذكر أنها موجودة من قبل greeting() هنا نقوم بتعريف الدالة
	  void greeting(string name)
	  {
	  // هنا نكتب ما سيحدث عند استدعاءها 
	  }
	

C++ أخطاء قد تظهر بسبب وضع قيم إفتراضية للباراميترات

في جميع الأمثلة, سنفترض أننا نريد تعريف دالة و في كل مرة نحاول تمرير قيم إفتراضية لبعض عناصرها.


المثال الأول

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

	void printMax(int a, int b, int c=0)
	{

	}
  


المثال الثاني

المثال التالي فيه مشكلة حيث أننا قمنا بإعطاء b قيمة إفتراضية و لم نعطي قيمة إفتراضية للباراميتر c الموجود بعده.
هذا الأمر سيؤدي لظهور مشكلة عند تشغيل الكود.

	void printMax(int a, int b=0, int c) 
	{

	}
  

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

error: default argument missing for parameter 3 of 'void printMax(int, int, int)'


المثال الثالث

في المثال التالي قمنا بإعطاء b و c قيم إفتراضية و هذا لن يسبب مشكلة لأنه لا يوجد بعدهما أي باراميتر.

	void printMax(int a, int b=0, int c=0) 
	{

	}
  


المثال الرابع

المثال التالي فيه مشكلة حيث أننا قمنا بإعطاء a قمية إفتراضية و لم نعطي قيمة إفتراضية للباراميترين b و c الموجودين بعده.
هذا الأمر سيؤدي لظهور مشكلة عند تشغيل الكود.

	void printMax(int a=0, int b, int c) 
	{

	}
  

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

error: default argument missing for parameter 2 of 'void printMax(int, int, int)'
error: default argument missing for parameter 3 of 'void printMax(int, int, int)'


المثال الخامس

في المثال التالي قمنا بإعطاء a و b و c قيم إفتراضية, أي كل البارميترات و بالتالي لا يوجد أي مشكلة هنا.

	void printMax(int a=0, int b=0, int c=0) 
	{

	}