المؤشرات في السي بلاس بلاس | C++ Pointers

 المؤشرات في السي بلاس بلاس C++

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


الفرق بين المرجع و المؤشر C++

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

في هذا الدرس ستفهم الفرق بين المراجع و المؤشرات بالنسبة للذاكرة و ستتعرف على المزايا الإضافية التي توفرها المؤشرات.

 تعريف مؤشر في C++

لتعريف مؤشر جديد نستخدم الرمز * مع الإشارة إلى أن نوع المؤشر يجب أن يكون نفس نوع الشيء الذي سيشير له في الذاكرة.

إذا اردنا تعريف مؤشر نوعه int و إسمه x فعندنا ثلاث خيارات كالتالي.

              // الأسلوب الأول و الذي يعتبر الأكثر استخداماً
              int* x;

              // الأسلوب الثاني
              int *x;

              // الأسلوب الثالث
              int * x;
            


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

مثال على تعريف مؤشر في C++

main.cpp
                #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 في الذاكرة.

تعريف مؤشر في السي بلاس بلاس C++

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

  • المؤشر ptr تم تخصيص مساحة خاصة له في الذاكرة و هذه المساحة لها عنوان و قيمة أيضاً.

  • قيمة المؤشر ptr هي عنوان المتغير lanugage في الذاكرة, و هذا يعني أن قيمة المؤشر دائماً تكون عنوان الشيء الذي يشير إليه في الذاكرة.


بالنسبة للتعامل مع المؤشر فلاحظنا التالي:

  • نكتب إسم المؤشر فقط في حال أردنا الوصول لقيمته, كمثال ptr

  • نضع & قبل إسم المؤشر في حال أردنا الوصول لعنوانه في الذاكرة, كمثال &ptr

  • نضع * قبل إسم المؤشر في حال أردنا الوصول لقيمة الشيئ الذي يشير إليه, كمثال *ptr


تحديد الشيء الذي يتم الإشارة إليه في C++

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

الآن إذا أردت جعل المؤشر يشير إلى شيء آخر موجود في الذاكرة, فكل ما عليك فعله هو تمرير عنوان الشيء الآخر له كقيمة.


في المثال التالي قمنا بتعريف متغيرين إسمهما x و y.
بعدها قمنا بتعريف مؤشر إسمه ptr جعلناه يشير للمتغير x و عرضنا التي يشير إليها.
ثم جعلنا ptr يشير للمتغير y و عرضنا القيمة التي يشير إليها.

مثال على تحديد الشيء الذي يتم الإشارة إليه في C++

main.cpp
                #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++

main.cpp
                #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 يمكن إستخدامها مع المؤشرات و أي شيء آخر نريد ضمان جعله لا يملك قيمة.


في المثال التالي قمنا بإنشاء مؤشر و طباعة القيمة الإفتراضية الموجودة فيه

المثال الأول انشاء مؤشر و طباعة القيمة الإفتراضية الموجودة فيه

main.cpp
                #include <iostream>

                using namespace std;

                int main()
                {
                // بدون جعله يشير لقيمة شيء موجود في الذاكرة x هنا قمنا بتعريف مؤشر إسمه
                int* x;

                // في الذاكرة x هنا قمنا بطباعة عنوان الشيء الذي يشير إليه المؤشر
                cout << "x = " << x;

                return 0;
                }
              

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

                x = 0x10
              

هنا وضعنا مثال حول كيفية إنشاء مؤشر لا يملك قيمة Null Pointer ) بالإضافة إلى كيفية التشييك على المؤشر لمعرفة ما إن كان يملك قيمة أم لا.

المثال الثاني  إنشاء مؤشر لا يملك قيمة Null Pointer ) 

main.cpp
                #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 و جعله يشير للمصفوفة.

مثال 

main.cpp
                #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, عند استدءعاها نمرر لها متغيرين فتقوم بتبديل قيمهما.

مثال 

main.cpp
                #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