web-dev-qa-db-fra.com

Comment construire une chaîne std :: avec un null intégré?

Si je veux construire une chaîne std :: avec une ligne comme:

std::string my_string("a\0b");

Lorsque je veux avoir trois caractères dans la chaîne résultante (a, null, b), je n'en reçois qu'un. Quelle est la syntaxe appropriée?

81
Bill

Depuis C++ 14

nous avons pu créer littéralement std::string

#include <iostream>
#include <string>

int main()
{
    using namespace std::string_literals;

    std::string s = "pl-\0-op"s;    // <- Notice the "s" at the end
                                    // This is a std::string literal not
                                    // a C-String literal.
    std::cout << s << "\n";
}

Avant C++ 14

Le problème est que le constructeur std::string Qui prend un const char* Suppose que l'entrée est une chaîne C. Les chaînes C sont terminées \0 Et l'analyse s'interrompt donc lorsqu'elle atteint le caractère \0.

Pour compenser cela, vous devez utiliser le constructeur qui construit la chaîne à partir d'un tableau de caractères (pas un C-String). Cela prend deux paramètres - un pointeur sur le tableau et une longueur:

std::string   x("pq\0rs");   // Two characters because input assumed to be C-String
std::string   x("pq\0rs",5); // 5 Characters as the input is now a char array with 5 characters.

Remarque: C++ std::string Est [~ # ~] pas [~ # ~] \0 - terminé (comme suggéré dans d'autres postes). Cependant, vous pouvez extraire un pointeur vers un tampon interne qui contient une chaîne C avec la méthode c_str().

Consultez également réponse de Doug T ci-dessous sur l'utilisation d'un vector<char>.

Consultez également RiaD pour une solution C++ 14.

122
Martin York

Si vous faites de la manipulation comme vous le feriez avec une chaîne de style c (tableau de caractères), pensez à utiliser

std::vector<char>

Vous avez plus de liberté pour le traiter comme un tableau de la même manière que vous traitez une chaîne c. Vous pouvez utiliser copy () pour copier dans une chaîne:

std::vector<char> vec(100)
strncpy(&vec[0], "blah blah blah", 100);
std::string vecAsStr( vec.begin(), vec.end());

et vous pouvez l'utiliser dans de nombreux endroits identiques, vous pouvez utiliser des chaînes en C

printf("%s" &vec[0])
vec[10] = '\0';
vec[11] = 'b';

Naturellement, cependant, vous souffrez des mêmes problèmes que les cordes en C. Vous pouvez oublier votre terminal nul ou écrire au-delà de l'espace alloué.

22
Doug T.

Je n'ai aucune idée pourquoi vous voudriez faire une telle chose, mais essayez ceci:

std::string my_string("a\0b", 3);
13
17 of 26

Quelles nouvelles fonctionnalités les littéraux définis par l'utilisateur ajoutent-ils à C++? présente une réponse élégante: définir

std::string operator "" _s(const char* str, size_t n) 
{ 
    return std::string(str, n); 
}

alors vous pouvez créer votre chaîne de cette façon:

std::string my_string("a\0b"_s);

ou même ainsi:

auto my_string = "a\0b"_s;

Il y a une façon "à l'ancienne":

#define S(s) s, sizeof s - 1 // trailing NUL does not belong to the string

alors vous pouvez définir

std::string my_string(S("a\0b"));
12
anonym

Ce qui suit fonctionnera ...

std::string s;
s.Push_back('a');
s.Push_back('\0');
s.Push_back('b');
8
Andrew Stein

Vous devrez faire attention à cela. Si vous remplacez "b" par un caractère numérique, vous créerez silencieusement la mauvaise chaîne en utilisant la plupart des méthodes. Voir: Règles pour les caractères d'échappement des littéraux de chaîne C++ .

Par exemple, j'ai laissé tomber cet extrait d'allure innocente au milieu d'un programme

// Create '\0' followed by '0' 40 times ;)
std::string str("\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00", 80);
std::cerr << "Entering loop.\n";
for (char & c : str) {
    std::cerr << c;
    // 'Q' is way cooler than '\0' or '0'
    c = 'Q';
}
std::cerr << "\n";
for (char & c : str) {
    std::cerr << c;
}
std::cerr << "\n";

Voici ce que ce programme a produit pour moi:

Entering loop.
Entering loop.

vector::_M_emplace_ba
QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ

C'était ma première déclaration d'impression à deux reprises, plusieurs caractères non imprimables, suivis d'un retour à la ligne, suivi par quelque chose dans la mémoire interne, que je viens d'écraser (puis d'imprimer, montrant qu'il a été écrasé). Le pire de tout, même en compilant cela avec avertissements détaillés et détaillés de gcc ne m'a donné aucune indication de quelque chose de mal, et l'exécution du programme via valgrind ne s'est pas plainte de schémas d'accès à la mémoire incorrects. En d'autres termes, il est complètement indétectable par les outils modernes.

Vous pouvez obtenir ce même problème avec la std::string("0", 100); beaucoup plus simple, mais l'exemple ci-dessus est un peu plus délicat, et donc plus difficile de voir ce qui ne va pas.

Heureusement, C++ 11 nous donne une bonne solution au problème en utilisant la syntaxe de liste d'initialisation. Cela vous évite d'avoir à spécifier le nombre de caractères (ce que, comme je l'ai montré ci-dessus, vous pouvez le faire de manière incorrecte), et évite de combiner des nombres d'échappement. std::string str({'a', '\0', 'b'}) est sans danger pour tout contenu de chaîne, contrairement aux versions qui prennent un tableau de char et une taille.

5
David Stone

En C++ 14, vous pouvez maintenant utiliser des littéraux

using namespace std::literals::string_literals;
std::string s = "a\0b"s;
std::cout << s.size(); // 3
4
RiaD

Mieux vaut utiliser std :: vector <char> si cette question n'est pas uniquement à des fins éducatives.

1
Harold Ekstrom

la réponse de anonym est excellente, mais il existe également une solution non macro en C++ 98:

template <size_t N>
std::string RawString(const char (&ch)[N])
{
  return std::string(ch, N-1);  // Again, exclude trailing `null`
}

Avec cette fonction, RawString(/* literal */) produira la même chaîne que S(/* literal */):

std::string my_string_t(RawString("a\0b"));
std::string my_string_m(S("a\0b"));
std::cout << "Using template: " << my_string_t << std::endl;
std::cout << "Using macro: " << my_string_m << std::endl;

De plus, il y a un problème avec la macro: l'expression n'est pas en fait un std::string Comme écrit, et ne peut donc pas être utilisée par exemple pour une simple affectation-initialisation:

std::string s = S("a\0b"); // ERROR!

... il serait donc préférable d'utiliser:

#define std::string(s, sizeof s - 1)

Évidemment, vous ne devez utiliser que l'une ou l'autre solution dans votre projet et l'appeler comme bon vous semble.

1
Kyle Strand