למה טיפוסים חשובים ב-C++
כשמצהירים על ערך עם טיפוס מפורש כמו int age = 30;, הטיפוס הוא לא רק תווית: הוא אומר למהדר כמה בתים לשריין, איך לפרש את הבתים האלה ואילו פעולות חוקיות. בחירה לא נכונה של טיפוס עלולה לגרום לאובדן דיוק שקט, לגלישה או ל-undefined behavior.
C++ מחלקת את הטיפוסים המובנים שלה לכמה משפחות: מספרים שלמים, מספרים בנקודה צפה, טיפוס תו וטיפוס בוליאני. נסתכל על כל אחת, ואז על הכללים שמכשילים אנשים.
הטיפוסים הבסיסיים
הנה אחד מכל טיפוס מרכזי בתוכנית אחת. שימו לב לסיומות הליטרלים (L, f, u) ולגרשיים הבודדים סביב char:
bool מודפס כברירת מחדל כ-1 או 0, לא כ-true/false. char משתמש בגרשיים בודדים: 'A' הוא תו אחד, בעוד ש-"A" (במירכאות כפולות) הוא ליטרל מחרוזת, טיפוס שונה לגמרי. שתי הטעויות האלה נפוצות מאוד בהתחלה.
הגדלים לא קבועים
זו ההפתעה הגדולה ביותר למי שמגיע משפות כמו Java. התקן של C++ מבטיח רק גדלים מינימליים וסדר יחסי (short ≤ int ≤ long ≤ long long). הגודל בפועל תלוי במהדר ובפלטפורמה. תמיד בדקו עם sizeof:
בבנייה טיפוסית של Linux ב-64 ביט תראו int = 4, long = 8. אבל ב-Windows של 64 ביט, long הוא רק 4 בתים. פער הניידות הזה הוא בדיוק הסיבה לא לכתוב קוד שמניח ש-long הוא 64 ביט.
כשצריך רוחב מדויק, השתמשו בטיפוסים השלמים ברוחב קבוע מ-<cstdint>:
השתמשו ב-int32_t/int64_t לפורמטים של קבצים, לפרוטוקולי רשת, או לכל דבר שחייב להתנהג זהה בכל המכונות. שימו לב להמרה (int)a: הזרמה של טיפוס של 8 ביט מדפיסה אותו כתו ולא כמספר, ולכן ממירים אותו קודם.
signed מול unsigned
כל טיפוס שלם מגיע בשני טעמים. טיפוס signed יכול להחזיק מספרים שליליים; טיפוס unsigned לא יכול, והוא מחליף את הטווח השלילי במקסימום חיובי גבוה יותר. int רגיל הוא signed כברירת מחדל.
חיסור מ-0 שהוא unsigned מתגלגל למספר חיובי ענק במקום להפוך לשלילי. זה פוגע באנשים כל הזמן, במיוחד עם size_t (טיפוס unsigned) שמוחזר מ-.size():
vector<int> v = {1, 2, 3};
// סכנה: v.size() הוא unsigned. אם v ריק, v.size() - 1 מתגלגל
// למספר עצום והלולאה רצה כמעט לנצח.
for (size_t i = 0; i <= v.size() - 1; i++) { /* ... */ }
העדיפו i < v.size() (אף פעם לא <= size() - 1), או עקפו את כל הבעיה עם לולאת for מבוססת טווח.
גלישה של מספרים שלמים היא undefined behavior
בניגוד לגלגול של unsigned (שמוגדר היטב), גלישה של מספר שלם signed היא undefined behavior ב-C++. המהדר רשאי לעשות כל דבר: להחזיר זבל, להסיר את הבדיקה באופטימיזציה, או לקרוס:
התיקון זהה למלכודת הגלישה בכל שפה: מבצעים את החישוב בטיפוס רחב יותר. המירו אחד האופרנדים ל-long long לפני ה-+, כך שהחיבור יתבצע ב-64 ביט. המרת התוצאה אחר כך מאוחרת מדי: הגלישה כבר קרתה.
בחירת הטיפוס הנכון
ברוב הקוד ברירות המחדל בסדר: int למספרים שלמים, double לעשרוניים. בחרו במשהו אחר רק כשיש לכם סיבה.
| טיפוס | גודל טיפוסי | מתי להשתמש |
|---|---|---|
int | 32 ביט | ברירת המחדל למספרים שלמים |
long long | 64 ביט | ערכים מעל כ-2 מיליארד: חותמות זמן, מונים גדולים |
double | 64 ביט | ברירת המחדל לעשרוניים, דיוק טוב |
float | 32 ביט | מערכים שבהם הזיכרון צפוף והדיוק יכול להיפגע |
bool | בית אחד | דגל של אמת/שקר |
int32_t / int64_t | מדויק | פורמטים חוצי פלטפורמות, פרוטוקולים, מניפולציה של ביטים |
כמה מלכודות שכדאי לזכור. ל-float יש רק כ-7 ספרות עשרוניות משמעותיות, ולכן 0.1f + 0.2f אינו בדיוק 0.3: העדיפו double אלא אם באמת צריך לחסוך בזיכרון. ו-char יכול להיות signed או unsigned בהתאם לפלטפורמה, אז אם אתם מבצעים חישובים על בתים גולמיים, כתבו במפורש signed char או unsigned char.
הבא בתור: מילת המפתח auto
לכתוב את הטיפוס בכל פעם זה מייגע, ולפעמים הטיפוס ארוך או קשה לניסוח. C++ מאפשרת למהדר להסיק אותו בשבילכם עם מילת המפתח auto: auto x = 42; הופך את x ל-int, ו-auto it = v.begin(); חוסך לכם להקליד טיפוס איטרטור ארוך. העמוד הבא מסביר מתי auto הופכת קוד לברור יותר ומתי היא מסתירה יותר מדי.
שאלות נפוצות
מהם טיפוסי הנתונים הבסיסיים ב-C++?
הטיפוסים הבסיסיים הם מספרים שלמים (short, int, long, long long), נקודה צפה (float, double, long double), טיפוס התו char והטיפוס הבוליאני bool. כל טיפוס שלם יכול להיות גם signed או unsigned. כל השאר, std::string, מערכים, המחלקות שלכם, בנוי מעל אלה.
מה ההבדל בין int ל-long ב-C++?
שניהם מאחסנים מספרים שלמים, אבל long מובטח להיות רחב לפחות כמו int (לרוב 64 ביט בפלטפורמות של 64 ביט, אבל רק 32 ביט ב-Windows). התקן קובע רק גדלים מינימליים, ולכן לרוחב מובטח השתמשו בטיפוסים ברוחב קבוע מ-<cstdint>, כמו int32_t ו-int64_t.
מה הגודל של int ב-C++?
התקן מבטיח רק ש-int הוא לפחות 16 ביט, אבל כמעט בכל מחשב שולחני ושרת מודרני הוא 32 ביט. מכיוון שהגדלים תלויים בפלטפורמה, לעולם אל תניחו: הדפיסו sizeof(int) כדי לבדוק, או השתמשו בטיפוסים מ-<cstdint> כמו int32_t כשצריך רוחב מדויק.