web-dev-qa-db-fra.com

printf avec std :: string?

D'après ce que j'ai compris, string est un membre de l'espace de noms std. Pourquoi les événements suivants se produisent-ils?

#include <iostream>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString);
    cin.get();

    return 0;
}

enter image description here

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

140
TheDarkIn1978

Il est en train de compiler parce que printf n'est pas sûr, 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 autre chose que ce à quoi il s'attend ne vous donnera certainement pas les résultats que vous souhaitez. C'est en fait un comportement indéfini, donc tout peut arriver.

Le moyen le plus simple de résoudre ce problème, puisque vous utilisez C++, est de l’imprimer normalement avec _std::cout_, puisque _std::string_ le prend en charge par la surcharge de l’opérateur:

_std::cout << "Follow this command: " << 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 *_ à terminaison nulle. En utilisant votre exemple:

_#include <iostream>
#include <string>
#include <stdio.h>

int main()
{
    using namespace std;

    string myString = "Press ENTER to quit program!";
    cout << "Come up and C++ me some time." << endl;
    printf("Follow this command: %s", myString.c_str()); //note the use of c_str
    cin.get();

    return 0;
}
_

Si vous voulez une fonction qui ressemble à printf, mais tapez safe, examinez les modèles variadiques (C++ 11, pris en charge par tous les principaux compilateurs à partir de MSVC12). Vous pouvez trouver un exemple d'un ici . Je ne connais rien d'implémenté de la sorte dans la bibliothèque standard, mais il peut y en avoir dans Boost, plus précisément boost::format .


[1]: Cela signifie que vous pouvez passer n'importe quel nombre d'arguments, mais la fonction compte sur vous pour lui indiquer le nombre et les types de ces arguments. Dans le cas de printf, cela signifie une chaîne contenant des informations de type codées telles que _%d_, qui signifie int. Si vous mentez à propos du type ou du nombre, la fonction n'a aucun moyen standard de le savoir, bien que certains compilateurs aient la possibilité de vérifier et de donner des avertissements lorsque vous mentez.

214
chris

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

Utilisez cout << your_string; à la place. Court, simple et typesafe. En fait, lorsque vous écrivez en C++, vous voulez généralement éviter complètement printf - c'est un reste de C qui est rarement nécessaire ou utile en C++.

Quant à pourquoi vous devriez utiliser cout au lieu de printf, les raisons sont nombreuses. Voici un échantillon des plus évidentes:

  1. Comme le montre la question, printf n'est pas digne de confiance. Si le type que vous transmettez diffère de celui indiqué dans le spécificateur de conversion, printf tentera d'utiliser tout ce qu'il trouve sur la pile comme s'il s'agissait du type spécifié, donnant ainsi un comportement non défini. Certains compilateurs peuvent avertir à ce sujet dans certaines circonstances, mais d'autres ne peuvent pas/ne veulent 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 de 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'ajouter plus/autres. La plupart des C++ bien écrits devraient utiliser ces types principalement pour implémenter des types orientés vers le problème à résoudre.
  3. Cela rend la mise en forme décente beaucoup plus difficile. Par exemple, lorsque vous imprimez des numéros à lire, vous souhaitez généralement insérer des milliers de séparateurs tous les quelques chiffres. Le nombre exact de chiffres et les caractères utilisés en tant que séparateurs varient, mais cout le couvre également. Par exemple:

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

    Les paramètres régionaux sans nom (le "") choisissent des paramètres régionaux en fonction de la configuration de l'utilisateur. Par conséquent, sur ma machine (configurée pour l’anglais américain), cela s’affiche sous la forme 123,456.78. Pour quelqu'un qui a son ordinateur configuré pour (par exemple) l'Allemagne, cela afficherait quelque chose comme 123.456,78. Pour une personne configurée pour l'Inde, elle serait imprimée sous la forme 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 toujours faux pour tout le monde. La seule façon de contourner le problème consiste à effectuer le formatage séparément, puis à transmettre le résultat sous forme de chaîne à printf, car printf lui-même ne fera pas fait le travail correctement.

  4. Bien qu'elles soient assez compactes, 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% auraient besoin d’examiner les choses pour savoir exactement ce que # dans %#x et comment. diffère de ce que # dans %#f signifie (et oui, ils signifient des choses totalement différentes).
40
Jerry Coffin

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

merci

27

Utilisez l'exemple std :: printf et c_str ():

std::printf("Follow this command: %s", myString.c_str());
6
Adel Ben Hamadi

Printf est en fait assez bon à utiliser si la taille compte. Cela signifie que si vous exécutez un programme dans lequel la mémoire pose un problème, alors printf est en fait une très bonne solution. Cout déplace essentiellement des bits pour laisser de la place à la chaîne, tandis que printf enregistre simplement quelques paramètres et les affiche à l'écran. Si vous deviez compiler un programme hello world simple, printf pourrait le compiler en moins de 60 000 bits, par opposition à cout, il faudrait plus d’un million de bits pour le compiler.

Pour votre situation, id suggère d’utiliser cout simplement parce qu’il est beaucoup plus pratique à utiliser. Bien que, je dirais que printf est quelque chose de bon à savoir.

2
howard howard

printf accepte un nombre variable d'arguments. Ceux-ci ne peuvent avoir que des types Plain Old Data (POD). Le code qui transmet autre chose que POD à printf ne compile que parce que le compilateur suppose que votre format est correct. %s signifie que l'argument respectif est censé être un pointeur sur un char. Dans votre cas, il s'agit d'un std::string pas const char*. printf ne le sait pas car le type d'argument est perdu et est supposé être restauré à partir du paramètre format. Lorsque vous transformez cet argument std::string en const char*, le pointeur obtenu pointe vers une zone de mémoire non pertinente au lieu de la chaîne C souhaitée. Pour cette raison, votre code imprime du charabia.

Bien que printf soit n excellent choix pour l’impression de texte mis en forme , (surtout si vous avez l’intention de le remplir), il peut être dangereux de ne pas activer les avertissements du compilateur. Activez toujours les avertissements , car de telles erreurs sont facilement évitables. Il n'y a aucune raison d'utiliser le mécanisme maladroit std::cout si la famille printf peut effectuer la même tâche de manière beaucoup plus rapide et plus jolie. Assurez-vous simplement que vous avez activé tous les avertissements (-Wall -Wextra) et tout ira bien. Si vous utilisez votre propre implémentation personnalisée printf, vous devez la déclarer avec le mécanisme __attribute__ qui permet au compilateur de comparer la chaîne de format aux paramètres fournis .

1
Hyena

La raison principale en est probablement qu'une chaîne C++ est une structure qui inclut une valeur de longueur courante, pas seulement l'adresse d'une séquence de caractères terminée par 0 octet. Printf et ses parents s'attendent à trouver une telle séquence, pas une structure, et sont donc confus par les chaînes C++.

En ce qui me concerne, j'estime que printf a un emplacement qui ne peut pas être rempli facilement par les fonctionnalités syntaxiques C++, tout comme les structures de table en HTML ont un emplacement qui ne peut pas être rempli par des divs. Comme Dykstra l'a écrit plus tard à propos du goto, il n'avait pas l'intention de créer une religion et ne faisait en réalité que s'opposer à son utilisation pour compenser un code mal conçu.

Ce serait très bien si le projet GNU ajoutait la famille printf à leurs extensions g ++.

1
MMacD