printf con std :: string?


157

La mia comprensione è che stringè un membro dello stdspazio dei nomi, quindi perché si verifica quanto segue?

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

inserisci qui la descrizione dell'immagine

Ogni volta che il programma viene eseguito, myStringstampa una stringa apparentemente casuale di 3 caratteri, come nell'output sopra.


8
Solo per farti sapere, molte persone criticano quel libro. Che posso capire, perché non c'è molto sulla programmazione orientata agli oggetti, ma non penso che sia così male come sostengono le persone.
Jesse Good,

Ouf! bene, è bene tenerlo a mente mentre mi faccio strada attraverso il libro. Sono sicuro che non sarà l'unico libro in C ++ che leggerò nel corso del prossimo anno o giù di lì, quindi spero che non faccia troppo male :)
TheDarkIn1978

L'uso dell'avviso del compilatore più alto risponderebbe alla tua domanda - durante la compilazione con gcc. Come MSVC gestisce questo - Non lo so.
Peter VARGA,

Risposte:


237

Si sta compilando perché printfnon è sicuro, poiché utilizza argomenti variabili in senso C 1 . printfnon ha alcuna opzione per std::string, solo una stringa in stile C. Usare qualcos'altro al posto di ciò che si aspetta sicuramente non ti darà i risultati desiderati. In realtà è un comportamento indefinito, quindi potrebbe succedere di tutto.

Il modo più semplice per risolvere questo problema, poiché stai usando C ++, è di stamparlo normalmente con std::cout, poiché lo std::stringsupporta attraverso il sovraccarico dell'operatore:

std::cout << "Follow this command: " << myString;

Se, per qualche motivo, è necessario estrarre la stringa in stile C, è possibile utilizzare il c_str()metodo di std::stringper ottenere un valore const char *che termina con null. Usando il tuo esempio:

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

Se si desidera una funzione simile printf, ma digitare safe, esaminare i modelli variadic (C ++ 11, supportato su tutti i principali compilatori a partire da MSVC12). Puoi trovarne un esempio qui . Non c'è nulla che io sappia dell'implementazione del genere nella libreria standard, ma potrebbe esserci in Boost, in particolare boost::format.


[1]: questo significa che puoi passare qualsiasi numero di argomenti, ma la funzione si affida a te per comunicargli il numero e il tipo di tali argomenti. Nel caso di printf, ciò significa una stringa con informazioni di tipo codificate come %dsignificato int. Se menti del tipo o del numero, la funzione non ha un modo standard di sapere, anche se alcuni compilatori hanno la possibilità di controllare e dare avvisi quando menti.


@MooingDuck, buon punto. È nella risposta di Jerry, ma essendo la risposta accettata, questo è ciò che la gente vede e potrebbe andarsene prima di vedere gli altri. Ho aggiunto questa opzione in modo da essere la prima soluzione vista e quella consigliata.
chris

43

Per favore, non usare printf("%s", your_string.c_str());

Usa cout << your_string;invece. Breve, semplice e versatile. In effetti, quando scrivi C ++, generalmente vuoi evitare del printftutto - è un residuo di C che raramente è necessario o utile in C ++.

Per quanto riguarda il motivo per cui dovresti usare coutinvece di printf, i motivi sono numerosi. Ecco un esempio di alcuni dei più ovvi:

  1. Come mostra la domanda, printfnon è sicuro. Se il tipo che passi differisce da quello indicato nello specificatore di conversione, printfproverà a usare tutto ciò che trova nello stack come se fosse il tipo specificato, dando un comportamento indefinito. Alcuni compilatori possono avvertire di ciò in alcune circostanze, ma alcuni compilatori non possono / non lo faranno affatto, e nessuno può in tutte le circostanze.
  2. printfnon è estensibile. Puoi solo passarvi i tipi primitivi. Il set di identificatori di conversione che comprende è codificato nella sua implementazione e non c'è modo di aggiungere altro / altri. La maggior parte del C ++ ben scritto dovrebbe usare questi tipi principalmente per implementare tipi orientati al problema da risolvere.
  3. Rende la formattazione decente molto più difficile. Per un esempio ovvio, quando stampi numeri che le persone possono leggere, in genere desideri inserire migliaia di separatori ogni poche cifre. Il numero esatto di cifre e caratteri usati come separatori varia, ma coutha anche quello coperto. Per esempio:

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

    La locale senza nome (la "") seleziona una locale in base alla configurazione dell'utente. Pertanto, sul mio computer (configurato per l'inglese americano), questo viene stampato come 123,456.78. Per qualcuno che ha il proprio computer configurato per (diciamo) la Germania, stamperebbe qualcosa del genere 123.456,78. Per qualcuno con esso configurato per l'India, verrebbe stampato come 1,23,456.78(e ovviamente ce ne sono molti altri). Con printfottengo esattamente un risultato: 123456.78. È coerente, ma è costantemente sbagliato per tutti ovunque. In sostanza, l'unico modo per aggirare il problema è eseguire la formattazione separatamente, quindi passare il risultato come stringa a printf, perché printfsemplicemente non eseguirà correttamente il lavoro.

  4. Sebbene siano piuttosto compatte, le printfstringhe di formato possono essere illeggibili. Anche tra i programmatori C che usano printfpraticamente ogni giorno, direi almeno il 99% avrebbe bisogno di guardare le cose per essere sicuro di quello che la #in %#xmezzo, e in che modo diverso da quello che l' #a %#fmezzi (e sì, significano cose completamente diverse ).

11
@ TheDarkIn1978: Probabilmente ti sei dimenticato di #include <string>. VC ++ ha delle stranezze nelle intestazioni che ti permetteranno di definire una stringa, ma non di inviarla a cout, senza includere l' <string>intestazione.
Jerry Coffin,

28
@Jerry: Voglio solo sottolineare che l'uso di printf è MOLTO più veloce dell'uso di cout quando si tratta di dati di grandi dimensioni. Quindi, per favore, non dire che è inutile: D
Programmatore,

7
@Programmer: vedere stackoverflow.com/questions/12044357/... . Riepilogo: la maggior parte delle volte coutè più lento, è perché hai usato std::endldove non dovresti.
Jerry Coffin,

29
Tipica arroganza di esperti C ++. Se printf esiste, perché non usarlo?
Kuroi Neko,

6
OK, scusa per il commento scattante. Tuttavia, printf è piuttosto utile per il debug e gli stream, sebbene notevolmente più potenti, hanno lo svantaggio che il codice non dà alcuna idea dell'output effettivo. Per l'output formattato, printf è ancora un'alternativa praticabile ed è un peccato che entrambi i sistemi non possano cooperare meglio. Solo la mia opinione, ovviamente.
Kuroi Neko,

28

utilizzare myString.c_str()se si desidera utilizzare una stringa di tipo c ( const char*) da utilizzare con printf

Grazie



1

Il motivo principale è probabilmente che una stringa C ++ è una struttura che include un valore di lunghezza corrente, non solo l'indirizzo di una sequenza di caratteri terminata da uno 0 byte. Printf e i suoi parenti si aspettano di trovare una tale sequenza, non una struttura, e quindi vengono confusi dalle stringhe C ++.

Parlando da solo, credo che printf abbia un posto che non può essere facilmente riempito dalle funzionalità sintattiche C ++, proprio come le strutture di tabella in html hanno un posto che non può essere facilmente riempito dai div. Come Dykstra scrisse in seguito sul goto, non aveva intenzione di fondare una religione e in realtà stava solo discutendo di non usarlo come un kludge per compensare un codice mal progettato.

Sarebbe abbastanza bello se il progetto GNU aggiungesse la famiglia printf alle loro estensioni g ++.


1

Printf è in realtà abbastanza buono da usare se le dimensioni contano. Significa che se stai eseguendo un programma in cui la memoria è un problema, allora printf è in realtà una soluzione molto valida e scadente. Cout essenzialmente sposta i bit sopra per fare spazio alla stringa, mentre printf accetta solo una sorta di parametri e li stampa sullo schermo. Se dovessi compilare un semplice programma Hello World, printf sarebbe in grado di compilarlo in meno di 60.000 bit invece di cout, richiederebbe oltre 1 milione di bit per compilare.

Per la tua situazione, ti suggerisco di usare cout semplicemente perché è molto più comodo da usare. Tuttavia, direi che printf è qualcosa di buono da sapere.


1

printfaccetta un numero variabile di argomenti. Quelli possono avere solo tipi POD (Plain Old Data). Codice che passa qualsiasi cosa diversa da POD per printfcompilare solo perché il compilatore presuppone che il formato sia corretto. %ssignifica che il rispettivo argomento dovrebbe essere un puntatore a char. Nel tuo caso si tratta di un std::stringno const char*. printfnon lo sa perché il tipo di argomento va perso e dovrebbe essere ripristinato dal parametro format. Quando si trasforma quell'argomento std::stringnel const char*puntatore risultante si punta a una regione di memoria irrilevante invece della stringa C desiderata. Per questo motivo il codice viene stampato senza senso.

Mentre printfè una scelta eccellente per la stampa di testo formattato , (specialmente se si intende avere il riempimento), può essere pericoloso se non sono stati abilitati gli avvisi del compilatore. Abilita sempre gli avvisi perché quindi errori come questo sono facilmente evitabili. Non c'è motivo di usare il std::coutmeccanismo goffo se la printffamiglia può fare lo stesso compito in un modo molto più veloce e più carino. Assicurati di aver abilitato tutti gli avvisi ( -Wall -Wextra) e sarai a posto. Nel caso in cui si utilizzi la propria printfimplementazione personalizzata, è necessario dichiararla con il __attribute__meccanismo che consente al compilatore di verificare la stringa di formato rispetto ai parametri forniti .

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.