Antworten:
Wenn Sie die Zeichenfolge ändern können:
// 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;
}
Wenn Sie die Zeichenfolge nicht ändern können, können Sie grundsätzlich dieselbe Methode verwenden:
// 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
ist eine lokale Variable, und durch Ändern wird der ursprüngliche Zeiger, der übergeben wird, nicht geändert. Funktionsaufrufe in C werden immer als Wert übergeben, niemals als Referenz.
free()
Funktion ist. Im Gegenteil - ich habe dies so konzipiert, dass aus Effizienzgründen keine Speicherzuweisung erforderlich ist. Wenn die übergebene Adresse dynamisch zugewiesen wurde, ist der Anrufer weiterhin für die Freigabe dieses Speichers verantwortlich, und der Anrufer muss sicherstellen, dass dieser Wert nicht mit dem hier zurückgegebenen Wert überschrieben wird.
isspace
to unsigned char
umwandeln, sonst rufen Sie undefiniertes Verhalten auf.
Hier ist eine, die die Zeichenfolge an die erste Position Ihres Puffers verschiebt. Möglicherweise möchten Sie dieses Verhalten, damit Sie die Zeichenfolge, wenn Sie sie dynamisch zugewiesen haben, weiterhin auf demselben Zeiger freigeben können, den trim () zurückgibt:
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;
}
Auf Richtigkeit prüfen:
#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;
}
Die Quelldatei war trim.c. Kompiliert mit 'cc -Wall trim.c -o trim'.
isspace
to unsigned char
umwandeln, sonst rufen Sie undefiniertes Verhalten auf.
isspace()
also auf, warum sollte es einen Unterschied zwischen " "
und geben "\n"
? Ich habe Unit-Tests für Zeilenumbrüche hinzugefügt und es sieht für mich in Ordnung aus
*(endp + 1) = '\0';
. Der Beispieltest für die Antwort verwendet einen Puffer von 64, wodurch dieses Problem vermieden wird.
Meine Lösung. String muss änderbar sein. Der Vorteil gegenüber einigen anderen Lösungen besteht darin, dass der Nicht-Leerzeichen-Teil an den Anfang verschoben wird, sodass Sie den alten Zeiger weiterhin verwenden können, falls Sie ihn später freigeben müssen ().
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);
}
Diese Version erstellt eine Kopie der Zeichenfolge mit strndup (), anstatt sie an Ort und Stelle zu bearbeiten. strndup () erfordert _GNU_SOURCE, daher müssen Sie möglicherweise Ihr eigenes strndup () mit malloc () und strncpy () erstellen.
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()
Invokes UB wenn s
ist ""
als der erste isspace()
Anruf würde isspace(p[-1])
und p[-1]
verweist nicht unbedingt eine rechtliche Lage.
isspace
to unsigned char
umwandeln, sonst rufen Sie undefiniertes Verhalten auf.
if(l==0)return;
, um
Hier ist meine C-Minibibliothek zum Trimmen von links, rechts, beiden, an Ort und Stelle und getrennt sowie zum Trimmen einer Reihe angegebener Zeichen (oder standardmäßig Leerzeichen).
#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); }
Die eine Hauptroutine macht alles. Es wird an Ort und Stelle abgeschnitten , wenn src == dst , andernfalls funktioniert es wie die strcpy
Routinen. Es schneidet eine Reihe von Zeichen ab, die in der Zeichenfolgenbegrenzung angegeben sindoder Leerzeichen, wenn null. Es schneidet links, rechts, beide und alle (wie tr). Es steckt nicht viel dahinter und es wird nur einmal über die Zeichenfolge iteriert. Einige Leute könnten sich beschweren, dass das Trimmen rechts links beginnt, es ist jedoch keine Strlen erforderlich, die sowieso links beginnt. (Auf die eine oder andere Weise muss man das Ende der Zeichenfolge erreichen, um die richtigen Zuschnitte zu erzielen, damit Sie die Arbeit genauso gut erledigen können, wie Sie möchten.) Es kann Argumente für Pipelining- und Cache-Größen und dergleichen geben - wer weiß . Da die Lösung von links nach rechts funktioniert und nur einmal iteriert, kann sie auch für Streams erweitert werden. Einschränkungen: Es funktioniert nicht mit Unicode- Zeichenfolgen.
dtab[*d]
wird nicht umgewandelt *d
, unsigned int
bevor es als Array-Index verwendet wird. Auf einem System mit signiertem Zeichen wird dies gelesen, dtab[-127]
was zu Fehlern und möglicherweise zum Absturz führen wird.
dtab[*delim++]
da char
Indexwerte umgewandelt werden müssen unsigned char
. Der Code geht von 8 Bit aus char
. delim
sollte als deklariert werden const char *
. dtab[0xFF & (unsigned int)*d]
würde klarer als dtab[(unsigned char)*d]
. Der Code funktioniert mit UTF-8-codierten Zeichenfolgen, entfernt jedoch keine Nicht-ASCII-Abstandssequenzen.
Hier ist mein Versuch einer einfachen, aber korrekten Trimmfunktion an Ort und Stelle.
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]))
um UB zu verhindern, wenn str is
"" . Prevents
str [-1] `.
isspace
to unsigned char
umwandeln, sonst rufen Sie undefiniertes Verhalten auf.
<ctype.h>
sollen mit Ints arbeiten, die entweder unsigned char
oder den speziellen Wert darstellen EOF
. Siehe stackoverflow.com/q/7131026/225757 .
Spät zur Trimmparty
Funktionen:
1. Schneiden Sie den Anfang schnell ab, wie bei einer Reihe anderer Antworten.
2. Trimmen Sie nach dem Ende mit nur 1 Test pro Schleife nach rechts. Wie @ jfm3, funktioniert jedoch für eine reine Leerzeichenfolge.
3. Um undefiniertes Verhalten zu vermeiden, wenn char
es sich um eine Signatur handelt char
, setzen Sie *s
auf unsigned char
.
Zeichenbehandlung "In allen Fällen ist das Argument ein
int
, dessen Wert alsunsigned char
oder darstellbar sein soll oder dem Wert des Makros entsprichtEOF
. Wenn das Argument einen anderen Wert hat, ist das Verhalten undefiniert." 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 kommentierte, dass das oben Gesagte die zugeschnittene Zeichenfolge nicht verschiebt. Um das zu tun ....
// 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);
}
Hier ist eine Lösung ähnlich der direkten Änderungsroutine von @ adam-rosenfields, ohne jedoch unnötig auf strlen () zurückzugreifen. Wie bei @jkramer wird die Zeichenfolge im Puffer nach links angepasst, sodass Sie denselben Zeiger freigeben können. Nicht optimal für große Zeichenfolgen, da memmove nicht verwendet wird. Enthält die ++ / - Operatoren, die @ jfm3 erwähnt. FCTX- basierte Unit-Tests enthalten.
#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();
Eine andere, bei der eine Zeile den eigentlichen Job macht:
#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
Konvertierungsspezifizierer, und am Ende ist es leider einfacher, dies von Hand zu tun.
Die meisten dieser Antworten haben mir nicht gefallen, weil sie eine oder mehrere der folgenden Antworten gegeben haben ...
Hier ist meine Version:
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
umwandeln, sonst rufen Sie undefiniertes Verhalten auf.
while (isspace((unsigned char) *szWrite)) szWrite++;
würde das verhindern. Der Code kopiert auch den gesamten nachgestellten Leerraum.
*szWrite = *szRead
wenn die Zeiger nicht gleich sind, die Schreibvorgänge in diesem Fall überspringen, aber dann haben wir einen weiteren Vergleich / Zweig hinzugefügt. Mit moderner CPU / MMU / BP habe ich keine Ahnung, ob diese Prüfung ein Verlust oder ein Gewinn wäre. Mit einfacheren Prozessoren und Speicherarchitekturen ist es billiger, nur die Kopie zu erstellen und den Vergleich zu überspringen.
Sehr spät zur Party ...
Single-Pass-Forward-Scan-Lösung ohne Backtracking. Jedes Zeichen in der Quellzeichenfolge wird genau einmal zweimal getestet . (Es sollte also schneller sein als die meisten anderen Lösungen hier, insbesondere wenn die Quellzeichenfolge viele nachgestellte Leerzeichen enthält.)
Dies umfasst zwei Lösungen, eine zum Kopieren und Trimmen einer Quellzeichenfolge in eine andere Zielzeichenfolge und die andere zum Trimmen der Quellzeichenfolge an Ort und Stelle. Beide Funktionen verwenden denselben Code.
Die (veränderbare) Zeichenfolge wird an Ort und Stelle verschoben, sodass der ursprüngliche Zeiger darauf unverändert bleibt.
#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'
und dann mit isspace()
. Es scheint verschwenderisch, alle Charaktere mit zu testen isspace()
. Das Zurückverfolgen vom Ende der Zeichenfolge sollte für nicht pathologische Fälle effizienter sein.
trim()
OK. Eckfall: trim2(char *d, const char *s)
hat Probleme bei d,s
Überlappung und s < d
.
trim()
sich in diesem Eckfall verhalten? Sie möchten eine Zeichenfolge zuschneiden und in den Speicher kopieren, der von der Zeichenfolge selbst belegt wird. Im Gegensatz dazu memmove()
muss hierfür die Länge der Quellzeichenfolge bestimmt werden, bevor der Schnitt selbst ausgeführt wird. Dazu muss die gesamte Zeichenfolge ein zusätzliches Mal gescannt werden. Es ist besser, eine andere rtrim2()
Funktion zu schreiben , die die Quelle rückwärts in das Ziel kopiert und wahrscheinlich ein zusätzliches Argument für die Länge der Quellzeichenfolge verwendet.
Ich bin mir nicht sicher, was Sie für "schmerzlos" halten.
C-Saiten sind ziemlich schmerzhaft. Wir können die erste Nicht-Leerzeichen-Zeichenposition trivial finden:
while (isspace (* p)) p ++;
Wir können die letzte Nicht-Leerzeichen-Charakterposition mit zwei ähnlichen trivialen Zügen finden:
während (* q) q ++; do {q--; } while (isspace (* q));
(Ich habe Ihnen den Schmerz erspart, die Operatoren *
und ++
gleichzeitig zu verwenden.)
Die Frage ist nun, was machst du damit? Der vorliegende Datentyp ist nicht wirklich eine große, robuste Zusammenfassung String
, über die man leicht nachdenken kann, sondern kaum mehr als ein Array von Speicherbytes. Ohne einen robusten Datentyp ist es unmöglich, eine Funktion zu schreiben, die die gleiche chomp
Funktion wie PHperytonby hat . Was würde eine solche Funktion in C zurückgeben?
do { q--; } ...
zu wissen *q != 0
.
Verwenden Sie eine Zeichenfolgenbibliothek , zum Beispiel:
Ustr *s1 = USTR1(\7, " 12345 ");
ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
... wie Sie sagen, dies ist ein "häufiges" Problem, ja, Sie müssen ein #include oder so einfügen und es ist nicht in libc enthalten, aber erfinden Sie nicht Ihren eigenen Hack-Job, indem Sie zufällige Zeiger speichern und size_t's nur dazu führen Puffer läuft über.
Wenn Sie verwenden glib
, können Sie g_strstrip verwenden
Um dieses Wachstum aufrechtzuerhalten, gibt es noch eine Option mit einer modifizierbaren Zeichenfolge:
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()
Gibt a zurück size_t
, das den Bereich von überschreiten kann int
. Leerzeichen sind nicht auf das Leerzeichen beschränkt. Schließlich, aber am wichtigsten: Undefiniertes Verhalten, strcpy(string, string + i * sizeof(char));
da sich Quell- und Zielarrays überlappen. Verwenden Sie memmove()
anstelle von strcpy()
.
while (isspace((int)string[i])) string[i--] = '\0';
die Schleife möglicherweise über den Anfang der Zeichenfolge hinausgeht. Sie sollten diese Schleife mit den vorherigen und folgenden Zeilen kombinieren und schreibenwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
end
nicht auf das nachfolgende Null-Byte zeigte und Sie end = ++i;
immer noch ein Problem mit Zeichenfolgen hatten, die alle Leerzeichen enthielten. Ich habe gerade den Code repariert.
Ich weiß, dass es viele Antworten gibt, aber ich poste meine Antwort hier, um zu sehen, ob meine Lösung gut genug ist.
// 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 wann *str < 0
.
size_t n
ist gut, aber die Schnittstelle informiert den Anrufer in keiner Weise, wenn n
er zu klein für eine vollständig zugeschnittene Zeichenfolge ist. Betrachten Sietrim(out, 12, "delete data not")
Der einfachste Weg, führende Leerzeichen in einer Zeichenfolge zu überspringen, ist, 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, das ist meine Sicht auf die Frage. Ich glaube, es ist die prägnanteste Lösung, die den vorhandenen String modifiziert ( free
funktioniert) und UB vermeidet. Für kleine Zeichenfolgen ist es wahrscheinlich schneller als eine Lösung mit 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
Test wird nur einmal benötigt. *b = 0;
nur einmal benötigt.
#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
hilft, alle Leerzeichen zu kürzen.
strndup
erstellen Sie schließlich einen neuen Zeichenfolgenpuffer, indem Sie Leerzeichen ausschließen.strndup()
ist nicht Teil des C-Standards, sondern nur Posix. Da es jedoch recht einfach zu implementieren ist, ist es keine große Sache.
trim_space("")
kehrt zurück NULL
. Ich würde einen Zeiger auf erwarten ""
. int len;
sollte sein size_t len;
. isspace(in[len - 1])
UB wann in[len - 1] < 0
.
while (isspace((unsigned char) *in) in++;
vorher len = strlen(in);
wäre effizienter als das späterewhile(len && *in && isspace(*in)) ++in, --len;
Persönlich würde ich meine eigenen rollen. Sie können strtok verwenden, müssen jedoch darauf achten (insbesondere, wenn Sie führende Zeichen entfernen), dass Sie wissen, welcher Speicher was ist.
Das Entfernen von nachgestellten Leerzeichen ist einfach und ziemlich sicher, da Sie einfach eine 0 über das letzte Leerzeichen setzen und vom Ende zurückzählen können. Führende Räume loszuwerden bedeutet, Dinge zu bewegen. Wenn Sie es an Ort und Stelle tun möchten (wahrscheinlich sinnvoll), können Sie einfach alles um einen Charakter zurückschieben, bis kein führendes Leerzeichen mehr vorhanden ist. Um effizienter zu sein, können Sie auch den Index des ersten Nicht-Leerzeichens finden und alles um diese Zahl zurückschieben. Oder Sie können einfach einen Zeiger auf das erste Nicht-Leerzeichen verwenden (aber dann müssen Sie genauso vorsichtig sein wie bei 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;
}
Ein bisschen spät zum Spiel, aber ich werde meine Routinen in den Kampf werfen. Sie sind wahrscheinlich nicht die absolut effizientesten, aber ich glaube, sie sind korrekt und einfach (mit rtrim()
Druck auf die Komplexität):
#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 in isspace()
to (unsigned char)
umwandeln, um undefiniertes Verhalten bei potenziell negativen Werten zu vermeiden. Vermeiden Sie es auch, die Zeichenfolge zu verschieben, ltrim()
wenn dies nicht erforderlich ist.
Die meisten der bisherigen Antworten lauten wie folgt:
strlen()
zuerst an und machen Sie einen zweiten Durchgang durch die gesamte Zeichenfolge.Diese Version macht nur einen Durchgang und geht nicht zurück. Daher kann es eine bessere Leistung als die anderen erzielen, allerdings nur dann, wenn es üblich ist, Hunderte von nachgestellten Leerzeichen zu haben (was bei der Ausgabe einer SQL-Abfrage nicht ungewöhnlich ist).
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()
und strcspn()
in einer engen Schleife. Dies ist sehr ineffizient und der Overhead wird den unbewiesenen Vorteil des einzelnen Vorwärtsdurchlaufs in den Schatten stellen. strlen()
wird normalerweise inline mit sehr effizientem Code erweitert, was kein wirkliches Problem darstellt. Das Trimmen des Anfangs und des Endes der Zeichenfolge ist viel schneller als das Testen jedes Zeichens in der Zeichenfolge auf Weißheit, selbst im speziellen Fall von Zeichenfolgen mit sehr wenigen oder keinen nicht weißen Zeichen.
Dies ist die kürzest mögliche Implementierung, die ich mir vorstellen kann:
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; }
Diese Funktionen ändern den ursprünglichen Puffer. Wenn er dynamisch zugewiesen wird, kann der ursprüngliche Zeiger freigegeben werden.
#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()
Ruft undefiniertes Verhalten für die leere Zeichenfolge auf. lstrip()
ist bei Zeichenfolgen mit einem langen Anfangsanteil an Leerzeichen unnötig langsam. isspace()
sollte kein char
Argument übergeben werden, da es undefiniertes Verhalten bei negativen Werten aufruft, die sich von unterscheiden EOF
.
Was halten Sie von der Verwendung der in der Kopfzeile Shlwapi.h definierten StrTrim-Funktion? Es ist einfach und definiert sich selbst.
Details finden Sie unter:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx
Wenn Sie haben,
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
wird dies ausCaptain
als "GeorgeBailey"
nicht geben "GeorgeBailey "
.
Um meine Saiten von beiden Seiten zu trimmen, benutze ich den Oldie, aber den Gooody.
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
anstelle von verwenden unsigned int
. Der Code hat viele redundante Tests und ruft undefiniertes Verhalten auf, strncpy(strData,&strData[S],L)
da sich die Quell- und Ziel-Arrays überlappen. Verwenden Sie memmove()
anstelle von strncpy()
.
Ich füge nur Code hinzu, weil der bisher veröffentlichte Code nicht optimal erscheint (und ich noch keinen Repräsentanten habe, der einen Kommentar abgeben kann.)
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()
ist eine GNU-Erweiterung. Wenn Sie es nicht haben oder etwas Äquivalentes, rollen Sie Ihr eigenes. Beispielsweise:
r = strdup(s + start);
r[end-start] = '\0';
isspace(0)
Wird als falsch definiert, können Sie beide Funktionen vereinfachen. Bewegen Sie auch das memmove()
Innere des if
Blocks.
Hier verwende ich die dynamische Speicherzuordnung, um die Eingabezeichenfolge auf die Funktion trimStr zu trimmen. Zunächst ermitteln wir, wie viele nicht leere Zeichen in der Eingabezeichenfolge vorhanden sind. Dann weisen wir ein Zeichenarray mit dieser Größe zu und kümmern uns um das nullterminierte Zeichen. Wenn wir diese Funktion verwenden, müssen wir den Speicher innerhalb der Hauptfunktion freigeben.
#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;
}
Hier ist, wie ich es mache. Die Zeichenfolge wird an Ort und Stelle gekürzt, sodass Sie sich keine Sorgen mehr machen müssen, wenn Sie die Rückgabe einer zurückgegebenen Zeichenfolge aufheben oder den Zeiger auf eine zugewiesene Zeichenfolge verlieren. Es ist vielleicht nicht die kürzest mögliche Antwort, aber es sollte den meisten Lesern klar sein.
#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;
}