المؤشرات في السي بلاس بلاس C++
في الدرس السابق تعرفنا على المراجع ( References ) و تعلمنا كيف أنها تستخدم بهدف جعلنا قادرين على الوصول إلى الأشياء المعرفة في الذاكرة بشكل مباشر.
المؤشرات ( Pointers ) تستخدم أيضاً للوصول لأي شيء يتم تعريفه في الذاكرة و هي تعطينا المزيد من المزايا في التحكم.
الفرق بين المرجع و المؤشر C++
الفرق الأساسي بينهما هو أن المؤشر يقوم بحجز مساحة في الذاكرة لتخزين عنوان الشيء الذي يؤشر إليه.
بالإضافة إلى ذلك, فإن المؤشر يمكنه الإشارة لأي شيء موجود في الذاكرة في أي وقت و لست مجبراً على تحديد الشيء الذي يشير إليه لحظة إنشاءه.
في هذا الدرس ستفهم الفرق بين المراجع و المؤشرات بالنسبة للذاكرة و ستتعرف على المزايا الإضافية التي توفرها المؤشرات.
تعريف مؤشر في C++
لتعريف مؤشر جديد نستخدم الرمز *
مع الإشارة إلى أن نوع المؤشر يجب أن يكون نفس نوع الشيء الذي سيشير له في الذاكرة.
إذا اردنا تعريف مؤشر نوعه int
و إسمه x
فعندنا ثلاث خيارات كالتالي.
// الأسلوب الأول و الذي يعتبر الأكثر استخداماً int* x; // الأسلوب الثاني int *x; // الأسلوب الثالث int * x;
الآن, لتعريف مؤشر و جعله يشير لقيمة شيئ موجود في الذاكرة, يجب أن نقوم بتمرير عنوان هذا الشيء كقيمة للمؤشر و عندها سيصبح المؤشر قادر على الوصول لقيمته و عنوانه كما سنرى في المثال التالي.
مثال على تعريف مؤشر في C++
#include <iostream> using namespace std; int main() { // "Arabic" و قيمته language هنا قمنا بتعريف متغير إسمه string language = "Arabic"; // في الذاكرة language هنا قمنا بتعريف مؤشر لعنوان المتغير string* ptr = &language; // language هنا قمنا بطباعة قيمة المتغير cout << "language = " << language << endl; // في الذاكرة language هنا قمنا بطباعة عنوان المتغير cout << "Address of language = " << &language << endl; // في الذاكرة ptr هنا قمنا بطباعة عنوان المؤشر cout << "Address of ptr in memory = " << &ptr << endl; // language و التي هي عنوان المتغير ptr هنا قمنا بطباعة القيمة الموجودة في المؤشر cout << "Value of ptr in memory = " << ptr << endl; // language في الذاكرة و التي هي قيمة المتغير ptr هنا قمنا بطباعة القيمة التي يشير إليها عنوان المؤشر cout << "Value that ptr point to = " << *ptr; return 0; }
•سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.
language = Arabic Address of language = 0x61fde0 Address of ptr in memory = 0x61fdd8 Value of ptr in memory = 0x61fde0 Value that ptr point to = Arabic
الصورة التالية تظهر كيف تم تخزين عنوان و قيمة المتغير language
و المؤشر ptr
في الذاكرة.
من المهم جداً أن تلاحظ الطريقة التي يتم فيها إنشاء المؤشر في الذاكرة حتى تعرف كيف تتعامل معه بسهولة.
المؤشر
ptr
تم تخصيص مساحة خاصة له في الذاكرة و هذه المساحة لها عنوان و قيمة أيضاً.قيمة المؤشر
ptr
هي عنوان المتغيرlanugage
في الذاكرة, و هذا يعني أن قيمة المؤشر دائماً تكون عنوان الشيء الذي يشير إليه في الذاكرة.
بالنسبة للتعامل مع المؤشر فلاحظنا التالي:
نكتب إسم المؤشر فقط في حال أردنا الوصول لقيمته, كمثال
ptr
نضع
&
قبل إسم المؤشر في حال أردنا الوصول لعنوانه في الذاكرة, كمثال&ptr
نضع
*
قبل إسم المؤشر في حال أردنا الوصول لقيمة الشيئ الذي يشير إليه, كمثال*ptr
تحديد الشيء الذي يتم الإشارة إليه في C++
في المثال السابق لاحظنا أن المؤشر في الذاكرة يشبه كثيراً الشيء الذي يشير إليه حيث يملك قيمة و عنوان خاص في الذاكرة.
و لاحظنا أيضاً أننا في المؤشر لا نضع قيم عادية, حيث أن قيمته تكون عنوان الشيء الذي يشير إليه في الذاكرة و الذي من خلاله يمكننا الوصول لقيمته.
الآن إذا أردت جعل المؤشر يشير إلى شيء آخر موجود في الذاكرة, فكل ما عليك فعله هو تمرير عنوان الشيء الآخر له كقيمة.
في المثال التالي قمنا بتعريف متغيرين إسمهما x
و y
.
بعدها قمنا بتعريف مؤشر إسمه ptr
جعلناه يشير للمتغير x
و عرضنا التي يشير إليها.
ثم جعلنا ptr
يشير للمتغير y
و عرضنا القيمة التي يشير إليها.
مثال على تحديد الشيء الذي يتم الإشارة إليه في C++
#include <iostream> using namespace std; int main() { // قيمته 2 y قيمته 1 و متغير x هنا قمنا بتعريف متغير int x = 1; int y = 2; // ptr هنا قمنا بتعريف مؤشر إسمه int* ptr; // x يشير إلى عنوان المتغير ptr هنا قمنا بجعل المؤشر ptr = &x; // x و التي هي قيمة المتغير ptr هنا قمنا بطباعة قيمة المتغير الذي يشير إليه cout << "*ptr = " << *ptr << endl; // y يشير إلى عنوان المتغير ptr هنا قمنا بجعل المؤشر ptr = &y; // y من جديد و التي أصبحت قيمة المتغير ptr هنا قمنا بطباعة قيمة المتغير الذي يشير إليه cout << "*ptr = " << *ptr << endl; return 0; }
•سنحصل على النتيجة التالية عند التشغيل.
*ptr = 1 *ptr = 2
الفيديو التالي يظهر كيف تم تغيير قيمة المؤشر ptr
حيث تم جعله يشير لعنوان المتغير x
و من ثم يشير لعنوان المتغير y
.
تغيير قيمة الشيء الذي يشير إليه المؤشر في C++
في المثال التالي قمنا بتعريف متغير إسمه x
قيمته 1
و مؤشر إسمه ptr
يشير للمتغير x
.
من خلال المؤشر ptr
قمنا بتغيير قيمة المتغير x
و من بعدها قمنا بطباعة قيمته للتأكد.
مثال على تغيير قيمة الشيء الذي يشير إليه المؤشر في C++
#include <iostream> using namespace std; int main() { // و قيمته 1 x هنا قمنا بتعريف متغير إسمه int x = 1; // في الذاكرة x و جعلناه يشير لعنوان المتغير ptr هنا قمنا بتعريف مؤشر إسمه int* ptr = &x; // إلى 5 ptr هنا قمنا بتغير قيمة المتغير الذي يشير إليه المؤشر *ptr = 5; // للتأكد ما إن كانت قد تغيرت أم لا x هنا قمنا بطباعة قيمة المتغير cout << "x = " << x << endl; // x و التي هي نفسها قيمة المتغير ptr هنا قمنا بطباعة قيمة المتغير الذي يشير إليه cout << "*ptr = " << *ptr; return 0; }
•سنحصل على النتيجة التالية عند التشغيل.
x = 5 *ptr = 5
أمثلة حول مزايا استخدام المؤشرات في C++
كيفية أنشاء مؤشر لايملك قيمة في C++
إذا أردت إنشاء مؤشر لا يملك قيمة, لا بد أن تعطيه القيمة NULL
أو القيمة nullptr
لكي تضمن أنه فارغ و لا يوجد فيه أي قيم إفتراضية.
عندما تمرر له إحدى هاتين القيمتين تصبح قيمته تساوي 0
و عندها يصبح بشكل قاطع لا يشير لأي شيء موجود في الذاكرة لأنه لا يمكن لعنوان شيء موجود في الذاكرة أن يكون 0
فقط.
ملاحظة حول استخدام مؤشرات لاتملك قيمة في C++
القيمة nullptr
هي قيمة مخصصة في لغة C++ للتعامل مع المؤشرات.
القيمة NULL
يمكن إستخدامها مع المؤشرات و أي شيء آخر نريد ضمان جعله لا يملك قيمة.
في المثال التالي قمنا بإنشاء مؤشر و طباعة القيمة الإفتراضية الموجودة فيه
المثال الأول انشاء مؤشر و طباعة القيمة الإفتراضية الموجودة فيه
#include <iostream> using namespace std; int main() { // بدون جعله يشير لقيمة شيء موجود في الذاكرة x هنا قمنا بتعريف مؤشر إسمه int* x; // في الذاكرة x هنا قمنا بطباعة عنوان الشيء الذي يشير إليه المؤشر cout << "x = " << x; return 0; }
•سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.
• نلاحظ أن المؤشر x
ظهر فيه قيمة عشوائية.
x = 0x10
هنا وضعنا مثال حول كيفية إنشاء مؤشر لا يملك قيمة ( Null Pointer ) بالإضافة إلى كيفية التشييك على المؤشر لمعرفة ما إن كان يملك قيمة أم لا.
المثال الثاني إنشاء مؤشر لا يملك قيمة ( Null Pointer )
#include <iostream> using namespace std; int main() { // و جعله لا يشير لأي شيء موجود في الذاكرة x هنا قمنا بتعريف مؤشر إسمه int* ptr = NULL; // لا يشير لشيء موجود في الذاكرة ptr سيتم تنفيذ أمر الطباعة الموضوع هنا إذا كان المؤشر if (!ptr) { cout << "ptr is null"; } // يشير لشيء موجود في الذاكرة ptr سيتم تنفيذ أمر الطباعة الموضوع هنا إذا كان المؤشر else { cout << "ptr is not null"; } return 0; }
•سنحصل على النتيجة التالية عند التشغيل.
ptr is null
إذاً عند التشييك على قيمة المؤشر نكتب (!ptr)
إذا أردنا تنفيذ أوامر في حال كان المؤشر فارغ.
و نكتب (ptr)
إذا أردنا تنفيذ أوامر في حال كان المؤشر يشير لشيء ما.
العوامل الحسابية و المؤشرات في السي بلاس بلاس
عندما نقوم بتعريف مصفوفة, فإننا نقوم بتعريف شيء واحد و نحدد عدد عناصره.
و عندها يقوم المترجم بإنشاء جميع عناصر المصفوفة بالترتيب وراء بعضها في الذاكرة.
إذا قمت بإضافة مؤشر لتعريف المصفوفة فهذا يجعلك قادر على التنقل بين عناصرها باستخدام العوامل الحسابية -
, --
, +
, ++
.
معلومة
عندما نستخدم مؤشر للتنقل بين عناصر المصفوفة, نقوم بجعل قيمة المؤشر تساوي المصفوفة كما هي.
عندها سيتم وضع عنوان أول عنصر في المصفوفة كقيمة للمؤشر كما سترى في المثال التالي.
في المثال التالي قمنا بتعريف مصفوفة إسمها fruit
تحتوي على ثلاث قيمة نصية.
بعدها قمنا بإنشاء مؤشر إسمه ptr
و جعله يشير للمصفوفة.
مثال
#include <iostream> using namespace std; int main() { // تحتوي على ثلاث قيم fruit هنا قمنا بتعريف مصفوفة إسمها string fruit[3] = {"Apple", "Banana", "Orange"}; // fruit و جعلناه يشير لعناصر المصفوفة ptr هنا قمنا بتعريف مؤشر إسمه string* ptr = fruit; // و من الطبيعي أنها ستكون أول قيمة موجودة في المصفوفة ptr هنا قمنا بطباعة قيمة العنصر الذي يشير إليه المؤشر cout << *ptr << endl; // بهدف الإنتقال إلى العنصر التالي الموجود في المصفوفة ptr هنا قمنا بإضافة 1 على عنوان العنصر الموجود في المؤشر ptr++; // و من الطبيعي أنها ستكون ثاني قيمة موجودة في المصفوفة ptr هنا قمنا بطباعة قيمة العنصر الذي يشير إليه المؤشر cout << *ptr << endl; // بهدف الإنتقال إلى العنصر التالي الموجود في المصفوفة ptr هنا قمنا بإضافة 1 على عنوان العنصر الموجود في المؤشر ptr++; // و من الطبيعي أنها ستكون ثالث قيمة موجودة في المصفوفة ptr هنا قمنا بطباعة قيمة العنصر الذي يشير إليه المؤشر cout << *ptr; return 0; }
•سنحصل على نتيجة تشبه النتيجة التالية عند التشغيل.
• نلاحظ أن المؤشر x
ظهر فيه قيمة عشوائية.
Apple Banana Orange
مثال على تعريف دالة تأخذ باراميتر عبارة عن مؤشر و استدعاءها
المثال التالي يعلمك كيف تنشئ دالة تأخذ باراميتر عبارة عن مؤشر و كيف نقوم بتمرير قيمة للبارامتير عند استدعاءها.
هنا قمنا بتعريف دالة إسمها swap
, عند استدءعاها نمرر لها متغيرين فتقوم بتبديل قيمهما.
مثال
#include <iostream> using namespace std; // لأننا سنشير لهذين العنوان من خلالهما b و a عند إستدعاءها نمرر لها عنوان متغيّرين مكان الباراميترين swap هنا قمنا بتعريف دالة إسمها void swap(int *a, int *b) { // الموجودين في الذاكرة b و a هنا ستقوم الدالة بتيديل قيم المتغيرين int temp = *a; *a = *b; *b = temp; } int main() { // و قيمته 5 y و قيمته 3 و متغير إسمه x هنا قمنا بتعريف متغير إسمه int x = 3; int y = 5; // لها حتى تبدل قيمهما y و x و تمرير عنوان المتغيرين swap() هنا قمنا باستدعاء الدالة swap(&x, &y); // ما إن كانت قيمهما قد تبدلت أم لا y و x هنا قمنا بطباعة قيمة المتغيرين cout << "x = " << x << endl; cout << "y = " << y; return 0; }
•سنحصل على النتيجة التالية عند التشغيل.
x = 5 y = 3