Como verifico se um C ++ std :: string inicia com uma determinada string e converte uma substring em um int?


242

Como implementar o seguinte (pseudocódigo Python) em C ++?

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

(Por exemplo, se argv[1]for --foo=98, então foo_valueé 98.)

Atualização: hesito em analisar o Boost, já que estou apenas tentando fazer uma alteração muito pequena em uma ferramenta simples de linha de comando (prefiro não precisar aprender como vincular e usar o Boost por um período menor) mudança).


Isso também é interessante.
manlio

Respostas:


449

Use uma sobrecarga rfindcom o posparâmetro:

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

Quem precisa de mais alguma coisa? STL puro!

Muitos interpretaram erroneamente que isso significa "pesquisar para trás por toda a cadeia de caracteres procurando o prefixo". Isso daria o resultado errado (por exemplo, string("tititito").rfind("titi")retorna 2 e, quando comparado == 0, retornaria false) e seria ineficiente (examinar toda a cadeia de caracteres em vez de apenas o início). Mas isso não ocorre porque passa o posparâmetro como 0, o que limita a pesquisa a corresponder apenas nessa posição ou anteriormente . Por exemplo:

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
esta resposta deve ser a mais votada, e não a melhor: D por que usar outra biblioteca quando você já tem STL.
Iuliu Atudosiei

@ sweisgerber.dev, estou confuso com sua primeira afirmação. O valor de retorno de findsomente será zero se titiestiver no início da string. Se for encontrado em outro lugar, você obterá um valor de retorno diferente de zero e, se não for encontrado, receberá npostambém um valor diferente de zero. Supondo que estou certo, eu preferiria esta resposta, já que não tenho que trazer nada fora do padrão (sim, eu sei que o Boost está em todo lugar, eu preferiria as principais bibliotecas C ++ para coisas simples como essa).
precisa

@ paxdiablo: você está certo, ele realmente verifica se começa titi, mas a parte de conversão está faltando.
sweisgerber.dev

2
Temos alguma evidência de que isso seja otimizado na maioria dos compiladores? Não encontrei em nenhum outro lugar mencionar que a otimização "find" ou "rfind" é uma prática comum com base no valor de retorno que está sendo verificado.
Superziyi 26/03/19

2
@alcoforado "rfind começará da parte de trás da string ..." Não, isso se aplica apenas à sobrecarga de rfind()que não requer um posparâmetro. Se você usar a sobrecarga que aceita um posparâmetro, ela não pesquisará toda a cadeia, apenas essa posição e a anterior. (Assim como regular, find()com o posparâmetro apenas aparece nessa posição ou mais tarde.) Portanto, se você passar pos == 0, como mostrado nesta resposta, ele literalmente considerará apenas as correspondências nessa posição. Isso já estava explicando tanto na resposta quanto nos comentários.
Arthur Tacca

188

Você faria assim:

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

Procurar uma biblioteca como o Boost.ProgramOptions que faça isso por você também é uma boa ideia.


7
O maior problema com isso é o atoi("123xyz")retorno 123, enquanto o Python int("123xyz")lança uma exceção.
10243 Tom

A solução alternativa, podemos fazer, é um sscanf () e comparar o resultado e o original, para decidir se devemos continuar ou lançar uma exceção.
Roopesh Majeti

1
Ou simplesmente substitua atoipor strtolou strtoll, o que nos permite detectar condições de erro no valor de entrada.
Tom

1
Esta é uma solução melhor do que rfindaquela que depende da otimização para funcionar.
Calmarius 19/09/19

143

Apenas para completar, vou mencionar a maneira C de fazê-lo:

Se stré sua string original, substré a substring que você deseja verificar e, em seguida,

strncmp(str, substr, strlen(substr))

retornará 0se str começar com substr. As funções strncmpe strlenestão no arquivo de cabeçalho C<string.h>

(originalmente publicado por Yaseen Rauf aqui , marcação adicionada)

Para uma comparação que não diferencia maiúsculas de minúsculas, use em strnicmpvez destrncmp .

Esta é a maneira C de fazer isso, para cadeias de caracteres C ++ você pode usar a mesma função como esta:

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

9
na verdade, todo mundo parece apenas ir "boost uso" e Eu, pelo menos sou grato por uma versão stl ou biblioteca OS
Força Gaia

Sim. No entanto, ele assume que a cadeia não possui caracteres nulos. Se não for o caso - deve-se usarmemcmp()
Avishai Y

por que alguém usaria outra coisa senão essa solução simples e bonita?
Adam Zahran

88

Se você já está usando o Boost, pode fazê-lo com algoritmos de sequência de impulso + elenco lexical de aumento:

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

Esse tipo de abordagem, como muitas das outras respostas fornecidas aqui, é aceitável para tarefas muito simples, mas a longo prazo, geralmente é melhor usar uma biblioteca de análise de linha de comando. O Boost possui um ( Boost.Program_options ), que pode fazer sentido se você já estiver usando o Boost.

Caso contrário, uma pesquisa por "analisador de linha de comando c ++" produzirá várias opções.


107
Obter enormes dependências para uma verificação de prefixo de cordas é como atirar em pássaros com cânones.
Tobi 29/05

150
"Usar impulso" é sempre a resposta errada quando alguém pergunta como executar uma operação simples de cadeia de caracteres em C ++.
Glenn Maynard

90
menos 1 por sugerir Boost
uglycoyote 27/10/16

37
Usar o boost aqui é correto, se você já usa o boost no seu projeto.
Alex Che

17
A resposta é prefixada com "Se você estiver usando o Boost ...". Claramente, esta é a resposta certa "... se você estiver usando o Boost". Caso contrário, veja a sugestão de @Thomas
NuSkooler 16/02

82

Código que eu me uso:

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

2
o mais conciso e depende apenas de std :: string, exceto remova o argumento opcional e enganoso.size () no final do substr final.
Ben Bryant

@ Ben-Bryant: Obrigado pela atenção. Não sabia que era opcional.
Hüseyin Yağlı

16
O uso substrleva a cópias desnecessárias. O str.compare(start, count, substr)método usado na resposta de Thomas é mais eficiente. A resposta de razvanco13 possui outro método que evita a cópia usando std::equal.
Felix Dombek

4
@ HüseyinYağlı Thomas uses atoi which is only for windowsHuh? atoitem sido uma função de biblioteca padrão C desde ... sempre. Na verdade, atoié ruim- não porque do Windows specific- mas porque é (1) C, não C ++, e (2) obsoleto mesmo em C (você deve estar usando strtolou uma das outras funções, relacionadas. Porque atoitem sem tratamento de erros, mas, novamente, isso é apenas em C).
Parthian Shot

50

Ninguém usou ainda a função de algoritmo / incompatibilidade STL . Se isso retornar verdadeiro, o prefixo será o prefixo 'toCheck':

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

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

Editar:

Como @ James T. Huggett sugere, std :: equal é mais adequado para a pergunta: A é um prefixo de B? e é um código ligeiramente mais curto:

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

Exemplo 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
Por que não usar std :: equal?
Brice M. Dempsey

Parece bom para mim. Também seria um código mais curto. Suponho, vou ter que editar a resposta agora: p
matiu

2
O uso std::equalde strings tem o lado negativo de não detectar o final da string, portanto, é necessário verificar manualmente se o prefixo é mais curto que a string inteira. (Como corretamente feito no exemplo prog, mas omitido no one-liner acima.)
Felix Dombek

Então, nenhum benefício sobre encontrar?
Андрей Вахрушев

26

Dado que ambas as strings - argv[1]e "--foo"- são C, a resposta de @ FelixDombek a melhor solução.

No entanto, vendo as outras respostas, achei interessante notar que, se o seu texto já estiver disponível como uma std::string, existe uma solução simples, de cópia zero e maximamente eficiente, que não foi mencionada até agora:

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

E se foo já é uma string:

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

6
rfind(x, 0) == 0realmente deve ser definido no padrão comostarts_with
porges

1
Não, porque rfind()(no lugar de startswith()) é muito ineficiente - ele continua pesquisando até o final da string.
Ankostis

4
@ankostis rfind (x) pesquisa desde o final até o início até encontrar x, de fato. Mas rfind (x, 0) começa a pesquisar desde o início (posição = 0) até o início; portanto, ele só procura onde precisa pesquisar; não pesquisa de / até o fim.
Anonymous Coward

18

Com o C ++ 17, você pode usar std::basic_string_viewe com o C ++ 20 std::basic_string::starts_withoustd::basic_string_view::starts_with .

O benefício de, std::string_viewem comparação com std::string- no gerenciamento de memória -, é que ele mantém apenas um ponteiro para uma "string" (sequência contígua de objetos do tipo char) e conhece seu tamanho. Exemplo sem mover / copiar as strings de origem apenas para obter o valor inteiro:

#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 Não, std::atoiestá completamente bem. Ele lança exceções na entrada incorreta (que é tratada neste código). Você tinha outra coisa em mente?
Roi Danton

Você está falando sobre o atoide <cstdlib>? A documentação diz "nunca lança exceções".
Roland Illig 16/10/1919

@RolandIllig Estou me referindo ao seu primeiro comentário. Parece que você está falando por engano, em atoivez de std::atoi. O primeiro não é seguro, enquanto o último é bom. Estou usando o último no código aqui.
Roi Danton

Por favor, prove para mim que std::atoirealmente lança uma exceção, citando uma referência adequada. Até você acreditar, eu não acredito em você, pois seria muito confuso ter os dois ::atoie std::atoiagir de uma maneira completamente diferente.
Roland Illig 17/10/19

4
@RolandIllig Obrigado por ser persistente! Você está certo, foi uma supervisão que std::atoifoi usada em vez de std::stoi. Eu consertei isso.
Roi Danton

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

3
@GregorDoroschenko responde à parte "verificar se a string começa com outra".
Etarion 13/04

1
Eficiente e elegante usando std :: string. Eu aprendi mais com isso.
Michael B

1
pontos extras para ser um one-liner adequado para uso comif (one-liner)
Adam.at.Epsilon

@Roland Illig Por que você acredita que o comportamento nesse caso é indefinido? A expressão retornará false porque substr retorna uma string do mesmo tamanho que o texto, de acordo com en.cppreference.com/w/cpp/string/basic_string/substr
Macsinus

11

Usando STL, isso pode se parecer com:

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
Isso deveria ser if (prefix.size()<=arg.size() && std::equal(...)).
Jared Grubb

10

Correndo o risco de ser queimado por usar construções C, acho que este sscanfexemplo é mais elegante do que a maioria das soluções Boost. E você não precisa se preocupar com o vínculo se estiver executando em qualquer lugar que tenha um intérprete 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;
}

Aqui está um exemplo de saída que demonstra que a solução lida com o lixo inicial / final tão corretamente quanto o código Python equivalente e mais corretamente do que qualquer coisa usando atoi(o que ignorará erroneamente um sufixo não numérico).

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

7
Se argv[i]for "--foo=9999999999999999999999999", o comportamento é indefinido (embora a maioria ou todas as implementações devam se comportar de maneira saudável). Estou assumindo 9999999999999999999999999 > INT_MAX.
Keith Thompson

10

Eu uso std::string::compareembrulhado no método utilitário como abaixo:

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

5

Por que não usar gnu getopts? Aqui está um exemplo básico (sem verificações de segurança):

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

Para o seguinte comando:

$ ./a.out --foo=33

Você vai ter

33

5

Caso você precise de compatibilidade com C ++ 11 e não possa usar o boost, aqui está um drop-in compatível com o boost com um exemplo de uso:

#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

Você também pode usar strstr:

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

mas acho que é bom apenas para cadeias curtas, porque precisa percorrer toda a cadeia quando a cadeia não começa realmente com 'substr'.


2

Ok, por que o uso complicado de bibliotecas e outras coisas? Os objetos String C ++ sobrecarregam o operador [], para que você possa comparar os caracteres .. Como o que acabei de fazer, porque quero listar todos os arquivos em um diretório e ignorar os arquivos invisíveis e os .. e. pseudofiles.

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

É simples assim..



2
@robertwb O Google+ não está mais disponível
_Static_assert 6/06/19

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
Seria ótimo se você evitar colar código sem explicação de código. Obrigado.
Reborn

1
Código ineficiente, continuaria pesquisando além do início da string.
Ankostis

0

Com o C ++ 11 ou superior, você pode usar find()efind_first_of()

Exemplo usando find para encontrar um único caractere:

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

Exemplo usando find para encontrar uma string completa e começando na posição 5:

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

Exemplo usando o find_first_of()e apenas o primeiro caractere, para pesquisar apenas no início:

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

Boa sorte!


Por que não encontrar? rfind (str, 0) não varrerá desnecessariamente uma sequência inteira para fazer uma seleção, pois ela não pode avançar. Veja outros.
usar o seguinte comando

0

Como o C ++ 11 std::regex_searchtambém pode ser usado para fornecer correspondência de expressões ainda mais complexas. O exemplo a seguir também lida com números flutuantes thorugh std::stofe uma conversão subsequente para int.

No entanto, o parseIntmétodo mostrado abaixo pode gerar uma std::invalid_argumentexceção se o prefixo não for correspondido; isso pode ser facilmente adaptado, dependendo da aplicação fornecida:

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

O tipo de mágica do padrão regex é bem detalhado na resposta a seguir .

EDIT: a resposta anterior não realizou a conversão para número inteiro.


0

Começando com C ++ 20, você pode usar o starts_withmétodo

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

Isso é completamente não testado. O princípio é o mesmo que o Python. Requer Boost.StringAlgo e Boost.LexicalCast.

Verifique se a sequência começa com a outra e, em seguida, obtenha a substring ('fatia') da primeira e converta-a usando conversão lexical.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.