Menu
flag Ar iconالعربيةdown icon

المتغيّرات في لغة C: التعريف والتهيئة والإسناد

كيفية تعريف متغيّر في لغة C، والفرق بين التعريف والتهيئة، وقواعد التسمية، وتعريف عدة متغيّرات دفعة واحدة، ولماذا يكون المتغيّر غير المهيّأ أشيع أخطاء المبتدئين.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

المتغيّر قطعة مسمّاة من الذاكرة تحمل قيمة يستطيع برنامجك قراءتها وتغييرها. وفي لغة C تصرّح دائمًا منذ البداية بنوع القيمة التي يحملها، وهذا الاختيار دائم طوال عمر المتغيّر.

تعريف متغيّر

الصيغة هي نوع، ثم اسم، ثم فاصلة منقوطة:

int age;
double price;
char grade;

يحجز كل سطر ذاكرة كافية لذلك نوع البيانات ويربط به اسمًا. لم يُخزَّن شيء بعد - المساحة موجودة، لكن محتواها هو ما تركه فيها ما جرى تنفيذه قبل ذلك.

ويمكنك إعطاء قيمة في اللحظة نفسها، وهو ما يُسمّى التهيئة:

لاحظ محدّدات التنسيق: %d لـ int، و%f لـ double، و%c لـ char. لا تفحص printf القيم التي تُعطى لها - بل تثق بسلسلة التنسيق، فالاختلاف يطبع كلامًا مهملًا لا خطأً.

الإسناد بعد التعريف

بمجرّد وجود المتغيّر تغيّره بـ =:

العلامة = في C إسناد، وليست مساواة. ويُقرأ score = score + 10 هكذا: "خُذ ما في score، وأضف 10، وأعد الناتج إلى score". أما عامل المقارنة فهو ==، والخلط بينهما خطأ شائع إلى حدّ أن له قسمًا خاصًا في صفحة القيم المنطقية.

والاختصار score += 10 يفعل الشيء نفسه بحروف أقل؛ وتغطّي عوامل الإسناد المركّب كل العمليات الحسابية.

فخّ المتغيّر غير المهيّأ

هذا أشيع أخطاء المبتدئين في C على الإطلاق، وهو لا يُنتج خطأً:

شغّله. قد ترى 15، وهي الإجابة الصحيحة. وقد ترى رقمًا عشوائيًا ضخمًا. وقد ترى شيئًا مختلفًا في تشغيل ثانٍ أو على جهاز آخر. كل ذلك سلوك "صحيح"، لأن قراءة متغيّر محلّي غير مهيّأ سلوك غير معرَّف - ولا يفرض معيار C أي شرط على ما يحدث.

وعلى خلاف كثير من اللغات، لا تصفّر C متغيّراتك المحلّية. فالمتغيّر المحلّي شريحة من المكدّس، والمكدّس مليء ببقايا استدعاءات دوال سابقة. وأحيانًا تصادف تلك البقايا أن تكون أصفارًا، وهذا ما يجعل هذا الخطأ خطيرًا إلى هذا الحد: فهو كثيرًا ما ينجح في الاختبار ويفشل في الإنتاج.

الإصلاح حرف واحد:

اجعلها عادة: هيّئ كل متغيّر عند نقطة تعريفه. وإن لم تكن لديك قيمة منطقية بعد، فإن 0 أو NULL أفضل من القمامة. وترجم بـ -Wall، الذي يلتقط معظم الحالات:

warning: 'total' is used uninitialized in this function

المتغيّرات العامة والمتغيّرات static استثناء - فهي تُهيَّأ بصفر تلقائيًا. والاعتماد على ذلك مقبول، لكن كتابة = 0 على أي حال توثّق قصدك.

تعريف عدة متغيّرات دفعة واحدة

يمكن للمتغيّرات من النوع نفسه أن تتشارك تعريفًا واحدًا، بمهيّئات أو بدونها:

هذا مسموح وشائع للقيم المترابطة بإحكام مثل x, y, z. وهو أيضًا موضع سوء قراءة كلاسيكي:

int a = 0, b = 0, c = 0;   /* الثلاثة كلها 0 */
int d, e, f = 0;           /* f وحدها 0 - أما d وe فقمامة */

يرتبط المهيّئ باسم واحد، لا بالسطر. وحين تختلف القيم في معناها، امنح كلًّا منها سطرها - فهذا أفضل للقراءة وأفضل للمقارنة بين النسخ.

ولتعريف المؤشّرات في سطر واحد الفخّ نفسه بصورة أحدّ:

int *p, q;   /* p مؤشّر إلى int؛ وq عدد int عادي */

ترتبط * بالاسم لا بالنوع. عرّف المؤشّرات واحدًا في كل سطر.

قواعد التسمية والأعراف

قواعد المترجم:

  • حروف وأرقام وشرطات سفلية فقط.
  • لا يجوز أن يبدأ برقم: total2 مقبول، أما 2total فلا.
  • لا يجوز أن يكون كلمة مفتاحية في C: لا متغيّر باسم int أو return أو for أو double.
  • حسّاس لحالة الأحرف: count وCount وCOUNT ثلاثة متغيّرات منفصلة.
  • الأسماء التي تبدأ بشرطة سفلية، أو تحتوي على شرطتين سفليتين متتاليتين، محجوزة للتطبيق. لا تُنشئها.
int itemCount;       /* مسموح */
int item_count;      /* مسموح - وهو الأسلوب المعتاد في C */
int _count;          /* مسموح لكنه محجوز - تجنّبه */
int item-count;      /* خطأ: '-' هو عامل الطرح */
int 3rd_place;       /* خطأ: يبدأ برقم */
int float;           /* خطأ: كلمة مفتاحية */

الأعراف التي تتبعها معظم شيفرة C: snake_case للمتغيّرات والدوال، وUPPER_CASE لـالثوابت المعرَّفة بماكرو، وأسماء تقول ما تعنيه القيمة لا ما نوعها. فـ elapsed_ms أفضل من t؛ وcustomer_count أفضل من n2.

وتبقى الأسماء القصيرة مقبولة حيث يكون النطاق ضئيلًا - فـ i كعدّاد حلقة مفهومة عالميًا، ولا أحد يريد for (int loop_iteration_index = 0; ...).

أين يعيش المتغيّر

موضع تعريف المتغيّر يحدّد من يستطيع رؤيته وكم يدوم. هذا هو النطاق، وخلاصته:

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

التعريف في أي موضع (C99 فما بعد)

في C89 الأصلية، كان على كل تعريف أن يسبق أي جملة في الكتلة:

int main(void) {
    int i, sum = 0;      /* كل التعريفات أولًا */
    printf("Starting\n");
    for (i = 0; i < 10; i++) { sum += i; }
    return 0;
}

ومنذ C99 يمكنك التعريف حيث تحتاج القيمة أول مرة، بما في ذلك داخل ترويسة for:

هذا أسلوب أفضل: يقع التعريف بجوار أول استخدام له، ولا تستطيع i التسرّب إلى بقية الدالة. ترجم بـ -std=c17 (أو أي إعداد C99 فما فوق) وسيعمل في كل مكان.

الأسئلة الشائعة

كيف تعرّف متغيّرًا في لغة C؟

اكتب النوع، ثم الاسم، ثم فاصلة منقوطة: int age;. ويمكنك إعطاؤه قيمة في الوقت نفسه - int age = 30; - وهذا ما يُسمّى التهيئة وهو ما تريده في كل الأحوال تقريبًا.

ما الفرق بين تعريف المتغيّر وتهيئته؟

التعريف يحجز المساحة ويسمّيها (int count;). أما التهيئة فتضع قيمة فيها لحظة إنشائها (int count = 0;). والمتغيّر المحلّي المعرَّف دون تهيئة يحتوي على البايتات التي كانت في ذلك العنوان سلفًا - وقراءته سلوك غير معرَّف.

ما قواعد أسماء المتغيّرات في لغة C؟

حروف وأرقام وشرطات سفلية فقط؛ ولا يجوز أن يبدأ الاسم برقم؛ ولا أن يكون كلمة مفتاحية مثل int أو for؛ وهو حسّاس لحالة الأحرف، فـ total وTotal متغيّران مختلفان. والأسماء التي تبدأ بشرطة سفلية محجوزة - تجنّبها.

هل يمكن تعريف المتغيّرات في أي موضع داخل دالة C؟

منذ C99 نعم - عرّف كل متغيّر في الموضع الذي تحتاجه فيه أول مرة. أما في معيار C89 الأصلي فكان على كل تعريف أن يظهر في رأس الكتلة، ولهذا تجد في الشيفرة القديمة جدارًا من التعريفات قبل أي عمل حقيقي.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن