ما هو تحويل الأنواع
تحويل الأنواع يعني تحويل قيمة من نوع إلى آخر - تحويل double إلى int، أو char إلى رمزه العددي، أو مؤشّر إلى صنف أساس إلى مؤشّر إلى صنف مشتقّ. تؤدّي C++ بعض هذه التحويلات نيابةً عنك تلقائيًا، لكن تلك التي تؤدّيها بصمت هي بالضبط حيث تختبئ العلل.
هناك نوعان: تحويلات ضمنية تحدث من تلقاء نفسها، وتحويلات صريحة تكتبها بنفسك. لقد صادفت أحد أعراض ذلك في المعاملات - القسمة الصحيحة. والتحويل هو الطريقة التي تتحكم بها في ذلك.
التحويلات الضمنية
عندما تمزج أنواعًا عددية في تعبير، ترفع C++ النوع "الأصغر" إلى "الأكبر" كي يتطابق الطرفان. وهذا يفعل عادةً ما تريده.
تبدأ المتاعب عندما يسير التحويل في الاتجاه المعاكس - من نوع أوسع إلى نوع أضيق. هذا تحويل تضييق، وقد يفقد البيانات بصمت.
التحويل من float إلى int يقطع نحو الصفر - فهو لا يقرّب، لذا تصبح 3.99 هي 3. وحشر 300 في char يسبّب فيضانًا. تنبّه مترجمات كثيرة هنا؛ وبعضها لا يفعل. وعندما تقصد التضييق فعلًا، صرّح به بوضوح عبر تحويل كي يعرف القارئ التالي أن ذلك كان مقصودًا.
إصلاح فخّ القسمة الصحيحة
السبب الأكثر شيوعًا للتحويل هو القسمة. عندما يكون المُعاملان عددين صحيحين، تؤدّي / قسمة صحيحة وتتخلّص من الباقي.
الإصلاح هو تطبيق static_cast<double> على أحد المُعاملين قبل القسمة. ومن الأخطاء الشائعة static_cast<double>(got / total) - وهو متأخّر جدًا، لأن got / total تساوي 0 بالفعل عند تنفيذ التحويل، فتحصل على 0.0. حوّل مُعاملًا، لا الناتج.
static_cast: تحويلك الافتراضي
تمنحك C++ أربعة تحويلات مسمّاة. والذي ستستخدمه في 95% من الأحيان هو static_cast<T>(value)، الذي يؤدّي تحويلات معرَّفة جيدًا بين أنواع مترابطة - تحويلات عددية، ومن enum إلى int، ومن void* رجوعًا إلى مؤشّر ذي نوع، والصعود/النزول في تسلسل أصناف عندما تعرف النوع مسبقًا.
فضّل static_cast على التحويل القديم بأسلوب C (int)balance. فالتحويل بأسلوب C سيجرّب أي تحويل كي يُترجَم الكود - بما في ذلك التحويلات الخطيرة أدناه - لذا قد يزيل const بصمت أو يعيد تفسير البايتات الخام. أما static_cast فلا يسمح إلا بالتحويلات التي يستطيع المترجم تبريرها فعلًا، كما أن static_cast<...> المطوَّل سهل البحث عنه في مراجعة الكود.
// تجنّب - تحويل بأسلوب C، بلا شبكة أمان:
int dollars = (int) balance;
// فضّل - صريح، مُتحقَّق منه، يسهل البحث عنه:
int dollars = static_cast<int>(balance);
التحويلات الثلاثة الأخرى (استخدمها بحذر)
التحويلات المتبقّية موجودة لمهام محدّدة وضيّقة. لا تلجأ إليها إلا حين يعجز static_cast فعلًا عن أداء المهمة.
const_cast يزيل const (أو يضيفه). واستخدامه المشروع الوحيد هو استدعاء واجهة برمجية بأسلوب C نسيت تعليم أحد المعاملات بـ const. أما تعديل كائن أُعلِن أصلًا بأنه const عبر const_cast فهو سلوك غير معرَّف.
void legacyApi(char* msg); // واجهة قديمة، لا تأخذ const
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // مقبول فقط إن لم تكتب legacyApi فيه
reinterpret_cast يعيد تفسير نمط البتّات الخام - مثلًا مؤشّر بوصفه عنوانًا عدديًا. وهو لا يؤدّي أي تحويل وغير آمن إلى حدّ كبير؛ وهو غالبًا علامة على أن عليك إعادة التفكير في التصميم.
dynamic_cast يحوّل بأمان مؤشّرًا أو مرجعًا إلى صنف أساس نحو نوع مشتقّ أثناء التشغيل، مستخدمًا النوع الفعلي للكائن. ويتطلّب أساسًا متعدّد الأشكال (صنفًا فيه دالة افتراضية virtual واحدة على الأقل) ويُعيد nullptr إذا لم ينطبق التحويل.
لو كان a يشير إلى Animal آخر، لأعاد dynamic_cast<Dog*> القيمة nullptr وعمل فرع else - وهذا بالضبط سبب كونه أكثر أمانًا من استخدام static_cast على نحو أعمى للنزول في تسلسل.
أخطاء شائعة يجب تجنّبها
- تحويل الناتج بدلًا من مُعامل.
static_cast<double>(a / b)يُسقط جزءك الكسري أولًا. حوّلaأوb. - افتراض أن التحويل من float إلى int يقرّب. بل يقطع:
static_cast<int>(2.99)هو2. للتقريب استخدمstd::roundوstd::lroundوغيرها. - اللجوء إلى تحويل بأسلوب C. فهو يخفي أيّ تحويل يحدث. استخدم
static_castفتحصل على خطأ ترجمة حين يكون التحويل غير آمن، بدلًا من مفاجأة صامتة. - التضييق إلى نوع أصغر مما يلزم. تحويل
300إلىcharأوlongضخم إلىintيلتفّ أو يفيض. اختر نوعًا هدفًا واسعًا بما يكفي للمدى.
التالي: If-Else
الآن وقد صرت قادرًا على تحويل القيم ومقارنتها بنظافة، فالخطوة التالية هي اتخاذ القرارات بها. تنفّذ تعليمة if-else كودًا مختلفًا تبعًا لما إذا كان الشرط true - أساس كل برنامج يتفرّع.
الأسئلة الشائعة
ما الفرق بين static_cast والتحويل بأسلوب C في C++؟
التحويل بأسلوب C مثل (int)x يجرّب كل تحويل بالتتابع - فقد يتحوّل بهدوء إلى reinterpret_cast خطير أو يزيل const. أما static_cast<int>(x) فلا يؤدّي إلا التحويلات المترابطة التي يستطيع المترجم التحقق منها، لذا يرفض المترجم ما لا معنى له. في C++ الحديثة، فضّل دائمًا static_cast على التحويلات بأسلوب C؛ فهو أكثر أمانًا وأسهل بكثير في البحث عنه بأداة grep.
كيف أحوّل int إلى double في C++؟
استخدم static_cast<double>(x). يهمّ هذا أكثر ما يهمّ مع القسمة: 5 / 2 قسمة صحيحة وتعطي 2، لكن static_cast<double>(5) / 2 تعطي 2.5. حوّل أحد المُعاملين قبل أن تحدث القسمة - فتحويل الناتج، static_cast<double>(5 / 2), يأتي متأخرًا جدًا ويظل يعطي 2.0.
لماذا يعطي تحويل قيمة كبيرة إلى نوع أصغر رقمًا خاطئًا في C++؟
التحويل إلى نوع لا يستطيع استيعاب القيمة هو تحويل تضييق. فالتحويل من float إلى int يقطع الجزء الكسري (static_cast<int>(3.99) يساوي 3), أما العدد الصحيح خارج المدى فإما يلتفّ (للأنواع غير المُوقّعة) أو يكون سلوكه معرَّفًا بحسب التنفيذ (للمُوقّعة). عادةً لن يوقفك المترجم، لذا حوّل عن قصد وتأكّد أن النوع الهدف واسع بما يكفي.