يخبر محدّد التنسيق printf كيف يحوّل قيمة إلى نصّ، وscanf كيف يحوّل نصًا إلى قيمة. وهو الجزء الوحيد من C الذي لا يستطيع المترجم مساعدتك فيه افتراضيًا - فأنواع الوسائط غير مرئية للدالة - لذا فهذه الصفحة هي الجدول الذي تراجعه.
والشكل العام للمواصفة هو:
%[flags][width][.precision][length]conversion
والمطلوب فقط هو % وحرف التحويل. و%-8.2lf تستخدم الأجزاء الخمسة كلها.
جدول التحويلات
| المحدّد | المعنى | وسيط printf | وسيط scanf |
|---|---|---|---|
%d | عدد صحيح عشري موقّع | int | int * |
%i | عدد صحيح موقّع | int (مثل %d) | int * (يقبل 0x والثماني ببادئة 0) |
%u | عشري غير موقّع | unsigned int | unsigned int * |
%f | فاصلة عائمة عشرية | double | float * |
%F | مثل %f، مع INF/NAN بأحرف كبيرة | double | - |
%e / %E | الترقيم العلمي | double | float * |
%g / %G | الأقصر من %f / %e | double | float * |
%a / %A | فاصلة عائمة سداسية عشرية | double | float * |
%c | محرف واحد | int (char مرفَّع) | char * |
%s | سلسلة نصية | char * (منتهية بـ '\0') | char * (مخزن) |
%p | عنوان مؤشّر | void * | void ** |
%x / %X | سداسي عشري غير موقّع | unsigned int | unsigned int * |
%o | ثماني غير موقّع | unsigned int | unsigned int * |
%n | يخزّن عدد المحارف المكتوبة حتى الآن | int * | int * |
%% | علامة % حرفية | لا شيء | يطابق % حرفية |
%[...] | مجموعة مسح (scanf فقط) | - | char * |
ومدخلان يستحقّان تحذيرًا. %n تكتب في الذاكرة بدل أن تطبع، وهي الآلية خلف ثغرة سلسلة التنسيق - فلا تدع نصّ المستخدم يصل إلى سلسلة تنسيق أبدًا. و**%p** تتطلّب void *؛ وتمرير int * مباشرةً غير معرَّف رغم أن كل مترجم حقيقي يطبع العنوان على أي حال.
مُعدِّلات الطول
يقع مُعدِّل الطول بين الدقّة وحرف التحويل، ويقول كم يبلغ حجم الوسيط فعلًا.
التحويلات ذات الإشارة (d i) وعديمة الإشارة (u x o) تأخذ أنواعًا مختلفة - فـ %lu تريد unsigned long لا long، والخطأ في ذلك سلوك غير معرَّف، لا هفوة شكلية.
| المُعدِّل | مع d i (printf / scanf) | مع u x o (printf / scanf) |
|---|---|---|
hh | int (من signed char) / signed char * | unsigned int (من unsigned char) / unsigned char * |
h | int (من short) / short * | unsigned int (من unsigned short) / unsigned short * |
| (بلا) | int / int * | unsigned int / unsigned int * |
l | long / long * | unsigned long / unsigned long * |
ll | long long / long long * | unsigned long long / unsigned long long * |
z | نوع الحجم ذو الإشارة / مؤشّر إليه | size_t / size_t * |
j | intmax_t / intmax_t * | uintmax_t / uintmax_t * |
t | ptrdiff_t / ptrdiff_t * | النظير عديم الإشارة لـ ptrdiff_t |
وأمّا عائلتا الفاصلة العائمة والمحارف:
| المُعدِّل | مع | نوع printf | نوع scanf |
|---|---|---|---|
| (بلا) | f e g a | double | float * |
l | f e g a | double (مقبول، بلا أثر) | double * |
L | f e g a | long double | long double * |
l | c s | wint_t / wchar_t * | wchar_t * |
والتركيبات التي تكتبها فعلًا:
و%zu هي التي يجدر حفظها. فـ sizeof وstrlen وكل حجم في المكتبة القياسية يُعيد size_t، وهو 8 بايتات على نظام 64 بتًا بينما int أربعة. وprintf("%d", strlen(s)) علّة حقيقية تبدو وكأنها تعمل على بعض المنصّات - استخدم %zu.
ويوجد المُعدِّلان hh وh أساسًا من أجل scanf، حيث يهمّ حجم الوجهة. أما في printf فيُرفَّع short إلى int قبل الاستدعاء، فكانت %d ستطبعه بشكل صحيح على أي حال؛ و%hd لا تفعل سوى توثيق القصد.
الأنواع ثابتة العرض
بالنسبة لأنواع <stdint.h> مثل int32_t، تكون المحدّدات المحمولة ماكروات من <inttypes.h>:
يتوسّع PRId32 إلى الحروف الصحيحة لمنصّتك، وتُدمَج القيم النصية الحرفية المتجاورة، ولهذا يُقسَّم التنسيق إلى ثلاث قطع. وهو قبيح؛ والبديل - التحويل إلى long long واستخدام %lld - أقرب إلى القراءة غالبًا وصحيح دائمًا.
الرايات
تأتي الرايات مباشرةً بعد % وتغيّر طريقة العرض. وتنطبق على printf فقط.
| الراية | الأثر | مثال | الخرج |
|---|---|---|---|
- | احشُ بعد القيمة بدل قبلها | %-6d| على 42 | 42 | |
+ | اطبع الإشارة دائمًا للقيم الموقّعة | %+d على 42 | +42 |
| مسافة | اطبع مسافة حيث كانت + ستكون | % d على 42 | 42 |
0 | احشُ بأصفار بدل مسافات | %06d على 42 | 000042 |
# | صيغة بديلة: 0x لـ %x، و0 لـ %o، وإبقاء النقطة لـ %g | %#x على 255 | 0xff |
ويمكن للرايات أن تتّحد: فـ %-+8.2f تعني عرضًا أدنى 8، ومنزلتين عشريتين، وإشارة دائمًا، وحشوًا بعد العدد. و- تتغلّب على 0 إن كتبتهما معًا.
العرض والدقّة
العرض حجم حقل أدنى - يُحشى الخرج ليبلغه، ولا يُقتطع أبدًا ليتّسع فيه. أما الدقّة (بعد نقطة) فتعني شيئًا مختلفًا لكل عائلة:
| التحويل | ماذا تعني الدقّة |
|---|---|
%f %e | الأرقام بعد الفاصلة العشرية (الافتراض 6) |
%g | مجموع الأرقام المعنوية (الافتراض 6) |
%s | أقصى عدد محارف تُطبع |
%d %i %u %x %o | أدنى عدد أرقام، محشوّ بأصفار |
ووضع * مكان أي من العددين يأخذه من وسيط int يُقرأ قبل القيمة نفسها. وبذلك تبني جداول تُحسَب عروض أعمدتها أثناء التشغيل.
وفي scanf للعرض وظيفة مختلفة وأهمّ بكثير: فهو يحدّ مقدار الإدخال الذي سيستهلكه التحويل، وهو الشيء الوحيد الواقف بين %s وفيضان المخزن.
حيث يختلف printf وscanf
يتشابهان ويختلفان في أربعة مواضع. وكل واحد منها مصدر علل حيّ:
| الموقف | printf | scanf |
|---|---|---|
قيمة double | %f (float مرفَّع إلى double) | %lf - فـ %f تعني float * |
| العرض | حجم حقل أدنى، يحشو | أقصى إدخال يُستهلَك، يقتطع |
| الوسائط | قيم | مؤشّرات - &x، أو اسم مصفوفة |
| مسافة في التنسيق | تطبع مسافة | تتخطّى أي سلسلة من المسافات البيضاء |
جرّب 2.5 hello. فالتنسيق الذي يقرؤه والتنسيق الذي يطبعه يستخدمان حرفين مختلفين لـ double نفسه، و15 في %15s تحدّ الإدخال عند 15 محرفًا زائد محرف الإنهاء - وهو بالضبط حجم word.
مجموعة المسح في scanf
%[...] تقرأ مجموعة من المحارف بدل كلمة محدّدة بالمسافات البيضاء. وهي طريقة قراءة سطر كامل بـ scanf، وإن بقيت fgets الأداة الأفضل:
[^\n] تعني "أي محارف عدا السطر الجديد". و%[0-9] كانت ستقرأ الأرقام فقط، و%[abc] تلك الحروف الثلاثة فقط. وخلافًا لـ %s، لا تتخطّى مجموعة المسح المسافات البيضاء في المقدّمة، فسطر جديد شارد في المخزن يجعلها لا تطابق شيئًا إطلاقًا.
الخطأ فيها سلوك غير معرَّف
لا تستطيع printf رؤية النوع الحقيقي لوسيطك. فهي تقرأ عدد البايتات الذي يقتضيه المحدّد، وتفسّرها كما يقول المحدّد. فعدم التطابق ليس خللًا في التنسيق - بل قراءة للذاكرة على أنها شيء خاطئ:
printf("%d\n", 3.5); /* prints garbage: reads a double's bits as int */
printf("%s\n", 42); /* treats 42 as an address - crash */
printf("%d\n", strlen(s)); /* size_t read as int - wrong on 64-bit */
printf("%f\n", 3); /* int read as double - garbage */
والدفاع يكلّف راية واحدة. ترجم بـ -Wall فيفحص GCC كل سلسلة تنسيق حرفية مقابل وسائطها:
gcc -Wall -Wextra program.c -o program
ويبلّغ عن كل من الأسطر الأربعة أعلاه وقت الترجمة. وهذا أعلى التحذيرات قيمةً في C على الإطلاق، وهو مفعّل افتراضيًا في المحرّر أعلاه.
وللجانب العملي من استخدام هذه - الجداول المحاذاة، وطباعة الأعداد العشرية بشكل مقروء، وقراءة الإدخال دون علّة السطر الجديد الكلاسيكية - انظر printf وscanf.
الأسئلة الشائعة
ماذا تعني %d في لغة C؟
%d هي محدّد التحويل لعدد صحيح عشري موقّع - أي int. ففي printf تطبع الوسيط كأرقام بالأساس 10؛ وفي scanf تقرأ أرقامًا بالأساس 10 إلى int *. و%i تعني الشيء نفسه في printf، لكنها في scanf تقبل أيضًا السداسي عشري 0x والثماني ببادئة صفر.
ما الفرق بين %f و%lf في لغة C؟
في printf لا فرق عمليًا: فوسائط float تُرفَّع إلى double، فتطبع %f كليهما، ويقبل C99 صيغة %lf كمرادف. أما في scanf فالفرق حاسم - %f تكتب في float * (4 بايتات) و%lf في double * (8 بايتات). واستخدام %f لـ double يُفسد الذاكرة.
كيف أطبع size_t أو long long في لغة C؟
%zu لـ size_t (ما تُعيده sizeof وstrlen) و%lld / %llu لـ long long / unsigned long long. واستخدام %d لهذه سلوك غير معرَّف - يصادف أن ينجح على أنظمة 32 بتًا ويسيء التصرّف على أنظمة 64 بتًا، ولهذا تعمّر هذه العلّة طويلًا.
ماذا يحدث إن استخدمت محدّد التنسيق الخاطئ في لغة C؟
إنه سلوك غير معرَّف، لا خطأ تقريب. فـ printf تقرأ الوسيط بوصفه ما يدّعيه المحدّد، فـ printf("%d", 3.5) تطبع قيمًا مهملة وprintf("%s", 42) تتبع العدد 42 كعنوان وتنهار عادةً. ترجم بـ -Wall، فيجعل ذلك GCC يفحص سلاسل التنسيق مقابل الوسائط.