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

enter image description here

Каждый раз, когда программа запускается, 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__, который позволяет компилятору проверять строку формата на предоставленные параметры.