اذهب إلى المحتوى

تهيئة المتغيرات وأنواع البيانات في لغة سي C


Naser Dakhel

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

أنواع التهيئة في لغة سي

هناك نوعان من التهيئة؛ تهيئة عند وقت التصريف compile time وتهيئة عند وقت التشغيل run time، ويعتمد النوع الذي ستحصل عليه على مدة التخزين storage duration للشيء الذي يُهيّأ.

يُصرح عن الكائنات ذات المدة الساكنة static duration إما خارج الدوال، أو داخلها باستخدام الكلمة المفتاحية extern أو static على أنها جزءٌ من التصريح، ويُهيّأ هذا النوع عند وقت التصريف فقط.

لجميع الكائنات الأخرى مدةٌ تلقائية automatic duration، يمكن تهيئتها فقط عند وقت التشغيل، إذ أن التصنيفين حصريان فيما بينهما.

على الرغم من ارتباط مدة التخزين بالربط (انظر مقال الربط) إلا أنهما مختلفان ويجب عدم الخلط فيما بينهما.

يمكن استخدام التهيئة عند وقت التصريف فقط في حال استخدام التعابير الثابتة constant expressions، بينما يمكن استخدام التهيئة عند وقت التشغيل لأي تعبير كان، وقد أُلغي قيد لغة سي القديمة الذي ينص على إمكانية تهيئة المتغيرات البسيطة وحدها عند وقت التشغيل، وليس المصفوفات، أو الهياكل، أو الاتحادات.

التعابير الثابتة

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

يؤكد المعيار على وجوب تقييم الأعداد الحقيقية عند وقت التصريف بدقة ونطاق مماثلين لحالة تقييمهم في وقت التشغيل. يوجد هناك طريقةٌ محدودة أكثر تدعى تعابير الأعداد الصحيحة الثابتة integral constant expressions، ولهذه التعابير نوع عدد صحيح وتحتوي على معاملات operands من نوع عدد صحيح ثابت أو معدّدات ثابتة enumeration constants، أو محارف ثابتة، بالإضافة إلى تعابير sizeof والأعداد الحقيقية الثابتة التي تكون معاملات لتحويل الأنواع casts، ويسمح لعوامل تحويل الأنواع بتحويل الأنواع الحسابية إلى أنواع صحيحة فقط. لا تُطبّق أي قيود على محتويات تعابير sizeof طبقًا لما سبق قوله (يُقيّم نوع التعبير وليس قيمته).

يُشابه التعبير الحسابي الثابت arithmetic constant expression التعبير الصحيح الثابت، ولكنه يسمح باستخدام الأعداد الصحيحة الثابتة، ويحدّ من استخدام تحويل الأنواع بالتحويل من نوع حسابي إلى آخر.

العنوان الثابت address constant هو مؤشر يشير إلى كائن ذي مدة تخزين ساكنة أو إلى مؤشر يشير إلى دالةٍ ما، ويمكنك الحصول على هذه العناوين باستخدام العامل "&" أو باستخدام التحويلات الاعتيادية للمصفوفات وأسماء الدوال إلى مؤشرات عندما تُستخدم ضمن تعبير، ويمكن استخدام كلٍ من العوامل "[]" و "." و "<-" و "&" و "*" ضمن التعبير طالما لا يتضمن ذلك الاستخدام محاولة للوصول لقيمة أي كائن.

استكمال عن التهيئة

يُسمح لأنواع الثوابت المختلفة بالتواجد في العديد من الأماكن، إلا أن تعابير الأعداد الصحيحة الثابتة شديدة الأهمية على وجه الخصوص لأنها من النوع الوحيد الذي قد يُستخدم لتحديد حجم المصفوفة وقيم ما يسبق تعليمة case. تتميز أنواع الثوابت المسموح باستخدامها مثل قيمة أولية لتهيئة التعابير بأنها أقل محدودية، إذ من المسموح لك باستخدام تعابير حسابية ثابتة، أو مؤشر فراغ، أو عنوان ثابت، أو عنوان ثابت لكائن زائد أو ناقص تعبير صحيح ثابت، ويعتمد الأمر طبعًا على نوع الشيء الذي يجري تهيئته سواءٌ كان نوع تعبير ثابت محدد مناسبًا أم لا.

إليك مثالًا يحتوي على تهيئة لعدة متغيرات:

#include <stdio.h>
#include <stdlib.h>

#define NMONTHS 12

int month = 0;

short month_days[] =
      {31,28,31,30,31,30,31,31,30,31,30,31};

char *mnames[] ={
      "January", "February",
      "March", "April",
      "May", "June",
      "July", "August",
      "September", "October",
      "November", "December"
};

main(){

      int day_count = month;

      for(day_count = month; day_count < NMONTHS;
              day_count++){
              printf("%d days in %s\n",
                      month_days[day_count],
                      mnames[day_count]);
      }
      exit(EXIT_SUCCESS);
}

مثال 1

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

تهيئة المصفوفات أحادية البعد بسيطةٌ أيضًا، إذ عليك فقط كتابة قائمة بالقيم التي تريدها وتفصل كل قيمة بفاصلة داخل أقواس معقوصة؛ ويوضّح المثال التالي طريقة تهيئة المصفوفة، ويُحدّد حجم المصفوفة طبقًا للقيم الأولية الموجودة إن لم تحدد حجم المصفوفة على نحوٍ صريح، أما إذا حددت الحجم صراحةً، فيجب أن يكون عدد القيم الأولية التي تزودها يساوي أو أصغر من الحجم، إذ يسبب إضافة قيم أكبر من حجم المصفوفة خطئًا، بينما ستهيّئ القيم الموجودة -وإن كانت قليلة- العناصر الأولى من المصفوفة.

يمكنك بناء سلسلة نصية يدويًا بالطريقة:

char str[] = {'h', 'e', 'l', 'l', 'o', 0};

يمكن أيضًا استخدام سلسلة نصية ضمن علامتي تنصيص لتهيئة مصفوفة من المحارف:

char str[] = "hello";

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

/* لا يوجد مكان للمحرف الفارغ */
char str[5] = "hello";

/* يوجد مكان للمحرف الفارغ */
char str[6] = "hello";

استخدم البرنامج في مثال 1 السلاسل النصية لأهداف مختلفة، إذ كان استخدامهم بهدف تهيئة مصفوفة من مؤشرات تشير إلى محارف، وهذا استخدام مختلفٌ تمامًا.

يمكن استخدام تعبير من نوع مناسب لتهيئة هياكل من نوع مدة تلقائية، وإلا فيجب استخدام قائمة تحتوي على تعابير ثابتة بين قوسين على النحو التالي:

#include <stdio.h>
#include <stdlib.h>

struct s{
      int a;
      char b;
      char *cp;
}ex_s = {
      1, 'a', "hello"
      };

main(){
      struct s first = ex_s;
      struct s second = {
              2, 'b', "byebye"
              };

      exit(EXIT_SUCCESS);
}

مثال 2

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

يحدث تجاهلٌ للأعضاء عديمة الاسم ضمن الهيكل أو الاتحاد خلال عملية التهيئة، سواءٌ كانت حقول بتات bitfields، أو مسافات فارغة لمحاذاة عنوان التخزين، فمن غير المطلوب أخذهم بالحسبان عندما تختار القيم الأولية لأعضاء الهيكل الحقيقية (ذات الاسم).

هناك طريقتان لكتابة القيم الأولية لكائنات تحتوي على كائنات فرعية بداخلها، فيمكن كتابة قيمة أولية لكل عضو بالطريقة:

struct s{
      int a;
      struct ss{
              int c;
              char d;
      }e;
}x[] = {
      1, 2, 'a',
      3, 4, 'b'
      };

مثال 3

سيُسنِد ما سبق القيمة 1 إلى x[0].a و 2 إلى x[0].e.c و a إلى x[0].e.d و 3 إلى x[1].a وهكذا.

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

struct s{
      int a;
      struct ss{
              int c;
              char d;
      }e;
}x[] = {
      {1, {2, 'a'}},
      {3, {4, 'b'}}
      };

مثال 4

استخدم الأقواس دائمًا، لأن هذه الطريقة آمنة، والأمر مماثل بالنسبة للمصفوفات بكونها هياكل:

float y[4][3] = {
      {1, 3, 5},      /* y[0][0], y[0][1], y[0][2] */
      {2, 4, 6},      /* y[1][0], y[1][1], y[1][2] */
      {3, 5, 7}       /* y[2][0], y[2][1], y[2][2] */
};

مثال 5

تُهيّأ قيم الأسطر الثلاث الأولى كاملةً من y، ويبقى السطر الرابع y[3]‎ غير مُهيّأ.

تُسند لجميع الكائنات ذات المدة الساكنة قيمٌ أولية ضمنية تلقائية إذا لم يوجد أي قيمة أولية لها، ويكون أثر القيمة الأولية التلقائية الضمنية مماثلًا لأثر إسناد القيمة 0 الثابتة، وهذا الأمر شائع الاستخدام، وتفترض معظم برامج لغة سي نتيجةً لذلك أن الكائنات الخارجية والكائنات الساكنة الداخلية تبدأ بالقيمة صفر.

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

يمكن أن يُستخدم التصريح داخل دالة ما (نطاق مرتبط بكتلة الدالة) للإشارة إلى كائن ذي ربطٍ خارجي External linkage أو ربط داخلي Internal linkage باستخدام عدّة طرق تطرقنا إليها في مقال الربط والنطاق وهناك مزيدٌ من الطرق التي سنتطرق إليها لاحقًا. إذا اتبعت الطرق السابقة (التي من المستبعد أن تتحقق من قبيل الصدفة)، فلا يمكنك تهيئة الكائن على أنه جزء من التصريح، وإليك الطريقة الوحيدة التي تستطيع تحقيق ذلك بها:

int x;                        /* ربط خارجي */
main(){
      extern int x = 5;       /* استخدام ممنوع */
}

لم يكشف مصرّفنا التهيئة الممنوعة في هذا المثال أيضًا.

ترجمة -وبتصرف- لقسم من الفصل Structured Data Types من كتاب The C Book.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...