printf com std :: string?

157

Meu entendimento é que stringé um membro do stdespaço para nome, então por que ocorre o seguinte?

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

insira a descrição da imagem aqui

Cada vez que o programa é executado, myStringimprime uma seqüência aparentemente aleatória de 3 caracteres, como na saída acima.

TheDarkIn1978
fonte
8
Só para que você saiba, muitas pessoas criticam esse livro. O que posso entender, porque não há muito sobre programação orientada a objetos, mas não acho que seja tão ruim quanto as pessoas afirmam.
Jesse Boa
ouf! bem, é bom ter isso em mente enquanto passo pelo livro. Tenho certeza de que não será a única C ++ livro que eu vou ler ao longo do próximo ano ou assim, então eu espero que não fazer muito damange :)
TheDarkIn1978
Usar o aviso mais alto do compilador responderia sua pergunta - ao compilar com o gcc. Como a MSVC lida com isso - eu não sei.
Peter VARGA

Respostas:

237

Ele está compilando porque printfnão é seguro para o tipo, pois usa argumentos variáveis ​​no sentido C 1 . printfnão tem opção para std::string, apenas uma string no estilo C. Usar outra coisa no lugar do que ele espera definitivamente não lhe dará os resultados desejados. Na verdade, é um comportamento indefinido, para que tudo possa acontecer.

A maneira mais fácil de corrigir isso, já que você está usando C ++, é imprimindo normalmente com std::cout, uma vez std::stringque suporta isso através da sobrecarga do operador:

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

Se, por algum motivo, você precisar extrair a seqüência de caracteres no estilo C, poderá usar o c_str()método de std::stringpara obter uma const char *terminação nula. Usando seu exemplo:

#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 você deseja uma função semelhante printf, mas com segurança, procure modelos variados (C ++ 11, suportado em todos os principais compiladores a partir do MSVC12). Você pode encontrar um exemplo de um aqui . Não há nada que eu saiba sobre isso implementado na biblioteca padrão, mas pode haver no Boost, especificamente boost::format.


[1]: Isso significa que você pode transmitir qualquer número de argumentos, mas a função depende de você para informar o número e os tipos desses argumentos. No caso de printf, isso significa uma string com informações de tipo codificado, como %dsignificado int. Se você mentir sobre o tipo ou número, a função não possui um meio padrão de saber, embora alguns compiladores tenham a capacidade de verificar e emitir avisos quando você mente.

chris
fonte
@MooingDuck, bom ponto. Está na resposta de Jerry, mas sendo a resposta aceita, é isso que as pessoas veem e podem sair antes de ver as outras. Eu adicionei essa opção para ser a primeira solução vista e a recomendada.
chris
43

Por favor não use printf("%s", your_string.c_str());

Use em cout << your_string;vez disso. Curto, simples e seguro. De fato, quando você está escrevendo C ++, geralmente deseja evitarprintf completamente - é uma sobra de C que raramente é necessária ou útil em C ++.

Por que você deve usar em coutvez de printf, os motivos são numerosos. Aqui está uma amostra de alguns dos mais óbvios:

  1. Como mostra a pergunta, printfnão é seguro para o tipo. Se o tipo que você passar for diferente daquele fornecido no especificador de conversão,printf tentará usar o que encontrar na pilha como se fosse o tipo especificado, fornecendo um comportamento indefinido. Alguns compiladores podem alertar sobre isso em algumas circunstâncias, mas alguns não podem / não querem, e nenhum pode em todas as circunstâncias.
  2. printfnão é extensível. Você só pode passar tipos primitivos para ele. O conjunto de especificadores de conversão que ele entende é codificado em sua implementação e não há como adicionar mais / outros. A maioria dos C ++ bem escritos deve usar esses tipos principalmente para implementar tipos orientados para o problema que está sendo resolvido.
  3. Torna a formatação decente muito mais difícil. Para um exemplo óbvio, quando você está imprimindo números para as pessoas lerem, normalmente deseja inserir milhares de separadores a cada poucos dígitos. O número exato de dígitos e os caracteres usados ​​como separadores variam, mas coutisso também abrange. Por exemplo:

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

    O código de idioma sem nome (o "") escolhe um código de idioma com base na configuração do usuário. Portanto, na minha máquina (configurada para inglês dos EUA) isso é impresso como 123,456.78. Para alguém que tem seu computador configurado para (digamos) a Alemanha, ele imprimirá algo como 123.456,78. Para alguém com ele configurado para a Índia, seria impresso como 1,23,456.78(e é claro que existem muitos outros). Com printfRecebo exatamente um resultado: 123456.78. É consistente, mas é constantemente errado para todos em todos os lugares. Essencialmente, a única maneira de contornar isso é fazer a formatação separadamente e passar o resultado como uma string para printf, porque printfela simplesmente não fará o trabalho corretamente.

  4. Embora sejam bastante compactas, as printfseqüências de formato podem ser ilegíveis. Mesmo entre os programadores C que usam printfpraticamente todos os dias, eu acho que pelo menos 99% precisariam procurar coisas para ter certeza do que significa o #in %#xe como isso difere do que o #in %#fsignifica (e sim, eles significam coisas totalmente diferentes )
Jerry Coffin
fonte
11
@ TheDarkIn1978: Você provavelmente esqueceu #include <string>. O VC ++ possui algumas peculiaridades em seus cabeçalhos que permitem definir uma sequência, mas não a envia cout, sem incluir o <string>cabeçalho.
Jerry Coffin
28
@ Jerry: Quero apenas salientar que o uso do printf é MUITO mais rápido do que o cout ao lidar com grandes dados. Assim, por favor, não diga que é inútil: D
Programador
7
@ Programador: consulte stackoverflow.com/questions/12044357/… . Resumo: na maioria das vezes couté mais lento, é porque você usou std::endlonde não deveria.
Jerry Coffin
29
Arrogância típica de especialista em C ++. Se printf existir, por que não usá-lo?
Kuroi neko
6
OK, desculpe pelo comentário rápido. Ainda assim, printf é bastante útil para depuração e os fluxos, embora muito mais poderosos, têm a desvantagem de que o código não dá nenhuma idéia da saída real. Para saída formatada, printf ainda é uma alternativa viável e é uma pena que os dois sistemas não possam cooperar melhor. Apenas minha opinião, claro.
Kuroi neko
28

use myString.c_str()se você deseja que uma string do tipo c ( const char*) use com printf

obrigado

Alessandro Pezzato
fonte
6

Use std :: printf e c_str () exemplo:

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

O principal motivo é provavelmente que uma string C ++ é uma estrutura que inclui um valor de tamanho atual, não apenas o endereço de uma sequência de caracteres terminada por um byte 0. Printf e seus parentes esperam encontrar essa sequência, não uma estrutura, e, portanto, ficam confusos com as seqüências de caracteres C ++.

Falando por mim, acredito que printf tem um local que não pode ser facilmente preenchido por recursos sintáticos do C ++, assim como as estruturas de tabela em html têm um local que não pode ser facilmente preenchido por divs. Como Dykstra escreveu mais tarde sobre o goto, ele não pretendia iniciar uma religião e estava apenas argumentando contra usá-la como argumento para compensar códigos mal projetados.

Seria muito bom se o projeto GNU adicionasse a família printf às suas extensões g ++.

MMacD
fonte
1

Printf é realmente muito bom de usar se o tamanho importa. Ou seja, se você estiver executando um programa em que a memória é um problema, o printf é na verdade uma solução muito boa e insuficiente. Cout essencialmente desloca bits para dar espaço para a string, enquanto printf apenas pega algum tipo de parâmetro e o imprime na tela. Se você compilasse um programa hello world simples, o printf seria capaz de compilá-lo em menos de 60.000 bits em vez de cout, levaria mais de 1 milhão de bits para compilar.

Para sua situação, eu sugiro usar o cout simplesmente porque é muito mais conveniente de usar. Embora, eu diria que printf é algo bom de se saber.

Howard Howard
fonte
1

printfaceita um número variável de argumentos. Esses podem ter apenas tipos de dados antigos simples (POD). Código que transmite qualquer coisa que não seja POD para printfcompilar apenas porque o compilador supõe que você tenha o formato correto. %ssignifica que o argumento respectivo deve ser um ponteiro para a char. No seu caso, é um std::stringnão const char*. printfnão sabe porque o tipo de argumento se perde e deve ser restaurado a partir do parâmetro format Ao transformar esse std::stringargumento emconst char* ponteiro resultante, ele apontará para alguma região irrelevante da memória, em vez da string C desejada. Por esse motivo, seu código imprime sem sentido.

Embora printfseja uma excelente opção para imprimir texto formatado (especialmente se você pretende ter preenchimento), pode ser perigoso se você não tiver ativado os avisos do compilador. Sempre ative avisos, pois erros como esse são facilmente evitáveis. Não há razão para usar o std::coutmecanismo desajeitado se a printffamília puder executar a mesma tarefa de uma maneira muito mais rápida e bonita. Apenas certifique-se de ter ativado todos os avisos ( -Wall -Wextra) e você será bom. Caso você use sua própria printfimplementação personalizada , declare-a com o __attribute__mecanismo que permite ao compilador verificar a cadeia de formatação em relação aos parâmetros fornecidos .

Hiena
fonte