الكلية الجامعية للعلوم التطبيقيةبرمجة 1
English
المحاضرة 4 · 100 دقيقة

الدالة printf للطباعة المنسقة

كيف نطبع النصوص والأرقام بالشكل الذي نريده تمامًا؟

إعداد: م. بدر الدين محمد الحليمي · م. محمد زهير كحيل

في نهاية هذه المحاضرة ستكون قادرًا على

  1. تشرح أجزاء سطر printf: الأمر والقالب والقيم
  2. تختار رمز التنسيق المناسب لكل قيمة
  3. تفرّق بين خطأ الترجمة وخطأ وقت التشغيل
  4. تتحكم في عدد الخانات العشرية والعرض والمحاذاة
  5. تطبع جدولًا منسقًا، وتبني نصًا منسقًا باستخدام String.format
محتويات المحاضرة
  1. تعمل printf مثل نموذج فيه فراغات
  2. كل سطر printf يتكوّن من ثلاثة أجزاء
  3. خمسة رموز تنسيق تغطي الأساسيات
  4. الرمز %t يحتاج إلى حرف بعده وإلى قيمة تاريخ
  5. كل فراغ يأخذ قيمته بالترتيب
  6. الرمز الخاطئ لا يوقف المترجم، بل يفشل أثناء التشغيل
  7. خطأ الترجمة يمنع التشغيل، وخطأ وقت التشغيل يظهر أثناءه
  8. القيمة الناقصة والرمز غير المناسب يفشلان عند التشغيل أيضًا
  9. الصيغة %.2f تحدد عدد الخانات العشرية
  10. العرض يحجز خانات: %5d يحاذي لليمين و%-10s يحاذي لليسار
  11. %05d و%c و%b و%% تكمل صندوق الأدوات
  12. العرض الثابت يطبع جدولًا مستقيم الأعمدة
  13. String.format تبني النص المنسق ولا تطبعه
  14. الخلاصة: printf = قالب + رموز تنسيق + قيم
  15. أسئلة المراجعة
الجزء الأول: القالب والقيم

تعمل printf مثل نموذج فيه فراغات

1. القالب

الجملة التي فيها فراغات. الجملة ثابتة، وكل %s فراغ ينتظر قيمة.

"Hello %s!"
2. القيمة

ما يملأ الفراغ.

"World"
3. النتيجة

ما تراه على الشاشة.

Hello World!

املأ الفراغ

توقّع الناتج ثم شغّل. غيّر World إلى اسمك وشغّل مرة أخرى.

public class Form {
    public static void main(String[] args) {
        System.out.printf("Hello %s!%n", "World");
    }
}
الناتج
Hello World!

كل سطر printf يتكوّن من ثلاثة أجزاء

الجزءفي المثالمعناه
1. الأمرSystem.out.printfاطبع بالتنسيق الذي أحدده لك.
2. القالب"Hello %s!%n"نص ثابت فيه فراغات تُملأ: %s و%n.
3. القيم"World"ما يوضع في الفراغات، وتفصل بينها فواصل.

نسينا %n في السطر الأول. ماذا يطبع البرنامج؟

public class NoNewLine {
    public static void main(String[] args) {
        System.out.printf("Hello %s!", "Sara");
        System.out.printf("Hello %s!%n", "Ali");
    }
}
أظهر الناتج
الناتج
Hello Sara!Hello Ali!

لماذا؟ printf تطبع القالب فقط، فإذا لم يكن فيه %n يبقى المؤشر في السطر نفسه. اضغط «تتبّع سطرًا سطرًا» وراقب مكان الناتج بعد كل أمر.

خمسة رموز تنسيق تغطي الأساسيات

الرمزالقيمةمثالالناتجللتذكّر
%sنص (String)("%s", "Ali")Alis = string: النص كما هو
%dعدد صحيح("%d", 25)25d = decimal: بدون فاصلة عشرية
%fعدد عشري("%f", 3.14)3.140000f = float: 6 خانات عشرية
%tالتاريخ والوقت("%tY", d)2026t = time: وY تعني السنة
%nسطر جديد("A%nB")A, Bn = new line: مثل الضغط على Enter

جرّب الرموز

توقّع كل سطر. ثم غيّر القيم، وأضف %n داخل أحد النصوص لتقسيمه إلى سطرين.

public class Specifiers {
    public static void main(String[] args) {
        System.out.printf("%s%n", "Ali");
        System.out.printf("%d%n", 25);
        System.out.printf("%f%n", 3.14);
        System.out.printf("A%nB%n");
    }
}
الناتج
Ali
25
3.140000
A
B

ماذا يعني الحرف d في %d؟

الرمز %t يحتاج إلى حرف بعده وإلى قيمة تاريخ

%t وحده لا يكفي: الحرف بعده يحدد الجزء المطلوب من التاريخ، وY تعني السنة. والقيمة نفسها يجب أن تكون تاريخًا، وليست رقمًا عاديًا.

اطبع السنة

نستخدم تاريخًا ثابتًا ليبقى الناتج ثابتًا. في GregorianCalendar يبدأ عدّ الشهور من 0، فالرقم 0 يعني يناير. غيّر السنة إلى 2030 وشغّل.

public class YearOnly {
    public static void main(String[] args) {
        System.out.printf("%tY%n", new java.util.GregorianCalendar(2026, 0, 1));
    }
}
الناتج
2026

كل فراغ يأخذ قيمته بالترتيب

فراغان وقيمتان

الترتيب مهم: الفراغ الأول يأخذ القيمة الأولى، والثاني يأخذ الثانية. النوع مهم: فراغ الرقم يحتاج رقمًا، وفراغ النص يحتاج نصًا.

public class NameAge {
    public static void main(String[] args) {
        System.out.printf("Name: %s, Age: %d%n", "Ali", 19);
    }
}
الناتج
Name: Ali, Age: 19

تحقّق سريع: ماذا سيُطبع؟

public class Blanks {
    public static void main(String[] args) {
        System.out.printf("%d + %d = %d", 2, 3, 5);
    }
}
أظهر الناتج
الناتج
2 + 3 = 5

لماذا؟ كل فراغ يأخذ القيمة التالية بالترتيب: الأول يأخذ 2، والثاني 3، والثالث 5.

تحقّق سريع: أي رمز تنسيق يناسب الفراغ لطباعة السعر؟

System.out.printf("Price: ___", 4.5);
الجزء الثاني: حين يفشل `printf`

الرمز الخاطئ لا يوقف المترجم، بل يفشل أثناء التشغيل

تحقّق سريع: اكتشف الخطأ. ماذا يحدث عند تنفيذ هذا السطر؟

System.out.printf("Age: %d", "19");

شاهد الفشل بنفسك

شغّل البرنامج: يظهر Age: أولًا، ثم رسالة java.util.IllegalFormatConversionException: d != java.lang.String. ثم اضغط «تتبّع سطرًا سطرًا»: تنجح الترجمة بلا مشكلة، ثم يحدث الفشل عند هذا السطر أثناء التنفيذ.

public class AgeError {
    public static void main(String[] args) {
        System.out.printf("Age: %d", "19");
        System.out.println("This line never runs");
    }
}
الناتج
Age: 
في الرسالةمعناها
IllegalFormatConversionExceptionاستثناء: القيمة لا تناسب رمز التنسيق.
dرمز التنسيق: %d.
!=لا يناسب.
java.lang.Stringنوع القيمة التي أعطيناها: نص.

خطأ الترجمة يمنع التشغيل، وخطأ وقت التشغيل يظهر أثناءه

وجه المقارنةخطأ الترجمةخطأ وقت التشغيل
من يكتشفه؟المترجم javacJVM أثناء التنفيذ
هل يعمل البرنامج؟لا، لا يُنتج ملف .classنعم، حتى يصل إلى السطر الخاطئ
هل يظهر ناتج؟لا شيءما طُبع قبل الخطأ
مثالint age = "19";printf("Age: %d", "19");

الخطأ نفسه، ومصيران مختلفان. في int age = "19"; يعرف المترجم نوع المتغير، فيرفض السطر. أما قالب printf فهو عند المترجم مجرد نص، وأي قيمة مقبولة بعده.

هذا خطأ ترجمة: البرنامج لا يعمل إطلاقًا

لاحظ أن Age: لا تظهر هنا إطلاقًا. احذف علامتي التنصيص.

public class AgeCompile {
    public static void main(String[] args) {
        int age = "19";
        System.out.printf("Age: %d%n", age);
    }
}
رسالة المترجمincompatible types: String cannot be converted to int
أظهر التصحيح
public class AgeCompile {
    public static void main(String[] args) {
        int age = 19;
        System.out.printf("Age: %d%n", age);
    }
}
الناتج
Age: 19

لماذا؟ المترجم يعرف أن age من نوع int، فيتوقف قبل التشغيل، ولذلك لا يُطبع شيء.

وهذا خطأ ترجمة أيضًا: الفاصلة الناقصة

المترجم لا يقرأ ما داخل القالب، لكنه يفحص شكل السطر نفسه. أضف الفاصلة بين القالب والقيمة.

public class AgeComma {
    public static void main(String[] args) {
        System.out.printf("Age: %d%n" 19);
    }
}
رسالة المترجم')' expected
أظهر التصحيح
public class AgeComma {
    public static void main(String[] args) {
        System.out.printf("Age: %d%n", 19);
    }
}
الناتج
Age: 19

لماذا؟ تبدو الرسالة بعيدة عن السبب: يطلب المترجم ) لأنه بعد القالب ينتظر فاصلة أو قوس إغلاق، فوجد الرقم 19. والسبب الحقيقي هو الفاصلة الناقصة.

أصلح خطأ وقت التشغيل

هذا البرنامج يُترجم بنجاح لكنه يفشل عند التشغيل. أصلحه ليطبع Age: 19. للتصحيح طريقتان: احذف علامتي التنصيص حول القيمة، أو استخدم %s.

public class AgeFix {
    public static void main(String[] args) {
        System.out.printf("Age: %d%n", "19");
    }
}
الناتج المتوقع
Age: 19
تلميح 1

"19" نص، و19 عدد.

الحل
public class AgeFix {
    public static void main(String[] args) {
        System.out.printf("Age: %d%n", 19);
    }
}

القيمة الناقصة والرمز غير المناسب يفشلان عند التشغيل أيضًا

السطرالرسالة بعد Exception in thread "main"السبب
printf("%d", 4.5);IllegalFormatConversionException: d != java.lang.Double%d لا يقبل عددًا عشريًا
printf("%f", 5);IllegalFormatConversionException: f != java.lang.Integer%f لا يقبل 5؛ اكتب 5.0
printf("%s %d%n", "Ali");MissingFormatArgumentException: Format specifier '%d'فراغان وقيمة واحدة
printf("%t", date);UnknownFormatConversionException: Conversion = 't'%t يحتاج إلى حرف بعده، مثل %tY

بدّلنا القيمتين. ماذا يظهر على الناتج قبل الخطأ؟

public class Swapped {
    public static void main(String[] args) {
        System.out.printf("Name: %s, Age: %d%n", 19, "Ali");
    }
}
أظهر الناتج
الناتج
Name: 19, Age: 

لماذا؟ %s يقبل أي قيمة فيطبع 19، ثم يصل %d إلى النص "Ali" فيتوقف البرنامج. لهذا قد يبدو أول الناتج صحيحًا والخطأ في مكان آخر.

قيمة ناقصة

شغّل البرنامج واقرأ الرسالة، ثم أضف القيمة 19 بعد "Ali".

public class Missing {
    public static void main(String[] args) {
        System.out.printf("%s %d%n", "Ali");
    }
}
الناتج
Ali 
الجزء الثالث: التحكم في الشكل

الصيغة %.2f تحدد عدد الخانات العشرية

%f يطبع 6 خانات عشرية دائمًا، وهذا نادرًا ما نريده. الرقم بعد النقطة يحدد عدد الخانات، وتُقرَّب القيمة في الطباعة فقط؛ أما المتغير نفسه فلا يتغير.

غيّر عدد الخانات

غيّر %.2f إلى %.3f ثم إلى %.0f، وتوقّع الناتج في كل مرة.

public class Precision {
    public static void main(String[] args) {
        double pi = 3.14159;
        System.out.printf("%f%n", pi);
        System.out.printf("%.2f%n", pi);
        System.out.println(pi);
    }
}
الناتج
3.141590
3.14
3.14159

هل تقطع %.1f الخانات أم تقرّبها؟

public class Rounding {
    public static void main(String[] args) {
        System.out.printf("%.1f%n", 2.75);
    }
}
أظهر الناتج
الناتج
2.8

لماذا؟ تقرّب %.1f إلى خانة واحدة ولا تقطع، فتصبح 2.75 هي 2.8.

إجمالي الفاتورة

السعر 12.5 والكمية 3. احسب الإجمالي في متغير من نوع double، واطبعه بخانتين عشريتين كما في الناتج المتوقع.

public class Receipt {
    public static void main(String[] args) {
        double price = 12.5;
        int quantity = 3;
        // Calculate the total and print it with printf

    }
}
الناتج المتوقع
Total: 37.50
تلميح 1

القالب هو "Total: %.2f%n".

الحل
public class Receipt {
    public static void main(String[] args) {
        double price = 12.5;
        int quantity = 3;
        double total = price * quantity;
        System.out.printf("Total: %.2f%n", total);
    }
}

العرض يحجز خانات: %5d يحاذي لليمين و%-10s يحاذي لليسار

الصيغةمعناهاالقيمةالناتج بين |
%5d5 خانات، ومحاذاة لليمين42| 42|
%-5d5 خانات، ومحاذاة لليسار42|42 |
%-10s10 خانات، ومحاذاة لليسار"Ali"|Ali |
%8.2f8 خانات، منها خانتان عشريتان3.14159| 3.14|

الخطوط العمودية تكشف المسافات

وضعنا | حول كل قيمة لترى المسافات. احذف علامة - من %-10s: إلى أين انتقل الاسم؟

public class Width {
    public static void main(String[] args) {
        System.out.printf("|%5d|%n", 42);
        System.out.printf("|%-10s|%n", "Ali");
        System.out.printf("|%8.2f|%n", 3.14159);
    }
}
الناتج
|   42|
|Ali       |
|    3.14|

الاسم أطول من العرض المحدد. ماذا يحدث؟

public class TooLong {
    public static void main(String[] args) {
        System.out.printf("|%3s|%n", "Mohammed");
    }
}
أظهر الناتج
الناتج
|Mohammed|

لماذا؟ العرض حد أدنى وليس حدًا أقصى: لا تقطع Java القيمة أبدًا، بل تأخذ ما تحتاج إليه من خانات.

%05d و%c و%b و%% تكمل صندوق الأدوات

الصيغةالقيمةالناتجالاستخدام
%05d4200042يملأ الخانات الفارغة بأصفار، مثل رقم الطالب
%c'A'Aحرف واحد من نوع char
%btruetrueقيمة من نوع boolean
%%لا تحتاج إلى قيمة%علامة النسبة نفسها

الرموز الأربعة معًا

غيّر %05d إلى %07d، وغيّر passed إلى false، ثم شغّل. لماذا لا يوجد مقابل لـ %% بين القيم؟

public class Toolbox {
    public static void main(String[] args) {
        int id = 42;
        char grade = 'A';
        boolean passed = true;
        System.out.printf("ID: %05d%n", id);
        System.out.printf("Grade: %c%n", grade);
        System.out.printf("Passed: %b%n", passed);
        System.out.printf("Score: %d%%%n", 95);
    }
}
الناتج
ID: 00042
Grade: A
Passed: true
Score: 95%

كم علامة % تظهر في الناتج؟

public class Percent {
    public static void main(String[] args) {
        System.out.printf("Discount: 50%%%n");
    }
}
أظهر الناتج
الناتج
Discount: 50%

لماذا؟ %% تطبع علامة واحدة، و%n سطر جديد. أما % وحدها في نهاية القالب فتفشل عند التشغيل برسالة UnknownFormatConversionException.

العرض الثابت يطبع جدولًا مستقيم الأعمدة

جدول الطلاب

السطر الأول رأس الجدول، وعرضه يساوي عرض الأسطر تحته. اضغط «تتبّع سطرًا سطرًا» وراقب الجدول يُبنى سطرًا بعد سطر. ثم أضف طالبًا رابعًا.

public class StudentTable {
    public static void main(String[] args) {
        System.out.printf("%-10s%5s%8s%n", "Name", "Age", "GPA");
        System.out.printf("%-10s%5d%8.2f%n", "Sara", 19, 3.75);
        System.out.printf("%-10s%5d%8.2f%n", "Ahmed", 20, 3.2);
        System.out.printf("%-10s%5d%8.2f%n", "Mohammed", 21, 2.5);
    }
}
الناتج
Name        Age     GPA
Sara         19    3.75
Ahmed        20    3.20
Mohammed     21    2.50

جدول الأسعار

اطبع الجدول المتوقع: اسم المادة بعرض 8 إلى اليسار، والسعر بعرض 6 وخانتين عشريتين. الكتاب Book بسعر 12.5، والقلم Pen بسعر 1.75.

public class PriceTable {
    public static void main(String[] args) {
        // Print the header, then one line for each item

    }
}
الناتج المتوقع
Item     Price
Book     12.50
Pen       1.75
تلميح 1

قالب الرأس "%-8s%6s%n".

تلميح 2

قالب الصفوف "%-8s%6.2f%n".

الحل
public class PriceTable {
    public static void main(String[] args) {
        System.out.printf("%-8s%6s%n", "Item", "Price");
        System.out.printf("%-8s%6.2f%n", "Book", 12.5);
        System.out.printf("%-8s%6.2f%n", "Pen", 1.75);
    }
}

String.format تبني النص المنسق ولا تطبعه

تعمل String.format بالقالب والقيم نفسها، لكنها تُرجع النص بدل طباعته. فنحفظه في متغير من نوع String ونستخدمه لاحقًا.

احفظ النص ثم اطبعه

اضغط «تتبّع سطرًا سطرًا». لا يظهر شيء بعد السطر الأول، بل يظهر صندوق line وفيه النص الجاهز، فنطبعه مرتين دون إعادة التنسيق.

public class FormatText {
    public static void main(String[] args) {
        String line = String.format("%s: %.2f", "GPA", 3.756);
        System.out.println(line);
        System.out.println(line);
    }
}
الناتج
GPA: 3.76
GPA: 3.76

لاحظ أن نتيجة String.format لا تُحفظ ولا تُطبع. ماذا يطبع البرنامج؟

public class FormatOnly {
    public static void main(String[] args) {
        String.format("%05d", 42);
        System.out.println("Done");
    }
}
أظهر الناتج
الناتج
Done

لماذا؟ String.format تُرجع النص فقط. فإذا لم نستخدم ما أرجعته، ضاع. الطباعة عمل printf وprintln.

رمز الطالب

استخدم String.format لبناء الرمز من الرقم 7 بعرض 4 خانات وأصفار بادئة، واحفظه في المتغير code. ثم اطبع Code: متبوعًا بقيمته باستخدام println.

public class StudentCode {
    public static void main(String[] args) {
        // Build the code with String.format, then print it

    }
}
الناتج المتوقع
Code: 0007
تلميح 1

%04d تعني 4 خانات مع أصفار بادئة.

الحل
public class StudentCode {
    public static void main(String[] args) {
        String code = String.format("%04d", 7);
        System.out.println("Code: " + code);
    }
}

الخلاصة: printf = قالب + رموز تنسيق + قيم

القالب
"Hi %s%n"
رموز التنسيق
%s %d %f %t %n
القيم
"Sara"
الناتج
Hi Sara

دورك الآن

اطبع الاسم Sara والعمر 19 في سطر واحد كما في الناتج المتوقع، ثم انتقل إلى سطر جديد. جرّب أولًا، ثم استخدم التلميح. وبعد نجاحك، جرّبه باسمك وعمرك.

public class AboutMe {
    public static void main(String[] args) {
        // Print the sentence with printf

    }
}
الناتج المتوقع
My name is Sara and I am 19.
تلميح 1

القالب هو "My name is %s and I am %d.%n".

الحل
public class AboutMe {
    public static void main(String[] args) {
        System.out.printf("My name is %s and I am %d.%n", "Sara", 19);
    }
}

أسئلة المراجعة

ماذا يحدث عند تنفيذ System.out.printf("%f", 5);؟

أي صيغة تطبع الاسم إلى اليسار في عرض 12 خانة؟

اجمع ما تعلمته. ماذا يطبع البرنامج؟

public class ReviewMix {
    public static void main(String[] args) {
        System.out.printf("%-6s|%03d|%.1f%%%n", "Sara", 7, 91.25);
    }
}
أظهر الناتج
الناتج
Sara  |007|91.3%

لماذا؟ %-6s تحجز 6 خانات لليسار، و%03d تكمل بالأصفار، و%.1f تقرّب إلى خانة واحدة، و%% تطبع %.

ما الفرق بين printf وString.format؟