يعرض هذا المقال مفهوم العوامل في لغة C وكيفية استخدامها في العمليات والتعليمات.
عوامل المضاعفة
تتضمن عوامل المضاعفة multiplicative operators عامل الضرب "*" والقسمة "/" وباقي القسمة "%"، ويعمل عاملا الضرب والقسمة بالطريقة التي تتوقع أن تعملان بها، لكلٍّ من الأنواع الحقيقية والصحيحة، إذ تنتج قيمة مقتطعة دون فواصل عشرية عند تقسيم الأعداد الصحيحة ويكون الاقتطاع باتجاه الصفر. يعمل عامل باقي القسمة وفق تعريفه فقط مع الأنواع الصحيحة، لأن القسمة الناتجة عن الأعداد الحقيقية لن تعطينا باقيًا.
إذا كانت القسمة غير صحيحة، وكان أيٌ من المُعاملان غير سالب، فنتيجة العامل "/" هي موجبة ومقرّبة باتجاه الصفر، ونستخدم العامل "%" للحصول على الباقي من هذه العملية، على سبيل المثال:
9/2 == 4 9%2 == 1
إذا كان أحد المُعاملات سالب، فنتيجة العامل "/" قد تكون أقرب عدد صحيح لنتيجة القسمة على أي من الاتجاهين (باتجاه الأكبر أو الأصغر)، وقد تكون إشارة نتيجة العامل "%" موجبةً أو سالبة، وتعتمد النتيجتين السابقتين حسب تعريف التنفيذ.
التعبير الآتي مساوٍ الصفر في جميع الحالات عدا حالة b
مساوية للصفر.
(a/b)*b + a%b - a
تُطبّق التحويلات الحسابية الاعتيادية على كلا المُعاملين.
عوامل الجمع
تتضمن عوامل الجمع additive operators عامل الجمع "+" والطرح "-"، وتتبع طريقة عمل هذه الدوال قواعدها المعتادة التي تعرفها. للعامل الثنائي والأحادي نفس الرمز، ولكن لكل واحد منهما معنًى مختلف؛ فعلى سبيل المثال، يَستخدم التعبيران a+b
وa-b
عاملًا ثنائيًّا (العامل -
للجمع و +
للطرح).
إذا أردنا استخدام العوامل الأحادية بذات الرموز، فسنكتب b+
أو b-
، ولعامل الطرح الأحادي وظيفةٌ واضحة ألا وهي أخذ القيمة السالبة لقيمة المُعامل، ولكن ما وظيفة عامل الجمع الأحادي؟ في الحقيقة لا يؤدي أي دور؛ ويُعد عامل الجمع الأحادي إضافةً جديدة إلى اللغة، إذ يعادل وجوده وجود عامل الطرح الأحادي ولكنه لا يؤدي أي دور لتغيير قيمة التعبير. القلة من مستخدمي لغة سي القديمة لاحظ عدم وجوده.
تُطبّق التحويلات الحسابية الاعتيادية على كلٍّ من مُعاملات العوامل الثنائية (للجمع والطرح)، وتُطبّق الترقية العددية الصحيحة على مُعاملات العوامل الأحادية فقط.
عوامل العمليات الثنائية
واحدة من مزايا لغة سي هي الطريقة التي تسمح بها لمبرمجي النظم بالتعامل مع الشيفرة البرمجية وكأنها شيفرة تجميعية Assembly code، وهو نوع شيفرة برمجية كان شائعًا قبل مجيء لغة سي، وكان هذا النوع من الشيفرات صعب التشغيل على عدة منصات (غير محمول non-portable). كما وضحت لنا لغة سي أن هذا الأمر لا يتطلب سحرًا لجعل الشيفرة محمولة، لكن ما هو هذا الشيء؟ هو ما يُعرف أحيانًا باسم "العبث بالبِتات bit-twiddling" وهي عملية التلاعب ببتات متغيرات الأعداد الصحيحة على نحوٍ منفرد. لا يمكن استخدام عوامل العمليات الثنائية bitwise operators على مُعاملات من نوع أعداد حقيقية إذ لا تُعد البتات الخاصة بها منفردة individual أو يمكن الوصول إليها.
هناك ستة عوامل للعمليات الثنائية موضحة في الجدول 7.2، الذي يوضح أيضًا نوع التحويلات الحسابية المُطبّقة.
العامل | التأثير | التحويل |
---|---|---|
&
|
العملية الثنائية AND | التحويلات الحسابية الاعتيادية |
\
|
العملية الثنائية OR | التحويلات الحسابية الاعتيادية |
^
|
العملية الثنائية XOR | التحويلات الحسابية الاعتيادية |
>>
|
إزاحة إلى اليسار | الترقية العددية الصحيحة |
<<
|
إزاحة إلى اليمين | الترقية العددية الصحيحة |
~
|
المتمم الأحادي | الترقية العددية الصحيحة |
[جدول 7.2. عوامل العمليات الثنائية]
العامل الوحيد الأحادي هو الأخير (المتمم الأحادي)، إذ يعكس حالة كل بِت في قيمة المُعامل وله نفس تأثير عامل الطرح الأحادي في حاسوب يعمل بنظام المتمم الأحادي، لكن معظم الحواسيب الآن تعمل بنظام المتمم الثنائي، لذلك وجوده مهم.
سيسهّل استخدام النظام الست عشري عن استخدام النظام العشري فهم طريقة هذه العوامل، لذا حان الوقت الآن لأن نعرفك على الثوابت الست عشرية. أي رقم يُكتب في بدايته "0x" يفسّر على أنه رقم ست عشري، على سبيل المثال القيمة "15" و"0xf"، أو "0XF" متكافئتان، جرّب تشغيل المثال التالي على حاسوبك، أو الأفضل من ذلك تنبّأ بوظيفة البرنامج قبل تشغيله.
#include <stdio.h> #include <stdlib.h> main(){ int x,y; x = 0; y = ~0; while(x != y){ printf("%x & %x = %x\n", x, 0xff, x&0xff); printf("%x | %x = %x\n", x, 0x10f, x|0x10f); printf("%x ^ %x = %x\n", x, 0xf00f, x^0xf00f); printf("%x >> 2 = %x\n", x, x >> 2); printf("%x << 2 = %x\n", x, x << 2); x = (x << 1) | 1; } exit(EXIT_SUCCESS); }
[مثال 2.9]
لنتكلم أوّلًا عن طريقة عمل الحلقة التكرارية في مثالنا، إذ أن المتغير الذي يتحكم بالحلقة هو x
ومُهيّأ بالقيمة صفر، وفي كل دورة تُوازن قيمته مع قيمة y
الذي ضُبِط بنمطٍ مستقل بنفس طول الكلمة ومكون من الواحدات، وذلك بأخذ المتمم الأحادي للصفر، وفي أسفل الحلقة يُزاح المتغير x
إلى اليسار مرةً واحدةً وتُجرى العملية الثنائية OR عليه، مما ينتج سلسلةً تبدأ على النحو التالي: "0، 1، 11، 111، …" بالنظام الثنائي.
تُجرى العمليات الثنائية على x
باستخدام كل من عوامل AND وOR وXOR (أي OR الحصرية أو دارة عدم التماثل) إضافةً إلى مُعاملات أخرى جديرة بالاهتمام، ومن ثم تُطبع النتيجة.
نجد أيضًا عوامل الإزاحة إلى اليمين واليسار، التي تعطينا نتيجةً بنوع وقيمة المُعامل الموجود على الجهة اليسرى مزاحةً إلى الجهة المحددة عددًا من المراتب حسب المُعامل الموجود على جهتها اليمنى، ويجب أن يكون المُعاملان أعدادًا صحيحة. تختفي البِتات المُزاحة إلى أي من طرفي المُعامل الأيسر، وينتج عن إزاحة مقدار من البتات أكبر من البتات الموجودة في الكلمة نتيجةً معتمدةً على التنفيذ.
تضمن الإزاحة إلى اليسار إزاحة الأصفار إلى البتات منخفضة الترتيب، بينما تكون الإزاحة إلى اليمين أكثر تعقيدًا، إذ إن الأمر متروك لتنفيذك للاختيار بين إجراء إزاحة منطقية أو حسابية إلى اليمين عند إزاحة المُعاملات ذات الإشارة. هذا يعني أن الإزاحة المنطقية تُزيح الأصفار باتجاه البت ذو الأكثر أهمية، بينما تنسخ الإزاحة الحسابية محتوى البت الأكثر أهمية الحالي إلى البت نفسه، ويصبح الخيار أوضح إذا أُزيح مُعامل عديم الإشارة إلى اليمين، ولا يوجد أي خيار هنا، إذ يجب أن تكون الإزاحة منطقية. ولهذا السبب يجب أن تتوقع أن تكون القيمة المُزاحة عند استخدام الإزاحة إلى اليمين مصرحٌ عنها مثل قيمةٍ عديمة الإشارة، أو أن يُحوّل نوعها cast إلى عديمة الإشارة لإجراء عملية الإزاحة كما يصف المثال التالي ذلك:
int i,j; i = (unsigned)j >> 4;
لا ينبغي على المُعامل الثاني (على الطرف الأيمن) لعامل الإزاحة أن يكون ثابتًا، إذ من الممكن استخدام أي دالة ذات نوع عدد صحيح؛ ومن المهم هنا الإشارة إلى أن قوانين مزج أنواع المُعاملات لا تنطبق على عوامل الإزاحة، إذ أن نوع نتيجة الإزاحة هي النوع المُزاح ذاته (بعد الترقية العددية الصحيحة) ولا تعتمد على أي شيءٍ آخر.
لنتكلم عن شيء مختلف قليلًا، وهي إحدى الحيل المفيدة التي يستخدمها مبرمجو لغة سي لكتابة برامجهم على نحوٍ أفضل. إذا أردت تشكيل قيمةٍ تحتوي على واحدات "1" في جميع خاناتها عدا البِت الأقل أهمية، بهدف تخزين نمطٍ آخر فيها، فمن غير المطلوب معرفة طول الكلمة في النظام الذي تستخدمه. على سبيل المثال، تستطيع استخدام الطريقة التالية لضبط البتات الأقل ترتيبًا لمتغيرٍ من نوع int
إلى 0x0f0
وجميع البتات الأخرى إلى 1
:
int some_variable; some_variable = ~0xf0f;
أُجري المتمم الأحادي على المتمم الأحادي لنمط البتات منخفضة الترتيب المرغوب، وهذا يُعطي بدوره القيمة المطلوبة والمستقلة تمامًا عن طول الكلمة، وهو شيء متكرر الحدوث في شيفرة لغة سي.
لا يوجد هناك مزيدٌ من الأشياء لقولها عن عوامل التلاعب بالبتات، وتجربتنا في تعليم لغة سي تدلنا على أنها سهلة الفهم والتعلم من معظم الناس، لذا دعنا ننتقل للموضوع التالي.
عوامل الإسناد
العنوان ليس خطأً مطبعيًا بل قصدنا "عوامل" بالجمع، إذ أن لدى لغة سي عدة عوامل إسناد على الرغم من رؤيتنا للعامل "=`"فقط حتى الآن. المثير للاهتمام بخصوص هذه العوامل هو أنها تعمل مثل العوامل الثنائية الأخرى، إذ تأخذ مُعاملين وتعطينا نتيجة، وتستخدم النتيجة لتكون جزءًا من التعبير. مثلًا في هذا التعبير:
x = 4;
تُسند القيمة 4
إلى المتغير x
، والنتيجة عن إسناد القيمة إلى المتغير x
هو استخدامها على النحو التالي:
a = (x = 4);
إذ ستخزِّن a
القيمة 4
المُسندة إليها بعد أن تُسنَد إلى x
. تجاهلت جميع عمليات الإسناد السابقة التي نظرنا إليها لحد اللحظة (عدا مثال واحد) ببساطة القيمة الناتجة عن عملية الإسناد بالرغم من وجودها.
بفضل هذه القيمة، يمكننا كتابة تعابير مشابهة لهذه:
a = b = c = d;
إذ تُسند قيمة المتغير d
إلى المتغير c
وتُسند هذه النتيجة إلى b
وهكذا دواليك، ونلاحظ هنا معالجة تعابير الإسناد من الجهة اليمنى إلى الجهة اليسرى، ولكن ماعدا ذلك فهي تعابير اعتيادية (القوانين التي تصف اتجاه المعالجة من اليمين أو من اليسار موجودةٌ في الجدول 9.2.).
هناك وصف موجود في القسم الذي يتكلم عن "التحويلات"، يصف ما الذي سيحدث في حالة التحويل من أنواع طويلة إلى أنواع قصيرة، وهذا ما يحدث عندما يكون المُعامل الذي يقع على يسار عامل الإسناد البسيط أقصر من المُعامل الذي يقع على يمينه.
عوامل الإسناد المتبقية هي عوامل إسناد مركّبة، وتعد اختصاراتٌ مفيدة يمكن اختصارها عندما يكون المُعامل ذاته على يمين ويسار عامل الإسناد، على سبيل المثال، يمكن اختصار التعبير التالي:
x = x + 1;
إلى التعبير:
x += 1;
وذلك باستخدام إحدى عوامل الإسناد المركّبة، وتكون النتيجة للتعبير الأول هي ذاتها للتعبير الثاني المختصر في أي حالة. يصبح هذا الأمر مفيدًا عندما يكون الجانب الأيسر من العامل تعبيرًا معقّدًا، وليس متغيرًا وحيدًا، وذلك في حالة استخدام المصفوفات والمؤشرات. يميل معظم مبرمجي لغة سي لاستخدام التعبير في المثال الثاني لأنه يبدو "أكثر منطقية"، وهذا ما يختلف عنده الكثير من المبتدئين عند تعلُّم هذه اللغة. يوضح الجدول 8.2 عوامل الإسناد المركبة، وستلاحظ استخدامنا المكثّف لها من الآن فصاعدًا.
=*
|
=/
|
=%
|
=+
|
=-
|
|
=&
|
=\
|
=^
|
=<<
|
=>>
|
[جدول 8.2. عوامل الإسناد المركبة]
تُطبَّق التحويلات الحسابية في كل حالة وكأنها تُطبق على كامل التعبير، أي كأن التعبير a+=b
مثلًا مكتوبٌ على النحو a=a+b
.
اقتباسللتذكير: تحمل نتيجة عامل الإسناد نوع وقيمة الكائن الذي أُسند إليه.
عوامل الزيادة والنقصان
قدّمت لغة سي عاملين أحاديين مميزين لإضافة واحد أو طرحه من تعبيرٍ ما نظرًا لشيوع هذه العملية البسيطة؛ إذ يضيف عامل الزيادة "++" واحدًا، ويطرح عامل النقصان "--" واحدًا، وتُستخدم هذه العوامل على النحو التالي:
x++; ++x; x--; --x;
إذ من الممكن أن يقع العامل قبل أو بعد المُعامل، ولا يختلف عمل العامل في الحالات الموضحة سابقًا حتى لو اختلف موقعه، ولكن الحالة تصبح أكثر تعقيدًا في بعض الأحيان ويصبح الفرق وفهمه مهمّان للاستخدام الصحيح.
إليك الفرق موضحًا في المثال التالي:
#include <stdio.h> #include <stdlib.h> main(){ int a,b; a = b = 5; printf("%d\n", ++a+5); printf("%d\n", a); printf("%d\n", b++ +5); printf("%d\n", b); exit(EXIT_SUCCESS); }
مثال 10.2
خرج المثال السابق هو:
11 6 10 6
يعود السبب في الفرق في النتائج إلى تغيير مواضع العوامل؛ فإذا ظهر عامل الزيادة أو النقصان قبل المتغير، فستتغيّر القيمة بمقدار واحد و تُستخدم القيمة الجديدة ضمن التعبير؛ أما إذا ظهر العامل بعد المتغير فستُستخدم القيمة القديمة في التعبير، ثم تُغيّر قيمة المتغير.
لا يستخدم مبرمجو سي عادةً التعليمة التالية لطرح أو جمع واحد:
x += 1;
بل يستخدمون التعليمة التالية:
x++; /* or */ ++x;
ينبغي تجنُّب استخدام المتغير ذاته أكثر من مرة في ذات التعبير إذا كان هذا النوع من العوامل مرتبطًا به، إذ لا يوجد هناك أي قاعدة واضحة تدلك على الجزء المحدد من التعبير الذي ستتغير فيه قيمة المتغير. قد يختار المصرّف "حفظ" جميع التغييرات وتطبيقها دفعةً واحدة، فعلى سبيل المثال لا يضمن التعبير التالي إسناد قيمة x
الأصلية مرّتين إلى y
:
y = x++ + --x;
وقد يُقيّم كما لو كان قد جرى توسعته إلى التعبير التالي:
y = x + (x-1);
لأن المصرّف يلاحظ عدم وجود تأثير أبدًا على قيمة x
.
تُجرى العمليات الحسابية في هذه الحالة تمامًا كما في حالة تعبير جمع، مثلًا x=x+1
، وتُطبّق التحويلات الحسابية الاعتيادية.
الأسبقية والتجميع
علينا النظر إلى الطريقة التي تعمل بها هذه العوامل بعد التكلم عنها. قد تعتقد أن عملية الجمع ليست بتلك الأهمية، فنتيجة التعبير
a + b + c
مساويةٌ للتعبير:
(a + b) + c
أو التعبير:
a + (b + c)
أليس كذلك؟ في الحقيقة لا، فهناك فرقٌ بين التعابير السابقة؛ فإذا تسبَّب التعبير a+b
بحالة طفحان، وكانت قيمة المتغير c
قريبة من قيمة b-
، فسيعطي التعبير الثاني الإجابة الصحيحة، بينما سيتسبب الأول بسلوك غير محدد. يمكننا ملاحظة هذه المشكلة بوضوح أكبر باستخدام قسمة الأعداد الصحيحة، إذ يعطي التعبير التالي:
a/b/c
نتائج مختلفةً تمامًا عند تجميعه بالطريقة:
a/(b/c)
أو بالطريقة:
(a/b)/c
يمكنك تجربة استخدام القيم a=10
و b=2
و c=3
للتأكُّد، إذ سيكون التعبير الأول: (2/3)/10
، ونتيجة 2/3
في قسمة الأعداد الصحيحة هي 0
، لذلك سنحصل على 10/0
، مما سيسبب طفحانًا overflow؛ بينما سيعطينا التعبير الثاني القيمة (10/2)
، وهي 5
، وبتقسيمها على 3
تعطينا 1
.
يُعرَف تجميع العوامل على هذا النحو بمصطلح الارتباط associativity، والمكون الثاني لتحديد طريقة عمل العوامل هو الأسبقية precedence، إذ لبعض العوامل أسبقيةٌ عن عوامل أخرى، وتُحسب قيم العوامل هذه في التعابير الفرعية أولًا قبل الانتقال إلى العوامل الأقل أهمية. تُستخدم قوانين الأسبقية في معظم لغات البرمجة عالية المستوى. "نعلم" أن التعبير:
a + b * c + d
يُجمّع على النحو التالي:
a + (b * c) + d
إذ أن عملية الضرب ذات أسبقية أعلى موازنةً بعملية الجمع.
تحظى لغة سي بوجود 15 مستوى أسبقية بفضل مجموعة العوامل الكبيرة التي تحتويها، يحاول القلة من الناس تذكرها جميعًا. يوضح الجدول 9.2 جميع المستويات، ويصف كلًّا من الأسبقية والارتباط. لم نتكلم عن جميع العوامل المذكورة في الجدول بعد. كن حذدرًا من استخدام نفس الرمز لبعض العوامل الأحادية والثنائية، والموضحة في الجدول أيضًا.
العامل | الاتجاه | ملاحظات |
---|---|---|
() [] <- .
|
من اليسار إلى اليمين | 1 |
! ~ ++ -- - + (cast) * & sizeof
|
من اليمين إلى اليسار | جميع العوامل أحادية |
* / %
|
من اليسار إلى اليمين | عوامل ثنائية |
+ -
|
من اليسار إلى اليمين | عوامل ثنائية |
<< >>
|
من اليسار إلى اليمين | عوامل ثنائية |
< =< > =>
|
من اليسار إلى اليمين | عوامل ثنائية |
== =!
|
من اليسار إلى اليمين | عوامل ثنائية |
&
|
من اليسار إلى اليمين | عامل ثنائي |
^
|
من اليسار إلى اليمين | عامل ثنائي |
\
|
من اليسار إلى اليمين | عامل ثنائي |
&&
|
من اليسار إلى اليمين | عامل ثنائي |
\\
|
من اليسار إلى اليمين | عامل ثنائي |
:?
|
من اليمين إلى اليسار | 2 |
= =+ وجميع عوامل الإسناد المركّبة
|
من اليمين إلى اليسار | عوامل ثنائية |
,
|
من اليسار إلى اليمين | عامل ثنائي |
[جدول 9.2. أسبقية العوامل وترابطها]
حيث أن:
- الأقواس بهدف تجميع التعابير، وليس استدعاء الدوال.
- هذا العامل غير مألوف، راجع القسم 1.4.3.
السؤال هنا هو، كيف أستطيع الاستفادة من هذه المعلومات؟ من المهم طبعًا أن تكون قادرًا على كتابة تعابير تعطي قيمًا صحيحة بمعرفة ترتيب تنفيذ العمليات، إضافةً إلى فهم وقراءة تعابير مكتوبة من مبرمجين آخرين. تبدأ خطوات كتابة تعبير أو قراءة تعبير مكتوب بالعثور على العامل الأحادي والمُعاملات المرتبطة معه، وهذه ليست بالمهمة الصعبة ولكنها تحتاج إلى بعض التمرين، بالأخص عندما تعرف أنه يمكن استخدام العوامل عددًا من المرات الاعتباطية بجانب مُعاملاتها، مثل التعبير التالي باستخدام العامل *
الأحادي:
a*****b
يعني التعبير السابق أن المتغير a
مضروبٌ بقيمة ما، وهذه القيمة هي تعبير يتضمن b
وعددًا من عوامل *
الأحادية.
تحديد العوامل الأحادية في التعبير ليست بالمهمة الصعبة، إليك بعض القواعد التي يجب أن تلجأ إليها:
- العاملان "++" و "-" أحاديان في جميع الحالات.
- العامل الذي يقع على يمين المُعامل مباشرةً هو عامل ثنائي (في حالة لم تتحقق القاعدة 1)، إذا كان العامل الذي يقع على يمين المُعامل ذاك ثنائيًّا.
- جميع العوامل الواقعة على يسار المُعامل أحادية (في حالة لم تتحقق القاعدة 2).
يمكنك دائمًا التفكير بعمل العوامل الأحادية أولًا قبل العوامل الأخرى بسبب أسبقيتها المرتفعة، إذ من الأشياء التي يجب عليك الانتباه عليها هي مواضع العاملين "++" و "--" إذا كانا قبل أو بعد المُعامل، فعلى سبيل المثال يحتوي التعبير التالي:
a + -b++ + c
على عاملين أحاديين مُطبقّان على b
. ترتبط العوامل الأحادية من اليمين إلى اليسار، إذًا على الرغم من قدوم -
أولًا، يُكتب التعبير على النحو التالي (باستخدام الأقواس للتوضيح):
a + -(b++) + c
تصبح الحالة أكثر وضوحًا إذا استُخدم العامل في البداية prefix بدلًا من النهاية postfix مثل عامل زيادة أو نقصان، ويكون الترتيب من اليمين إلى اليسار في هذه الحالة أيضًا، ولكن العوامل ستظهر متتاليةً بجانب بعضها بعضًا.
بعد تعلُّمنا لطريقة فهم العوامل الأحادية، أصبح من السهل قراءة التعبير من اليسار إلى اليمين، وعندما تجد عاملًا ثنائيًا أبقِه في بالك، وانظر إلى يمينه؛ فإذا كان العامل الثنائي التالي ذو أسبقية أقل فسيكون العامل الذي تنظر إليه (الذي تبقيه في بالك) هو جزء من تعبير جزئي عليك تقييم قيمته قبل أي خطوة أخرى؛ أما إذا كان العامل الثنائي التالي من نفس الأسبقية فأعِد تنفيذ العملية حتى تصل إلى عامل ذو أسبقية مختلفة؛ وعندما تجد عاملًا ذا أسبقية منخفضة، قيّم قيمة التعبير الجزئي الواقع على يسار العامل وفقًا لقوانين الارتباط؛ أما إذا وجدت عاملًا ذا أسبقية عالية فانسَ جميع ما سبقه، إذ أن المُعامل الواقع على يسار العامل عالي الأسبقية هو جزءٌ من تعبير جزئي آخر يقع على الطرف الأيسر وينتمي إلى العامل الجديد.
لا تقلق إذا لم تضّح لديك الصورة بعد، إذ يواجه العديد من مبرمجي لغة سي مشكلات تتعلق بهذه النقطة، ويعتادون فيما بعد على تجميع التعابير "بنظرة عين"، دون الحاجة لتطبيق القوانين مباشرةً.
لكن الأمر المهم هنا هو الخطوة التي تلي تجميعك لهذه التعابير، أتذكر "التحويلات الحسابية الاعتيادية"؟ إذ فسَّرت هذه التحويلات كيف يمكنك التنبؤ بنوع التعبير عن طريق النظر إلى المُعاملات الموجودة. والآن إذا مزجت أنواعًا مختلفة في تعبير معقد، ستُحدّد أنواع التعابير الجزئية فقط من خلال أنواع المُعاملات الموجودة في التعبير الجزئي، ألقِ نظرةً على المثال التالي:
#include <stdio.h> #include <stdlib.h> main(){ int i,j; float f; i = 5; j = 2; f = 3.0; f = f + j / i; printf("value of f is %f\n", f); exit(EXIT_SUCCESS); }
[مثال 11.2]
قيمة الخرج هي 3.0000
وليس 5.0000
مما يفاجئ البعض الذي اعتقد أن القسمة ستكون قسمة أعداد حقيقية فقط لأن متغير من نوع float
كان موجودًا في التعليمة.
كان عامل القسمة بالطبع يحتوي على متغيرين من نوع int
فقط على الجانبين، لذا أُجريت العملية الحسابية على أنها قسمة أعداد صحيحة وأنتجت صفرًا، واحتوى عامل الجمع على float
و int
على طرفيه، وبذلك -وحسب قوانين التحويلات الحسابية الاعتيادية- يُحوَّل النوع int
إلى float
، وهو النوع الصحيح لإجراء عملية الإسناد أيضًا، مما أعفانا من تحويلات أخرى.
استعرض القسم السابق عن تحويل الأنواع casts طريقةً لتغيير نوع التعبير من نوعه الطبيعي إلى النوع الذي تريده، ولكن كُن حذرًا، إذ سيستخدم التحويل التالي قسمة صحيحة:
(float)(j/i)
ثم يحوّل النتيجة إلى float
، وللمحافظة على باقي القسمة، يجب عليك كتابة التحويل بالطريقة:
(float)j/i
مما سيجبر استخدام القسمة الحقيقية.
الأقواس
كما وضّح المثال السابق، يمكنك تجاوز أسبقية وارتباط لغة سي الاعتيادية عن طريق استخدام الأقواس. لم يكن للأقواس أي معنًى آخر في لغة سي القديمة، ولم تضمن أيضًا ترتيب تقييم القيمة ضمن التعابير، مثل:
int a, b, c; a+b+c; (a+b)+c; a+(b+c);
إذا كان عليك استخدام متغيرات مؤقتة للحصول على ترتيب التقييم كما أردته، وهو أمر مهم إذا كنت تعرف أن هناك بعض التعابير التي تكون عرضةً للطفحان overflow، ولتجنُّب هذا الأمر عليك أن تعدّل ترتيب تقييم التعبير.
ينص معيار سي على أن تقييم التعبير يجب أن يحدث بناءً على الترتيب المحدد وفق الأسبقية وتجميع التعابير، ويمكن للمصرّف أن يعيد تجميع التعابير إن لم يؤثر ذلك على النتيجة النهائية بهدف زيادة الكفاءة.
على سبيل المثال، لا يمكن للمصرّف أن يعيد كتابة التعبير التالي:
a = 10+a+b+5;
إلى:
a = 15+a+b;
إلا في حالة تأكُّده من أن القيمة النهائية لن تكون مختلفةً عن التعبير الأولي الذي يتضمن قيم a
وb
، وهذه الحالة محققةٌ إذا كان المتغيران من نوع عدد صحيح عديم الإشارة، أو عدد صحيح ذو إشارة وكانت العملية لا تتسبب في بإطلاق استثناء عند التشغيل run-time exception والناتج عن طفحان.
الآثار الجانبية
نعيد ونكرر التحذير الوارد بشأن عوامل الزيادة: ليس من الآمن استخدام المتغير ذاته أكثر من مرة في نفس التعبير، وذلك في حالة كان تقييم التعبير يغيّر من قيمة المتغير ويؤثر على القيمة النهائية للتعبير، وذلك بسبب التغيير (أو التغييرات) "المحفوظة" والمُطبّقة فقط عند الوصول لنهاية التعليمة. على سبيل المثال التعبير f = f+1
آمن على الرغم من ظهور المتغير f
مرتين في تعبير يغير من قيمتها، والتعبير ++f
آمن أيضًا، ولكن ;f = f++
غير آمن.
تنبع المشكلة من استخدام عامل الإسناد أو عوامل الزيادة والنقصان أو استدعاء دالة تغيّر من قيمة متغير خارجي External مُستخدم في تعبير ما، وتُعرف هذه المشاكل باسم "الآثار الجانبية Side Effects"، ولا تحدد سي ترتيب حدوث هذه الآثار الجانبية ضمن تعبير ما (سنتوسّع لاحقًا بخصوص هذه المشكلة، ونناقش "نقاط التسلسل Sequence points").
ترجمة -وبتصرف- لقسم من الفصل Variables and Arithmetic من كتاب The C Book.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.