מה זה type casting
type casting (המרת טיפוסים) פירושו להמיר ערך מטיפוס אחד לאחר: להפוך double ל-int, char לקוד המספרי שלו, או מצביע למחלקת בסיס למצביע למחלקה נגזרת. C++ מבצעת חלק מההמרות האלה בשבילכם אוטומטית, אבל אלה שהיא עושה בשקט הן בדיוק המקום שבו באגים מתחבאים.
יש שני סוגים: המרות מרומזות (implicit) שקורות מעצמן, ו-casts מפורשים (explicit) שכותבים בעצמכם. כבר פגשתם סימפטום אחד של זה באופרטורים: חילוק בשלמים. casting הוא הדרך לקחת עליו שליטה.
המרות מרומזות
כשמערבבים טיפוסי מספרים בביטוי, C++ מקדמת את הטיפוס ה"קטן" ל"גדול" כדי ששני הצדדים יתאימו. בדרך כלל זה עושה את מה שרציתם.
הצרות מתחילות כשההמרה הולכת לכיוון השני: מטיפוס רחב יותר לצר יותר. זו המרת narrowing, והיא יכולה לאבד נתונים בשקט.
המרה מ-float ל-int קוטעת לכיוון אפס: היא לא מעגלת, אז 3.99 הופך ל-3. ודחיפה של 300 לתוך char גולשת. מהדרים רבים מזהירים כאן; חלק לא. כשאתם באמת מתכוונים לצמצם, אמרו את זה במפורש עם cast, כדי שהקורא הבא ידע שזה היה בכוונה.
תיקון מלכודת החילוק בשלמים
הסיבה הנפוצה ביותר לעשות cast היא חילוק. כששני האופרנדים שלמים, / מבצע חילוק בשלמים וזורק את השארית.
התיקון הוא static_cast<double> על אופרנד אחד לפני החילוק. טעות נפוצה היא static_cast<double>(got / total): זה מאוחר מדי, כי got / total כבר שווה 0 כשה-cast רץ, אז מקבלים 0.0. המירו אופרנד, לא את התוצאה.
static_cast: ה-cast שבברירת המחדל
C++ נותנת ארבעה casts בעלי שם. זה שתשתמשו בו ב-95% מהמקרים הוא static_cast<T>(value), שמבצע המרות מוגדרות היטב בין טיפוסים קשורים: המרות מספריות, enum ל-int, void* חזרה למצביע עם טיפוס, ומעלה או מטה בהיררכיית מחלקות כשאתם כבר יודעים מה הטיפוס.
העדיפו static_cast על פני ה-cast הישן בסגנון C, (int)balance. cast בסגנון C ינסה כל המרה כדי שהקוד יתקמפל, כולל המסוכנות שבהמשך, ולכן הוא יכול בשקט להסיר const או לפרש מחדש בתים גולמיים. static_cast מאפשר רק המרות שהמהדר באמת יכול להצדיק, ואת ה-static_cast<...> הארוך קל מאוד למצוא ב-code review.
// להימנע: cast בסגנון C, בלי רשת ביטחון:
int dollars = (int) balance;
// להעדיף: מפורש, נבדק, קל לחיפוש:
int dollars = static_cast<int>(balance);
שלושת ה-casts האחרים (במשורה)
שאר ה-casts קיימים לתפקידים ספציפיים וצרים. פנו אליהם רק כש-static_cast באמת לא יכול לעשות את העבודה.
const_cast מסיר (או מוסיף) const. השימוש הלגיטימי היחיד שלו הוא קריאה ל-API בסגנון C ששכח לסמן פרמטר כ-const. שינוי של אובייקט שהוצהר במקור כ-const דרך const_cast הוא undefined behavior.
void legacyApi(char* msg); // API ישן, לא מקבל const
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // בסדר רק אם legacyApi לא כותב אליו
reinterpret_cast מפרש מחדש את תבנית הביטים הגולמית, למשל מצביע ככתובת מספרית. הוא לא מבצע שום המרה ומסוכן מאוד; כמעט תמיד זה סימן שכדאי לחשוב מחדש על העיצוב.
dynamic_cast ממיר בבטחה מצביע או הפניה למחלקת בסיס לטיפוס נגזר בזמן ריצה, לפי הטיפוס האמיתי של האובייקט. הוא דורש בסיס פולימורפי (מחלקה עם לפחות פונקציה וירטואלית אחת) ומחזיר nullptr אם ה-cast לא מתאים.
אילו 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וכדומה. - שימוש ב-cast בסגנון C. הוא מסתיר איזו המרה קורית. השתמשו ב-
static_castותקבלו שגיאת קומפילציה כשההמרה לא בטוחה, במקום הפתעה שקטה. - צמצום לטיפוס קטן מדי. המרה של
300ל-charאו שלlongענק ל-intמתגלגלת או גולשת. בחרו טיפוס יעד רחב מספיק לטווח.
הבא בתור: If-Else
עכשיו כשאתם יודעים להמיר ולהשוות ערכים בצורה נקייה, השלב הבא הוא לקבל איתם החלטות. משפט if-else מריץ קוד שונה לפי האם תנאי הוא true: הבסיס של כל תוכנית שמתפצלת.
שאלות נפוצות
מה ההבדל בין static_cast ל-cast בסגנון C ב-C++?
cast בסגנון C כמו (int)x מנסה כל המרה בתורה: הוא יכול בשקט להפוך ל-reinterpret_cast מסוכן או להסיר const. static_cast<int>(x) מבצע רק המרות קשורות שהמהדר יכול לאמת, כך שהמהדר דוחה שטויות. ב-C++ מודרנית תמיד העדיפו static_cast על פני casts בסגנון 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++?
המרה לטיפוס שלא יכול להכיל את הערך היא המרת narrowing (צמצום). המרה מ-float ל-int קוטעת את השבר (static_cast<int>(3.99) הוא 3), ומספר שלם מחוץ לטווח או מתגלגל (unsigned) או שתוצאתו תלויה במימוש (signed). המהדר בדרך כלל לא יעצור אתכם, אז המירו בכוונה ווודאו שטיפוס היעד רחב מספיק.