Використання printf із сучасним C++
Що знадобилося б для модернізації printf? Це питання може здатися дивним багатьом розробникам, які вважають, що C++ вже надає сучасну заміну printf. бібліотеку введення-виводу на основі потоків даних (streams), яка досконала несхожа на STL і реалізує жодного з принципів STL, які стосуються ефективності.
Згідно з визначенням Олександра Степанова та Деніеля Роуза (Daniel Rose) у їхній книзі «From Mathematics to Generic Programming» (Addison-Wesley Professional, 2015), «узагальнене програмування — це підхід до розробки, який фокусується на проектуванні алгоритмів та структур даних, здатних працювати у найбільш універсальних умовах без втрати ефективності».
Якщо чесно, ні printf, ні cout жодною мірою не відображають сучасний C++. від мови програмування C. Такі функції передували появі варіативних шаблонів (variadic templates). Останні пропонують по-справжньому сучасний і надійний механізм для підтримки кількості типів або аргументів, що варіюються. оптимізація.Розвиток архітектур процесорів пішов так, що printf явно набуває переваг і мало що робиться для підвищення продуктивності поліморфічного підходу, закладеного в cout. Тому, якщо вам потрібні продуктивність та ефективність, краще вибрати printf. Крім того, її застосування дає чіткіший код. Ось приклад:
Специфікатор перетворення %f повідомляє printf, що тут очікується число з плаваючою точкою, яке слід перетворити на десяткову нотацію. Символ \n — це звичайний символ нового рядка, який можна доповнити символом перекладу рядка. Перетворення значень із плаваючою точкою передбачає точність до шести знаків після десяткової точки. Таким чином, у цьому прикладі будуть виведені такі знаки з наступним новим рядком:
Досягнення цього результату з допомогою cout спочатку здається порівняно простим:
Тут cout покладається на оператор перевантаження, щоб направити чи надіслати число з плаваючою точкою в потік виведення. Мені не подобається таке використання оператора навантаження, але погоджуся, що це справа персонального стилю програмування. Нарешті, endl завершує вставкою нового рядка потік виведення. Однак це не зовсім те, що в прикладі з printf і дає висновок з іншою точністю після десяткової точки:
Тут постає очевидне питання: як можна змінити точність відповідних абстракцій? Ну, якщо мені потрібні лише два знаки після десяткової точки, можна просто вказати це прямо у специфікаторі printf:
Тепер printf округлятиме число до двох знаків після десяткової точки:
Щоб отримати той же результат у випадку cout, потрібно набрати трохи більше коду:
#include // потрібно для setprecision std::cout
Навіть якщо ви не проти багатослівності всього цього і вам подобається гнучкість або виразність такого варіанту, враховуйте, що ця абстракція має свою ціну. Насамперед маніпулятори fixed і setprecision зберігають стан (stateful), отже, їх вплив залишається до того часу, поки ви звернете їх чи скинете. На противагу цьому специфікатор перетворення в printf включає все, що потрібно для одного конкретного перетворення, не впливаючи на інший код. Інші витрати можуть не мати ніякого значення в більшості випадків виведення, але одного прекрасного дня ви раптом помітите, що чужі програми здатні виводити дані набагато швидше, ніж ваші. Крім витрат від віртуальних функцій, endl робить більше, ніж ви, можливо, очікували. Він не тільки відправляє знак нового рядка у висновок, а й змушує нижчий потік скидати свій висновок. При написанні коду для будь-якого різновиду введення-виводу, будь то в консоль, файл на диску, мережне з'єднання або навіть у графічному конвеєрі, скидання зазвичай обходиться дуже дорого, а скидання, що повторюються, безсумнівно, призведуть до падіння продуктивності.
Тепер, коли я трохи дослідив і порівняв printf і cout, настав час повернутися до первісного питання: що знадобилося б для модернізації printf? Зрозуміло, з появою сучасного C++, прикладом чого є C++11 та пізніші стандарти, я можу підвищити продуктивність праці та надійність printf, не жертвуючи продуктивністю. Інший сторонній член C++ Standard Library - офіційний клас string мови. Хоча репутація цього класу за минулі роки теж була зганьблена, він все ж таки забезпечує чудову продуктивність.Хоч і не безгрішний, він надає дуже корисний спосіб обробки рядків C++. Тому будь-яка модернізація printf має на практиці добре уживатися зі string та wstring. Погляньмо, що можна зробити. Спочатку дозвольте мені усунути те, що я вважаю найприкрішою проблемою printf:
std::string value = "Hello"; printf("%s\n", value);
Насправді це мало б працювати, але, впевнений, ви чітко розумієте, що натомість результатом буде те, що ласкаво називають «невизначеною поведінкою». Як відомо, вся суть printf у виведенні тексту, а C++-клас string є основним втіленням тексту у мові C++. Мені потрібно обернути printf так, щоб це просто працювало. Я не хочу постійно висмикувати символьний масив рядка, що завершується нулем, таким чином:
printf("%s\n", value.c_str());
Це просто втомлює, тому я маю намір виправити це обгортанням printf. Зазвичай у своїй писали іншу варіативну функцію. Наприклад, щось у такому дусі:
void Print(char const * const format, . )
На жаль, це нічого не дає мені. Можливо, було б зручно обернути певний різновид printf, щоб записувати в якийсь інший буфер, але в даному випадку я не отримав би нічого корисного. Я не хочу повертатися до варіативних функцій у стилі C. Натомість я прагну використовувати можливості сучасного C++. На щастя, завдяки варіативним шаблонам C++11 мені ніколи більше не доведеться писати варіативну функцію. Замість обгортання printf в іншу варіативну функцію її можна обернути на варіативний шаблон:
template void Print(char const * const format, Args const & . args) noexcept
Спочатку може здатися, що я виграв не так багато. Якби я мав викликати функцію Print так:
це призвело б до розкриття пакету аргументів args, що складається з 123 і 456, всередині тіла варіативного шаблону, наче я написав просто:
printf("%d %d\n", 123, 456);
То що я виграв? Звичайно, я викликаю printf, а не vprintf, і мені не потрібно керувати va_list і пов'язаними макросами, що крутять стек, але я просто пересилаю аргументи. Однак нехай вас не дурить простота цього рішення. Компілятор знову розпаковуватиме аргументи шаблону функції так, ніби я безпосередньо викликав printf, а значить, в обгортанні printf таким способом не буде жодних витрат. Це також означає, що вона залишається повноправним елементом C++ і що я можу задіяти потужні методи метапрограмування в цій мові для вбудовування будь-якого необхідного коду, причому з повним узагальненням. Замість простого розкриття пакета параметрів args можна обернути кожний аргумент, щоб додати до нього будь-яку налаштовувальну інформацію, необхідну printf. Розглянемо наступний шаблон функції:
template T Argument(T value) noexcept
Цей код виглядає не робить нічого особливого, і це дійсно так, але тепер я можу розкрити пакет параметрів, щоб обернути кожен аргумент в одну з цих функцій:
template void Print(char const * const format, Args const & . args) noexcept
Функцію Print можна викликати таким чином:
Але тепер це призводить до наступного розкриття:
printf("%d %d\n", Argument(123), Argument(456));
Це дуже цікаво. Звичайно, для цих цілих аргументів ніякої різниці немає, але тепер можна перевантажувати функцію Argument для обробки рядкових класів C++:
template T const * Argument(std::basic_string const & value) noexcept
Далі я просто викликаю функцію Print з якимись рядками:
Компілятор зрештою розкриє внутрішню функцію printf наступним чином:
printf("%d %s %ls\n", Argument(123), Argument(hello), Argument(world));
Це гарантує, що масив символів кожного рядка, що завершується нулем, передається в printf і забезпечує чітко визначену поведінку:
Поряд із шаблоном функції Print я також використовую ряд перевантажених версій для неформатованого виводу. Це зазвичай безпечніше і позбавляє printf від випадкової інтерпретації довільних рядків, що містять специфікатори перетворення. Ці функції перераховані на рис. 1.
Мал. 1. Відображення неформатованого висновку
inline void Print(char const * const value) noexcept < Print("%s", value); >inline void Print(wchar_t const * const value) noexcept < Print("%ls", value); >template void Print(std::basic_string const & value) noexcept
Перші дві перевантажені версії просто форматують звичайний та «широкосимвольний» масиви відповідно. Заключний шаблон функції перенаправляє у відповідну перевантажену версію залежно від типу аргументу: string чи wstring. Завдяки цим функціям можна безпечно виводити деякі специфікатори перетворення буквально:
Це знімає найпоширенішу проблему з printf, безпечно та прозоро обробляючи рядковий висновок. А як щодо форматування самих рядків? C++ Standard Library надає різні варіанти printf для запису в буфери рядків символів. З них я знаходжу найбільш ефективними snprintf та swprintf. Ці дві функції обробляють символьний та широкосимвольний висновок відповідно.Вони дозволяють вказувати максимальну кількість символів, яка може бути записана, і повертають значення, за допомогою якого можна обчислити, яке потрібне простір, якщо вихідний буфер виявиться недостатньо великим. Тим не менш, самі по собі вони схильні до помилок і дуже стомлюються у використанні. Настав час внести в них дещо з сучасного C++.
C не підтримує перевантаження функцій, але C++ використовувати її дуже зручно, і це відкриває двері в узагальнене програмування, тому я почну з обгортання snprintf і swprintf як функцій, що викликаються StringPrint. Крім того, я використовую шаблони варіативних функцій, щоб використовувати переваги безпечного розкриття аргументів, раніше застосованого для функції Print. на рис. 2 наведено код для обох функцій. Ці функції також перевіряють, що результат не дорівнює -1, а саме таке значення повертають нижчележачі функції, коли виникає якась проблема, що виправляється при розборі форматуючого рядка. Я використовую контрольний вираз (assertion), тому що просто припускаю, що це помилка і її треба виправити до поширення виробничого коду. Можливо, ви захочете замінити його на виняток, але враховуйте, що надійного способу перетворення всіх помилок у виключення немає, оскільки все одно можна передати неприпустимі аргументи, що призведуть до невизначеної поведінки. Сучасний C++ не є захищеним від дурнів C++.
Мал. 2. Низькорівневі функції форматування рядка
template int StringPrint(char * const buffer, size_t const bufferCount, char const * const format, Args const & . args) noexcept < int const result = snprintf(buffer, bufferCount, format, Argument(args) . ); ASSERT(-1! = result); return result; >template int StringPrint(wchar_t * const buffer, size_t const bufferCount, wchar_t * const * const format, Args const & . args) noexcept
Функції StringPrint забезпечують узагальнений спосіб операцій із форматуванням рядків. Тепер можна зосередитися на специфіці класу string, і з ним здебільшого вимагає управління пам'яттю. Я хотів би писати код приблизно так:
std::string result; Format(result, "%d %s %ls", 123, hello, world); ASSERT("123 Hello World" == result);
Тут немає видимого керування буферами. Мені не потрібно з'ясовувати, наскільки великий буфер потрібно створити. Я лише прошу функцію Format логічно надати відформатований висновок об'єкту string. Як завжди, Format може бути шаблоном функції, а саме варіативним:
template void Format(std::basic_string & buffer, T const * const format, Args const & . args)
Реалізувати цю функцію можна різними способами. Трохи експериментів та хорошої дози профілювання цілком достатньо. Простий, але наївний підхід припустити, що рядок або порожній, або занадто малий, щоб зберігати форматований висновок. У цьому випадку я почав би з визначення необхідного розміру за допомогою StringPrint і припасування буфера під цей розмір, а потім знову викликав би StringPrint з правильним буфером. Щось на кшталт цього:
size_t const size = StringPrint(nullptr, 0, format, args.); buffer.resize(size); StringPrint(&buffer[0], buffer.size() + 1, format, args . );
Приріст на 1 необхідно, так як і snprintf, і swprintf припускають, що розмір буфера, що повідомляється, включає місце для завершального нульового символу. Це працює досить добре, але має бути очевидним, що з продуктивністю тут все дуже погано. Підхід, що забезпечує набагато більш високу продуктивність у більшості випадків, — припускати, що рядок досить великий, щоб зберігати форматований висновок і виконувати припасування розміру буфера лише за необхідності. Це майже вивертає попередній код навиворіт, але в цьому випадку код дуже надійний. Я починаю зі спроби відформатувати рядок безпосередньо у буфері:
size_t const size = StringPrint(&buffer[0], buffer.size() + 1, format, args.);
Якщо рядок спочатку порожній або недостатньо великий, отриманий розмір виявиться більшим за розмір рядка, і я знатиму, що розмір рядка треба змінити до повторного виклику StringPrint:
if (size > buffer.size())
Якщо отриманий розмір менше розміру рядка, форматування успішно виконано, але буфер потрібно відсікти під цей розмір:
Зрештою, якщо розміри збігаються, нічого робити не треба і функція Format може просто повернути керування. Повний шаблон функції Format представлений на рис. 3. Якщо ви знайомі з класом string, то, можливо, згадайте, що він також повідомляє свою місткість (capacity), і у вас може виникнути спокуса присвоїти розміру рядка його місткість до першого виклику StringPrint, вважаючи, що це покращить ваші шанси на коректне форматування рядка з першого заходу. Питання в тому, а чи можна змінити розмір об'єкта string швидше, ніж printf зможе розібрати його рядок, що форматує, і обчислити необхідний розмір буфера.Грунтуючись на своїх неформальних тестах, можу дати відповідь: коли бачите, зміна розміру string для відповідності його місткості потребує дещо більшої простої зміни повідомлення, що потрібно буде очистити будь-які додаткові символи, а це виявиться це швидше, ніж printf. форматуючий рядок, залежить від того, скільки символів доведеться очистити і наскільки складним має бути форматування. Я застосовую навіть більш швидкісний алгоритм для виведення великих обсягів рядкових даних, але я знаходжу, що функція Format на рис. забезпечує хорошу продуктивність у більшості сценаріїв.
3. Форматування рядків.
template void Format(std::basic_string & buffer, T const * const format, Args const & . args) < size_t const size = StringPrint(&buffer[0], buffer.size() + 1, format, args . ); (size >buffer.size()) < buffer.resize(size); StringPrint(&buffer[0], buffer.size() + 1, format, args . ); >else if (size < buffer.size()) < buffer.resize(size);
Маючи в своєму розпорядженні функцію Format, стає дуже легко писати різні допоміжні функції для поширених операцій форматування рядків.
inline std::string ToString(wchar_t const * value) < std::string result; ;
Або відформатувати числа з плаваючою точкою:
inline std::string ToString(double const value, unsigned const precision = 6) < std::string result; = ToString (123.456, 2));
Для одержимих продуктивністю такі функції спеціалізованих перетворень легко піддаються оптимізації, оскільки необхідні розміри буферів цілком передбачувані, але це залишу вам самостійного дослідження.
Це просто набір корисних функцій із моєї бібліотеки виводу для сучасного C++. Сподіваюся, вони до певної міри підкажуть вам, як використовувати сучасний C++ для оновлення старих методів програмування на C і C++. До речі, у моїй бібліотеці виводу визначено функції Argument, а також низькорівневі функції StringPrint у вкладеному просторі імен Internal. Це допомагає тримати бібліотеку легко читається і простий для розуміння, але ви можете упорядкувати свою реалізацію як забажаєте.
Kenny Kerr — Висококваліфікований програміст. Живе у Канаді. Автор навчальних курсів для Pluralsight, володар звання Microsoft MVP. Веде блог kennykerr.ca Крім того, читайте його нотатки у twitter.com/kennykerr.
Для чого використовується %s у функції printf?
Для виведення на консоль у мові програмування Сі найчастіше застосовується функція printf() . Але якщо з висновком простого рядка особливих проблем не виникає, то при виведенні інших типів є свої особливості.
Формальне визначення функції printf виглядає так:
printf(рядок_форматування, список_аргументів)
Перший параметр представляє рядок, який може містити довільний текст у подвійних лапках. Наприклад:
#include int main(void)
Крім стандартного тексту ми можемо передавати у рядок спеціальні групи символів, які називаються послідовностями, що управляють. Найбільш поширені з них:
- \n : переклад на новий рядок
- \t: табуляція
- \r : повернення каретки (курсора) на початок рядка
- \ : зворотний сліш
- \ : одинарна лапка
- \ : подвійна лапка
#include int main(void)
Book "The C Programming Language" Brian Kernighan, Dennis Ritchie
Специфікації перетворення
Специфікації перетворення дозволяють встановити формат виведення різних типів даних. Формальне визначення специфікацій перетворення виглядає так:
% прапори ширина_поля.точність модифікатор специфікатор
З цих компонентів обов'язковими є лише два% та специфікатор.
Які специфікатори ми можемо використати:
- %c: для виведення окремих символів (тип char)
- %s : для виведення рядків
- %d : для виведення цілих чисел зі знаком (тип int)
- %i : для виведення цілих чисел зі знаком (тип int)
- %u : для виведення цілих позитивних чисел (тип unsigned)
- %f : для виведення чисел із плаваючою точкою (float, double)
- %F : для виведення чисел з плаваючою точкою у верхньому регістрі (float, double)
- %e: для виведення експоненційного представлення чисел з плаваючою точкою (float, double)
- %E : для виведення експоненційного представлення чисел з плаваючою точкою у верхньому регістрі (float, double)
- %g : для виведення чисел використовується більш короткий формат %e або %f
- %G : для виведення чисел використовується більш короткий формат %E або %F
- %o : для виведення вісімкових чисел без знака
- %x : для виведення шістнадцяткових чисел
- %X : для виведення шістнадцяткових чисел у верхньому регістрі
- %% : для виведення знака відсотка
Застосуємо кілька специфікаторів:
#include int main(void)
Значення для специфікаторів передаються через аргумент, який іде другим параметром функції printf.
x = 71.898598 x = 7.189860e+001 symbol = r number = 23 Hello Word 2013
Для виведення числових значень з плаваючою точкою ми можемо використовувати ширину поля та точність. Ширина поля представляє ціле позитивне число, яке визначає довжину значення, що виводиться в символах. Точність – це також ціле позитивне число, яке визначає кількість цифр у дробовій частині. Наприклад:
float r = 71.8986; printf("x = %4.2f \n", r); // 71.90
Тут специфікація перетворення представляє рядок "%4.2f", де "4" - ширина поля (для виведення числа будуть використовуватись 4 цифри), а "2" - точність (2 цифри в дрібній частині).
Модифікатори дозволяють конкретизувати значення, що виводиться. Використовуються такі модифікатори:
- h : для виведення значень short int
- l : для виведення значень long int та unsigned long int
- ll : для виведення значень long long int і unsigned long long int
- L: для виведення значень long double
short r = 7100; printf("x = % hd", r);
Необов'язково передавати за одним значенням для виведення функцію printf. Ми можемо передавати безліч значень:
printf("Name=%s \t Age=%d \t Weight=%3.2f", "Tom", 23, 63.3456);
Перший специфікатор у рядку форматування - %s, і для нього значення буде поставлятися першим аргументом - рядком Tom. Другий специфікатор - %d, для нього значення береться з другого аргументу - числа 23. І так далі.
Додатково можна використовувати прапори, які додатково дозволяють керувати форматуванням виводу:
- - : вказує, що вирівнювання йтиме від лівого краю (за замовчуванням використовується вирівнюється праворуч).
- + : якщо значення, що виводиться, має знак (+ або -), то воно виводиться. Без цього прапора знак виводиться лише у разі негативного значення.
- пропуск : вставляє пропуск на місці знака перед позитивними числами
- # : При використанні специфікаторів "o", "x", "X" значення числа виводиться з попередніми символами 0, 0x або 0X. При використанні зі специфікаторами "f", "g", "G" десяткова точка виводитиметься, навіть якщо в числі немає дробової частини
Приклад використання прапорів:
#include int main(void)
8 8 +8 010 8.000000 Tom 38 Tom 38
Синтаксис специфікації форматування: printf та wprintf функції
Різні функції printf та wprintf приймають рядок формату та необов'язкові аргументи та створюють форматовану послідовність символів для вихідних даних. Рядок формату не містить жодної або містить декілька директив, які є або літеральними символами для вихідних даних, або закодованими специфікаціями перетворення, що описують спосіб форматування аргументу у вихідних даних Ця стаття описує синтаксис для кодування специфікацій перетворення у рядку формату. Список цих функцій див. у розділі Потокове введення-виведення.
Специфікація перетворення складається з необов'язкових та обов'язкових полів, що мають такий вигляд:
Кожне поле специфікації перетворення — це символ або число, яке вказує на конкретний параметр формату або описувач перетворення. Обов'язкове поле type визначає тип перетворення, яке застосовується до аргументу. Необов'язкові прапори, ширина і точність полів визначають інші аспекти формату, такі як початкові прогалини або нулі, обґрунтування та точність, що відображається. Поле size вказує розмір використаного та перетвореного аргументу.
Базова специфікація перетворення містить лише символ відсотка та символ type. Наприклад, %s визначає перетворення рядків, щоб відобразити символ %, якщо символ символу не має сенсу в полі формату.
Для забезпечення безпеки та стабільності переконайтеся, що рядки специфікації формату не визначені кінцевим користувачем.
printf( user_name ); /* Danger!
Натомість використовуйте наступний код:
printf("%s", user_name);
У Visual Studio 2015 printf було оголошено inline і scanf переміщені в заголовки та сімейства функцій. Якщо ви переносите старий код, ви можете побачити LNK2019 у зв'язку з цими функціями. Додаткові відомості див.
Специфікатор перетворення типів
Символ специфікації перетворення type визначає, як повинен інтерпретуватись відповідний аргумент: як символ, рядок, покажчик, ціле число або число з плаваючою комою. type — єдине обов'язкове поле специфікації перетворення;
Аргументи, які йдуть за рядком формату, інтерпретуються відповідно до відповідного символу type та необов'язковому префіксу size. Перетворення для типів char символів та wchar_t вказуються за допомогою або C однобайтового і багатобайтового та широкого символьного рядка задаються з c допомогою s або S в залежності від функції форматування, що використовується. Аргументи символів та рядків, вказані за допомогою та c
s інтерпретуються як char char* та сімейними printf функціями, а також wchar_t wchar_t* як і сімейними функціями wprintf . Аргументи символів та рядків, вказані за допомогою та C
S інтерпретуються як wchar_t wchar_t* та сімейними printf функціями, а також char char* як і сімейними функціями wprintf . Ця поведінка залежить від корпорації Майкрософт. З історичних причин wprintf функції використовують c і s посилаються на wchar_t символи, а також C
S вказують вузькі символи.
Цілі типи, такі як short , int , long long long та їх unsigned варіанти, вказуються за допомогою d , , i , o , u
x і X . Типи з плаваючою комою, такі як float double , e
f
A
E і long double , вказуються за допомогою , a , F
g та . G За замовчуванням, якщо вони не змінюються префіксом розміру , цілі аргументи примушуються до int типу і аргументи з плаваючою комою примушуються до double . У 64-розрядних системах int використовується 32-розрядне значення. Тому 64-розрядні цілі числа будуть усічені при форматуванні вихідних даних, якщо не використовується префікс ll розміру або I64 не використовується. Типи покажчиків, вказані за допомогою p розміру покажчика за промовчанням для платформи.
Тільки для систем Майкрософт:
Символ Z типу та поведінка c символів , C
s а S також символів типів при використанні з printf функціями wprintf — розширення Майкрософт. Стандарт ISO C використовує та s узгоджено використовується c для вузьких символів та рядків, а C
S також для розширених символів та рядків у всіх функціях форматування.
Символи поля типу
| Символ типу |
Аргумент |
Формат виведення |
|
c
|
Символ |
При використанні функцій printf визначає однобайтовий символ; під час використання з функціями wprintf визначає розширений символ. |
|
C
|
Символ |
При використанні функцій printf визначає розширений символ; під час використання з функціями wprintf визначає однобайтовий символ. |
|
d
|
Ціле |
Десяткове ціле число зі знаком. |
|
i
|
Ціле |
Десяткове ціле число зі знаком. |
|
o
|
Ціле |
Вісімкове ціле число без знака. |
|
u
|
Ціле |
Десяткове ціле число без знаку. |
|
x
|
Ціле |
Ціла кількість без знака; використовує значення "abcdef". |
|
X
|
Ціле |
Ціла кількість без знака; використовує значення "ABCDEF". |
|
e
|
З плаваючою комою |
Підписане значення з формою [-]dddd e [ + - |]dd[d], де d - одна десяткова цифра, dddd — одна або кілька десяткових цифр залежно від зазначеної точності або шести за промовчанням, а dd[d] — дві або три десяткові цифри в залежності від формату виводу та розміру експонента. |
|
E
|
З плаваючою комою |
Ідентичний формату e , за винятком того, що E замість e подання експонента. |
|
f
|
З плаваючою комою |
Підписане значення, яке має форму [-]dddd . dd, де d має одну чи кілька десяткових цифр. Кількість цифр перед десятковою комою залежить від величини числа, а кількість знаків після десяткової коми залежить від зазначеної точності або використовується шість за промовчанням. |
|
F
|
З плаваючою комою |
Ідентичний формату, за винятком нескінченності та вихідних f даних NaN. |
|
g
|
З плаваючою комою |
Підписані значення відображаються в f форматі або e формат, залежно від того, що є більш компактним для заданого значення та точності. Формат e використовується тільки в тому випадку, якщо експонент значення менше -4 або більше або дорівнює аргументу точності . Нулі в кінці відкидаються, а десяткова кома відображається тільки в тому випадку, якщо за нею слідує хоча б одна цифра. |
|
G
|
З плаваючою комою |
Ідентичний формату g , за винятком того, що e
E натомість представляє експонент (де це необхідно). |
|
a
|
З плаваючою комою |
Підписане шістнадцяткове значення подвійної точності з плаваючою комою, що має форму [] 0x hh+ | - p []dd, де h.hh є шістнадцятковими цифрами (з використанням малих літер) мантиса, та dd — одна чи кілька цифр для експонента. - Точність визначає кількість цифр після коми. |
|
A
|
З плаваючою комою |
Підписане шістнадцяткове значення подвійної точності з плаваючою комою, що має форму [-] 0X hhh P [ - + |]dd, де h.hhhh є шістнадцятковими цифрами (використовуючи літери букв) мантиса, та dd — одна чи кілька цифр для експонента. Точність визначає кількість цифр після коми. |
|
n
|
Вказівник на ціле число |
Число символів, які успішно записані на даний момент у потік чи буфер. Це значення зберігається в цілому числі, адреса якого вказана як аргумент. Розмір цілочисельного значення, яке посилається покажчик, управляється префіксом специфікації розміру аргументу. Описувач n вимкнено за замовчуванням. Для отримання додаткових відомостей див. примітку з безпеки. |
|
p
|
Тип покажчика |
Відображення аргументу у вигляді адреси у шістнадцяткових цифрах. |
|
s
|
Рядок |
При використанні функцій printf визначає рядок однобайтових або багатобайтових символів; під час використання з функціями wprintf визначає рядок розширених символів. Символи відображаються до першого нульового символу або доти, доки не буде досягнуто значення precision. |
|
S
|
Рядок |
У разі використання з функціями printf визначає рядок розширених символів; під час використання з функціями wprintf визначає рядок однобайтових або багатобайтових символів. Символи відображаються до першого нульового символу або доти, доки не буде досягнуто значення precision. |
|
Z
|
Структура ANSI_STRING чи UNICODE_STRING |
VS 2013 та більш ранніх версій
Коли адреса ANSI_STRING або UNICODE_STRING структура передається як аргумент, відображає рядок, що міститься в буфері, на який вказує Buffer поле структури. Використовуйте префікс модифікатора розміру для вказівки аргументу w UNICODE_STRING, наприклад %wZ. Поле Length структура повинна містити значення довжини рядка в байтах. Поле MaximumLength структура повинна містити значення довжини буфера в байтах. |
Універсальне середовище виконання C (UCRT)
Існує відома проблема UCRT, яка в даний час підтримується для забезпечення сумісності. S Як і описувач, Z описувач без префікса модифіксу розміру посилається на UNICODE_STRING використання вузької функції друку (наприклад printf) та ANSI_STRING при використанні широкої функції друку (наприклад wprintf).
Натомість Z використовуйте для hZ вказівки ANSI_STRING значення. wZ (або lZ ) можна використовувати для вказівки UNICODE_STRING значення .
У Visual Studio 2015 і пізніших версіях, якщо аргумент, відповідний описувач перетворення з плаваючою комою ( a , , f
E
e
F
A
g ) G є нескінченним, невизначеним або NaN, форматовані вихідні дані відповідають стандарту C99. У цій таблиці перелічені форматовані вихідні дані.
| Значення |
Вихідні дані |
| Infinity |
inf |
| Несигнальне значення NaN |
nan |
| Сигнальне значення NaN |
nan(snan) |
| Невизначене значення NaN |
nan(ind) |
Будь-який із цих рядків може бути префіксований знаком. Якщо символ описувача перетворення type з плаваючою комою є великою літерою, вихідні дані форматуються також великими літерами. Наприклад, якщо специфікатором формату є %F замість %f, нескінченність форматується як INF замість inf. Функції scanf можуть також аналізувати ці рядки, тому ці значення можуть здійснювати круговий шлях через функції printf і scanf .
До виходу Visual Studio 2015 серед CRT використовувався інший нестандартний формат для вихідних даних значень нескінченності, невизначених значень і значень NaN.
| Значення |
Вихідні дані |
| + Нескінченність |
1. # INF випадкові цифри |
| -Нескінченність |
-1. випадкові цифри |
| Невизначене (те саме, що й не число без виклику виключення) |
цифри .#IND випадкових цифр |
| Число |
цифри .#NAN випадкових цифр |
Будь-який із цих рядків може бути префіксований знаком і може бути відформатований по-різному залежно від ширини поля та точності, іноді з незвичайними ефектами. Наприклад, друк 1.#J , printf("%.2f\n", INFINITY) так як #INF буде "округлено" до двох цифр точності.
Якщо аргумент, який відповідає %s або %S , або поле Buffer аргументу, який відповідає %Z є вказівником NULL, відображається значення "(NULL)".
У всіх експоненційних форматах мінімальна кількість цифр показника ступеня за умовчанням дорівнює двом (три використовуються тільки при необхідності). За допомогою _set_output_format функції можна задати число цифр, що відображаються в три для зворотної сумісності з кодом, написаним Visual Studio 2013 і раніше.
%n Оскільки формат по суті небезпечний, він вимкнено за замовчуванням. При %n виявленні у рядку формату викликається обробник неприпустимих параметрів, як описано в розділі перевірки параметрів. Відомості про активацію %n підтримки див. у статті _set_printf_count_output.
Директиви прапорів
Перше необов'язкове поле у специфікації перетворення містить директиви прапора. Це поле містить нуль або кілька символів прапорів, які вказують вихідне обґрунтування та контрольні вихідні дані знаків, порожні значення, початкові нулі, десяткові точки та шістнадцяткові префікси. У специфікації перетворення може бути зазначено декілька директив прапорів, і символи прапорів можуть розміщуватись у будь-якому порядку.
Прапори символи
| Прапор |
Значення |
За замовчуванням. |
|
-
|
Вирівнювання результату лівим краєм у межах заданої ширини поля. |
Вирівнювання з правого краю. |
|
+
|
Використовуйте знак (+ або -), щоб префіксувати вихідне значення, якщо воно має підписаний тип. |
Знак відображається лише для негативних значень зі знаком. |
|
0
|
Якщо ширина префіксовано 0 , початкові нулі додаються до досягнення мінімальної ширини. Якщо вказано одночасно 0 і - , знак 0 ігнорується. Якщо 0 заданий для цілісного формату ( i , , u
x , X , , o ) d та специфікації точності також присутні (наприклад, %04.d — 0 ігноруються). 0 поставлено для a формату з A плаваючою комою, початкові нулі додаються в мантису після 0x префікса або 0X префікса. |
Без наповнення. |
| порожній (" ") |
Використовуйте порожнє значення, щоб префіксувати вихідне значення, якщо воно підписане та позитивне. |
Порожній префікс не відображається. |
|
#
|
Якщо він використовується з параметром o , x або X форматом # , прапор використовує 00x (або 0X відповідно) префікс будь-якого вихідного значення, відмінного від нуля. |
Префікс не відображається. |
| При використанні з прапором e , E , f
a
F
A або форматуванням # прапор змушує вихідне значення утримувати десяткову точку. |
Десятковий роздільник з'являється лише тоді, коли після нього є цифри. |
| Якщо використовується у поєднанні з форматом g або G прапор # примусово додає десятковий роздільник до вихідного значення та забороняє відкидати кінцеві нулі. |
Специфікація ширини
У специфікації перетворення необов'язкове поле специфікації ширини відображається після будь-яких символів прапор. width — це невід'ємне ціле число, яке керує мінімальним числом символів, які є вихідними. - )) додаються пробіли, у кількості, необхідної для досягнення мінімальної ширини. width префікс дорівнює 0, початкові нулі додаються в цілочисленні або з плаваючою комою перетворення до досягнення мінімальної ширини, за винятком випадків, коли перетворення дорівнює нескінченності або NaN .
Специфікація ширини ніколи не викликає усічення значення. width воно не вказано, всі символи значення вихідні, за умови точної специфікації.
Якщо як специфікація ширини вказана зірочка ( * ), значення ширини задається аргументом int зі списку аргументів. width має передувати значенню, що відформатується у списку аргументів, як показано в цьому прикладі:
printf("%0*d", 5, 3);
Відсутнє чи невелике width значення у специфікації перетворення не призводить до усічення вихідного значення. width значення поле розширюється, щоб містити результат перетворення.
Специфікація точності
У специфікації перетворення третє необов'язкове поле є специфікацією точності.
На відміну від специфікації ширини, специфікація точності може викликати або усічення вихідного значення або округлення значення з плаваючою комою. precision задано значення 0, а перетворене значення дорівнює 0, результат не виводить символів, як показано в цьому прикладі:
printf("%.0d", 0); /* No characters output */
Якщо специфікація точності є зірочкою ( * ), аргумент int зі списку аргументів надає значення У списку аргументів аргумент. precision повинен передувати форматованого значення, як показано в наступному прикладі:
printf( "%.*f", 3, 3.14159265); /* 3.142 output */
Символ type визначає інтерпретацію precision або точність за умовчанням при precision опущення, як показано в наступній таблиці.
Вплив значень точності на тип
| Тип |
Значення |
За замовчуванням. |
|
a , A
|
Точність визначає кількість цифр після коми. |
Точність за замовчуванням — 13. Якщо точність дорівнює 0, десяткова кома не виводиться, якщо не використовується прапор # . |
|
c , C
|
Точність не застосовується. |
Символ відображається. |
|
d , , i
u
o
x , X
|
Точність визначає мінімальну кількість цифр, що виводиться. precision, вихідне значення доповнюється зліва нулями. точність. |
Точність за замовчуванням – 1. |
|
e , E
|
Кількість знаків дробової частини задається специфікацією точності. |
Точність за замовчуванням – 6. Якщо точність дорівнює 0 або період ( . ) відображається без числа після нього, десяткова точка не виводиться. |
|
f , F
|
Значення точності задає кількість цифр після десяткової коми Якщо десяткова кома присутня, перед нею принаймні одна цифра. |
Точність за замовчуванням – 6. Якщо точність дорівнює 0 або якщо період ( . ) відображається без числа після нього, десяткова точка не виводиться. |
|
g , G
|
Точність визначає максимальну кількість значущих цифр. |
Виводяться шість цифр, а кінцеві нулі усікаються. |
|
s , S
|
Точність визначає максимальну кількість символів, що виводяться. Символи, що виходять за межі precision, не виводяться. |
Символи друкуються, доки не буде знайдено порожній символ. |
Специфікація розміру аргументу
У специфікації перетворення поле size - це модифікатор довжини аргументу для описувача перетворення type. Префікси поля розміру в поле типу , j
h
hh , l , (малі літери), L , I
w
t
z
ll (верхній регістр i), I32 і I64 вкажіть "розмір" відповідного аргументу - довгий або короткий, 32-розрядний або 64-розрядний, однобайтовий або широкий символ залежно від того, що вони змінюють. Ці префікси розміру використовуються із символами type у сімействах функцій printf і wprintf визначення інтерпретації розмірів аргументів, як показано у наступній таблиці. Поле size є необов'язковим деяких типів аргументів. Якщо префікс розміру не вказано, модуль форматування використовує цілі аргументи, наприклад, підписані або не підписані char , short , int , long і типи перерахування як 32-розрядні типи int , а аргументи float , double і long double з плаваючою комою використовуються як 64-розрядні типи double. Така поведінка відповідає правилам підвищення рівня аргументів за промовчанням для списків аргументів змінних. Додаткові відомості про просування аргументів див. у розділі Многоточие і аргументи за промовчанням у висловах Postfix. У 32-розрядних та 64-розрядних системах специфікація перетворення 64-розрядного цілісного аргументу має містити префікс ll розміру або I64 . В іншому випадку поведінка модуля форматування не визначена.
Деякі типи мають різний розмір у 32-розрядному та 64-розрядному коді. Наприклад, size_t на 32 біта довша в коді, скомпілюваному для x86, і на 64 біта довша в коді, скомпілюваному для x64.Для створення коду форматування для типів із змінною кількістю байт, який не залежить від платформи, можна використовувати модифікатор розміру аргументу зі змінною кількістю байт. Натомість використовуйте модифікатор розміру 64-розрядного аргументу і явно підвищити тип аргументу змінної ширини до 64 біт. Модифікатор розміру аргументу ( I у верхньому регістрі i) обробляє цілі аргументи змінної ширини, але рекомендується використовувати модифікатори типу j
t
z та модифікатори для переносимості.
Префікси розміру для описників типів формату printf та wprintf
| Щоб вказати |
Використовуваний префікс |
Зі специфікатором типу |
char unsigned char |
hh
|
d , i , o , u , x або X
|
short int short unsigned int |
h
|
d , i , o , u , x або X
|
__int32 unsigned __int32 |
I32
|
d , i , o , u , x або X
|
__int64 unsigned __int64 |
I64
|
d , i , o , u , x або X
|
intmax_t uintmax_t |
j або I64
|
d , i , o , u , x або X
|
| long double |
l (маленька літера L) або L
|
a , A , e , E , f , F , g або G
|
long int long unsigned int |
l (маленька L) |
d , i , o , u , x або X
|
long long int unsigned long long int |
ll (малі літери LL) |
d , i , o , u , x або X
|
| ptrdiff_t |
t або I (верхній регістр i) |
d , i , o , u , x або X
|
| size_t |
z або I (верхній регістр i) |
d , i , o , u , x або X
|
| Однобайтовий символ |
h
|
c або C
|
| Розширений символ |
l (маленька літера L) або w
|
c або C
|
| Рядок однобайтових символів |
h
|
s , S або Z
|
| Рядок розширених символів |
l (маленька літера L) або w
|
s , S або Z
|
Типи ptrdiff_t і size_t є __int32 або unsigned __int32 на 32-розрядних платформах і __int64 або unsigned __int64 на 64-розрядних платформах. I Префікси (верхній регістр i), j
t і z розміри беруть правильну ширину аргументу для платформи.
У Visual C++ хоча long double є окремим типом, він має внутрішнє уявлення, як і тип double .
Описувач hc або описувач типів є синонімом c функцій printf та функцій wprintf C
hC . Описувач lc типів або wC описник типів є синонімом функцій printf C та функцій c wprintf. lC
wc Описувач hs або описувач типів є синонімом s функцій printf та функцій wprintf S
hS . Описувач ls типів або wS описник типів є синонімом S функцій printf та функцій s wprintf. lS
ws
Тільки для систем Майкрософт:
I Префікси модифікатора розміру аргументів (великі літери i), I32
I64 а також w префікси розміру аргументів є розширеннями Microsoft і не сумісні з ISO C. Префікс h при використанні з даними типу char та l префіксом (маленьким регістром L) при використанні з даними типу double є розширення Microsoft.