لكل متغيّر في C نوع، تختاره حين تعرّفه ويثبت مدى حياته. ويقرّر النوع ثلاثة أشياء: كم بايتًا يشغل المتغيّر، وكيف تُفسَّر تلك البايتات، وأي العمليات لها معنى عليه.
وقائمة أنواع C قصيرة. والتعقيد في المُعدِّلات.
الأنواع الأساسية الأربعة
charيحمل بايتًا واحدًا. ويُستخدم للمحارف المفردة ('A')، ولبايتات السلسلة النصية، وأحيانًا كعدد صحيح صغير جدًا.intهو حصان العمل للأعداد الصحيحة. عدّادات الحلقات والأحجام والمعرّفات.floatو**double** يحملان الأعداد الحقيقية. وdoubleذو دقّة تقارب الضعف.voidهو النوع الرابع بمعنى ما، لكنه يعني "لا قيمة" - فلا يمكنك تعريف متغيّرvoid، بل استخدامه فقط كنوع إرجاع دالة أو قائمة وسائط فارغة أو مؤشّر بلا نوع.
لاحظ اللاحقة f في 3.14f. فبدونها تكون القيمة الحرفية من نوع double، وإسنادها إلى float يفقد الدقّة بصمت. وبعض المترجمات تحذّر من ذلك.
المُعدِّلات: short وlong وunsigned
تأتي الأنواع الصحيحة الأساسية مع مُعدِّلات تغيّر الحجم أو الإشارة.
لكل نوع صحيح محدّد تنسيق خاص به: %hd لـ short، و%d لـ int، و%ld لـ long، و%lld لـ long long، و%u لغير الموقّع. واستخدام الخاطئ منها سلوك غير معرَّف، لا خطأ تقريب.
unsigned تزيل بتّ الإشارة وتضاعف المدى الموجب. فـ unsigned int يحمل من 0 إلى نحو 4.3 مليار بدل -2.1 إلى +2.1 مليار. وهو الخيار الصحيح لما لا يمكن أن يكون سالبًا فعلًا - الأحجام، وعدد البايتات، وأنماط البتّات - وفخّ لأي شيء قد يُطرح إلى ما دون الصفر.
وsigned هي الافتراض لـ int وshort وlong وlong long، فلا تكتبها تقريبًا أبدًا. والموضع الوحيد الذي تهمّ فيه هو char: فكون char المجرّد موقّعًا أو غير موقّع يحدّده التطبيق، لذا اكتب signed char أو unsigned char حين تهمّ الإشارة.
الأحجام: ما تحصل عليه فعلًا
يحدّد معيار C حدودًا دنيا، لا أحجامًا مضبوطة. وعلى أي جهاز حديث بـ 64 بتًا يعمل بلينكس أو ماك أو ويندوز سترى هذا:
| النوع | الحجم المعتاد | المدى المعتاد |
|---|---|---|
char | 1 بايت | -128 إلى 127 (أو 0 إلى 255) |
short | 2 بايت | -32,768 إلى 32,767 |
int | 4 بايت | -2,147,483,648 إلى 2,147,483,647 |
long | 8 بايت (4 على ويندوز) | نحو ±9.2 كوينتيليون |
long long | 8 بايت | نحو ±9.2 كوينتيليون |
float | 4 بايت | ~7 أرقام معنوية |
double | 8 بايت | ~15 رقمًا معنويًا |
long double | 16 بايت (يتباين) | أكثر من double |
وصفّ long هو الذي يوقع الناس: فهو 8 بايتات على لينكس وماك، و4 بايتات على ويندوز بـ 64 بتًا. والشيفرة التي تفترض أن long يمكنه حمل قيمة بـ 64 بتًا ليست محمولة. استخدم long long، أو الأنواع مضبوطة العرض من stdint.h (int32_t وuint64_t) حين يكون الحجم جزءًا من المتطلّب.
sizeof: اسأل المترجم
لا تخمّن حجمًا أبدًا - قِسْه:
sizeof عامل لا دالة، ويُقيَّم وقت الترجمة. ويُنتج قيمة من نوع size_t، تُطبع بـ %zu.
وsizeof(char) مضمون أن يساوي 1 بالضبط - فذلك هو تعريف البايت في C. وكل ما عداه يُقاس نسبةً إليه.
limits.h وfloat.h
المدَيات المضبوطة لمترجمك متاحة كثوابت مسمّاة:
هذه هي القيم التي تقارن بها حين تحتاج أن تعرف هل ستفيض عملية ما. وفحص if (a > INT_MAX - b) قبل حساب a + b هو كيف تكتشف الفيضان قبل وقوعه - وهو أمر مهمّ، لأنك لا تستطيع اكتشاف فيضان القيم الموقّعة بعده.
فيضان الأعداد الصحيحة
ما يحدث حين تتجاوز قيمة مدى نوعها يعتمد كليًا على الإشارة.
فيضان غير الموقّع معرَّف: تلتفّ القيمة بمقياس 2^N.
والحالة الثانية مصدر حقيقي للعلل. فحلقة مثل for (unsigned i = n - 1; i >= 0; i--) لا تنتهي أبدًا، لأن القيمة غير الموقّعة دائمًا >= 0.
وفيضان الموقّع سلوك غير معرَّف. ليس "يلتفّ" - بل غير معرَّف. ويُسمَح للمترجم بافتراض أنه لا يحدث أبدًا والتحسين على ذلك الأساس، ما يعني أن فحص فيضان مكتوبًا بعد وقوعه يمكن حذفه:
int sum = a + b;
if (sum < a) { /* قد يحذف المترجم هذا كليًا */ }
افحص قبلًا، باستخدام الحدود:
if (b > 0 && a > INT_MAX - b) {
/* a + b ستفيض - عالج ذلك */
}
دقّة الفاصلة العائمة
يخزّن float وdouble الأعداد ثنائيًا، ومعظم الكسور العشرية ليس لها صيغة ثنائية مضبوطة - تمامًا كما أن 1/3 ليس له صيغة عشرية مضبوطة.
والقاعدة المترتّبة: لا تقارن أبدًا قيم الفاصلة العائمة بـ ==. قارن الفرق المطلق بهامش تسامح صغير بدلًا من ذلك.
ولا تستخدم الفاصلة العائمة للنقود أبدًا. خزّن السنتات كعدد صحيح؛ فـ long long من السنتات مضبوط حيث لا يكون double من الدولارات كذلك.
اختيار النوع
قائمة قرار قصيرة تغطّي معظم الشيفرات:
- الأعداد الصحيحة:
intما لم يكن لديك سبب. فهو النوع الذي يعالجه المعالج بأعلى كفاءة، والنوع الذي بُنيت حوله كل قواعد الحساب. - أي شيء أكبر من ملياري وحدة:
long long، أوint64_tمنstdint.h. - الأحجام والأطوال وفهارس المصفوفات من
sizeofأوstrlen:size_t. فهو غير موقّع ومضمون أن يتّسع لأي كائن. - الأعداد العشرية:
double. واستخدمfloatفقط لتنصيف الذاكرة في المصفوفات الكبيرة أو على عتاد مضمّن بلا وحدة دقّة مزدوجة. - المحارف المفردة والبايتات الخام:
charللنصّ، وunsigned charللبيانات الثنائية. - صحيح/خطأ:
boolمنstdbool.h- انظر القيم المنطقية في C. - عروض بتّات مضبوطة (صيغ الملفات، بروتوكولات الشبكة، مسجّلات العتاد):
stdint.h-uint8_tوint16_tوuint32_tوهكذا.
خلط الأنواع
حين تدمج نوعين مختلفين في تعبير واحد، تحوّلهما C من خلف ظهرك قبل إجراء الحساب. وذلك مفيد عادةً وكارثي أحيانًا:
الأول قسمة صحيحة: فكلا المعاملين int، فتكون النتيجة int ويُسقَط الكسر. والثالث أسوأ - فمقارنة قيمة موقّعة بأخرى غير موقّعة تحوّل الموقّعة إلى غير موقّعة، فتحيل -1 إلى عدد موجب ضخم.
وتلك القواعد التحويلية، وكيفية السيطرة عليها بتحويلات صريحة، هي موضوع تحويل الأنواع في C.
الأسئلة الشائعة
ما أنواع البيانات الأساسية في لغة C؟
أربعة أنواع أساسية: char للمحارف المفردة والبايتات، وint للأعداد الصحيحة، وfloat وdouble للأعداد العشرية. وتغيّر المُعدِّلات حجمها وإشارتها - short وlong وlong long وsigned وunsigned - فتُنتج المجموعة الكاملة.
كم بايتًا يشغل int في لغة C؟
أربعة بايتات (32 بتًا) في الغالب الأعمّ على أنظمة سطح المكتب والخوادم الحديثة، بمدى يقارب -2.1 مليار إلى 2.1 مليار. ولا يضمن المعيار سوى بايتين على الأقل، والمتحكّمات الدقيقة ذات 16 بتًا تستخدم بايتين فعلًا. استخدم sizeof(int) إن احتجت اليقين.
ما الفرق بين float وdouble في لغة C؟
float أربعة بايتات بنحو 7 أرقام عشرية معنوية؛ وdouble ثمانية بايتات بنحو 15. وdouble هو الافتراضي للقيم الحرفية ذات الفاصلة العائمة وللدوال الرياضية، وما لم تكن تخزّن ملايين القيم أو تستهدف رقاقة مضمّنة، فإن double هو الخيار الصحيح.
ماذا يحدث حين يفيض int في لغة C؟
بالنسبة إلى int موقّع، الفيضان سلوك غير معرَّف - فقد يلتفّ المترجم أو يشبع القيمة أو يحذف الفحص كليًا بالتحسين. أما بالنسبة إلى int غير موقّع فهو معرَّف تمامًا: تلتفّ القيمة بمقياس 2^N، فتكون UINT_MAX + 1 صفرًا. لا تعتمد أبدًا على فيضان القيم الموقّعة.