C: الثوابت وتحويل الأنواع

الثوابت أشبه بقيمة pi في الرياضيات - تُحدد مرة ولا تتغير أبدا. تحويل الأنواع أشبه بصرف العملات: للعملات المختلفة أسعار ثابتة، لكن انتبه لفقدان الدقة عند التحويل.

1. القيم الحرفية

القيمة الحرفية هي قيمة ثابتة مكتوبة مباشرة في شفرتك. لا تحتاج لحسابها - يمكنك رؤية قيمتها بنظرة.

(1) القيم الحرفية الصحيحة

C
int a = 42;
int b = 0xFF;
int c = 052;
int d = 0b101010;
الأساس البادئة مثال القيمة العشرية
عشري لا شيء 42 42
سداسي عشري 0x أو 0X 0xFF 255
ثماني 0 052 42
ثنائي (امتداد GNU لـ C99 / معيار C23) 0b أو 0B 0b101010 42
💡 نصيحة: وضع صفر قبل الرقم عن طريق الخطأ يحوله إلى ثماني. int x = 010; تساوي 8، وليس 10 - خطأ كلاسيكي للمبتدئين.

القيم الحرفية الصحيحة افتراضيا من نوع int. إذا تجاوزت القيمة نطاق int، تصبح تلقائيا long أو long long. يمكنك أيضا تحديد النوع يدويا:

اللاحقة المعنى مثال
u أو U غير موقّع 42U
l أو L long 42L
ll أو LL long long 42LL
UL unsigned long 42UL
⚠️ ملاحظة: استخدم L الكبيرة بدلا من l الصغيرة، لأن l يُسهل الخلط بينها وبين الرقم 1. 42l تبدو مثل 421.

(2) القيم الحرفية للفاصلة العائمة

C
double a = 3.14;
double b = 2.0e8;
double c = 1.5E-3;
float  d = 3.14f;

القيم الحرفية للفاصلة العائمة افتراضيا من نوع double. أضف اللاحقة f أو F لنوع float، أو l/L لنوع long double.

🔥 خطأ شائع:

C
float f = 3.14;

3.14 قيمة حرفية من نوع double. إسنادها إلى متغير float يسبب اقتطاعا ضمنيا، وقد يحذّر المترجم. الطريقة الصحيحة:

C
float f = 3.14f;

(3) القيم الحرفية للمحارف

محرف واحد محاط بعلامات اقتباس مفردة:

C
char ch = 'A';
char newline = '\n';
char zero = '\0';

نوع القيمة الحرفية للمحرف في C هو int (وليس char) - تفصيلة دقيقة يسهل تفويتها:

C
printf("%zu\n", sizeof('A'));
TEXT 📖 للعرض فقط
4

في C، sizeof('A') تساوي 4 (حجم int)، بينما في C++ تساوي 1 (حجم char). هذا اختلاف دقيق بين C و C++.

(4) القيم الحرفية للسلاسل النصية

تسلسل من المحارف محاط بعلامات اقتباس مزدوجة:

C
printf("مرحبا بالعالم!\n");

القيم الحرفية للسلاسل النصية نوعها char[]. يُضيف المترجم تلقائيا \0 كمنهي، لذا "abc" تشغل فعليا 4 بايت (a، b، c، \0).

⚠️ ملاحظة: القيم الحرفية للسلاسل النصية تُخزن في ذاكرة القراءة فقط. محاولة تعديل واحدة تسبب سلوكا غير معرف (عادة انهيار):

C
char *s = "hello";
s[0] = 'H';

2. المُعدِّل const

const هي كلمة مفتاحية في C تجعل المتغير "للقراءة فقط" - بمجرد تهيئته، لا يمكن تعديله:

C
const int MAX_SIZE = 100;
const float PI = 3.14159f;
const char NEWLINE = '\n';

محاولة تعديل متغير const تسبب خطأ ترجمة:

C
const int MAX_SIZE = 100;
MAX_SIZE = 200;
TEXT 📖 للعرض فقط
error: assignment of read-only variable 'MAX_SIZE'

(1) const والمصفوفات

هل يمكن استخدام متغير const لتحديد حجم مصفوفة؟

C
const int SIZE = 10;
int arr[SIZE];
⚠️ ملاحظة: في C89، هذا غير مسموح - أحجام المصفوفات يجب أن تكون ثوابت وقت الترجمة، و const int في C ليست ثابت وقت ترجمة (بل مجرد متغير للقراءة فقط وقت التشغيل). أدخل C99 المصفوفات متغيرة الطول (VLAs)، لذا الشفرة أعلاه تُترجم تحت C99، لكن VLAs مميزة اختيارية لا تدعمها جميع المترجمات.

إذا كنت تحتاج ثابت وقت ترجمة حقيقي لحجم مصفوفة، استخدم #define أو enum.

(2) const والمؤشرات

الجمع بين const والمؤشرات تحد كلاسيكي في C. هناك ثلاث حالات:

C
int value = 42;

const int *p1 = &value;
int * const p2 = &value;
const int * const p3 = &value;
التعريف المعنى
const int *p البيانات المُشار إليها لا يمكن تعديلها؛ المؤشر نفسه يمكنه الإشارة لمكان آخر
int * const p المؤشر نفسه لا يمكن تعديله (عنوان ثابت)؛ البيانات المُشار إليها يمكن تغييرها
const int * const p لا المؤشر ولا البيانات المُشار إليها يمكن تعديلها
💡 نصيحة: انظر إلى أي جانب من * تقع const. إذا كانت على اليسار، فهي تعدّل البيانات. إذا كانت على اليمين، فهي تعدّل المؤشر.


3. ثوابت وحدات الماكرو #define

#define توجيه معالج أولي يُستخدم لتعريف ثوابت وحدات الماكرو:

C
#define PI 3.14159
#define MAX_SIZE 100
#define NEWLINE '\n'

يستبدل المعالج الأولي جميع تكرارات PI بـ 3.14159 قبل الترجمة. ليست متغيرا - لا تشغل ذاكرة ولا لديها فحص أنواع.

(1) #define مقابل const

الميزة #define const
متى تُعالج مرحلة المعالجة الأولية (استبدال نصي) مرحلة الترجمة
فحص الأنواع لا نعم
النطاق من التعريف حتى نهاية الملف تتبع قواعد نطاق المتغيرات
التنقيح المنقح لا يمكنه رؤية اسم الماكرو المنقح يمكنه رؤية متغير const
يمكن أخذ عنوانها لا نعم
💡 نصيحة: فضّل const - لديها فحص أنواع وقيود نطاق، ما يجعلها أكثر أمانا. استخدم #define لعلامات الترجمة الشرطية أو حين تحتاج ثوابت وقت الترجمة.

(2) مزالق وحدات الماكرو الشائعة

C
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;

قد تتوقع 3 + 3 * 2 = 12، لكن الماكرو يتوسع إلى 3 + 3 * 2 = 9. وحدات الماكرو هي استبدال نصي بحت - لا تحترم أسبقية المعاملات.

الحل: أضف أقواسا للماكرو:

C
#define DOUBLE(x) ((x) + (x))

4. ثوابت التعدادات

enum طريقة أخرى لتعريف ثوابت أعداد صحيحة:

C
enum Color {
    RED,
    GREEN,
    BLUE
};

افتراضيا، تبدأ القيم من 0 وتتزايد: RED = 0، GREEN = 1، BLUE = 2. يمكنك أيضا تعيين القيم يدويا:

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

TUE = 2، WED = 3، ...، SUN = 101 (كل قيمة لاحقة أكبر بواحد من السابقة).

قيم التعدادات هي ثوابت وقت ترجمة حقيقية، صالحة لأحجام المصفوفات وسيناريوهات مشابهة. مقارنة بـ #define، تحمل التعدادات معلومات النوع ومرئية بالاسم في المنقح - فضّلها حينما أمكن.


5. التحويل الضمني للأنواع

عند خلط أنواع مختلفة في تعبير، تقوم C تلقائيا بتحويل الأنواع. فهم قواعد التحويل الضمني مفتاح لتجنب الأخطاء.

(1) الترويج الصحيح

في التعابير، يُروّج char و short تلقائيا إلى int قبل أي عملية:

C
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
TEXT 📖 للعرض فقط
4

نتيجة a + b هي int (4 بايت)، وليس char (1 بايت).

(2) التحويل الحسابي

عند جمع قيم من أنواع مختلفة في عملية، يُحوّل النوع الأدنى تلقائيا إلى النوع الأعلى. التسلسل الهرمي للتحويل من الأدنى إلى الأعلى:

TEXT 📖 للعرض فقط
int → unsigned int → long → unsigned long → long long → unsigned long long → float → double → long double

القاعدة: قبل عملية، يُحوّل المعامل ذو الرتبة الأدنى تلقائيا إلى الرتبة الأعلى.

C
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
TEXT 📖 للعرض فقط
2.5

يُحوّل a تلقائيا إلى double، ثم تُنفذ قسمة الفاصلة العائمة، والنتيجة 2.5.

(3) تحويل الإسناد

أثناء الإسناد، تُحوّل القيمة في الطرف الأيمن تلقائيا إلى نوع الطرف الأيسر:

C
int x = 3.14;
double y = 42;

3.14 المُسندة إلى int تُقتطع إلى 3؛ 42 المُسندة إلى double تصبح 42.0.

⚠️ ملاحظة: عند إسناد قيمة فاصلة عائمة إلى نوع صحيح، يُتجاهل الجزء الكسري ببساطة (بدون تقريب):

C
int n = 2.9;

n تساوي 2، وليس 3.

(4) فخ القسمة الصحيحة

هذا أكثر المزالق شيوعا للمبتدئين:

C
int a = 5;
int b = 2;
double result = a / b;
printf("%.1f\n", result);
TEXT 📖 للعرض فقط
2.0

تتوقع 2.5، لكنك تحصل على 2.0. السبب: a / b قسمة صحيحة، نتيجتها 2 (عدد صحيح)، الذي يُحوّل بدوره إلى القيمة double وهي 2.0.

الحل: اجعل أحد المعاملات على الأقل عددا عشريا:

TEXT 📖 للعرض فقط
double result = (double)a / b;

أو:

C
double result = a * 1.0 / b;

أو:

C
double result = 5.0 / 2;

6. التحويل الصريح للأنواع

حين لا يقوم التحويل الضمني بما تحتاجه، يمكنك استخدام التحويل الصريح لتخبر المترجم بالضبط بالنوع الذي تريده:

(1) الصياغة

C
(type_name)expression

▶ مثال

C
#include <stdio.h>

int main(void) {
    int a = 7;
    int b = 2;

    printf("قسمة صحيحة: %d\n", a / b);
    printf("مع التحويل: %.2f\n", (double)a / b);

    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
قسمة صحيحة: 3
مع التحويل: 3.50

(double)a تحول a أولا إلى 7.0. ثم، عند جمعها مع b، يُروّج b أيضا ضمنيا إلى double، وتُنفذ قسمة الفاصلة العائمة.

▶ مثال

C
#include <stdio.h>
#include <math.h>

int main(void) {
    double x = 3.7;

    printf("اقتطاع مباشر: %d\n", (int)x);
    printf("تقريب: %d\n", (int)(x + 0.5));
    printf("دالة round(): %.0f\n", round(x));

    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
اقتطاع مباشر: 3
تقريب: 4
دالة round(): 4
💡 نصيحة: يحدد C99 أن اقتطاع الفاصلة العائمة إلى عدد صحيح يُقرّب نحو الصفر. لذا (int)(-3.7) نتيجتها -3، وليس -4. هذا يختلف عن "التقريب نحو الأسفل" (نحو السالب اللانهائي)، لذا كن حذرا.


7. مزالق تحويل الأنواع الشائعة

(1) خلط الموقّعة وغير الموقّعة

C
int a = -5;
unsigned int b = 10;

if (a + b > 0) {
    printf("أكبر من 0\n");
} else {
    printf("أصغر من أو يساوي 0\n");
}
TEXT 📖 للعرض فقط
أكبر من 0

قد تظن أن -5 + 10 = 5 > 0 صحيح، لكن سلوك هذه الشفرة يعتمد على قواعد التحويل الضمني. عند خلط int و unsigned int في عملية، يُحوّل int إلى unsigned int. -5 تصبح عددا موجبا كبيرا جدا عند تفسيرها كغير موقّعة، فالنتيجة فعلا أكبر من 0 - لكن هذا ربما ليس المنطق الذي قصدته.

⚠️ ملاحظة: لا تخلط أبدا الأعداد الموقّعة وغير الموقّعة في العمليات. إذا كان لا بد، استخدم التحويل الصريح.

(2) فقدان الدقة

C
int big = 123456789;
float f = big;
printf("%d\n", big);
printf("%.0f\n", f);
TEXT 📖 للعرض فقط
123456789
123456792

float تمتلك فقط 6-7 أرقام معنوية ولا يمكنها تمثيل عدد صحيح من 9 أرقام بدقة - الثلاثة أرقام الأخيرة تُفقد. كن حذرا جدا عند إسناد أعداد صحيحة كبيرة إلى float.

(3) تحويل التجاوز

C
char ch = 200;
printf("%d\n", ch);

على المنصات التي يكون فيها char موقّعا، 200 تتجاوز نطاق char (-128 إلى 127)، ما يؤدي إلى سلوك غير معرف - ربما تُطبع -56. إذا كنت تحتاج تخزين قيم من 0 إلى 255، استخدم unsigned char.

(4) تحويل المؤشرات

التحويل بين مؤشرات من أنواع مختلفة خطير:

C
int value = 0x41424344;
char *p = (char *)&value;
printf("%c\n", *p);

المخرجات تعتمد على ترتيب بايتات النظام (كبير النهاية أو صغير النهاية) وغير قابلة للنقل. هذا النوع من التحويل يُستخدم فقط في البرمجة منخفضة المستوى ويجب أن يتجنبه المبتدئون.


❓ أسئلة شائعة

س هل أستخدم #define أم const؟
ج فضّل const. لديها فحص أنواع وتتبع قواعد النطاق وصديقة للمنقح. #define استبدال نصي بحت بلا سلامة أنواع، لكنها ثابت وقت ترجمة يمكن استخدامه لأحجام المصفوفات والترجمة الشرطية. إذا كانت القيمة يجب أن تكون متاحة وقت الترجمة (مثل حجم مصفوفة)، استخدم #define أو enum؛ وإلا فاستخدم const.
س لماذا 5/2 تساوي 2 وليس 2.5؟
ج لأن كليهما 5 و 2 من نوع int، وتحدد C أن القسمة الصحيحة تُنتج عددا صحيحا - يُتجاهل الجزء الكسري. للحصول على 2.5، اجعل أحد المعاملات عشريا على الأقل: 5.0/2 أو (double)5/2. هذا أحد أكثر فخاخ المبتدئين شيوعا في C.
س هل التحويل يغير قيمة المتغير الأصلي؟
ج لا. التحويل يُنتج فقط قيمة محولة مؤقتة - قيمة ونوع المتغير الأصلي يبقيان دون تغيير. مثلا، (int)3.7 تُنتج القيمة المؤقتة 3، لكن 3.7 تبقى 3.7.
س لماذا خلط العمليات الموقّعة وغير الموقّعة يُنتج أخطاء كثيرة؟
ج لأن معيار C ينص على أنه عند الخلط، تُحوّل القيمة الموقّعة ضمنيا إلى غير موقّعة. العدد السالب يصبح قيمة موجبة كبيرة جدا كغير موقّع، ما يجعل المقارنات والحسابات تُنتج نتائج غير بديهية تماما. تجنب الخلط، أو استخدم التحويل الصريح قبل العملية.

📖 ملخص

📝 تمارين

  1. اكتب برنامجا يُخرج العدد 255 بالصيغ العشرية والسداسية عشرية والثمانية، ولاحظ صيغ المخرجات
  2. اكتب برنامجا لحساب مساحة دائرة (نصف القطر كـ int بقيمة 10، و pi معرّفة كـ const double)، مع الحذر من فخ القسمة الصحيحة
  3. اكتب برنامجا لاختبار التالي وسجل النتائج: قيمة (int)3.9، وقيمة (int)(-3.9)، ومخرجات %d بعد char ch = 128. اشرح كل نتيجة
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%