الثابت قيمة لها اسم ووعد بألّا تتغيّر. وتمنحك C ثلاث آليات لذلك، وهي أشياء مختلفة فعلًا في العمق - واحدة متغيّر، وواحدة استبدال نصّي، وواحدة نوع عددي صحيح.
لماذا نسمّي القيمة أصلًا
قارن هذين السطرين:
double area = 3.14159 * r * r;
double area = PI * r * r;
الثاني أفضل لثلاثة أسباب تظهر كلها لاحقًا. فهو يقول ما يعنيه العدد. ويظهر في مكان واحد بالضبط، فتغيير الدقّة تعديل واحد بدل بحث. وخطأ مطبعي في PI خطأ ترجمة، بينما خطأ مطبعي في 3.14195 جواب خاطئ.
وتُسمّى الأعداد غير المسمّاة المبعثرة في الشيفرة أرقامًا سحرية، وإزالتها من أرخص التحسينات التي يمكنك إدخالها على برنامج C.
const
تسم الكلمة المفتاحية const متغيّرًا بأنه للقراءة فقط:
أزل التعليق عن الإسناد فيرفض المترجم: assignment of read-only variable 'PI'. وذلك الخطأ وقت البناء هو المقصد كله.
والمتغيّر const متغيّر حقيقي. له نوع، فيفحص المترجم كيفية استخدامك له. ويطيع النطاق، فلا يتسرّب const داخل دالة إلى خارجها. وله عنوان، فيمكنك أخذ مؤشّر إليه. ويستطيع المنقّح أن يريك اسمه وقيمته.
ويجب تهيئة const عند نقطة التعريف - فلا فرصة ثانية:
const int limit; /* خطأ (أو قيمة مهملة للأبد): لا شيء يستطيع الإسناد إليه لاحقًا */
const int limit = 50; /* صحيح */
#define
البديل من المعالج الأولي هو الماكرو:
هذا ليس متغيّرًا. فقبل تشغيل المترجم، يستبدل المعالج الأولي كل ورود لـ PI في المصدر بالمحارف 3.14159265358979. ولا يرى المترجم الاسم إطلاقًا - وهذا بالضبط سبب كون #define بلا نوع وغير قابل للظهور في المنقّح.
لاحظ أنه لا فاصلة منقوطة ولا =. فالماكرو نصّ خام، لذا فإن هذا الخطأ الشائع:
#define MAX_USERS 100; /* لاحظ الفاصلة المنقوطة */
int limit = MAX_USERS + 1; /* يتوسّع إلى: int limit = 100; + 1; -> خطأ */
يُنتج خطأً في سطر لا يشبه التعريف إطلاقًا. وعُرف تسمية الماكروات بأحرف كبيرة موجود تحديدًا ليعرف القرّاء أن ثمّة استبدالًا يجري.
وتُكتب الماكروات عُرفًا بـ UPPER_CASE؛ وتتبعه متغيّرات const عادةً كذلك حين تقوم مقام الفكرة نفسها.
ثوابت enum
الصيغة الثالثة تمنحك مجموعة من الأعداد الصحيحة المسمّاة:
تبدأ القيم من 0 وتزيد بواحد ما لم تُسنِدها، فتكون MON صفرًا وFRI أربعة. وenum { ... } مجهولة الاسم صيغة شائعة لثوابت عددية صحيحة بسيطة.
ولثوابت enum ميزة حقيقية على البديلين: فهي ثوابت عددية صحيحة وقت الترجمة يفهمها المترجم، وهي مرئية للمنقّح. والقصة الكاملة في التعدادات في C.
أيّها تستخدم
| الحاجة | استخدم |
|---|---|
| ثابت ذو نوع ونطاق وقابل للتنقيح | const |
حجم مصفوفة، أو عنوان case، أو عرض حقل بتّي | enum أو #define |
| ثابت نصّي | #define أو const char * |
| ثابت فاصلة عائمة | const double |
| مجموعة من القيم الصحيحة المترابطة | enum |
أي شيء يجب أن يراه المعالج الأولي (#if، حرّاس التضمين) | #define |
والخيار الافتراضي ينبغي أن يكون const. والجأ إلى #define حين يجب أن توجد القيمة قبل الترجمة، وإلى enum حين تحتاج عددًا صحيحًا وقت الترجمة أو مجموعة مسمّاة.
والموضع الذي يؤلم فيه هذا فعلًا هو أحجام المصفوفات:
فـ const int في C هو متغيّر للقراءة فقط، لا تعبير ثابت وقت الترجمة. وداخل الدالة تقبل C99 صيغة int local[const_size] بجعلها مصفوفة متغيّرة الطول؛ أما على نطاق الملف أو داخل struct أو كعنوان case فتُرفض رفضًا تامًا. وenum و#define تعملان في كل مكان.
(وهذا أحد الفروق الحقيقية عن C++، حيث يكون const int تعبيرًا ثابتًا فعلًا. والشيفرات المتنقّلة بين اللغتين تتعثّر به.)
const مع المؤشّرات
تتّحد const والمؤشّرات بثلاث طرق، ومعانيها غير قابلة للتبادل. اقرأ التعريف من الاسم نحو الخارج:
const char *p; /* p تشير إلى محارف يجب ألّا تُعدَّل */
char * const q; /* q نفسها لا يمكن إعادة توجيهها؛ والمحارف قابلة للتغيير */
const char * const r; /* لا المؤشّر ولا المحارف قابلة للتغيير */
وصيغة const char * هي التي ستكتبها أكثر، لأنها النوع الصحيح لوسيط دالة يقرأ سلسلة نصية دون تغييرها:
وإضافة const إلى وسيط للقراءة فقط لا تكلّف شيئًا وتوثّق العقد بطريقة يفرضها المترجم. وهي تستحقّ الفعل في كل مكان.
القيم النصية الحرفية ثابتة فعليًا
فخّ دقيق وُجدت const لالتقاطه:
char *s = "hello"; /* صياغة C سليمة، لكن s تشير إلى ذاكرة للقراءة فقط */
s[0] = 'H'; /* سلوك غير معرَّف - انهيار عادةً */
تعيش القيم النصية الحرفية في قسم للقراءة فقط من الملف التنفيذي. والكتابة عبر ذلك المؤشّر تُنتج خطأ تجزئة عادةً. وتعريف المؤشّر بـ const char *s = "hello"; يجعل المترجم يرفض الإسناد بدل أن يدعه ينهار أثناء التشغيل.
وإن احتجت سلسلة قابلة للتعديل، فانسخها إلى مصفوفة:
الأسئلة الشائعة
كيف تعرّف ثابتًا في لغة C؟
أضف const إلى التعريف وامنحه قيمة: const double PI = 3.14159;. عندئذٍ يرفض المترجم أي محاولة للإسناد إليه. والبديل الأقدم هو ماكرو المعالج الأولي، #define PI 3.14159، وهو يجري استبدالًا نصيًا بدلًا من ذلك.
ما الفرق بين const و#define في لغة C؟
ينشئ const متغيّرًا حقيقيًا ذا نوع يعرفه المترجم، فيفحص نوعه، ويظهر في المنقّح، ويطيع النطاق. أما #define فاستبدال نصّي خالص يجري قبل الترجمة - بلا نوع وبلا نطاق ولا يمكن فحصه أثناء التنقيح. فضّل const إلّا إن احتجت القيمة في سياق يتطلّب ثابتًا وقت الترجمة.
هل يمكن استخدام متغيّر const كحجم مصفوفة في لغة C؟
ليس لمصفوفة ثابتة الحجم على نطاق الملف. فـ const int N = 10; int arr[N]; مصفوفة متغيّرة الطول في C99 (مسموحة داخل دالة، لا على نطاق الملف، ولا داخل بنية). وللحصول على حجم حقيقي وقت الترجمة، استخدم #define N 10 أو enum { N = 10 };.
ماذا تعني const char *p؟
إنها مؤشّر إلى محرف ثابت: يمكنك تغيير ما يشير إليه p، لا المحرف الذي يشير إليه. أما char * const p فالعكس - مؤشّر ثابت إلى محرف قابل للتعديل. وconst char * const p تقفل الاثنين. اقرأ التعريف انطلاقًا من الاسم نحو الخارج.