Odpowiedzi:
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
to unsigned 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 ():
char *trim(char *str)
{
size_t len = 0;
char *frontp = str;
char *endp = NULL;
if( str == NULL ) { return NULL; }
if( str[0] == '\0' ) { return str; }
len = strlen(str);
endp = str + len;
/* Move the front and back pointers to address the first non-whitespace
* characters from each end.
*/
while( isspace((unsigned char) *frontp) ) { ++frontp; }
if( endp != frontp )
{
while( isspace((unsigned char) *(--endp)) && endp != frontp ) {}
}
if( frontp != str && endp == frontp )
*str = '\0';
else if( str + len - 1 != endp )
*(endp + 1) = '\0';
/* Shift the string so that it starts at str so that if it's dynamically
* allocated, we can still free it on the returned pointer. Note the reuse
* of endp to mean the front of the string buffer now.
*/
endp = str;
if( frontp != str )
{
while( *frontp ) { *endp++ = *frontp++; }
*endp = '\0';
}
return str;
}
Sprawdź poprawność:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
/* Paste function from above here. */
int main()
{
/* The test prints the following:
[nothing to trim] -> [nothing to trim]
[ trim the front] -> [trim the front]
[trim the back ] -> [trim the back]
[ trim front and back ] -> [trim front and back]
[ trim one char front and back ] -> [trim one char front and back]
[ trim one char front] -> [trim one char front]
[trim one char back ] -> [trim one char back]
[ ] -> []
[ ] -> []
[a] -> [a]
[] -> []
*/
char *sample_strings[] =
{
"nothing to trim",
" trim the front",
"trim the back ",
" trim front and back ",
" trim one char front and back ",
" trim one char front",
"trim one char back ",
" ",
" ",
"a",
"",
NULL
};
char test_buffer[64];
char comparison_buffer[64];
size_t index, compare_pos;
for( index = 0; sample_strings[index] != NULL; ++index )
{
// Fill buffer with known value to verify we do not write past the end of the string.
memset( test_buffer, 0xCC, sizeof(test_buffer) );
strcpy( test_buffer, sample_strings[index] );
memcpy( comparison_buffer, test_buffer, sizeof(comparison_buffer));
printf("[%s] -> [%s]\n", sample_strings[index],
trim(test_buffer));
for( compare_pos = strlen(comparison_buffer);
compare_pos < sizeof(comparison_buffer);
++compare_pos )
{
if( test_buffer[compare_pos] != comparison_buffer[compare_pos] )
{
printf("Unexpected change to buffer @ index %u: %02x (expected %02x)\n",
compare_pos, (unsigned char) test_buffer[compare_pos], (unsigned char) comparison_buffer[compare_pos]);
}
}
}
return 0;
}
Plik źródłowy to trim.c. Skompilowane za pomocą „cc -Wall trim.c -o trim”.
isspace
to unsigned 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ć ().
void trim(char * s) {
char * p = s;
int l = strlen(p);
while(isspace(p[l - 1])) p[--l] = 0;
while(* p && isspace(* p)) ++p, --l;
memmove(s, p, l + 1);
}
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 ().
char * trim(char * s) {
int l = strlen(s);
while(isspace(s[l - 1])) --l;
while(* s && isspace(* s)) ++s, --l;
return strndup(s, l);
}
trim()
wywołuje UB, jeśli s
jest ""
tak, jak isspace()
byłoby to pierwsze wywołanie isspace(p[-1])
i p[-1]
niekoniecznie odnosi się do legalnej lokalizacji.
isspace
to unsigned char
, w przeciwnym razie wywołasz niezdefiniowane zachowanie.
if(l==0)return;
aby uniknąć zerowej długości str
Oto 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).
#ifndef STRLIB_H_
#define STRLIB_H_ 1
enum strtrim_mode_t {
STRLIB_MODE_ALL = 0,
STRLIB_MODE_RIGHT = 0x01,
STRLIB_MODE_LEFT = 0x02,
STRLIB_MODE_BOTH = 0x03
};
char *strcpytrim(char *d, // destination
char *s, // source
int mode,
char *delim
);
char *strtriml(char *d, char *s);
char *strtrimr(char *d, char *s);
char *strtrim(char *d, char *s);
char *strkill(char *d, char *s);
char *triml(char *s);
char *trimr(char *s);
char *trim(char *s);
char *kill(char *s);
#endif
#include <strlib.h>
char *strcpytrim(char *d, // destination
char *s, // source
int mode,
char *delim
) {
char *o = d; // save orig
char *e = 0; // end space ptr.
char dtab[256] = {0};
if (!s || !d) return 0;
if (!delim) delim = " \t\n\f";
while (*delim)
dtab[*delim++] = 1;
while ( (*d = *s++) != 0 ) {
if (!dtab[0xFF & (unsigned int)*d]) { // Not a match char
e = 0; // Reset end pointer
} else {
if (!e) e = d; // Found first match.
if ( mode == STRLIB_MODE_ALL || ((mode != STRLIB_MODE_RIGHT) && (d == o)) )
continue;
}
d++;
}
if (mode != STRLIB_MODE_LEFT && e) { // for everything but trim_left, delete trailing matches.
*e = 0;
}
return o;
}
// perhaps these could be inlined in strlib.h
char *strtriml(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_LEFT, 0); }
char *strtrimr(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_RIGHT, 0); }
char *strtrim(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_BOTH, 0); }
char *strkill(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_ALL, 0); }
char *triml(char *s) { return strcpytrim(s, s, STRLIB_MODE_LEFT, 0); }
char *trimr(char *s) { return strcpytrim(s, s, STRLIB_MODE_RIGHT, 0); }
char *trim(char *s) { return strcpytrim(s, s, STRLIB_MODE_BOTH, 0); }
char *kill(char *s) { return strcpytrim(s, s, STRLIB_MODE_ALL, 0); }
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 .
dtab[*d]
nie rzutuje *d
na unsigned int
przed użyciem go jako indeksu tablicy. W systemie z podpisanym char, spowoduje to odczytanie, do dtab[-127]
którego spowoduje błędy i prawdopodobnie awarię.
dtab[*delim++]
ponieważ char
wartości indeksu muszą być rzutowane na unsigned char
. Kod zakłada 8-bitowe char
. delim
należy zadeklarować jako const char *
. dtab[0xFF & (unsigned int)*d]
będzie jaśniejsze jak dtab[(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.
void trim(char *str)
{
int i;
int begin = 0;
int end = strlen(str) - 1;
while (isspace((unsigned char) str[begin]))
begin++;
while ((end >= begin) && isspace((unsigned char) str[end]))
end--;
// Shift all characters back to the start of the string array.
for (i = begin; i <= end; i++)
str[i - begin] = str[i];
str[i - begin] = '\0'; // Null terminate string.
}
while ((end >= begin) && isspace(str[end]))
aby uniemożliwić UB, gdy str is
"" . Prevents
str [-1] ".
isspace
to unsigned 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 nich EOF
. 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 znakiem char
, rzutuj *s
na unsigned char
.
Obsługa znaków „We wszystkich przypadkach argumentem jest an
int
, którego wartość powinna być reprezentowana jakounsigned char
wartość makra lub powinna być równa wartości makraEOF
. Jeśli argument ma inną wartość, zachowanie jest nieokreślone”. C11 § 7.4 1
#include <ctype.h>
// Return a pointer to the trimmed string
char *string_trim_inplace(char *s) {
while (isspace((unsigned char) *s)) s++;
if (*s) {
char *p = s;
while (*p) p++;
while (isspace((unsigned char) *(--p)));
p[1] = '\0';
}
// If desired, shift the trimmed string
return s;
}
@chqrlie skomentował, że powyższe nie zmienia przyciętego ciągu. Aby to zrobić ...
// Return a pointer to the (shifted) trimmed string
char *string_trim_inplace(char *s) {
char *original = s;
size_t len = 0;
while (isspace((unsigned char) *s)) {
s++;
}
if (*s) {
char *p = s;
while (*p) p++;
while (isspace((unsigned char) *(--p)));
p[1] = '\0';
// len = (size_t) (p - s); // older errant code
len = (size_t) (p - s + 1); // Thanks to @theriver
}
return (s == original) ? s : memmove(original, s, len + 1);
}
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 .
#include <ctype.h>
void trim(char * const a)
{
char *p = a, *q = a;
while (isspace(*q)) ++q;
while (*q) *p++ = *q++;
*p = '\0';
while (p > a && isspace(*--p)) *p = '\0';
}
/* See http://fctx.wildbearsoftware.com/ */
#include "fct.h"
FCT_BGN()
{
FCT_QTEST_BGN(trim)
{
{ char s[] = ""; trim(s); fct_chk_eq_str("", s); } // Trivial
{ char s[] = " "; trim(s); fct_chk_eq_str("", s); } // Trivial
{ char s[] = "\t"; trim(s); fct_chk_eq_str("", s); } // Trivial
{ char s[] = "a"; trim(s); fct_chk_eq_str("a", s); } // NOP
{ char s[] = "abc"; trim(s); fct_chk_eq_str("abc", s); } // NOP
{ char s[] = " a"; trim(s); fct_chk_eq_str("a", s); } // Leading
{ char s[] = " a c"; trim(s); fct_chk_eq_str("a c", s); } // Leading
{ char s[] = "a "; trim(s); fct_chk_eq_str("a", s); } // Trailing
{ char s[] = "a c "; trim(s); fct_chk_eq_str("a c", s); } // Trailing
{ char s[] = " a "; trim(s); fct_chk_eq_str("a", s); } // Both
{ char s[] = " a c "; trim(s); fct_chk_eq_str("a c", s); } // Both
// Villemoes pointed out an edge case that corrupted memory. Thank you.
// http://stackoverflow.com/questions/122616/#comment23332594_4505533
{
char s[] = "a "; // Buffer with whitespace before s + 2
trim(s + 2); // Trim " " containing only whitespace
fct_chk_eq_str("", s + 2); // Ensure correct result from the trim
fct_chk_eq_str("a ", s); // Ensure preceding buffer not mutated
}
// doukremt suggested I investigate this test case but
// did not indicate the specific behavior that was objectionable.
// http://stackoverflow.com/posts/comments/33571430
{
char s[] = " foobar"; // Shifted across whitespace
trim(s); // Trim
fct_chk_eq_str("foobar", s); // Leading string is correct
// Here is what the algorithm produces:
char r[16] = { 'f', 'o', 'o', 'b', 'a', 'r', '\0', ' ',
' ', 'f', 'o', 'o', 'b', 'a', 'r', '\0'};
fct_chk_eq_int(0, memcmp(s, r, sizeof(s)));
}
}
FCT_QTEST_END();
}
FCT_END();
Kolejny, z jedną linią wykonującą prawdziwą robotę:
#include <stdio.h>
int main()
{
const char *target = " haha ";
char buf[256];
sscanf(target, "%s", buf); // Trimming on both sides occurs here
printf("<%s>\n", buf);
}
%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:
void fnStrTrimInPlace(char *szWrite) {
const char *szWriteOrig = szWrite;
char *szLastSpace = szWrite, *szRead = szWrite;
int bNotSpace;
// SHIFT STRING, STARTING AT FIRST NON-SPACE CHAR, LEFTMOST
while( *szRead != '\0' ) {
bNotSpace = !isspace((unsigned char)(*szRead));
if( (szWrite != szWriteOrig) || bNotSpace ) {
*szWrite = *szRead;
szWrite++;
// TRACK POINTER TO LAST NON-SPACE
if( bNotSpace )
szLastSpace = szWrite;
}
szRead++;
}
// TERMINATE AFTER LAST NON-SPACE (OR BEGINNING IF THERE WAS NO NON-SPACE)
*szLastSpace = '\0';
}
isspace
to unsigned 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 raz dwa 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.
#include <stddef.h>
#include <ctype.h>
char * trim2(char *d, const char *s)
{
// Sanity checks
if (s == NULL || d == NULL)
return NULL;
// Skip leading spaces
const unsigned char * p = (const unsigned char *)s;
while (isspace(*p))
p++;
// Copy the string
unsigned char * dst = (unsigned char *)d; // d and s can be the same
unsigned char * end = dst;
while (*p != '\0')
{
if (!isspace(*dst++ = *p++))
end = dst;
}
// Truncate trailing spaces
*end = '\0';
return d;
}
char * trim(char *s)
{
return trim2(s, s);
}
'\0'
a następnie testowana z isspace()
. Testowanie wszystkich postaci przy użyciu programu wydaje się marnotrawstwem isspace()
. 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 z d,s
nakładaniem się i s < 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 do memmove()
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:
while (isspace (* p)) p ++;
Możemy znaleźć ostatnią pozycję znaku niebędącego białymi znakami z dwoma podobnymi trywialnymi ruchami:
while (* q) q ++; zrobić {q--; } while (isspace (* q));
(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 jak chomp
funkcja PHperytonby . Jaka byłaby taka funkcja w C?
do { q--; } ...
się dowiesz *q != 0
.
Użyj biblioteki ciągów , na przykład:
Ustr *s1 = USTR1(\7, " 12345 ");
ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
... 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.
Żeby to rosło, jeszcze jedna opcja z modyfikowalnym ciągiem znaków:
void trimString(char *string)
{
size_t i = 0, j = strlen(string);
while (j > 0 && isspace((unsigned char)string[j - 1])) string[--j] = '\0';
while (isspace((unsigned char)string[i])) i++;
if (i > 0) memmove(string, string + i, j - i + 1);
}
strlen()
zwraca wartość, size_t
która może przekroczyć zakres int
. 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żyj memmove()
zamiast strcpy()
.
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, a end = ++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.
// Trims leading whitespace chars in left `str`, then copy at almost `n - 1` chars
// into the `out` buffer in which copying might stop when the first '\0' occurs,
// and finally append '\0' to the position of the last non-trailing whitespace char.
// Reture the length the trimed string which '\0' is not count in like strlen().
size_t trim(char *out, size_t n, const char *str)
{
// do nothing
if(n == 0) return 0;
// ptr stop at the first non-leading space char
while(isspace(*str)) str++;
if(*str == '\0') {
out[0] = '\0';
return 0;
}
size_t i = 0;
// copy char to out until '\0' or i == n - 1
for(i = 0; i < n - 1 && *str != '\0'; i++){
out[i] = *str++;
}
// deal with the trailing space
while(isspace(out[--i]));
out[++i] = '\0';
return i;
}
isspace(*str)
UB kiedy *str < 0
.
size_t n
jest dobre, ale interfejs w żaden sposób nie informuje wywołującego o n
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,
#include <stdio.h>
int main()
{
char *foo=" teststring ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
return 0;
}
" 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.
void stripWS_LT(char *str)
{
char *a = str, *b = str;
while (isspace((unsigned char)*a)) a++;
while (*b = *a++) b++;
while (b > str && isspace((unsigned char)*--b)) *b = 0;
}
b > str
Badanie jest potrzebne tylko raz. *b = 0;
potrzebne tylko raz.
#include <ctype.h>
#include <string.h>
char *trim_space(char *in)
{
char *out = NULL;
int len;
if (in) {
len = strlen(in);
while(len && isspace(in[len - 1])) --len;
while(len && *in && isspace(*in)) ++in, --len;
if (len) {
out = strndup(in, len);
}
}
return out;
}
isspace
pomaga przyciąć wszystkie białe przestrzenie.
strndup
aby utworzyć nowy bufor ciągu, wykluczając spacje.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("")
zwraca NULL
. Spodziewałbym się wskaźnika ""
. int len;
powinno być size_t len;
. isspace(in[len - 1])
UB kiedy in[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).
#include "stdafx.h"
#include "malloc.h"
#include "string.h"
int main(int argc, char* argv[])
{
char *ptr = (char*)malloc(sizeof(char)*30);
strcpy(ptr," Hel lo wo rl d G eo rocks!!! by shahil sucks b i g tim e");
int i = 0, j = 0;
while(ptr[j]!='\0')
{
if(ptr[j] == ' ' )
{
j++;
ptr[i] = ptr[j];
}
else
{
i++;
j++;
ptr[i] = ptr[j];
}
}
printf("\noutput-%s\n",ptr);
return 0;
}
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):
#include <ctype.h>
#include <string.h>
/*
Public domain implementations of in-place string trim functions
Michael Burr
michael.burr@nth-element.com
2010
*/
char* ltrim(char* s)
{
char* newstart = s;
while (isspace( *newstart)) {
++newstart;
}
// newstart points to first non-whitespace char (which might be '\0')
memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '\0' terminator
return s;
}
char* rtrim( char* s)
{
char* end = s + strlen( s);
// find the last non-whitespace character
while ((end != s) && isspace( *(end-1))) {
--end;
}
// at this point either (end == s) and s is either empty or all whitespace
// so it needs to be made empty, or
// end points just past the last non-whitespace character (it might point
// at the '\0' terminator, in which case there's no problem writing
// another there).
*end = '\0';
return s;
}
char* trim( char* s)
{
return rtrim( ltrim( s));
}
char
argument na isspace()
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).
static char const WHITESPACE[] = " \t\n\r";
static void get_trim_bounds(char const *s,
char const **firstWord,
char const **trailingSpace)
{
char const *lastWord;
*firstWord = lastWord = s + strspn(s, WHITESPACE);
do
{
*trailingSpace = lastWord + strcspn(lastWord, WHITESPACE);
lastWord = *trailingSpace + strspn(*trailingSpace, WHITESPACE);
}
while (*lastWord != '\0');
}
char *copy_trim(char const *s)
{
char const *firstWord, *trailingSpace;
char *result;
size_t newLength;
get_trim_bounds(s, &firstWord, &trailingSpace);
newLength = trailingSpace - firstWord;
result = malloc(newLength + 1);
memcpy(result, firstWord, newLength);
result[newLength] = '\0';
return result;
}
void inplace_trim(char *s)
{
char const *firstWord, *trailingSpace;
size_t newLength;
get_trim_bounds(s, &firstWord, &trailingSpace);
newLength = trailingSpace - firstWord;
memmove(s, firstWord, newLength);
s[newLength] = '\0';
}
strspn()
i strcspn()
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ć:
static const char *WhiteSpace=" \n\r\t";
char* trim(char *t)
{
char *e=t+(t!=NULL?strlen(t):0); // *e initially points to end of string
if (t==NULL) return;
do --e; while (strchr(WhiteSpace, *e) && e>=t); // Find last char that is not \r\n\t
*(++e)=0; // Null-terminate
e=t+strspn (t,WhiteSpace); // Find first char that is not \t
return e>t?memmove(t,e,strlen(e)+1):t; // memmove string contents and terminator
}
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.
#include <string.h>
void rstrip(char *string)
{
int l;
if (!string)
return;
l = strlen(string) - 1;
while (isspace(string[l]) && l >= 0)
string[l--] = 0;
}
void lstrip(char *string)
{
int i, l;
if (!string)
return;
l = strlen(string);
while (isspace(string[(i = 0)]))
while(i++ < l)
string[i-1] = string[i];
}
void strip(char *string)
{
lstrip(string);
rstrip(string);
}
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 "
.
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!
char *trimAll(char *strData)
{
unsigned int L = strlen(strData);
if(L > 0){ L--; }else{ return strData; }
size_t S = 0, E = L;
while((!(strData[S] > ' ') || !(strData[E] > ' ')) && (S >= 0) && (S <= L) && (E >= 0) && (E <= L))
{
if(strData[S] <= ' '){ S++; }
if(strData[E] <= ' '){ E--; }
}
if(S == 0 && E == L){ return strData; } // Nothing to be done
if((S >= 0) && (S <= L) && (E >= 0) && (E <= L)){
L = E - S + 1;
memmove(strData,&strData[S],L); strData[L] = '\0';
}else{ strData[0] = '\0'; }
return strData;
}
size_t
zamiast unsigned 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żyj memmove()
zamiast strncpy()
.
Uwzględniam tylko kod, ponieważ kod opublikowany do tej pory wydaje się nieoptymalny (i nie mam jeszcze przedstawiciela do komentowania).
void inplace_trim(char* s)
{
int start, end = strlen(s);
for (start = 0; isspace(s[start]); ++start) {}
if (s[start]) {
while (end > 0 && isspace(s[end-1]))
--end;
memmove(s, &s[start], end - start);
}
s[end - start] = '\0';
}
char* copy_trim(const char* s)
{
int start, end;
for (start = 0; isspace(s[start]); ++start) {}
for (end = strlen(s); end > 0 && isspace(s[end-1]); --end) {}
return strndup(s + start, end - start);
}
strndup()
jest rozszerzeniem GNU. Jeśli nie masz tego lub czegoś równoważnego, wyrzuć własne. Na przykład:
r = strdup(s + start);
r[end-start] = '\0';
isspace(0)
jest zdefiniowana jako fałsz, możesz uprościć obie funkcje. Przesuń również memmove()
wnętrze if
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.
#include<stdio.h>
#include<stdlib.h>
char *trimStr(char *str){
char *tmp = str;
printf("input string %s\n",str);
int nc = 0;
while(*tmp!='\0'){
if (*tmp != ' '){
nc++;
}
tmp++;
}
printf("total nonempty characters are %d\n",nc);
char *trim = NULL;
trim = malloc(sizeof(char)*(nc+1));
if (trim == NULL) return NULL;
tmp = str;
int ne = 0;
while(*tmp!='\0'){
if (*tmp != ' '){
trim[ne] = *tmp;
ne++;
}
tmp++;
}
trim[nc] = '\0';
printf("trimmed string is %s\n",trim);
return trim;
}
int main(void){
char str[] = " s ta ck ove r fl o w ";
char *trim = trimStr(str);
if (trim != NULL )free(trim);
return 0;
}
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.
#include <ctype.h>
#include <string.h>
void trim_str(char *s)
{
const size_t s_len = strlen(s);
int i;
for (i = 0; i < s_len; i++)
{
if (!isspace( (unsigned char) s[i] )) break;
}
if (i == s_len)
{
// s is an empty string or contains only space characters
s[0] = '\0';
}
else
{
// s contains non-space characters
const char *non_space_beginning = s + i;
char *non_space_ending = s + s_len - 1;
while ( isspace( (unsigned char) *non_space_ending ) ) non_space_ending--;
size_t trimmed_s_len = non_space_ending - non_space_beginning + 1;
if (s != non_space_beginning)
{
// Non-space characters exist in the beginning of s
memmove(s, non_space_beginning, trimmed_s_len);
}
s[trimmed_s_len] = '\0';
}
}
char* strtrim(char* const str)
{
if (str != nullptr)
{
char const* begin{ str };
while (std::isspace(*begin))
{
++begin;
}
auto end{ begin };
auto scout{ begin };
while (*scout != '\0')
{
if (!std::isspace(*scout++))
{
end = scout;
}
}
auto /* std::ptrdiff_t */ const length{ end - begin };
if (begin != str)
{
std::memmove(str, begin, length);
}
str[length] = '\0';
}
return str;
}