Có một phương pháp sạch, tốt nhất là tiêu chuẩn để cắt khoảng trắng hàng đầu và dấu kiểm từ một chuỗi trong C không? Tôi tự lăn, nhưng tôi nghĩ đây là một vấn đề phổ biến với một giải pháp phổ biến không kém.
Có một phương pháp sạch, tốt nhất là tiêu chuẩn để cắt khoảng trắng hàng đầu và dấu kiểm từ một chuỗi trong C không? Tôi tự lăn, nhưng tôi nghĩ đây là một vấn đề phổ biến với một giải pháp phổ biến không kém.
Câu trả lời:
Nếu bạn có thể sửa đổi chuỗi:
// 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;
}
Nếu bạn không thể sửa đổi chuỗi, thì về cơ bản bạn có thể sử dụng cùng một phương thức:
// 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
là một biến cục bộ và việc thay đổi nó không thay đổi con trỏ ban đầu được truyền vào. Các lệnh gọi hàm trong C luôn luôn là giá trị truyền, không bao giờ chuyển qua tham chiếu.
free()
hàm. Hoàn toàn ngược lại - Tôi đã thiết kế điều này để tránh sự cần thiết phải phân bổ bộ nhớ cho hiệu quả. Nếu địa chỉ được truyền được phân bổ động, thì người gọi vẫn có trách nhiệm giải phóng bộ nhớ đó và người gọi cần chắc chắn không ghi đè giá trị đó bằng giá trị được trả về ở đây.
isspace
tới unsigned char
, nếu không bạn gọi hành vi không xác định.
Đây là một trong đó chuyển chuỗi vào vị trí đầu tiên của bộ đệm của bạn. Bạn có thể muốn hành vi này để nếu bạn tự động phân bổ chuỗi, bạn vẫn có thể giải phóng nó trên cùng một con trỏ mà trim () trả về:
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;
}
Kiểm tra tính chính xác:
#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;
}
Tập tin nguồn đã được trim.c. Được biên dịch với 'cc -Wall trim.c -o trim'.
isspace
tới unsigned char
, nếu không bạn gọi hành vi không xác định.
isspace()
như vậy tại sao sẽ có sự khác biệt giữa " "
và "\n"
? Tôi đã thêm các unit test cho dòng mới và có vẻ OK để tôi ... ideone.com/bbVmqo
*(endp + 1) = '\0';
. Bài kiểm tra ví dụ về câu trả lời sử dụng bộ đệm 64 để tránh vấn đề này.
Giải pháp của tôi. Chuỗi phải được thay đổi. Ưu điểm trên một số giải pháp khác là nó di chuyển phần không phải không gian về đầu để bạn có thể tiếp tục sử dụng con trỏ cũ, trong trường hợp bạn phải giải phóng () nó sau.
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);
}
Phiên bản này tạo một bản sao của chuỗi với strndup () thay vì chỉnh sửa nó tại chỗ. strndup () yêu cầu _GNU_SOURCE, vì vậy có lẽ bạn cần tạo strndup () của riêng bạn với malloc () và 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()
gọi UB nếu s
là cuộc gọi ""
đầu tiên và không nhất thiết phải tham chiếu một địa điểm hợp pháp. isspace()
isspace(p[-1])
p[-1]
isspace
tới unsigned char
, nếu không bạn gọi hành vi không xác định.
if(l==0)return;
để tránh str có độ dài bằng không
Đây là thư viện C mini của tôi để cắt tỉa trái, phải, cả hai, tất cả, tại chỗ và tách biệt và cắt xén một tập hợp các ký tự được chỉ định (hoặc khoảng trắng theo mặc định).
#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); }
Một thói quen chính làm tất cả. Nó cắt đúng vị trí nếu src == dst , nếu không, nó hoạt động như các strcpy
thói quen. Nó cắt một tập hợp các ký tự được chỉ định trong chuỗi delimhoặc khoảng trắng nếu null. Nó cắt trái, phải, cả hai và tất cả (như tr). Không có gì nhiều cho nó, và nó lặp lại qua chuỗi chỉ một lần. Một số người có thể phàn nàn rằng việc cắt phải bắt đầu ở bên trái, tuy nhiên, không cần strlen nào bắt đầu ở bên trái. . . Vì giải pháp hoạt động từ trái sang phải và chỉ lặp lại một lần, nên nó có thể được mở rộng để hoạt động trên các luồng. Hạn chế: nó không hoạt động trên chuỗi unicode .
dtab[*d]
không cast *d
để unsigned int
trước khi sử dụng nó như là một chỉ số mảng. Trên một hệ thống có ký hiệu char, nó sẽ đọc lên dtab[-127]
và sẽ gây ra lỗi và có thể bị sập.
dtab[*delim++]
vì char
các giá trị chỉ mục phải được truyền tới unsigned char
. Mã giả định 8 bit char
. delim
nên được tuyên bố là const char *
. dtab[0xFF & (unsigned int)*d]
sẽ rõ ràng hơn như dtab[(unsigned char)*d]
. Mã này hoạt động trên các chuỗi được mã hóa UTF-8, nhưng sẽ không loại bỏ các chuỗi khoảng cách không phải ASCII.
Đây là nỗ lực của tôi tại một chức năng cắt tại chỗ đơn giản nhưng chính xác.
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]))
ngăn UB khi str is
"" . Prevents
str [-1] `.
isspace
tới unsigned char
, nếu không bạn gọi hành vi không xác định.
<ctype.h>
được dự định để làm việc với ints, đại diện cho một unsigned char
hoặc giá trị đặc biệt EOF
. Xem stackoverflow.com/q/7131026/225757 .
Đi dự tiệc muộn
Các tính năng:
1. Cắt bắt đầu nhanh chóng, như trong một số câu trả lời khác.
2. Sau khi đi đến cuối, cắt tỉa bên phải chỉ với 1 thử nghiệm trên mỗi vòng lặp. Giống như @ jfm3, nhưng hoạt động cho một chuỗi toàn khoảng trắng)
3. Để tránh hành vi không xác định khi char
được ký char
, hãy *s
chuyển sang unsigned char
.
Xử lý ký tự "Trong mọi trường hợp đối số là một
int
, giá trị sẽ được biểu diễn dưới dạngunsigned char
hoặc sẽ bằng giá trị của macroEOF
. Nếu đối số có bất kỳ giá trị nào khác, hành vi không được xác định." 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 nhận xét ở trên không dịch chuyển chuỗi đã cắt. Làm như vậy....
// 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);
}
Đây là một giải pháp tương tự như thói quen sửa đổi tại chỗ @ adam-rosenfields nhưng không cần dùng đến strlen (). Giống như @jkramer, chuỗi được điều chỉnh bên trái trong bộ đệm để bạn có thể giải phóng cùng một con trỏ. Không tối ưu cho các chuỗi lớn vì nó không sử dụng memmove. Bao gồm các toán tử ++ / - mà @ jfm3 đề cập. Bao gồm các bài kiểm tra đơn vị dựa trên 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();
Một số khác, với một dòng làm công việc thực sự:
#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
xác định chuyển đổi, và cuối cùng, nó chỉ đơn giản hơn để làm điều đó bằng tay, tôi sợ.
Tôi không thích hầu hết những câu trả lời này vì họ đã làm một hoặc nhiều điều sau đây ...
Đây là phiên bản của tôi:
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
tới unsigned char
, nếu không bạn gọi hành vi không xác định.
while (isspace((unsigned char) *szWrite)) szWrite++;
sẽ ngăn chặn điều đó. Mã cũng sao chép tất cả các khoảng trắng ở cuối.
*szWrite = *szRead
khi con trỏ không bằng nhau sẽ bỏ qua phần ghi trong trường hợp đó, nhưng sau đó chúng tôi đã thêm một so sánh / nhánh khác. Với CPU / MMU / BP hiện đại, tôi không biết kiểm tra đó sẽ là lỗ hay lãi. Với các bộ xử lý và kiến trúc bộ nhớ đơn giản hơn, việc sao chép và bỏ qua so sánh sẽ rẻ hơn.
Đến bữa tiệc rất muộn ...
Giải pháp quét đơn chuyển tiếp không có quay lại. Mỗi nhân vật trong chuỗi nguồn được kiểm tra chính xác một lần hai lần. (Vì vậy, nó phải nhanh hơn hầu hết các giải pháp khác ở đây, đặc biệt nếu chuỗi nguồn có nhiều khoảng trắng ở cuối.)
Điều này bao gồm hai giải pháp, một để sao chép và cắt một chuỗi nguồn thành một chuỗi đích khác và một để cắt chuỗi nguồn tại chỗ. Cả hai hàm đều sử dụng cùng một mã.
Chuỗi (có thể sửa đổi) được di chuyển tại chỗ, do đó con trỏ ban đầu đến nó vẫn không thay đổi.
#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'
và sau đó được kiểm tra isspace()
. Có vẻ lãng phí để kiểm tra tất cả các nhân vật với isspace()
. Quay lui từ cuối chuỗi nên hiệu quả hơn cho các trường hợp không bệnh lý.
trim()
ĐỒNG Ý. Trường hợp góc: trim2(char *d, const char *s)
có rắc rối khi d,s
chồng chéo và s < d
.
trim()
cư xử thế nào? Bạn đang yêu cầu cắt và sao chép một chuỗi vào bộ nhớ bị chiếm bởi chính chuỗi đó. Không giống như memmove()
, điều này đòi hỏi phải xác định độ dài của chuỗi nguồn trước khi tự cắt, yêu cầu quét toàn bộ chuỗi thêm một thời gian. Tốt hơn là viết một rtrim2()
hàm khác biết sao chép nguồn vào đích ngược lại và có thể cần một đối số độ dài chuỗi nguồn bổ sung.
Tôi không chắc những gì bạn cho là "không đau."
Dây C khá đau. Chúng ta có thể tìm thấy vị trí ký tự không phải khoảng trắng đầu tiên một cách tầm thường:
while (isspace (* p)) p ++;
Chúng ta có thể tìm thấy vị trí ký tự không phải khoảng trắng cuối cùng với hai bước di chuyển tầm thường tương tự:
trong khi (* q) q ++; làm {q--; } while (isspace (* q));
(Tôi đã tránh cho bạn nỗi đau khi sử dụng *
và các ++
nhà khai thác cùng một lúc.)
Câu hỏi bây giờ là bạn làm gì với điều này? Kiểu dữ liệu trong tay không thực sự là một bản tóm tắt lớn mạnh mẽ String
dễ nghĩ, mà thay vào đó thực sự hầu như không hơn một mảng byte lưu trữ. Thiếu một kiểu dữ liệu mạnh mẽ, không thể viết một hàm sẽ làm giống như hàm của PHperytonby chomp
. Điều gì một chức năng như vậy trong C trở lại?
do { q--; } ...
biết *q != 0
.
Sử dụng một thư viện chuỗi , ví dụ:
Ustr *s1 = USTR1(\7, " 12345 ");
ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
... như bạn nói đây là một vấn đề "phổ biến", vâng, bạn cần bao gồm một #incoide hoặc như vậy và nó không được bao gồm trong libc nhưng đừng phát minh ra công việc hack của riêng bạn lưu trữ con trỏ ngẫu nhiên và size_t theo cách đó chỉ dẫn đến tràn bộ đệm.
Nếu bạn đang sử dụng glib
, thì bạn có thể sử dụng g_strstrip
Để duy trì sự phát triển này, thêm một tùy chọn với chuỗi có thể sửa đổi:
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()
trả về một size_t
có thể vượt quá phạm vi int
. khoảng trắng không bị giới hạn trong ký tự khoảng trắng. Cuối cùng nhưng quan trọng nhất: Hành vi không xác định trên strcpy(string, string + i * sizeof(char));
vì mảng nguồn và đích trùng nhau. Sử dụng memmove()
thay vì strcpy()
.
while (isspace((int)string[i])) string[i--] = '\0';
có thể lặp ngoài đầu chuỗi. Bạn nên kết hợp vòng lặp này với các dòng trước và sau và viếtwhile (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
end
đã không trỏ đến byte null và bạn end = ++i;
vẫn gặp vấn đề với các chuỗi chứa tất cả các ký tự khoảng trắng. Tôi chỉ sửa mã.
Tôi biết có nhiều câu trả lời, nhưng tôi đăng câu trả lời của mình lên đây để xem giải pháp của tôi có đủ tốt không.
// 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 khi *str < 0
.
size_t n
là tốt, nhưng giao diện không thông báo cho người gọi theo bất kỳ cách nào khi n
quá nhỏ cho một chuỗi cắt hoàn chỉnh. Hãy xem xéttrim(out, 12, "delete data not")
Cách dễ nhất để bỏ qua các khoảng trắng hàng đầu trong một chuỗi là, 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 đây là tôi đưa ra câu hỏi. Tôi tin rằng đó là giải pháp ngắn gọn nhất giúp sửa đổi chuỗi tại chỗ ( free
sẽ hoạt động) và tránh mọi UB. Đối với các chuỗi nhỏ, nó có thể nhanh hơn một giải pháp liên quan đến 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
kiểm tra chỉ cần một lần. *b = 0;
chỉ cần một lần.
#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
giúp cắt tỉa tất cả các khoảng trắng.
strndup
để tạo bộ đệm chuỗi mới bằng cách loại trừ khoảng trắng.strndup()
không phải là một phần của tiêu chuẩn C mà chỉ là Posix. Nhưng vì nó khá dễ thực hiện nên nó không phải là vấn đề lớn.
trim_space("")
trả lại NULL
. Tôi mong đợi một con trỏ đến ""
. int len;
nên size_t len;
. isspace(in[len - 1])
UB khi nào in[len - 1] < 0
.
while (isspace((unsigned char) *in) in++;
trước len = strlen(in);
sẽ hiệu quả hơn so với sauwhile(len && *in && isspace(*in)) ++in, --len;
Cá nhân, tôi sẽ tự lăn. Bạn có thể sử dụng strtok, nhưng bạn cần cẩn thận khi làm như vậy (đặc biệt nếu bạn xóa các ký tự đầu) để bạn biết bộ nhớ là gì.
Loại bỏ các dấu cách là dễ dàng và khá an toàn, vì bạn chỉ cần đặt 0 ở trên cùng của không gian cuối cùng, đếm ngược từ cuối. Loại bỏ không gian hàng đầu có nghĩa là di chuyển mọi thứ xung quanh. Nếu bạn muốn thực hiện tại chỗ (có thể hợp lý), bạn có thể tiếp tục chuyển mọi thứ trở lại một ký tự cho đến khi không còn khoảng trống. Hoặc, để hiệu quả hơn, bạn có thể tìm chỉ mục của ký tự không phải không gian đầu tiên và dịch chuyển mọi thứ trở lại theo số đó. Hoặc, bạn chỉ có thể sử dụng một con trỏ đến ký tự không phải không gian đầu tiên (nhưng sau đó bạn cần cẩn thận theo cách tương tự như bạn làm với 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;
}
Một chút muộn để trò chơi, nhưng tôi sẽ ném thói quen của tôi vào cuộc cạnh tranh. Chúng có thể không phải là hiệu quả tuyệt đối nhất, nhưng tôi tin rằng chúng chính xác và chúng đơn giản (với rtrim()
việc đẩy phong bì phức tạp):
#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
lập luận để isspace()
để (unsigned char)
tránh hành vi undefined trên các giá trị tiêu cực tiềm tàng. Cũng tránh di chuyển chuỗi nếu trong ltrim()
nếu không cần thiết.
Hầu hết các câu trả lời cho đến nay làm một trong những điều sau đây:
strlen()
đầu tiên, thực hiện một lần thứ hai thông qua toàn bộ chuỗi.Phiên bản này chỉ thực hiện một lần và không quay lại. Do đó, nó có thể hoạt động tốt hơn các loại khác, mặc dù chỉ khi có hàng trăm khoảng trắng ở cuối (điều này không phải là bất thường khi xử lý đầu ra của truy vấn 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()
và strcspn()
trong một vòng lặp chặt chẽ. Điều này là rất không hiệu quả và chi phí sẽ làm giảm lợi thế chưa được chứng minh của đường chuyền đơn. strlen()
thường được mở rộng nội tuyến với mã rất hiệu quả, không phải là một mối quan tâm thực sự. Việc cắt xén phần đầu và phần cuối của chuỗi sẽ nhanh hơn nhiều so với việc kiểm tra mọi ký tự trong chuỗi về độ trắng ngay cả trong trường hợp đặc biệt của chuỗi có rất ít hoặc không có ký tự không phải màu trắng.
Đây là cách thực hiện ngắn nhất có thể mà tôi có thể nghĩ ra:
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; }
Các hàm này sẽ sửa đổi bộ đệm ban đầu, vì vậy nếu được phân bổ động, con trỏ ban đầu có thể được giải phóng.
#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()
gọi hành vi không xác định trên chuỗi trống. lstrip()
là chậm một cách không cần thiết trên chuỗi với một phần dài các ký tự khoảng trắng. isspace()
không nên thông qua một char
đối số vì nó gọi hành vi không xác định trên các giá trị âm khác với EOF
.
Bạn nghĩ gì về việc sử dụng hàm StrTrim được xác định trong tiêu đề Shlwapi.h.? Nó là thẳng về phía trước chứ không phải tự xác định.
Thông tin chi tiết có thể được tìm thấy trên:
http://msdn.microsoft.com/en-us/l Library / windows / desktop / bb773454 (v = vs85) .aspx
Nếu bạn có
char ausCaptain[]="GeorgeBailey ";
StrTrim(ausCaptain," ");
Điều này sẽ cho ausCaptain
như "GeorgeBailey"
không "GeorgeBailey "
.
Để cắt chuỗi của tôi từ cả hai phía, tôi sử dụng oldie nhưng gooody;) Nó có thể cắt bất cứ thứ gì với ascii nhỏ hơn một khoảng trắng, có nghĩa là các ký tự điều khiển cũng sẽ được cắt bớt!
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
thay vì unsigned int
. Mã này có rất nhiều kiểm tra dự phòng và gọi hành vi không xác định trên strncpy(strData,&strData[S],L)
vì các mảng nguồn và đích trùng nhau. Sử dụng memmove()
thay vì strncpy()
.
Tôi chỉ bao gồm mã bởi vì mã được đăng cho đến nay dường như không tối ưu (và tôi chưa có đại diện để nhận xét.)
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()
là một phần mở rộng GNU. Nếu bạn không có nó hoặc một cái gì đó tương đương, cuộn của riêng bạn. Ví dụ:
r = strdup(s + start);
r[end-start] = '\0';
isspace(0)
được định nghĩa là sai, bạn có thể đơn giản hóa cả hai chức năng. Cũng di chuyển memmove()
bên trong if
khối.
Ở đây tôi sử dụng cấp phát bộ nhớ động để cắt chuỗi đầu vào cho hàm trimStr. Đầu tiên, chúng tôi tìm thấy có bao nhiêu ký tự không trống tồn tại trong chuỗi đầu vào. Sau đó, chúng tôi phân bổ một mảng ký tự với kích thước đó và chăm sóc ký tự kết thúc null. Khi chúng ta sử dụng chức năng này, chúng ta cần giải phóng bộ nhớ bên trong chức năng chính.
#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;
}
Đây là cách tôi làm điều đó. Nó cắt chuỗi tại chỗ, do đó, không phải lo lắng về việc sắp xếp lại chuỗi trả về hoặc mất con trỏ vào chuỗi được phân bổ. Nó có thể không phải là câu trả lời ngắn nhất có thể, nhưng nó phải rõ ràng với hầu hết độc giả.
#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;
}