ما هو المتغيّر
المتغيّر هو جزء مُسمّى من الذاكرة يحمل قيمة. في C++ يكون لكل متغيّر نوع ثابت - يُختار عند الإعلان عنه - ولا يتغيّر هذا النوع أبدًا. وهذا ما يجعل C++ مكتوبة الأنواع بشكل ساكن (statically typed): يعرف المُترجِم نوع كل متغيّر قبل تشغيل البرنامج ويرفض ترجمة شيفرة تخزّن قيمة من النوع الخطأ.
يتكوّن الإعلان من ثلاثة أجزاء: النوع، والاسم، و(غالبًا دائمًا) قيمة ابتدائية.
اقرأ int age = 30; على أنها "أنشئ int باسم age وضع فيه 30". تُنهي الفاصلة المنقوطة الجملة، تمامًا كما أوضحت صفحة التعليقات أن كل جملة تفعل ذلك. لاحظ أن isActive طُبعت على هيئة 1: يظهر bool على شكل 1/0 افتراضيًا، وهو ما تبني عليه صفحة أنواع البيانات.
التهيئة مقابل الإسناد
يبدو هذان متشابهين لكنهما عمليتان مختلفتان، وهذا الفرق من أهم الأفكار في هذه الصفحة.
التهيئة تمنح المتغيّر قيمته الأولى كجزء من الإعلان. أما الإسناد فيغيّر قيمة متغيّر موجود بالفعل.
يمكنك الإعلان دون تهيئة ثم الإسناد لاحقًا، لكن انتبه:
ينجح ذلك لأننا أسندنا score قبل قراءته. النسخة الخطِرة هي قراءته أولًا - وهو ما سنتناوله تاليًا.
فخّ المتغيّر غير المهيّأ
هذا هو الفخّ التقليدي في C++ الذي لا وجود له في كثير من اللغات الأخرى. قراءة متغيّر محلي أُعلن عنه لكنه لم يُعطَ قيمة قطّ هي سلوك غير معرَّف: فالمتغيّر يحمل أيّ بايتات صادف وجودها في تلك الذاكرة.
int score; // غير مهيّأ
std::cout << score; // سلوك غير معرَّف - يطبع قيمة عشوائية، أو "يعمل"، أو ينهار
سيبني المُترجِم هذا دون اعتراض. قد يطبع 0، وقد يطبع 32766، وقد يتصرّف على نحو مختلف في كل تشغيل أو على كل جهاز - وهو ما يجعل تتبّع هذه العلل عذابًا. هناك خطّا دفاع:
- هيّئ دائمًا عند الإعلان.
int score = 0;لا يكلّف شيئًا ويزيل المشكلة برمّتها. - فعّل التحذيرات. الترجمة بـ
-Wall -Wextraتجعل المُترجِم يُشير إلى كثير من القراءات غير المهيّأة قبل أن تؤذيك.
فضّل الخيار الأول لكل متغيّر محلي: امنحه قيمة بداية معقولة في اللحظة نفسها التي تنشئه فيها.
أنماط التهيئة: = و() و{}
تمنحك C++ عدة طرق لكتابة قيمة ابتدائية. وهي تفعل الشيء نفسه في الغالب، لكن للتهيئة بالأقواس المعقوفة ميزة أمان إضافية تستحق المعرفة.
سبب اللجوء إلى {} هو أنها ترفض التحويلات المُضيِّقة (narrowing) - أي الإسنادات التي تفقد البيانات بصمت. فمع = تُقتطع القيمة الكسرية دون إشعار؛ ومع {} يوقفك المُترجِم:
int x = 3.9; // يُترجَم - يصبح x بصمت 3 (يُرمى الجزء .9)
int y{3.9}; // خطأ ترجمة - التضييق من double إلى int غير مسموح
ضبط ذلك في وقت الترجمة هو بالضبط ما تريده. ومن العادات الحديثة الشائعة اعتماد {} افتراضيًا من أجل هذا الفحص الإضافي، والعودة إلى = فقط حين تكون التهيئة بالنسخ أكثر سلاسة في القراءة.
قواعد التسمية والأعراف
تفرض C++ بضع قواعد صارمة، ثم يبني الجميع أعرافًا فوقها. القواعد: قد يحتوي الاسم على حروف وأرقام و_؛ ولا يجوز أن يبدأ برقم؛ ولا يجوز أن يكون كلمة محجوزة (مثل int أو return)؛ وهو حسّاس لحالة الأحرف (age وAge متغيّران مختلفان). تجنّب الأسماء التي تبدأ بشَرطة سفلية يليها حرف كبير، أو التي تحتوي على شرطتين سفليتين متتاليتين - فتلك محجوزة للتنفيذ (implementation).
الأعراف التي تتبعها معظم شيفرات C++:
- تستخدم المتغيّرات
snake_caseأوcamelCase- اختر واحدًا والتزم به:item_countأوitemCount. - ينبغي أن تصف الأسماء القيمة:
countلاc؛ وuser_emailلاx.
الأسماء الواضحة ليست زينة - بل هي الطريقة التي ستقرأ بها نفسُك المستقبلية الشيفرة. فعبارة total_price = item_count * price_per_item تشرح نفسها بطريقة لن تبلغها t = c * p أبدًا.
نطاق المتغيّر
يوجد المتغيّر داخل الكتلة فقط - أي { ... } - حيث أُعلن عنه، ويُدمَّر عند القوس المعقوف الذي يغلقها. هذا هو نطاقه (scope). والمتغيّر المُعلَن داخل حلقة أو كتلة if يكون غير مرئي خارجها:
ينتمي كلٌّ من i وsquare إلى الحلقة ويزولان عند انتهائها؛ أما total فمُعلَن في الكتلة الخارجية، لذا يبقى. ويمكن لكتلة داخلية أيضًا أن تحجب (shadow) اسمًا من كتلة خارجية: فمتغيّر جديد بالاسم نفسه يُخفي مؤقتًا المتغيّر الخارجي إلى أن تُغلق الكتلة الداخلية، وهذا مصدر شائع للالتباس، لذا تجنّب إعادة استخدام الأسماء عبر النطاقات المتداخلة.
الخلاصة العملية: أعلن عن كل متغيّر في أصغر كتلة تحتاجه، وهيّئه هناك. فالنطاق الضيّق يعني عددًا أقل من الأسماء التي تتنافس على انتباهك، وفرصًا أقل لقراءة قيمة بعيدًا عن المكان الذي ضُبطت فيه.
التالي: أنواع البيانات
بدأ كل متغيّر في هذه الصفحة بنوع - int وdouble وstring وbool. تفصّل الصفحة التالية أنواع البيانات في C++: الأنواع الأساسية وأحجامها، الأعداد الصحيحة ذات الإشارة وغير ذات الإشارة، float مقابل double، وchar، وكيفية اختيار النوع المناسب للمهمّة.
الأسئلة الشائعة
كيف تُعلن عن متغيّر في C++؟
اكتب النوع، ثم اسمًا، ثم أعطِه قيمة اختياريًا: int age = 30;. النوع (int) ثابت طوال عمر المتغيّر؛ والاسم (age) هو الطريقة التي تشير بها إليه. يمكنك الإعلان دون قيمة - int age; - لكن بالنسبة لمتغيّر محلي يتركه ذلك حاملًا لقيمة عشوائية إلى أن تُسند له قيمة، لذا هيّئه دائمًا عند نقطة الإعلان.
ما الفرق بين التهيئة والإسناد في C++؟
التهيئة تمنح المتغيّر قيمته الأولى كجزء من إعلانه: int x = 5; أو int x{5};. أما الإسناد فيغيّر قيمة متغيّر موجود بالفعل: x = 7;. ويهمّ هذا التمييز لأن قراءة متغيّر محلي أُعلن عنه لكنه لم يُهيَّأ قطّ هي سلوك غير معرَّف.
ماذا يحدث إذا استخدمت متغيّرًا غير مهيّأ في C++؟
قراءة قيمة متغيّر محلي غير مهيّأ هي سلوك غير معرَّف (undefined behavior): فالمتغيّر يحمل أيّ بايتات كانت موجودة أصلًا في تلك الذاكرة، لذا قد يطبع برنامجك رقمًا عشوائيًا، أو يعمل بمحض الصدفة، أو ينهار. ولن يوقفك المُترجِم (وإن كان كثير منها يحذّر مع -Wall)، فالحل هو منح المتغيّرات المحلية قيمة دائمًا عند الإعلان عنها.