วิธีที่ดีที่สุดในการตัด std :: string คืออะไร?


812

ขณะนี้ฉันใช้รหัสต่อไปนี้เพื่อตัดทั้งหมดstd::stringsในโปรแกรมของฉัน:

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

มันใช้งานได้ดี แต่ฉันสงสัยว่ามีบางกรณีที่อาจล้มเหลวหรือไม่?

แน่นอนว่าคำตอบของทางเลือกที่หรูหราและวิธีการแก้ปัญหาด้านซ้ายก็ยินดีต้อนรับ


549
คำตอบสำหรับคำถามนี้เป็นเครื่องพิสูจน์ว่าห้องสมุดมาตรฐาน C ++ ขาดอย่างไร
Idan K

83
@IdanK และยังไม่มีฟังก์ชั่นนี้ใน C ++ 11
ควอนตัม

44
@IdanK: ยอดเยี่ยมใช่มั้ย! ดูตัวเลือกทั้งหมดที่แข่งขันตอนนี้เรามีที่จำหน่ายของเราปราศจากภาระผูกพันกับความคิดคนเดียวของ " วิธีการที่เราจะต้องทำมัน"!
Lightness Races ในวงโคจร

59
@LightnessRacesinOrbit ฟังก์ชั่นภายในประเภทนั่นคือการตัดสินใจออกแบบและการเพิ่มฟังก์ชั่นการตัดแต่งให้กับสตริงอาจ (อย่างน้อยภายใต้ c ++) ไม่ใช่วิธีที่ดีที่สุดต่อไป - แต่ไม่ได้ให้วิธีมาตรฐานที่จะทำแทนที่จะปล่อยให้ทุกคนหงุดหงิด ปัญหาเล็ก ๆ เช่นเดียวกันซ้ำแล้วซ้ำอีกแน่นอนไม่ได้ช่วยใครเลย
codeling

27
คุณสามารถถามได้ว่าทำไมฟังก์ชั่นการตัดแต่งไม่ได้ถูกสร้างขึ้นในstd::stringคลาสเมื่อมันเป็นฟังก์ชั่นแบบนี้ที่ทำให้ภาษาอื่น ๆ น่าใช้
HelloGoodbye

คำตอบ:


648

แก้ไขตั้งแต่ c ++ 17 บางส่วนของไลบรารีมาตรฐานถูกลบออก โชคดีที่เริ่มต้นด้วย c ++ 11 เรามี lambdas ซึ่งเป็นโซลูชั่นที่เหนือกว่า

#include <algorithm> 
#include <cctype>
#include <locale>

// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
        return !std::isspace(ch);
    }));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
        return !std::isspace(ch);
    }).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}

// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

ขอบคุณhttps://stackoverflow.com/a/44973498/524503สำหรับการนำเสนอโซลูชั่นที่ทันสมัย

คำตอบเดิม:

ฉันมักจะใช้หนึ่งใน 3 เหล่านี้สำหรับความต้องการของฉัน:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start
static inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}

// trim from end
static inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}

// trim from both ends
static inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

พวกเขาค่อนข้างอธิบายตนเองและทำงานได้ดีมาก

แก้ไข : BTW ฉันstd::ptr_funมีที่จะช่วยแก้ความกำกวมstd::isspaceเพราะมีคำจำกัดความที่สองที่รองรับสถานที่ นี่อาจเป็นนักแสดงได้เหมือนกัน แต่ฉันมักจะชอบสิ่งนี้ดีกว่า

แก้ไข : เพื่อที่อยู่ความคิดเห็นบางอย่างเกี่ยวกับการยอมรับพารามิเตอร์โดยการอ้างอิงการปรับเปลี่ยนและกลับมา ฉันเห็นด้วย. การใช้งานที่ฉันต้องการจะเป็นสองชุดหนึ่งสำหรับในสถานที่และหนึ่งที่ทำสำเนา ตัวอย่างที่ดีกว่าคือ:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>

// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}

// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}

// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}

// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

ฉันกำลังรักษาคำตอบดั้งเดิมไว้ด้านบน แต่เพื่อบริบทและเพื่อรักษาคำตอบที่ได้รับคะแนนโหวตสูงยังคงมีอยู่


28
รหัสนี้ล้มเหลวในบางสายระหว่างประเทศ (shift-jis ในกรณีของฉันเก็บไว้ใน std :: string); ฉันลงเอยด้วยการใช้boost::trimเพื่อแก้ปัญหา
Tom

5
ฉันใช้พอยน์เตอร์แทนการอ้างอิงเพื่อให้เข้าใจได้ง่ายขึ้นว่าฟังก์ชั่นเหล่านี้แก้ไขสตริงในสถานที่แทนที่จะสร้างสำเนา
Marco Leogrande

3
โปรดทราบว่าด้วย isspace คุณสามารถรับพฤติกรรมที่ไม่ได้กำหนดด้วยอักขระที่ไม่ใช่ ASCII ได้อย่างง่ายดายstacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
ทำไมถึงคงที่? นี่เป็นที่ที่ต้องการชื่อเนมสเปซหรือไม่?
เทรเวอร์ Hickey

3
@TrevorHickey แน่นอนคุณสามารถใช้เนมสเปซนิรนามแทนได้หากต้องการ
Evan Teran

417

การใช้อัลกอริธึมสตริงของ Boostจะง่ายที่สุด:

#include <boost/algorithm/string.hpp>

std::string str("hello world! ");
boost::trim_right(str);

str"hello world!"อยู่ในขณะนี้ นอกจากนี้ยังมีtrim_leftและtrimที่จดจ้องทั้งสองด้าน


หากคุณเพิ่ม_copyคำต่อท้ายให้กับชื่อฟังก์ชั่นใด ๆ ข้างต้นเช่นtrim_copyฟังก์ชั่นจะส่งคืนสำเนาที่ถูกตัดของสตริงแทนการแก้ไขผ่านการอ้างอิง

หากคุณเพิ่ม_ifคำต่อท้ายให้กับชื่อฟังก์ชั่นใด ๆ ข้างต้นเช่นtrim_copy_ifคุณสามารถตัดแต่งอักขระทั้งหมดที่ทำให้ภาคแสดงของคุณกำหนดเองซึ่งตรงข้ามกับช่องว่าง


7
มันขึ้นอยู่กับสถานที่เกิดเหตุ ภาษาเริ่มต้นของฉัน (VS2005, en) หมายถึงแท็บช่องว่างการขึ้นบรรทัดใหม่ขึ้นบรรทัดใหม่แท็บแนวตั้งและแบบฟอร์มฟีด
MattyT

117
Boost เป็นค้อนขนาดใหญ่สำหรับปัญหาเล็ก ๆ เช่นนี้
Casey Rodarmor

143
@rodarmor: Boost แก้ปัญหาเล็ก ๆ มากมาย มันเป็นค้อนขนาดใหญ่ที่แก้ได้มากมาย
Nicol Bolas

123
Boost เป็นชุดของค้อนขนาดแตกต่างกันในการแก้ปัญหาที่แตกต่างกัน
อิบราฮิม

11
@rodarmor คุณบอกว่าราวกับว่า Boost เป็นหินใหญ่ก้อนเดียวที่ไม่มีสิ่งใดหรือทั้งหมดซึ่งรวมถึงส่วนหัวอันใดอันหนึ่งของมันจะสร้างความเสียหายให้กับสิ่งที่อยู่ในโปรแกรม ซึ่งไม่ใช่กรณีที่ชัดเจน Btw ฉันไม่เคยใช้ Boost เลย fwiw
underscore_d

61

ใช้รหัสต่อไปนี้เพื่อเว้นวรรค (ต่อท้าย) ตัดแต่งด้านขวาและอักขระแท็บจากstd::strings( ideone ):

// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
    str = str.substr( 0, endpos+1 );
    str = str.substr( startpos );
}
else {
    str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}

และเพื่อความสมดุลของสิ่งต่าง ๆ ฉันจะใส่รหัสตัดด้านซ้ายด้วย ( ideone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}

4
สิ่งนี้จะไม่ตรวจจับช่องว่างในรูปแบบอื่น ... ขึ้นบรรทัดใหม่ตัวดึงข้อมูลบรรทัดการคืนสินค้าโดยเฉพาะ
Tom

1
ขวา. คุณต้องปรับแต่งมันสำหรับช่องว่างที่คุณต้องการตัด แอปพลิเคชันของฉันโดยเฉพาะคาดหวังว่าจะมีช่องว่างและแท็บ แต่คุณสามารถเพิ่ม \ n \ r เพื่อรับสิ่งอื่น
Bill the Lizard

5
str.substr(...).swap(str)จะดีกว่า. บันทึกการบ้าน
updogliu

4
@updogliu จะไม่ใช้การมอบหมายย้ายbasic_string& operator= (basic_string&& str) noexcept;หรือไม่
nurettin

8
คำตอบนี้ไม่ได้แก้ไขสตริงที่มีช่องว่างทั้งหมด ซึ่งเป็นความล้มเหลว
Tom Andersen

56

สิ่งที่คุณกำลังทำอยู่นั้นดีและมีประสิทธิภาพ ฉันใช้วิธีเดียวกันมานานแล้วและยังไม่พบวิธีที่เร็วกว่านี้:

const char* ws = " \t\n\r\f\v";

// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}

// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}

// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
    return ltrim(rtrim(s, t), t);
}

โดยการจัดหาอักขระที่จะตัดแต่งคุณมีความยืดหยุ่นในการตัดอักขระที่ไม่ใช่ช่องว่างและประสิทธิภาพในการตัดแต่งเฉพาะอักขระที่คุณต้องการตัดแต่ง


หากคุณเปลี่ยนคำสั่งซื้อtrimเช่นทำให้มันrtrim(ltrim(s, t), t)มีประสิทธิภาพมากขึ้นเล็กน้อย
CITBL

1
@CITBL ฟังก์ชั่นด้านในจะดำเนินการก่อนดังนั้นวิธีที่คุณจะตัดแต่งจากด้านซ้ายก่อนที่จะตัดจากด้านขวา ฉันคิดว่ามันจะมีประสิทธิภาพน้อยลงใช่มั้ย
Galik

เผง ความผิดพลาดของฉัน
CITBL

ถ้าคุณใช้ basic_string และเทมเพลตบน CharT คุณสามารถทำสิ่งนี้กับสตริงทั้งหมดเพียงแค่ใช้ตัวแปรเทมเพลตสำหรับช่องว่างเพื่อให้คุณใช้มันเช่น ws <CharT> ทางเทคนิค ณ จุดนั้นคุณสามารถทำให้พร้อมสำหรับ c ++ 20 และทำเครื่องหมาย constexpr ด้วยเช่นกันซึ่งหมายถึงแบบอินไลน์
Beached

@Beached แน่นอน ค่อนข้างซับซ้อนที่จะตอบคำถามที่นี่แม้ว่า ฉันได้เขียนฟังก์ชั่นเทมเพลตสำหรับสิ่งนี้และมีส่วนเกี่ยวข้องอย่างแน่นอน ฉันได้ลองวิธีการต่าง ๆ มากมายและยังไม่แน่ใจว่าวิธีใดดีที่สุด
Galik

55

สายไปงานเลี้ยง แต่ไม่เป็นไร ตอนนี้ C ++ 11 อยู่ที่นี่เรามีแลมบ์ดาและตัวแปรอัตโนมัติ ดังนั้นเวอร์ชั่นของฉันซึ่งจัดการสตริงว่างและช่องว่างทั้งหมดก็คือ:

#include <cctype>
#include <string>
#include <algorithm>

inline std::string trim(const std::string &s)
{
   auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
   return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}

เราสามารถสร้าง iterator แบบย้อนกลับจากwsfrontและใช้สิ่งนั้นเป็นเงื่อนไขการเลิกจ้างในครั้งที่สองfind_if_notแต่มันมีประโยชน์เฉพาะในกรณีของสตริงที่มีช่องว่างทั้งหมดและ gcc 4.8 อย่างน้อยไม่ฉลาดพอที่จะสรุปประเภทของ iterator ย้อนกลับ ( std::string::const_reverse_iterator) autoด้วย ฉันไม่รู้ว่าการสร้าง iterator แบบย้อนกลับนั้นแพงแค่ไหนดังนั้น YMMV ที่นี่ ด้วยการเปลี่ยนแปลงนี้โค้ดจะมีลักษณะดังนี้:

inline std::string trim(const std::string &s)
{
   auto  wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}

9
ดี +1 จากฉัน C ++ 11 ที่แย่เกินไปไม่แนะนำให้ตัด () ลงใน std :: string และทำให้ทุกคนง่ายขึ้นสำหรับทุกคน
Milan Babuškov

3
ฉันมักจะต้องการเรียกใช้ฟังก์ชันหนึ่งที่จะตัดสตริงแทนการดำเนินการนั้น
linquize

22
สำหรับสิ่งที่คุ้มค่าไม่จำเป็นต้องใช้แลมบ์ดานั้น คุณสามารถผ่านstd::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
+1 สำหรับคำตอบเดียวกับการนำไปใช้ที่ทำได้เพียงหนึ่งสำเนาสตริง O (N)
Alexei Averchenko

4
คอมไพเลอร์ @vmrob ไม่จำเป็นว่าฉลาด การทำในสิ่งที่คุณพูดนั้นไม่ชัดเจน:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
จอห์นเบเกอร์

42

ลองสิ่งนี้มันใช้งานได้สำหรับฉัน

inline std::string trim(std::string& str)
{
    str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
    str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
    return str;
}

12
หากสตริงของคุณไม่มีช่องว่าง suffixing สิ่งนี้จะลบเริ่มต้นที่ npos + 1 == 0 และคุณจะลบสตริงทั้งหมด
mhsmith

3
@rgove โปรดอธิบาย str.find_last_not_of(x)ส่งคืนตำแหน่งของอักขระตัวแรกไม่เท่ากับ x มันจะส่งคืน npos หากไม่มีตัวอักษรไม่ตรงกับ x ในตัวอย่างถ้าไม่มีช่องว่าง suffixing มันจะคืนค่าเทียบเท่าstr.length() - 1โดยให้ผลเป็นหลักstr.erase((str.length() - 1) + 1).นั่นคือเว้นแต่ว่าฉันเข้าใจผิดอย่างมหันต์
เทรวิส

5
นี้ควรกลับ std :: string & เพื่อหลีกเลี่ยงการเรียกใช้ตัวสร้างการคัดลอกโดยไม่จำเป็น
heksesang

7
ฉันสับสนว่าเหตุใดจึงส่งคืนสำเนาหลังจากแก้ไขพารามิเตอร์ส่งคืน
Galik

3
@MiloDC ความสับสนของฉันคือเหตุผลที่ส่งคืนสำเนาแทนที่จะเป็นข้อมูลอ้างอิง std::string&มันทำให้รู้สึกมากขึ้นกับผมที่จะกลับมา
Galik

25

ฉันชอบโซลูชันของ tzaman ปัญหาเดียวของมันคือมันไม่ได้ตัดแต่งสตริงที่มีช่องว่างเท่านั้น

หากต้องการแก้ไขข้อบกพร่อง 1 ข้อให้เพิ่ม str.clear () ระหว่างบรรทัดที่กันจอน 2 เส้น

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;

ดี :) ปัญหาเกี่ยวกับการแก้ปัญหาทั้งสองอย่างของเราคือพวกมันจะตัดปลายทั้งสองด้าน ไม่สามารถสร้างltrimหรือเป็นrtrimแบบนี้
tzaman

44
ดี แต่ไม่สามารถจัดการกับสตริงด้วยช่องว่างภายใน เช่น trim (abc def ") -> abc เหลือเพียง abc เท่านั้น
liheyuan

ทางออกที่ดีถ้าคุณรู้ว่าไม่มีพื้นที่ว่างภายใน!
Elliot Gorokhovsky

นี้เป็นสิ่งที่ดีและง่าย std::stringstreamแต่ก็ยังค่อนข้างช้าเป็นสตริงที่ได้รับการคัดลอกลงและออกจาก
Galik

23

http://ideone.com/nFVtEo

std::string trim(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it))
        it++;

    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit))
        rit++;

    return std::string(it, rit.base());
}

1
ทางออกที่สง่างามสำหรับการตัดแต่งพื้นที่พื้นฐานในที่สุด ... :)
jave.web

วิธีการทำงาน: นี่เป็นวิธีการคัดลอกเหมือน - พบตำแหน่งของอักขระตัวแรกที่ไม่ใช่ช่องว่าง ( it) และย้อนกลับ: ตำแหน่งของอักขระหลังจากที่มีช่องว่างเท่านั้น ( rit) - หลังจากนั้นจะส่งกลับสตริงที่สร้างขึ้นใหม่ == สำเนาของส่วนหนึ่งของสตริงต้นฉบับ - ส่วนหนึ่งขึ้นอยู่กับตัววนซ้ำเหล่านั้น ...
jave.web

ขอบคุณทำงานให้ฉัน: std: string s = "โอ้ noez: space \ r \ n"; std :: string clean = trim (s);
Alexx Roche

15

ในกรณีของสตริงที่ว่างเปล่าโค้ดของคุณจะถือว่าการเพิ่ม 1 เพื่อstring::nposให้string::nposเป็น0 เป็นประเภทstring::size_typeซึ่งไม่ได้ลงนาม ดังนั้นคุณต้องพึ่งพาพฤติกรรมการล้นของการเพิ่ม


23
คุณกำลังใช้ถ้อยคำราวกับว่ามันแย่ พฤติกรรมล้นล้นที่ลงนามแล้วนั้นไม่ดี
MSalters

2
เพิ่ม1ที่จะstd::string::npos ต้องให้เป็นไปตาม0 C++ Standardดังนั้นจึงเป็นข้อสันนิษฐานที่ดีที่สามารถพึ่งพาได้อย่างแน่นอน
Galik

13

แฮกออกจากCplusplus.com

std::string choppa(const std::string &t, const std::string &ws)
{
    std::string str = t;
    size_t found;
    found = str.find_last_not_of(ws);
    if (found != std::string::npos)
        str.erase(found+1);
    else
        str.clear();            // str is all whitespace

    return str;
}

ใช้งานได้กับเคสว่างเช่นกัน :-)


4
นี่เป็นเพียงrtrimไม่ใช่ltrim
ub3rst4r

1
คุณสนใจที่จะใช้ find_first_not_of บ้างไหม? มันค่อนข้างง่ายที่จะแก้ไข
Abhinav Gauniyal

13

ด้วย C ++ 17 คุณสามารถใช้basic_string_view :: remove_prefixและbasic_string_view :: remove_suffix :

std::string_view trim(std::string_view s)
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));

    return s;
}

ทางเลือกที่ดี:

std::string_view ltrim(std::string_view s)
{
    s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
         [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view rtrim(std::string_view s)
{
    s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
        [](int c) {return !std::isspace(c);})));

    return s;
}

std::string_view trim(std::string_view s)
{
    return ltrim(rtrim(s));
}

ฉันไม่แน่ใจว่าคุณกำลังทดสอบอะไร แต่ในตัวอย่างstd :: find_first_not_of ของคุณจะส่งคืนstd :: string :: nposและstd :: string_view :: sizeจะกลับมา 4 ขนาดต่ำสุดที่เห็นได้ชัดคือจำนวนองค์ประกอบที่จะเป็น ลบออกโดยมาตรฐาน :: string_view :: remove_prefix ทั้ง gcc 9.2 และ clang 9.0 จัดการสิ่งนี้อย่างถูกต้อง: godbolt.org/z/DcZbFH
Phidelux

1
ขอบคุณ! ดูดีกับผม.
Contango

11

ทางออกของฉันอยู่บนพื้นฐานของคำตอบโดย @ Bill จิ้งจก

โปรดทราบว่าฟังก์ชั่นเหล่านี้จะส่งกลับสตริงที่ว่างเปล่าถ้าสตริงอินพุตมีอะไรนอกจากช่องว่าง

const std::string StringUtils::WHITESPACE = " \n\r\t";

std::string StringUtils::Trim(const std::string& s)
{
    return TrimRight(TrimLeft(s));
}

std::string StringUtils::TrimLeft(const std::string& s)
{
    size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
    return (startpos == std::string::npos) ? "" : s.substr(startpos);
}

std::string StringUtils::TrimRight(const std::string& s)
{
    size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
    return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}

9

คำตอบของฉันคือการปรับปรุงตามคำตอบที่ดีที่สุดสำหรับโพสต์นี้ที่ตัดแต่งอักขระควบคุมเช่นเดียวกับช่องว่าง (0-32 และ 127 บนตาราง ASCII )

std::isgraphพิจารณาว่าตัวละครมีการแสดงกราฟิกดังนั้นคุณสามารถใช้สิ่งนี้เพื่อแก้ไขคำตอบของอีวานที่จะลบตัวละครใด ๆ ที่ไม่มีการแสดงกราฟิกจากด้านใดด้านหนึ่งของสตริง ผลลัพธ์ที่ได้คือทางออกที่ยอดเยี่ยมกว่า:

#include <algorithm>
#include <functional>
#include <string>

/**
 * @brief Left Trim
 *
 * Trims whitespace from the left end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& ltrim(std::string& s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
    std::ptr_fun<int, int>(std::isgraph)));
  return s;
}

/**
 * @brief Right Trim
 *
 * Trims whitespace from the right end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& rtrim(std::string& s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
    std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
  return s;
}

/**
 * @brief Trim
 *
 * Trims whitespace from both ends of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& trim(std::string& s) {
  return ltrim(rtrim(s));
}

หมายเหตุ: หรือคุณควรจะสามารถใช้งานได้std::iswgraphหากคุณต้องการการรองรับอักขระที่มีตัวพิมพ์ใหญ่ แต่คุณจะต้องแก้ไขรหัสนี้เพื่อเปิดใช้งานการstd::wstringปรับแต่งซึ่งเป็นสิ่งที่ฉันยังไม่ได้ทดสอบ (ดูหน้าอ้างอิงสำหรับstd::basic_stringการสำรวจตัวเลือกนี้) .


3
std :: ptr_fun เลิกใช้แล้ว
johnbakers

8

ด้วย C ++ 11 ยังมีโมดูลนิพจน์ทั่วไปซึ่งแน่นอนว่าสามารถใช้ในการตัดแต่งช่องว่างนำหน้าหรือต่อท้าย

อาจจะเป็นสิ่งนี้:

std::string ltrim(const std::string& s)
{
    static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
    return std::regex_replace(s, lws, "");
}

std::string rtrim(const std::string& s)
{
    static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
    return std::regex_replace(s, tws, "");
}

std::string trim(const std::string& s)
{
    return ltrim(rtrim(s));
}

8

นี่คือสิ่งที่ฉันใช้ เพียงแค่เอาที่ว่างออกจากด้านหน้าจากนั้นถ้ามีอะไรเหลือให้ทำแบบเดียวกันจากด้านหลัง

void trim(string& s) {
    while(s.compare(0,1," ")==0)
        s.erase(s.begin()); // remove leading whitespaces
    while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
        s.erase(s.end()-1); // remove trailing whitespaces
}

8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   

2
มันจะมีประสิทธิภาพมากขึ้นเล็กน้อยถ้าคุณทำสิ่งที่อยู่ในลำดับที่ตรงกันข้ามและตัดแต่งจากด้านขวาก่อนที่จะเรียกการเปลี่ยนแปลงโดยการตัดแต่งด้านซ้าย
Galik

7

สำหรับสิ่งที่คุ้มค่านี่คือการปรับแต่งให้มีประสิทธิภาพ มันเร็วกว่าการตัดแต่งแบบอื่น ๆ ที่ฉันเคยเห็น แทนที่จะใช้ตัววนซ้ำและ std :: find มันจะใช้สตริง c และดัชนีดิบ มันปรับกรณีพิเศษต่อไปนี้: สตริง 0 ขนาด (ไม่ทำอะไรเลย), สตริงที่ไม่มีช่องว่างเพื่อตัดแต่ง (ไม่ทำอะไรเลย), สตริงที่มีช่องว่างต่อท้ายเพื่อตัดแต่ง (เพิ่งปรับขนาดสตริง), สตริงที่ช่องว่างทั้งหมด . และสุดท้ายในกรณีที่เลวร้ายที่สุด (สตริงที่มีช่องว่างนำหน้า) จะเป็นการดีที่สุดที่จะสร้างโครงสร้างการคัดลอกที่มีประสิทธิภาพโดยทำการคัดลอกเพียง 1 ครั้งจากนั้นย้ายสำเนานั้นมาแทนที่สตริงเดิม

void TrimString(std::string & str)
{ 
    if(str.empty())
        return;

    const auto pStr = str.c_str();

    size_t front = 0;
    while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}

    size_t back = str.length();
    while(back > front && std::isspace(int(pStr[back-1]))) {--back;}

    if(0 == front)
    {
        if(back < str.length())
        {
            str.resize(back - front);
        }
    }
    else if(back <= front)
    {
        str.clear();
    }
    else
    {
        str = std::move(std::string(str.begin()+front, str.begin()+back));
    }
}

@bmgda บางทีในทางทฤษฎีแล้วรุ่นที่เร็วที่สุดอาจมีลายเซ็นนี้: extern "C" เป็นโมฆะ string_trim (ถ่าน ** เริ่มต้น, ถ่าน ** สิ้นสุด _) ... จับดริฟท์ของฉันเหรอ?

6

วิธีที่สง่างามในการทำเช่นนั้นอาจเป็นได้

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

และมีการใช้ฟังก์ชั่นการสนับสนุนเป็น:

std::string & ltrim(std::string & str)
{
  auto it =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it);
  return str;   
}

std::string & rtrim(std::string & str)
{
  auto it =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it.base() , str.end() );
  return str;   
}

และเมื่อคุณทำสิ่งเหล่านี้เรียบร้อยแล้วคุณสามารถเขียนสิ่งนี้ได้เช่นกัน:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}

6

การปรับใช้ Trim C ++ 11:

static void trim(std::string &s) {
     s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
     s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}

5

ฉันเดาว่าถ้าคุณเริ่มขอให้ "วิธีที่ดีที่สุด" ในการตัดแต่งสตริงฉันจะบอกว่าการใช้งานที่ดีจะเป็นสิ่งที่:

  1. ไม่จัดสรรสตริงชั่วคราว
  2. มีการโอเวอร์โหลดสำหรับการตัดแต่งแบบแทนที่และการคัดลอก
  3. สามารถปรับแต่งได้ง่ายเพื่อยอมรับลำดับการตรวจสอบ / ตรรกะที่แตกต่างกัน

เห็นได้ชัดว่ามีหลายวิธีมากเกินไปที่จะเข้าถึงสิ่งนี้และแน่นอนขึ้นอยู่กับสิ่งที่คุณต้องการ อย่างไรก็ตามไลบรารีมาตรฐาน C ยังคงมีฟังก์ชั่นที่มีประโยชน์มากใน <string.h> เช่น memchr มีเหตุผลที่ C ยังคงถูกมองว่าเป็นภาษาที่ดีที่สุดสำหรับ IO - stdlib นั้นมีประสิทธิภาพบริสุทธิ์

inline const char* trim_start(const char* str)
{
    while (memchr(" \t\n\r", *str, 4))  ++str;
    return str;
}
inline const char* trim_end(const char* end)
{
    while (memchr(" \t\n\r", end[-1], 4)) --end;
    return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
    return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
    str.assign(trim_start(str.c_str()),
        trim_end(str.c_str() + str.length()));
}

int main()
{
    char str [] = "\t \nhello\r \t \n";

    string trimmed = trim(str, strlen(str));
    cout << "'" << trimmed << "'" << endl;

    system("pause");
    return 0;
}

3

ฉันไม่แน่ใจว่าสภาพแวดล้อมของคุณเหมือนกันหรือไม่ แต่ในกรณีของฉันสตริงข้อความว่างเปล่าจะทำให้โปรแกรมยกเลิก ฉันจะตัดการโทรที่ลบด้วย if (! s.empty ()) หรือใช้ Boost ตามที่ระบุไว้แล้ว


3

นี่คือสิ่งที่ฉันมาด้วย:

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

การแยกสตรีมจะขจัดช่องว่างออกโดยอัตโนมัติดังนั้นจึงสามารถใช้งานได้เหมือนเครื่องราง
สวยสะอาดและหรูหราเกินไปถ้าฉันพูดอย่างนั้น ;)


15
อืม; นี่ถือว่าเป็นสตริงที่ไม่มีช่องว่างภายใน (เช่นช่องว่าง) OP กล่าวเพียงว่าเขาต้องการตัดช่องว่างทางซ้ายหรือขวา
SuperElectric

3

บริจาควิธีแก้ปัญหาเสียงดัง trimค่าเริ่มต้นในการสร้างสตริงใหม่และกลับมาแก้ไขในขณะที่trim_in_placeปรับเปลี่ยนสตริงที่ส่งผ่านไป trimฟังก์ชั่นรองรับ C ++ 11 ความหมายย้าย

#include <string>

// modifies input string, returns input

std::string& trim_left_in_place(std::string& str) {
    size_t i = 0;
    while(i < str.size() && isspace(str[i])) { ++i; };
    return str.erase(0, i);
}

std::string& trim_right_in_place(std::string& str) {
    size_t i = str.size();
    while(i > 0 && isspace(str[i - 1])) { --i; };
    return str.erase(i, str.size());
}

std::string& trim_in_place(std::string& str) {
    return trim_left_in_place(trim_right_in_place(str));
}

// returns newly created strings

std::string trim_right(std::string str) {
    return trim_right_in_place(str);
}

std::string trim_left(std::string str) {
    return trim_left_in_place(str);
}

std::string trim(std::string str) {
    return trim_left_in_place(trim_right_in_place(str));
}

#include <cassert>

int main() {

    std::string s1(" \t\r\n  ");
    std::string s2("  \r\nc");
    std::string s3("c \t");
    std::string s4("  \rc ");

    assert(trim(s1) == "");
    assert(trim(s2) == "c");
    assert(trim(s3) == "c");
    assert(trim(s4) == "c");

    assert(s1 == " \t\r\n  ");
    assert(s2 == "  \r\nc");
    assert(s3 == "c \t");
    assert(s4 == "  \rc ");

    assert(trim_in_place(s1) == "");
    assert(trim_in_place(s2) == "c");
    assert(trim_in_place(s3) == "c");
    assert(trim_in_place(s4) == "c");

    assert(s1 == "");
    assert(s2 == "c");
    assert(s3 == "c");
    assert(s4 == "c");  
}

3

ซึ่งสามารถทำได้มากขึ้นเพียงแค่ใน C ++ 11 เนื่องจากการเพิ่มขึ้นของและback()pop_back()

while ( !s.empty() && isspace(s.back()) ) s.pop_back();

วิธีการที่แนะนำโดย OP ไม่เลวเช่นกัน - เพียงเล็กน้อยยากที่จะติดตาม
สูงศักดิ์

3

นี่คือรุ่นของฉัน:

size_t beg = s.find_first_not_of(" \r\n");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);

คุณขาดอักขระตัวสุดท้าย +1 ในความยาวแก้ปัญหานี้
galinette

2

วิธีการข้างต้นนั้นยอดเยี่ยม แต่บางครั้งคุณต้องการใช้การรวมกันของฟังก์ชั่นสำหรับสิ่งที่รูทีนของคุณคิดว่าเป็นช่องว่าง ในกรณีนี้การใช้ functors เพื่อรวมการดำเนินการอาจทำให้เกิดความยุ่งเหยิงดังนั้นฉันจึงชอบการวนซ้ำแบบเรียบง่ายที่ฉันสามารถปรับเปลี่ยนได้สำหรับการตัดแต่ง นี่คือฟังก์ชั่นการปรับแต่งการตัดแต่งเล็กน้อยที่คัดลอกมาจากเวอร์ชั่น C ที่นี่บน SO ในตัวอย่างนี้ฉันตัดแต่งอักขระที่ไม่ใช่ตัวอักษรและตัวเลข

string trim(char const *str)
{
  // Trim leading non-letters
  while(!isalnum(*str)) str++;

  // Trim trailing non-letters
  end = str + strlen(str) - 1;
  while(end > str && !isalnum(*end)) end--;

  return string(str, end+1);
}

2

นี่คือการดำเนินการตรงไปตรงมา สำหรับการดำเนินการอย่างง่ายคุณอาจไม่ควรใช้โครงสร้างพิเศษใด ๆ ฟังก์ชั่น isspace แบบบิวด์อิน () จะดูแลอักขระสีขาวหลายรูปแบบดังนั้นเราควรใช้ประโยชน์จากมัน คุณต้องพิจารณากรณีพิเศษที่สตริงว่างหรือเพียงช่องว่าง การตัดขอบซ้ายหรือขวาอาจได้มาจากรหัสต่อไปนี้

string trimSpace(const string &str) {
   if (str.empty()) return str;
   string::size_type i,j;
   i=0;
   while (i<str.size() && isspace(str[i])) ++i;
   if (i == str.size())
      return string(); // empty string
   j = str.size() - 1;
   //while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
   while (isspace(str[j])) --j
   return str.substr(i, j-i+1);
}

2

นี่เป็นวิธีแก้ปัญหาที่เข้าใจง่ายสำหรับผู้เริ่มต้นที่ไม่คุ้นเคยกับการเขียนstd::ทุกที่และยังไม่คุ้นเคยกับ - ความถูกconstต้อง, iterators, STL algorithms, ฯลฯ ...

#include <string>
#include <cctype> // for isspace
using namespace std;


// Left trim the given string ("  hello!  " --> "hello!  ")
string left_trim(string str) {
    int numStartSpaces = 0;
    for (int i = 0; i < str.length(); i++) {
        if (!isspace(str[i])) break;
        numStartSpaces++;
    }
    return str.substr(numStartSpaces);
}

// Right trim the given string ("  hello!  " --> "  hello!")
string right_trim(string str) {
    int numEndSpaces = 0;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (!isspace(str[i])) break;
        numEndSpaces++;
    }
    return str.substr(0, str.length() - numEndSpaces);
}

// Left and right trim the given string ("  hello!  " --> "hello!")
string trim(string str) {
    return right_trim(left_trim(str));
}

หวังว่ามันจะช่วย ...


1

รุ่นนี้ย่อส่วนภายในของช่องว่างและไม่ใช่ตัวอักษร:

static inline std::string &trimAll(std::string &s)
{   
    if(s.size() == 0)
    {
        return s;
    }

    int val = 0;
    for (int cur = 0; cur < s.size(); cur++)
    {
        if(s[cur] != ' ' && std::isalnum(s[cur]))
        {
            s[val] = s[cur];
            val++;
        }
    }
    s.resize(val);
    return s;
}

1

อีกตัวเลือกหนึ่ง - ลบอักขระหนึ่งตัวหรือมากกว่าจากปลายทั้งสอง

string strip(const string& s, const string& chars=" ") {
    size_t begin = 0;
    size_t end = s.size()-1;
    for(; begin < s.size(); begin++)
        if(chars.find_first_of(s[begin]) == string::npos)
            break;
    for(; end > begin; end--)
        if(chars.find_first_of(s[end]) == string::npos)
            break;
    return s.substr(begin, end-begin+1);
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.