sprintf - C++ printf con std:: string?
printf string c (7)
Según entiendo, esa string
es un miembro del std
nombres std
, 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;
}
Cada vez que se ejecuta el programa, myString
imprime una cadena aparentemente aleatoria de 3 caracteres, como en el resultado anterior.
Está compilando porque printf
no es seguro, ya que usa argumentos variables en el sentido C 1 . printf
no tiene opción para std::string
, solo una cadena 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 podría pasar cualquier cosa.
La forma más fácil de solucionar esto, ya que está usando C ++, es imprimirlo normalmente con std::cout
, ya que std::string
admite eso a través de la sobrecarga del operador:
std::cout << "Follow this command: " << myString;
Si, por algún motivo, necesita extraer la cadena de estilo C, puede usar el método c_str()
de std::string
para obtener un const char *
que tenga 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 que sea como printf
, pero escriba safe, mire en plantillas variadic (C ++ 11, compatible con todos los compiladores principales a partir de MSVC12). Puedes encontrar un ejemplo de uno here . No hay nada que yo sepa 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 cantidad de argumentos, pero la función depende de usted para contarle el número y los tipos de esos argumentos. En el caso de printf
, eso significa una cadena con información de tipo codificado como %d
significa 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.
La razón principal es, probablemente, que una cadena C ++ es una estructura que incluye un valor de longitud actual, no solo la dirección de una secuencia de caracteres terminados por un byte 0. Printf y sus familiares esperan encontrar tal secuencia, no una estructura, y por lo tanto se confunden con las cadenas de C ++.
Hablando por mi cuenta, creo que printf tiene un lugar que no puede ser llenado fácilmente por las características sintácticas de C ++, así como las estructuras de tablas en html tienen un lugar que no puede ser llenado fácilmente por los 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 usarla como un obstáculo para compensar el código mal diseñado.
Sería bastante agradable si el proyecto GNU añadiera la familia printf a sus extensiones g ++.
No use printf("%s", your_string.c_str());
Use cout << your_string;
en lugar. Corto, simple y tipo seguro. De hecho, cuando escribe C ++, generalmente quiere evitar printf
completo: es un remanente de C que rara vez se necesita o es útil en C ++.
En cuanto a por qué debería usar cout
lugar de printf
, las razones son numerosas. Aquí hay una muestra de algunos de los más obvios:
- Como muestra la pregunta,
printf
no es seguro para tipos. Si el tipo que pasa difiere del especificado en el especificador de conversión,printf
intentará 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 bajo ninguna circunstancia. -
printf
no es extensible. Solo puedes pasarle tipos primitivos. El conjunto de especificadores de conversión que comprende está codificado en su implementación, y no hay forma de que agregue más / otros. La mayoría de los C ++ bien redactados deberían usar estos tipos principalmente para implementar tipos orientados hacia el problema que se está resolviendo. Hace que el formateo decente sea mucho más difícil. Para un ejemplo obvio, cuando está imprimiendo números para que las personas lean, normalmente desea insertar separadores de miles cada pocos dígitos. La cantidad exacta de dígitos y los caracteres utilizados como separadores varía, pero
cout
tiene eso cubierto. Por ejemplo:std::locale loc(""); std::cout.imbue(loc); std::cout << 123456.78;
La configuración regional sin nombre (el "") elige una configuración regional en función de la configuración del usuario. Por lo tanto, en mi máquina (configurada para inglés estadounidense) se imprime como
123,456.78
. Para alguien que tiene su computadora configurada para (digamos) Alemania, imprimiría algo así como123.456,78
. Para alguien con la configuración para India, imprimiría como1,23,456.78
(y, por supuesto, hay muchos otros). Conprintf
obtengo exactamente un resultado:123456.78
. Es consistente, pero es sistemáticamente incorrecto para todos en todas partes. Básicamente, la única forma de evitarlo es hacer el formateo por separado, luego pasar el resultado como una cadena aprintf
, porqueprintf
sí mismo simplemente no hará el trabajo correctamente.- Aunque son bastante compactas, las cadenas de formato de
printf
pueden ser bastante ilegibles. Incluso entre los programadores de C que usanprintf
prácticamente todos los días, supongo que al menos el 99% necesitaría buscar cosas para estar seguro de qué significa el#
en%#x
, y cómo eso difiere de lo que significa el#
en%#f
(y sí, significan cosas completamente diferentes).
Printf es bastante bueno para usar si el tamaño importa. Es decir, si está ejecutando un programa donde la memoria es un problema, entonces printf es en realidad una solución muy buena y bajo evaluación. Cout esencialmente desplaza los bits para dejar espacio para la cadena, mientras que printf simplemente toma algún tipo de parámetro y lo imprime en la pantalla. Si tuviera que compilar un programa hello world simple, 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, id sugiere usar cout simplemente porque es mucho más conveniente de usar. Aunque, yo diría que printf es algo bueno de saber.
Utilice std::printf y c_str () ejemplo:
std::printf("Follow this command: %s", myString.c_str());
utiliza myString.c_str () si quieres una cadena tipo c (const char *) para usar con printf
printf
acepta una cantidad variable de argumentos. Esos solo pueden tener tipos de datos antiguos simples (POD). El código que pasa algo que no sea POD a printf
solo compila porque el compilador supone que tienes tu formato correcto. %s
significa que el argumento respectivo se supone que es un puntero a un char
. En su caso, es una std::string
not const char*
. printf
no lo sabe porque el tipo de argumento se pierde y se supone que debe restaurarse desde el parámetro de formato. Al convertir el argumento std::string
en const char*
el puntero resultante apuntará a una región de memoria irrelevante en lugar de a la cadena C deseada. Por esa razón, su código imprime galimatías.
Si bien printf
es una excelente opción para imprimir texto con formato , (especialmente si tiene la intención de tener relleno), puede ser peligroso si no ha activado las advertencias del compilador. Siempre habilite las advertencias porque estos errores pueden evitarse fácilmente. No hay ninguna razón para utilizar el torpe mecanismo de std::cout
si la familia printf
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 implementación de printf
personalizada, debe declararla con el mecanismo __attribute__
que permite al compilador verificar la cadena de formato con los parámetros proporcionados .