Apa cara terbaik untuk memotong std :: string?


812

Saat ini saya menggunakan kode berikut untuk memotong kanan semua std::stringsprogram saya:

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

Ini berfungsi dengan baik, tapi saya ingin tahu apakah ada beberapa kasus akhir di mana ia mungkin gagal?

Tentu saja, jawaban dengan alternatif yang elegan dan juga solusi kiri diterima.


549
Jawaban atas pertanyaan ini adalah bukti betapa tidak adanya pustaka standar C ++.
Idan K

83
@IdanK Dan masih tidak memiliki fungsi ini di C ++ 11.
kuantum

44
@IdanK: Bagus, bukan! Lihatlah semua pilihan bersaing sekarang kita miliki kami, tidak terbebani oleh ide satu orang dari " the cara kita harus melakukannya"!
Lightness Races dalam Orbit

59
Fungsionalitas @LightnessRacesinOrbit dalam suatu tipe, baik itu keputusan desain, dan menambahkan fungsi trim ke string mungkin (setidaknya di bawah c ++) toh bukan solusi terbaik - tetapi tidak menyediakan cara standar untuk melakukannya, sebagai gantinya membiarkan semua orang khawatir. masalah kecil yang sama berulang-ulang, tentu juga tidak membantu siapa pun
codeling

27
Anda dapat mempertanyakan mengapa fungsi pemangkasan tidak dimasukkan ke dalam std::stringkelas, ketika fungsi seperti ini yang membuat bahasa lain begitu bagus untuk digunakan (misalnya Python).
HelloGoodbye

Jawaban:


648

EDIT Sejak c ++ 17, beberapa bagian dari perpustakaan standar telah dihapus. Untungnya, dimulai dengan c ++ 11, kami memiliki lambdas yang merupakan solusi terbaik.

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

Terima kasih kepada https://stackoverflow.com/a/44973498/524503 untuk membuka solusi modern.

Jawaban asli:

Saya cenderung menggunakan salah satu dari 3 ini untuk kebutuhan pemangkasan saya:

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

Mereka cukup jelas dan bekerja dengan sangat baik.

EDIT : BTW, saya ada std::ptr_fundi sana untuk membantu disambiguasi std::isspacekarena sebenarnya ada definisi kedua yang mendukung lokal. Ini bisa saja menjadi pemeran yang sama, tapi saya cenderung lebih suka ini.

EDIT : Untuk membahas beberapa komentar tentang menerima parameter dengan referensi, memodifikasi dan mengembalikannya. Saya setuju. Sebuah implementasi yang saya lebih suka akan menjadi dua set fungsi, satu untuk di tempat dan satu yang membuat salinan. Satu set contoh yang lebih baik adalah:

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

Saya menyimpan jawaban asli di atas untuk konteks dan untuk menjaga agar jawaban yang dipilih tetap tersedia.


28
Kode ini gagal pada beberapa string internasional (shift-jis dalam kasus saya, disimpan dalam std :: string); Saya akhirnya menggunakan boost::trimuntuk memecahkan masalah.
Tom

5
Saya akan menggunakan pointer daripada referensi, sehingga dari callpoint jauh lebih mudah untuk memahami bahwa fungsi-fungsi ini mengedit string di tempat, daripada membuat salinan.
Marco Leogrande

3
Perhatikan bahwa dengan isspace Anda dapat dengan mudah mendapatkan perilaku tidak terdefinisi dengan karakter non-ASCII yang ditumpuk-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
Mengapa statis? Apakah ini tempat namespace anonim lebih disukai?
Trevor Hickey

3
@ TrevorHickey, tentu saja, Anda bisa menggunakan namespace anonim sebagai gantinya jika Anda mau.
Evan Teran

417

Menggunakan algoritma string Boost akan lebih mudah:

#include <boost/algorithm/string.hpp>

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

strsekarang "hello world!". Ada juga trim_leftdan trim, yang memangkas kedua belah pihak.


Jika Anda menambahkan _copyakhiran ke salah satu nama fungsi di atas trim_copy, mis . Fungsi tersebut akan mengembalikan salinan string yang terpotong alih-alih memodifikasinya melalui referensi.

Jika Anda menambahkan _ifakhiran ke salah satu nama fungsi di atas trim_copy_if, misalnya , Anda dapat memangkas semua karakter yang memenuhi predikat khusus Anda, bukan hanya spasi putih saja.


7
Itu tergantung pada lokasi. Lokal default saya (VS2005, id) berarti tab, spasi, pengembalian carriage, baris baru, tab vertikal, dan umpan formulir dipangkas.
MattyT

117
Boost adalah palu besar untuk masalah sekecil itu.
Casey Rodarmor

143
@rodarmor: Meningkatkan memecahkan banyak masalah kecil. Ini adalah palu besar yang banyak membantu.
Nicol Bolas

123
Boost adalah seperangkat palu dari berbagai ukuran yang menyelesaikan banyak masalah.
Ibrahim

11
@ prodarmor Anda mengatakan bahwa seolah-olah Boost adalah monolith semua atau tidak sama sekali, di mana termasuk salah satu headernya entah bagaimana mempengaruhi seluruh program seseorang. Yang jelas bukan itu masalahnya. Btw, saya tidak pernah menggunakan Boost, fwiw.
underscore_d

61

Gunakan kode berikut untuk memangkas spasi (trailing) dan karakter tab dari 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));
}

Dan hanya untuk menyeimbangkan semuanya, saya akan memasukkan kode trim kiri juga ( ideone ):

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

4
Ini tidak akan mendeteksi bentuk spasi putih lainnya ... baris baru, umpan baris, carriage return khususnya.
Tom

1
Baik. Anda harus menyesuaikannya untuk spasi putih yang ingin Anda pangkas. Aplikasi khusus saya hanya mengharapkan spasi dan tab, tetapi Anda dapat menambahkan \ n \ r untuk menangkap yang lain.
Bill the Lizard

5
str.substr(...).swap(str)lebih baik. Simpan tugas.
updogliu

4
@updogliu Tidak akankah itu menggunakan pemindahan tugas basic_string& operator= (basic_string&& str) noexcept;?
nurettin

8
Jawaban ini tidak mengubah string yang SEMUA spasi. Yang gagal.
Tom Andersen

56

Apa yang Anda lakukan baik dan kuat. Saya telah menggunakan metode yang sama untuk waktu yang lama dan saya belum menemukan metode yang lebih cepat:

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

Dengan memasok karakter yang akan dipangkas Anda memiliki fleksibilitas untuk memangkas karakter yang bukan spasi dan efisiensi untuk memangkas hanya karakter yang ingin Anda pangkas.


jika Anda mengubah urutan trim, yaitu membuatnya rtrim(ltrim(s, t), t)akan sedikit lebih efisien
CITBL

1
@ CITBL Fungsi bagian dalam dilakukan terlebih dahulu sehingga cara Anda akan memotong dari kiri sebelum memotong dari kanan. Saya pikir itu akan menjadi kurang efisien bukan?
Galik

Persis. Kesalahan saya
CITBL

jika Anda menggunakan basic_string dan templat pada CharT, Anda dapat melakukan ini untuk semua string, cukup gunakan variabel templat untuk spasi putih sehingga Anda menggunakannya seperti <<arT>. secara teknis pada titik itu Anda bisa membuatnya siap untuk c ++ 20 dan menandainya constexpr juga karena ini menyiratkan inline
Beached

@Bached Memang. Agak rumit untuk memasukkan jawaban di sini. Saya telah menulis fungsi templat untuk ini dan tentunya cukup terlibat. Saya telah mencoba banyak pendekatan berbeda dan masih belum yakin mana yang terbaik.
Galik

55

Agak terlambat ke pesta, tapi tidak apa-apa. Sekarang C ++ 11 ada di sini, kami memiliki lambdas dan variabel otomatis. Jadi versi saya, yang juga menangani semua spasi putih dan string kosong, adalah:

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

Kita dapat membuat iterator terbalik dari wsfrontdan menggunakannya sebagai kondisi penghentian di detik find_if_nottetapi itu hanya berguna dalam kasus string semua-spasi, dan gcc 4,8 setidaknya tidak cukup pintar untuk menyimpulkan jenis iterator terbalik ( std::string::const_reverse_iterator) dengan auto. Saya tidak tahu seberapa mahal membangun iterator terbalik, jadi YMMV di sini. Dengan perubahan ini, kode ini terlihat seperti ini:

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
Bagus. +1 dari saya. Sayang sekali C ++ 11 tidak memperkenalkan trim () ke std :: string dan membuat hidup lebih mudah untuk semua orang.
Milan Babuškov

3
Saya selalu ingin satu pemanggilan fungsi untuk memangkas string, alih-alih menerapkannya
linquize

22
Untuk apa nilainya, tidak perlu menggunakan lambda itu. Anda bisa lewat std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
+1 untuk mungkin satu-satunya jawaban dengan implementasi yang hanya melakukan satu salinan string O (N).
Alexei Averchenko

4
@vmrob kompiler belum tentu sepintar itu. melakukan apa yang Anda katakan adalah ambigu:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers

42

Coba ini, ini bekerja untuk saya.

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
Jika string Anda tidak mengandung spasi suffixing, ini akan dihapus mulai dari npos + 1 == 0, dan Anda akan menghapus seluruh string.
mhsmith

3
@ pindah Tolong jelaskan. str.find_last_not_of(x)mengembalikan posisi karakter pertama tidak sama dengan x. Ini hanya mengembalikan npos jika tidak ada karakter yang tidak cocok dengan x. Dalam contoh, jika tidak ada ruang suffixing, itu akan mengembalikan setara dengan str.length() - 1, menghasilkan dasarnya str.erase((str.length() - 1) + 1).Yaitu, kecuali saya sangat keliru.
Travis

5
Ini harus mengembalikan std :: string & untuk menghindari pemanggilan copy constructor yang tidak perlu.
heksesang

7
Saya bingung mengapa ini mengembalikan salinan setelah memodifikasi parameter pengembalian?
Galik

3
@ MoiloDC Kebingungan saya adalah mengapa mengembalikan salinan bukan referensi. Lebih masuk akal bagi saya untuk kembali std::string&.
Galik

25

Saya suka solusi tzaman, satu-satunya masalah dengan itu adalah tidak memangkas string yang hanya berisi spasi.

Untuk memperbaiki 1 cacat itu, tambahkan str.clear () di antara 2 garis pemangkas

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

Bagus :) masalahnya dengan kedua solusi kami, adalah mereka akan memotong kedua ujungnya; tidak dapat membuat ltrimatau rtrimseperti ini.
tzaman

44
Bagus, tetapi tidak dapat menangani string dengan spasi putih internal. mis. trim (abc def ") -> abc, hanya abc yang tersisa.
liheyuan

Solusi yang bagus jika Anda tahu tidak akan ada spasi putih internal!
Elliot Gorokhovsky

Ini bagus dan mudah tetapi juga cukup lambat karena string disalin ke dalam dan keluar dari 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
Solusi elegan untuk trim ruang dasar akhirnya ... :)
jave.web

Bagaimana ini bekerja: Ini adalah solusi seperti salinan - ia menemukan posisi karakter pertama yang bukan spasi ( it) dan membalikkan: posisi karakter setelah hanya ada spasi ( rit) - setelah itu mengembalikan string yang baru dibuat == salinan bagian dari string asli - bagian berdasarkan iterator itu ...
jave.web

Terima kasih, bekerja untuk saya: std: string s = "Oh noez: space \ r \ n"; std :: string clean = trim (s);
Alexx Roche

15

Dalam kasus string kosong, kode Anda mengasumsikan bahwa menambahkan 1 untuk string::nposmemberikan 0. string::nposadalah tipe string::size_type, yang tidak ditandatangani. Dengan demikian, Anda mengandalkan perilaku penambahan yang melimpah.


23
Anda mengatakan itu seolah-olah itu buruk. Perilaku overflow integer yang ditandatangani buruk.
MSalters

2
Menambahkan 1ke std::string::npos harus memberi 0sesuai dengan C++ Standard. Jadi itu asumsi yang bagus yang bisa diandalkan.
Galik

13

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

Ini berfungsi untuk kasus nol juga. :-)


4
Ini hanya rtrim, tidakltrim
ub3rst4r

1
^ Apakah Anda keberatan menggunakan find_first_not_of? Relatif mudah untuk memodifikasinya.
Abhinav Gauniyal

13

Dengan C ++ 17 Anda dapat menggunakan basic_string_view :: remove_prefix dan 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;
}

Alternatif yang bagus:

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

Saya tidak yakin apa yang Anda uji, tetapi dalam contoh Anda std :: find_first_not_of akan mengembalikan std :: string :: npos dan std :: string_view :: size akan kembali 4. Minimal jelas empat, jumlah elemen yang akan dihapus oleh std :: string_view :: remove_prefix . Baik gcc 9.2 dan clang 9.0 menanganinya dengan benar: godbolt.org/z/DcZbFH
Phidelux

1
Terima kasih! Terlihat bagus untukku.
Contango

11

Solusi saya berdasarkan jawaban oleh @Bill the Lizard .

Perhatikan bahwa fungsi-fungsi ini akan mengembalikan string kosong jika string input hanya berisi spasi kosong.

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

Jawaban saya adalah peningkatan pada jawaban teratas untuk posting ini yang memangkas karakter kontrol serta spasi (0-32 dan 127 pada tabel ASCII ).

std::isgraphmenentukan apakah karakter memiliki representasi grafis, sehingga Anda dapat menggunakan ini untuk mengubah jawaban Evan untuk menghapus karakter yang tidak memiliki representasi grafis dari kedua sisi string. Hasilnya adalah solusi yang jauh lebih elegan:

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

Catatan: Atau Anda dapat menggunakan std::iswgraphjika Anda membutuhkan dukungan untuk karakter lebar, tetapi Anda juga harus mengedit kode ini untuk mengaktifkan std::wstringmanipulasi, yang merupakan sesuatu yang belum saya uji (lihat halaman referensi untuk std::basic_stringmenjelajahi opsi ini) .


3
std :: ptr_fun sudah tidak digunakan lagi
johnbakers

8

Dengan C ++ 11 juga muncul modul ekspresi reguler , yang tentu saja dapat digunakan untuk memangkas ruang depan atau belakang.

Mungkin kira-kira seperti ini:

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

Inilah yang saya gunakan. Terus keluarkan ruang dari depan, dan kemudian, jika ada yang tersisa, lakukan hal yang sama dari belakang.

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
Akan sedikit lebih efisien jika Anda melakukannya dengan urutan yang berlawanan dan memotong dari kanan terlebih dahulu sebelum menjalankan shift dengan memotong ke kiri.
Galik

7

Untuk apa nilainya, berikut ini adalah implementasi yang ramping dengan pandangan terhadap kinerja. Ini jauh lebih cepat daripada banyak rutinitas trim lainnya yang pernah saya lihat. Alih-alih menggunakan iterator dan std :: ditemukan, ia menggunakan string dan indeks c mentah. Ini mengoptimalkan kasus khusus berikut: ukuran 0 string (tidak melakukan apa-apa), string tanpa spasi untuk memangkas (tidak melakukan apa-apa), string dengan hanya trailing spasi untuk memotong (hanya mengubah ukuran string), string yang sepenuhnya spasi putih (hanya menghapus string) . Dan akhirnya, dalam kasus terburuk (string dengan spasi kosong), ia melakukan yang terbaik untuk melakukan konstruksi salinan yang efisien, hanya melakukan 1 salinan dan kemudian memindahkan salinan itu di tempat string asli.

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

@ bbmda mungkin secara teoritis versi tercepat mungkin memiliki tanda tangan ini: extern "C" void string_trim (char ** begin_, char ** end_) ... Tangkap maksud saya?

6

Cara yang elegan untuk melakukannya bisa seperti

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

Dan fungsi pendukung diimplementasikan sebagai:

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

Dan setelah Anda memiliki semua ini, Anda dapat menulis ini juga:

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

6

Implementasi 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

Saya kira jika Anda mulai meminta "cara terbaik" untuk memotong string, saya akan mengatakan implementasi yang baik adalah yang:

  1. Tidak mengalokasikan string sementara
  2. Memiliki kelebihan untuk trim di tempat dan salin trim
  3. Dapat dengan mudah disesuaikan untuk menerima urutan / logika validasi yang berbeda

Jelas ada terlalu banyak cara berbeda untuk mendekati ini dan itu pasti tergantung pada apa yang sebenarnya Anda butuhkan. Namun, pustaka standar C masih memiliki beberapa fungsi yang sangat berguna di <string.h>, seperti memchr. Ada alasan mengapa C masih dianggap sebagai bahasa terbaik untuk IO - stdlibnya adalah efisiensi murni.

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

Saya tidak yakin apakah lingkungan Anda sama, tetapi di tambang, case string kosong akan menyebabkan program dibatalkan. Saya akan membungkus panggilan penghapusan dengan if (! S.empty ()) atau menggunakan Boost seperti yang telah disebutkan.


3

Inilah yang saya pikirkan:

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

Ekstraksi aliran menghilangkan spasi putih secara otomatis, jadi ini berfungsi seperti pesona.
Cukup bersih dan elegan juga, jika saya mengatakannya sendiri. ;)


15
Hmm; ini mengasumsikan bahwa string tidak memiliki spasi putih internal (misalnya spasi). OP hanya mengatakan dia ingin memangkas spasi di kiri atau kanan.
SuperElectric

3

Menyumbangkan solusi saya untuk kebisingan. trimdefault untuk membuat string baru dan mengembalikan yang dimodifikasi sambil trim_in_placememodifikasi string yang diteruskan ke sana. The trimmendukung fungsi c ++ 11 langkah semantik.

#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

Ini dapat dilakukan lebih sederhana di C ++ 11 karena penambahan back()dan pop_back().

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

Pendekatan yang disarankan oleh OP juga tidak buruk - hanya sedikit lebih sulit untuk diikuti.
Nobar

3

Ini versi saya:

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

Anda kehilangan karakter terakhir. Panjang +1 untuk memecahkan ini
galinette

2

Metode di atas bagus, tetapi kadang-kadang Anda ingin menggunakan kombinasi fungsi untuk apa yang rutin Anda anggap sebagai spasi kosong. Dalam hal ini, menggunakan functors untuk menggabungkan operasi bisa berantakan jadi saya lebih suka loop sederhana yang saya bisa modifikasi untuk trim. Berikut ini adalah fungsi trim sedikit dimodifikasi disalin dari versi C di sini di SO. Dalam contoh ini, saya memangkas karakter non alfanumerik.

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

Berikut ini adalah implementasi lurus ke depan. Untuk operasi sederhana seperti itu, Anda mungkin tidak boleh menggunakan konstruksi khusus apa pun. Fungsi built-in isspace () menangani berbagai bentuk karakter putih, jadi kita harus memanfaatkannya. Anda juga harus mempertimbangkan kasus khusus di mana string kosong atau hanya sekelompok spasi. Potong kiri atau kanan dapat diturunkan dari kode berikut.

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

Inilah solusi yang mudah dimengerti bagi pemula yang tidak terbiasa menulis di std::mana-mana dan belum terbiasa dengan const-correctness, iterators, STL algorithms, dll ...

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

Semoga ini bisa membantu ...


1

Versi ini memangkas spasi putih internal dan non-alfanumerik:

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

Namun pilihan lain - menghapus satu atau lebih karakter dari kedua ujungnya.

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);
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.