عندما يتعامل الكثيرون مع لغة C لأول مرة، يشعرون أنها لغة "صلبة" بعض الشيء.

لا توجد بها الكثير من التغليفات الجاهزة، ولا الكثير من السكريات النحوية، بل يجب كتابة العديد من الأشياء خطوة بخطوة.

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

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

إذا تم فهم هذه الأساسيات بشكل صحيح، فسيكون فهمك أكثر صلابة سواء واصلت تعلم C++ أو Java أو Python لاحقًا، أو تعاملت مع أنظمة التشغيل أو الأنظمة المضمنة أو هياكل البيانات.

ستقوم هذه المقالة بربط أساسيات لغة C الأساسية معًا وفقًا للترتيب الأسهل للمبتدئين لبناء الإدراك، مما يساعدك على بناء إطار معرفي متكامل.


أولاً، عند تعلم لغة C، تمسك بثلاثة محاور رئيسية أولاً

عند البدء في تعلم لغة C، أسهل خطأ يمكن ارتكابه هو تعلمها وكأنها "حفظ كلمات".

اليوم تحفظ if، وغدًا تحفظ for، وبعد غد تذهب لحفظ scanf والمصفوفات والمؤشرات.

يبدو أنك تعلمت الكثير، لكن في الواقع ما في ذهنك إلا رمال متناثرة.

الطريقة الأفضل هي التمسك بثلاثة محاور رئيسية أولاً:

الأول، كيف تُخزّن البيانات.

المتغيرات والمصفوفات والهياكل والسلاسل النصية، كلها في جوهرها تحل مشكلة "أين توضع المعلومات".

الثاني، كيف يتحكم البرنامج في سير التنفيذ.

التنفيذ التسلسلي والتفرع الشرطي والتكرار الحلقي، كلها تجيب عن "كيف تُنجز الأمور خطوة بخطوة".

الثالث، كيف يجد البرنامج البيانات.

سيمتد هذا عبر scanf وتمرير معاملات الدوال والمصفوفات والمؤشرات وحتى عمليات الملفات.

طالما تتعلم متبعًا هذه المحاور الرئيسية الثلاثة، سترتبط العديد من نقاط المعرفة التي تبدو متفرقة بشكل طبيعي.


ثانيًا، المتغيرات: لماذا يستطيع البرنامج "تذكر" البيانات

لكي يعالج البرنامج البيانات، يجب أولاً أن يكون قادرًا على تخزينها.

والمتغيرات هي أبسط "وحدات التخزين" في البرنامج.

على سبيل المثال:

int age = 18;

هذا السطر من الكود يعني: تعريف متغير صحيح اسمه age وتخزين القيمة 18 فيه.

هناك مفهومان أساسيان يجب فهمهما بوضوح أولاً:المُعرّفات وأنواع البيانات.

1. المُعرّفات: تسمية البيانات

أسماء المتغيرات وأسماء الدوال وأسماء المصفوفات، تُعرف مجتمعة بالمُعرّفات.

المُعرّفات لا تُكتب عشوائيًا، بل يجب أن تستوفي قواعد أساسية:

  • تتكون من أحرف وأرقام وشرطة سفلية
  • لا يمكن أن تبدأ برقم
  • لا يمكن أن تتطابق مع الكلمات المفتاحية
  • حساسة لحالة الأحرف

على سبيل المثال:

int score;
int _num;
int age1;

هذه الكتابات كلها صحيحة.

لكن الأمثلة التالية غير قانونية:

int 2a;
int if;

لذا، فإن الخطوة الأولى لتعلم تعريف المتغيرات هي في الواقع تعلم إعطاء البيانات اسمًا قانونيًا وواضحًا.

2. أنواع البيانات: تحدد ما يمكن للمتغير احتواؤه

لغة C ليست "يمكنك أخذ أي متغير ووضع أي شيء فيه"، بل يجب التصريح عن نوع كل متغير أولاً.

الأنواع الأساسية الشائعة هي:

int a = 10;       // 整数
float b = 3.14;   // 浮点数
char c = 'A';     // 字符

هذا يعني أن للمتغير ليس فقط اسمًا، بل أيضًا قيدًا يحدد "ما الذي يُسمح بوضعه في هذا الصندوق".

في مرحلة التعلم الأولية، يجب بناء هذا الوعي، لأنه سيؤثر على كل عمليات الإدخال والإخراج والعمليات الحسابية وفهم الذاكرة لاحقًا.


ثالثًا، الإدخال والإخراج: كيف يتفاعل البرنامج مع الإنسان

البرنامج لا يقوم فقط بالحسابات بصمت داخليًا، بل يجب أن يتعامل مع الناس أيضًا.

أكثر طرق التفاعل شيوعًا هي الإدخال والإخراج.

1. الإخراج: عرض النتائج

أكثر دوال الإخراج شيوعًا في لغة C هي printf.

على سبيل المثال:

printf("Hello C!\n");

إذا أردت إخراج متغير، فستحتاج لاستخدام محددات التنسيق:

int age = 18;
printf("%d\n", age);

هنا %d تعني "الإخراج بتنسيق عدد صحيح".

محددات التنسيق الشائعة هي:

  • %d: عدد صحيح
  • %f: عدد فاصلة عائمة
  • %c: حرف
  • %s: سلسلة نصية

على سبيل المثال:

float price = 19.9;
char ch = 'A';

printf("%f\n", price);
printf("%c\n", ch);

printf الأهم فيها ليس حفظ الرموز، بل فهم أن:

عندما يُخرج البرنامج بيانات، يجب أن يعرف بأي نوع سيفسرها.

2. الإدخال: لماذا غالبًا ما تأتي scanf مع &

إذا كانت printf هي "عرض المحتوى"، فإن scanf هي "قراءة ما يدخله المستخدم".

على سبيل المثال:

int age;
scanf("%d", &age);

معنى هذا السطر من الكود هو: قراءة عدد صحيح من لوحة المفاتيح وتخزينه في المتغير age.

قد يتساءل البعض، لماذا نكتب هنا &age وليس age؟

السبب بسيط:

scanf لا تريد قراءة القيمة الحالية لـ age، بل تريد "كتابة المحتوى الذي أدخله المستخدم في المكان الذي توجد فيه age".

لذا فهي لا تحتاج القيمة، بل العنوان.

بمعنى أن:

  • age يمثل القيمة الموجودة في المتغير
  • &age يمثل عنوان المتغير في الذاكرة

علامة & هذه، في الواقع، قد قادتك بالفعل إلى التفكير الجوهري الأعمق في لغة C:

البرنامج لا يعالج القيم فقط، بل يعالج أيضًا المواقع التي توجد بها القيم.


رابعًا، المعاملات والتعبيرات: كيف "يحسب" البرنامج فعليًا

بعد أن أصبحت المتغيرات موجودة، وكذلك الإدخال والإخراج، سيبدأ البرنامج بعد ذلك في معالجة البيانات.

ومعالجة البيانات لا تنفصل عن المعاملات والتعبيرات.

1. المعاملات الحسابية

أبسط المعاملات الحسابية هي:

+  -  *  /  %

على سبيل المثال:

int a = 10;
int b = 3;

printf("%d\n", a + b);
printf("%d\n", a % b);

أكثر ما يستحق انتباه المبتدئين فيها هو / و %.

قسمة الأعداد الصحيحة

إذا كان كلا الطرفين أعدادًا صحيحة:

15 / 4

الناتج هو 3، وليس 3.75.

لأن قسمة الأعداد الصحيحة تحتفظ فقط بالجزء الصحيح.

قسمة الفاصلة العائمة

طالما أن أحد الطرفين عدد فاصلة عائمة:

15 / 4.0

عندها سيصبح الناتج 3.75.

عملية باقي القسمة (الموديولو)

15 % 4

الناتج هو 3، ويمثل الباقي.

% يمكن استخدامها فقط مع الأعداد الصحيحة، لكنها عملية جدًا.

عند تحديد الفردي والزوجي، أو تفكيك الأرقام، أو معالجة الأنماط الدورية، غالبًا ما تحتاج لاستخدامها.

2. الإسناد المركب

بالإضافة إلى:

a = a + 3;

يمكن كتابتها أيضًا كـ:

a += 3;

بشكل مشابه يوجد أيضًا:

a -= 3;
a *= 3;
a /= 3;
a %= 3;

هذه الكتابات شائعة بشكل خاص في الحلقات التكرارية ومعالجة المصفوفات.

3. العمليات المنطقية

تُستخدم المعاملات المنطقية لدمج عدة شروط:

  • !: النفي (ليس)
  • &&: الوصل (و)
  • ||: الفصل (أو)

على سبيل المثال:

if (age >= 18 && age <= 60)
{
    printf("符合条件\n");
}

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


خامسًا، التفرع: جعل البرنامج يتعلم "اتخاذ الخيارات"

في المشكلات الواقعية، الكثير من الأمور لا تُنفذ بالتسلسل، بل "إذا تحقق شرط معين، نفذ أ؛ وإلا نفذ ب".

هذه هي وظيفة بنية التفرع.

1. if: أبسط أشكال الحكم الشرطي

على سبيل المثال:

if (x > 0)
{
    printf("x是一个正数");
}
else
{
    printf("x不是一个正数");
}

هذا النوع من الكود يكاد يكون ترجمة مباشرة للغة الطبيعية:

  • إذا تحقق الشرط، نفذ كتلة الكود السابقة
  • وإلا، نفذ كتلة الكود التالية

2. الحكم متعدد الفروع

إذا كانت الحالات أكثر، يمكن استخدام else if للتوسع أكثر:

if (score >= 90)
    printf("A");
else if (score >= 80)
    printf("B");
else if (score >= 70)
    printf("C");
else if (score >= 60)
    printf("D");
else
    printf("E");

هذه البنية مناسبة بشكل خاص لمعالجة "القواعد المجزأة"، مثل تصنيف الدرجات، أو حساب الرسوم، أو تقسيم الفئات العمرية.

3. switch: أوضح عند معالجة القيم الثابتة

إذا كان شرط الحكم هو "أي قيمة ثابتة يساويها هذا المتغير"، فإن switch ستكون أكثر ترتيبًا:

switch (grade)
{
case 'A':
    printf("优秀");
    break;
case 'B':
    printf("良好");
    break;
default:
    printf("其他");
}

النقطة الأكثر أهمية هنا هي break.

إذا نسيت كتابتها، سيستمر البرنامج في التنفيذ للأسفل، مما يسبب تأثير "الاختراق".


سادسًا، الحلقات التكرارية: جعل البرنامج يتعلم تكرار الأفعال

إذا كان التفرع هو "اتخاذ الخيارات"، فإن الحلقات التكرارية هي "التكرار".

أحد أكثر الأشياء التي يتقنها الحاسوب هو تنفيذ نفس مجموعة القواعد بدقة لمرات عديدة.

والحلقات التكرارية هي تجسيد لهذه القدرة.

1. while: استمر في التنفيذ طالما تحقق الشرط

int i = 1;
while (i <= 5)
{
    printf("%d\n", i);
    i++;
}

هذا الكود سيُخرج الأرقام من 1 إلى 5.

while تتميز بأنها: تفحص أولاً، ثم تنفذ.

لذا إذا كان الشرط خاطئًا من البداية، فقد لا تُنفذ الحلقة ولا مرة.

2. do...while: تنفذ مرة واحدة على الأقل أولاً

int i = 1;
do
{
    printf("%d\n", i);
    i++;
} while (i <= 5);

على عكس while، فإن do...while تنفذ مرة واحدة أولاً، ثم تفحص الشرط.

3. for: أكثر حلقات العد استخدامًا

for (int i = 1; i <= 5; i++)
{
    printf("%d\n", i);
}

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

4. القيمة الحقيقية للحلقات التكرارية

أكثر مهام الحلقات التكرارية شيوعًا في البداية تشمل:

  • حساب المجموع
  • إحصاء العدد
  • إيجاد القيمة العظمى والصغرى
  • التجول في عناصر المصفوفة
  • إخراج الأعداد التي تحقق شرطًا ضمن نطاق معين

لذا فالحلقات التكرارية ليست مجرد قوالب نحوية، بل هي عقلية برمجية مهمة جدًا:

تقسيم المشكلة إلى عملية "تكرار تنفيذ نفس مجموعة الخطوات".


سابعًا، الدوال: تغليف جزء من الوظيفة بشكل منفصل

عندما يصبح البرنامج أطول فأطول، فإن كتابة كل الكود داخل main ستبدو فوضوية للغاية.

هنا تظهر الحاجة للدوال.

يمكن فهم الدالة على أنها:

كتلة من الكود مخصصة لإنجاز مهمة معينة، ويمكن استدعاؤها بشكل متكرر.

على سبيل المثال:

int add(int a, int b)
{
    int c = a + b;
    return c;
}

عند الاستدعاء نكتب:

int sum = add(10, 20);
printf("%d", sum);

1. لماذا الدوال مهمة

للدوال قيمتان جوهريتان:

  • تفكيك البرنامج المعقد، مما يجعل الهيكل أوضح
  • تغليف المنطق المتكرر، لتجنب كتابة نفس الكود مرارًا وتكرارًا

2. المعاملات والقيمة المُعادة

المعاملات في الدالة هي تمرير البيانات إلى الداخل.

القيمة المُعادة هي إخراج النتيجة إلى الخارج.

يبدو هذان المفهومان بسيطين، لكنهما تقريبًا يحددان المعنى الكامل للدالة.

3. التمرير بالقيمة والتمرير بالعنوان

هذه خطوة حاسمة جدًا عند تعلم الدوال.

تمرير المعاملات العادي، في جوهره، هو نسخ القيمة وإعطاء النسخة للدالة.

حتى لو عدلت الدالة المعامل داخليًا، فلن يؤثر ذلك بالضرورة على المتغير الخارجي.

إذا كان ما تم تمريره هو العنوان، فسيختلف الوضع.

يمكن للدالة من خلال هذا العنوان أن تتعامل مباشرة مع那块 الذاكرة التي يوجد بها المتغير الخارجي.

وهذا أيضًا هو سبب وجود ارتباط قوي جدًا بين الدوال وscanf والمؤشرات.

4. المصفوفات كمعاملات للدوال

تمرير المصفوفات يستحق الانتباه بشكل خاص.

لأنه عندما تُمرر مصفوفة كمعامل إلى دالة، فإن ما يُمرر في الجوهر هو عنوان العنصر الأول.

هذا يعني أن تعديل عناصر المصفوفة داخل الدالة، غالبًا ما سيؤثر مباشرة على المصفوفة الأصلية.

هذا الأمر مهم جدًا لفهم "التمرير بالعنوان".


ثامنًا، المصفوفات: عندما لا يكون متغير واحد كافيًا

إذا كان البرنامج يعالج رقمًا واحدًا فقط، فمتغير واحد يكفي.

لكن إذا أردت معالجة 10 درجات، أو 5 إحداثيات، أو قائمة درجات فصل كامل، فمن الواضح أن متغيرًا واحدًا لن يكفي.

هنا يجب استخدام المصفوفات.

1. المصفوفة أحادية البعد: مجموعة من البيانات من نفس النوع

int arr[5] = {1, 2, 3, 4, 5};

هذا يعني تعريف مصفوفة تحتوي على 5 أعداد صحيحة.

جوهر المصفوفة يمكن فهمه على أنه:

وضع مجموعة من البيانات من نفس النوع معًا بالترتيب.

2. الفهارس تبدأ من 0

أحد أهم قواعد المصفوفات هو أن الفهارس تبدأ من 0.

arr[0]   // 第一个元素
arr[1]   // 第二个元素

هذه التفصيلة الصغيرة هي من أكثر الأماكن التي يخطئ فيها المبتدئون.

3. المصفوفات والحلقات التكرارية شريكان طبيعيان

العمليات الشائعة على المصفوفات، غالبًا ما يجب استخدامها مع الحلقات التكرارية:

for (int i = 0; i < 5; i++)
{
    printf("%d\n", arr[i]);
}

التجول، الجمع، البحث، والإحصاء، كلها تقريبًا لا تنفصل عن هذا الثنائي.

4. المصفوفة ثنائية البعد: مصفوفة المصفوفات

على سبيل المثال:

int arr[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

المصفوفات ثنائية البعد مناسبة جدًا لتمثيل البيانات ثنائية البعد مثل الجداول والمصفوفات وكشوف الدرجات.


تاسعًا، المؤشرات: أهم خطوة لفهم لغة C

الكثيرون يتوترون بمجرد سماع كلمة مؤشرات.

إنها بالفعل جزء أكثر تجريدًا في لغة C، لكنها أيضًا أكثر مكان يعكس الخصائص منخفضة المستوى لهذه اللغة.

1. ما هو المؤشر

تذكر جملة واحدة أولاً:

المؤشر هو متغير يخزن عنوانًا.

على سبيل المثال:

int a = 10;
int *p = &a;

هنا:

  • a هو متغير عادي
  • &a هو عنوان a
  • p هو متغير مؤشر
  • p يخزن بداخله عنوان a

2. ماذا يمثل *p

إذا كان p يخزن عنوان a، فإن:

*p

يمثل جلب القيمة الموجودة في الموقع المقابل من خلال هذا العنوان.

على سبيل المثال:

printf("%d\n", *p);

الناتج هو 10.

3. المؤشر أحادي المستوى والمؤشر ثنائي المستوى

إذا كان لمتغير المؤشر نفسه عنوان أيضًا، فيمكن عندها الاستمرار في تعريف "مؤشر إلى مؤشر":

int a = 10;
int *p = &a;
int **pp = &p;

على الرغم من أن الأمر يبدو ملتفًا، إلا أنه في الجوهر مجرد "يمكن أيضًا الاستمرار في حفظ العنوان".

4. العلاقة بين المؤشرات والمصفوفات عميقة جدًا

على سبيل المثال:

int arr[5] = {10, 20, 30, 40, 50};

عند الوصول للعنصر الثالث:

arr[2]
*(arr + 2)

في العديد من السياقات تكونان متكافئتين.

هذا يوضح أن هناك علاقة وثيقة جدًا بين اسم المصفوفة والعنوان، وهذا بالتحديد هو مفتاح فهم المصفوفات وتمرير معاملات الدوال وعمليات المؤشرات.


عاشرًا، الهياكل والسلاسل النصية والمعالجة المسبقة

عندما ترسخ الأساس السابق، ستنتقل لغة C تدريجيًا من "التدرب على القواعد" إلى "التعبير عن كائنات حقيقية".

1. الهياكل: دمج عدة خصائص معًا

على سبيل المثال:

struct Person
{
    char name[10];
    int age;
    float height;
};

هذا يعني أن "شخصًا" واحدًا يمكن أن يمتلك في نفس الوقت خصائص متعددة مثل الاسم والعمر والطول.

عند الوصول لأعضاء الهيكل، الطريقة الشائعة في الكتابة هي:

person.age

أما إذا كان مؤشرًا إلى هيكل، فيجب كتابتها كـ:

p->age

الأهمية الكبيرة للهياكل تكمن في أنها تجعل البرنامج لا يعالج القيم المفردة فقط، بل يبدأ في معالجة "كائن".

2. السلاسل النصية

في لغة C، السلاسل النصية ليست نوع بيانات مستقلًا في الواقع، بل هي مصفوفة من الأحرف.

على سبيل المثال:

char str[] = "hello";

جوهرها هو عدد من الأحرف مخزنة بشكل متتالي، وتنتهي بعلامة \0 كعلامة نهاية.

فهم هذه النقطة مهم جدًا لتعلم طول السلسلة النصية ومساحة مصفوفة الأحرف ودوال معالجة السلاسل النصية لاحقًا.

3. #define: استبدال قبل الترجمة

على سبيل المثال:

#define PI 3.14

هذا ليس متغيرًا، بل هو استبدال نصي يحدث في مرحلة المعالجة المسبقة.

في مرحلة المبتدئين، يكفي معرفة هذه النقطة:

جوهرها ليس "تخزين البيانات"، بل "استبدال محتوى الكود قبل الترجمة".


حادي عشر، الملفات: جعل البرنامج يتعامل مع البيانات الخارجية

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

بل يمكنه أيضًا قراءة وكتابة الملفات.

1. المدخل الأساسي لعمليات الملفات

في لغة C، يتم التعامل مع الملفات عادة من خلال مؤشر الملف.

على سبيل المثال:

FILE *fp = fopen("output.txt", "w");

هذا يعني فتح ملف بطريقة الكتابة.

2. طرق الفتح الشائعة

الأنماط الشائعة هي:

  • "r": للقراءة فقط
  • "w": للكتابة فقط
  • "a": للإلحاق
  • "rb" / "wb": للقراءة والكتابة بالطريقة الثنائية
  • مع وجود +: للجمع بين القراءة والكتابة

طريقة الفتح تحدد ما يمكن للبرنامج فعله بالملف.

3. دوال قراءة وكتابة الملفات الشائعة

على سبيل المثال:

  • fgetc / fputc
  • fgets / fputs

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

لكن كائن الإدخال والإخراج فقط تغير من لوحة المفاتيح والشاشة إلى الملفات الموجودة على القرص.


ثاني عشر، صورة متكاملة

المحتوى أعلاه هو مسار يتوسع طبقة تلو الأخرى:

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

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

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

وهذا أيضًا هو السبب في أن الكثيرين يشعرون أن لغة C صعبة، لكن بعد أن يتعمقوا فيها حقًا، يشعرون أنها أساسية بشكل خاص.

لأنها لا تعلمك "كيف ترصف الكود"، بل تعلمك:

كيف يعمل برنامج الحاسوب فعليًا.