Menu
flag Ar iconالعربيةdown icon

تحويل الأنواع في لغة C: التحويلات الضمنية والتحويلات الصريحة

تحوّل C بين الأنواع باستمرار - أحيانًا لأنك طلبت ذلك بتحويل صريح، وأكثر الأحيان من تلقاء نفسها. إليك قواعد الترقية، وصياغة التحويل الصريح، والتحويلات التي تفقد البيانات بصمت.

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

تحوّل C القيم بين الأنواع باستمرار. وبعض تلك التحويلات تكتبه بنفسك بتحويل صريح؛ ومعظمها يؤدّيه المترجم بصمت وفق قواعد لم تخترها. ومعرفة أيّها أيّ هي الفرق بين "لماذا يكون معدّلي 3 دائمًا؟" وشيفرة تفعل ما تقول.

التحويل الضمني

كلما لقيت قيمة من نوع سياقًا يتوقّع نوعًا آخر، حوّلتها C:

التحويلات التي لا يمكن أن تفقد معلومات (int إلى double، وchar إلى int، وshort إلى long) هي توسيع وهي آمنة دائمًا. والتحويلات في الاتجاه الآخر تضييق ويمكن أن تفقد بيانات - فقد صارت 3.9 أعلاه 3 بلا تحذير ما لم تطلب واحدًا بـ -Wconversion.

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

حين يكون لعامل ثنائي معاملان من نوعين مختلفين، تحوّلهما C إلى نوع مشترك قبل أداء العمل. والسلّم من الأسفل إلى الأعلى:

  1. كل ما هو أصغر من int (char وshort و_Bool) يُرقّى إلى int. وهذه ترقية الأعداد الصحيحة وتحدث أولًا دائمًا.
  2. إن كان أحد الطرفين long double، صار الآخر long double.
  3. وإلا إن كان أحدهما double، صار الآخر double.
  4. وإلا إن كان أحدهما float، صار الآخر float.
  5. وإلا، فبين الأنواع الصحيحة، تفوز ذات الرتبة الأعلى، وإن تعادلت الرتب فاز غير الموقّع.

وتلك القاعدة الأخيرة هي التي تسبّب أخطاءً حقيقية. أما البقية فبديهية.

ترقية الأعداد الصحيحة هي سبب عدم فيضان حسابات char بالطريقة التي قد تتوقّعها - وسبب فيضانها حين تخزّن النتيجة مجددًا في char.

التحويلات الصريحة

التحويل الصريح هو النوع الهدف بين قوسين:

(double)x
(int)3.9
(char)65
(unsigned int)n

وينطبق على التعبير الذي يليه مباشرةً، ويرتبط بإحكام شديد - أوثق من * و/ و+.

يقسم السطر الأول كأعداد صحيحة (فيعطي 3) ثم يحوّل 3 إلى 3.0 - بعد فوات الأوان. أما الثاني فيحوّل total قبل القسمة، فيرى العامل / قيمة double وقيمة int، فيرقّي int ويؤدّي قسمة ذات فاصلة عائمة.

وتحويل معامل واحد يكفي. فالتحويلات الحسابية المعتادة تتولّى الآخر.

إصلاح القسمة الصحيحة

هذا هو أشيع سبب على الإطلاق لكتابة تحويل صريح في C:

وسطر النسبة المئوية مفيد: فـ passed / n هي 3 / 5، وتساوي 0 كأعداد صحيحة، و0 * 100 تساوي 0. والضرب قبل القسمة (100 * passed / n) يصلحها حتى بلا تحويل صريح، لأن 300 / 5 دقيقة - لكن ذلك لا ينجح إلا حين تتعاون الأرقام. والتحويل الصريح هو الإصلاح الموثوق.

اقتطاع لا تقريب

تحويل قيمة ذات فاصلة عائمة إلى عدد صحيح يهمل الكسر. وهو يقتطع باتجاه الصفر - ولا يقرّب:

وإن بنيت هذا على جهازك، فتذكّر أن math.h تحتاج -lm عند الربط على لينكس.

وخطر آخر: تحويل قيمة ذات فاصلة عائمة أكبر من أن يتّسع لها النوع الصحيح سلوك غير معرَّف، لا التفافًا. فـ (int)1e20 قد تنتج أي شيء. افحص المدى قبل التحويل حين لا تكون القيمة تحت سيطرتك.

char وint

قيمة char في C هي عدد صحيح صغير يحمل رمز محرف. والتحويل بين الاثنين عمل يومي:

digit - '0' هي الاصطلاح المعياري لتحويل محرف رقم إلى قيمته، وتعمل لأن محارف الأرقام العشرة مضمون أنها متتالية. وللحروف، فضّل toupper() وtolower() من ctype.h على حساب + 32 - فالإزاحة حقيقة في ASCII لا ضمانة في C.

وفخّ مرتبط: تأخذ دوال ctype.h مثل isdigit وtoupper قيمة int يجب أن تكون إما EOF وإما قابلة للتمثيل كـ unsigned char. وتمرير char عادي سالب (وهو ممكن، إذ قد يكون char العادي موقّعًا) غير معرَّف. حوّله: isdigit((unsigned char)c).

فخّ الموقّع وغير الموقّع

الخطوة 5 من سلّم التحويل - فوز غير الموقّع عند التعادل - تُنتج أكثر مقارنات C إدهاشًا:

تُحوَّل -1 إلى unsigned int، فيُعاد تفسير نمط بتاتها على أنه 4,294,967,295. وذلك أكبر من 1، فالمقارنة خاطئة.

والتحويل نفسه يجعل الحلقات تدور إلى الأبد:

/* BUG: i is unsigned, so i >= 0 is always true. When i is 0, i-- wraps. */
for (size_t i = n - 1; i >= 0; i--) { ... }

ويجعل فحوص الأطوال تفشل:

/* BUG: strlen returns size_t (unsigned). If the string is shorter
   than 5, len - 5 wraps to a huge number and the test passes. */
if (strlen(s) - 5 > 0) { ... }

أعد كتابتها if (strlen(s) > 5) فلا يقع الطرح إطلاقًا.

والدفاعات: أبقِ العدّادات والفهارس بإشارة واحدة في كل مكان، وترجم بـ -Wsign-compare (المتضمَّنة في -Wextra)، وحين تضطرّ إلى الخلط، حوّل صراحةً بعد التأكّد من أن القيمة لا يمكن أن تكون سالبة.

تحويل المؤشّرات

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

على جهاز صغير الطرف يطبع هذا 01 00 00 00. وفحص تمثيل الكائن عبر unsigned char * من تحويلات المؤشّرات القليلة التي يباركها المعيار صراحةً.

ومعظم تحويلات المؤشّرات الأخرى غير مباركة. فقراءة int عبر float * تخالف قاعدة التسمية المستعارة الصارمة وهي سلوك غير معرَّف وإن تُرجمت؛ استخدم memcpy لإعادة تفسير البايتات بدلًا من ذلك.

وعرفان يستحقّان المعرفة. يتحوّل void * من وإلى أي نوع مؤشّر كائن دون تحويل صريح في C، ولهذا ينبغي ألّا تحوّل نتيجة malloc:

int *arr = malloc(n * sizeof *arr);        /* correct C */
int *arr = (int *)malloc(n * sizeof *arr); /* needless; hides a missing header */

والتحويل مطلوب في C++، ولهذا تحمله شيفرة كثيرة. أما في C فيمكن أن يخفي خطأ نسيان <stdlib.h>.

وprintf("%p", ...) تتوقّع void *، فوسائط المؤشّرات هناك تحتاج تحويلًا فعلًا: printf("%p", (void *)p).

متى يكون التحويل الصريح الجواب الخاطئ

التحويل الصريح يُسكِت المترجم. وأحيانًا كان المترجم محقًّا.

long big = 5000000000L;
int  small = (int)big;    /* the cast hides real data loss */

إن كانت القيمة تتّسع فعلًا، وثّق التحويلُ أنك تحقّقت. وإن كانت قد لا تتّسع، فقد حوّل التحويلُ تحذيرًا إلى إجابة خاطئة صامتة. وقبل كتابة واحد، اسأل ما إذا كان الإصلاح هو تغيير نوع متغيّر بدلًا من ذلك - double بدل int، وsize_t بدل int، وlong long بدل long. والتحويل الصريح هو الأداة الصحيحة أساسًا حين يجب أن يلتقي نوعان صحيحان لعملية واحدة، كما في (double)sum / count.

الأسئلة الشائعة

كيف تجري تحويلًا صريحًا في لغة C؟

ضع النوع الهدف بين قوسين قبل القيمة: (double)x و(int)3.9 و(char)65. وينطبق التحويل على التعبير الذي يليه مباشرةً، فـ (double)a / b تحوّل a أولًا ثم تقسم، بينما (double)(a / b) تقسم كأعداد صحيحة وتحوّل النتيجة.

كيف أحوّل int إلى float في لغة C؟

الإسناد يفعل ذلك ضمنًا - فـ double d = 5; تخزّن 5.0. وداخل تعبير كثيرًا ما تحتاج تحويلًا صريحًا: فـ (double)total / count تفرض قسمة ذات فاصلة عائمة بدل القسمة الصحيحة.

ماذا يحدث حين تحوّل float إلى int في لغة C؟

يُهمَل الجزء الكسري - يُقتطَع باتجاه الصفر، ولا يُقرَّب أبدًا. فـ (int)3.9 تساوي 3 و(int)-3.9 تساوي -3. وللتقريب، أضف 0.5 قبل التحويل للموجبات، أو استخدم round() أو floor() أو ceil() من math.h.

لماذا تعطي مقارنة عدد موقّع بآخر غير موقّع إجابة خاطئة؟

لأن التحويلات الحسابية المعتادة في C تحوّل القيمة الموقّعة إلى غير موقّعة، فـ -1 < 1u خاطئة: إذ تصبح -1 عددًا موجبًا ضخمًا. أبقِ العدّادات والأحجام بإشارة واحدة، أو حوّل صراحةً بعد التحقّق من أن القيمة لا يمكن أن تكون سالبة.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن