Menu
flag Ar iconالعربيةdown icon

محدّدات التنسيق في لغة C: المرجع الكامل لـ printf وscanf

كل محدّدات التنسيق في C في مكان واحد - ماذا تعني ‎%d و%s و%c و%f و%p وما بقي، ومُعدِّلات الطول l وll وh وz، والعرض والدقّة والرايات، وأيّها يختلف بين printf وscanf.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

يخبر محدّد التنسيق printf كيف يحوّل قيمة إلى نصّ، وscanf كيف يحوّل نصًا إلى قيمة. وهو الجزء الوحيد من C الذي لا يستطيع المترجم مساعدتك فيه افتراضيًا - فأنواع الوسائط غير مرئية للدالة - لذا فهذه الصفحة هي الجدول الذي تراجعه.

والشكل العام للمواصفة هو:

%[flags][width][.precision][length]conversion

والمطلوب فقط هو % وحرف التحويل. و%-8.2lf تستخدم الأجزاء الخمسة كلها.

جدول التحويلات

المحدّدالمعنىوسيط printfوسيط scanf
%dعدد صحيح عشري موقّعintint *
%iعدد صحيح موقّعint (مثل %d)int * (يقبل 0x والثماني ببادئة 0)
%uعشري غير موقّعunsigned intunsigned int *
%fفاصلة عائمة عشريةdoublefloat *
%Fمثل %f، مع INF/NAN بأحرف كبيرةdouble-
%e / %Eالترقيم العلميdoublefloat *
%g / %Gالأقصر من %f / %edoublefloat *
%a / %Aفاصلة عائمة سداسية عشريةdoublefloat *
%cمحرف واحدint (‏char مرفَّع)char *
%sسلسلة نصيةchar * (منتهية بـ '\0')char * (مخزن)
%pعنوان مؤشّرvoid *void **
%x / %Xسداسي عشري غير موقّعunsigned intunsigned int *
%oثماني غير موقّعunsigned intunsigned 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)
hhint (من signed char) / signed char *unsigned int (من unsigned char) / unsigned char *
hint (من short) / short *unsigned int (من unsigned short) / unsigned short *
(بلا)int / int *unsigned int / unsigned int *
llong / long *unsigned long / unsigned long *
lllong long / long long *unsigned long long / unsigned long long *
zنوع الحجم ذو الإشارة / مؤشّر إليهsize_t / size_t *
jintmax_t / intmax_t *uintmax_t / uintmax_t *
tptrdiff_t / ptrdiff_t *النظير عديم الإشارة لـ ptrdiff_t

وأمّا عائلتا الفاصلة العائمة والمحارف:

المُعدِّلمعنوع printfنوع scanf
(بلا)f e g adoublefloat *
lf e g adouble (مقبول، بلا أثر)double *
Lf e g along doublelong double *
lc swint_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| على 4242 |
+اطبع الإشارة دائمًا للقيم الموقّعة%+d على 42+42
مسافةاطبع مسافة حيث كانت + ستكون% d على 42 42
0احشُ بأصفار بدل مسافات%06d على 42000042
#صيغة بديلة: 0x لـ %x، و0 لـ %o، وإبقاء النقطة لـ %g%#x على 2550xff

ويمكن للرايات أن تتّحد: فـ %-+8.2f تعني عرضًا أدنى 8، ومنزلتين عشريتين، وإشارة دائمًا، وحشوًا بعد العدد. و- تتغلّب على 0 إن كتبتهما معًا.

العرض والدقّة

العرض حجم حقل أدنى - يُحشى الخرج ليبلغه، ولا يُقتطع أبدًا ليتّسع فيه. أما الدقّة (بعد نقطة) فتعني شيئًا مختلفًا لكل عائلة:

التحويلماذا تعني الدقّة
%f %eالأرقام بعد الفاصلة العشرية (الافتراض 6)
%gمجموع الأرقام المعنوية (الافتراض 6)
%sأقصى عدد محارف تُطبع
%d %i %u %x %oأدنى عدد أرقام، محشوّ بأصفار

ووضع * مكان أي من العددين يأخذه من وسيط int يُقرأ قبل القيمة نفسها. وبذلك تبني جداول تُحسَب عروض أعمدتها أثناء التشغيل.

وفي scanf للعرض وظيفة مختلفة وأهمّ بكثير: فهو يحدّ مقدار الإدخال الذي سيستهلكه التحويل، وهو الشيء الوحيد الواقف بين %s وفيضان المخزن.

حيث يختلف printf وscanf

يتشابهان ويختلفان في أربعة مواضع. وكل واحد منها مصدر علل حيّ:

الموقفprintfscanf
قيمة 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 يفحص سلاسل التنسيق مقابل الوسائط.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن