206 votes

Comment utiliser printf avec std :: string

Ma compréhension est que string est un membre de l'espace de noms std, alors pourquoi se produit-il ce qui suit?

#include 

int main()
{
    using namespace std;

    string myString = "Appuyez sur ENTRER pour quitter le programme!";
    cout << "Venez me dire bonjour en C++." << endl;
    printf("Suivez cette commande: %s", myString);
    cin.get();

    return 0;
}

description de l'image ici

Chaque fois que le programme est exécuté, myString imprime une chaîne de 3 caractères apparemment aléatoire, comme dans la sortie ci-dessus.

8 votes

Juste pour que vous le sachiez, beaucoup de gens critiquent ce livre. Ce que je peux comprendre, car il n'y a pas grand-chose sur la programmation orientée objet, mais je ne pense pas que ce soit aussi mauvais que ce que les gens prétendent.

0 votes

Ouf! eh bien, c'est bon de garder cela à l'esprit pendant que je parcours le livre. Je suis sûr que ce ne sera pas le seul livre en C++ que je lirai au cours de l'année prochaine ou plus, donc j'espère qu'il ne fera pas trop de dégâts :)

0 votes

En utilisant le niveau d'avertissement le plus élevé du compilateur répondrait à votre question - lors de la compilation avec gcc. Comment MSVC gère cela - je ne sais pas.

310voto

chris Points 28950

Mise à jour C++23

Nous disposons enfin de std::print comme moyen d'utiliser std::format pour une sortie directe :

#include 
#include 

int main() {
    // ...
    std::print("Suivez cette commande : {}", myString);
    // ...
}

Cela combine le meilleur des deux approches.

Réponse originale

Cela compile car printf n'est pas sûr rapports aux types, car il utilise des arguments variables dans le sens C1. printf n'a pas d'option pour std::string, seulement une chaîne de style C. Utiliser quelque chose d'autre à la place de ce qu'il attend ne vous donnera définitivement pas les résultats souhaités. C'est en fait un comportement indéfini, donc n'importe quelle chose pourrait se produire.

La manière la plus simple de corriger ceci, puisque vous utilisez du C++, est de l'afficher normalement avec std::cout, car std::string prend en charge cela via la surcharge d'opérateur :

std::cout << "Suivez cette commande : " << myString;

Si, pour une raison quelconque, vous devez extraire la chaîne de style C, vous pouvez utiliser la méthode c_str() de std::string pour obtenir un const char * qui est null-terminée. En utilisant votre exemple :

#include 
#include 
#include 

int main()
{
    using namespace std;

    string myString = "Appuyez sur ENTREE pour quitter le programme!";
    cout << "Venez me voir en C++ à l'occasion." << endl;
    printf("Suivez cette commande : %s", myString.c_str()); //notez l'utilisation de c_str
    cin.get();

    return 0;
}

Si vous souhaitez une fonction similaire à printf, mais sûr au niveau des types, regardez du côté des modèles variadiques (C++11, pris en charge par tous les principaux compilateurs à partir de MSVC12). Vous pouvez trouver un exemple d'une telle fonction ici. Rien de ce que je connais n'est implémenté de cette manière dans la bibliothèque standard, mais il pourrait y en avoir dans Boost, spécifiquement boost::format.


[1] : Cela signifie que vous pouvez passer n'importe quel nombre d'arguments, mais la fonction a besoin que vous lui indiquiez le nombre et les types de ces arguments. Dans le cas de printf, cela signifie une chaîne avec des informations de type encodées comme %d signifiant int. Si vous mentez sur le type ou le nombre, la fonction n'a pas de moyen standard de le savoir, bien que certains compilateurs aient la capacité de vérifier et de donner des avertissements lorsque vous mentez.

0 votes

@MooingDuck, Bon point. C'est dans la réponse de Jerry, mais étant la réponse acceptée, c'est ce que les gens voient, et ils pourraient partir avant de voir les autres. J'ai ajouté cette option afin qu'elle soit la première solution vue et recommandée.

0 votes

Mise à jour : std::format est inclus dans C++20, bien qu'il ne soit pas inclus dans clang pour le moment (je ne suis pas sûr pour gcc)

1 votes

@OvinusReal, Oui, mais il n'y a pas encore de nouvelle fonction pour imprimer dans stdout. std::format seul ne nous aide pas vraiment ici car il n'y a pas de véritable mise en forme en cours (cout << "..." << s; vs. cout << format("... {}", s);).

42voto

Alessandro Pezzato Points 3762

Utilisez myString.c_str() si vous voulez une chaîne de type C (const char*) à utiliser avec printf.

40voto

Jerry Coffin Points 237758

Veuillez ne pas utiliser printf("%s", your_string.c_str());

Utilisez plutôt cout << your_string;. Court, simple et sûr au niveau des types. En fait, lorsque vous écrivez en C++, vous voulez généralement éviter complètement printf -- c'est un reste du C qui est rarement nécessaire ou utile en C++.

Quant à pourquoi vous devriez utiliser cout plutôt que printf, les raisons sont nombreuses. Voici un échantillon de quelques-unes des plus évidentes :

  1. Comme le montre la question, printf n'est pas sûr au niveau des types. Si le type que vous transmettez diffère de celui donné dans le spécificateur de conversion, printf essaiera d'utiliser ce qu'il trouve sur la pile comme s'il s'agissait du type spécifié, entraînant un comportement indéfini. Certains compilateurs peuvent avertir de cela dans certaines circonstances, mais certains compilateurs ne peuvent pas ou ne le feront pas du tout, et aucun ne peut le faire dans toutes les circonstances.

  2. printf n'est pas extensible. Vous ne pouvez lui transmettre que des types primitifs. L'ensemble des spécificateurs de conversion qu'il comprend est codé en dur dans son implémentation, et il n'y a aucun moyen pour vous d'en ajouter d'autres. La plupart des C++ bien écrit devrait utiliser ces types principalement pour implémenter des types orientés vers le problème à résoudre.

  3. Cela rend la mise en forme correcte beaucoup plus difficile. Par exemple évident, lorsque vous imprimez des chiffres pour que les gens les lisent, vous voulez généralement insérer des séparateurs de milliers tous les quelques chiffres. Le nombre exact de chiffres et les caractères utilisés comme séparateurs varient, mais cout le gère également. Par exemple :

    std::locale loc("");
    std::cout.imbue(loc);
    
    std::cout << 123456.78;

    La locale sans nom (le "") choisit une locale basée sur la configuration de l'utilisateur. Par conséquent, sur ma machine (configurée pour l'anglais américain), cela s'affiche comme 123,456.78. Pour quelqu'un qui a configuré son ordinateur pour (disons) l'Allemagne, cela s'afficherait comme 123.456,78. Pour quelqu'un l'ayant configuré pour l'Inde, cela s'afficherait comme 1,23,456.78 (et bien sûr, il y en a beaucoup d'autres). Avec printf j'obtiens exactement un résultat : 123456.78. C'est cohérent, mais c'est systématiquement incorrect pour tout le monde partout. Essentiellement, la seule façon de contourner cela est de formater séparément, puis de transmettre le résultat sous forme de chaîne à printf, car printf simplement ne le fera pas correctement.

  4. Bien qu'ils soient assez compacts, les chaînes de format printf peuvent être assez illisibles. Même parmi les programmeurs C qui utilisent printf pratiquement tous les jours, je suppose qu'au moins 99% devraient chercher des informations pour être sûrs de ce que le # dans %#x signifie, et comment cela diffère de ce que le # dans %#f signifie (et oui, ils ont des significations totalement différentes).

0 votes

Lorsque j'essaie de compiler cout << myString << endl; je reçois l'erreur suivante: Erreur 1 erreur C2679: binaire '<<' : aucun opérateur trouvé qui prend un opérande de type 'std::string' (ou il n'y a pas de conversion acceptable)

11 votes

@TheDarkIn1978: Vous avez probablement oublié d'#inclure . VC++ a quelques particularités dans ses en-têtes qui vous permettront de définir une chaîne de caractères, mais pas de l'envoyer à cout, sans inclure l'en-tête .

43 votes

@Jerry: Je veux juste souligner que l'utilisation de printf est BEAUCOUP plus rapide que l'utilisation de cout lorsqu'on manipule de grandes quantités de données. Ainsi, s'il te plaît, ne dis pas que c'est inutile :D

16voto

Adel Ben Hamadi Points 563

Utilisez std::printf et c_str() exemple :

std::printf("Suivez cette commande : %s", myString.c_str());

3voto

shuhalo Points 1040

Vous pouvez utiliser snprintf pour déterminer le nombre de caractères nécessaires et allouer un tampon de la bonne taille.

int length = std::snprintf(nullptr, 0, "Il ne peut y avoir que %i\n", 1);
char* str = new char[length+1]; // un caractère de plus pour le terminateur nul
std::snprintf(str, length + 1, "Il ne peut y avoir que %i\n", 1);
std::string cppstr(str);
delete[] str;

Il s'agit d'une légère adaptation d'un exemple sur cppreference.com

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X