Bir C ++ std :: dizesinin belirli bir dizeyle başlayıp başlamamasını nasıl kontrol ederim ve bir alt dizeyi int'e dönüştürürüm?


242

C ++ 'da (Python sözde kodu) nasıl uygularım?

if argv[1].startswith('--foo='):
    foo_value = int(argv[1][len('--foo='):])

(Örneğin, argv[1]bir --foo=98sonra foo_valueise 98).

Güncelleme: Boost'a bakmaktan çekiniyorum, çünkü basit bir küçük komut satırı aracında çok küçük bir değişiklik yapmaya çalışıyorum (bir minör için Boost'u nasıl bağlayacağınızı ve kullanacağınızı öğrenmek istemem değişiklik).


Bu da ilginç.
manlio

Yanıtlar:


449

Aşağıdaki parametreye rfindsahip bir aşırı yük kullanın pos:

std::string s = "tititoto";
if (s.rfind("titi", 0) == 0) {
  // s starts with prefix
}

Kim başka bir şeye ihtiyaç duyar? Saf STL!

Birçoğu bunu "öneki arayan dizenin tamamında geriye doğru ara" anlamına gelen yanlış okur. Bu yanlış sonuç verir (örn. string("tititito").rfind("titi"), 2 ile karşılaştırıldığında == 0yanlış döndürür) ve verimsiz olur (sadece başlangıç ​​yerine tüm dizeye bakarak). Ancak bunu yapmaz çünkü posparametreyi şu şekilde geçirir 0, bu da aramayı yalnızca o konumda veya daha erken eşleşecek şekilde sınırlar . Örneğin:

std::string test = "0123123";
size_t match1 = test.rfind("123");    // returns 4 (rightmost match)
size_t match2 = test.rfind("123", 2); // returns 1 (skipped over later match)
size_t match3 = test.rfind("123", 0); // returns std::string::npos (i.e. not found)

32
bu cevap en çok oylanan destek değil, D: Niçin STL'niz varsa başka bir kütüphane kullanmalısınız.
Iuliu Atudosiei

@ sweisgerber.dev, ilk çekiştiğiniz konusunda kafam karıştı. Dönüş değeri findeğer irade sadece sıfır titiolan bir başlangıç dizesinin. Başka bir yerde bulunursa, sıfır olmayan bir dönüş değeri alırsınız ve bulunmazsa, npossıfır olmayan bir değer de elde edersiniz . Haklı olduğumu varsayarak, bu cevabı tercih ederim, çünkü standart olmayan herhangi bir şey getirmek zorunda değilim (evet, Boost'un her yerde olduğunu biliyorum, sadece böyle basit şeyler için çekirdek C ++ kütüphanelerini tercih ederim).
paxdiablo

@paxdiablo: haklısın, gerçekten başlayıp başlamadığını kontrol ediyor titi, ama dönüşüm kısmı eksik.
sweisgerber.dev

2
Çoğu derleyicide bunun optimize edildiğine dair kanıtımız var mı? "Bul" ya da "rfind" optimizasyon kontrol ettiği dönüş değerine dayalı yaygın bir uygulama bahseden başka bir yerde bulamıyorum.
Superziyi

2
@alcoforado "rfind dizginin arkasından başlayacak ..." Hayır, bu sadece aşırı yüklenmesi için geçerli rfind()bir posparametre almaz . Bir posparametreyi alan aşırı yükü kullanırsanız, tüm dizeyi değil, yalnızca o konumu ve daha önce arama yapar. ( Parametre find()ile normalde olduğu gibi possadece o pozisyonda veya daha sonra görünür.) Bu nedenle pos == 0, bu cevapta gösterildiği gibi geçerseniz , kelimenin tam anlamıyla sadece bir pozisyondaki maçları dikkate alır. Bu, hem cevapta hem de yorumlarda zaten açıklanmıştı.
Arthur Tacca

188

Bunu şöyle yaparsınız:

std::string prefix("--foo=");
if (!arg.compare(0, prefix.size(), prefix))
    foo_value = atoi(arg.substr(prefix.size()).c_str());

Bunu sizin için yapan Boost.ProgramOptions gibi bir lib aramak da iyi bir fikirdir.


7
Bununla ilgili en büyük sorun atoi("123xyz")geri dönüyor 123, oysa Python'un int("123xyz")bir istisnası var.
Tom

Yapabileceğimiz geçici çözüm, bir sscanf () yöntemine ulaşmak ve sonucu ve orijinali karşılaştırmak, devam etmek veya istisna atmak konusunda karar vermek.
Roopesh Majeti

1
Veya giriş değerindeki hata koşullarını tespit etmemizi sağlayan veya atoiile değiştirin . strtolstrtoll
Tom

1
Bu, rfindçalışmak için optimizasyona bağlı olandan daha iyi bir çözümdür .
Calmarius

143

Sadece bütünlük için, bunu yapmanın C yolundan bahsedeceğim:

Eğer strorijinal dize, substro zaman, kontrol etmek istediğiniz alt dize olduğunu

strncmp(str, substr, strlen(substr))

dönecektir 0eğer str başlar ile substr. Fonksiyonlar strncmpve strlenC başlık dosyasında bulunmaktadır<string.h>

(aslen Yaseen Rauf tarafından buraya gönderildi , işaretleme eklendi)

Büyük / küçük harfe duyarlı olmayan bir karşılaştırma için, strnicmpyerine kullanın strncmp.

Bunu yapmanın C yolu, C ++ dizeleri için aşağıdaki gibi aynı işlevi kullanabilirsiniz:

strncmp(str.c_str(), substr.c_str(), substr.size())

9
Gerçekten, herkes sadece "kullanım artırmak" gitmek gibi görünüyor ve ben biri için bir stl veya OS kütüphane sürümü için müteşekkirim
Force Gaia

Evet. Ancak, dizenin boş karakter içermediğini varsayar. Durum böyle değilse - kişi kullanmalıdırmemcmp()
Avishai Y

neden bu basit güzel çözümden başka bir şey kullanılmasın ki?
Adam Zahran

88

Zaten Boost kullanıyorsanız, boost string algoritmaları + boost lexical cast ile yapabilirsiniz:

#include <boost/algorithm/string/predicate.hpp>
#include <boost/lexical_cast.hpp>

try {    
    if (boost::starts_with(argv[1], "--foo="))
        foo_value = boost::lexical_cast<int>(argv[1]+6);
} catch (boost::bad_lexical_cast) {
    // bad parameter
}

Burada verilen diğer cevapların çoğu gibi bu tür bir yaklaşım çok basit görevler için uygundur, ancak uzun vadede genellikle bir komut satırı ayrıştırma kütüphanesi kullanmaktan daha iyidir. Boost zaten bir Boost kullanıyorsa mantıklı olabilecek bir tane ( Boost.Program_options ) vardır.

Aksi takdirde "c ++ komut satırı ayrıştırıcısı" araması bir dizi seçenek sunar.


107
Bir dize öneki denetimi için büyük bağımlılıklar çekmek, kanonlarla kuş vurmak gibidir.
Tobi

150
Birisi C ++ basit bir dize işlemi yapmak istediğinde "Kullanım Boost" her zaman yanlış cevaptır.
Glenn Maynard

90
Boost
önermek

37
Projenizde zaten boost kullanıyorsanız, burada boost kullanmak doğrudur.
Alex Che

17
Cevabın başına "Yükseltme ... kullanıyorsanız" ifadesi gelir. Açıkçası bu doğru cevap "... Boost kullanıyorsanız". Değilse, @Thomas tarafından öneriye bakın
NuSkooler

82

Kendimi kullandığım kod:

std::string prefix = "-param=";
std::string argument = argv[1];
if(argument.substr(0, prefix.size()) == prefix) {
    std::string argumentValue = argument.substr(prefix.size());
}

2
en özlü ve yalnızca son substr sonunda isteğe bağlı ve yanıltıcı argument.size () kaldırmak dışında std :: string bağlıdır.
Ben Bryant

@ ben-bryant: Başınız için teşekkürler. İsteğe bağlı olduğunu bilmiyordum.
Hüseyin Yağlı

16
Kullanmak substrgereksiz kopyalamaya yol açar. str.compare(start, count, substr)Kullanılan yöntem Thomas'ın cevap daha verimlidir. razvanco13'ün cevabı , kullanarak kopyalamayı önleyen başka bir yönteme sahiptir std::equal.
Felix Dombek

4
@ HüseyinYağlı Thomas uses atoi which is only for windowsHa? atoio zamandan beri bir C standart kütüphane fonksiyonudur. Aslına bakılırsa, atoi's Windows specific- ama onun (1) C C ++, ve (2) bile C kullanımdan kaldırılmış, çünkü (kullandığınız edilmelidir çünkü kötü-olduğu strtolveya diğer ilgili fonksiyonların biri. Çünkü atoisahiptir hata işleme yok ama yine de, bu sadece C, zaten).
Parthian Shot

50

Henüz kimse STL algoritması / uyumsuzluk işlevini kullanmadı. Bu true değerini döndürürse, önek 'toCheck'in önekidir:

std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()

Tam örnek program:

#include <algorithm>
#include <string>
#include <iostream>

int main(int argc, char** argv) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
                  << "Will print true if 'prefix' is a prefix of string" << std::endl;
        return -1;
    }
    std::string prefix(argv[1]);
    std::string toCheck(argv[2]);
    if (prefix.length() > toCheck.length()) {
        std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
                  << "'prefix' is longer than 'string'" <<  std::endl;
        return 2;
    }
    if (std::mismatch(prefix.begin(), prefix.end(), toCheck.begin()).first == prefix.end()) {
        std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck << '"' << std::endl;
        return 0;
    } else {
        std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"' << toCheck << '"' << std::endl;
        return 1;
    }
}

Düzenle:

@James T. Huggett'in öne sürdüğü gibi, std :: equal şu ​​soru için daha uygun: A, B'nin bir öneki mi? ve biraz daha kısa kod:

std::equal(prefix.begin(), prefix.end(), toCheck.begin())

Tam örnek program:

#include <algorithm>
#include <string>
#include <iostream>

int main(int argc, char **argv) {
  if (argc != 3) {
    std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
              << "Will print true if 'prefix' is a prefix of string"
              << std::endl;
    return -1;
  }
  std::string prefix(argv[1]);
  std::string toCheck(argv[2]);
  if (prefix.length() > toCheck.length()) {
    std::cerr << "Usage: " << argv[0] << " prefix string" << std::endl
              << "'prefix' is longer than 'string'" << std::endl;
    return 2;
  }
  if (std::equal(prefix.begin(), prefix.end(), toCheck.begin())) {
    std::cout << '"' << prefix << '"' << " is a prefix of " << '"' << toCheck
              << '"' << std::endl;
    return 0;
  } else {
    std::cout << '"' << prefix << '"' << " is NOT a prefix of " << '"'
              << toCheck << '"' << std::endl;
    return 1;
  }
}

2
Neden std :: equal kullanmıyorsunuz?
Brice M. Dempsey

Bana uyar. Daha kısa kod da olurdu. Ben, şimdi cevabı düzenlemek zorunda kalacağım: p
matiu

2
std::equalDizeleri kullanmanın , dize ucunu algılamamasının dezavantajı vardır, bu nedenle önekin tüm dizeden daha kısa olup olmadığını manuel olarak kontrol etmeniz gerekir. (Örnek
programda

Yani, rfind'e göre bir faydası yok mu?
Андрей Вахрушев

26

- Her iki dizeleri göz önüne alındığında argv[1]ve "--foo"- C dizeleri FelixDombek cevabı @ eller aşağı en iyi çözüm olduğunu.

Bununla birlikte, diğer cevapları görerek, metniniz zaten mevcutsa std::string, o zamana kadar bahsedilmeyen basit, sıfır kopya, maksimum verimli bir çözümün var olduğunu belirtmeye değer olduğunu düşündüm :

const char * foo = "--foo";
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(strlen(foo));

Ve foo zaten bir dize ise:

std::string foo("--foo");
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(foo.length());

6
rfind(x, 0) == 0gerçekten standart olarak tanımlanmalıdırstarts_with
porges

1
Hayır, çünkü rfind()(yerine startswith()) çok verimsiz - dizenin sonuna kadar aramaya devam ediyor.
ankostis

4
@ankostis rfind (x) sondan başlayarak x'i bulana kadar arama yapar. Ancak rfind (x, 0) başlangıçtan (konum = 0) başlangıca kadar aramaya başlar; bu yüzden yalnızca araması gereken yerleri arar; sonuna kadar / sonuna kadar arama yapmaz.
Anonim Korkak

18

C ++ 17 std::basic_string_viewile C ++ 20 std::basic_string::starts_withveya ile birlikte kullanabilirsiniz std::basic_string_view::starts_with.

Yararının std::string_viewkıyasla std::stringilgili bellek yönetimi - - sadece bir "dizge" için bir işaretçi (kömürün benzeri nesnelerin bitişik sekans) sahip olduğu ve boyutu bilmemesidir. Tam sayı değerini almak için kaynak dizeleri taşımadan / kopyalamadan örnek:

#include <exception>
#include <iostream>
#include <string>
#include <string_view>

int main()
{
    constexpr auto argument = "--foo=42"; // Emulating command argument.
    constexpr auto prefix = "--foo=";
    auto inputValue = 0;

    constexpr auto argumentView = std::string_view(argument);
    if (argumentView.starts_with(prefix))
    {
        constexpr auto prefixSize = std::string_view(prefix).size();
        try
        {
            // The underlying data of argumentView is nul-terminated, therefore we can use data().
            inputValue = std::stoi(argumentView.substr(prefixSize).data());
        }
        catch (std::exception & e)
        {
            std::cerr << e.what();
        }
    }
    std::cout << inputValue; // 42
}

1
@RolandIllig Hayır, std::atoitamamen iyi. Hatalı girdiye (bu kodda işlenir) istisnalar atar. Aklınızda başka bir şey mi var?
Roi Danton

Bahsediyorsun atoigelen <cstdlib>? Dokümantasyon diyor "o istisnalarını atar asla".
Roland Illig

@RolandIllig İlk yorumunuza atıfta bulunuyorum. Görünüşe göre, atoibunun yerine yanlışlıkla konuşuyorsunuz std::atoi. Birincisi kullanmak güvenli değil, ikincisi gayet iyi. Buradaki kodda ikincisini kullanıyorum.
Roi Danton

Lütfen bana, std::atoiuygun bir referans göstererek gerçekten bir istisna getirdiğini kanıtlayın . Yapana kadar sana inanmıyorum çünkü ikisine birden sahip olmak ::atoive std::atoitamamen farklı bir şekilde davranmak çok kafa karıştırıcı olurdu .
Roland Illig

4
@RolandIllig Kalıcı olduğunuz için teşekkürler! Haklısın, std::atoiyerine kullanılan bir gözetimdi std::stoi. Bunu düzelttim.
Roi Danton

12
text.substr(0, start.length()) == start

3
@GregorDoroschenko "dize başka bir ile başlayıp başlamadığını kontrol et" kısmı cevap verir.
erteleme

1
Std :: string kullanarak verimli ve zarif. Bundan en çok şey öğrendim.
Michael B

1
if (one-liner)
Adam.at.Epsilon

@Roland Illig Bu durumda davranışın neden tanımsız olduğuna inanıyorsunuz? Altstr
Macsinus

11

STL kullanmak şöyle görünebilir:

std::string prefix = "--foo=";
std::string arg = argv[1];
if (prefix.size()<=arg.size() && std::equal(prefix.begin(), prefix.end(), arg.begin())) {
  std::istringstream iss(arg.substr(prefix.size()));
  iss >> foo_value;
}

2
Öyle olmalı if (prefix.size()<=arg.size() && std::equal(...)).
Jared Grubb

10

C yapılarını kullanmak için alev alma riski altında, bu sscanförneğin çoğu Boost çözümünden daha zarif olduğunu düşünüyorum . Python tercümanı olan herhangi bir yerde koşuyorsanız, bağlantı hakkında endişelenmenize gerek yok!

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
    for (int i = 1; i != argc; ++i) {
        int number = 0;
        int size = 0;
        sscanf(argv[i], "--foo=%d%n", &number, &size);
        if (size == strlen(argv[i])) {
            printf("number: %d\n", number);
        }
        else {
            printf("not-a-number\n");
        }
    }
    return 0;
}

Çözümde, önde gelen / sondaki çöpleri eşdeğer Python kodu kadar doğru ve kullanan her şeyden daha doğru atoi(sayısal olmayan bir soneki yanlışlıkla yok sayar) gösteren bazı örnek çıktılar .

$ ./scan --foo=2 --foo=2d --foo='2 ' ' --foo=2'
number: 2
not-a-number
not-a-number
not-a-number

7
Öyleyse argv[i], "--foo=9999999999999999999999999"davranış tanımlanmamıştır (ancak uygulamaların çoğu veya tümünün uygun davranması gerekir). Sanırım 9999999999999999999999999 > INT_MAX.
Keith Thompson

10

Ben std::string::compareaşağıdaki gibi yarar yöntemi sarılmış kullanın :

static bool startsWith(const string& s, const string& prefix) {
    return s.size() >= prefix.size() && s.compare(0, prefix.size(), prefix) == 0;
}

5

Neden GNU Getopts kullanmıyorsunuz? Temel bir örnek (güvenlik kontrolleri olmadan):

#include <getopt.h>
#include <stdio.h>

int main(int argc, char** argv)
{
  option long_options[] = {
    {"foo", required_argument, 0, 0},
    {0,0,0,0}
  };

  getopt_long(argc, argv, "f:", long_options, 0);

  printf("%s\n", optarg);
}

Aşağıdaki komut için:

$ ./a.out --foo=33

Alacaksın

33

5

C ++ 11 uyumluluğuna ihtiyacınız varsa ve boost kullanamazsanız, işte bir kullanım örneğiyle boost uyumlu bir bırakma:

#include <iostream>
#include <string>

static bool starts_with(const std::string str, const std::string prefix)
{
    return ((prefix.size() <= str.size()) && std::equal(prefix.begin(), prefix.end(), str.begin()));
}

int main(int argc, char* argv[])
{
    bool usage = false;
    unsigned int foos = 0; // default number of foos if no parameter was supplied

    if (argc > 1)
    {
        const std::string fParamPrefix = "-f="; // shorthand for foo
        const std::string fooParamPrefix = "--foo=";

        for (unsigned int i = 1; i < argc; ++i)
        {
            const std::string arg = argv[i];

            try
            {
                if ((arg == "-h") || (arg == "--help"))
                {
                    usage = true;
                } else if (starts_with(arg, fParamPrefix)) {
                    foos = std::stoul(arg.substr(fParamPrefix.size()));
                } else if (starts_with(arg, fooParamPrefix)) {
                    foos = std::stoul(arg.substr(fooParamPrefix.size()));
                }
            } catch (std::exception& e) {
                std::cerr << "Invalid parameter: " << argv[i] << std::endl << std::endl;
                usage = true;
            }
        }
    }

    if (usage)
    {
        std::cerr << "Usage: " << argv[0] << " [OPTION]..." << std::endl;
        std::cerr << "Example program for parameter parsing." << std::endl << std::endl;
        std::cerr << "  -f, --foo=N   use N foos (optional)" << std::endl;
        return 1;
    }

    std::cerr << "number of foos given: " << foos << std::endl;
}

2

Ayrıca şunları kullanabilirsiniz strstr:

if (strstr(str, substr) == substr) {
    // 'str' starts with 'substr'
}

ancak dize aslında 'substr' ile başlamadığı zaman tüm dize döngü gerekir çünkü sadece kısa dizeleri için iyi olduğunu düşünüyorum.


2

Peki neden kütüphanelerin ve materyallerin karmaşık kullanımı? C ++ String nesneleri [] operatörünü aşırı yüklüyor, bu yüzden sadece karakterleri karşılaştırabilirsiniz .. Sadece yaptığım gibi, çünkü bir dizindeki tüm dosyaları listelemek ve görünmez dosyaları ve .. ve yoksaymak istiyorum. pseudofiles.

while ((ep = readdir(dp)))
{
    string s(ep->d_name);
    if (!(s[0] == '.')) // Omit invisible files and .. or .
        files.push_back(s);
}

Bu kadar basit..



2
@robertwb Google+ artık kullanılamıyor
_Static_assert

0
std::string text = "--foo=98";
std::string start = "--foo=";

if (text.find(start) == 0)
{
    int n = stoi(text.substr(start.length()));
    std::cout << n << std::endl;
}

3
Kod açıklaması olmadan kod yapıştırmaktan kaçınırsanız harika olur. Teşekkür ederim.
Reborn

1
Verimsiz kod, dizenin başından itibaren aramaya devam eder.
ankostis

0

C ++ 11 veya daha yüksek bir sürümle find()vefind_first_of()

Tek bir karakter bulmak için find işlevini kullanan örnek:

#include <string>
std::string name = "Aaah";
size_t found_index = name.find('a');
if (found_index != std::string::npos) {
    // Found string containing 'a'
}

Tam bir dize bulmak ve 5. konumdan başlayarak find işlevini kullanan örnek:

std::string name = "Aaah";
size_t found_index = name.find('h', 3);
if (found_index != std::string::npos) {
    // Found string containing 'h'
}

find_first_of()Yalnızca başlangıçta arama yapmak için ve yalnızca ilk karakteri kullanan örnek :

std::string name = ".hidden._di.r";
size_t found_index = name.find_first_of('.');
if (found_index == 0) {
    // Found '.' at first position in string
}

İyi şanslar!


Neden bulmuyorsun? rfind (str, 0), ilerleyemediği için seçim yapmak için dizenin tamamını gereksiz yere taramaz. Diğerlerine bakın.
user2864740

0

C ++ 11 std::regex_searchaynı zamanda daha karmaşık ifadeler eşleşmesi sağlamak için de kullanılabilir. Aşağıdaki örnek, kayan sayıları std::stofve ardından gelenint .

Ancak, önek eşleşmezse, parseIntaşağıda gösterilen yöntem bir std::invalid_argumentistisna oluşturabilir; verilen uygulamaya bağlı olarak kolayca uyarlanabilir:

#include <iostream>
#include <regex>

int parseInt(const std::string &str, const std::string &prefix) {
  std::smatch match;
  std::regex_search(str, match, std::regex("^" + prefix + "([+-]?(?=\\.?\\d)\\d*(?:\\.\\d*)?(?:[Ee][+-]?\\d+)?)$"));
  return std::stof(match[1]);
}

int main() {
    std::cout << parseInt("foo=13.3", "foo=") << std::endl;
    std::cout << parseInt("foo=-.9", "foo=") << std::endl;
    std::cout << parseInt("foo=+13.3", "foo=") << std::endl;
    std::cout << parseInt("foo=-0.133", "foo=") << std::endl;
    std::cout << parseInt("foo=+00123456", "foo=") << std::endl;
    std::cout << parseInt("foo=-06.12e+3", "foo=") << std::endl;

//    throw std::invalid_argument
//    std::cout << parseInt("foo=1", "bar=") << std::endl;

    return 0;
}

Normal ifade modelinin büyüsü aşağıdaki cevapta ayrıntılı olarak açıklanmıştır .

EDIT: önceki yanıt tamsayıya dönüştürme gerçekleştirmedi.


0

C ++ 20 ile başlayarak starts_withyöntemi kullanabilirsiniz .

std::string s = "abcd";
if (s.starts_with("abc")) {
    ...
}

-3
if(boost::starts_with(string_to_search, string_to_look_for))
    intval = boost::lexical_cast<int>(string_to_search.substr(string_to_look_for.length()));

Bu tamamen denenmemiş. Prensip Python ile aynıdır. Boost.StringAlgo ve Boost.LexicalCast gerektirir.

Dizenin diğer dizeyle başlayıp başlamadığını kontrol edin ve ardından ilk dizenin alt dizesini ('dilim') alın ve sözlü yayın kullanarak dönüştürün.

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.