Module ৩ · কনস্ট্যান্ট, qualifier, ইনপুট আর আউটপুট
Scanf: মাথা না খারাপ করে ইনপুট নেওয়া
এই lesson-এ যা শিখবে
- একটা integer, একটা দশমিক, একটা অক্ষর আর একটা শব্দ পড়তে পারবে, আর
scanfযা ফেরত দিল সেটা ছাপতে পারবে। - Ampersand-কে বাক্সের ঠিকানা হিসেবে বোঝাতে পারবে, আর ওটা ছাড়া কী হয় সেটা বলতে পারবে।
- পড়ে থাকা newline একটা ফাঁকা জায়গা দিয়ে এড়াতে পারবে, আর
%s-এ width বসিয়ে সীমা টানতে পারবে।
Bob নিজের program-এ 12 34 লেখে আর ওটা কিছু না ছেপেই মরে যায়। ও এমন একটা লাইন থেকে একটা অক্ষর সরায় যেটা ও নিজে লেখেনি, আর program চলতে শুরু করে। তারপর ও সংখ্যার পরে একটা অক্ষর পড়ে আর এমন একটা অক্ষর পায় যেটা ও কখনো টাইপই করেনি।
দুইটাই একই lesson, দুইবার শেখা। printf থেকে যে format ভাষাটা তুমি চেনো, scanf সেটাই উল্টো করে ব্যবহার করে, সাথে একটা বাড়তি নিয়ম আর একটা বিখ্যাত ফাঁদ। এই lesson ওই দুইটা জিনিস, আর কিছু না।
Scanf হলো printf-র আয়না, সাথে একটা বাড়তি নিয়ম
printf মান নেয় আর লেখা ছাপে। scanf লেখা নেয় আর মান লেখে, মানে ওগুলো রাখার একটা জায়গা ওর দরকার।
ওকে variable দিলে ওটা variable-র মানের একটা নকল পেত, আর নকলে লিখলে কিছুই বদলায় না। তাই তুমি বদলে বাক্সটার ঠিকানা দাও, একটা ampersand দিয়ে।
একটা scanf call, আর প্রতিটা অংশ যা করে
int items = scanf("format", &first, &second);
&x x-র ঠিকানা: x কোথায় থাকে, ভিতরে কী আছে তা না
যেটা printf-র কখনো লাগে না আর scanf-র সবসময় লাগে
%d %u একটা int, একটা unsigned int
%ld %lld একটা long, একটা long long
%lf একটা double. এখানে %f float পড়ে আর double নষ্ট করে
%c ঠিক একটা অক্ষর, ফাঁকা জায়গাও ধরে
%s একটা শব্দ, প্রথম ফাঁকা জায়গায় থেমে যায়
%19s একই জিনিস, বড়জোর 19 অক্ষর আর শেষের চিহ্ন লেখে
%x %o base 16 বা base 8-এ লেখা একটা সংখ্যা
একটা ফাঁকা format-এ, এখানে যত ফাঁকা জায়গা আছে সব এড়িয়ে যাও
" %c" মানে একটা ফাঁকা তারপর %c, আর ওটাই ফাঁদের সমাধান
ফেরত দেয় কয়টা জিনিস ভরা হলো। Input ফুরালে EOF
- যে specifier একটা variable ভরে তার ঠিকানা লাগে, তাই প্রতিটারই ampersand লাগে।
- ফাঁকা জায়গা মানে space, tab আর newline, আর
%d,%fআর%sশুরুর আগে ওগুলো এড়িয়ে যায়। %c-ই একমাত্র যেটা এড়ায় না, আর পুরো ফাঁদটা ওখানেই।
তাই গড়নটা সবসময় একই: input-র আয়না হওয়া একটা format, প্রতিটা লক্ষ্যে একটা ampersand, আর ফেরত পাওয়া একটা গোনা যেটার দিকে তুমি তাকাও।
Scanf যা ফেরত দেয় প্রতিবার সেটা ছাপো
scanf ফেরত দেয় ও সফলভাবে কয়টা জিনিস ভরল। কয় অক্ষর তা না, আর মানগুলো যুক্তিসঙ্গত কি না তাও না।
ওই সংখ্যাটা গ্রাহ্য না করা beginner-দের সবচেয়ে সাধারণ অভ্যাস, আর ওটাই ভুল input-কে পরিষ্কার একটা কথার বদলে ভুল উত্তরে বদলে দেয়। ওটা নিয়ে কী করতে হয় সেটা lesson 5; এই lesson কেবল ওটা ছাপে।
#include <stdio.h>
int main(void)
{
int roll = 0;
int read = scanf("%d", &roll);
printf("scanf returned %d\n", read);
printf("roll is %d\n", roll);
return 0;
}
scanf returned 1
roll is 47
ওই output-টা 47 input-র জন্য। একটা জিনিস চাওয়া হয়েছিল, একটা ভরা হয়েছে, তাই উত্তরটা 1।
সংখ্যার বদলে একটা শব্দ লেখো, প্রথম লাইনটা হবে 0, আর ওটাই lesson 5-র পুরো বিষয়।
একসাথে কয়েকটা মান, আর format-এ ফাঁকা জায়গা
একটা format কয়েকটা মান চাইতে পারে। এদের মাঝে তুমি সাধারণত একটা ফাঁকা জায়গা লেখো, মানে "এখানে যত ফাঁকা আছে এড়িয়ে যাও", আর তাতে Enter চাপার newline-টাও ধরা পড়ে।
তাই একটাই format এক লাইনে বা তিন লাইনে লেখা মান পড়তে পারে, কিছু না বদলেই।
#include <stdio.h>
int main(void)
{
int roll = 0;
char section = '?';
double cgpa = 0.0;
int read = scanf("%d %c %lf", &roll, §ion, &cgpa);
printf("scanf returned %d\n", read);
printf("roll : %d\n", roll);
printf("section : %c\n", section);
printf("cgpa : %.2f\n", cgpa);
return 0;
}
scanf returned 3
roll : 47
section : B
cgpa : 3.78
ওই output-টা 47 B 3.78 input-র জন্য। একই তিনটা মান তিন লাইনে লিখলেও ফল একই।
%c-র আগের ফাঁকা জায়গাটা ওখানে সত্যিকারের কাজ করছে, আর পরের অংশটা বলে কেন।
%c ফাঁকা জায়গা এড়ায় না, আর newline একটা ফাঁকা জায়গা
বাকি প্রতিটা specifier পড়া শুরুর আগে সামনের ফাঁকা জায়গা এড়িয়ে যায়। %c যায় না, কারণ কখনো কখনো একটা ফাঁকা জায়গাই তোমার চাওয়া অক্ষর।
কথাটা যুক্তিসঙ্গত আর এটাই প্রতিটা beginner C course-র সবচেয়ে বেশি রিপোর্ট হওয়া bug বানায়। ঘটনাটা এই ক্রমে ঘটে।
তুমি 47 লিখে Enter চাপো। %d 4 আর 7 পড়ে, newline-এ থেমে যায়, আর ওটাকে stream-এ ফেলে রাখে। তারপর %c পরের অক্ষরটা পড়ে, আর সেটা ওই newline।
সমাধানটা format string-এ একটা ফাঁকা জায়গা। ওখানে একটা ফাঁকা মানে "যত ফাঁকা আছে এড়িয়ে যাও", তাই cursor newline পেরিয়ে যায় আর তুমি যে অক্ষরটা চেয়েছিলে সেটাই পড়ে।
এই কারণেই উপরের Example 2-র দ্বিতীয় পড়াটা " %c" লেখা ছিল, "%c" না।
%s ফাঁকা জায়গায় থামে, আর কোথায় লেখা থামাতে হয় জানে না
%s একটা শব্দ পড়ে: সামনের ফাঁকা জায়গা এড়ায়, তারপর পরের ফাঁকা জায়গা পর্যন্ত অক্ষর নেয়। ফাঁকা জায়গাওয়ালা একটা লাইন ও পড়তে পারে না, আর গোটা লাইন পড়া Module 10-র বিষয়।
বিপজ্জনক অর্ধেকটা হলো অন্য প্রান্তে ও কী করে। খালি একটা %s যত অক্ষর আসে ততই লিখে যায়, ওদের জায়গা আছে কি না না দেখেই।
সমাধান একটা width: %19s বড়জোর 19 অক্ষর লেখে, সাথে C প্রতিটা লেখার শেষে যে চিহ্নটা বসায় সেটা। উনিশ আর একটা চিহ্ন বিশটা বাক্সে আঁটে।
#include <stdio.h>
int main(void)
{
char word[20] = {0};
char initial = '?';
int first = scanf("%19s", word);
int second = scanf(" %c", &initial);
printf("first scanf returned %d, word is %s\n", first, word);
printf("second scanf returned %d, initial is %c\n", second, initial);
return 0;
}
first scanf returned 1, word is Progsity
second scanf returned 1, initial is R
ওই output-টা Progsity R input-র জন্য। Array Module 9-র আর লেখা Module 10-র বিষয়, তাই আপাতত char word[20]-কে পাশাপাশি বিশটা বাক্স ধরে নাও।
খেয়াল করো word-র সাথে কোনো ampersand নেই। একটা array-র নাম নিজেই একটা ঠিকানা, আর এটাই ampersand-র নিয়মের একমাত্র ব্যতিক্রম, আর Module 9 আর Module 11 থাকার কারণ।
Double-র জন্য %lf, সবসময়, আর কেবল এখানেই
printf-এ %f float আর double দুইটাই ছাপে, কারণ call-এ ঢোকার পথে float-টা চওড়া হয়ে যায়।
scanf-এ কিছুই চওড়া হয় না, কারণ তুমি একটা ঠিকানা দিচ্ছ আর ঠিকানা মাপ নিয়ে কিছু বলে না। %f মানে "এখানে চার byte লেখো" আর %lf মানে "আট byte লেখো"।
double-র জন্য %f দিলে scanf আট byte-র বাক্সে চার byte লেখে। মানটা ভুল হয়, Playground কিছুই বলে না, আর যে সংখ্যাটা তুমি ছাপো সেটা সাধারণত শূন্য।
তাই নিয়মটা ছোট: scanf-এ double মানে %lf, প্রতিবার।
সবচেয়ে ছোট কাজের পড়া। দুইটা মান, একটা format, আর গোনাটা ছাপা, যাতে তুমি দেখতে পাও কাজ হয়েছে।
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
int read = scanf("%d %d", &a, &b);
printf("read %d value(s)\n", read);
printf("%d + %d = %d\n", a, b, a + b);
return 0;
}
read 2 value(s)
12 + 34 = 46
ওই output-টা 12 34 input-র জন্য। যেকোনো একটা ampersand সরিয়ে দাও, Playground কিছু না বলেই compile করবে, তারপর program বেয়াড়াপনা করবে।
ফাঁদ আর তার সমাধান এক program-এ। দুইটা পড়া একটা ফাঁকা জায়গা ছাড়া হুবহু এক।
#include <stdio.h>
int main(void)
{
int roll = 0;
char section = '?';
int first = scanf("%d", &roll);
int second = scanf(" %c", §ion);
printf("returned %d and %d\n", first, second);
printf("roll %d, section [%c], as a number %d\n", roll, section, section);
return 0;
}
returned 1 and 1
roll 47, section [B], as a number 66
ওই output-টা এক লাইনে 47 আর পরের লাইনে B দেওয়ার জন্য। দ্বিতীয় format থেকে ফাঁকা জায়গাটা সরিয়ে দাও, একই input দুই লাইন জুড়ে section [] দেয়, as a number 10 দেয়, আর ফেরত পাওয়া গোনা দুইটা একই থাকে।
শেষ অংশটা দুইবার পড়ো। ফেরত পাওয়া মান এই ভুলটা ধরতে পারে না, কারণ scanf-কে যা বলা হয়েছিল ও ঠিক তাই করেছে আর সফল হয়েছে।
একটা নাম, একটা roll, একটা section আর একটা ফল, পড়া আর lesson 3-র width দিয়ে আবার ছাপা।
#include <stdio.h>
int main(void)
{
char name[20] = {0};
int roll = 0;
char section = '?';
double cgpa = 0.0;
int read = scanf("%19s %d %c %lf", name, &roll, §ion, &cgpa);
printf("fields read : %d\n", read);
printf("%-10s%6d%4c%8.2f\n", name, roll, section, cgpa);
return 0;
}
fields read : 4
Nusrat 47 B 3.78
ওই output-টা Nusrat 47 B 3.78 input-র জন্য। চারটা specifier, চারটা মান, চারটা গোনা, আর একটা ampersand ইচ্ছে করেই নেই, কারণ name একটা array।
%c-র আগের ফাঁকা জায়গাটা এখনো আছে। এই input-এ ওটা কিছুই করছে না, কারণ %d newline-র বদলে একটা ফাঁকা জায়গায় থেমেছে, আর ওটা রেখে দিতে কোনো খরচ নেই।
এটা কোথায় কাজে লাগে
- এই platform-র judge-র প্রতিটা problem। তোমার program-র standard input-এ একটা file তুলে দেওয়া হয়, আর তোমার পড়াগুলোই ওটাকে ভিতরে আনে। এই module-র graded problem-গুলো সবই একটা
scanfলাইন দিয়ে শুরু, যেটা তোমার জন্য লিখে দেওয়া। - একটা configuration parser।
key valueগড়নের একটা setting file পড়া মানে দুই specifier-র একটাscanf-কে ঘিরে একটা loop, আর ফেরত পাওয়া গোনাই loop-কে থামতে বলে। Module 6 সেই loop বন্ধ করে। - যেকোনো interactive tool-র command prompt। একটা database shell, একটা calculator, একটা খেলার চাল নেওয়ার ঘর: সবাই একটা token পড়ে, গোনাটা দেখে, তারপর কাজ করে।
- কোনো ব্যাংকের রাতের import। নির্দিষ্ট গড়নের লক্ষ লক্ষ record পড়া হয়, প্রতি record type-র জন্য একটা format string দিয়ে। যে record-র গোনা কম আসে সেটা আন্দাজ না করে reject file-এ যায়।
যে ভুলগুলো সবাই করে
১. Ampersand ভুলে যাওয়া।
int roll = 0;
scanf("%d", roll);
Playground-এ নীরব, কারণ যাচাইটা থাকে -Wall-র পিছনে। Local gcc -Wall বলে warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int'। scanf roll-এ যে সংখ্যাটা আছে সেটা নেয় আর ওটাকে লেখার ঠিকানা ধরে নেয়। তারপর কী হয় সেটা undefined behaviour: আমাদের চালানোয় লেখাটা আটকে দেওয়া হয়েছিল আর scanf 0 ফেরত দিয়েছিল, আর অন্য মেশিনে program-টাকেই মেরে ফেলা হয়।
২. সংখ্যার পরে খালি একটা %c।
scanf("%d", &roll);
scanf("%c", §ion);
কোথাও কিছু বলা হয় না, আর দুইটা পড়াই 1 ফেরত দেয়। যে অক্ষরটা পড়া হয় সেটা Enter key-র রেখে যাওয়া newline, মানে 10 নম্বর অক্ষর। সমাধান একটা ফাঁকা জায়গা: scanf(" %c", §ion)।
৩. double-র জন্য %f।
double cgpa = 0.0;
scanf("%f", &cgpa);
Playground-এ নীরব, আর scanf 1 ফেরত দেয় যেন কাজ হয়ে গেছে। আমাদের চালানোয় প্রতিটা input-র জন্য ওটা ছেপেছে 0.00। Local gcc -Wall বলে warning: format '%f' expects argument of type 'float *', but argument 2 has type 'double *' আর %lf পরামর্শ দেয়।
৪. Width ছাড়া একটা %s।
char word[20] = {0};
scanf("%s", word);
দুই command line-র কোনোটাতেই কিছু বলে না। তুমি যত ছোট শব্দ দিয়ে পরীক্ষা করো সবগুলোতেই কাজ করে, আর 40 অক্ষরের একটা শব্দ বাক্সগুলোর শেষ পেরিয়ে 20 অক্ষর লিখে ফেলে। সত্যিকারের নিরাপত্তার ফাঁকের একটা গোটা পরিবারের গড়নই এটা, আর সমাধান তিনটা অক্ষর: %19s।
Kenji একটা calculator-র সবচেয়ে সহজ দুইটা button পরীক্ষা করছে, আর সংখ্যাগুলো এত বড় যে দ্বিতীয় উত্তরটা প্রথমটার জায়গায় আঁটবে না।
Input. এক লাইনে দুইটা পূর্ণসংখ্যা a আর b।
Output. দুই লাইন: যোগফল, তারপর গুণফল।
Constraints. -100000 <= a, b <= 100000। যোগফল int-এ আঁটে আর গুণফল আঁটে না।
Sample. Input 3 4 দিলে 7 তারপর 12।
#include <stdio.h>
int main(void)
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
/* Two printf calls. The product does not fit in an int. */
return 0;
}
sum-and-product নামে graded। Module 2-র lesson 2-র এক পাশ চওড়া করার নিয়মটা এখানে হুবহু খাটে।
Zara-র sign-in desk-এ নামের ঘরটা উনিশ অক্ষর চওড়া, আর কোনো একদিন কেউ এর চেয়ে লম্বা নাম লিখবেই।
Input. বড়জোর এক লাইন, তাতে ফাঁকা জায়গা ছাড়া একটা শব্দ। ফাঁকাও হতে পারে।
Output. দুই লাইন: Hello, <নাম>! তারপর read আর scanf যা ফেরত দিল।
Constraints. শব্দটা 1 থেকে 19 অক্ষরের। ফাঁকা input -1 ফেরত দেয় আর ফাঁকা নামকে শুভেচ্ছা জানায়।
Sample. Input Maria দিলে Hello, Maria! তারপর read 1।
#include <stdio.h>
int main(void)
{
char name[20] = {0};
int read = scanf("%19s", name);
/* Two lines: the greeting, then what scanf gave back. */
return 0;
}
greet-name নামে graded। Format-র 19-টা যেমন আছে তেমনই রাখো, আর না থাকা ampersand-টাকেও হাত দিয়ো না: array-র নাম নিজেই একটা ঠিকানা।
একটা roll number, একটা section-র অক্ষর আর একটা ফল পড়ো, তারপর তিনটা মিলিয়ে সাজানো লাইনে ছাপো।
Input. একটা roll number, একটা section-র অক্ষর আর একটা দশমিক, এই ক্রমে, এক লাইনে বা কয়েক লাইনে।
Output. তিন লাইন: Roll, Section আর CGPA, প্রতিটা label আট অক্ষরে ভরাট আর প্রতিটা মান নয় চওড়া ঘরে ডানে সাজানো।
Sample. Input 47 B 3.78 দিলে এমন একটা slip, যার তিনটা মান একই কলামে শেষ হয়।
#include <stdio.h>
int main(void)
{
int roll = 0;
char section = '?';
double cgpa = 0.0;
int read = scanf("%d %c %lf", &roll, §ion, &cgpa);
/* Three printf calls, then one more line printing read. */
return 0;
}
এই module-এ graded না। তারপর তিনটা মান তিন লাইনে দিয়ে আবার চালাও আর দেখে নাও কিছুই বদলায়নি।
Run in Compilerতিনটা দশমিক সংখ্যা পড়ো আর তাদের গড় ঠিক দুই দশমিক ঘর পর্যন্ত ছাপো।
Input. ফাঁকা জায়গা দিয়ে আলাদা করা তিনটা দশমিক সংখ্যা।
Output. এক লাইনে গড়টা, দুই দশমিক ঘর পর্যন্ত।
Sample. Input 1 2 4 দিলে 2.33।
#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);
/* One printf. Divide by 3.0 and not by 3, and ask yourself why. */
return 0;
}
এই module-এ graded না। কঠিন অংশটা ওই মন্তব্যেই: বের করো প্রতিটা মান double হলে (a + b + c) / 3 কী দেয়, আর ওগুলো int হলে কী দিত।
যে প্রশ্নগুলো সবার মনে আসে
%s-র ampersand লাগে না কেন?কারণ একটা array-র নাম মানেই তার প্রথম বাক্সের ঠিকানা। এই এক জায়গাতেই নিয়মটা ভাঙা মনে হয়, আর Module 9-এ গিয়ে সেটা আর ভাঙা মনে হয় না।
scanfকি ফাঁকা জায়গাসহ গোটা লাইন পড়তে পারে?বুদ্ধিমানের মতো না।
%sপ্রথম ফাঁকায় থেমে যায়, আর ওটা এড়ানোর কায়দাগুলো আসল যন্ত্রটার চেয়ে খারাপ, আর সেই যন্ত্রটা হলো Module 10-রfgets।প্রতিটা specifier-র আগেই কি ফাঁকা জায়গা দেব?
সব জায়গায় ওটা নিরীহ আর দরকার কেবল
%c-র আগে। অনেকে অভ্যাসবশে সব জায়গায় লেখে, আর তাতে কেউ কখনো ঠকেনি।"Enter a number" জাতীয়
printfprompt কি দরকার?Judge-এ না, আর ওগুলো তোমার test ফেল করাবে, কারণ judge তোমার ছাপা প্রতিটা অক্ষর মেলায়। পরীক্ষা করার সময় লেখো, আর submit করার আগে সরিয়ে ফেলো।
Format যত চায় তার চেয়ে বেশি মান লিখলে কী হয়?
বাড়তিগুলো stream-এ পড়ে থাকে, পরের পড়ার অপেক্ষায়। কিছুই হারায় না আর কেউ আপত্তিও করে না, আর problem lesson-র
line-parserঠিক এটার উপরেই দাঁড়ানো।
মূল কথা
scanfবাক্সের ভিতরে লেখে, তাই ওর একটা ঠিকানা লাগে, আর ampersand সেটাই দেয়।- ও কয়টা জিনিস ভরল সেটা ফেরত দেয়, আর ওই সংখ্যাটা ছাপা এমন একটা অভ্যাস যেটা গড়ে তোলা উচিত।
- Format-এ ফাঁকা জায়গা মানে "এখানে যত ফাঁকা আছে এড়িয়ে যাও", newline-ও ধরে।
%c-ই একমাত্র specifier যেটা ফাঁকা এড়ায় না, আর" %c"তার সমাধান।%sএকটা শব্দ নেয় আর তার একটা width লাগে, নইলে ও বাক্সগুলোর শেষ পেরিয়ে লেখে।scanf-এdoubleমানে%lf, আর সেখানে%fPlayground-এ একটা নীরব ব্যর্থতা।
এরপর তুমি পড়াটা সফল হয়েছে ধরে নেওয়া ছেড়ে দেবে, আর ফেরত পাওয়া গোনাটাকে দিয়ে সত্যিকারের কাজ করাবে।
lesson ৪ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: ইনপুট ভুল এলে: যা পড়লে তা যাচাই করা