Std :: string'i kırpmanın en iyi yolu nedir?


812

Şu anda std::stringsbenim programları tüm düzeltmek için aşağıdaki kodu kullanıyorum :

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

İyi çalışıyor, ancak başarısız olabileceği bazı son durumlar olup olmadığını merak ediyorum?

Tabii ki, zarif alternatifler ve sol çözüm ile cevaplar bekliyoruz.


549
Bu sorunun cevabı, C ++ standart kütüphanesinin eksikliğinin bir kanıtıdır.
Idan K

83
@IdanK Ve hala C ++ 11 bu işlevi yoktur.
kuantum

44
@IdanK: Harika, değil mi! "Tek bir kişinin fikri ipoteksiz bizim emrinde Şu anda elimizde tüm rakip seçeneklerine bakın biz bunu yapmalıyız bu şekilde"!
Yörüngedeki Hafiflik Yarışları

59
Bir türdeki @LightnessRacesinOrbit işlevselliği, bu bir tasarım kararıdır ve bir dizeye bir kırpma işlevi eklemek (en azından c ++ altında) zaten en iyi çözüm olmayabilir - ancak bunu yapmak için standart bir yol sağlamaz, bunun yerine herkesin üzülmesine izin verir aynı küçük sorunları tekrar tekrar, kesinlikle kimseye de yardım
etmiyor

27
Düzeltme işlevlerinin std::stringsınıfta neden yerleşik olmadığını, diğer dilleri kullanmayı bu kadar güzel kılan işlevler olduğunda (örneğin Python) sorgulayabilirsiniz .
HelloGoodbye

Yanıtlar:


648

EDIT c ++ 17'den beri, standart kitaplığın bazı bölümleri kaldırıldı. Neyse ki, c ++ 11 ile başlayarak, üstün bir çözüm olan lambdalarımız var.

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

Modern çözümü getirdiği için https://stackoverflow.com/a/44973498/524503'e teşekkürler .

Orijinal cevap:

Kırpma ihtiyaçlarım için şu 3'ten birini kullanma eğilimindeyim:

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

Oldukça açıklayıcıdırlar ve çok iyi çalışırlar.

EDIT : BTW, std::ptr_funorada yerelleştirmek std::isspacedestekleyen ikinci bir tanım var çünkü anlam ayrıştırmak için var. Bu aynı bir oyuncu olabilirdi, ama bunu daha çok sevme eğilimindeyim.

DÜZENLEME : Bir parametreyi başvuru ile kabul etme, değiştirme ve döndürme ile ilgili bazı yorumları ele almak için. Katılıyorum. Muhtemelen tercih edeceğim bir uygulama, biri yerinde diğeri kopya yapan iki işlev kümesi olacaktır. Daha iyi bir örnek:

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

Yukarıda verilen orijinal cevabı bağlam için ve yüksek oylanan cevabı hala mevcut tutmak adına saklıyorum.


28
Bu kod bazı uluslararası dizelerde başarısız oldu (benim durumumda shift-jis, bir std :: string saklanır); boost::trimSonunda sorunu çözmek için kullandım .
Tom

5
Referanslar yerine işaretçiler kullanırdım, böylece çağrı noktasından bu işlevlerin bir kopya oluşturmak yerine yerinde dizeyi düzenlediğini anlamak çok daha kolay olur.
Marco Leogrande

3
İsspace ile ASCII olmayan karakterler ile kolayca tanımlanamayacağınızı unutmayın stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
Neden statik? Anonim bir ad alanının tercih edileceği yer burası mı?
Trevor Hickey

3
@TrevorHickey, elbette, isterseniz adsız bir ad alanı kullanabilirsiniz.
Evan Teran

417

Boost'un dize algoritmalarını kullanmak en kolayı olacaktır:

#include <boost/algorithm/string.hpp>

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

strşimdi "hello world!". Ayrıca trim_leftve trimher iki tarafı da kesen var.


_copyYukarıdaki işlev adlarından herhangi birine bir sonek eklerseniz trim_copy, işlev dizeyi başvuru yoluyla değiştirmek yerine kesilmiş bir kopyasını döndürür.

_ifYukarıdaki işlev adlarından herhangi birine bir sonek eklerseniz trim_copy_if, yalnızca boşlukların aksine, özel yükleminizi tatmin eden tüm karakterleri kırpabilirsiniz.


7
Yerel ayara bağlıdır. Varsayılan yerel ayarım (VS2005, en) sekmeler, boşluklar, satır başları, satırsonları, dikey sekmeler ve form beslemelerinin kırpıldığı anlamına gelir.
MattyT

117
Boost, bu kadar küçük bir sorun için çok büyük bir çekiç.
Casey Rodarmor

143
@rodarmor: Boost, birçok küçük sorunu çözer. Çok fazla çözen büyük bir çekiç.
Nicol Bolas

123
Boost, birçok farklı problemi çözen birçok farklı boyutta çekiç setidir.
İbrahim

11
@rodarmor Boost'un bir ya hep ya hiç bir monolitmiş gibi olduğunu söylüyorsunuz, üstbilgilerinden birini dahil etmek bir şekilde her şeyi kişinin programına yüklüyor. Bu kesinlikle doğru değil. Btw, Boost'u hiç kullanmadım, fwiw.
underscore_d

61

std::strings( İdeone ) ' dan boşlukları ve sekme karakterlerini sağ kırpmak (sondaki) için aşağıdaki kodu kullanın :

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

Ve sadece işleri dengelemek için, sol trim kodunu da ekleyeceğim ( ideone ):

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

4
Bu, diğer beyaz boşluk biçimlerini algılamaz ... özellikle yeni satır, satır besleme, satır başı.
Tom

1
Sağ. Kırpmak istediğiniz boşluk için özelleştirmeniz gerekir. Benim özel uygulama sadece boşluklar ve sekmeler bekliyordu, ancak diğerlerini yakalamak için \ n \ r ekleyebilirsiniz.
Bill the Lizard

5
str.substr(...).swap(str)daha iyi. Ödevi kaydedin.
updogliu

4
@updogliu Taşıma ataması kullanmayacak basic_string& operator= (basic_string&& str) noexcept;mı?
nurettin

8
Bu yanıt TÜM boşluk olan dizeleri değiştirmez. Bu bir başarısızlık.
Tom Andersen

56

Yaptığınız iyi ve sağlam. Uzun zamandır aynı yöntemi kullandım ve daha hızlı bir yöntem bulamadım:

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

Kesilecek karakterleri sağlayarak, boşluk olmayan karakterleri düzeltme esnekliğine ve yalnızca kırpılmasını istediğiniz karakterleri kırpma verimliliğine sahip olursunuz.


eğer sırayı değiştirirseniz trim, yani rtrim(ltrim(s, t), t)biraz daha verimli olacaktır
CITBL

1
@CITBL İç fonksiyon önce gerçekleştirilir, böylece sağdan düzeltme yapmadan önce soldan kesilir . Bunun daha az verimli olacağını düşünüyorum değil mi?
Galik

Kesinlikle. Benim hatam
CITBL

Eğer CharT üzerinde basic_string ve template kullanırsanız bunu tüm dizeler için yapabilirsiniz, ws <CharT> gibi kullanmanız için boşluk için bir şablon değişkeni kullanmanız yeterlidir. teknik olarak bu noktada c ++ 20 için hazır hale getirebilir ve bu da satır içi anlamına geldiği için bunu constexpr olarak işaretleyebilirsiniz
Beached

@ Gerçekten de. Yine de burada bir cevap koymak biraz karmaşık. Bunun için şablon fonksiyonları yazdım ve kesinlikle oldukça ilgili. Bir sürü farklı yaklaşım denedim ve hala hangisinin en iyisi olduğundan emin değilim.
Galik

55

Partiye biraz geç, ama boş ver. Şimdi C ++ 11 burada, lambdalarımız ve otomatik değişkenlerimiz var. Bu nedenle, tüm boşlukları ve boş dizeleri de işleyen sürümüm:

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

Bir ters yineleyici yapabilir wsfrontve bunu ikinci olarak sonlandırma koşulu olarak kullanabiliriz, find_if_notancak bu yalnızca bir boşluk alanı dizesi için yararlıdır ve gcc 4.8 en azından ters yineleyicinin türünü çıkartacak kadar akıllı değildir ( std::string::const_reverse_iterator) ile auto. Ters bir yineleyici oluşturmanın ne kadar pahalı olduğunu bilmiyorum, bu yüzden YMMV burada. Bu değişiklikle kod şöyle görünür:

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
Güzel. Benden +1. Çok kötü C ++ 11, std :: string'e trim () eklemedi ve hayatı herkes için kolaylaştırdı.
Milan Babuškov

3
Her zaman bir işlev çağrısını uygulamak yerine dizeyi kırpmak istiyorum
Ocak'ta 8

22
Değeri için, bu lambda'yı kullanmaya gerek yok. Sadece geçebilirsiniz std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
Büyük olasılıkla yalnızca bir O (N) dize kopyası yapan uygulamadaki tek cevap için +1.
Alexei Averchenko

4
@vmrob derleyicileri o kadar akıllı olmak zorunda değildir. söylediklerini yapmak belirsiz:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers

42

Bunu dene, benim için çalışıyor.

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
Dizenizde sonda boşluk yoksa, bu npos + 1 == 0'dan başlayarak silinir ve dizenin tamamını silersiniz.
mhsmith

3
@rgove Lütfen açıklayınız. str.find_last_not_of(x)x karakterine eşit olmayan ilk karakterin konumunu döndürür. Yalnızca hiçbir karakter x ile eşleşmediğinde npos döndürür. Örnekte, sonek boşlukları yoksa, çok yanlış bir şekilde yanılmadıkça, str.length() - 1esasen str.erase((str.length() - 1) + 1).budur.
Travis

5
Bu, kopya yapıcısını gereksiz yere çağırmamak için std :: string & öğesini döndürmelidir.
heksesang

7
Bu dönüş parametresini değiştirdikten sonra neden bir kopyasını döndürdüğünü kafam karıştı?
Galik

3
@MiloDC Benim karışıklığım neden referans yerine bir kopyasını geri vermenizdir . Geri dönmek benim için daha anlamlı std::string&.
Galik

25

Ben tzaman'ın çözümünü seviyorum, bununla ilgili tek sorun, sadece boşluklar içeren bir dize kesmemesidir.

Bu 1 kusuru düzeltmek için 2 düzeltici çizgisinin arasına bir str.clear () ekleyin

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

Nice :) Her iki çözümümüzle ilgili sorun, her iki ucu da kesecekleri; Bir yapamaz ltrimveya rtrimbu gibi.
tzaman

44
İyi, ama iç boşluk ile dize ile baş edemez. örneğin trim (abc def ") -> abc, sadece abc kaldı.
liheyuan

Herhangi bir dahili boşluk olmayacağını biliyorsanız iyi bir çözüm!
Elliot Gorokhovsky

Bu güzel ve kolaydır, ancak dize içine ve dışına kopyalandığında da oldukça yavaştır 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
Sonunda temel alan döşemesi için zarif çözüm ... :)
jave.web

Bu nasıl çalışır: Bu kopya benzeri bir çözümdür - boşluk ( it) ve ters olmayan ilk karakterin konumunu bulur : tersine: yalnızca boşluklar ( rit) olan karakterin konumu - bundan sonra yeni oluşturulan bir dize döndürür == orijinal dizenin bir kısmı - bu yineleyicilere dayanan bir bölüm ...
jave.web

Teşekkür ederim, benim için çalıştı: std: string s = "Oh noez: space \ r \ n"; std :: string clean = kırpma (lar);
Alexx Roche

15

Boş bir dize olması durumunda, kodunuz string::npos0'a 1 değerini eklemenin , imzalanmamış string::npostipte olduğunu varsayar string::size_type. Böylece, ekleme taşma davranışına güveniyorsunuz.


23
Kötü gibi sanıyorsun. İmzalı tamsayı taşması davranışı kötü.
MSalters

2
Ekleme 1için std::string::npos gereken vermek 0göre C++ Standard. Bu yüzden kesinlikle güvenilebilecek iyi bir varsayımdır.
Galik

13

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

Bu null durum için de geçerlidir. :-)


4
Bu sadece rtrim, değilltrim
ub3rst4r

1
^ find_first_not_of kullanmayı düşünür müsünüz? Değiştirmek nispeten kolaydır.
Abhinav Gauniyal

13

C ++ 17 ile basic_string_view :: remove_prefix ve basic_string_view :: remove_suffix'i kullanabilirsiniz :

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

Güzel bir alternatif:

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

Eminim sen test ediyoruz değilim neyi ancak örnek içinde std :: find_first_not_of dönecektir std :: string :: KAGKlar ve std :: string_view :: boyutu asgari besbelli dörttür 4. dönecektir, eleman sayısını olmak std :: string_view :: remove_prefix tarafından kaldırıldı . Hem gcc 9.2 hem de clang 9.0 bunu doğru bir şekilde ele alıyor: godbolt.org/z/DcZbFH
Phidelux

1
Teşekkürler! Bana iyi görünüyor.
Contango

11

@Bill the Lizard'ın cevabına dayanan çözümüm .

Giriş dizesi boşluktan başka bir şey içermiyorsa bu işlevlerin boş dizeyi döndüreceğini unutmayın.

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

Cevabım, bu karakterin üst yanıta kontrol karakterlerini ve boşlukları ( ASCII tablosunda 0-32 ve 127 ) kesen bir gelişmedir .

std::isgraphbir karakterin grafik temsili olup olmadığını belirler, böylece bunu dize'nin her iki tarafından grafik temsili olmayan herhangi bir karakteri kaldırmak için Evan'ın cevabını değiştirmek için kullanabilirsiniz. Sonuç çok daha zarif bir çözümdür:

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

Not: Alternatif olarak std::iswgraph, geniş karakterler için desteğe ihtiyacınız varsa kullanabilmeniz gerekir, ancak std::wstringtest etmediğim bir şey olan manipülasyonu etkinleştirmek için bu kodu da düzenlemeniz gerekir ( std::basic_stringbu seçeneği keşfetmek için referans sayfasına bakın ) .


3
std :: ptr_fun Kullanımdan kaldırıldı
johnbakers

8

C ++ 11 ile aynı zamanda önde gelen veya arkadaki boşlukları düzeltmek için kullanılabilen düzenli bir ifade modülü de geldi .

Belki böyle bir şey:

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

Ben bunu kullanıyorum. Sadece alanı önden çıkarmaya devam edin ve sonra kalan bir şey varsa, aynısını arkadan yapın.

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
Bunları ters sırayla yaparsanız ve sola kırparak bir vardiya çağırmadan önce sağdan keserseniz biraz daha verimli olur.
Galik

7

Değeri için, burada performansa yönelik bir trim uygulaması var. Etrafımda gördüğüm birçok trim rutininden çok daha hızlı. Yineleyiciler ve std :: buluntuları kullanmak yerine, ham c dizeleri ve dizinleri kullanır. Aşağıdaki özel durumları optimize eder: boyut 0 dize (hiçbir şey yapmaz), kırpılacak boşluk olmayan dize (hiçbir şey yapmaz), yalnızca düzelecek boşluk olan dize (sadece dizeyi yeniden boyutlandırır), tamamen boşluk olan dize (sadece dizeyi temizle) . Ve son olarak, en kötü durumda (önde gelen boşluklu dize), verimli bir kopya oluşturma gerçekleştirmek, sadece 1 kopya yapmak ve daha sonra bu kopyayı orijinal dize yerine taşımak için elinden geleni yapar.

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 teorik olarak en hızlı versiyon şu imzayı taşıyor olabilir: extern "C" void string_trim (char ** begin_, char ** end_) ... Drift'imi yakaladın mı?

6

Bunu yapmanın zarif bir yolu

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

Ve destekleyici işlevler şu şekilde uygulanır:

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

Ve tüm bunları yerine getirdikten sonra, bunu da yazabilirsiniz:

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

6

Trim C ++ 11 uygulaması:

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

Eğer bir dize kırpmak için "en iyi yolu" sormaya başlar sanırım, iyi bir uygulama biri olacağını söyleyebilirim:

  1. Geçici dizeler ayırmaz
  2. Yerinde kırpma ve kopya kırpma için aşırı yükler var
  3. Farklı doğrulama dizilerini / mantığını kabul etmek için kolayca özelleştirilebilir

Açıkçası buna yaklaşmak için çok farklı yollar vardır ve bu gerçekten ihtiyacınız olan şeye bağlıdır. Ancak, C standart kütüphanesinde <string.h> dosyasında memchr gibi bazı çok yararlı işlevler vardır. C'nin hala IO için en iyi dil olarak kabul edilmesinin bir nedeni var - stdlib saf verimliliktir.

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

Ortamınızın aynı olup olmadığından emin değilim, ancak benimki boş dize örneği programın iptal edilmesine neden olacak. Ya silme çağrısını bir if (! S.empty ()) ile sararım veya daha önce de belirtildiği gibi Boost'u kullanırdım.


3

İşte ben geldim:

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

Akış çıkarma, boşlukları otomatik olarak ortadan kaldırır, bu nedenle bu bir cazibe gibi çalışır.
Ben de oldukça temiz ve zarif, eğer kendim söylesem. ;)


15
Hmm; bu, dizenin dahili boşluk olmadığını (örneğin boşluklar) varsayar. OP, yalnızca sol veya sağdaki boşlukları kırpmak istediğini söyledi.
SuperElectric

3

Gürültüye çözümüme katkıda bulunuyorum. trimvarsayılan olarak yeni bir dize oluşturur ve trim_in_placeona geçirilen dizeyi değiştirirken değiştirilen dizeyi döndürür. trim++ 11 hareket semantik c işlev destekler.

#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

Bunun nedeni eklenmesi daha basit C ++ 11 içinde yapılabilir back()ve pop_back().

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

OP'nin önerdiği yaklaşım da fena değil - takip edilmesi biraz daha zor.
nobar

3

İşte benim sürüm:

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

Son karakteri kaçırıyorsunuz.
Uzunluktaki

2

Yukarıdaki yöntemler harikadır, ancak bazen rutininizin boşluk olduğunu düşündüğü işlevlerin bir kombinasyonunu kullanmak istersiniz. Bu durumda, işlemleri birleştirmek için functors kullanmak dağınık olabilir, bu yüzden trim için değiştirebileceğim basit bir döngüyü tercih ederim. Burada, SO'daki C versiyonundan kopyalanan biraz değiştirilmiş bir kırpma işlevi. Bu örnekte, alfasayısal olmayan karakterleri kırpıyorum.

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

İşte basit bir uygulama. Böyle basit bir işlem için, muhtemelen herhangi bir özel yapı kullanmamalısınız. Yerleşik isspace () işlevi, beyaz karakterlerin çeşitli biçimlerini halleder, bu yüzden bundan yararlanmalıyız. Ayrıca, dizenin boş olduğu veya sadece bir grup boşluğun olduğu özel durumları da düşünmeniz gerekir. Sol veya sağ kırpma aşağıdaki koddan türetilebilir.

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

İşte std::her yerde yazmak için kullanılmayan ve henüz - constdoğruluk, iterators, STL algorithms, vb.'ye aşina olmayan yeni başlayanlar için anlaşılması kolay bir çözüm .

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

Umarım yardımcı olur...


1

Bu sürüm dahili boşlukları ve alfasayısal olmayanları düzeltir:

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

Yine başka bir seçenek - her iki uçtan bir veya daha fazla karakteri kaldırır.

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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.