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 символів, наприклад, у вихідному тексті вище.


8
Тільки для вас, багато людей критикують цю книгу. Що я можу зрозуміти, тому що не існує багато предметно-орієнтованого програмування, але я не думаю, що це так погано, як люди заявляють.
Джессі Гуд

уф! ну добре це мати на увазі, поки я проходжу книгу. Я впевнений, що це не буде єдиною книгою C ++, яку я прочитаю протягом наступного року або близько того, тож сподіваюся, що це не зробить занадто великої шкоди :)
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, Добрий момент. Це у відповіді Джеррі, але, як прийнята відповідь, це те, що бачать люди, і вони можуть піти, перш ніж побачити інших. Я додав цей варіант для того, щоб було першим баченим рішенням та рекомендованим.
chris

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
@Jerry: Просто хочу зазначити, що використання printf набагато швидше, ніж використання cout при роботі з великими даними. Отже, не кажіть, що це марно: D
програміст

7
@Programmer: см stackoverflow.com/questions/12044357 / ... . Підсумок: більшість випадків coutвідбувається повільніше, тому що ви використовували std::endlтам, де не повинні.
Джері Коффін

29
Типова зарозумілість експерта C ++. Якщо printf існує, чому б не використовувати його?
kuroi neko

6
Добре, вибачте за смішний коментар. Тим не менш, printf досить зручний для налагодження, і потоки, хоча і набагато потужніші, мають той недолік, що код не дає уявлення про фактичний вихід. Для форматованого виводу printf все ще є життєздатною альтернативою, і шкода, що обидві системи не можуть краще співпрацювати. Просто моя думка, звичайно.
kuroi neko



1

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

Якщо говорити про себе, я вважаю, що printf має місце, яке не може бути легко заповнене синтаксичними функціями C ++, так само як структури таблиць у html мають місце, яке неможливо легко заповнити дівами. Як пізніше Дайкстра писав про гото, він не мав наміру започаткувати релігію і насправді лише сперечався проти використання її як хитрості для компенсації погано розробленого коду.

Було б цілком приємно, якби проект GNU додав сімейство printf до своїх розширень g ++.


1

Printf насправді дуже хороший у використанні, якщо розмір має значення. Це означає, що якщо ви працюєте з програмою, де пам'ять є проблемою, то printf насправді є дуже хорошим і під рейтинговим рішенням. Cout по суті зміщує біти, щоб звільнити рядок, в той час як printf просто приймає якісь параметри і друкує його на екран. Якби ви склали просту світову програму привіт, printf змогла б скласти її менше ніж 60 000 біт на відміну від cout, для її складання знадобиться понад 1 мільйон біт.

Для вашої ситуації я пропоную використовувати cout просто тому, що це набагато зручніше використовувати. Хоча, я б заперечував, що printf - це щось добре знати.


1

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.