2012-06-02 4 views
104

Я понимаю, что string является членом пространства имен std, так почему же происходит следующее?C++ printf с std :: string?

#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 символов, например, в указанной выше продукции.

+6

Просто, чтобы вы знали, многие люди [критикуют] (http://stackoverflow.com/questions/5250596/how-should-i-undo-damage-caused-by-reading-c-primer-plus) эта книга. Что я могу понять, потому что о объектно-ориентированном программировании мало, но я не думаю, что это так плохо, как люди утверждают. –

+0

ouf! хорошо, хорошо помнить об этом, пока я прохожу через книгу. Я уверен, что это не будет единственная книга на C++, которую я буду читать в течение следующего года или около того, поэтому я надеюсь, что это не слишком много damange :) – TheDarkIn1978

+0

Использование самого высокого предупреждения о компиляторе ответит на ваш вопрос - ** когда ** компиляция с gcc. Как MSVC справляется с этим - я не знаю. –

ответ

153

Это компиляции, потому что printf не типобезопасен, так как он использует переменные аргументы в смысле C . 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> 

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). Вы можете найти пример одного из here. Я ничего не знаю о реализованных в стандартной библиотеке, но может быть в Boost, в частности boost::format.


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

+1

упоминания об использовании 'cout' для строки также? –

+0

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

21

использование myString.c_str(), если вы хотите с-как строка (Const символ *) для использования с Printf

35

Пожалуйста, не используйте 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 (и, конечно же, есть много других). С printf я получаю ровно один результат: 123456.78. Это согласовано, но неправильно для всех. По сути, единственный способ обойти это - сделать форматирование отдельно, а затем передать результат в виде строки в printf, потому что printf сам будет не выполнить работу правильно.

  4. Хотя они довольно компактны, строки формата printf могут быть довольно нечитаемыми. Даже среди программистов C, которые используют printf практически каждый день, я бы предположил, что по крайней мере 99% должны будут посмотреть вещи, чтобы быть уверенными в том, что означает # в %#x, и как это отличается от того, что означает # в %#f (и да, они означают совершенно разные вещи).
+0

, когда я пытаюсь скомпилировать 'cout << myString << endl;' i получает следующую ошибку: 'Ошибка 1 ошибка C2679: двоичный '<<': оператор не найден, который принимает правый операнд типа 'std :: string '(или нет приемлемого преобразования) ' – TheDarkIn1978

+8

@ TheDarkIn1978: Вы, вероятно, забыли' #include '. У VC++ есть некоторые странности в заголовках, которые позволят вам определить строку, но не отправлять ее в 'cout', не включая заголовок' '. –

+0

, что я забыл (на самом деле не знал)! спасибо :) – TheDarkIn1978

1

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

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

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

3

Использование std::printf и c_str() пример:

std::printf("Follow this command: %s", myString.c_str()); 
1

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

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

0

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

В то время как printf - это an excellent choice for printing out formatted text (особенно если вы собираетесь иметь отступы), это может быть опасно, если вы не включили предупреждения компилятора. Всегда включайте предупреждения, потому что ошибок, подобных этому, легко избежать. Нет причин использовать неуклюжий механизм std::cout, если семья printf может выполнить одну и ту же задачу гораздо быстрее и красивее. Просто убедитесь, что вы включили все предупреждения (-Wall -Wextra), и вам будет хорошо. Если вы используете свою собственную реализацию printf, вы должны объявить ее с помощью механизма __attribute__, который enables the compiler to check the format string against the parameters provided.

Смежные вопросы