มีวิธีการที่สะอาดและเป็นมาตรฐานโดยเฉพาะในการตัดส่วนที่นำหน้าและช่องว่างต่อท้ายจากสตริงใน C หรือไม่? ฉันจะกลิ้งของตัวเอง แต่ฉันจะคิดว่านี่เป็นปัญหาที่พบบ่อยกับการแก้ปัญหาที่พบบ่อยเท่าเทียมกัน
มีวิธีการที่สะอาดและเป็นมาตรฐานโดยเฉพาะในการตัดส่วนที่นำหน้าและช่องว่างต่อท้ายจากสตริงใน C หรือไม่? ฉันจะกลิ้งของตัวเอง แต่ฉันจะคิดว่านี่เป็นปัญหาที่พบบ่อยกับการแก้ปัญหาที่พบบ่อยเท่าเทียมกัน
คำตอบ:
หากคุณสามารถปรับเปลี่ยนสตริง:
// 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
มิฉะนั้นคุณจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
นี่คือสตริงที่เลื่อนไปยังตำแหน่งแรกของบัฟเฟอร์ของคุณ คุณอาจต้องการพฤติกรรมนี้เพื่อให้หากคุณจัดสรรสตริงแบบไดนามิกคุณยังสามารถทำให้ว่างบนตัวชี้เดียวกันกับที่ตัด () ส่งคืน:
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;
}
ไฟล์ต้นฉบับตัดแต่ง รวบรวมด้วย 'cc -Wall trim.c -o trim'
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;
เพื่อหลีกเลี่ยงความยาวเป็นศูนย์
นี่คือห้องสมุดขนาดเล็ก C ของฉันสำหรับตัดขอบซ้ายขวาทั้งในสถานที่และแยกจากกันและตัดแต่งชุดอักขระที่ระบุ (หรือพื้นที่สีขาวตามค่าเริ่มต้น)
#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) มีไม่มากไปและมันวนซ้ำสตริงเพียงครั้งเดียว บางคนอาจบ่นว่าการเล็มขวาเริ่มทางด้านซ้ายอย่างไรก็ตามไม่จำเป็นต้องใช้สเตนซึ่งเริ่มทางด้านซ้าย (ไม่ทางใดก็ทางหนึ่งคุณต้องไปที่จุดสิ้นสุดของสายอักขระภายนอกเพื่อให้ถูกต้องดังนั้นคุณอาจจะทำงานตามที่คุณไปด้วย) อาจมีข้อโต้แย้งเกี่ยวกับการวางท่อและขนาดแคชและใครรู้ . เนื่องจากโซลูชันทำงานจากซ้ายไปขวาและวนซ้ำเพียงครั้งเดียวจึงสามารถขยายเพื่อทำงานบนสตรีมได้เช่นกัน ข้อ จำกัด : มันไม่ทำงานบนสตริงUnicode
dtab[*d]
ไม่ส่ง*d
ไปunsigned int
ก่อนใช้เป็นดัชนีอาร์เรย์ บนระบบที่มีถ่านที่ลงชื่อแล้วสิ่งนี้จะอ่านข้อมูลdtab[-127]
ซึ่งจะก่อให้เกิดข้อบกพร่องและอาจผิดพลาดได้
dtab[*delim++]
เพราะค่าดัชนีจะต้องโยนไปchar
unsigned char
รหัสอนุมาน char
8 ควรได้รับการประกาศให้เป็นdelim
จะเป็นที่ชัดเจน รหัสทำงานบนสตริงที่เข้ารหัส UTF-8 แต่จะไม่ตัดลำดับระยะห่างที่ไม่ใช่ ASCII const char *
dtab[0xFF & (unsigned int)*d]
dtab[(unsigned char)*d]
นี่คือความพยายามของฉันที่ฟังก์ชั่นการตัดแต่งแบบง่าย แต่ถูกต้อง
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 สตริงจะถูกปรับซ้ายภายในบัฟเฟอร์เพื่อให้คุณสามารถปล่อยตัวชี้เดียวกันได้ ไม่เหมาะสำหรับสตริงขนาดใหญ่เนื่องจากไม่ได้ใช้ 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
เพื่อunsigned char
มิฉะนั้นคุณจะเรียกใช้พฤติกรรมที่ไม่ได้กำหนด
while (isspace((unsigned char) *szWrite)) szWrite++;
จะป้องกันไม่ให้ รหัสยังคัดลอกพื้นที่สีขาวต่อท้ายทั้งหมด
*szWrite = *szRead
เมื่อตัวชี้ไม่เท่ากันจะข้ามการเขียนในกรณีนั้น แต่จากนั้นเราได้เพิ่มการเปรียบเทียบ / สาขาอื่น ด้วย CPU / MMU / BP ที่ทันสมัยฉันไม่รู้เลยว่าการตรวจสอบนั้นจะเป็นการสูญเสียหรือได้รับ ด้วยตัวประมวลผลและสถาปัตยกรรมหน่วยความจำที่ง่ายขึ้นมันถูกกว่าที่จะทำสำเนาและข้ามการเปรียบเทียบ
ดึกมากไปงานปาร์ตี้ ...
โซลูชันการสแกนไปข้างหน้าแบบ Single-pass ที่ไม่มีการย้อนรอย อักขระทุกตัวในสตริงที่มาจะถูกทดสอบสองครั้งครั้งเดียว (ดังนั้นควรเร็วกว่าโซลูชันอื่น ๆ ส่วนใหญ่ที่นี่โดยเฉพาะอย่างยิ่งหากสตริงต้นฉบับมีช่องว่างต่อท้ายจำนวนมาก)
ซึ่งรวมถึงโซลูชันที่สองวิธีหนึ่งที่จะคัดลอกและตัดแต่งสายอักขระของแหล่งที่มาลงในสายปลายทางอื่น ฟังก์ชั่นทั้งสองใช้รหัสเดียวกัน
สตริง (แก้ไขได้) จะถูกย้ายในสถานที่ดังนั้นตัวชี้เดิมไปยังคงไม่เปลี่ยนแปลง
#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()
ฟังก์ชั่นต่าง ๆที่รู้ว่าจะคัดลอกแหล่งที่มาไปยังปลายทางไปข้างหลังและอาจใช้เวลาอาร์กิวเมนต์อาร์กิวเมนต์ความยาวของสตริงเพิ่มเติม
ฉันไม่แน่ใจว่าสิ่งที่คุณพิจารณาว่า "เจ็บปวด"
สาย C ค่อนข้างเจ็บปวด เราสามารถค้นหาตำแหน่งของอักขระที่ไม่ใช่ช่องว่างแรกได้โดยง่าย:
ในขณะที่ (isspace (* p)) p ++;
เราสามารถหาตำแหน่งตัวละครที่ไม่ใช่ช่องว่างสุดท้ายด้วยการเคลื่อนไหวสองอย่างที่คล้ายกัน:
ในขณะที่ (* 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()
ผลตอบแทนที่สามารถเกินช่วงของ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;
UB เมื่อ size_t len;
isspace(in[len - 1])
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 น้อยกว่าช่องว่างหมายความว่า chars ควบคุมจะถูกตัดแต่งด้วย!
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 ก่อนอื่นเราจะพบว่ามีอักขระที่ไม่ว่างเปล่าจำนวนมากในสตริงอินพุต จากนั้นเราจัดสรรอาร์เรย์อักขระที่มีขนาดนั้นและดูแลอักขระที่สิ้นสุดด้วยค่า null เมื่อเราใช้ฟังก์ชั่นนี้เราจำเป็นต้องเพิ่มหน่วยความจำภายในฟังก์ชั่นหลัก
#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;
}