printf с помощью std :: string?

157

Насколько я понимаю, 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 символов, например, в выводе выше.

TheDarkIn1978
источник
8
Просто чтобы вы знали, многие критикуют эту книгу. Что я могу понять, потому что в объектно-ориентированном программировании не так много, но я не думаю, что это так плохо, как утверждают люди.
Джесси Гуд
Ouf! хорошо, хорошо помнить об этом, пока я пробираюсь сквозь книгу. Я уверен, что это будет не единственная книга по С ++, которую я прочитаю в течение следующего года или около того, так что я надеюсь, что она не принесет много пользы :)
TheDarkIn1978
Использование самого высокого предупреждения компилятора ответило бы на ваш вопрос - при компиляции с помощью gcc. Как MSVC справляется с этим - я не знаю.
Петр Варга

Ответы:

237

Он компилируется, потому что printfне является типобезопасным, поскольку использует переменные аргументы в смысле Си 1 . printfне имеет опции 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, но безопасна по типу, изучите шаблоны с переменным числом аргументов (C ++ 11, поддерживается во всех основных компиляторах начиная с MSVC12). Вы можете найти пример одного здесь . Я ничего не знаю о такой реализации в стандартной библиотеке, но может быть в Boost, в частности boost::format.


[1]: Это означает, что вы можете передать любое количество аргументов, но функция полагается на вас, чтобы сообщить количество и типы этих аргументов. В случае printf, это означает строку с закодированной информацией типа, такой как %dзначение int. Если вы лжете о типе или числе, функция не имеет стандартного способа узнать, хотя некоторые компиляторы имеют возможность проверять и выдавать предупреждения, когда вы лжете.

Крис
источник
@ MooingDuck, хорошая мысль. Это в ответе Джерри, но, будучи принятым ответом, это то, что люди видят, и они могут уйти, прежде чем увидят других. Я добавил эту опцию, чтобы быть первым увиденным решением и рекомендованным.
Крис
43

Пожалуйста, не используйте printf("%s", your_string.c_str());

Используйте cout << your_string;вместо этого. Коротко, просто и небезопасно. На самом деле, когда вы пишете на C ++, вы, как правило, хотите printfполностью избежать - это остаток от C, который редко нужен или полезен в C ++.

Что касается того, почему вы должны использовать coutвместо printf, причины многочисленны. Вот несколько самых очевидных примеров:

  1. Как показывает вопрос, printfне является типобезопасным. Если передаваемый вами тип отличается от указанного в спецификаторе преобразования, printfон попытается использовать все, что найдет в стеке, как если бы это был указанный тип, что приведет к неопределенному поведению. Некоторые компиляторы могут предупреждать об этом при некоторых обстоятельствах, но некоторые компиляторы не могут / не будут вообще, и ни один не может при любых обстоятельствах.
  2. printfне расширяемый Вы можете передавать ему только примитивные типы. Набор спецификаторов преобразования, который он понимает, жестко запрограммирован в его реализации, и вы не сможете добавить больше / другие. Большинство хорошо написанных C ++ должны использовать эти типы прежде всего для реализации типов, ориентированных на решаемую проблему.
  3. Это делает приличное форматирование намного сложнее. В качестве очевидного примера, когда вы печатаете числа для чтения людьми, вы, как правило, хотите вставлять тысячи разделителей через каждые несколько цифр. Точное количество цифр и символов, используемых в качестве разделителей, варьируется, но coutоно также охватывается. Например:

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

    Безымянный языковой стандарт («») выбирает языковой стандарт на основе конфигурации пользователя. Поэтому на моей машине (настроенной для английского языка США) это распечатывается как 123,456.78. Для кого-то, чей компьютер настроен для (скажем) Германии, это распечатало бы что-то вроде 123.456,78. Для кого-то с настроенным для Индии, он будет распечатан как 1,23,456.78(и, конечно, есть много других). С printfI получить ровно один результат: 123456.78. Это последовательно, но это всегда неправильно для всех и везде. По сути, единственный способ обойти это состоит в том, чтобы выполнить форматирование отдельно, а затем передать результат в виде строки printf, так как printfсам по себе просто не будет выполнять работу правильно.

  4. Хотя они довольно компактны, printfстроки формата могут быть совершенно нечитаемыми. Даже среди программистов C , которые используют printfпрактически каждый день, я думаю , по крайней мере , 99% нужно будет искать вещи , чтобы быть уверенным , что #в %#xсредства, и как это отличается от того , что #в %#fсредства (и да, они имеют в виду совершенно разные вещи ).
Джерри Гроб
источник
11
@ TheDarkIn1978: Вы, вероятно, забыли #include <string>. В заголовках VC ++ есть некоторые странности, которые позволяют вам определять строку, но не отправлять ее cout, не включая <string>заголовок.
Джерри Коффин
28
@ Джерри: Просто хочу отметить, что использование printf НАМНОГО быстрее, чем использование cout при работе с большими данными. Поэтому, пожалуйста, не говорите, что это бесполезно: D
Программист
7
@Programmer: см stackoverflow.com/questions/12044357/... . Резюме: в большинстве случаев это coutпроисходит медленнее, потому что вы использовали std::endlтам, где не должны.
Джерри Коффин
29
Типичное высокомерие C ++. Если printf существует, почему бы не использовать его?
kuroi neko
6
ОК, извините за быстрый комментарий. Тем не менее, printf довольно удобен для отладки, и потоки, хотя и гораздо более мощные, имеют недостаток, заключающийся в том, что код не дает никакого представления о реальном выводе. Для форматированного вывода printf по-прежнему является жизнеспособной альтернативой, и жаль, что обе системы не могут лучше взаимодействовать. Просто мое мнение, конечно.
kuroi neko
28

используйте, myString.c_str()если вы хотите, чтобы c-like string ( const char*) использовалась с printf

Спасибо

Алессандро Пеццато
источник
6

Используйте пример std :: printf и c_str ():

std::printf("Follow this command: %s", myString.c_str());
Адель Бен Хамади
источник
1

Основная причина, вероятно, заключается в том, что строка C ++ - это структура, которая включает значение текущей длины, а не только адрес последовательности символов, оканчивающейся на 0 байт. Printf и его родственники ожидают найти такую ​​последовательность, а не структуру, и поэтому запутываются в строках C ++.

Говоря сам за себя, я считаю, что printf имеет место, которое не может быть легко заполнено синтаксическими функциями C ++, так же как структуры таблиц в html имеют место, которое не может быть легко заполнено элементами div. Как Дикстра писал позже о гото, он не собирался создавать религию и на самом деле только спорил против использования ее в качестве клэджа, чтобы восполнить плохо спроектированный код.

Было бы неплохо, если бы проект GNU добавил семейство printf к своим расширениям g ++.

MMacD
источник
1

Printf на самом деле довольно хорошо использовать, если размер имеет значение. Это означает, что если вы запускаете программу, в которой проблема с памятью, то printf на самом деле является очень хорошим и менее подходящим решением. Cout существенно сдвигает биты, чтобы освободить место для строки, в то время как printf просто принимает какие-то параметры и выводит их на экран. Если вы скомпилируете простую программу hello world, printf сможет скомпилировать ее менее чем в 60 000 бит, в отличие от cout, для компиляции потребуется более 1 миллиона бит.

Для вашей ситуации id предлагает использовать cout просто потому, что его гораздо удобнее использовать. Хотя я бы сказал, что printf - это то, что нужно знать.

Говард Говард
источник
1

printfпринимает переменное количество аргументов. У них могут быть только типы простых старых данных (POD). Код, который передает что-либо кроме POD printfтолько для компиляции, потому что компилятор предполагает, что вы правильно выбрали формат. %sозначает, что соответствующий аргумент должен быть указателем на char. В вашем случае это std::stringне так const char*. printfне знает этого, потому что тип аргумента теряется и должен быть восстановлен из параметра формата. Превращая этот std::stringаргумент вconst char* результирующий указатель будет указывать на некоторую нерелевантную область памяти вместо желаемой строки C. По этой причине ваш код печатает бред.

Хотя printfэто отличный выбор для распечатки отформатированного текста (особенно , если вы собираетесь иметь отступы), это может быть опасно , если вы не включили предупреждения компилятора. Всегда включайте предупреждения, потому что таких ошибок легко избежать. Нет смысла использовать неуклюжий std::coutмеханизм, если printfсемья может выполнять ту же задачу намного быстрее и красивее. Просто убедитесь, что вы включили все предупреждения ( -Wall -Wextra), и все будет хорошо. В случае, если вы используете свою собственную пользовательскую printfреализацию, вы должны объявить ее с помощью __attribute__механизма, который позволяет компилятору сверять строку формата с предоставленными параметрами .

Гиена
источник