العوامل هي أفعال لغة C: تأخذ قيمًا وتنتج قيمًا جديدة. هناك نحو 40 عاملًا، لكن عشرة منها تغطّي معظم الشيفرة، واثنان منها لهما فخاخ حادّة بما يكفي لتستحق أقسامًا خاصة بها.
العوامل الحسابية
(تطبع %% في سلسلة التنسيق علامة نسبة مئوية حرفية - أما % وحدها فتبدأ محدّد تنسيق.)
العوامل الحسابية الثنائية الخمسة هي + - * / %، إضافةً إلى - الأحادي للنفي و+ الذي لا يفعل شيئًا يُذكر.
فخّ القسمة الصحيحة
يؤدّي a / b بعددين صحيحين قسمة صحيحة ويرمي الباقي. ولا يوجد تقريب - بل تُقتطع النتيجة باتجاه الصفر.
الحالة الأخيرة هي الخطأ الذي يصمد حتى الإنتاج. فالإسناد إلى double لا يفيد: إذ تكون القسمة total / count قد تمّت سلفًا، وصارت int سلفًا، قبل وقوع الإسناد. والحلّ هو جعل أحد المعاملين ذا فاصلة عائمة داخل التعبير، عادةً بـتحويل نوع.
قاعدتان مرتبطتان: القسمة على صفر بأعداد صحيحة سلوك غير معرَّف وتُعطِّل البرنامج عادةً، وINT_MIN / -1 يفيض.
عامل باقي القسمة
يعطي % الباقي ويعمل على الأعداد الصحيحة فقط:
فحص x % 2 == 0 للزوجية واستخدام % n لالتفاف فهرس حول مصفوفة هما الاستخدامان اليوميّان.
ومع الأعداد السالبة، يتبع % في C إشارة المقسوم:
لذا فإن x % 2 == 1 فحص معطوب للفردية حين يمكن أن تكون x سالبة - إذ إن -7 % 2 يساوي -1. استخدم x % 2 != 0 بدلًا منه.
أما بواقي الفاصلة العائمة فإن % معها خطأ ترجمة؛ استخدم fmod() من math.h.
الإسناد والإسناد المركّب
يخزّن = قيمة. أما الصيغ المركّبة فتجمع عملية مع التخزين:
يعني x += 5 ما يعنيه x = x + 5، لكنه يقيّم x مرة واحدة فقط - وهو ما يهمّ حين يكون الهدف شيئًا مثل arr[compute_index()].
والإسناد نفسه تعبير يعطي القيمة المسنَدة، ولهذا يعمل a = b = c = 0 (فهو يُسنِد من الطرف الأخير إلى الأول) ولهذا يُترجَم خطأ = مقابل == بصمت.
الزيادة والإنقاص
يضيف ++ واحدًا، ويطرح -- واحدًا. ولكلٍّ منهما صيغة سابقة وصيغة لاحقة:
كجملة مستقلة يمكن تبادل الصيغتين، وi++ هو الخيار المتعارف عليه في ترويسة for لا لسبب أعمق من العادة.
وما يجب ألّا تفعله هو استخدام متغيّر أكثر من مرة في تعبير يُعدَّل فيه أيضًا:
int i = 5;
int x = i++ + i++; /* سلوك غير معرَّف */
arr[i] = i++; /* سلوك غير معرَّف */
printf("%d %d", i++, i);/* سلوك غير معرَّف */
لا تحدّد C ترتيب تقييم تلك التعبيرات الفرعية، فالنتيجة ليست غير محدّدة فحسب - بل سلوك البرنامج كله غير معرَّف. قسّم الجملة إلى اثنتين فيزول الالتباس.
عوامل المقارنة
ستة منها، وكلها تنتج قيمة int: 1 للصحيح و0 للخطأ.
كون النتيجة int عاديًا بدلًا من نوع منطقي مخصّص خاصية مميِّزة للغة C - انظر القيم المنطقية في C لما يترتّب على ذلك، بما في ذلك خطأ = مقابل ==.
وهناك خطآن في المقارنة خاصّان بـ C. فالتسلسل لا يعمل كما توحي الرياضيات: if (1 < x < 10) صحيح دائمًا، لأن 1 < x يُقيَّم إلى 0 أو 1 ثم يُقارَن ذلك بـ 10. اكتب if (x > 1 && x < 10). ومقارنة السلاسل النصية بـ == تقارن المؤشّرات لا النص - استخدم strcmp من string.h.
العوامل المنطقية
&& عامل "و"، و|| عامل "أو"، و! عامل النفي. وتعامل الثلاثة أي قيمة غير صفرية على أنها صحيحة.
وهي تقصّر التقييم: يتوقّف && بمجرّد أن يكون أحد المعاملين خاطئًا، ويتوقّف || بمجرّد أن يكون أحدهما صحيحًا. ولا يُقيَّم الباقي إطلاقًا، وهذا ليس تحسينًا فحسب - بل ضمانة يمكنك الاعتماد عليها للأمان:
اعكس ترتيب هذين الفحصين فيقوم الاستدعاء بـ NULL بمراجعة مؤشّر فارغ ويتعطّل. احرس أولًا ثم استخدم - فالترتيب في سلسلة && جزء من المنطق.
العامل الشرطي
عامل C الوحيد ذو المعاملات الثلاثة يختار بين قيمتين:
condition ? value_if_true : value_if_false. إنه تعبير، فيصلح حيث لا تصلح جملة if - داخل وسيط لـ printf، أو في مهيّئ. أبقِه قصيرًا؛ فالعوامل الشرطية المتداخلة تصبح غير مقروءة بسرعة.
العوامل البتّية باختصار
تعمل هذه على البتات المفردة لعدد صحيح:
تُستخدم للرايات والأقنعة وسجلّات العتاد. وهناك تحذيران: & و| ليسا && و|| (فهما لا يقصّران التقييم ويعملان بتًّا بتًّا)، وإزاحة قيمة موقّعة أو الإزاحة بأكثر من عرض النوع سلوك غير معرَّف. استخدم الأنواع غير الموقّعة للعمل على البتات.
الأولوية
حين يخلو التعبير من الأقواس، يقرّر هذا الجدول ما يرتبط بماذا. الأعلى أولويةً أولًا:
| المستوى | العوامل | جهة الارتباط |
|---|---|---|
| 1 | () [] -> . x++ x-- | من الأسبق إلى اللاحق |
| 2 | ! ~ ++x --x +x -x *p &x sizeof (type) | من اللاحق إلى الأسبق |
| 3 | * / % | من الأسبق إلى اللاحق |
| 4 | + - | من الأسبق إلى اللاحق |
| 5 | << >> | من الأسبق إلى اللاحق |
| 6 | < <= > >= | من الأسبق إلى اللاحق |
| 7 | == != | من الأسبق إلى اللاحق |
| 8 | & | من الأسبق إلى اللاحق |
| 9 | ^ | من الأسبق إلى اللاحق |
| 10 | عامل OR البتّي | من الأسبق إلى اللاحق |
| 11 | && | من الأسبق إلى اللاحق |
| 12 | عامل OR المنطقي | من الأسبق إلى اللاحق |
| 13 | ?: | من اللاحق إلى الأسبق |
| 14 | = += -= *= /= %= وأخواتها | من اللاحق إلى الأسبق |
| 15 | , | من الأسبق إلى اللاحق |
النتائج العملية:
a + b * c /* يعني a + (b * c) */
a < b == c < d /* يعني (a < b) == (c < d) - نادرًا ما يكون هذا مقصودك */
x & 1 == 0 /* يعني x & (1 == 0)، أي x & 0 - خطأ كلاسيكي */
*p++ /* يعني *(p++) - راجِع p، ثم قدّمها */
السطر الثالث جدير بالحفظ: يرتبط == أوثق من &، لذا تحتاج فحوص أقنعة البتات إلى أقواس - (x & 1) == 0.
لا أحد يحفظ المستويات الخمسة عشر كلها، ولا أحد يحتاج إلى ذلك. تعلّم أن * يغلب +، وأن المقارنات تغلب && الذي يغلب ||، وأن كل ما عدا ذلك يأخذ أقواسًا:
if ((flags & MASK) != 0 && (count > 0 || force)) { ... }
هذا السطر يحمل أقواسًا زائدة عن الحاجة وهو أفضل بها.
الأسئلة الشائعة
ماذا يفعل % في لغة C؟
% هو عامل باقي القسمة: يعطي باقي القسمة الصحيحة. فـ 17 % 5 يساوي 2. ويعمل على الأعداد الصحيحة فقط - واستخدامه على float أو double خطأ ترجمة، والمقابل للفاصلة العائمة هو fmod() من math.h.
لماذا يعطي 5 / 2 الناتج 2 في لغة C؟
لأن كلا المعاملين عدد صحيح، فتجري C قسمة صحيحة وتهمل الكسر. اجعل أحد الطرفين قيمة ذات فاصلة عائمة لتحصل على 2.5: 5 / 2.0، أو حوّل أحد المعاملين بـ (double)a / b.
ما الفرق بين i++ و++i؟
كلاهما يضيف واحدًا إلى i. فـ ++i (السابقة) تزيد أولًا وتعطي القيمة الجديدة؛ وi++ (اللاحقة) تعطي القيمة القديمة ثم تزيد بعد ذلك. وكجملة مستقلة هما متطابقان - ولا يهمّ الفرق إلا حين تُستخدم النتيجة، كما في int b = a++; مقابل int b = ++a;.
ما أولوية العوامل في لغة C؟
هي الترتيب الذي ترتبط به العوامل حين يخلو التعبير من الأقواس. فـ * و/ و% ترتبط أوثق من + و-، اللذين يرتبطان أوثق من المقارنات، التي ترتبط أوثق من &&، ثم ||، ثم الإسناد. وعند الشك، أضف أقواسًا - فهي لا تكلّف شيئًا وتزيل السؤال.