Każda funkcja z <string.h> to mała pętla po tablicy char, która zatrzymuje się na znaku końca napisu. Gdy to jest jasne, biblioteka przestaje wyglądać jak lista tajemniczych nazw, a zaczyna jak garść pętli, które można by napisać samodzielnie. Ta strona omawia te, które wykonują niemal całą prawdziwą pracę, oraz zasady rozmiarów, które chronią przed zapisem za końcem buforów.
Wszystko tutaj wymaga jednego dołączenia:
#include <string.h>
strlen: ile jest znaków
strlen liczy bajty aż do terminatora, bez niego. Zwraca size_t, więc wypisuj wynik przez %zu.
strlen wynosi 6, a sizeof wynosi 32. Pierwsze to tekst, drugie to pamięć. Dlaczego to rozróżnienie jest tak ważne, wyjaśnia strona o napisach.
strlen przy każdym wywołaniu przechodzi cały napis, więc nie wstawiaj go do warunku pętli po tym samym, niezmiennym napisie:
/* liczy caly napis od nowa w kazdej iteracji */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* policz raz */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy i strncpy: kopiowanie
strcpy(dst, src) kopiuje znaki razem z terminatorem. Nie ma pojęcia, jak duże jest dst, więc wywołujący musi zagwarantować, że dst mieści co najmniej strlen(src) + 1 bajtów.
strncpy przyjmuje maksymalną liczbę znaków, ale ma słynną pułapkę: gdy źródło ma co najmniej n bajtów, kopiuje dokładnie n znaków i żadnego terminatora. Zawsze dopisuj go sam:
sizeof dst - 1 razem z jawnym terminatorem to wzorzec do zapamiętania. (strncpy dopełnia też krótkie źródła zerami aż do n bajtów, co przy dużych buforach jest zmarnowaną pracą: funkcję zaprojektowano do rekordów o stałej szerokości, a nie z myślą o bezpieczeństwie.)
strcat i strncat: łączenie
strcat(dst, src) dopisuje src na końcu tego, co już jest w dst. Miejsce docelowe musi zmieścić oba napisy plus jeden terminator i musi już zawierać poprawny napis: dopisywanie do niezainicjalizowanej pamięci to niezdefiniowane zachowanie.
Zwróć uwagę na char path[64] = "/home/ada"; zamiast char path[64];: to inicjalizator sprawia, że przed pierwszym strcat jest to poprawny napis.
strncat(dst, src, n) dopisuje co najwyżej n znaków i zawsze dodaje terminator, więc n to pozostałe wolne miejsce, a nie całkowity rozmiar bufora:
strncat(dst, src, sizeof dst - strlen(dst) - 1);
Każde kolejne wywołanie strcat od nowa przeszukuje miejsce docelowe w poszukiwaniu jego końca. Budowanie w ten sposób długiego napisu w pętli ma złożoność kwadratową. Przy czymkolwiek większym śledź własną pozycję zapisu albo użyj snprintf, opisanego na stronie o konwersji napisów.
strcmp: porównywanie
== porównuje adresy, więc dla dwóch różnych tablic z tym samym tekstem daje fałsz. strcmp porównuje znaki i zwraca znak różnicy.
Trzy zasady:
- Równość sprawdzaj przez
strcmp(a, b) == 0. Zapisif (strcmp(a, b))znaczy „jeśli się różnią”, co prawie każdy czyta na odwrót. - Zdefiniowany jest tylko znak wyniku. Nie porównuj go z
1ani-1. - Kolejność wynika z wartości bajtów, więc w ASCII
"Zebra"jest przed"apple". Aby porównać bez rozróżniania wielkości liter, najpierw zamień obie kopie na małe litery: popularnestrcasecmpto rozszerzenie POSIX, a nie standardowe C.
strncmp(a, b, n) porównuje tylko pierwsze n znaków i jest to czysty sposób na sprawdzenie prefiksu:
strchr i strstr: wyszukiwanie
strchr(s, ch) znajduje pierwsze wystąpienie znaku, a strrchr ostatnie. strstr(haystack, needle) znajduje podnapis. Wszystkie trzy zwracają wskaźnik do oryginalnego napisu albo NULL, gdy nic nie pasuje.
Odjęcie początku od zwróconego wskaźnika daje indeks. Jego typ to ptrdiff_t, wypisywany przez %td. Ponieważ wynik wskazuje do wnętrza oryginalnej tablicy, at + 1 to reszta napisu bez żadnego kopiowania, bardzo częsty idiom w C.
Zawsze sprawdzaj NULL przed dereferencją. strchr(email, '@') + 1 na napisie bez @ wylicza adres z NULL i od tej chwili program nie ma już zdefiniowanego zachowania.
Dzielenie napisu
Nie ma funkcji split, ale strchr plus terminator załatwia sprawę w miejscu. Ten przykład dzieli adres e-mail na użytkownika i domenę:
memcpy kopiuje dokładną liczbę bajtów, nie przejmując się terminatorami, a tego właśnie chcesz, gdy długość jest już znana. Terminator dopisuje się potem ręcznie.
Przykład w praktyce: porządkowanie imienia i nazwiska
Wszystko razem: przycinanie, porównywanie, kopiowanie i łączenie:
Każdy zapis jest ograniczony własnym sizeof miejsca docelowego, a po każdym strncpy następuje jawny terminator. Ta dyscyplina, stosowana konsekwentnie, sprawia, że kod napisowy w C jest bezpieczny.
Zasady rozmiarów do zapamiętania
strcpypotrzebujestrlen(src) + 1wolnych bajtów w miejscu docelowym.strcatpotrzebujestrlen(dst) + strlen(src) + 1.- Po
strncpysam zapiszdst[n - 1] = '\0'. - W
strncatliczba to pozostałe miejsce:sizeof dst - strlen(dst) - 1. - Sprawdzaj wynik
strchristrstrpod kątemNULL, zanim go użyjesz. - Używaj
sizeof dsttylko wtedy, gdydstjest prawdziwą tablicą. Wewnątrz funkcji przyjmującejchar *dstsizeofdaje rozmiar wskaźnika, więc długość bufora przekazuj jako osobny parametr.
Najczęściej zadawane pytania
Co zwraca strcmp w C?
Zero, gdy oba napisy są identyczne, wartość ujemną, gdy pierwszy jest w kolejności przed drugim, i wartość dodatnią, gdy jest po nim. Znaczenie ma tylko znak: nigdy nie zakładaj, że zwraca -1 lub 1. Test, którego potrzebujesz, to prawie zawsze if (strcmp(a, b) == 0).
Dlaczego w C nie można porównywać napisów przez ==?
a == b porównuje dwa adresy, a nie znaki. Dwie osobne tablice zawierające "cat" leżą pod różnymi adresami, więc porównanie daje fałsz, nawet jeśli tekst jest taki sam. Użyj strcmp(a, b) == 0 z <string.h>.
Czym różni się strcpy od strncpy?
strcpy kopiuje aż do '\0' źródła, nie mając pojęcia, jak duże jest miejsce docelowe. strncpy zatrzymuje się po co najwyżej n bajtach, ale jeśli źródło jest tak długie, nie kopiuje terminatora, więc musisz sam napisać dst[n - 1] = '\0';. Żadna z nich nie jest automatycznie bezpieczna, dyscyplina rozmiarów należy do ciebie.
Jak znaleźć podnapis w C?
strstr(haystack, needle) zwraca wskaźnik na pierwsze wystąpienie albo NULL, jeśli go nie ma. Ponieważ zwraca wskaźnik do oryginalnego napisu, found - haystack daje indeks. Gdy szukasz pojedynczego znaku, użyj strchr.