Jeśli możesz zmodyfikować ciąg:
// Note: This function returns a pointer to a substring of the original string.
// If the given string was allocated dynamically, the caller must not overwrite
// that pointer with the returned value, since the original pointer must be
// deallocated using the same allocator with which it was allocated. The return
// value must NOT be deallocated using free() etc.
char *trimwhitespace(char *str)
{
char *end;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
return str;
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// Write new null terminator character
end[1] = '\0';
return str;
}
Jeśli nie możesz zmodyfikować ciągu, możesz użyć zasadniczo tej samej metody:
// Stores the trimmed input string into the given output buffer, which must be
// large enough to store the result. If it is too small, the output is
// truncated.
size_t trimwhitespace(char *out, size_t len, const char *str)
{
if(len == 0)
return 0;
const char *end;
size_t out_size;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
{
*out = 0;
return 1;
}
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
end++;
// Set output size to minimum of trimmed string length and buffer size minus 1
out_size = (end - str) < len-1 ? (end - str) : len-1;
// Copy trimmed string and add null terminator
memcpy(out, str, out_size);
out[out_size] = 0;
return out_size;
}
str
jest zmienną lokalną, a zmiana jej nie zmienia przekazywanego pierwotnego wskaźnika. Wywołania funkcji w języku C są zawsze przekazywane przez wartość, nigdy nie są przekazywane przez odniesienie.free()
funkcji. Wręcz przeciwnie - zaprojektowałem to, aby uniknąć konieczności przydzielania pamięci dla wydajności. Jeśli przekazany adres został przydzielony dynamicznie, to wywołujący nadal jest odpowiedzialny za zwolnienie tej pamięci, a wywołujący musi upewnić się, że nie nadpisze tej wartości wartością zwróconą tutaj.isspace
tounsigned char
, w przeciwnym razie wywołasz niezdefiniowane zachowanie.Oto taki, który przesuwa ciąg na pierwszą pozycję twojego bufora. Możesz chcieć tego zachowania, aby po dynamicznym przydzieleniu ciągu nadal można było zwolnić go na tym samym wskaźniku, który zwraca funkcja trim ():
Sprawdź poprawność:
Plik źródłowy to trim.c. Skompilowane za pomocą „cc -Wall trim.c -o trim”.
źródło
isspace
tounsigned char
, w przeciwnym razie wywołasz niezdefiniowane zachowanie.isspace()
więc dlaczego miałaby istnieć różnica między" "
i"\n"
? Dodałem testy jednostkowe dla nowych linii i wydaje mi się to w porządku ... ideone.com/bbVmqo*(endp + 1) = '\0';
. Przykładowy test odpowiedzi używa bufora 64, co pozwala uniknąć tego problemu.Moje rozwiązanie. Ciąg musi być zmienny. Zaletą nad niektórymi innymi rozwiązaniami jest to, że przesuwa część nieprzestrzenną na początek, dzięki czemu można nadal używać starego wskaźnika, na wypadek gdybyś musiał go później zwolnić ().
Ta wersja tworzy kopię ciągu za pomocą strndup () zamiast edytować go w miejscu. strndup () wymaga _GNU_SOURCE, więc być może będziesz musiał stworzyć własną strndup () za pomocą malloc () i strncpy ().
źródło
trim()
wywołuje UB, jeślis
jest""
tak, jakisspace()
byłoby to pierwsze wywołanieisspace(p[-1])
ip[-1]
niekoniecznie odnosi się do legalnej lokalizacji.isspace
tounsigned char
, w przeciwnym razie wywołasz niezdefiniowane zachowanie.if(l==0)return;
aby uniknąć zerowej długości strOto moja mini biblioteka C do przycinania lewej, prawej, wszystkich, na miejscu i osobno oraz przycinania zestawu określonych znaków (lub domyślnie białych znaków).
zawartość strlib.h:
zawartość strlib.c:
Jedna główna rutyna to wszystko. Obcina w miejscu, jeśli src == dst , w przeciwnym razie działa jak
strcpy
procedury. To przycina zestawu znaków określonego w ciągu delimlub spacje, jeśli null. Przycina w lewo, w prawo, oba i wszystkie (jak tr). Nie ma w tym wiele, a iteruje po ciągu tylko raz. Niektórzy ludzie mogą narzekać, że trymowanie po prawej zaczyna się po lewej stronie, jednak nie jest potrzebny strlen, który i tak zaczyna się po lewej stronie. (Tak czy inaczej, aby uzyskać prawidłowe przycinanie, musisz dotrzeć do końca łańcucha, więc równie dobrze możesz wykonać pracę na bieżąco). Mogą istnieć argumenty dotyczące przetwarzania potoków i rozmiarów pamięci podręcznej i tym podobne - kto wie . Ponieważ rozwiązanie działa od lewej do prawej i wykonuje iterację tylko raz, można je rozszerzyć, aby działało również na strumieniach. Ograniczenia: nie działa na ciągach znaków Unicode .źródło
dtab[*d]
nie rzutuje*d
naunsigned int
przed użyciem go jako indeksu tablicy. W systemie z podpisanym char, spowoduje to odczytanie, dodtab[-127]
którego spowoduje błędy i prawdopodobnie awarię.dtab[*delim++]
ponieważchar
wartości indeksu muszą być rzutowane naunsigned char
. Kod zakłada 8-bitowechar
.delim
należy zadeklarować jakoconst char *
.dtab[0xFF & (unsigned int)*d]
będzie jaśniejsze jakdtab[(unsigned char)*d]
. Kod działa na łańcuchach zakodowanych w UTF-8, ale nie usuwa sekwencji odstępów innych niż ASCII.Oto moja próba prostej, ale poprawnej funkcji przycinania na miejscu.
źródło
while ((end >= begin) && isspace(str[end]))
aby uniemożliwić UB, gdystr is
"". Prevents
str [-1] ".isspace
tounsigned char
, w przeciwnym razie wywołasz niezdefiniowane zachowanie.<ctype.h>
są przeznaczone do pracy z wartościami ints, które reprezentująunsigned char
wartość specjalną lub jedną z nichEOF
. Zobacz stackoverflow.com/q/7131026/225757 .Spóźniony na przyjęcie wykończeniowe
Cechy:
1. Szybko przytnij początek, tak jak w wielu innych odpowiedziach.
2. Po przejściu do końca, przycinanie prawej strony z tylko 1 testem na pętlę. Podobnie jak @ jfm3, ale działa dla całego ciągu znaków spacji)
3. Aby uniknąć niezdefiniowanego zachowania, gdy
char
jest znakiemchar
, rzutuj*s
naunsigned char
.@chqrlie skomentował, że powyższe nie zmienia przyciętego ciągu. Aby to zrobić ...
źródło
Oto rozwiązanie podobne do procedury modyfikacji w miejscu @ adam-rosenfields, ale bez niepotrzebnego uciekania się do strlen (). Podobnie jak @jkramer, ciąg jest korygowany w lewo w buforze, dzięki czemu można zwolnić ten sam wskaźnik. Nie jest optymalny dla dużych strun, ponieważ nie używa memmove. Obejmuje operatory ++ / - wymienione w @ jfm3. Zawiera testy jednostkowe oparte na FCTX .
źródło
Kolejny, z jedną linią wykonującą prawdziwą robotę:
źródło
%n
konwersji, a obawiam się, że na końcu łatwiej jest to zrobić ręcznie.Nie podobały mi się te odpowiedzi, ponieważ wykonały co najmniej jedną z następujących czynności ...
Oto moja wersja:
źródło
isspace
tounsigned char
, w przeciwnym razie wywołasz niezdefiniowane zachowanie.while (isspace((unsigned char) *szWrite)) szWrite++;
by temu zapobiegło. Kod kopiuje również wszystkie końcowe białe znaki.*szWrite = *szRead
wtedy, gdy wskaźniki nie są równe, pomija zapisy w tym przypadku, ale dodaliśmy kolejne porównanie / gałąź. Przy nowoczesnym CPU / MMU / BP nie mam pojęcia, czy ta kontrola byłaby stratą, czy zyskiem. Przy prostszych procesorach i architekturach pamięci tańsze jest po prostu skopiowanie i pominięcie porównania.Bardzo późno na imprezę ...
Rozwiązanie do jednoprzebiegowego skanowania do przodu bez cofania. Każdy znak w łańcuchu źródłowym jest testowany dokładnie
razdwa razy. (Powinien więc być szybszy niż większość innych rozwiązań tutaj, zwłaszcza jeśli ciąg źródłowy ma dużo spacji na końcu).Obejmuje to dwa rozwiązania, jedno do kopiowania i przycinania ciągu źródłowego do innego ciągu docelowego, a drugie do przycinania ciągu źródłowego w miejscu. Obie funkcje używają tego samego kodu.
Ciąg (modyfikowalny) jest przenoszony w miejscu, więc oryginalny wskaźnik do niego pozostaje niezmieniony.
źródło
'\0'
a następnie testowana zisspace()
. Testowanie wszystkich postaci przy użyciu programu wydaje się marnotrawstwemisspace()
. Wycofywanie się od końca struny powinno być skuteczniejsze w przypadkach niepatologicznych.trim()
DOBRZE. Obudowa narożna:trim2(char *d, const char *s)
ma problemy zd,s
nakładaniem się is < d
.trim()
zachować w tym narożnym przypadku ? Prosisz o przycięcie i skopiowanie ciągu do pamięci zajmowanej przez sam ciąg. W przeciwieństwie domemmove()
tego wymaga to określenia długości łańcucha źródłowego przed wykonaniem samego przycinania, co wymaga dodatkowego przeskanowania całego ciągu. Lepiej napisać innąrtrim2()
funkcję, która wie, że kopiuje źródło do miejsca docelowego wstecz i prawdopodobnie pobiera dodatkowy argument długości ciągu źródłowego.Nie jestem pewien, co uważasz za „bezbolesne”.
Struny C są dość bolesne. Możemy znaleźć pierwszą pozycję znaku niebędącego białymi znakami w trywialny sposób:
Możemy znaleźć ostatnią pozycję znaku niebędącego białymi znakami z dwoma podobnymi trywialnymi ruchami:
(Oszczędziłem ci bólu używania operatorów
*
i++
w tym samym czasie.)Pytanie brzmi, co z tym zrobisz? Typ danych, o którym mowa, nie jest tak naprawdę dużą, solidną abstrakcją, o
String
której łatwo jest pomyśleć, ale zamiast tego tak naprawdę niewiele więcej niż tablica bajtów pamięci. Brak solidnego typu danych uniemożliwia napisanie funkcji, która będzie działać tak samo jakchomp
funkcja PHperytonby . Jaka byłaby taka funkcja w C?źródło
do { q--; } ...
się dowiesz*q != 0
.Użyj biblioteki ciągów , na przykład:
... jak mówisz, że jest to "powszechny" problem, tak, musisz dołączyć #include lub coś takiego i nie jest to zawarte w libc, ale nie wymyślaj własnego zadania hakerskiego, przechowującego losowe wskaźniki i size_t w ten sposób prowadzi tylko do przepełnienia bufora.
źródło
Jeśli używasz
glib
, możesz użyć g_stripźródło
Żeby to rosło, jeszcze jedna opcja z modyfikowalnym ciągiem znaków:
źródło
strlen()
zwraca wartość,size_t
która może przekroczyć zakresint
. spacja nie jest ograniczona do znaku spacji. Wreszcie, ale najważniejsze: niezdefiniowane zachowanie włączone,strcpy(string, string + i * sizeof(char));
ponieważ tablice źródłowe i docelowe nakładają się. Użyjmemmove()
zamiaststrcpy()
.while (isspace((int)string[i])) string[i--] = '\0';
może zapętlić się poza początkiem ciągu. Powinieneś połączyć tę pętlę z poprzednimi i następnymi liniami i napisaćwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
end
nie wskazywała na końcowy bajt zerowy, aend = ++i;
nadal miałeś problem z łańcuchami zawierającymi wszystkie białe znaki. Właśnie naprawiłem kod.Wiem, że jest wiele odpowiedzi, ale zamieszczam odpowiedź tutaj, aby sprawdzić, czy moje rozwiązanie jest wystarczająco dobre.
źródło
isspace(*str)
UB kiedy*str < 0
.size_t n
jest dobre, ale interfejs w żaden sposób nie informuje wywołującego on
tym, że jest zbyt mały dla pełnego przyciętego ciągu. Rozważtrim(out, 12, "delete data not")
Najłatwiejszym sposobem na pominięcie początkowych spacji w ciągu jest imho,
źródło
" foo bar "
.Ok, to moje podejście do pytania. Uważam, że jest to najbardziej zwięzłe rozwiązanie, które modyfikuje ciąg w miejscu (
free
zadziała) i pozwala uniknąć jakiegokolwiek UB. W przypadku małych strun jest to prawdopodobnie szybsze niż rozwiązanie obejmujące memmove.źródło
b > str
Badanie jest potrzebne tylko raz.*b = 0;
potrzebne tylko raz.isspace
pomaga przyciąć wszystkie białe przestrzenie.strndup
aby utworzyć nowy bufor ciągu, wykluczając spacje.źródło
strndup()
nie jest częścią standardu C, ale tylko Posix. Ale ponieważ jest to dość łatwe do wdrożenia, nie jest to wielka sprawa.trim_space("")
zwracaNULL
. Spodziewałbym się wskaźnika""
.int len;
powinno byćsize_t len;
.isspace(in[len - 1])
UB kiedyin[len - 1] < 0
.while (isspace((unsigned char) *in) in++;
wcześniejlen = strlen(in);
byłaby bardziej wydajna niż późniejszawhile(len && *in && isspace(*in)) ++in, --len;
Osobiście zrobiłbym własny. Możesz użyć strtok, ale musisz uważać, robiąc to (szczególnie jeśli usuwasz wiodące znaki), aby wiedzieć, czym jest pamięć.
Pozbycie się końcowych spacji jest łatwe i całkiem bezpieczne, ponieważ możesz po prostu wstawić 0 ponad ostatnią spacją, licząc od końca. Pozbycie się wiodących spacji oznacza przenoszenie rzeczy. Jeśli chcesz to zrobić w miejscu (prawdopodobnie rozsądne), możesz po prostu przesuwać wszystko do tyłu o jeden znak, aż nie będzie spacji wiodącej. Lub, aby być bardziej wydajnym, możesz znaleźć indeks pierwszego znaku innego niż spacja i cofnąć wszystko o tę liczbę. Lub możesz po prostu użyć wskaźnika do pierwszego znaku innego niż spacja (ale wtedy musisz być ostrożny w taki sam sposób, jak w przypadku strtok).
źródło
źródło
Trochę za późno do gry, ale wrzucę swoje procedury do walki. Prawdopodobnie nie są najbardziej wydajne, ale uważam, że są poprawne i proste (z
rtrim()
przesuwaniem obwiedni złożoności):źródło
char
argument naisspace()
to,(unsigned char)
aby uniknąć niezdefiniowanego zachowania na potencjalnie ujemnych wartościach. Unikaj również przesuwania struny,ltrim()
jeśli nie jest to konieczne.Większość dotychczasowych odpowiedzi dotyczy jednej z następujących czynności:
strlen()
najpierw, wykonując drugie przejście przez cały ciąg.Ta wersja wykonuje tylko jedno przejście i nie cofa się. Dlatego może działać lepiej niż inne, chociaż tylko wtedy, gdy często występują setki spacji końcowych (co nie jest niczym niezwykłym, gdy mamy do czynienia z wynikiem zapytania SQL).
źródło
strspn()
istrcspn()
w ciasnej pętli. Jest to bardzo nieefektywne, a narzut przyćmiewa niesprawdzoną przewagę pojedynczego podania w przód.strlen()
jest zwykle rozszerzany w tekście za pomocą bardzo wydajnego kodu, nie jest to prawdziwy problem. Przycinanie początku i końca łańcucha będzie znacznie szybsze niż testowanie białości każdego znaku w ciągu, nawet w specjalnym przypadku ciągów z bardzo małą liczbą znaków innych niż białe lub bez nich.To najkrótsza możliwa implementacja, o jakiej mogę pomyśleć:
źródło
char *trim(char *s) { char *p = s, *e = s + strlen(s); while (e > s && isspace((unsigned char)e[-1])) { *--e = '\0'; } while (isspace((unsigned char)*p)) { p++; } if (p > s) { memmove(s, p, e + 1 - p); } return s; }
Te funkcje zmodyfikują oryginalny bufor, więc jeśli zostanie przydzielony dynamicznie, oryginalny wskaźnik może zostać zwolniony.
źródło
rstrip()
wywołuje niezdefiniowane zachowanie na pustym łańcuchu.lstrip()
jest niepotrzebnie powolny na łańcuchu z długą początkową częścią białych znaków.isspace()
nie należy przekazywaćchar
argumentu, ponieważ wywołuje niezdefiniowane zachowanie na wartościach ujemnych innych niżEOF
.Co myślisz o korzystaniu z funkcji StrTrim zdefiniowanej w nagłówku Shlwapi.h.? Jest to proste, a raczej samodzielne definiowanie.
Szczegóły można znaleźć pod adresem :
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx
Jeśli masz
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
to dasz
ausCaptain
jak"GeorgeBailey"
nie"GeorgeBailey "
.źródło
Aby przyciąć struny z obu stron, używam starego, ale gooody;) Może przyciąć wszystko z ascii mniej niż spacją, co oznacza, że znaki kontrolne również zostaną przycięte!
źródło
size_t
zamiastunsigned int
. Kod zawiera wiele redundantnych testów i wywołuje niezdefiniowane zachowanie,strncpy(strData,&strData[S],L)
ponieważ tablice źródłowa i docelowa nakładają się. Użyjmemmove()
zamiaststrncpy()
.Uwzględniam tylko kod, ponieważ kod opublikowany do tej pory wydaje się nieoptymalny (i nie mam jeszcze przedstawiciela do komentowania).
strndup()
jest rozszerzeniem GNU. Jeśli nie masz tego lub czegoś równoważnego, wyrzuć własne. Na przykład:źródło
isspace(0)
jest zdefiniowana jako fałsz, możesz uprościć obie funkcje. Przesuń równieżmemmove()
wnętrzeif
bloku.Tutaj używam dynamicznej alokacji pamięci, aby przyciąć ciąg wejściowy do funkcji trimStr. Najpierw sprawdzamy, ile niepustych znaków znajduje się w ciągu wejściowym. Następnie przydzielamy tablicę znaków o tym rozmiarze i dbamy o znak zakończony znakiem null. Kiedy używamy tej funkcji, musimy zwolnić pamięć wewnątrz funkcji głównej.
źródło
Oto jak to robię. Obcina ciąg w miejscu, więc nie martw się o cofnięcie przydziału zwracanego ciągu lub utratę wskaźnika do przydzielonego ciągu. Może nie jest to najkrótsza możliwa odpowiedź, ale powinna być zrozumiała dla większości czytelników.
źródło
źródło