Menu

פונקציות מחרוזות ב-C: strlen, strcpy, strcat, strcmp ועוד

סיור מעשי ב-<string.h>: מדידה עם strlen, העתקה עם strcpy ו-strncpy, חיבור עם strcat, השוואה עם strcmp, חיפוש עם strchr ו-strstr, ואיך לקבוע גודל של buffers כך שאף אחת מהן לא תגלוש.

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

כל פונקציה ב-<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 כשאתם מחפשים תו בודד.

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

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

להתחיל