Comment puis-je vérifier si une chaîne std :: C ++ commence par une certaine chaîne et convertir une sous-chaîne en int?

242

Comment implémenter les éléments suivants (pseudocode Python) en C ++?

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

(Par exemple, si argv[1]c'est le cas --foo=98, alors l' foo_valueest 98.)

Mise à jour: j'hésite à étudier Boost, car je cherche simplement à apporter une toute petite modification à un simple petit outil en ligne de commande (je préfère ne pas avoir à apprendre à me connecter et à utiliser Boost pour un mineur changement).

Daryl Spitzer
la source
C'est aussi intéressant.
manlio

Réponses:

449

Utilisez une surcharge rfinddont le posparamètre est:

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

Qui a besoin d'autre chose? Pure STL!

Beaucoup ont mal interprété cela pour signifier "rechercher en arrière dans toute la chaîne à la recherche du préfixe". Cela donnerait le mauvais résultat (par exemple string("tititito").rfind("titi")retourne 2 donc par rapport à contre == 0retournerait faux) et ce serait inefficace (regarder à travers la chaîne entière au lieu du simple début). Mais il ne le fait pas car il transmet le posparamètre as 0, ce qui limite la recherche à une correspondance uniquement à cette position ou plus tôt . Par exemple:

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)
Ludovic Aubert
la source
32
cette réponse devrait être la plus votée et non pas la boost: D pourquoi utiliser une autre bibliothèque quand vous avez déjà STL.
Iuliu Atudosiei
@ sweisgerber.dev, je suis confus sur votre premier argument. La valeur de retour de findne sera nulle que si elle titiest au début de la chaîne. S'il est trouvé ailleurs, vous obtiendrez une valeur de retour non nulle et, s'il n'est pas trouvé, vous obtiendrez nposégalement une valeur non nulle. En supposant que j'ai raison, je préférerais cette réponse car je n'ai pas à apporter de choses non standard (oui, je sais que Boost est partout, je préférerais simplement les bibliothèques C ++ de base pour des choses simples comme celle-ci).
paxdiablo
@paxdiablo: vous avez raison, il vérifie en effet si cela commence par titi, mais la partie conversion est manquante.
sweisgerber.dev
2
Avons-nous des preuves que cela est optimisé dans la plupart des compilateurs? Je ne trouve pas ailleurs mentionner que l'optimisation "find" ou "rfind" est une pratique courante basée sur la valeur de retour par rapport à laquelle il vérifie.
Superziyi
2
@alcoforado "rfind commencera à l'arrière de la chaîne ..." Non, cela ne s'applique qu'à la surcharge de rfind()qui ne prend pas de posparamètre. Si vous utilisez la surcharge qui prend un posparamètre, il ne recherchera pas la chaîne entière, seulement cette position et plus tôt. (Tout comme normal find()avec posparamètre ne regarde que dans cette position ou plus tard.) Donc, si vous passez pos == 0, comme indiqué dans cette réponse, il ne considérera littéralement que les correspondances à cette position. Cela était déjà expliqué dans la réponse et les commentaires.
Arthur Tacca
188

Vous le feriez comme ceci:

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

La recherche d'une bibliothèque telle que Boost.ProgramOptions qui le fait pour vous est également une bonne idée.

Thomas
la source
7
Le plus gros problème avec cela est que les atoi("123xyz")retours 123, tandis que Python int("123xyz")lève une exception.
Tom
La solution de contournement, nous pouvons le faire, consiste à utiliser un sscanf () et à comparer le résultat et l'original, pour décider de poursuivre ou de lever l'exception.
Roopesh Majeti
1
Ou remplacez simplement atoipar strtolou strtoll, ce qui nous permet de détecter les conditions d'erreur dans la valeur d'entrée.
Tom
1
C'est une meilleure solution que rfindcelle qui dépend de l'optimisation pour fonctionner.
Calmarius
143

Juste pour être complet, je mentionnerai la manière C de le faire:

Si strest votre chaîne d'origine, substrest la sous-chaîne que vous souhaitez vérifier, puis

strncmp(str, substr, strlen(substr))

reviendra 0si str commence par substr. Les fonctions strncmpet strlensont dans le fichier d'en-tête C<string.h>

(initialement publié par Yaseen Rauf ici , balisage ajouté)

Pour une comparaison insensible à la casse, utilisez strnicmpau lieu de strncmp.

C'est la façon C de le faire, pour les chaînes C ++, vous pouvez utiliser la même fonction comme ceci:

strncmp(str.c_str(), substr.c_str(), substr.size())
Felix Dombek
la source
9
en effet, tout le monde semble juste "utiliser boost" et je suis pour ma part reconnaissant pour une version de bibliothèque stl ou OS
Force Gaia
Oui. Cependant, il suppose que la chaîne ne contient aucun caractère nul. Si ce n'est pas le cas - il faut utilisermemcmp()
Avishai Y
pourquoi quelqu'un utiliserait-il autre chose que cette belle solution simple?
Adam Zahran
88

Si vous utilisez déjà Boost, vous pouvez le faire avec des algorithmes de chaîne boost + boost cast lexical:

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

Ce type d'approche, comme la plupart des autres réponses fournies ici, convient aux tâches très simples, mais à long terme, il est généralement préférable d'utiliser une bibliothèque d'analyse de ligne de commande. Boost en a un ( Boost.Program_options ), ce qui peut avoir du sens si vous utilisez déjà Boost.

Sinon, une recherche de «analyseur de ligne de commande c ++» donnera un certain nombre d'options.

Ferruccio
la source
107
Tirer d'énormes dépendances pour une vérification de préfixe de chaîne, c'est comme tirer sur des oiseaux avec des canons.
Tobi
150
"Use Boost" est toujours la mauvaise réponse quand quelqu'un demande comment faire une simple opération de chaîne en C ++.
Glenn Maynard
90
moins 1 pour avoir suggéré Boost
uglycoyote
37
Utiliser boost ici est correct, si vous utilisez déjà boost dans votre projet.
Alex Che
17
La réponse est préfixée par "Si vous utilisez Boost ...". C'est clairement la bonne réponse "... si vous utilisez Boost". Sinon, regardez la suggestion de @Thomas
NuSkooler
82

Code que j'utilise moi-même:

std::string prefix = "-param=";
std::string argument = argv[1];
if(argument.substr(0, prefix.size()) == prefix) {
    std::string argumentValue = argument.substr(prefix.size());
}
Hüseyin Yağlı
la source
2
le plus concis et ne dépend que de std :: string, sauf supprimer l'argument optionnel et trompeur.size () à la fin du dernier substrat.
Ben Bryant
@ ben-bryant: Merci pour les avertissements. Je ne savais pas que c'était facultatif.
Hüseyin Yağlı
16
L'utilisation substrconduit à une copie inutile. La str.compare(start, count, substr)méthode utilisée dans la réponse de Thomas est plus efficace. La réponse de razvanco13 a une autre méthode qui évite la copie en utilisant std::equal.
Felix Dombek du
4
@ HüseyinYağlı Thomas uses atoi which is only for windowsHuh? atoiest une fonction de bibliothèque standard C depuis ... depuis toujours. En effet, atoiest mauvais- pas parce qu'il est Windows specific- mais parce qu'il est (1) C, pas C ++, et (2) dépréciée même en C (vous devez utiliser strtolou l' une des autres fonctions connexes. Comme atoia pas de gestion d'erreur. Mais, encore une fois, ce n'est qu'en C, de toute façon).
Parthian Shot
50

Personne n'a encore utilisé l' algorithme STL / la fonction de non-concordance . Si cela retourne vrai, prefix est un préfixe de 'toCheck':

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

Exemple de prog complet:

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

Éditer:

Comme le suggère @James T. Huggett, std :: equal est mieux adapté à la question: A est-il un préfixe de B? et est un code légèrement plus court:

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

Exemple de prog complet:

#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;
  }
}
matiu
la source
2
Pourquoi ne pas utiliser std :: equal?
Brice M. Dempsey
Ça me semble bien. Ce serait aussi un code plus court. Je spose, je vais devoir éditer la réponse maintenant: p
matiu
2
L'utilisation std::equalde chaînes a l'inconvénient de ne pas détecter la fin de la chaîne, vous devez donc vérifier manuellement si le préfixe est plus court que la chaîne entière. (Comme correctement fait dans l'exemple de prog, mais omis dans le one-liner ci-dessus.)
Felix Dombek
Donc, aucun avantage sur rfind?
Андрей Вахрушев
26

Étant donné que les deux chaînes - argv[1]et "--foo"- sont des chaînes C, @ FelixDombek's answer la meilleure solution.

En voyant les autres réponses, cependant, j'ai pensé qu'il valait la peine de noter que, si votre texte est déjà disponible en tant que std::string, il existe une solution simple, sans copie et à efficacité maximale qui n'a pas été mentionnée jusqu'à présent:

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

Et si foo est déjà une chaîne:

std::string foo("--foo");
if (text.rfind(foo, 0) == 0)
    foo_value = text.substr(foo.length());
Marcelo Cantos
la source
6
rfind(x, 0) == 0devrait vraiment être défini dans la norme asstarts_with
porges
1
Non, car rfind()(à la place de startswith()) est très inefficace - il continue de chercher jusqu'à la fin de la chaîne.
ankostis
4
@ankostis rfind (x) recherche de la fin au début jusqu'à ce qu'il trouve x, en effet. Mais rfind (x, 0) commence la recherche depuis le début (position = 0) jusqu'au début; il recherche donc uniquement là où il doit être recherché; ne recherche pas de / jusqu'à la fin.
Anonymous Coward
18

Avec C ++ 17, vous pouvez utiliser std::basic_string_view& avec C ++ 20 std::basic_string::starts_withou std::basic_string_view::starts_with.

L'avantage de std::string_viewpar rapport à std::string- en ce qui concerne la gestion de la mémoire - est qu'il ne détient qu'un pointeur vers une "chaîne" (séquence contiguë d'objets de type char) et connaît sa taille. Exemple sans déplacer / copier les chaînes source juste pour obtenir la valeur entière:

#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
}
Roi Danton
la source
1
@RolandIllig Non, std::atoic'est très bien. Il lève des exceptions sur une mauvaise entrée (qui est gérée dans ce code). Aviez-vous autre chose en tête?
Roi Danton
Parlez-vous de la atoide <cstdlib>? La documentation dit "ça ne lève jamais d'exceptions".
Roland Illig
@RolandIllig Je fais référence à votre premier commentaire. Il semble que vous parliez par erreur atoiau lieu de std::atoi. Le premier n'est pas sûr à utiliser, tandis que le second est très bien. J'utilise ce dernier dans le code ici.
Roi Danton
Veuillez me prouver que std::atoijette en effet une exception, en citant une référence appropriée. Jusqu'à ce que vous le fassiez, je ne vous crois pas, car ce serait très déroutant d'avoir les deux ::atoiet d' std::atoiagir d'une manière complètement différente.
Roland Illig
4
@RolandIllig Merci d'être persévérant! Vous avez raison, c'est un oubli qui a std::atoiété utilisé à la place std::stoi. J'ai corrigé ça.
Roi Danton
12
text.substr(0, start.length()) == start
Macsinus
la source
3
@GregorDoroschenko, il répond à la partie "vérifier si la chaîne commence par une autre".
etarion
1
Efficace et élégant en utilisant std :: string. J'en ai le plus appris.
Michael B
1
des points supplémentaires pour être une doublure adaptée à une utilisation avecif (one-liner)
Adam.at.Epsilon
@Roland Illig Pourquoi pensez-vous que le comportement dans ce cas n'est pas défini? L'expression retournera false parce que substr retourne une chaîne de la même longueur que le texte selon en.cppreference.com/w/cpp/string/basic_string/substr
Macsinus
11

En utilisant STL, cela pourrait ressembler à:

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;
}
razvanco13
la source
2
Ça devrait l'être if (prefix.size()<=arg.size() && std::equal(...)).
Jared Grubb
10

Au risque d'être critiqué pour l'utilisation de constructions C, je pense que cet sscanfexemple est plus élégant que la plupart des solutions Boost. Et vous n'avez pas à vous soucier de la liaison si vous exécutez n'importe où qui a un interpréteur 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;
}

Voici un exemple de sortie qui montre que la solution gère les ordures de début / fin aussi correctement que le code Python équivalent, et plus correctement que tout ce qui utilise atoi(qui ignorera par erreur un suffixe non numérique).

$ ./scan --foo=2 --foo=2d --foo='2 ' ' --foo=2'
number: 2
not-a-number
not-a-number
not-a-number
À M
la source
7
Si tel argv[i]est le cas "--foo=9999999999999999999999999", le comportement n'est pas défini (bien que la plupart ou la totalité des implémentations doivent se comporter correctement). Je suppose 9999999999999999999999999 > INT_MAX.
Keith Thompson
10

J'utilise std::string::compareenveloppé dans une méthode utilitaire comme ci-dessous:

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

Pourquoi ne pas utiliser les getopts gnu? Voici un exemple de base (sans contrôles de sécurité):

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

Pour la commande suivante:

$ ./a.out --foo=33

Tu auras

33
Carl Cook
la source
5

Dans le cas où vous avez besoin de la compatibilité C ++ 11 et ne pouvez pas utiliser boost, voici un drop-in compatible avec boost avec un exemple d'utilisation:

#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;
}
vallismortis
la source
2

Vous pouvez également utiliser strstr:

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

mais je pense que c'est bon uniquement pour les chaînes courtes, car il doit parcourir toute la chaîne lorsque la chaîne ne commence pas réellement par «substr».

szx
la source
2

Ok pourquoi l'utilisation compliquée des bibliothèques et des trucs? Les objets C ++ String surchargent l'opérateur [], vous pouvez donc simplement comparer les caractères .. Comme ce que je viens de faire, car je veux répertorier tous les fichiers dans un répertoire et ignorer les fichiers invisibles et les .. et. pseudofiles.

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

C'est si simple..

Nils
la source
2
@robertwb Google+ n'est plus disponible
_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;
}
mois
la source
3
Ce serait formidable, si vous évitez de coller du code sans explication de code. Je vous remercie.
Reborn
1
Code inefficace, continuerait la recherche après le début de la chaîne.
ankostis
0

Avec C ++ 11 ou supérieur, vous pouvez utiliser find()etfind_first_of()

Exemple utilisant find pour trouver un seul caractère:

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

Exemple utilisant find pour trouver une chaîne complète et à partir de la position 5:

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

Exemple utilisant le find_first_of()et uniquement le premier caractère, pour rechercher uniquement au début:

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

Bonne chance!

danger89
la source
Pourquoi ne pas trouver? rfind (str, 0) n'analysera pas inutilement une chaîne entière pour faire une sélection car elle ne peut pas avancer. Voir les autres.
user2864740
0

Depuis C ++ 11 std::regex_searchpeut également être utilisé pour fournir des expressions encore plus complexes. L'exemple suivant gère également les nombres flottants std::stofet une conversion ultérieure enint .

Cependant, la parseIntméthode illustrée ci-dessous peut lever une std::invalid_argumentexception si le préfixe n'est pas mis en correspondance; cela peut être facilement adapté en fonction de l'application donnée:

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

Le type de magie du modèle d'expression régulière est bien détaillé dans la réponse suivante .

EDIT: la réponse précédente n'a pas effectué la conversion en entier.

alextoind
la source
0

À partir de C ++ 20, vous pouvez utiliser la starts_withméthode.

std::string s = "abcd";
if (s.starts_with("abc")) {
    ...
}
Sam Kumar
la source
-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()));

Ceci est complètement non testé. Le principe est le même que celui de Python. Nécessite Boost.StringAlgo et Boost.LexicalCast.

Vérifiez si la chaîne commence par l'autre chaîne, puis récupérez la sous-chaîne («tranche») de la première chaîne et convertissez-la à l'aide d'une conversion lexicale.

blwy10
la source