Відповіді:
Якщо ви можете змінити рядок:
// 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
щоб 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. Складено з 'куб.см.оздоблення стін.c -о обробкою'.
isspace
щоб 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
щоб unsigned char
, в іншому випадку ви закликаєте до невизначеному поведінки.
if(l==0)return;
щоб уникнути нульової довжини str
Ось моя міні-бібліотека С для обрізки ліворуч, праворуч, обидва, всі, на місці та окремо, та обрізання набору вказаних символів (або пробіл за замовчуванням).
#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
підпрограми. Він обрізає набір символів, вказаний у роздільній строціабо пробіл, якщо нуль. Він обрізає ліворуч, праворуч і те, і все (як тр). Це не так багато, і він повторюється над рядком лише один раз. Деякі люди можуть скаржитися, що обрізка праворуч починається ліворуч, однак, жоден стринг не потрібен, який починається зліва. (Так чи інакше вам доведеться дістати до кінця рядка для правильних обрізків, щоб ви могли також виконати роботу, як ви йдете.) Можливо, слід зробити аргументи щодо розмірів конвеєра та кешу, і таке - хто знає . Оскільки рішення працює зліва направо і повторюється лише один раз, його також можна розширити для роботи на потоках. Обмеження: він не працює на рядках unicode .
dtab[*d]
не звертається *d
до того, unsigned int
як використовувати його як індекс масиву. У системі з підписаним знаком цього означення буде прочитано, 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
щоб unsigned char
, в іншому випадку ви закликаєте до невизначеному поведінки.
<ctype.h>
призначені для роботи з ints, які представляють собою 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, рядок регулюється ліворуч у буфері, щоб ви могли звільнити той же покажчик. Не є оптимальним для великих струн, оскільки в ньому не використовується пам'ятка. Включає операторів ++ / -, які згадує @ 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
щоб unsigned char
, в іншому випадку ви закликаєте до невизначеному поведінки.
while (isspace((unsigned char) *szWrite)) szWrite++;
заважав би цьому. Код також копіює весь пробіл.
*szWrite = *szRead
коли покажчики не рівні, пропустить запис у такому випадку, але тоді ми додали ще одне порівняння / гілку. З сучасним процесором / MMU / BP я не маю уявлення, чи буде ця перевірка втратою чи виграшем. З більш простими процесорами та архітектурою пам'яті дешевше просто зробити копію і пропустити порівняння.
Дуже пізно на вечірку ...
Рішення з односказовим скануванням вперед, без зворотного огляду. Кожен символ у вихідному рядку тестується рівно один раз двічі. (Отже, це має бути швидше, ніж більшість інших рішень тут, особливо якщо в початковому рядку є багато пробілів.)
Це включає два рішення: одне - копіювати та обрізати вихідний рядок в інший рядок призначення, а інший - обрізати вихідний рядок на місці. Обидві функції використовують один і той же код.
Рядок (що змінюється) переміщується на місці, тому вихідний вказівник на нього залишається незмінним.
#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()
функцію, яка знає скопіювати джерело до місця призначення назад і, ймовірно, бере додатковий аргумент довжини рядка.
Я не впевнений, що ви вважаєте "безболісним".
Струни досить болючі. Ми можемо знайти першу позицію символів, що не є пробілом, тривіально:
while (isspace (* p)) p ++;
Ми можемо знайти останню позицію символів без пробілів з двома подібними тривіальними рухами:
while (* q) q ++; робити {q--; } while (isspace (* q));
(Я позбавив вас від того, що ви одночасно користуєтесь операторами *
та ++
операторами.)
Питання зараз - що ти з цим робиш? Даний тип даних насправді не є великим надійним рефератом, String
про який легко подумати, але натомість ледве чи не більшим, ніж масив байтів для зберігання. Не маючи надійного типу даних, неможливо написати функцію, яка буде виконувати те саме, що і chomp
функція PHperytonby . Що б повернула така функція в C?
do { q--; } ...
знати *q != 0
.
Використовуйте , наприклад, бібліотеку рядків :
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()
повертає a, 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")
Найпростіший спосіб пропустити провідні пробіли в рядку - це,
#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. Для невеликих струн це, мабуть, швидше, ніж рішення, що стосується пам'яті.
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()
не є частиною стандарту С, а лише 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 "
.
Для обрізки моїх струн з обох сторін я використовую олджі, але гуді;) Це може обрізати що завгодно з 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;
}