כל פונקציה ב-<string.h> היא לולאה קטנה על מערך של char שעוצרת בתו ה-null המסיים. ברגע שזה ברור, הספרייה מפסיקה להיראות כמו רשימה של שמות קריפטיים ומתחילה להיראות כמו קומץ הלולאות שהייתם כותבים בעצמכם. העמוד הזה עוסק באלה שנושאות כמעט את כל העבודה האמיתית, ובכללי הגודל שמונעים מהן לכתוב מעבר לסוף ה-buffers שלכם.
כל מה שכאן דורש include אחד:
#include <string.h>
strlen: כמה תווים
strlen סופרת בתים עד התו המסיים, לא כולל אותו. היא מחזירה size_t, לכן הדפיסו אותה עם %zu.
strlen היא 6, ו-sizeof הוא 32. הראשון הוא הטקסט, השני הוא האחסון. בעמוד המחרוזות מוסבר למה ההבחנה הזו חשובה כל כך.
strlen עוברת על כל המחרוזת בכל קריאה, לכן אל תשימו אותה בתנאי של לולאה על אותה מחרוזת שלא משתנה:
/* סופרת מחדש את כל המחרוזת בכל איטרציה */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* סופרים פעם אחת */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy ו-strncpy: העתקה
strcpy(dst, src) מעתיקה את התווים ואת התו המסיים. אין לה מושג מה גודל dst, ולכן הקורא חייב להבטיח ש-dst מחזיק לפחות strlen(src) + 1 בתים.
strncpy מקבלת מספר מקסימלי, אבל יש לה מלכודת מפורסמת: כשהמקור באורך n בתים לפחות, היא מעתיקה בדיוק n תווים ובלי תו מסיים. תמיד סיימו את המחרוזת בעצמכם:
sizeof dst - 1 ועוד תו מסיים מפורש הוא הדפוס שכדאי לשנן. (strncpy גם מרפדת מקורות קצרים באפסים עד n בתים, וזו עבודה מבוזבזת ב-buffers גדולים: היא תוכננה לרשומות ברוחב קבוע, לא לבטיחות.)
strcat ו-strncat: חיבור
strcat(dst, src) מוסיפה את src לסוף מה שכבר נמצא ב-dst. היעד חייב להכיל את שתי המחרוזות ועוד תו מסיים אחד, והוא חייב כבר להכיל מחרוזת תקינה: הוספה לזיכרון לא מאותחל היא undefined behavior.
שימו לב ל-char path[64] = "/home/ada"; במקום char path[64];: המאתחל הוא מה שהופך אותו למחרוזת תקינה עבור ה-strcat הראשון.
strncat(dst, src, n) מוסיפה לכל היותר n תווים ותמיד מוסיפה תו מסיים, כך ש-n הוא המקום שנשאר, ולא הגודל הכולל של ה-buffer:
strncat(dst, src, sizeof dst - strlen(dst) - 1);
קריאות חוזרות ל-strcat סורקות מחדש את היעד כדי למצוא את הסוף שלו בכל פעם. בנייה של מחרוזת ארוכה בלולאה בדרך הזו היא ריבועית. לכל דבר בגודל משמעותי, עקבו בעצמכם אחרי מיקום הכתיבה או השתמשו ב-snprintf, שמוסברת בעמוד המרת מחרוזות.
strcmp: השוואה
== משווה כתובות, ולכן הוא false עבור שני מערכים נפרדים שמחזיקים את אותו טקסט. strcmp משווה את התווים ומחזירה את הסימן של ההפרש.
שלושה כללים:
- בדקו
strcmp(a, b) == 0לשוויון. כתיבתif (strcmp(a, b))פירושה "אם הן שונות", וזה נקרא הפוך כמעט לכולם. - רק הסימן מוגדר. אל תשוו את התוצאה ל-
1או ל--1. - הסדר הוא לפי ערך הבית, ולכן
"Zebra"בא לפני"apple"ב-ASCII. להשוואה שלא מבחינה בין אותיות גדולות לקטנות, המירו קודם את שני העותקים לאותיות קטנות:strcasecmpהנפוצה היא הרחבה של POSIX, ולא C סטנדרטית.
strncmp(a, b, n) משווה רק את n התווים הראשונים, וזו הדרך הנקייה לבדוק קידומת:
strchr ו-strstr: חיפוש
strchr(s, ch) מוצאת את המופע הראשון של תו, ו-strrchr מוצאת את האחרון. strstr(haystack, needle) מוצאת תת־מחרוזת. שלושתן מחזירות מצביע לתוך המחרוזת המקורית, או NULL כשאין התאמה.
חיסור המצביע המוחזר מההתחלה נותן את האינדקס. הטיפוס שלו הוא ptrdiff_t, ומדפיסים אותו עם %td. מכיוון שהתוצאה מצביעה לתוך המערך המקורי, at + 1 הוא שאר המחרוזת בלי שום העתקה, ניב נפוץ מאוד ב-C.
תמיד בדקו NULL לפני הפניה. strchr(email, '@') + 1 על מחרוזת בלי @ מחשב כתובת מתוך NULL, והתוכנית כבר לא מוגדרת.
פיצול מחרוזת
אין פונקציית split, אבל strchr ועוד תו מסיים עושים את העבודה במקום. הדוגמה הזו חותכת כתובת אימייל לשם משתמש ולדומיין:
memcpy מעתיקה מספר בתים מדויק בלי להתעניין בתווים מסיימים, וזה מה שרוצים כשכבר יודעים את האורך. אחר כך כותבים את התו המסיים ידנית.
דוגמה מלאה: נרמול שם
מחברים את כל הסיור: חיתוך רווחים, השוואה, העתקה וחיבור:
כל כתיבה מוגבלת ב-sizeof של היעד עצמו, ואחרי כל strncpy מגיע תו מסיים מפורש. המשמעת הזו, כשמיישמים אותה בעקביות, היא מה שהופך קוד מחרוזות ב-C לבטוח.
כללי גודל שכדאי לשמור
strcpyצריכהstrlen(src) + 1בתים פנויים ביעד.strcatצריכהstrlen(dst) + strlen(src) + 1.- אחרי
strncpy, כתבוdst[n - 1] = '\0'בעצמכם. - ב-
strncat, המספר הוא המקום שנשאר:sizeof dst - strlen(dst) - 1. - בדקו אם
strchrו-strstrהחזירוNULLלפני שמשתמשים בתוצאה. - השתמשו ב-
sizeof dstרק כש-dstהוא מערך אמיתי. בתוך פונקציה שמקבלתchar *dst,sizeofנותן את גודל המצביע, לכן העבירו את אורך ה-buffer כפרמטר נפרד.
שאלות נפוצות
מה strcmp מחזירה ב-C?
אפס כששתי המחרוזות זהות, ערך שלילי כשהראשונה באה לפני השנייה בסדר המיון, וערך חיובי כשהיא באה אחריה. רק הסימן בעל משמעות: לעולם אל תניחו שהיא מחזירה -1 או 1. הבדיקה שאתם רוצים היא כמעט תמיד if (strcmp(a, b) == 0).
למה אי אפשר להשוות מחרוזות עם == ב-C?
a == b משווה את שתי הכתובות, לא את התווים. שני מערכים נפרדים שמחזיקים "cat" נמצאים בכתובות שונות, ולכן ההשוואה היא false למרות שהטקסט זהה. השתמשו ב-strcmp(a, b) == 0 מתוך <string.h>.
מה ההבדל בין strcpy ל-strncpy?
strcpy מעתיקה עד ה-'\0' של המקור, בלי שום מושג מה גודל היעד. strncpy עוצרת אחרי n בתים לכל היותר, אבל אם המקור ארוך כזה היא מעתיקה בלי תו מסיים, ולכן אתם חייבים לכתוב dst[n - 1] = '\0'; בעצמכם. אף אחת מהן לא בטוחה אוטומטית: המשמעת לגבי הגודל היא שלכם.
איך מוצאים תת־מחרוזת ב-C?
strstr(haystack, needle) מחזירה מצביע למופע הראשון, או NULL אם אין כזה. מכיוון שהיא מחזירה מצביע לתוך המחרוזת המקורית, found - haystack נותן לכם את האינדקס. השתמשו ב-strchr כשאתם מחפשים תו בודד.