Respostas:
Se você pode modificar a sequência:
// 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;
}
Se você não pode modificar a string, pode usar basicamente o mesmo método:
// 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
é uma variável local e sua alteração não altera o ponteiro original que está sendo passado. As chamadas de função em C são sempre passadas por valor, nunca passadas por referência.
free()
função. Muito pelo contrário - projetei isso para evitar a necessidade de alocação de memória para obter eficiência. Se o endereço passado foi alocado dinamicamente, o responsável pela chamada ainda é responsável por liberar essa memória e o responsável pela chamada precisa garantir que não substitua esse valor pelo valor retornado aqui.
isspace
a unsigned char
, caso contrário, você invocar um comportamento indefinido.
Aqui está um que muda a string para a primeira posição do seu buffer. Você pode querer esse comportamento para que, se você alocou dinamicamente a sequência, ainda pode liberá-la no mesmo ponteiro que trim () retorna:
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;
}
Teste de correção:
#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;
}
O arquivo de origem foi trim.c. Compilado com 'cc -Wall trim.c -o trim'.
isspace
a unsigned char
, caso contrário, você invocar um comportamento indefinido.
isspace()
então por que haveria uma diferença entre " "
e "\n"
? Eu adicionei testes de unidade para novas linhas e parece OK para mim ... ideone.com/bbVmqo
*(endp + 1) = '\0';
. O teste de exemplo na resposta usa um buffer de 64 que evita esse problema.
Minha solução A sequência deve ser alterável. A vantagem, acima de algumas das outras soluções, de que ela move a parte não espacial para o início, para que você possa continuar usando o ponteiro antigo, caso precise liberá-lo () mais tarde.
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);
}
Esta versão cria uma cópia da string com strndup () em vez de editá-la no lugar. strndup () requer _GNU_SOURCE, então talvez você precise criar seu próprio strndup () com malloc () e 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()
chama UB se s
for ""
como isspace()
seria a primeira chamada isspace(p[-1])
e p[-1]
não necessariamente faz referência a um local legal.
isspace
a unsigned char
, caso contrário, você invocar um comportamento indefinido.
if(l==0)return;
para evitar comprimento zero str
Aqui está minha mini biblioteca C para aparar à esquerda, direita, ambos, todos, no lugar e separados, e aparar um conjunto de caracteres especificados (ou espaço em branco por padrão).
#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); }
A única rotina principal faz tudo. Ele apara se src == dst , caso contrário, funciona como as strcpy
rotinas. Apara um conjunto de caracteres especificado no delim da stringou espaço em branco se nulo. Apara esquerda, direita, ambos e tudo (como tr). Não há muito e itera sobre a string apenas uma vez. Algumas pessoas podem reclamar que o corte à direita começa à esquerda, no entanto, não é necessário nenhum strlen que comece à esquerda. (De uma forma ou de outra, você precisa chegar ao final da sequência para obter as guarnições corretas, para que você possa fazer o trabalho à medida que for avançando.) . Como a solução funciona da esquerda para a direita e itera apenas uma vez, ela pode ser expandida para funcionar também em fluxos. Limitações: não funciona em cadeias unicode .
dtab[*d]
não é convertido *d
para unsigned int
antes de usá-lo como um índice de matriz. Em um sistema com char assinado, será lido o dtab[-127]
que causará bugs e possivelmente travará.
dtab[*delim++]
porque os char
valores do índice devem ser convertidos em unsigned char
. O código assume 8 bits char
. delim
deve ser declarado como const char *
. dtab[0xFF & (unsigned int)*d]
seria mais claro como dtab[(unsigned char)*d]
. O código funciona em cadeias codificadas em UTF-8, mas não remove seqüências de espaçamento não ASCII.
Aqui está minha tentativa de uma função de ajuste simples, mas correta no local.
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]))
evitar UB quando str is
"" . Prevents
str [-1] `.
isspace
a unsigned char
, caso contrário, você invocar um comportamento indefinido.
<ctype.h>
destinam-se a trabalhar com ints, que representam um unsigned char
ou o valor especial EOF
. Consulte stackoverflow.com/q/7131026/225757 .
Tarde para a festa
Características:
1. Apare o início rapidamente, como em várias outras respostas.
2. Depois de ir para o final, corte a direita com apenas 1 teste por loop. Como @ jfm3, mas funciona para uma sequência de espaços em branco)
3. Para evitar um comportamento indefinido quando char
é assinado char
, faça a conversão *s
para unsigned char
.
Manipulação de caracteres "Em todos os casos, o argumento é um
int
, cujo valor deve ser representável comounsigned char
ou deve ser igual ao valor da macroEOF
. Se o argumento tiver outro valor, o comportamento será indefinido." 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 comentou que o item acima não altera a sequência aparada. Para fazer isso ....
// 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);
}
Aqui está uma solução semelhante à rotina de modificação no local @ adam-rosenfields, mas sem recorrer desnecessariamente ao strlen (). Como @jkramer, a string é ajustada à esquerda no buffer para que você possa liberar o mesmo ponteiro. Não é ideal para cadeias grandes, pois não usa memmove. Inclui os operadores ++ / - mencionados pelo @ jfm3. Testes de unidade baseados em FCTX incluídos.
#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();
Outro, com uma linha fazendo o trabalho real:
#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
especificador de conversão, e no final é apenas mais simples fazê-lo manualmente, receio.
Não gostei da maioria dessas respostas porque elas fizeram um ou mais dos seguintes ...
Aqui está a minha versão:
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
a unsigned char
, caso contrário, você invocar um comportamento indefinido.
while (isspace((unsigned char) *szWrite)) szWrite++;
impediria isso. O código também copia todo o espaço em branco à direita.
*szWrite = *szRead
quando os ponteiros não são iguais ignoraria as gravações nesse caso, mas adicionamos outra comparação / ramificação. Com a CPU / MMU / BP moderna, não tenho idéia se essa verificação seria uma perda ou um ganho. Com processadores e arquiteturas de memória mais simples, é mais barato fazer a cópia e pular a comparação.
Muito tarde para a festa ...
Solução de varredura direta de passagem única sem retorno. Cada caractere na cadeia de origem é testado exatamente uma vez duas vezes. (Portanto, deve ser mais rápido que a maioria das outras soluções aqui, especialmente se a cadeia de origem tiver muitos espaços à direita.)
Isso inclui duas soluções, uma para copiar e aparar uma sequência de origem em outra sequência de destino e a outra para aparar a sequência de origem no local. Ambas as funções usam o mesmo código.
A sequência (modificável) é movida no local, portanto, o ponteiro original permanece inalterado.
#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'
e testada com isspace()
. Parece um desperdício testar todos os personagens isspace()
. O retorno do final da sequência deve ser mais eficiente para casos não patológicos.
trim()
ESTÁ BEM. Caixa de canto: trim2(char *d, const char *s)
apresenta problemas ao se d,s
sobrepor e s < d
.
trim()
se comportar? Você está pedindo para aparar e copiar uma sequência na memória ocupada pela própria sequência. Ao contrário memmove()
, isso requer determinar o comprimento da cadeia de origem antes de fazer o corte em si, o que requer a varredura de toda a cadeia por um tempo adicional. Melhor escrever uma rtrim2()
função diferente que saiba copiar a fonte para o destino de trás para frente e provavelmente use um argumento adicional para o comprimento da string da fonte.
Não sei ao certo o que você considera "indolor".
C strings são bastante dolorosas. Podemos encontrar a primeira posição de caractere sem espaço em branco trivialmente:
while (isspace (* p)) p ++;
Podemos encontrar a última posição de caractere não-espaço em branco com dois movimentos triviais semelhantes:
enquanto (* q) q ++; faça {q--; } while (isspace (* q));
(Poupei o trabalho de usar os operadores *
e ++
ao mesmo tempo.)
A questão agora é o que você faz com isso? O tipo de dados em questão não é realmente um grande resumo robusto String
e fácil de se pensar, mas, na verdade, quase nada além de uma matriz de bytes de armazenamento. Na falta de um tipo de dados robusto, é impossível escrever uma função que faça o mesmo que a chomp
função do PHperytonby . O que essa função em C retornaria?
do { q--; } ...
de saber *q != 0
.
Use uma biblioteca de cadeias , por exemplo:
Ustr *s1 = USTR1(\7, " 12345 ");
ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
... como você diz que esse é um problema "comum", sim, você precisa incluir um #include ou não, e ele não está incluído na libc, mas não invente seu próprio trabalho de hack, armazenando ponteiros aleatórios e size_t, que só levam a estouros de buffer.
Se você estiver usando glib
, poderá usar g_strstrip
Apenas para continuar crescendo, mais uma opção com uma string modificável:
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()
retorna um size_t
que pode exceder o intervalo de int
. espaço em branco não é restrito ao caractere de espaço. Finalmente, mas mais importante: Comportamento indefinido ativado strcpy(string, string + i * sizeof(char));
porque as matrizes de origem e destino se sobrepõem. Use em memmove()
vez de strcpy()
.
while (isspace((int)string[i])) string[i--] = '\0';
pode fazer um loop além do início da string. Você deve combinar esse loop com as linhas anteriores e seguintes e escreverwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
end
pois não apontava para o byte nulo à direita e você end = ++i;
ainda tinha um problema para cadeias contendo todos os caracteres de espaço em branco. Eu apenas consertei o código.
Sei que tenho muitas respostas, mas posto aqui minha resposta para ver se minha solução é boa o suficiente.
// 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 quando *str < 0
.
size_t n
é bom, mas a interface não informa o chamador de maneira alguma sobre n
ser muito pequeno para uma sequência cortada completa. Consideretrim(out, 12, "delete data not")
A maneira mais fácil de ignorar espaços à esquerda em uma string é 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, esta é a minha opinião sobre a questão. Eu acredito que é a solução mais concisa que modifica a string no lugar ( free
funcionará) e evita qualquer UB. Para strings pequenas, provavelmente é mais rápido que uma solução que envolva 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
teste é necessário apenas uma vez. *b = 0;
necessário apenas uma vez.
#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
ajuda a aparar todos os espaços em branco.
strndup
para criar um novo buffer de string excluindo espaços.strndup()
não faz parte do padrão C, mas apenas do Posix. Mas, como é muito fácil de implementar, não é grande coisa.
trim_space("")
retorna NULL
. Eu esperaria um ponteiro para ""
. int len;
deveria ser size_t len;
. isspace(in[len - 1])
UB quando in[len - 1] < 0
.
while (isspace((unsigned char) *in) in++;
antes len = strlen(in);
seria mais eficiente que a posteriorwhile(len && *in && isspace(*in)) ++in, --len;
Pessoalmente, eu rolaria o meu. Você pode usar o strtok, mas precisa tomar cuidado (principalmente se estiver removendo caracteres iniciais) para saber qual é a memória.
Livrar-se dos espaços à direita é fácil e bastante seguro, pois você pode simplesmente colocar um 0 no topo do último espaço, contando desde o final. Livrar-se dos espaços principais significa mudar as coisas. Se você quiser fazê-lo no lugar (provavelmente sensato), basta mudar tudo de volta para um personagem até que não haja espaço à frente. Ou, para ser mais eficiente, você pode encontrar o índice do primeiro caractere não espacial e mudar tudo de volta por esse número. Ou, você pode simplesmente usar um ponteiro para o primeiro caractere não espacial (mas, em seguida, você precisa ter cuidado da mesma maneira que no 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;
}
Um pouco tarde para o jogo, mas vou jogar minhas rotinas na briga. Provavelmente, eles não são os mais eficientes, mas acredito que estão corretos e são simples (com o rtrim()
empurrão do envelope da complexidade):
#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
argumento para isspace()
a (unsigned char)
evitar um comportamento indefinido em valores potencialmente negativos. Evite também mover a corda, se ltrim()
for necessário.
Até agora, a maioria das respostas segue um destes procedimentos:
strlen()
primeiro, fazendo uma segunda passagem por toda a cadeia.Esta versão faz apenas uma passagem e não retrocede. Portanto, ele pode ter um desempenho melhor que os outros, embora apenas se for comum ter centenas de espaços à direita (o que não é incomum ao lidar com a saída de uma consulta 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()
e strcspn()
em um circuito fechado. Isso é muito ineficiente e a sobrecarga diminui a vantagem não comprovada do passe único para frente. strlen()
geralmente é expandido em linha com código muito eficiente, não é uma preocupação real. Aparar o início e o fim da sequência será muito mais rápido do que testar todos os caracteres da sequência quanto à brancura, mesmo no caso especial de sequências com muito poucos ou nenhum caractere não branco.
Esta é a implementação mais curta possível em que consigo pensar:
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; }
Essas funções modificarão o buffer original, portanto, se alocado dinamicamente, o ponteiro original poderá ser liberado.
#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()
chama um comportamento indefinido na sequência vazia. lstrip()
é desnecessariamente lento na string com uma longa porção inicial de caracteres de espaço em branco. isspace()
não deve receber um char
argumento porque invoca um comportamento indefinido em valores negativos diferentes de EOF
.
O que você acha sobre o uso da função StrTrim definida no cabeçalho Shlwapi.h.? É simples e bastante definidor por conta própria.
Detalhes podem ser encontrados em:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx
Se você tiver,
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
isso dará ausCaptain
como "GeorgeBailey"
não "GeorgeBailey "
.
Para aparar minhas cordas dos dois lados, uso o oldie, mas o bobo;) Ele pode aparar qualquer coisa com ascii menor que um espaço, o que significa que os caracteres de controle também serão aparados!
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
vez de unsigned int
. O código possui muitos testes redundantes e invoca um comportamento indefinido strncpy(strData,&strData[S],L)
porque as matrizes de origem e destino se sobrepõem. Use em memmove()
vez de strncpy()
.
Estou incluindo apenas o código porque o código postado até o momento parece subótimo (e ainda não tenho o representante para comentar).
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()
é uma extensão GNU. Se você não o possui ou algo equivalente, faça o seu. Por exemplo:
r = strdup(s + start);
r[end-start] = '\0';
isspace(0)
é definido como falso, você pode simplificar as duas funções. Mova também o memmove()
interior do if
bloco.
Aqui eu uso a alocação dinâmica de memória para aparar a string de entrada na função trimStr. Primeiro, descobrimos quantos caracteres não vazios existem na string de entrada. Em seguida, alocamos uma matriz de caracteres com esse tamanho e cuidamos do caractere terminado nulo. Quando usamos essa função, precisamos liberar a memória dentro da função principal.
#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;
}
Aqui está como eu faço isso. Ele apara a sequência no lugar, portanto, não se preocupe em desalocar uma sequência retornada ou perder o ponteiro para uma sequência alocada. Pode não ser a resposta mais curta possível, mas deve ficar clara para a maioria dos leitores.
#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;
}