اذهب إلى المحتوى

التحويلات ما بين الأنواع في تعابير لغة سي C


Naser Dakhel

سنتعرف في هذا المقال عن كيفية إجراء التحويلات ما بين الأنواع في تعابير لغة سي C

التعابير والعمليات الحسابية

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

دعنا نبدأ أولًا ببعض المصطلحات، إذ تُبنى التعابير في لغة سي من مزيجٍ يتكون من العوامل والمُعاملات operands. لنأخذ على سبيل المثال التعبير التالي:

x = a+b*(-c)

لدينا العوامل = و + و * و -، والمُعاملات التي هي المتغيرات x و a و b و c، كما يمكنك ملاحظة القوسين أيضًا اللذين يمكن استخدامهما في تجميع التعبيرات الجزئية مثل c-. تنقسم معظم مجموعة عوامل لغة سي الواسعة إلى عوامل ثنائية binary operators تأخذ مُعاملين، أو عوامل أحادية unary operators تأخذ مُعاملًا واحدًا؛ ففي مثالنا كان - مُستخدمًا مثل عاملٍ أحادي، ويؤدي دورًا مختلفًا عن عامل الطرح الثنائي الذي يُمثّل بالرمز ذاته. قد تنظر إلى الفرق بأنه لا يستحق الذكر وأن وظيفة العامل ذاتها أو متشابهة في الحالتين، ولكنه على النقيض تمامًا فإنه يستحقّ الذكر، لأن لبعض العوامل -كما ستجد لاحقًا- شكل ثنائي وآخر أحادي وكلّ وظيفة مختلفة كاملًا عن الأخرى، ويُعد عامل الضرب الثنائي * الذي يعمل عمل الموجّه باستخدام المؤشرات في حالته الأحادية مثالًا جيدًا على ذلك.

تتميز لغة سي بأن العوامل قد تظهر بصورةٍ متتالية دون الحاجة للأقواس للفصل فيما بينهما في تعبيرٍ ما، إذ يمكننا كتابة المثال السابق على النحو التالي وسيظلّ تعبيرًا صالحًا.

x = a+b*-c;

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

التحويلات

تسمح لغة سي بمزج عدّة أنواع ضمن التعبير الواحد، وتسمح أيضًا بالعوامل التي يؤدي استخدامها إلى تحويلات للأنواع ضمنيًا، يصف هذا القسم الطريقة التي تحدث بها هذه التحويلات. ينبغي على مبرمجي لغة سي القديمة (التي سبقت المعيار) قراءة هذا القسم بانتباه، إذ تغيّرت العديد من القواعد وبالأخص التحويل من float إلى double والتحويل من أنواع الأعداد الصحيحة short، كما أن القواعد الأساسية في حفظ القيم value preserving قد تغيرت جدًأ في لغة سي المعيارية.

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

إليك أنواع التحويلات المتنوعة في التعابير الحسابية:

  • الترقيات العددية الصحيحة integral promotions.
  • التحويلات بين الأنواع العددية الصحيحة.
  • التحويلات بين الأنواع العددية العشرية.
  • التحويلات ما بين الأنواع العددية الصحيحة والعشرية.

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

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

السبب في هذا التغيير هو تقليل القيم التي قد تفاجئك عند مزج قيم من نوع ذو إشارة مع آخر عديم الإشارة، ففي معظم الحالات لا تعرف سبب هذا التغيير، وكان الدافع هنا التحويل إلى نتيجة "أكثر استخدامًا وطلبًا".

الترقية العددية الصحيحة

أقل العمليات الحسابية دقةً في لغة سي هي باستخدام نوع الأعداد الصحيحة int، لذلك تحصل هذه التحويلات ضمنيًّا في كل مرة تُستخدم الكائنات المذكورة في الأسفل ضمن تعبيرٍ ما. التحويل مُعرّف كما يلي:

  • عند تطبيق الترقية العددية الصحيحة إلى نوع short أو char (أو حقل البِت bitfield أو نوع المعدّد enumeration type الذَين لم نتطرق إليهما بعد):
    • ستُحّول القيمة إلى int، إذا كان من الممكن للمتغير تخزين جميع قيم النوع الأصل.
    • عدا ذلك، ستُحوَّل إلى unsigned int.

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

تُطبَّق هذه الترقيات على نحوٍ متكرر بمثابة جزءٍ من التحويلات الحسابية الاعتيادية ومُعاملات عوامل الإزاحة الأحادية، مثل + و - و ~، كما تُطبّق عندما يكون التعبير مُستخدمًا مثل وسيط لدالة ما دون أي معلومات عن النوع ضمن نموذج الدالة الأولي function prototype، كما سنشرح لاحقًا.

الأعداد الصحيحة ذات الإشارة وعديمة الإشارة

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

في حال التحويل من عدد صحيح ذو إشارة إلى عدد صحيح عديم الإشارة وكان طول هذا العدد مساويًا لطول (أو أطول من) النوع الأصلي، فلن تتغير القيمة بعد التحويل إذا كان العدد ذو الإشارة موجبًا؛ أما إذا كانت القيمة سالبة فهذا يعني تحويلها إلى صيغة ذات إشارة للنوع الأطول وجعلها عديمة الإشارة عن طريق إضافة قيمتها إلى القيمة العظمى التي يستطيع النوع عديم الإشارة تخزينها زائد واحد. تُحافظ هذه العملية على نمط البِتّات الأصلي للأرقام الموجبة وتضمن "خانة الإشارة الموسّعة" للأرقام السالبة وذلك في نظام المتمّم الثنائي.

لا يوجد هناك أي حالات "طفحان overflow" في جميع حالات تحويل عدد صحيح إلى نوع عديم إشارة قصير، فالنتيجة معرّفةٌ وفق "الباقي غير السالب مقسومًا على القيمة العظمى للرقم عديم الإشارة الذي يمكن تمثيله باستخدام النوع القصير زائد واحد". يعني هذا ببساطة أنه في بيئة تعمل بنظام المتمم الثنائي، تُنسخ البِتات منخفضة الترتيب low-order إلى الهدف ويكون التخلُّص من البتات مرتفعة الترتيب high-order.

قد تحصل بعض المشاكل عند تحويل العدد الصحيح إلى نوع ذي إشارة قصير إن لم يكن هناك مساحةٌ كافيةٌ لتخزين القيمة، وفي هذه الحالة تكون النتيجة حسب التنفيذ implementation defined، كما قد يتوقع معظم من اعتاد على سي القديمة أن يُنسخ نمط البِتات منخفضة الترتيب.

من الممكن أن يكون البند الأخير مثيرًا للقلق بعض الشيء إذا كنت تتذكر الترقية العددية الصحيحة، لأنك قد تنظر إلى الأمر على النحو التالي: إذا أسندت متغيرًا من نوع char إلى متحولٍ من نوع char، فسيُرقّى المتغير على اليمين إلى نوع من أنوع int. إذًا، هل من الممكن أن يؤدي الإسناد إلى تحويل int إلى char (مثلًا) وتفعيل البند "التعريف حسب التنفيذ"؟ الإجابة هي لا، لأن عملية الإسناد لا تضمّ ترقية الأعداد الصحيحة، لذا لا تقلق.

الأعداد العشرية والصحيحة

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

كما ذكرنا سابقًا، لا توجد هناك أي مشكلة إذا حدث التحويل بصورةٍ تصاعدية من float إلى double إلى long double، إذ من الممكن لجميع الأنواع السابقة تخزين جميع القيم التي تتسع في الأنواع الأصغر منها، وبذلك تحصل عملية التحويل دون أي فقدان للمعلومات؛ بينما سينتج في التحويل في الاتجاه المعاكس سلوكٌ غير محدد في حال كانت القيمة خارج مجال القيم التي يمكن للنوع تخزينها، وفي حال كانت القيمة ضمن المجال ولكن لا يمكن تخزينها بدقتها بالضبط النتيجة، ستكون واحدةً من القيمتين المجاورتين الممكن تخزينها، ويجري اختيارها حسب التنفيذ، وهذا يعني أن القيمة ستفقد جزءًا من دقتها.

التحويلات الحسابية الاعتيادية

هناك العديد من التعابير التي تتضمن استخدام تعابير فرعية subexpressions تحتوي على خليطٍ من الأنواع مع عوامل، مثل "+" و"*" وما شابه. إذا كانت للمُعاملات ضمن التعبير عدّة أنواع، فهذا يعني أن هناك بعض التحويلات الواجب إجراؤها حتى تكون النتيجة النهائية من نوع معين، والتحويلات هي:

  • إذا كان أي من المُعاملَين من نوع long double يُحوَّل المُعامل الآخر إلى long double ويكون هذا هو نوع النتيجة.
  • ماعدا ذلك، إذا كان أيٌ من المُعاملَين من نوع double، يُحوَّل المُعامل الآخر إلى double ويكون هذا هو نوع النتيجة.
  • ماعدا ذلك، إن كان أي من المُعاملَين من نوع float، يُحوًل المُعامل الآخر إلى float ويكون هذا هو نوع النتيجة.
  • ماعدا ذلك، تُطبّق الترقية العددية الصحيحة لكلا المُعاملين حسب التحويلات التالية:
    • إذا كان أيٌ من المُعاملَين من نوع unsigned long int، يُحوَّل المُعامل الآخر إلى unsigned long int ويكون هذا نوع النتيجة.
    • ماعدا ذلك، إذا كان أيٌ من المُعاملَين من نوع long int، يُحوَّل المُعامل الآخر إلى long int ويكون هذا هو نوع النتيجة.
    • ماعدا ذلك، إذا كان أيٌ من المُعاملَين من نوع unsigned int، يُحوَّل المُعامل الآخر إلى unsigned int ويكون هذا نوع النتيجة.
    • ماعدا ذلك، يجب أن يكون كلا المُعاملين من نوع int وعلى هذا نوع النتيجة أيضًا.

يتضمن المعيار جملةً غريبة: "يمكن تمثيل قيم المُعاملات من نوع الأعداد العشرية ونتائج تعابيرها بدقة ومجال أكبر من المطلوبة بالنسبة لنوعها، بالتالي لا يحدث تغيير للأنواع". السبب في هذا هو الحفاظ على معاملة لغة سي القديمة للمتغيرات من أنواع الأعداد العشرية، إذ كانت تُرقّى المتغيرات من نوع float في سي القديمة تلقائيًّا إلى double بالطريقة ذاتها التي تُرقّى متغيرات من نوع char إلى int، لذلك يمكن إنجاز التعبير الذي يحوي متغيرات من نوع float فقط كما لو كانت المتغيرات من نوع double، ولكن نوع النتيجة سيكون دائمًا float.

التأثير الوحيد لهذه العملية هو على حساب الأداء، وهو غير مهم لمعظم المستخدمين؛ ويُحدَّد ما إذا كانت التحويلات ستُطبّق أم لا، وأي نوع منها سيطبّق، عند الوصول إلى العامل operator.

لا تسبّب التحويلات بين الأنواع ومزجها أي مشكلات عمومًا، ولكن هناك بعض النقاط التي يجب الانتباه إليها؛ إذ يُعد المزج بين الأنواع ذات الإشارة وعديمة الإشارة بسيطًا إلى أن يحتوي النوع ذو الإشارة قيمة سالبة، إذ لا يمكن تمثيل قيمته باستخدام متغير عديم الإشارة، وعلينا إيجاد حل لهذه المشكلة. ينص المعيار على أن نتيجة تحويل عدد سالب إلى نوع عديم الإشارة هي أكبر قيمة يمكن تخزينها في النوع عديم الإشارة زائد واحد مضافةً إلى العدد السالب، ولأن الطفحان غير ممكن الحدوث في الأنواع عديمة الإشارة فالنتيجة دائمًا معرّفة على المجال. ولنأخذ int بطول 16 بِت مثالًا، إذ أن مجال النوع عديم الإشارة هو 0 إلى 65535، وبتحويل قيمة سالبة (ولتكن "7-") للنوع هذا يجب إضافة 7- إلى 65536 الذي يعطينا الناتج 65529.

يحتفظ المعيار بالطريقة القديمة في لغة سي، إذ يُسند نمط البتات في الرقم ذي الإشارة إلى الرقم عديم الإشارة، والطريقة التي يصفها المعيار هي الطريقة ذاتها التي تنتج عن إسناد نمط بِتّات على حاسوب يعمل بنظام المتمم الثنائي، وعلى أنظمة المتمم الأحادي أن تبذل مزيدًا من المجهود لتصل للنتيجة المرجوّة.

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

من السهل القول "لا تفعل هذا"، ولكن الأمر قد يحدث عن طريق الخطأ وفي هذه الحالة ستكون النتائج مفاجئة جدًا. ألقِ نظرةً على المثال التالي:

#include <stdio.h>
#include <stdlib.h>
main(){
      int i;
      unsigned int stop_val;

      stop_val = 0;
      i = -10;

      while(i <= stop_val){
              printf("%d\n", i);
              i = i + 1;
      }
      exit(EXIT_SUCCESS);
}

[مثال 7.2]

ربما تتوقع أن يطبع البرنامج لائحة قيم من "10-" إلى "0"، لكن هذا خاطئ، إذ تكمن المشكلة هنا في الموازنة؛ أي يُوازَن المتغير i الذي يخزن القيمة 10- مع متغير عديم الإشارة يخزن القيمة 0، ووفقًا لقواعد الحساب (استذكرها إن أردت) يجب أن نحوّل كلا النوعين إلى unsigned int أوّلًا ومن ثم نجري الموازنة، وتصبح القيمة 10- مساوية 65526 على الأقل (تفقد ملف الترويسة <limits.h>) بعد تحويلها، وتُوازن فيما بعد مع 0 وهي أكبر من القيمة كما هو واضح، وبذلك لا تُنفّذ الحلقة التكرارية إطلاقًا. العبرة هنا هو أنه عليك تجنُّب استخدام الأعداد عديمة الإشارة إلا في حالة استخدامك المقصود لها، وعندما تستخدمها انتبه جيّدًا بخصوص مزجها مع الأعداد ذات الإشارة.

المحارف العريضة

كما ذكرنا سابقًا، يسمح المعيار بمجموعات المحارف الموسّعة، إذ يمكنك استخدام ترميز الإدخال بالإزاحة shift-in والإخراج بالإزاحة shift-out، التي تسمح بتخزين المحارف متعددة البايتات في سلاسل نصية اعتيادية في لغة سي، والتي في حقيقة الأمر مصفوفات من نوع char كما سنتعرف لاحقًا؛ أو يمكنك استخدام التمثيل الذي يستخدم أكثر من بايت واحد لتخزين كل محرف من المحارف. يمكننا استخدام سلاسل الإزاحة فقط في حالة معالجة المحارف بترتيب محدد، إذ إن الطريقة عديمة الفائدة في حال أردت إنشاء مصفوفة محارف والوصول إليهم بغض النظر عن ترتيبهم. إليك مثالًا استخدمناه سابقًا مضافًا إليه أدلة indexes مصفوفة منطقية وفعلية:

0 1 2  3   4 5 6  7   8 9 (actual array index)
a b c <SI> a b g <SO> x y
0 1 2      3 4 5      6 7 (logical index)

حتى لو استطعنا الوصول إلى المُدخلة "الصالحة correct" ذات الدليل "5" في المصفوفة، فلن تنتهِ المشكلة، إذ لا يمكن تمييز النتيجة التي حصلنا عليها إن كانت مرمّزة أو هي "g" حرفيًّا. الحل الواضح لهذه المشكلة هو استخدام قيم مميّزة لجميع المحارف في مجموعة المحارف التي نستخدمها، لكن هذا الأمر يتطلب مزيدًا من البتات الموجودة في char اعتيادي، وأن نكون قادرين على تخزين كل قيمة على نحوٍ منفصل دون استخدام تقنية الإزاحة أو أي تقنية تعتمد على موضع القيم، وهذا هو الغرض من استخدام النوع wchar_t؛ إذ يُعدّ هذا النوع مرادفًا لأنواع الأعداد الصحيحة الأخرى (يمكنك الاطلاع على تعريفه في ملف الترويسة "")، وهو نوعٌ معرّفٌ حسب التنفيذ ويُستخدم في تخزين المحارف الموسعة عندما تريد إنشاء مصفوفة منها. يضمن المعيار التفاصيل التالية المتعلقة بقيمة المحارف العريضة:

  • يستطيع المتغير من نوع wchar_t تخزين قيم فريدة لكل محرف من أكبر مجموعة محارف يدعمها التنفيذ.
  • المحرف الفارغ null قيمته الصفر.
  • تماثل قيمة ترميز كل محرف من مجموعة المحارف الأساسية (ألقِ نظرةً على مقال المحارف المُستخدمة في لغة C فقرة الأبجدية الاعتيادية) في النوع wchar_t القيمة المُخزنة في char.

هناك دعم أكبر لطريقة ترميز المحارف هذه، مثل السلاسل النصية strings التي تكلمنا عنها سابقًا، إذ تُنفَّذ على أنها مصفوفة من المحارف char، مع أن قيمتها تبدو على النحو التالي:

"a string"

للحصول على سلاسل نصية من نوع wchar_t، اكتب السلسلة النصية كما هي مسبوقة بالحرف L، على سبيل المثال:

L"a string"

علينا أن نفهم الفرق بين المثالين السابقين، إذ أن السلاسل النصية هي في حقيقة الأمر مصفوفات وعلى الرغم من غرابة الأمر إلا أنّنا نستطيع استخدام دليل المصفوفة عليها:

"a string"[4]
L"a string"[4]

كلا التعبيرين السابقين صالح، إذ أن التعبير الأول من نوع char وقيمته ممثلةٌ بالحرف r (تذكر أن دليل المصفوفات يبدأ من صفر وليس واحد)، والتعبير الثاني من نوع wchar_t وقيمته ممثلةٌ أيضًا بالحرف r.

يصبح الأمر مثيرًا للاهتمام عند استخدامنا للمحارف الموسعة، إذ تظهر لنا بعض المشكلات إذا استخدمنا الترميز <a> و <b> للدلالة على محارف "إضافية" عن مجموعة المحارف الاعتيادية، أي ترميز هذه المحارف باستخدام تقنية إزاحة ما، لاحظ المثالين:

"abc<a><b>"[3]
L"abc<a><b>"[3]

الحالة الثانية سهلة الفهم، فهي مصفوفةٌ من نوع wchar_t والترميز الموافق لها يبدأ بالمحرف <a> أيًّا كان هذا الترميز (لنفترض أنه ترميز إلى الحرف اليوناني الموافق)؛ أما الحالة الأولى فهي غير ممكنة التنبؤ، إذ أن النوع هو char بلا شك لكن قيمته هي وسم الإدخال بالإزاحة غالبًا.

كما هو الحال مع السلاسل النصية، هناك ثوابت محارف عريضة، مثل 'a' التي لها نوع char وقيمة الترميز متجاوبة مع قيمة المحرف a، أما المحرف التالي:

L'a'

فهو ثابت من نوع wchar_t، وعند استخدام المحارف متعددة البايتات في المثال الذي سبقه، فهذا يعني أن قيمته تساوي محارف متعددة في محرف ثابت واحد على سبيل المثال:

'xy'

في الحقيقة، يُعد هذا التعبير صحيحًا ولكنه يعني شيئًا طريفًا. وسيُحوَّل المحرف متعدد البايتات في المثال الثاني إلى قيمة wchar_t الموافقة.

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

التحويل بين الأنواع

في بعض الأحيان، ينتج نوع بيانات من تعبير ما ولكنك لا تريد استخدام هذا النوع، وتريد تحويله قسريًّا إلى نوع مختلف، وهذا هو الغرض من التحويل بين الأنواع casts. عند وضع اسم النوع بين قوسين على النحو التالي:

(int)

فأنت تنشئ هنا عاملًا أحاديًا unary operator يُسمّى بالتحويل بين الأنواع cast، إذ يغير التحويل بين الأنواع قيمة التعبير الواقع على يمينه إلى النوع المحدد بداخل الأقواس. على سبيل المثال، إذا كنت تجري عملية القسمة بين عددين صحيحين a/b فسيستخدم التعبير الناتج قسمة الأعداد الصحيحة ويتخلص من أي باقي، ويمكنك استخدام متغيرات وسيطة من نوع أعداد عشرية للحفاظ على الجزء العشري من القيمة الناتجة أو استخدام التحويل بين الأنواع. يوضح المثال التالي الطريقتين:

#include <stdio.h>
#include <stdlib.h>

/*
* Illustrates casts.
* For each of the numbers between 2 and 20,
* print the percentage difference between it and the one
* before
*/
main(){
      int curr_val;
      float temp, pcnt_diff;

      curr_val = 2;
      while(curr_val <= 20){
              /*
               * % difference is
               * 1/(curr_val)*100
               */
              temp = curr_val;
              pcnt_diff = 100/temp;
              printf("Percent difference at %d is %f\n",
                      curr_val, pcnt_diff);
              /*
               * Or, using a cast:
               */
              pcnt_diff = 100/(float)curr_val;
              printf("Percent difference at %d is %f\n",
                      curr_val, pcnt_diff);
              curr_val = curr_val + 1;
      }
      exit(EXIT_SUCCESS);
}

[مثال 8.2]

الطريقة الأسهل لتتذكر الاستخدام الصحيح للتحويل بين الأنواع هو كتابته وكأنك تُصرّح عن متحول من نوع تريده، ومن ثم ضع الأقواس حول التصريح بالكامل واحذف اسم المتغير، مما سيعطيك التحويل بين الأنواع. يوضح الجدول 6.2 بعض الأمثلة البسيطة، قد تلاحظ أن بعض الأنواع لم تُقدّم بعد، لكن سيتوضّح التحويل بين الأنواع أكثر عند استخدام الأنواع المعقدة. تجاهل الأمثلة التي لا تفهمها بعد، لأنك ستكون قادرًا على استخدام هذا الجدول مثل مرجعٍ لاحقًا.

التصريح التحويل بين الأنواع النوع
;int x (int) int
;float f (float) float
;char x[30] (char [30]) مصفوفة من char
;int *ip (* int) مؤشر إلى int
;()int (*f) (() (*) int) مؤشر لدالة تُعيد النوع int

[جدول 6.2. التحويل بين الأنواع]

ترجمة -وبتصرف- لقسم من الفصل Variables and Arithmetic من كتاب The C Book.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...