printf с помощью std :: string?
Я понимаю, что string
является членом пространства имен std
, поэтому почему происходит следующее:
#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;
}
Каждый раз, когда программа запускается, myString
печатает кажущуюся случайную строку из 3-х символов, например, на выходе выше.
Ответы
Ответ 1
Он компилируется, потому что printf
небезопасен, так как использует переменные аргументы в C-смысле 1. printf
нет опции для std::string
, только std::string
в стиле C. Использование чего-то другого вместо того, что он ожидает, определенно не даст вам желаемых результатов. Это на самом деле неопределенное поведение, так что может случиться что угодно.
Самый простой способ исправить это, поскольку вы используете C++, это печатать его обычно с помощью std::cout
, поскольку std::string
поддерживает это через перегрузку операторов:
std::cout << "Follow this command: " << myString;
Если по какой-то причине вам нужно извлечь строку в стиле C, вы можете использовать метод c_str()
std::string
чтобы получить const char *
, заканчивающийся нулем. Используя ваш пример:
#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;
}
Если вам нужна функция, похожая на printf
, но с типом safe, посмотрите шаблоны с переменным числом аргументов (C++ 11, поддерживается во всех основных компиляторах начиная с MSVC12). Вы можете найти пример одного здесь. Я ничего не знаю о такой реализации в стандартной библиотеке, но может быть в Boost, особенно boost::format
.
[1]: Это означает, что вы можете передать любое количество аргументов, но функция полагается на вас, чтобы сообщить количество и типы этих аргументов. В случае printf
это означает строку с закодированной информацией о типе, такой как %d
означает int
. Если вы лжете о типе или числе, функция не имеет стандартного способа узнать, хотя некоторые компиляторы имеют возможность проверять и выдавать предупреждения, когда вы лжете.
Ответ 2
Пожалуйста, не используйте printf("%s", your_string.c_str());
Используйте cout << your_string;
вместо этого. Короткие, простые и типичные. Фактически, когда вы пишете С++, вы вообще хотите избежать printf
целиком - это остатки от C, которые редко нужны или полезны в С++.
Что касается почему, вы должны использовать cout
вместо printf
, причины многочисленны. Здесь выборка из нескольких наиболее очевидных:
- Как показывает вопрос,
printf
не является безопасным для типов. Если тип, который вы передаете, отличается от заданного в спецификаторе преобразования, printf
будет пытаться использовать все, что он находит в стеке, как если бы это был указанный тип, давая поведение undefined. Некоторые компиляторы могут предупреждать об этом при некоторых обстоятельствах, но некоторые компиляторы вообще не могут/не будут, и ни при каких обстоятельствах никто не сможет.
-
printf
не расширяется. Вы можете передавать только примитивные типы. Набор спецификаторов преобразования, которые он понимает, жестко закодирован в его реализации, и нет никакого способа добавить вас к другим/другим. Большинство хорошо написанных С++ должны использовать эти типы прежде всего для реализации типов, ориентированных на решаемую проблему.
-
Это делает достойное форматирование намного сложнее. Для очевидного примера, когда вы печатаете числа для чтения людей, вы обычно хотите вставлять тысячи разделителей каждые несколько цифр. Точное количество цифр и символов, используемых в качестве разделителей, различается, но cout
тоже покрывает. Например:
std::locale loc("");
std::cout.imbue(loc);
std::cout << 123456.78;
Неизвестный языковой стандарт ("") выбирает локаль на основе пользовательской конфигурации. Поэтому на моей машине (настроенной для американского английского) это печатается как 123,456.78
. Для кого-то, у кого есть компьютер, настроенный для (скажем) Германии, он распечатает что-то вроде 123.456,78
. Для тех, кто настроен для Индии, он будет распечатываться как 1,23,456.78
(и, конечно, есть много других). С printf
я получаю ровно один результат: 123456.78
. Это непротиворечиво, но оно всегда ошибочно для всех. По сути, единственный способ обойти это состоит в том, чтобы сделать форматирование отдельно, а затем передать результат как строку в printf
, потому что printf
сам просто не выполнит задание правильно.
- Хотя они довольно компактны, строки формата
printf
могут быть довольно нечитаемыми. Даже среди программистов C, которые используют printf
практически каждый день, я бы предположил, что по крайней мере 99% должны будут посмотреть вещи, чтобы убедиться, что означает #
в %#x
, и как это отличается от того, что #
in %#f
означает (и да, они означают совершенно разные вещи).
Ответ 3
используйте myString.c_str()
если вы хотите использовать c-подобную строку (const char*
) для printf
Спасибо
Ответ 4
Используйте пример std :: printf и c_str():
std::printf("Follow this command: %s", myString.c_str());
Ответ 5
Printf на самом деле неплохо использовать, если размер имеет значение. Значение, если вы используете программу, в которой проблема с памятью, тогда printf на самом деле является очень хорошим и доступным решением. Cout существенно сдвигает бит, чтобы освободить место для строки, в то время как printf просто принимает какие-то параметры и выводит их на экран. Если вы собираетесь составить простую программу приветствия, printf сможет скомпилировать ее менее чем на 60 000 бит, а не cout, для ее компиляции потребуется более 1 миллиона бит.
В вашей ситуации id предлагает использовать cout просто потому, что его гораздо удобнее использовать. Хотя, я бы сказал, что printf - это то, что нужно знать.
Ответ 6
Основная причина, вероятно, в том, что строка С++ представляет собой структуру, которая включает значение текущей длины, а не только адрес последовательности символов, заканчивающихся 0 байтом. Printf и его родственники ожидают найти такую последовательность, а не структуру, и поэтому путают строки С++.
Говоря сам за себя, я считаю, что printf имеет место, которое не может быть легко заполнено синтаксическими функциями С++, так же как структуры таблиц в html имеют место, которое не может быть легко заполнено div. Поскольку Дыкстра позже писал о goto, он не собирался начинать религию и на самом деле просто спорил с тем, чтобы использовать ее как клочья, чтобы компенсировать плохо разработанный код.
Было бы неплохо, если бы проект GNU добавил семейство printf в свои расширения g++.
Ответ 7
printf
принимает переменное количество аргументов. Они могут иметь только обычные типы данных (POD). Код, который передает ничего, кроме POD, на printf
компилируется только потому, что компилятор предполагает, что вы получили свой формат в правильном направлении. %s
означает, что соответствующий аргумент должен быть указателем на char
. В вашем случае это std::string
not const char*
. printf
не знает этого, потому что тип аргумента теряется и должен быть восстановлен из параметра формата. При повороте этого аргумента std::string
в const char*
результирующий указатель укажет на некоторую нерелевантную область памяти вместо нужной строки C. По этой причине ваш код печатает тарабарщину.
В то время как printf
отличный выбор для печати форматированного текста (особенно если вы намереваетесь иметь отступы), это может быть опасно, если вы не включили предупреждения компилятора. Всегда включать предупреждения, потому что ошибок, подобных этому, легко избежать. Нет причин использовать неуклюжий механизм std::cout
, если семейство printf
может выполнять одну и ту же задачу намного быстрее и красивее. Просто убедитесь, что вы включили все предупреждения (-Wall -Wextra
), и все будет хорошо. Если вы используете свою собственную реализацию printf
, вы должны объявить ее с помощью механизма __attribute__
, который позволяет компилятору проверять строку формата на предоставленные параметры.