Ответы:
Если вы можете изменить строку:
// 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;
}
Если вы не можете изменить строку, то вы можете использовать в основном тот же метод:
// 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
- локальная переменная, и ее изменение не изменяет исходный указатель, который передается. Вызовы функций в C всегда передаются по значению, а не по ссылке.
free()
функции. Скорее наоборот - я разработал это, чтобы избежать необходимости выделения памяти для эффективности. Если переданный адрес был распределен динамически, то вызывающая сторона по-прежнему отвечает за освобождение этой памяти, и вызывающая сторона должна быть уверена, что не перезаписывает это значение значением, возвращаемым здесь.
isspace
to unsigned char
, в противном случае вы вызываете неопределенное поведение.
Вот тот, который сдвигает строку в первую позицию вашего буфера. Возможно, вам понадобится такое поведение, чтобы при динамическом размещении строки вы могли освобождать ее по тому же указателю, который возвращает 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;
}
Тест на правильность:
#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;
}
Исходный файл был trim.c. Скомпилировано с 'cc -Wall trim.c -o trim'.
isspace
to unsigned char
, в противном случае вы вызываете неопределенное поведение.
isspace()
так почему же существует разница между " "
и "\n"
? Я добавил модульные тесты для переноса строк и это выглядит КИ для меня ... ideone.com/bbVmqo
*(endp + 1) = '\0';
. В примере теста ответа используется буфер 64, что позволяет избежать этой проблемы.
Мое решение. Строка должна быть изменяемой. Преимущество перед некоторыми другими решениями заключается в том, что он перемещает непробельную часть в начало, поэтому вы можете продолжать использовать старый указатель на случай, если вам придется освободить () его позже.
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);
}
Эта версия создает копию строки с помощью strndup (), а не редактирует ее на месте. strndup () требует _GNU_SOURCE, поэтому, возможно, вам нужно создать свой собственный strndup () с помощью malloc () и 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()
вызывает UB , если s
это ""
как первый isspace()
вызов будет isspace(p[-1])
и p[-1]
не обязательно ссылаться на юридическое местоположение.
isspace
to unsigned char
, в противном случае вы вызываете неопределенное поведение.
if(l==0)return;
чтобы избежать нулевой длины str
Вот моя библиотека C mini для обрезки левого, правого, как, все, на месте и отдельно, и обрезать набор указанных символов (или пробел по умолчанию).
#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); }
Одна основная рутина делает все это. Он обрезается на месте, если src == dst , в противном случае он работает как strcpy
процедуры. Обрезает набор символов, указанных в строке delimили пробел, если ноль. Он обрезает левый, правый, оба и все (как tr). В этом нет ничего особенного, и он перебирает строку только один раз. Некоторые люди могут жаловаться, что обрезка справа начинается слева, однако, нет необходимости в strlen, который начинается в любом случае слева. (Так или иначе, вы должны добраться до конца строки для правильной обрезки, так что вы можете выполнять работу по ходу дела.) Могут быть аргументы о конвейерной обработке и размерах кэша и тому подобное - кто знает , Поскольку решение работает слева направо и повторяется только один раз, его можно расширить и на потоки. Ограничения: он не работает со строками Unicode .
dtab[*d]
не бросает *d
на unsigned int
перед ее использованием в качестве индекса массива. В системе с подписанным char это будет считывать, dtab[-127]
что приведет к ошибкам и, возможно, к аварийному завершению .
dtab[*delim++]
потому что char
значения индекса должны быть приведены к unsigned char
. Код предполагает 8-битный char
. delim
должен быть объявлен как const char *
. dtab[0xFF & (unsigned int)*d]
понятнее как dtab[(unsigned char)*d]
. Код работает с строками в кодировке UTF-8, но не удаляет последовательности без ASCII.
Вот моя попытка простой, но правильной функции обрезки на месте.
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]))
предотвратить UB, когда str is
"" . Prevents
str [-1] `.
isspace
to unsigned char
, в противном случае вы вызываете неопределенное поведение.
<ctype.h>
предназначены для работы с целочисленными значениями, которые представляют либо unsigned char
специальное значение, либо EOF
. См. Stackoverflow.com/q/7131026/225757 .
До поздней вечеринки
Особенности:
1. Обрежьте начало быстро, как и в ряде других ответов.
2. Пройдя до конца, обрежьте вправо только с 1 тестом на петлю. Как @ jfm3, но работает в течение всего белого пространства строки)
3. Для того, чтобы избежать неопределенное поведение , когда char
это знаковое char
, приведение *s
к unsigned char
.
Обработка символов «Во всех случаях аргумент является аргументом
int
, значение которого должно быть представлено какunsigned char
или должно быть равно значению макросаEOF
. Если аргумент имеет любое другое значение, поведение не определено». 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 прокомментировал, что выше не сдвигает обрезанную строку. Для этого ....
// 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);
}
Вот решение, похожее на процедуру модификации на месте @ adam-rosenfields, но без необходимости прибегать к strlen (). Как и @jkramer, строка корректируется в пределах буфера, чтобы вы могли освободить тот же указатель. Не оптимально для больших строк, так как не использует memmove. Включает операторы ++ / -, о которых упоминает @ jfm3. Включены юнит-тесты на основе 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();
Другой, с одной линией, выполняющей настоящую работу:
#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
спецификатором преобразования, и, в конце концов, я просто боюсь сделать это вручную.
Мне не понравились большинство из этих ответов, потому что они сделали одно или несколько из следующих действий ...
Вот моя версия:
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
, в противном случае вы вызываете неопределенное поведение.
while (isspace((unsigned char) *szWrite)) szWrite++;
помешал бы этому. Код также копирует все оставшиеся пробелы.
*szWrite = *szRead
тогда, когда указатели не равны, в этом случае пропускает записи, но затем мы добавили еще одну ветвь сравнения / сравнения. С современным CPU / MMU / BP я понятия не имею, будет ли эта проверка проигрышной или выигрышной. С более простыми процессорами и архитектурой памяти дешевле просто скопировать и пропустить сравнение.
Я не уверен, что вы считаете "безболезненным".
Струны C довольно болезненны. Мы можем найти первую позицию непробельного символа тривиально:
while (isspace (* p)) p ++;
Мы можем найти последнюю позицию непробельного символа с двумя похожими тривиальными ходами:
while (* q) q ++; do {q--; } while (isspace (* q));
(Я избавлен вам боль с помощью *
и ++
операторов одновременно.)
Вопрос сейчас в том, что вы делаете с этим? Тип данных, который мы имеем в виду, на самом деле не является большим здравым абстракцией, String
о котором легко думать, а просто не больше, чем массив байтов хранения. Не имея надежного типа данных, невозможно написать функцию, которая будет выполнять те же функции, что и chomp
функция PHperytonby . Что бы могла вернуть такая функция в C?
do { q--; } ...
узнать *q != 0
.
Очень поздно на вечеринку ...
Однопроходное решение для сканирования вперед без возврата. Каждый символ в исходной строке проверяется ровно один раз дважды. (Так что это должно быть быстрее, чем большинство других решений здесь, особенно если в исходной строке много пробелов.)
Это включает два решения: одно для копирования и обрезки исходной строки в другую строку назначения, а другое для обрезки исходной строки на месте. Обе функции используют один и тот же код.
Строка (модифицируемая) перемещается на место, поэтому исходный указатель на нее остается неизменным.
#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'
и затем проверяется с помощью isspace()
. Кажется расточительным проверять всех персонажей isspace()
. Откат от конца строки должен быть более эффективным для непатологических случаев.
trim()
ХОРОШО. Угловой корпус: trim2(char *d, const char *s)
возникают проблемы при d,s
наложении и s < d
.
trim()
вести себя в этом угловом случае ? Вы просите обрезать и скопировать строку в память, занятую самой строкой. В отличие от memmove()
этого, это требует определения длины исходной строки перед выполнением самой обрезки, что требует дополнительного сканирования всей строки. Лучше написать другую rtrim2()
функцию, которая знает, как скопировать источник в место назначения в обратном направлении, и, вероятно, принимает дополнительный аргумент длины строки источника.
Используйте библиотеку строк , например:
Ustr *s1 = USTR1(\7, " 12345 ");
ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
... как вы говорите, это "распространенная" проблема, да, вам нужно включить #include или около того, и он не включен в libc, но не изобретайте свою собственную хакерскую работу, хранящую случайные указатели, а size_t таким образом приводит только к переполнение буфера.
Если вы используете glib
, то вы можете использовать g_strstrip
Просто, чтобы сохранить этот рост, еще один вариант с изменяемой строкой:
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()
возвращает , size_t
что может превысить диапазон int
. пробел не ограничен пробелом. Наконец, но самое важное: неопределенное поведение, strcpy(string, string + i * sizeof(char));
потому что массивы источника и назначения перекрываются. Используйте memmove()
вместо strcpy()
.
while (isspace((int)string[i])) string[i--] = '\0';
может зацикливаться за началом строки. Вы должны объединить этот цикл с предыдущими и последующими строками и написатьwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
end
не указывало на завершающий нулевой байт, и у вас end = ++i;
все еще была проблема со строками, содержащими все пробельные символы. Я только что исправил код.
Я знаю, что есть много ответов, но я публикую свой ответ здесь, чтобы посмотреть, достаточно ли хорошее решение
// 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 когда *str < 0
.
size_t n
хорошо, но интерфейс никоим образом не информирует вызывающую сторону о том, n
что он слишком мал для полной обрезанной строки. Рассмотримtrim(out, 12, "delete data not")
Самый простой способ пропустить начальные пробелы в строке, 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 "
.
Хорошо, это мой взгляд на вопрос. Я считаю, что это наиболее краткое решение, которое изменяет строку на месте ( free
будет работать) и избегает любых UB. Для небольших строк это, вероятно, быстрее, чем решение с использованием 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
Тест требуется только один раз. *b = 0;
нужен только один раз.
#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
помогает обрезать все пробелы.
strndup
для создания нового строкового буфера, исключив пробелы.strndup()
не является частью стандарта C, а только Posix. Но так как это довольно легко реализовать, это не имеет большого значения.
trim_space("")
возвращается NULL
. Я ожидаю указатель на ""
. int len;
должно быть size_t len;
. isspace(in[len - 1])
UB когда in[len - 1] < 0
.
while (isspace((unsigned char) *in) in++;
прежде чем len = strlen(in);
будет более эффективным , чем позднееwhile(len && *in && isspace(*in)) ++in, --len;
Лично я бы сам себя катал. Вы можете использовать strtok, но вам нужно позаботиться об этом (особенно если вы удаляете ведущих символов), чтобы вы знали, что такое память.
Избавиться от конечных пробелов легко и довольно безопасно, так как вы можете просто поставить 0 поверх последнего пробела, считая от конца до конца. Избавление от пробелов означает движение вещей вокруг. Если вы хотите сделать это на месте (вероятно, разумно), вы можете просто перемещать все назад на один символ, пока не будет пробела. Или, чтобы быть более эффективным, вы можете найти индекс первого непробельного символа и сдвинуть все обратно на это число. Или вы можете просто использовать указатель на первый непробельный символ (но тогда вам нужно быть осторожным так же, как вы делаете с 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;
}
Немного опаздываю к игре, но я переверну рутину. Они, вероятно, не самые эффективные, но я считаю, что они правильные и простые (с rtrim()
учетом сложности):
#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
аргумент к, isspace()
чтобы (unsigned char)
избежать неопределенного поведения потенциально отрицательных значений. Также избегайте перемещения строки, если в ней ltrim()
нет необходимости.
Большинство ответов до сих пор делают одно из следующего:
strlen()
Сначала позвоните , сделав второй проход через всю строку.Эта версия делает только один проход и не возвращается. Следовательно, он может работать лучше, чем другие, хотя только в том случае, если распространены сотни пробелов (что весьма обычно при работе с выводом запроса 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()
и strcspn()
в жесткой петле. Это очень неэффективно, и накладные расходы превзойдут недоказанное преимущество одного прямого прохода. strlen()
обычно расширяется встроенным очень эффективным кодом, а не реальной проблемой. Обрезка начала и конца строки будет намного быстрее, чем проверка каждого символа в строке на белизну, даже в особом случае строк с очень небольшим количеством или отсутствием небелых символов.
Это кратчайшая возможная реализация, о которой я могу подумать:
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; }
Эти функции изменят исходный буфер, поэтому при динамическом размещении исходный указатель может быть освобожден.
#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()
вызывает неопределенное поведение для пустой строки. lstrip()
излишне медленно работает со строкой с длинной начальной частью пробельных символов. isspace()
не должен передаваться char
аргумент, потому что он вызывает неопределенное поведение для отрицательных значений, отличных от EOF
.
Что вы думаете об использовании функции StrTrim, определенной в заголовке Shlwapi.h.? Это прямолинейно, скорее, самоопределение.
Подробности можно найти по адресу :
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx
Если у вас есть
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
Это даст ausCaptain
как "GeorgeBailey"
нет "GeorgeBailey "
.
Чтобы обрезать мои струны с обеих сторон, я использую oldie, но gooody;) Он может обрезать что угодно с ascii меньше пробела, что означает, что управляющие символы также будут обрезаны!
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
вместо unsigned int
. Код имеет множество избыточных тестов и вызывает неопределенное поведение, strncpy(strData,&strData[S],L)
потому что массивы источника и назначения перекрываются. Используйте memmove()
вместо strncpy()
.
Я только включаю код, потому что код, размещенный до сих пор, кажется неоптимальным (и у меня пока нет представителя, чтобы комментировать.)
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()
является расширением GNU. Если у вас его нет или что-то подобное, сверните свое. Например:
r = strdup(s + start);
r[end-start] = '\0';
isspace(0)
определено как ложное, вы можете упростить обе функции. Также переместите memmove()
внутрь if
блока.
Здесь я использую динамическое выделение памяти для обрезки входной строки до функции trimStr. Сначала мы выясним, сколько непустых символов существует во входной строке. Затем мы выделяем массив символов с таким размером и заботимся о нулевом символе в конце. Когда мы используем эту функцию, нам нужно освободить память внутри основной функции.
#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;
}
Вот как я это делаю. Он обрезает строку на месте, поэтому не беспокойтесь об освобождении возвращаемой строки или потере указателя на выделенную строку. Возможно, это не самый короткий ответ, но он должен быть понятен большинству читателей.
#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;
}