Come posso verificare se uno std :: string C ++ inizia con una determinata stringa e convertire una sottostringa in un int?


242

Come posso implementare il seguente (pseudocodice Python) in C ++?

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

(Ad esempio, se lo argv[1]è --foo=98, allora lo foo_valueè 98.)

Aggiornamento: sono riluttante a esaminare Boost, poiché sto solo cercando di apportare una piccola modifica a un semplice piccolo strumento da riga di comando (preferirei non dover imparare a collegarmi e utilizzare Boost per un minore modificare).


Anche questo è interessante.
manlio,

Risposte:


449

Utilizzare un sovraccarico di rfindcui abbia il posparametro:

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

Chi ha bisogno di qualcos'altro? Pure STL!

Molti hanno letto male questo significato per "cercare all'indietro attraverso l'intera stringa cercando il prefisso". Ciò darebbe un risultato errato (es. string("tititito").rfind("titi")Restituisce 2, quindi se confrontato con == 0restituirebbe false) e sarebbe inefficiente (guardando attraverso l'intera stringa anziché solo l'inizio). Ma non lo fa perché passa il posparametro as 0, che limita la ricerca in modo che corrisponda solo a quella posizione o precedente . Per esempio:

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
questa risposta dovrebbe essere la più votata, non quella boost: D perché usare un'altra libreria quando hai già STL.
Iuliu Atudosiei,

@ sweisgerber.dev, sono confuso sulla tua prima contesa. Il valore restituito da findsarà solo zero se si tititrova all'inizio della stringa. Se viene trovato da qualche altra parte, otterrai un valore di ritorno diverso da zero e, se non viene trovato, otterrai nposanche un valore diverso da zero. Supponendo di avere ragione, preferirei questa risposta dal momento che non devo inserire alcun materiale non standard (sì, lo so che Boost è ovunque, preferirei semplicemente librerie C ++ di base per cose semplici come questa).
paxdiablo,

@paxdiablo: hai ragione, controlla davvero se inizia titi, ma manca la parte di conversione.
sweisgerber.dev,

2
Abbiamo qualche prova che questo sia ottimizzato nella maggior parte dei compilatori? Non trovo altrove menzionare l'ottimizzazione "trova" o "rfind" è una pratica comune basata sul valore di ritorno che sta verificando.
Superziyi,

2
@alcoforado "rfind inizierà dal retro della stringa ..." No, questo vale solo per il sovraccarico di rfind()quello non accetta un posparametro. Se si utilizza il sovraccarico che accetta un posparametro, non cercherà l'intera stringa, ma solo quella posizione e precedenti. (Proprio come il normale find()con il posparametro appare solo in quella posizione o in seguito.) Quindi se passi pos == 0, come mostrato in questa risposta, prenderà in considerazione letteralmente solo le partite in quella posizione. Ciò stava già spiegando sia la risposta che i commenti.
Arthur Tacca,

188

Lo faresti così:

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

Cercare una libreria come Boost.ProgramOptions che fa questo per te è anche una buona idea.


7
Il problema più grande con questo è che atoi("123xyz")ritorna 123, mentre Python int("123xyz")genera un'eccezione.
Tom,

La soluzione alternativa, possiamo fare, è sscanf () e confrontare il risultato e l'originale, per decidere se procedere o generare un'eccezione.
Roopesh Majeti,

1
O semplicemente sostituire atoicon strtolo strtoll, che ci consente di rilevare le condizioni di errore nel valore di input.
Tom,

1
Questa è una soluzione migliore di rfindquella che dipende dall'ottimizzazione per funzionare.
Calmarius,

143

Solo per completezza, menzionerò il modo C per farlo:

Se strè la stringa originale, substrè la sottostringa che si desidera controllare, quindi

strncmp(str, substr, strlen(substr))

tornerà 0se str inizia con substr. Le funzioni strncmpe strlensono nel file di intestazione C.<string.h>

(originariamente pubblicato da Yaseen Rauf qui , aggiunto markup)

Per un confronto senza distinzione tra maiuscole e minuscole, utilizzare strnicmpinvece di strncmp.

Questo è il modo C per farlo, per le stringhe C ++ puoi usare la stessa funzione in questo modo:

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

9
infatti, tutti sembrano semplicemente "usare boost" e io per primo sono grato per una versione della libreria stl o OS
Force Gaia

Sì. Tuttavia, presuppone che la stringa non contenga caratteri null. In caso contrario - si dovrebbe usarememcmp()
Avishai del

perché qualcuno dovrebbe usare qualcosa di diverso da questa semplice bella soluzione?
Adam Zahran,

88

Se stai già utilizzando Boost, puoi farlo con algoritmi di stringa di boost + boost cast lessicale:

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

Questo tipo di approccio, come molte delle altre risposte fornite qui, va bene per compiti molto semplici, ma a lungo termine di solito è meglio usare una libreria di analisi della riga di comando. Boost ne ha uno ( Boost.Program_options ), che può avere senso se si utilizza già Boost.

Altrimenti la ricerca di "parser da riga di comando c ++" produrrà una serie di opzioni.


107
Tirare enormi dipendenze per un controllo del prefisso della stringa è come sparare agli uccelli con i canoni.
Tobi,

150
"Usa Boost" è sempre la risposta sbagliata quando qualcuno chiede come eseguire una semplice operazione di stringa in C ++.
Glenn Maynard,

90
meno 1 per aver suggerito Boost
uglycoyote il

37
Usare boost qui è giusto, se già usi boost nel tuo progetto.
Alex Che,

17
La risposta è preceduta da "Se stai usando Boost ...". Chiaramente questa è la risposta giusta "... se stai usando Boost". Altrimenti, guarda il suggerimento di @Thomas
NuSkooler,

82

Codice che uso me stesso:

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

2
il più conciso e dipende solo da std :: string, tranne per la rimozione dell'argomentazione facoltativa e fuorviante.size () alla fine del substr finale.
Ben Bryant,

@ ben-bryant: grazie per il testa a testa. Non sapevo che fosse facoltativo.
Hüseyin Yağlı,

16
L'uso substrporta a copie non necessarie. Il str.compare(start, count, substr)metodo usato nella risposta di Thomas è più efficiente. La risposta di razvanco13 ha un altro metodo che evita di copiare usando std::equal.
Felix Dombek,

4
@ HüseyinYağlı Thomas uses atoi which is only for windowsHuh? atoiè stata una funzione di libreria standard C da ... sempre. In punto di fatto, atoiè male- non perché di Windows specific- ma perché è (1) C, non C ++, e (2) deprecato anche in C (si dovrebbe utilizzare strtolo una delle altre, funzioni correlate. Perché atoiha nessuna gestione degli errori, ma, ancora una volta, è solo in C).
Parthian Shot

50

Nessuno ha ancora usato l' algoritmo STL / la funzione di mancata corrispondenza . Se questo restituisce true, il prefisso è un prefisso di "toCheck":

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

Esempio completo prog:

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

Modificare:

Come suggerisce @James T. Huggett, std :: equal si adatta meglio alla domanda: A è un prefisso di B? ed è un codice leggermente più corto:

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

Esempio completo prog:

#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
Perché non usare std :: equal?
Brice M. Dempsey,

Per me va bene. Sarebbe anche un codice più breve. Spose, dovrò modificare la risposta ora: p
matiu,

2
L'uso di std::equalfor string ha il rovescio della medaglia che non rileva la fine della stringa, quindi è necessario verificare manualmente se il prefisso è più corto dell'intera stringa. (Come correttamente fatto nell'esempio prog, ma omesso nel one-liner sopra.)
Felix Dombek,

Quindi, nessun beneficio su rfind?
Андрей Вахрушев

26

Dato che entrambe le stringhe - argv[1]e "--foo"- sono stringhe C, la risposta di @FelixDombek è senza dubbio la migliore soluzione.

Vedendo le altre risposte, tuttavia, ho pensato che valesse la pena notare che, se il tuo testo è già disponibile come std::string, allora esiste una soluzione semplice, a zero copie, al massimo dell'efficienza che non è stata menzionata finora:

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

E se foo è già una stringa:

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

6
rfind(x, 0) == 0dovrebbe davvero essere definito nello standard asstarts_with
porges il

1
No, perché rfind()(al posto di startswith()) è molto inefficiente - continua a cercare fino alla fine della stringa.
ankostis

4
@ankostis rfind (x) cerca dalla fine fino all'inizio fino a trovare x, anzi. Ma rfind (x, 0) inizia la ricerca dall'inizio (posizione = 0) fino all'inizio; quindi cerca solo dove deve essere cercato; non cerca da / fino alla fine.
Anonymous Coward,

18

Con C ++ 17 puoi usare std::basic_string_viewe con C ++ 20 std::basic_string::starts_witho std::basic_string_view::starts_with.

Il vantaggio std::string_viewrispetto a std::string- per quanto riguarda la gestione della memoria - è che contiene solo un puntatore a una "stringa" (sequenza contigua di oggetti simili a caratteri) e ne conosce le dimensioni. Esempio senza spostare / copiare le stringhe di origine solo per ottenere il valore intero:

#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 No, std::atoiva benissimo. Genera eccezioni in caso di input errato (che viene gestito in questo codice). Avevi in ​​mente qualcos'altro?
Roi Danton,

Stai parlando del atoida <cstdlib>? La documentazione dice "non genera mai eccezioni".
Roland Illig,

@RolandIllig Mi riferisco al tuo primo commento. Sembra che tu stia parlando erroneamente atoiinvece di std::atoi. Il primo non è sicuro da usare, mentre il secondo va bene. Sto usando quest'ultimo nel codice qui.
Roi Danton,

Per favore, dimostrami che std::atoieffettivamente genera un'eccezione, citando un riferimento adatto. Fino a quando non lo farai, non ti credo poiché sarebbe molto confuso avere entrambi ::atoie std::atoirecitare in un modo completamente diverso.
Roland Illig,

4
@RolandIllig Grazie per essere persistente! Hai ragione, è std::atoistata usata una svista invece di std::stoi. L'ho risolto.
Roi Danton,

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

3
@GregorDoroschenko risponde alla parte "controlla se la stringa inizia con un'altra".
Etarion,

1
Efficiente ed elegante usando std :: string. Ho imparato di più da questo.
Michael B,

1
punti extra per essere un one-liner adatto per l'uso conif (one-liner)
Adam.at.Epsilon

@Roland Illig Perché credi che il comportamento in quel caso non sia definito? L'espressione restituirà false perché substr restituisce una stringa della stessa lunghezza del testo secondo en.cppreference.com/w/cpp/string/basic_string/substr
Macsinus

11

Usando STL questo potrebbe apparire come:

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
Questo dovrebbe essere if (prefix.size()<=arg.size() && std::equal(...)).
Jared Grubb,

10

A rischio di essere infiammato per l'uso di costrutti C, penso che questo sscanfesempio sia più elegante della maggior parte delle soluzioni Boost. E non devi preoccuparti del collegamento se corri ovunque con un interprete Python!

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

Ecco alcuni output di esempio che dimostrano che la soluzione gestisce la spazzatura iniziale / finale correttamente come il codice Python equivalente e più correttamente di qualsiasi altra cosa utilizzi atoi(che ignorerà erroneamente un suffisso non numerico).

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

7
In argv[i]tal caso "--foo=9999999999999999999999999", il comportamento non è definito (sebbene la maggior parte o tutte le implementazioni dovrebbero comportarsi in modo corretto). Sto assumendo 9999999999999999999999999 > INT_MAX.
Keith Thompson,

10

Uso un std::string::comparemetodo di utilità avvolto come di seguito:

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

5

Perché non usare gnu getopts? Ecco un esempio di base (senza controlli di sicurezza):

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

Per il seguente comando:

$ ./a.out --foo=33

Otterrete

33

5

Nel caso in cui sia necessaria la compatibilità C ++ 11 e non è possibile utilizzare boost, ecco un drop-in compatibile con boost con un esempio di utilizzo:

#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

Puoi anche usare strstr:

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

ma penso che sia buono solo per stringhe brevi perché deve passare attraverso l'intera stringa quando la stringa in realtà non inizia con 'substr'.


2

Ok, perché l'uso complicato di librerie e cose? Gli oggetti stringa C ++ sovraccaricano l'operatore [], quindi puoi solo confrontare i caratteri .. Come quello che ho appena fatto, perché voglio elencare tutti i file in una directory e ignorare i file invisibili e il .. e. pseudofiles.

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

È così semplice..



2
@robertwb Google+ non è più disponibile
_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
Sarebbe fantastico se eviti di incollare il codice senza spiegazione del codice. Grazie.
Rinasce il

1
Codice inefficiente, continuerebbe a cercare oltre l'inizio della stringa.
ankostis

0

Con C ++ 11 o versioni successive puoi usare find()efind_first_of()

Esempio usando find per trovare un singolo carattere:

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

Esempio usando find per trovare una stringa completa e partendo dalla posizione 5:

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

Esempio usando find_first_of()solo e il primo carattere, per cercare solo all'inizio:

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

In bocca al lupo!


Perché non trovare? rfind (str, 0) non scansionerà inutilmente un'intera stringa per effettuare una selezione in quanto non può avanzare. Vedi gli altri.
user2864740

0

Poiché C ++ 11 std::regex_searchpuò anche essere utilizzato per fornire una corrispondenza delle espressioni ancora più complessa. L'esempio seguente gestisce anche numeri fluttuanti attraverso std::stofe un cast successivo a int.

Tuttavia, il parseIntmetodo mostrato di seguito potrebbe generare std::invalid_argumentun'eccezione se il prefisso non corrisponde; questo può essere facilmente adattato a seconda dell'applicazione fornita:

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

Il tipo di magia del modello regex è ben dettagliato nella seguente risposta .

EDIT: la risposta precedente non ha eseguito la conversione in numero intero.


0

A partire da C ++ 20, è possibile utilizzare il starts_withmetodo

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

Questo è completamente non testato. Il principio è lo stesso di quello di Python. Richiede Boost.StringAlgo e Boost.LexicalCast.

Controlla se la stringa inizia con l'altra stringa, quindi ottieni la sottostringa ('slice') della prima stringa e convertila usando il cast lessicale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.