¿Cómo verifico si un C ++ std :: string comienza con una cadena determinada y convierto una subcadena en un int?


242

¿Cómo implemento lo siguiente (pseudocódigo de Python) en C ++?

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

(Por ejemplo, si argv[1]es --foo=98, entonces foo_valuees 98).

Actualización: dudo en investigar Boost, ya que solo estoy buscando hacer un cambio muy pequeño en una pequeña herramienta de línea de comandos (preferiría no tener que aprender a vincular y usar Boost para un menor cambio).


Esto también es interesante.
manlio

Respuestas:


449

Utilice una sobrecarga de los rfindcuales tiene el posparámetro:

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

¿Quién necesita algo más? STL puro!

Muchos han leído mal esto para significar "buscar hacia atrás en toda la cadena buscando el prefijo". Eso daría un resultado incorrecto (por ejemplo, string("tititito").rfind("titi")devuelve 2, por lo que, en comparación con == 0, devolvería falso) y sería ineficiente (mirar a través de toda la cadena en lugar de solo el comienzo). Pero no lo hace porque pasa el posparámetro como 0, lo que limita la búsqueda para que solo coincida en esa posición o antes . Por ejemplo:

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 respuesta debería ser la más votada, no la de impulso: D por qué usar otra biblioteca cuando ya tiene STL.
Iuliu Atudosiei

@ sweisgerber.dev, estoy confundido con tu primera disputa. El valor de retorno de findsolo será cero si titiestá al comienzo de la cadena. Si se encuentra en otro lugar, obtendrá un valor de retorno distinto de cero y, si no se encuentra, obtendrá un valor nposque también sea distinto de cero. Suponiendo que tengo razón, preferiría esta respuesta ya que no tengo que traer ningún material no estándar (sí, sé que Boost está en todas partes, prefiero las bibliotecas centrales de C ++ para cosas simples como esta).
paxdiablo

@paxdiablo: tiene razón, de hecho verifica si comienza titi, pero falta la parte de conversión.
sweisgerber.dev

2
¿Tenemos alguna evidencia de que esto está optimizado en la mayoría de los compiladores? No encuentro en ninguna otra parte mencionar que la optimización "encontrar" o "rfind" es una práctica común basada en el valor de retorno con el que se está comprobando.
Superziyi

2
@alcoforado "rfind comenzará desde la parte posterior de la cadena ..." No, eso solo se aplica a la sobrecarga de rfind()que no toma un posparámetro. Si usa la sobrecarga que toma un posparámetro, entonces no buscará toda la cadena, solo esa posición y antes. (Al igual que regular find()conpos parámetro solo se ve en esa posición o más adelante). Entonces, si pasa pos == 0, como se muestra en esta respuesta, literalmente solo considerará las coincidencias en esa posición. Eso ya se explicaba tanto en la respuesta como en los comentarios.
Arthur Tacca

188

Lo harías así:

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

También es una buena idea buscar una biblioteca como Boost.ProgramOptions que haga esto por usted.


77
El mayor problema con esto es que atoi("123xyz")regresa 123, mientras que Python int("123xyz")lanza una excepción.
Tom

La solución alternativa, que podemos hacer, es hacer un sscanf () y comparar el resultado y el original, para decidir si proceder o lanzar una excepción.
Roopesh Majeti

1
O simplemente reemplace atoicon strtolo strtoll, lo que nos permite detectar condiciones de error en el valor de entrada.
Tom

1
Esta es una mejor solución que la rfindque depende de la optimización para funcionar.
Calmarius

143

Solo para completar, mencionaré la forma C de hacerlo:

Si stres su cadena original, substres la subcadena que desea verificar, entonces

strncmp(str, substr, strlen(substr))

volverá 0si str comienza con substr. Las funciones strncmpy strlenestán en el archivo de encabezado C<string.h>

(publicado originalmente por Yaseen Rauf aquí , marcado agregado)

Para una comparación entre mayúsculas y minúsculas, use strnicmp lugar destrncmp .

Esta es la forma en C de hacerlo, para cadenas de C ++ puede usar la misma función como esta:

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

99
de hecho, todo el mundo parece simplemente "usar boost" y por mi parte estoy agradecido por una versión de biblioteca stl o OS
Force Gaia

Si. Sin embargo, se supone que la cadena no tiene caracteres nulos. Si no es el caso - uno debería usarmemcmp()
Avishai Y

¿Por qué alguien usaría algo más que esta simple y hermosa solución?
Adam Zahran el

88

Si ya está utilizando Boost, puede hacerlo con algoritmos de cadena de impulso + lanzamiento léxico de impulso:

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

Este tipo de enfoque, como muchas de las otras respuestas proporcionadas aquí, está bien para tareas muy simples, pero a la larga generalmente es mejor usar una biblioteca de análisis de línea de comandos. Boost tiene uno ( Boost.Program_options ), que puede tener sentido si ya estás usando Boost.

De lo contrario, una búsqueda de "analizador de línea de comando c ++" arrojará una serie de opciones.


107
Obtener grandes dependencias para una verificación de prefijo de cadena es como disparar pájaros con cánones.
Tobi

150
"Usar refuerzo" siempre es la respuesta incorrecta cuando alguien pregunta cómo hacer una operación de cadena simple en C ++.
Glenn Maynard

90
menos 1 por sugerir Boost
uglycoyote

37
Usar boost aquí es correcto, si ya usas boost en tu proyecto.
Alex Che

17
La respuesta tiene el prefijo "Si está utilizando Boost ...". Claramente esta es la respuesta correcta "... si estás usando Boost". Si no, mire la sugerencia de @Thomas
NuSkooler

82

Código que uso yo mismo:

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

2
el más conciso y solo depende de std :: string, excepto que elimina el argumento opcional y engañoso.size () al final del substr final.
Ben Bryant

@ ben-bryant: Gracias por el aviso. No sabía que era opcional.
Hüseyin Yağlı

16
El uso substrconduce a copias innecesarias. El str.compare(start, count, substr)método utilizado en la respuesta de Thomas es más eficiente. La respuesta de razvanco13 tiene otro método que evita la copia mediante el uso std::equal.
Felix Dombek

44
@ HüseyinYağlı Thomas uses atoi which is only for windows¿Eh? atoiha sido una función de biblioteca estándar C desde ... siempre. De hecho, atoies malo, no porque sea específico de Windows, sino porque es (1) C, no C ++, y (2) en desuso incluso en C (debe usar strtoluna u otra de las funciones relacionadas. Porque atoitiene sin manejo de errores. Pero, de nuevo, eso es solo en C, de todos modos).
Parthian Shot

50

Nadie usó el STL función de algoritmo / desajuste todavía. Si esto devuelve verdadero, el prefijo es un prefijo de 'toCheck':

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

Programa completo de ejemplo:

#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 sugiere @James T. Huggett, std :: equal es una mejor opción para la pregunta: ¿Es A un prefijo de B? y es un código ligeramente más corto:

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

Programa completo de ejemplo:

#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 qué no usar std :: equal?
Brice M. Dempsey

Suena bien para mí. Sería un código más corto también. Supongo que tendré que editar la respuesta ahora: p
matiu

2
Usar std::equalfor strings tiene el inconveniente de que no detecta el final de la cadena, por lo que debe verificar manualmente si el prefijo es más corto que toda la cadena. (Como se hizo correctamente en el programa de ejemplo, pero se omitió en la línea de arriba.)
Felix Dombek

Entonces, ¿no hay beneficio sobre rfind?
Андрей Вахрушев

26

Dado que ambas cadenas - argv[1]y "--foo"- son cadenas C, la respuesta de @ FelixDombek la mejor solución.

Sin embargo, al ver las otras respuestas, pensé que valía la pena señalar que, si su texto ya está disponible como un std::string, entonces existe una solución simple, de copia cero y máxima eficiencia que no se ha mencionado hasta ahora:

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

Y si foo ya es una cadena:

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

66
rfind(x, 0) == 0realmente debe ser definido en la norma comostarts_with
PORGES

1
No, porque rfind()(en lugar de startswith()) es muy ineficiente: sigue buscando hasta el final de la cadena.
ankostis

44
@ankostis rfind (x) busca desde el final hasta el comienzo hasta encontrar x, de hecho. Pero rfind (x, 0) comienza a buscar desde el principio (posición = 0) hasta el inicio; entonces solo busca donde necesita buscar; no busca desde / hasta el final.
Anónimo cobarde

18

Con C ++ 17 puede usar std::basic_string_view& con C ++ 20 std::basic_string::starts_witho std::basic_string_view::starts_with.

El beneficio de, std::string_viewen comparación std::stringcon la gestión de la memoria, es que solo contiene un puntero a una "cadena" (secuencia contigua de objetos tipo char) y conoce su tamaño. Ejemplo sin mover / copiar las cadenas de origen solo para obtener el valor entero:

#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::atoiestá completamente bien. Lanza excepciones en la entrada incorrecta (que se maneja en este código). ¿Tenías algo más en mente?
Roi Danton el

¿Estás hablando de la atoide <cstdlib>? La documentación dice "nunca arroja excepciones".
Roland Illig

@RolandIllig Me refiero a tu primer comentario. Parece que estás hablando por error en atoilugar de std::atoi. El primero no es seguro de usar, mientras que el segundo está bien. Estoy usando el último en el código aquí.
Roi Danton

Por favor, demuéstrame que std::atoiefectivamente arroja una excepción, citando una referencia adecuada. Hasta que lo hagas, no te creo, ya que sería muy confuso tener ambos ::atoiy std::atoiactuar de una manera completamente diferente.
Roland Illig

44
@RolandIllig ¡Gracias por ser persistente! Tienes razón, fue un descuido que std::atoise utilizó en lugar de std::stoi. Ya lo arreglé.
Roi Danton el

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

3
@GregorDoroschenko responde a la parte "verificar si la cadena comienza con otra".
etarion

1
Eficiente y elegante usando std :: string. Aprendí más de esto.
Michael B

1
puntos adicionales por ser un trazador de líneas adecuado para usar conif (one-liner)
Adam.at.Epsilon

@Roland Illig ¿Por qué crees que el comportamiento en ese caso no está definido? La expresión devolverá false porque substr devuelve una cadena de la misma longitud que el texto de acuerdo con en.cppreference.com/w/cpp/string/basic_string/substr
Macsinus

11

Usando STL esto podría verse así:

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
Eso debería ser if (prefix.size()<=arg.size() && std::equal(...)).
Jared Grubb

10

A riesgo de ser criticado por usar construcciones en C, creo que este sscanfejemplo es más elegante que la mayoría de las soluciones Boost. ¡Y no tiene que preocuparse por los enlaces si está ejecutando en cualquier lugar que tenga un intérprete de 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;
}

Aquí hay algunos resultados de ejemplo que demuestran que la solución maneja la basura inicial / final tan correctamente como el código Python equivalente, y más correctamente que cualquier cosa que use atoi(que ignorará erróneamente un sufijo no numérico).

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

77
Si argv[i]es así "--foo=9999999999999999999999999", el comportamiento es indefinido (aunque la mayoría o todas las implementaciones deberían comportarse de manera sensata). Asumo 9999999999999999999999999 > INT_MAX.
Keith Thompson

10

Yo uso std::string::compareenvuelto en el método de utilidad como a continuación:

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

5

¿Por qué no usar gnu getopts? Aquí hay un ejemplo básico (sin controles de seguridad):

#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 el siguiente comando:

$ ./a.out --foo=33

Conseguirás

33

5

En caso de que necesite compatibilidad con C ++ 11 y no pueda usar boost, aquí hay un complemento compatible con boost con un ejemplo 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

También puedes usar strstr:

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

pero creo que es bueno solo para cadenas cortas porque tiene que recorrer toda la cadena cuando la cadena en realidad no comienza con 'substr'.


2

Ok, ¿por qué el uso complicado de bibliotecas y otras cosas? Los objetos de cadena de C ++ sobrecargan el operador [], por lo que puede comparar caracteres ... Como lo que acabo de hacer, porque quiero enumerar todos los archivos en un directorio e ignorar los archivos invisibles y ... y. pseudofiles.

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

Es así de simple..



2
@robertwb Google+ ya no está disponible
taStatic_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
Sería genial si evita pegar el código sin la explicación del código. Gracias.
Reborn

1
Código ineficiente, continuaría buscando más allá del inicio de la cadena.
ankostis

0

Con C ++ 11 o superior puedes usar find()yfind_first_of()

Ejemplo usando find para encontrar un único carácter:

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

Ejemplo usando find para encontrar una cadena completa y comenzando desde la posición 5:

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

Ejemplo usando el find_first_of()y solo el primer carácter, para buscar solo al inicio:

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

¡Buena suerte!


¿Por qué no encontrar? rfind (str, 0) no escaneará innecesariamente una cadena completa para hacer una selección ya que no puede avanzar. Ver a otros
usuario2864740

0

Dado que C ++ 11 std::regex_searchtambién se puede utilizar para proporcionar coincidencias de expresiones aún más complejas. El siguiente ejemplo maneja también números flotantes a través de std::stofun lanzamiento posterior aint .

Sin embargo, el parseIntmétodo que se muestra a continuación podría generar una std::invalid_argumentexcepción si el prefijo no coincide; esto se puede adaptar fácilmente según la aplicación dada:

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

El tipo de magia del patrón regex está bien detallado en la siguiente respuesta .

EDITAR: la respuesta anterior no realizó la conversión a entero.


0

Comenzando con C ++ 20, puede usar el 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()));

Esto no se ha probado por completo. El principio es el mismo que el de Python. Requiere Boost.StringAlgo y Boost.LexicalCast.

Verifique si la cadena comienza con la otra cadena, y luego obtenga la subcadena ('corte') de la primera cadena y conviértala usando el reparto léxico.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.