Module ৩ · কনস্ট্যান্ট, qualifier, ইনপুট আর আউটপুট
Printf: format specifier, width আর precision
এই lesson-এ যা শিখবে
- যেকোনো format specifier-কে চারটা আলাদা অংশ হিসেবে পড়তে পারবে, আর নিজে একটা লিখতে পারবে।
- Width, minus flag আর zero flag দিয়ে এমন table ছাপতে পারবে যার কলামগুলো মিলে থাকে।
printfযা ফেরত দেয় সেটা কাজে লাগাতে পারবে, আর Playground-এ ভুল specifier কী করে সেটা বলতে পারবে।
Amara একটা contest-র leaderboard ছাপছে। ওর নামগুলো নানা দৈর্ঘ্যের, তাই solved কলামটা প্রতি সারিতে অন্য জায়গায় শুরু হয়। পুরো জিনিসটা দেখতে কাটাছেঁড়া চিঠির মতো। ও বিশ মিনিট ধরে হাতে ফাঁকা জায়গা বসাচ্ছে।
সমাধানটা চারটা অক্ষর। 1972 সাল থেকে printf-র ভিতরে ছোট একটা সাজানোর ভাষা আছে, আর প্রায় কাউকেই ওটা ঠিকমতো শেখানো হয় না। এই lesson ওটা ঠিকমতো শেখায়।
Format string মানে ফুটো করা কিছু লেখা
printf-র প্রথম argument একটা ছাঁচ। এর সবকিছুই যেমন আছে তেমনই ছাপা হয়, কেবল % দিয়ে শুরু হওয়া অংশটা ছাড়া, যেটা একটা conversion specification: একটা ফুটো, সাথে সেটা ভরার নির্দেশ।
Format string-র পরের মানগুলো ফুটোগুলো ক্রম ধরে ভরে। চলার সময় কোনো যাচাই হয় না, আর এই একটা কথা থেকেই এই lesson-র প্রতিটা ভুল আসে।
একটা specifier, চারটা ঐচ্ছিক অংশ আর একটা বাধ্যতামূলক
%[flags][width][.precision]conversion
flags - ঘরের ভিতরে বাঁয়ে সাজানো
0 ফাঁকা জায়গার বদলে শূন্য দিয়ে ঘর ভরা
+ ধনাত্মক হলেও সবসময় চিহ্ন ছাপা
একটা ফাঁকা জায়গা, যোগ চিহ্নের জায়গায় ফাঁকা ছাপা
width একটা পূর্ণসংখ্যা: সবচেয়ে কম কত অক্ষর লাগবে
.precision float-র জন্য, দশমিকের পরে কয় অঙ্ক
string-র জন্য, বড়জোর কয় অক্ষর ছাপা হবে
conversion d i একটা signed int u একটা unsigned int
ld একটা long lld একটা long long
f double, স্থির দশমিক e double, বৈজ্ঞানিক
g double, যেটা ছোট হয়
c একটা অক্ষর s একটা string
x X hexadecimal, ছোট বা বড় হাতের
o octal p একটা pointer
zu একটা size_t %% একটা শতাংশ চিহ্ন
- কেবল
%আর conversion বাধ্যতামূলক।%dএকটা পূর্ণ specifier। - Width সবচেয়ে কম কতটুকু, সবচেয়ে বেশি কতটুকু না। ঘরের চেয়ে বড় মান বাইরে বেরিয়ে যায়।
- Conversion-র আগের অক্ষরটা একটা length modifier, আর ওটা type-র সাথে না মিললে পড়াটাই ভুল হয়।
Width মানে একটা মান সবচেয়ে কম যত ঘর নেবে
Width মানটাকে ফাঁকা জায়গা দিয়ে ভরাট করে, যতক্ষণ না ঘরটা ওই চওড়া হয়। Default-এ ভরাটটা বাঁ দিকে বসে, তাই মানটা ঘরের ডান দিকে গিয়ে দাঁড়ায়।
দুইটা flag এটা বদলায়। Minus চিহ্ন ভরাটটা ডান দিকে সরায়, তাই মানটা বাঁয়ে বসে। Zero ফাঁকার বদলে শূন্য দিয়ে ভরে, যেটা কেবল সংখ্যার জন্যই অর্থবহ।
#include <stdio.h>
int main(void)
{
printf("[%d]\n", 42);
printf("[%8d]\n", 42);
printf("[%-8d]\n", 42);
printf("[%08d]\n", 42);
printf("[%+d]\n", 42);
printf("[% d]\n", 42);
printf("[%8.3f]\n", 3.14159);
printf("[%-8.3f]\n", 3.14159);
printf("[%.3s]\n", "Progsity");
printf("[%%]\n");
return 0;
}
[42]
[ 42]
[42 ]
[00000042]
[+42]
[ 42]
[ 3.142]
[3.142 ]
[Pro]
[%]
ওই output-র প্রতিটা তৃতীয় বন্ধনী ঠিক সেখানেই, যেখানে ঘরটা শুরু আর শেষ হয়। মাঝের অক্ষর গুনে দেখো, প্রতিটা নিয়ম নিজেই বুঝিয়ে দেয়।
নবম লাইনটাই চমক। String-র বেলায় precision সবচেয়ে কম না, সবচেয়ে বেশি কত, তাই %.3s আটটার মধ্যে তিনটা অক্ষর ছেপেছে।
শেষ লাইনটা হলো শতাংশ চিহ্ন ছাপার উপায়। একা একটা % specifier শুরু করে দেয়, তাই তুমি দুইটা লেখো।
Precision দুই রকম জিনিস বোঝায়
Float-এ precision মানে দশমিকের পরে কয় অঙ্ক, আর মানটা তাতে আঁটার জন্য গোল করা হয়। টাকা আর মাপজোকের প্রায় প্রতিটা problem %.2f গড়নটাই চায়।
String-এ precision মানে বড়জোর কয়টা অক্ষর ছাপা হবে, আর বাকিটা বাদ পড়ে।
Integer-এ precision মানে সবচেয়ে কম কয় অঙ্ক, শূন্য দিয়ে ভরাট করে, যেটা কেউ কমই চায় আর চিনে রাখা ভালো।
তাই একই দুইটা অক্ষরের মানে গোল করা, কেটে দেওয়া বা ভরাট করা, আর সেটা ঠিক করে তিন ঘর ডানের একটা অক্ষর।
মিলে থাকা table মানে কেবল width, আর কিছু না
Amara-র leaderboard-এ প্রতিটা কলামের একটা করে width লাগবে, আর লেখার কলামটায় minus flag লাগবে যাতে নামগুলো একই জায়গা থেকে শুরু হয়।
#include <stdio.h>
int main(void)
{
printf("%-14s%8s%10s\n", "PLAYER", "SOLVED", "PENALTY");
printf("%-14s%8d%10d\n", "Amara", 9, 412);
printf("%-14s%8d%10d\n", "Kenji", 9, 1207);
printf("%-14s%8d%10d\n", "Zara", 7, 388);
printf("%-14s%8d%10d\n", "Bob", 2, 44);
return 0;
}
PLAYER SOLVED PENALTY
Amara 9 412
Kenji 9 1207
Zara 7 388
Bob 2 44
একটাই format string, পাঁচবার ব্যবহার করা। Header সারিতে সংখ্যার কলামের শিরোনামের জন্য %s দেওয়া, আর এই কারণেই পাঁচ লাইনেই width মিলে যায়।
খেয়াল করো, specifier-গুলোর মাঝে কোনো ফাঁকা জায়গা নেই। Width আগেই ফাঁকটুকু দিচ্ছে, আর তার সাথে ফাঁকা জায়গা যোগ করলেই table এক অক্ষর সরে যায়।
Printf তোমাকে একটা সংখ্যা ফেরত দেয়
এটা প্রায় কেউ জানে না। printf ফেরত দেয় ও কয়টা অক্ষর লিখল, আর লেখা ব্যর্থ হলে একটা ঋণাত্মক সংখ্যা।
বেশির ভাগ program ওটা গ্রাহ্য করে না, আর সেটা ঠিক আছে। "ওই লাইনটা কত চওড়া ছিল" প্রশ্নের এটাই সৎ উত্তর। আর এটা এজন্যও জরুরি যে পরের lesson-এ scanf-কে ঠিকভাবে ব্যবহার করাই হয় ফেরত পাওয়া মান দিয়ে।
#include <stdio.h>
int main(void)
{
int written = printf("Amara scored %d\n", 97);
printf("that line was %d characters long\n", written);
printf("a %%d holds %d, a %%x holds %x, a %%o holds %o\n", 255, 255, 255);
printf("a size_t needs %%zu: %zu\n", sizeof(int));
return 0;
}
Amara scored 97
that line was 16 characters long
a %d holds 255, a %x holds ff, a %o holds 377
a size_t needs %zu: 4
ষোলোটা অক্ষর: চোদ্দটা দেখা যায়, সাথে newline, সাথে 97-র দ্বিতীয় অঙ্কটা। নিজে গুনে দেখো, কারণ ওই লাইনটার মানেই এটা।
তৃতীয় লাইনটা একটা মান তিনটা base-এ তিনবার ছাপে। Module 3-র lesson 1 ওই base-গুলো literal হিসেবে লিখেছিল; এখানে সেগুলো আবার পড়া হচ্ছে।
চতুর্থটা %zu ব্যবহার করে, যেটা sizeof-র দেওয়া type-র specifier। Module 2-র lesson 5 ওটা চিনিয়েছিল, আর এটা এই তালিকায় আছে কারণ সেখানে %d এমন একটা ভুল যেটা কেউ ধরিয়ে দেবে না।
Specifier আর মান যখন মেলে না
Specifier printf-কে বলে কয় byte নিতে হবে আর সেগুলো কীভাবে পড়তে হবে। ওটা ভুল হলেও printf ওই byte-গুলোই নেয় আর ওভাবেই পড়ে।
যে format যাচাই এটা ধরে সেটা থাকে -Wall-র পিছনে, আর Playground compile করে gcc -O2 -std=c17 -lm দিয়ে, কোনো -Wall ছাড়া। তাই Playground-এ এই ধরনের ভুল একদম নীরব, compile-র সময়েও আর চলার সময়েও।
এটা Playground এড়িয়ে চলার কারণ না। এটা নিচের চারটা অমিল চোখে চিনে রাখার কারণ, কারণ ওগুলো খুঁজতে তুমি কোনো সাহায্য পাবে না।
একটা মান, তিনটা width, যাতে ঘর জিনিসটা আসলে কী সেটা তুমি দেখতে পাও।
#include <stdio.h>
int main(void)
{
int marks = 7;
printf("|%d|\n", marks);
printf("|%4d|\n", marks);
printf("|%-4d|\n", marks);
return 0;
}
|7|
| 7|
|7 |
তিন লাইন, একটা মান। দাগগুলো কেবল এজন্য, যাতে তুমি ফাঁকা জায়গা গুনতে পারো।
Run in Compilerটাকা দুই দশমিক ঘরে, পরিমাণ ডানে সাজানো, আর নাম বাঁয়ে। তুমি যত problem পাবে তার অর্ধেকের গড়নই এটা।
#include <stdio.h>
int main(void)
{
printf("%-10s%5s%10s\n", "ITEM", "QTY", "TAKA");
printf("%-10s%5d%10.2f\n", "rice", 12, 45.00);
printf("%-10s%5d%10.2f\n", "oil", 3, 120.50);
printf("%-10s%5d%10.2f\n", "salt", 7, 2.50);
printf("%-10s%5d%10.2f\n", "TOTAL", 22, 1083.50);
return 0;
}
ITEM QTY TAKA
rice 12 45.00
oil 3 120.50
salt 7 2.50
TOTAL 22 1083.50
দশমিক বিন্দুগুলো মিলে থাকে কারণ ওই কলামের প্রতিটা সংখ্যার width আর precision এক। .2 সরিয়ে দাও, কলামটা ভেঙে পড়বে।
সত্যিকারের টাকা গোনা হয় পূর্ণ পয়সায়, Module 2-র lesson 3-র নিয়ম। এটা সাজানোর উদাহরণ, আর এই module-র graded problem-গুলো সবই পূর্ণসংখ্যা ব্যবহার করে।
Run in Compilerএকটা device থেকে পড়া মান, চার ভাবে ছাপা, কারণ নানা লোক নানা ভাবে চাইবে।
#include <stdio.h>
int main(void)
{
unsigned int reg = 48879;
printf("dec:%10u\n", reg);
printf("hex:%10x\n", reg);
printf("HEX:%10X\n", reg);
printf("oct:%10o\n", reg);
printf("padded hex: 0x%08X\n", reg);
return 0;
}
dec: 48879
hex: beef
HEX: BEEF
oct: 137357
padded hex: 0x0000BEEF
শেষ লাইনটাই সব জায়গায় hex dump লেখার নিয়ম। 0x তোমার টাইপ করা লেখা, আর 08 মান ছোট হলেও আট অঙ্ক ধরে রাখে।
এটা কোথায় কাজে লাগে
- এই platform-র প্রতিটা judge। Progsity-র judge তোমার output আর প্রত্যাশিত file অক্ষরে অক্ষরে মেলায়। পয়সার কলামে একটা শূন্য না থাকলেই wrong answer, আর তোমার আর ওটার মাঝখানে কেবল specifier-টাই দাঁড়ানো।
- যেকোনো Linux মেশিনের
ls -lতালিকা। মাপ, তারিখ আর নামের কলামগুলো ঠিক এই width দিয়েই ছাপা হয়, C code দিয়ে, 1971-এ লেখা আর এখনো চলা একটা program-এ। - আরও পাঁচটা ভাষা। Python, Java, Go, Rust আর PHP সবাই এই format ভাষার সরাসরি উত্তরসূরি বয়ে বেড়ায়। C-র রূপটা শিখলে ওদেরগুলোও এমনিতেই শেখা হয়ে যায়।
- কোনো ব্যাংকের export job-র CSV writer। প্রতিটা ঘর নির্দিষ্ট precision-এ ছাপা হয়, যাতে file পড়া দুইটা system শেষ পয়সা পর্যন্ত একই সংখ্যায় একমত থাকে।
যে ভুলগুলো সবাই করে
১. Specifier যে মানের সাথে মেলে না।
double price = 12.5;
printf("%d\n", price);
Playground-এ নীরব, কারণ format যাচাই থাকে -Wall-র পিছনে আর Playground ওটা দেয় না। Local gcc -Wall বলে warning: format '%d' expects argument of type 'int', but argument 2 has type 'double' আর সাহায্য করে %f পরামর্শ দিয়ে। যে সংখ্যাটা ছাপা হয় তার কোনো মানে নেই।
২. ফুটোর চেয়ে কম মান দেওয়া।
printf("marks %d and %d\n", 90);
Playground-এ নীরব। প্রথম ফুটোটা 90 পায়। দ্বিতীয়টা পড়ে যা কিছু argument-র জায়গায় পড়ে ছিল, তাই সেখানে অর্থহীন একটা সংখ্যা ভেসে ওঠে। -Wall দিলে কথাটা হয় warning: format '%d' expects a matching 'int' argument।
৩. লেখার মধ্যে একা একটা শতাংশ চিহ্ন।
printf("VAT is 15% of the bill\n");
Playground-এ নীরব, আর output এমনভাবে গোলমাল হয় যেটা দেখে কারণটা আন্দাজই করা যায় না। %, তার পরের ফাঁকা জায়গা আর of-র o, তিনটা মিলে একটা specifier হিসেবে পড়া হয়, তাই ওই অক্ষরগুলো হাওয়া হয়ে যায় আর সেখানে একটা সংখ্যা এসে বসে। শতাংশ চিহ্নের জন্য %% লেখো।
৪. sizeof বা long long-র জন্য %d।
long long total = 5000000000;
printf("%d\n", total);
printf("%d\n", sizeof(int));
Playground-এ দুই লাইনেই নীরব। %d চার byte নেয় আর ওই দুইটা মানই আট byte, তাই যে সংখ্যাটা ছাপা হয় সেটা ওদের ভুল অর্ধেক থেকে বানানো। long long-র জন্য %lld আর sizeof-র জন্য %zu, আর Module 2-র lesson 5 থেকেই ওগুলো এসেছে।
Kenji একটা device register পড়ছে আর ওটা তিনটা base-এ চায়, প্রতিটা একই দশ চওড়া ঘরে বসানো।
Input. এক লাইনে decimal-এ একটা পূর্ণসংখ্যা n।
Output. চার লাইন: dec:, hex:, HEX: আর oct:, প্রতিটার পরে n একটা 10 চওড়া ঘরে।
Constraints. 0 <= n <= 4294967295, তাই মানটার জন্য unsigned int লাগবে।
Sample. Input 255 দিলে dec: 255 আর আরও তিন লাইন।
#include <stdio.h>
int main(void)
{
unsigned int n = 0;
scanf("%u", &n);
/* Four lines, four bases, one width. */
return 0;
}
hex-octal-dump নামে graded। Range-র একদম উপরে চার লাইনের একটা এগারো অক্ষর চওড়া হয়, আর সেটাই ঠিক।
উপরের example-র Amara-র table আবার বানাও, প্রতিটা কলাম তিন অক্ষর করে চওড়া করে। শেষে Maria-র জন্য একটা সারি যোগ করো, 5 solved আর 91 penalty দিয়ে।
Output. ছয় লাইন, header আর পাঁচজন খেলোয়াড়, প্রতিটা কলাম মিলে থাকবে।
কোনো input নেই। Copy না করে পুরোটা টাইপ করো, আর একটা width বদলে দেখো table কীভাবে সরে যায়।
#include <stdio.h>
int main(void)
{
/* One format string, used six times. Start from the example above. */
return 0;
}
এই module-এ graded না: input ছাড়া program-র একটাই সম্ভাব্য output, আর একটা test কোনো judge না।
Run in Compilerতিনটা দাম দশমিক হিসেবে পড়ো আর প্রতিটা ঠিক দুই দশমিক ঘর পর্যন্ত ছাপো, দশ চওড়া একটা ঘরে। প্রতিটার পরে taka শব্দটা বসাও।
Input. এক লাইনে তিনটা দশমিক সংখ্যা।
Output. তিন লাইন, প্রতিটায় একটা দাম তারপর একটা ফাঁকা জায়গা তারপর taka।
Sample. Input 45 120.5 2.5 দিলে 45.00 taka আর আরও দুই লাইন।
#include <stdio.h>
int main(void)
{
double a = 0.0;
double b = 0.0;
double c = 0.0;
scanf("%lf %lf %lf", &a, &b, &c);
/* Three printf calls, one width and one precision each. */
return 0;
}
এই module-এ graded না। এই ভাবনার graded রূপটা problem lesson-র invoice-exact, যেটা বদলে পূর্ণ পয়সায় গোনে।
নিজের একটা লাইন ছাপো, তারপর ছাপো ওই লাইনটা কয় অক্ষরের ছিল, printf-র দেওয়া মান দিয়ে, নিজে গোনা কোনো সংখ্যা দিয়ে না।
Output. দুই লাইন। দ্বিতীয়টা বলে প্রথমটা কত লম্বা ছিল।
তারপর প্রথম লাইনের লেখা বদলাও আর দেখো দ্বিতীয় লাইনটা তোমার হাত না লাগিয়েই ওটাকে অনুসরণ করে।
#include <stdio.h>
int main(void)
{
int written = printf("Progsity Learn, C track\n");
/* One more printf, using written. */
return 0;
}
এই module-এ graded না। তারপর কাগজে এটার উত্তর দাও: গোনাটায় newline ধরা আছে কি না, আর স্মৃতির উপর ভরসা না করে সেটা কীভাবে প্রমাণ করবে।
Run in Compilerযে প্রশ্নগুলো সবার মনে আসে
মান বড় হলে কি width ওটাকে কেটে দেয়?
না। Width কেবল সবচেয়ে কম কতটুকু। ঘরটা মানের মাপে বড় হয়ে যায় আর পরের কলামগুলো সরে যায়। এই কারণেই একটা লম্বা সারিতে table ভাঙে, সব জায়গায় না।
printf-এfloatআরdoubleদুইটার জন্যই%fকেন?কারণ call-এ ঢোকার পথে
floatargument-টাdoubleহয়ে চওড়া হয়ে যায়।scanf-এ কিছুই চওড়া হয় না, আর সেজন্যই সেখানে সত্যিই%lfলাগে। সেটাই পরের lesson।%.2fকি গোল করে না কেটে দেয়?গোল করে। ও যেটাকে গোল করে সেটা
double-এ জমা মান, আর Module 2-র lesson 3 দেখিয়েছিল ওটা সবসময় তোমার টাইপ করা মান না।Width কি variable-এ রাখা যায়?
যায়, একটা তারা দিয়ে:
printf("%*d", 8, marks)প্রথম argument থেকে width নেয়। এটা কমই লাগে, আর হাতে তোলার চেয়ে চিনে রাখাই কাজের।আমার সংখ্যাটা অদ্ভুত জায়গায় এসে বসছে কেন?
প্রায় সবসময়ই ওই একই string-এ আগে কোথাও একা একটা
%আছে। খারাপ specifier-র পরের সবকিছুই সরে যায়, তাই ক্ষতির জায়গায় না তাকিয়ে তার বাঁ দিকে তাকাও।
মূল কথা
- Specifier মানে একটা শতাংশ চিহ্ন, তারপর flag, width আর precision, তারপর একটা conversion অক্ষর।
- Width সবচেয়ে কম কতটুকু: বড় মানের জন্য ঘর বাড়ে, আর কোনো মান কখনো কাটা যায় না।
- Precision float গোল করে, string কেটে দেয়, আর integer-কে শূন্য দিয়ে ভরাট করে।
- Minus flag বাঁয়ে সাজায় আর zero flag শূন্য দিয়ে ভরে; দুইটাই width-র আগে বসে।
printfকয় অক্ষর লিখল সেটা ফেরত দেয়, আর%%একটা শতাংশ চিহ্ন ছাপে।- ভুল specifier Playground-এ নীরব, কারণ format যাচাইয়ের জন্য
-Wallলাগে।
এরপর তুমি পুরো জিনিসটা উল্টে দিয়ে মান পড়বে, যেখানে একই format ভাষার একটা বাড়তি নিয়ম আর একটা বিখ্যাত ফাঁদ আছে।
lesson ৩ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Scanf: মাথা না খারাপ করে ইনপুট নেওয়া