printf con std :: string?


157

Entiendo que stringes un miembro del stdespacio de nombres, entonces, ¿por qué ocurre lo siguiente?

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

ingrese la descripción de la imagen aquí

Cada vez que se ejecuta el programa, myStringimprime una cadena aparentemente aleatoria de 3 caracteres, como en el resultado anterior.


8
Solo para hacerle saber, mucha gente critica ese libro. Lo que puedo entender, porque no hay mucho sobre programación orientada a objetos, pero no creo que sea tan malo como la gente dice.
Jesse Good

ouf! bueno, es bueno tener esto en mente mientras camino por el libro. Estoy seguro de que no será el único libro de C ++ que leeré en el transcurso del próximo año, así que espero que no haga mucho daño :)
TheDarkIn1978

Usar la advertencia más alta del compilador respondería su pregunta, cuando compila con gcc. Cómo maneja MSVC esto, no lo sé.
Peter VARGA

Respuestas:


237

Está compilando porque printfno es de tipo seguro, ya que utiliza argumentos variables en el sentido C 1 . printfno tiene opción para std::string, solo una cadena de estilo C. Usar algo más en lugar de lo que espera definitivamente no le dará los resultados que desea. En realidad es un comportamiento indefinido, por lo que cualquier cosa podría suceder.

La forma más fácil de solucionar esto, ya que está usando C ++, es imprimirlo normalmente std::cout, ya std::stringque lo soporta mediante la sobrecarga del operador:

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

Si, por alguna razón, necesita extraer la cadena de estilo C, puede usar el c_str()método de std::stringpara obtener una const char *terminación nula. Usando tu ejemplo:

#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 desea una función similar printf, pero segura, busque plantillas variadas (C ++ 11, compatible con todos los compiladores principales a partir de MSVC12). Puedes encontrar un ejemplo de uno aquí . No sé nada implementado de esa manera en la biblioteca estándar, pero puede haber en Boost, específicamente boost::format.


[1]: Esto significa que puede pasar cualquier número de argumentos, pero la función depende de usted para decirle el número y los tipos de esos argumentos. En el caso de printf, eso significa una cadena con información de tipo codificada como %dsignificado int. Si miente sobre el tipo o número, la función no tiene una forma estándar de conocimiento, aunque algunos compiladores tienen la capacidad de verificar y dar advertencias cuando miente.


@ MoooDuck, buen punto. Está en la respuesta de Jerry, pero al ser la respuesta aceptada, esto es lo que la gente ve, y podrían irse antes de ver a los demás. He agregado esa opción para que sea la primera solución vista y la recomendada.
Chris

43

Por favor no use printf("%s", your_string.c_str());

Usar en su cout << your_string;lugar. Corto, simple y seguro. De hecho, cuando escribe C ++, generalmente desea evitarlo por printfcompleto: es un resto de C que rara vez se necesita o es útil en C ++.

En cuanto a por qué debe usar en coutlugar de printf, las razones son numerosas. Aquí hay una muestra de algunos de los más obvios:

  1. Como muestra la pregunta, printfno es de tipo seguro. Si el tipo que pasa difiere del dado en el especificador de conversión, printfintentará usar lo que encuentre en la pila como si fuera el tipo especificado, dando un comportamiento indefinido. Algunos compiladores pueden advertir sobre esto en algunas circunstancias, pero algunos compiladores no pueden / no lo harán en absoluto, y ninguno puede hacerlo en todas las circunstancias.
  2. printfNo es extensible. Solo puede pasarle tipos primitivos. El conjunto de especificadores de conversión que entiende está codificado en su implementación, y no hay forma de agregar más / otros. La mayoría de los C ++ bien escritos deberían usar estos tipos principalmente para implementar tipos orientados al problema que se está resolviendo.
  3. Hace que el formateo decente sea mucho más difícil. Para un ejemplo obvio, cuando imprime números para que la gente los lea, generalmente desea insertar miles de separadores cada pocos dígitos. El número exacto de dígitos y los caracteres utilizados como separadores varían, perocout tiene eso cubierto. Por ejemplo:

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

    La configuración regional sin nombre (la "") selecciona una configuración regional basada en la configuración del usuario. Por lo tanto, en mi máquina (configurada para inglés de EE. UU.), Esto se imprime como 123,456.78. Para alguien que tiene su computadora configurada para (digamos) Alemania, imprimiría algo así 123.456,78. Para alguien con él configurado para India, se imprimiría como 1,23,456.78(y, por supuesto, hay muchos otros). Con printfpuedo obtener exactamente un resultado: 123456.78. Es consistente, pero es consistentemente incorrecto para todos en todas partes. Esencialmente, la única forma de evitarlo es formatear por separado, luego pasar el resultado como una cadena a printf, porque printfsimplemente no hará el trabajo correctamente.

  4. Aunque son bastante compactas, las printfcadenas de formato pueden ser bastante ilegibles. Incluso entre los programadores de C que usan printfprácticamente todos los días, supongo que al menos el 99% necesitaría buscar cosas para asegurarse de lo que significa #in %#xy cómo difiere de lo que significa #in %#f(y sí, significan cosas completamente diferentes )

11
@ TheDarkIn1978: Probablemente se te olvidó #include <string>. VC ++ tiene algunas rarezas en sus encabezados que le permitirán definir una cadena, pero no enviarla cout, sin incluir el <string>encabezado.
Jerry Coffin

28
@Jerry: Solo quiero señalar que usar printf es MUCHO más rápido que usar cout cuando se trata de datos grandes. Por lo tanto, no diga que es inútil: D
Programador

77
@Programador: consulte stackoverflow.com/questions/12044357/… . Resumen: la mayoría de las veces coutes más lento, es porque has usado std::endldonde no deberías.
Jerry Coffin

29
Típica arrogancia experta en C ++. Si printf existe, ¿por qué no usarlo?
kuroi neko

66
OK, perdón por el comentario rápido. Aún así, printf es bastante útil para la depuración y las secuencias, aunque mucho más potentes, tienen el inconveniente de que el código no da ninguna idea de la salida real. Para la salida formateada, printf sigue siendo una alternativa viable, y es una pena que ambos sistemas no puedan cooperar mejor. Sólo mi opinión, por supuesto.
kuroi neko



1

La razón principal es probablemente que una cadena de C ++ es una estructura que incluye un valor de longitud actual, no solo la dirección de una secuencia de caracteres terminados en un byte 0. Printf y sus familiares esperan encontrar una secuencia de este tipo, no una estructura, y por lo tanto se confunden con las cadenas de C ++.

Hablando por mí mismo, creo que printf tiene un lugar que no se puede llenar fácilmente con características sintácticas de C ++, al igual que las estructuras de tabla en html tienen un lugar que no se puede llenar fácilmente con divs. Como Dykstra escribió más tarde sobre el goto, no tenía la intención de comenzar una religión y en realidad solo estaba argumentando en contra de usarlo como un error para compensar el código mal diseñado.

Sería bastante bueno si el proyecto GNU agregara la familia printf a sus extensiones de g ++.


1

Printf es realmente bastante bueno para usar si el tamaño importa. Lo que significa que si está ejecutando un programa donde la memoria es un problema, printf es en realidad una solución muy buena y bajo evaluación. Cout esencialmente cambia los bits para dejar espacio para la cadena, mientras que printf solo toma algún tipo de parámetros y lo imprime en la pantalla. Si compilara un programa simple de hello world, printf podría compilarlo en menos de 60, 000 bits en lugar de cout, tomaría más de 1 millón de bits para compilar.

Para su situación, sugeriría usar cout simplemente porque es mucho más conveniente de usar. Aunque, diría que printf es algo bueno para saber.


1

printfacepta un número variable de argumentos. Esos solo pueden tener tipos de datos antiguos simples (POD). Código que pasa cualquier cosa que no sea POD a printfsolo compila porque el compilador asume que tienes el formato correcto. %ssignifica que se supone que el argumento respectivo es un puntero a a char. En tu caso es un std::stringno const char*. printfno lo sabe porque el tipo de argumento se pierde y se supone que debe restaurarse desde el parámetro de formato. Al convertir ese std::stringargumento enconst char* el puntero resultante, apuntará a alguna región irrelevante de memoria en lugar de la cadena C deseada. Por esa razón, su código imprime galimatías.

Si bien printfes una excelente opción para imprimir texto formateado (especialmente si tiene la intención de tener relleno), puede ser peligroso si no ha habilitado las advertencias del compilador. Siempre active las advertencias porque entonces errores como este son fácilmente evitables. No hay razón para usar el std::coutmecanismo torpe si la printffamilia puede hacer la misma tarea de una manera mucho más rápida y bonita. Solo asegúrate de haber habilitado todas las advertencias ( -Wall -Wextra) y estarás bien. En caso de que use su propia printfimplementación personalizada , debe declararla con el __attribute__mecanismo que permite al compilador verificar la cadena de formato con los parámetros proporcionados .

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.