Cách tốt nhất để cắt std :: chuỗi là gì?


812

Tôi hiện đang sử dụng mã sau đây để cắt đúng tất cả các std::stringschương trình của mình:

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

Nó hoạt động tốt, nhưng tôi tự hỏi nếu có một số trường hợp kết thúc mà nó có thể thất bại?

Tất nhiên, câu trả lời với các lựa chọn thay thế thanh lịch và giải pháp cắt bên trái đều được chào đón.


549
Các câu trả lời cho câu hỏi này là một minh chứng cho việc thiếu thư viện chuẩn C ++.
Idan K

83
@IdanK Và nó vẫn không có chức năng này trong C ++ 11.
lượng tử

44
@IdanK: Tuyệt, không phải thế! Nhìn vào tất cả các tùy chọn cạnh tranh bây giờ chúng tôi có lúc xử lý của chúng tôi, không bị cản trở bởi ý tưởng một người duy nhất của " những cách mà chúng ta phải làm điều đó"!
Các cuộc đua nhẹ nhàng trong quỹ đạo

59
@LightnessRacesinOrbit chức năng trong một loại, đó là quyết định thiết kế và thêm chức năng cắt vào chuỗi có thể (ít nhất là theo c ++) dù sao đi nữa - nhưng không cung cấp bất kỳ cách tiêu chuẩn nào để thực hiện, thay vào đó, hãy để mọi người băn khoăn những vấn đề nhỏ như vậy lặp đi lặp lại, chắc chắn cũng không giúp được ai
mã hóa

27
Bạn có thể đặt câu hỏi tại sao các hàm cắt xén không được tích hợp vào std::stringlớp, khi đó là các hàm như thế này làm cho các ngôn ngữ khác trở nên rất hay để sử dụng (ví dụ Python).
HelloGoodbye

Câu trả lời:


648

EDIT Kể từ c ++ 17, một số phần của thư viện chuẩn đã bị xóa. May mắn thay, bắt đầu với c ++ 11, chúng tôi có lambdas là một giải pháp ưu việt.

#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;
}

Cảm ơn https://stackoverflow.com/a/44973498/524503 đã đưa ra giải pháp hiện đại.

Câu trả lời gốc:

Tôi có xu hướng sử dụng một trong 3 điều này cho nhu cầu cắt tỉa của mình:

#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));
}

Họ khá tự giải thích và làm việc rất tốt.

EDIT : BTW, tôi có std::ptr_funtrong đó để giúp định hướng std::isspacebởi vì thực sự có một định nghĩa thứ hai hỗ trợ các địa phương. Đây có thể là một diễn viên giống nhau, nhưng tôi có xu hướng thích điều này tốt hơn.

EDIT : Để giải quyết một số ý kiến ​​về việc chấp nhận một tham số bằng cách tham chiếu, sửa đổi và trả lại nó. Tôi đồng ý. Một triển khai mà tôi có thể thích sẽ là hai bộ hàm, một bộ tại chỗ và một bộ tạo ra một bản sao. Một tập hợp các ví dụ tốt hơn sẽ là:

#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;
}

Tôi đang giữ câu trả lời ban đầu ở trên mặc dù cho bối cảnh và vì lợi ích của việc giữ câu trả lời được bình chọn cao vẫn có sẵn.


28
Mã này đã thất bại trên một số chuỗi quốc tế (shift-jis trong trường hợp của tôi, được lưu trữ trong chuỗi std ::); Tôi đã kết thúc bằng cách sử dụng boost::trimđể giải quyết vấn đề.
Tom

5
Tôi sử dụng các con trỏ thay vì các tham chiếu, để từ điểm gọi dễ hiểu hơn nhiều, các hàm này sẽ chỉnh sửa chuỗi tại chỗ, thay vì tạo một bản sao.
Marco Leogrande

3
Lưu ý rằng với isspace bạn có thể dễ dàng nhận được hành vi undefined với các ký tự ASCII stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
Tại sao tĩnh? Đây có phải là nơi một không gian tên ẩn danh sẽ được ưa thích?
Trevor Hickey

3
@TrevorHickey, chắc chắn, bạn có thể sử dụng một không gian tên ẩn danh thay thế nếu bạn thích.
Evan Teran

417

Sử dụng thuật toán chuỗi của Boost sẽ dễ dàng nhất:

#include <boost/algorithm/string.hpp>

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

str Hiện tại là "hello world!" . Cũng có trim_lefttrim, mà cắt cả hai bên.


Nếu bạn thêm _copyhậu tố vào bất kỳ tên hàm nào ở trên trim_copy, ví dụ , hàm sẽ trả về một bản sao được cắt xén của chuỗi thay vì sửa đổi nó thông qua một tham chiếu.

Nếu bạn thêm _ifhậu tố vào bất kỳ tên hàm nào ở trên trim_copy_if, ví dụ: bạn có thể cắt tất cả các ký tự thỏa mãn vị từ tùy chỉnh của mình, trái ngược với chỉ các khoảng trắng.


7
Nó phụ thuộc vào địa phương. Ngôn ngữ mặc định của tôi (VS2005, en) có nghĩa là các tab, dấu cách, trả về vận chuyển, dòng mới, tab dọc và nguồn cấp dữ liệu biểu mẫu được cắt bớt.
MattyT

117
Boost là một cái búa lớn cho một vấn đề nhỏ như vậy.
Casey Rodarmor

143
@rodarmor: Boost giải quyết nhiều vấn đề nhỏ. Đó là một cái búa lớn giải quyết rất nhiều.
Nicol Bolas

123
Boost là một bộ búa có nhiều kích cỡ khác nhau giải quyết nhiều vấn đề khác nhau.
Ibrahim

11
@rodarmor Bạn nói rằng như thể Boost là một khối nguyên khối hoàn toàn hoặc không có gì, trong đó bao gồm một trong những tiêu đề của nó bằng cách nào đó gây ra toàn bộ điều trên chương trình của một người. Mà rõ ràng không phải là trường hợp. Btw, tôi chưa bao giờ sử dụng Boost, fwiw.
gạch dưới

61

Sử dụng mã sau đây để cắt đúng (dấu) khoảng trắng và ký tự tab từ 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));
}

Và chỉ để cân bằng mọi thứ, tôi cũng sẽ bao gồm mã cắt bên trái ( ideone ):

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

4
Điều này sẽ không phát hiện các hình thức khác của khoảng trắng ... đặc biệt là dòng mới, nguồn cấp dữ liệu, đặc biệt là vận chuyển trở lại.
Tom

1
Đúng. Bạn phải tùy chỉnh nó cho khoảng trắng bạn muốn cắt. Ứng dụng cụ thể của tôi chỉ mong đợi các khoảng trắng và tab, nhưng bạn có thể thêm \ n \ r để bắt những cái khác.
Bill Lizard

5
str.substr(...).swap(str)tốt hơn. Lưu một bài tập.
updogliu

4
@updogliu Nó không sử dụng chuyển nhượng basic_string& operator= (basic_string&& str) noexcept;?
Nurettin

8
Câu trả lời này không làm thay đổi chuỗi là TẤT CẢ không gian. Đó là một thất bại.
Tom Andersen

56

Những gì bạn đang làm là tốt và mạnh mẽ. Tôi đã sử dụng cùng một phương pháp trong một thời gian dài và tôi vẫn chưa tìm thấy một phương pháp nhanh hơn:

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);
}

Bằng cách cung cấp các ký tự được cắt bớt, bạn có thể linh hoạt cắt bớt các ký tự không phải khoảng trắng và hiệu quả để chỉ cắt các ký tự bạn muốn cắt bớt.


nếu bạn thay đổi thứ tự trim, nghĩa là làm cho nó rtrim(ltrim(s, t), t)hiệu quả hơn một chút
CITBL

1
@CITBL Chức năng bên trong được thực hiện trước tiên theo cách của bạn, nó sẽ cắt từ bên trái trước khi cắt từ bên phải. Tôi nghĩ rằng nó sẽ ít hiệu quả hơn phải không?
Galik

Chính xác. Sai lầm của tôi
CITBL

nếu bạn sử dụng basic_ chuỗi và mẫu trên CharT, bạn có thể thực hiện việc này cho tất cả các chuỗi, chỉ cần sử dụng biến mẫu cho khoảng trắng để bạn sử dụng nó như ws <CharT>. về mặt kỹ thuật tại thời điểm đó, bạn có thể làm cho nó sẵn sàng cho c ++ 20 và đánh dấu nó là chính xác vì điều này ngụ ý nội tuyến
Beached

@Beached Thật vậy. Một chút phức tạp để đưa vào một câu trả lời ở đây mặc dù. Tôi đã viết các hàm mẫu cho việc này và nó chắc chắn có liên quan. Tôi đã thử một loạt các cách tiếp cận khác nhau và vẫn không chắc chắn cách nào là tốt nhất.
Galik

55

Bit đến bữa tiệc muộn, nhưng đừng bận tâm. Bây giờ C ++ 11 đã có, chúng ta có các biến lambdas và auto. Vì vậy, phiên bản của tôi, cũng xử lý tất cả các khoảng trắng và chuỗi rỗng, là:

#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));
}

Chúng ta có thể tạo một trình vòng lặp ngược từ wsfrontvà sử dụng điều kiện đó như là điều kiện kết thúc trong lần thứ hai find_if_notnhưng điều đó chỉ hữu ích trong trường hợp chuỗi toàn khoảng trắng và ít nhất gcc 4.8 không đủ thông minh để suy ra kiểu trình vòng lặp ngược ( std::string::const_reverse_iterator) với auto. Tôi không biết việc xây dựng một trình lặp ngược đắt tiền như thế nào, vì vậy YMMV ở đây. Với sự thay đổi này, mã trông như thế này:

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
Đẹp. +1 từ tôi. Quá tệ C ++ 11 đã không đưa trim () vào chuỗi std :: và làm cho mọi người dễ dàng hơn.
Milan Babuškov

3
Tôi luôn muốn một hàm gọi để cắt chuỗi, thay vì thực hiện nó
nối tiếp

22
Đối với những gì nó có giá trị, không cần phải sử dụng lambda đó. Bạn chỉ có thể vượt qua std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
+1 có lẽ là câu trả lời duy nhất với việc triển khai chỉ thực hiện một bản sao chuỗi O (N).
Alexei Averchenko

4
Trình biên dịch @vmrob không nhất thiết phải thông minh. làm những gì bạn nói là mơ hồ:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers

42

Hãy thử điều này, nó làm việc cho tôi.

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
Nếu chuỗi của bạn không chứa khoảng trắng hậu tố, điều này sẽ xóa bắt đầu từ npose + 1 == 0 và bạn sẽ xóa toàn bộ chuỗi.
mhsmith

3
@rgove Hãy giải thích. str.find_last_not_of(x)trả về vị trí của ký tự đầu tiên không bằng x. Nó chỉ trả về npose nếu không có ký tự không khớp với x. Trong ví dụ, nếu không có khoảng trắng hậu tố, nó sẽ trả về tương đương str.length() - 1, mang lại cơ bản str.erase((str.length() - 1) + 1).Đó là, trừ khi tôi nhầm lẫn khủng khiếp.
Travis

5
Điều này sẽ trả về std :: string & để tránh việc gọi trình xây dựng sao chép một cách không cần thiết.
heksesang

7
Tôi bối rối tại sao điều này trả về một bản sao sau khi sửa đổi tham số trả về?
Galik

3
@MiloDC Sự nhầm lẫn của tôi là tại sao trả lại một bản sao thay vì một tài liệu tham khảo. Nó có ý nghĩa hơn với tôi để trở lại std::string&.
Galik

25

Tôi thích giải pháp của tzaman, vấn đề duy nhất với nó là nó không cắt một chuỗi chỉ chứa khoảng trắng.

Để sửa lỗi 1 lỗi đó, hãy thêm một str.clear () vào giữa 2 dòng tông đơ

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

Mặc dù vậy, vấn đề với cả hai giải pháp của chúng tôi là chúng sẽ cắt cả hai đầu; không thể thực hiện ltrimhoặc rtrimnhư thế này.
tzaman

44
Tốt, nhưng không thể xử lý chuỗi với khoảng trắng bên trong. ví dụ: trim (abc def ") -> abc, chỉ còn lại abc.
liheyuan

Một giải pháp tốt nếu bạn biết sẽ không có bất kỳ khoảng trắng nội bộ nào!
Elliot Gorokhovsky

Điều này là tốt đẹp và dễ dàng nhưng nó cũng khá chậm khi chuỗi được sao chép vào và ra 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
Giải pháp trang nhã cho không gian cơ bản cuối cùng ... :)
jave.web

Cách thức hoạt động: Đây là một giải pháp giống như bản sao - nó tìm vị trí của ký tự đầu tiên không phải là itkhoảng trắng ( rit) và đảo ngược: vị trí của ký tự mà sau đó chỉ có khoảng trắng ( ) - sau đó nó trả về một chuỗi mới được tạo == một bản sao của một phần của chuỗi gốc - một phần dựa trên các trình vòng lặp đó ...
jave.web

Cảm ơn bạn, đã làm việc cho tôi: std: string s = "Oh noez: space \ r \ n"; std :: chuỗi sạch = trim (s);
Alexx Roche

15

Trong trường hợp chuỗi rỗng, mã của bạn giả sử rằng thêm 1 để string::nposcho 0. string::nposlà loại string::size_type, không dấu. Vì vậy, bạn đang dựa vào hành vi tràn của bổ sung.


23
Bạn đang phrasing rằng như thể nó xấu. hành vi tràn số nguyên là xấu.
MSalters

2
Thêm 1vào std::string::npos phải cho 0theo C++ Standard. Vì vậy, đó là một giả định tốt có thể hoàn toàn dựa vào.
Galik

13

Bị tấn công khỏi 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;
}

Điều này làm việc cho các trường hợp null là tốt. :-)


4
Đây chỉ là rtrim, không phảiltrim
ub3rst4r

1
^ bạn có phiền khi sử dụng find_first_not_of không? Nó tương đối dễ dàng để sửa đổi nó.
Abhinav Gauniyal

13

Với C ++ 17, bạn có thể sử dụng basic_opes_view :: remove_prefixbasic_opes_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;
}

Một thay thế tốt đẹp:

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));
}

Tôi không chắc chắn những gì bạn đang kiểm tra, nhưng trong ví dụ của bạn std :: find_first_not_of sẽ trả về std :: string :: nposestd :: string_view :: size sẽ trả về 4. Tối thiểu rõ ràng là bốn, số phần tử sẽ là bị xóa bởi std :: string_view :: remove_prefix . Cả gcc 9.2 và clang 9.0 đều xử lý chính xác điều này: godbolt.org/z/DcZbFH
Phidelux

1
Cảm ơn! Co vẻ tôt vơi tôi.
Contango

11

Giải pháp của tôi dựa trên câu trả lời của @Bill the Lizard .

Lưu ý rằng các hàm này sẽ trả về chuỗi trống nếu chuỗi đầu vào không chứa gì ngoài khoảng trắng.

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

Câu trả lời của tôi là một cải tiến dựa trên câu trả lời hàng đầu cho bài đăng này kiểm soát các ký tự cũng như khoảng trắng (0-32 và 127 trên bảng ASCII ).

std::isgraphxác định xem một ký tự có biểu diễn đồ họa hay không, vì vậy bạn có thể sử dụng điều này để thay đổi câu trả lời của Evan để xóa bất kỳ ký tự nào không có biểu diễn đồ họa từ hai bên của chuỗi. Kết quả là một giải pháp thanh lịch hơn nhiều:

#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));
}

Lưu ý: Ngoài ra, bạn sẽ có thể sử dụng std::iswgraphnếu bạn cần hỗ trợ cho các ký tự rộng, nhưng bạn cũng sẽ phải chỉnh sửa mã này để kích hoạt std::wstringthao tác, đây là điều mà tôi chưa thử nghiệm (xem trang tham khảo std::basic_stringđể khám phá tùy chọn này) .


3
std :: ptr_fun Không được dùng nữa
johnbakers 8/12/2016

8

Với C ++ 11 cũng xuất hiện một mô-đun biểu thức chính quy , tất nhiên có thể được sử dụng để cắt các khoảng trắng ở đầu hoặc cuối.

Có lẽ một cái gì đó như thế này:

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

Đây là những gì tôi sử dụng. Chỉ cần tiếp tục loại bỏ không gian từ phía trước, và sau đó, nếu có bất cứ điều gì còn lại, làm tương tự từ phía sau.

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
Sẽ hiệu quả hơn một chút nếu bạn thực hiện những thao tác theo thứ tự ngược lại và cắt từ bên phải trước khi gọi một ca làm việc bằng cách cắt bên trái.
Galik

7

Đối với những gì nó có giá trị, đây là một triển khai cắt tỉa với một mắt hướng đến hiệu suất. Nó nhanh hơn nhiều so với nhiều thói quen cắt tỉa khác mà tôi đã thấy xung quanh. Thay vì sử dụng các trình vòng lặp và std :: find, nó sử dụng các chuỗi c và chỉ mục thô. Nó tối ưu hóa các trường hợp đặc biệt sau: chuỗi size 0 (không làm gì), chuỗi không có khoảng trắng để cắt (không làm gì), chuỗi chỉ có khoảng trắng theo sau để cắt (chỉ thay đổi kích thước chuỗi), chuỗi hoàn toàn khoảng trắng (chỉ xóa chuỗi) . Và cuối cùng, trong trường hợp xấu nhất (chuỗi có khoảng trắng hàng đầu), tốt nhất là thực hiện xây dựng bản sao hiệu quả, chỉ thực hiện 1 bản sao và sau đó di chuyển bản sao đó thay cho chuỗi gốc.

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 về mặt lý thuyết có thể là phiên bản nhanh nhất có thể có chữ ký này: extern "C" void string_trim (char ** started_, char ** end_) ... Bắt tôi trôi?

6

Một cách làm thanh lịch có thể giống như

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

Và các chức năng hỗ trợ được thực hiện như:

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;   
}

Và một khi bạn đã có tất cả những thứ này, bạn cũng có thể viết nó:

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

6

Cắt C ++ 11 thực hiện:

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

Tôi đoán nếu bạn bắt đầu yêu cầu "cách tốt nhất" để cắt một chuỗi, tôi muốn nói rằng một triển khai tốt sẽ là một trong đó:

  1. Không phân bổ các chuỗi tạm thời
  2. Có quá tải cho cắt tại chỗ và sao chép cắt
  3. Có thể dễ dàng tùy chỉnh để chấp nhận các chuỗi / logic xác nhận khác nhau

Rõ ràng có quá nhiều cách khác nhau để tiếp cận điều này và nó chắc chắn phụ thuộc vào những gì bạn thực sự cần. Tuy nhiên, thư viện chuẩn C vẫn có một số hàm rất hữu ích trong <string.h>, như memchr. Có một lý do tại sao C vẫn được coi là ngôn ngữ tốt nhất cho IO - stdlib của nó là hiệu quả thuần túy.

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

Tôi không chắc môi trường của bạn có giống nhau không, nhưng trong tôi, trường hợp chuỗi trống sẽ khiến chương trình hủy bỏ. Tôi sẽ kết thúc cuộc gọi đó bằng if (! S.empty ()) hoặc sử dụng Boost như đã đề cập.


3

Đây là những gì tôi nghĩ ra:

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

Khai thác luồng giúp loại bỏ khoảng trắng tự động, vì vậy điều này hoạt động như một nét duyên dáng.
Khá sạch sẽ và thanh lịch quá, nếu tôi nói như vậy bản thân mình. ;)


15
Hừm; điều này giả định rằng chuỗi không có khoảng trắng bên trong (ví dụ: khoảng trắng). OP chỉ nói rằng anh ấy muốn cắt khoảng trắng ở bên trái hoặc bên phải.
SuperElectric

3

Đóng góp giải pháp của tôi cho tiếng ồn. trimmặc định để tạo một chuỗi mới và trả về chuỗi đã sửa đổi trong khi trim_in_placesửa đổi chuỗi được truyền cho chuỗi đó. Các trimchức năng hỗ trợ ngữ nghĩa di chuyển 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

Điều này có thể được thực hiện đơn giản hơn trong C ++ 11 do có thêm back()pop_back().

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

Cách tiếp cận được đề xuất bởi OP cũng không tệ - chỉ khó hơn một chút để làm theo.
tộc

3

Đây là phiên bản của tôi:

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);

Bạn đang thiếu nhân vật cuối cùng. +1 trong chiều dài sẽ giải quyết điều này
galinette

2

Các phương pháp trên là tuyệt vời, nhưng đôi khi bạn muốn sử dụng kết hợp các hàm cho những gì thói quen của bạn coi là khoảng trắng. Trong trường hợp này, sử dụng functor để kết hợp các thao tác có thể trở nên lộn xộn, vì vậy tôi thích một vòng lặp đơn giản mà tôi có thể sửa đổi cho phần cắt. Đây là một chức năng cắt sửa đổi một chút được sao chép từ phiên bản C ở đây trên SO. Trong ví dụ này, tôi đang cắt các ký tự không chữ và số.

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

Đây là một thực hiện thẳng về phía trước. Đối với một hoạt động đơn giản như vậy, có lẽ bạn không nên sử dụng bất kỳ cấu trúc đặc biệt nào. Hàm isspace () tích hợp sẽ xử lý các dạng ký tự trắng khác nhau, vì vậy chúng ta nên tận dụng nó. Bạn cũng phải xem xét các trường hợp đặc biệt trong đó chuỗi trống hoặc đơn giản là một loạt các khoảng trắng. Cắt trái hoặc phải có thể được lấy từ đoạn mã sau.

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

Đây là một giải pháp dễ hiểu cho người mới bắt đầu không sử dụng để viết std::ở mọi nơi và chưa quen với const-có tính chính xác, iterators, STL algorithm, v.v ...

#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));
}

Hy vọng nó giúp...


1

Phiên bản này cắt khoảng trắng nội bộ và không chữ và số:

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

Một tùy chọn khác - loại bỏ một hoặc nhiều ký tự từ cả hai đầu.

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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.