Menu

המרת מחרוזת ל-int ב-C: atoi, strtol והמרת מספרים לטקסט

המרה בין טקסט למספרים ב-C: למה atoi לא יכולה לדווח על שגיאה, איך משתמשים נכון ב-strtol וב-strtod עם endptr ו-errno, הפיכת מספרים למחרוזות עם snprintf, וטריקים לספרה בודדת עם '0'.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

טקסט מגיע ממשתמשים, מקבצים ומשורת הפקודה כתווים. "42" הוא שלושה בתים, '4', '2', '\0', ושום דבר בו הוא המספר 42 עד שממירים אותו. C מציעה שני דורות של פונקציות המרה: הישנות והקצרות שלא יכולות לדווח על שגיאות, והארוכות יותר שכן. העמוד הזה מציג את שתיהן ומסביר למה תוכניות אמיתיות משתמשות בסט השני.

המשפחה הישנה: atoi, atol, atof

השמות שלהן אומרים הכול: ASCII ל-int, ל-long, ל-double. ארגומנט אחד, תוצאה אחת.

רווחים בהתחלה מדולגים, סימן אופציונלי נקרא, ספרות נצרכות, וכל מה שאחריהן מוזנח. זה נוח עד שהקלט שגוי:

שלושתן מדפיסות 0. אין קוד חזרה, אין דגל, אין דרך לשאול "זה עבד?", וערך שגולש מ-int הוא undefined behavior ולא שגיאה מדווחת. לליטרל קבוע בתוכנית צעצוע זה נסבל. לכל דבר שאדם או קובץ יכולים לספק, זה מחולל באגים שקטים.

הכלי הנכון: strtol

strtol (string to long) היא אותה המרה עם שני ערוצים לדיווח על מה שקרה.

long strtol(const char *text, char **endptr, int base);

endptr מקבל את התו הראשון שהפונקציה לא צרכה. base הוא בסיס הספירה: השתמשו ב-10, או ב-0 כדי לתת לקידומת של הליטרל עצמו להחליט (0x להקסדצימלי, 0 בהתחלה לאוקטלי).

קריאת endptr עונה על השאלות ש-atoi לא יכולה:

  • end == input אומר ששום דבר לא הומר: הטקסט לא התחיל במספר.
  • *end != '\0' אומר שהיה תוכן בסוף. אם זו שגיאה, זו החלטה שלכם: זה בסדר כשמנתחים את "42 apples", ובעיה כשכל השורה הייתה אמורה להיות מספר.

גלישה מדווחת בנפרד דרך errno. את הניב המלא והנכון שווה לכתוב פעם אחת ולהשתמש בו שוב:

שלושה פרטים הופכים את זה לנכון. errno = 0 לפני הקריאה חשוב, כי הספרייה רק מציבה ערך ב-errno ואף פעם לא מנקה אותו, כך שערך ישן מקריאה קודמת היה נקרא ככישלון של הקריאה הזו. strtol מחזירה long, שעשוי להיות רחב יותר מ-int, ולכן בדיקת הטווח מול INT_MIN/INT_MAX נפרדת מ-ERANGE. והחזרת הצלחה כסטטוס תוך מסירת הערך דרך מצביע היא הדרך שבה פונקציות C מדווחות "או ערך או כישלון": הערך המוחזר לא יכול לעשות את שתי העבודות.

strtoul היא המקבילה ל-unsigned, ו-strtoll/strtoull מטפלות ב-long long.

בסיסים אחרים

בסיס 16 קורא הקסדצימלי, ובסיס 0 בודק את הקידומת ומחליט בעצמו.

בסיס 0 הוא מלכודת במקרה ספציפי אחד: מספר עשרוני מרופד באפסים כמו "012" נקרא כאוקטלי ויוצא 10. אם הקלט שלכם עשרוני, כתבו 10 במפורש.

מספרים עשרוניים: strtod

strtod היא אותה צורה עבור double, ול-atof יש אותו פגם כמו ל-atoi.

strtod מקבלת גם כתיב מדעי ("1.5e3"), מספרים עשרוניים בהקסדצימלי, ואת המילים "inf" ו-"nan". ה-locale משפיע על המפריד העשרוני, וזה מפתיע אנשים שמנתחים קבצים שנכתבו במקום אחר. ב-locale ברירת המחדל "C" המפריד הוא תמיד נקודה.

ממספרים לטקסט: snprintf

בכיוון ההפוך, snprintf היא התשובה הסטנדרטית. היא כותבת לכל היותר את הגודל שנותנים לה, תמיד מסיימת את המחרוזת, ומחזירה את האורך שהתוצאה המלאה הייתה צריכה.

הערך המוחזר הוא בדיקת הקטיעה. אם הוא גדול מגודל ה-buffer או שווה לו, הטקסט לא נכנס:

שני דברים שלא כדאי לפנות אליהם: itoa אינה חלק מ-C הסטנדרטית, כך שקוד שמשתמש בה לא יתקמפל בכל מקום, ו-sprintf היא snprintf בלי מגבלת הגודל, כלומר היא תכתוב מעבר לסוף ה-buffer שלכם בלי שום תלונה.

תווים בודדים ו-'0'

מובטח שתווי הספרות '0' עד '9' רציפים, ולכן חיסור פשוט ממיר בין תו של ספרה לבין הערך שלה.

רק ספרות מתנהגות כך: אותיות לא חייבות להיות רציפות, ולכן c - 'a' אינו נייד לחשבון עם האלפבית. הגנו על החיסור עם isdigit לפני שסומכים עליו:

ה-cast ל-unsigned char נדרש מאותה סיבה כמו במחרוזות: הפונקציות של <ctype.h> לא מוגדרות לארגומנטים שליליים, ו-char רגיל יכול להיות signed.

באיזו פונקציה להשתמש

  • טקסט ממשתמש, מקובץ או מ-argv → strtol / strtod עם הבדיקה המלאה.
  • ליטרל שכתבתם בעצמכם ושבשליטתכם → atoi מקובלת, אם כי לא מרשימה.
  • מספר לתוך buffer → snprintf, ובדקו את הערך המוחזר.
  • תו של ספרה אחת → החסירו '0', אחרי isdigit.
  • אף פעם → gets, sprintf, itoa.

שאלות נפוצות

איך ממירים מחרוזת ל-int ב-C?

strtol(text, &end, 10) היא התשובה הנכונה: היא מחזירה את הערך, מציבה ב-end את התו הראשון שלא נצרך, ומדווחת על גלישה דרך errno. atoi(text) קצרה יותר, אבל מחזירה 0 גם עבור "0" וגם עבור "hello", בלי שום דרך להבדיל ביניהם.

מה הבעיה עם atoi ב-C?

היא לא יכולה לדווח על כישלון. atoi("abc") מחזירה 0, שאי אפשר להבחין בינו לבין אפס אמיתי, וגלישה היא undefined behavior ולא שגיאה מדווחת. השתמשו ב-strtol בכל פעם שהטקסט הגיע ממשתמש, מקובץ או מארגומנט של שורת הפקודה.

איך ממירים int למחרוזת ב-C?

snprintf(buf, sizeof buf, "%d", n). היא סטנדרטית, מוגבלת לגודל ה-buffer, ומחזירה את מספר התווים שהתוצאה המלאה הייתה צריכה, כך שערך מוחזר ששווה ל-sizeof buf או גדול ממנו אומר שהטקסט נקטע. itoa היא לא C סטנדרטית.

איך הופכים את התו '7' למספר 7 ב-C?

מחסירים '0': int digit = c - '0';. תווי הספרות רציפים בכל ערכת תווים של C, ולכן החיסור נותן 0 עד 9. הגנו עליו קודם עם isdigit((unsigned char)c), כי לכל תו אחר לחשבון הזה אין משמעות.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל