সরাসরি পর্বে যান
পর্ব ০৭ · ৫৫-এর মধ্যে · মডিউল ২

ইনপুট-আউটপুট — cin, cout, format আর print

Input & Output — Reading Safely and Printing Neatly in Modern C++

Taught in Bangla, this lesson covers console I/O in C++: reading with std::cin, handling bad input with clear and ignore, std::getline, formatting with iomanip, std::format and std::println, choosing between cout, printf and cerr, and fast I/O settings.

এই পর্বে যা শিখবেন

  • std::cin >> ভেতরে ভেতরে ইনপুট কীভাবে কাটে, আর ভুল ইনপুটে stream-এর কী হয়
  • ভুল ইনপুট ধরে আবার পড়া, আর ইনপুট শেষ (EOF) পর্যন্ত পড়ার নিরাপদ লুপ
  • std::getline দিয়ে পুরো লাইন পড়া, আর বিখ্যাত “হারানো Enter” বাগ সারানো
  • <iomanip> আর std::format/std::println দিয়ে নিখুঁত মার্কশিট বা রসিদ ছাপা
  • std::cerr, কনটেস্টের গতি-কৌশল, বাংলা লেখার সমস্যা আর AI-এর লেখা I/O কোড যাচাই

১ · ইনপুট-আউটপুট আসলে কী — দুটি ধারণা

পর্ব ০৪-এ আপনি std::cout দিয়ে লেখা ছাপিয়েছেন, আর জেনেছেন এটি একটি stream — একটি পাইপ। এই পর্বে সেই পাইপের দুই দিকই পুরোপুরি হাতে নেবেন: ব্যবহারকারীর কাছ থেকে নিরাপদে পড়া, আর ফলাফল সুন্দর করে সাজিয়ে দেখানো। কোড দেখার আগে দুটি ধারণা পরিষ্কার করে নেওয়া যাক, কারণ এই পর্বের প্রায় সব বাগ এই দুটি না বোঝা থেকে আসে।

ধারণা ১: stream হলো buffer-যুক্ত পাইপ

আপনার প্রোগ্রাম কখনো সরাসরি কিবোর্ড ছোঁয় না। আপনি যখন টাইপ করেন, অক্ষরগুলো প্রথমে terminal ধরে রাখে; Enter চাপলে পুরো লাইনটি অপারেটিং সিস্টেম হয়ে প্রোগ্রামের ইনপুট buffer-এ এসে জমা হয়। std::cin সেই buffer থেকে যতটুকু দরকার ততটুকু তুলে নেয়, বাকিটা পরের পড়ার জন্য রেখে দেয়। আউটপুটেও একই: std::cout-এ লেখা আগে buffer-এ জমে, সুযোগমতো একবারে স্ক্রিনে যায়।

কাঁচাবাজারের ঝুড়ির কথা ভাবুন। দোকানদার (অপারেটিং সিস্টেম) একবারে এক ঝুড়ি সবজি নামিয়ে দেন — “Rafi 21 3.75” আর শেষে Enter. আপনি (প্রোগ্রাম) ঝুড়ি থেকে একটা একটা করে জিনিস তোলেন: আগে নাম, তারপর বয়স, তারপর CGPA. যা তোলেননি, তা ঝুড়িতেই থাকে — পরের বার হাত দিলে সেটাই আগে হাতে আসবে। এই পর্বের সবচেয়ে বিখ্যাত বাগটি ঠিক এখান থেকে আসে: ঝুড়ির তলায় পড়ে থাকা একটি Enter.

পাইপ হওয়ার আরেকটি বড় সুবিধা: পাইপের অন্য মাথায় কী আছে, প্রোগ্রাম জানে না, জানার দরকারও নেই। ./result < marks.txt লিখলে ইনপুট আসে ফাইল থেকে, ./result > out.txt লিখলে আউটপুট যায় ফাইলে — কোডে এক অক্ষরও বদলাতে হয় না। একে বলে redirection. Codeforces-এর মতো online judge ঠিক এভাবেই আপনার প্রোগ্রামকে গোপন ইনপুট খাওয়ায় (অংশ ৮-এ বিস্তারিত)।

ধারণা ২: মান আর তার লেখা আলাদা জিনিস

মেমরিতে double cgpa = 3.75; সাধারণত ৬৪টি বিট (পর্ব ০৫)। স্ক্রিনে কিন্তু বিট যায় না, যায় অক্ষর। একই মান অনেকভাবে লেখা যায় — 3.75, 3.750, 3.75 (সামনে ফাঁকা), +3.75, এমনকি ইউরোপের কিছু দেশে 3,75. মান একটাই, উপস্থাপন ভিন্ন। আউটপুট formatting মানে এই উপস্থাপন বেছে নেওয়া; ইনপুট পড়া মানে উল্টো কাজ — অক্ষরের সারি থেকে মান তৈরি করা।

মূল ধারণা

Input = লেখা → মান (parsing), output = মান → লেখা (formatting). দুই দিকেই টাইপ সিদ্ধান্ত নেয়। int-এ পড়তে বললে stream শুধু অঙ্ক খোঁজে; std::string-এ পড়তে বললে প্রথম ফাঁকা জায়গা পর্যন্ত সব নেয়। আর formatting শুধু চেহারা বদলায় — {:.2f} দিয়ে 84.6667-কে 84.67 দেখালেও মেমরির মান 84.6667-ই থাকে।

২ · std::cin >> — ফাঁকা জায়গায় কাটা টুকরো

>>-কে বলে extraction operator (“বের করে আনার অপারেটর”) — তীরের দিক দেখুন, stream থেকে ভ্যারিয়েবলের দিকে। <<-এর মতো এটিও চেইন করা যায়, কারণ প্রতিটি >> সেই একই stream ফেরত দেয়। আর C-এর scanf-এর মতো & বা %d লাগে না: ভ্যারিয়েবলের টাইপ দেখেই compiler ঠিক সংস্করণটি বেছে নেয়।

std::string name;
int age{};
double cgpa{};
std::cin >> name >> age >> cgpa;   // ইনপুট: Rafi 21 3.75

প্রতিটি >> তিনটি নিয়ম মেনে চলে: (১) শুরুতে যত whitespace — স্পেস, ট্যাব, নতুন লাইন — সব বাদ দেয়; (২) ভ্যারিয়েবলের টাইপের সাথে মেলে এমন অক্ষর যতক্ষণ পায়, পড়ে; (৩) প্রথম অমিল অক্ষর দেখে থামে, আর সেটিকে buffer-এই রেখে দেয়। ধাপে ধাপে দেখুন (· মানে একটি স্পেস):

1std::string name;2int age{};3double cgpa{};4std::cin >> name >> age >> cgpa;
buffer, ভ্যারিয়েবল আর stream-এর অবস্থা
buffer
name
age
cgpa
অবস্থা
  1. Enter চাপার পর। ভ্যারিয়েবল তিনটি {} দিয়ে শূন্য/খালি করে বানানো হয়েছে। ব্যবহারকারী লিখলেন Rafi 21 3.75 আর Enter — পুরো লাইন, শেষের \n-সহ, এখন buffer-এ। খেয়াল করুন: Enter-ও একটি অক্ষর।
  2. >> name — একটি শব্দ। std::string-এর জন্য নিয়ম: whitespace না আসা পর্যন্ত সব অক্ষর। “Rafi” পড়ে স্পেসে থামল। স্পেসটি খাওয়া হয়নি, buffer-এর সামনে রয়ে গেছে। তাই “Rafiul Islam” লিখলে name-এ আসত শুধু “Rafiul”.
  3. >> age — একটি পূর্ণসংখ্যা। প্রথমে সামনের স্পেস বাদ। তারপর অঙ্ক খোঁজা: '2', '1' — তারপর স্পেস, যা অঙ্ক নয়, তাই থামা। লেখা “21” থেকে সংখ্যা 21 বানিয়ে age-এ রাখা হলো। এই অক্ষর থেকে সংখ্যা বানানোর কাজটিই parsing.
  4. >> cgpa — একটি দশমিক। আবার স্পেস বাদ, তারপর “3.75” — দশমিক বিন্দুও এবার বৈধ, কারণ টাইপ double. \n দেখে থামল। সব ভ্যারিয়েবল পূরণ, কিন্তু \n buffer-এ রয়ে গেল! পরের >> একে whitespace হিসেবে বাদ দেবে, কিন্তু অংশ ৪-এর getline দেবে না।
  5. ভিন্ন দৃশ্য: ইনপুট “Rafi abc 3.75”. name ঠিকই “Rafi” পেল। কিন্তু age-এর জন্য প্রথম অক্ষর 'a' — অঙ্ক নয়। পড়া ব্যর্থ: C++11 থেকে age-এ বসে 0, আর stream চলে যায় fail অবস্থায়। এরপর >> cgpa কিছুই করে না — fail অবস্থার stream পরের সব পড়া চুপচাপ বাদ দেয়। “abc” buffer-এই আটকে থাকে।

একটি মজার ফল লক্ষ করুন: whitespace বাদ দেওয়ার নিয়মে নতুন লাইনও পড়ে। তাই Rafi 21 3.75 এক লাইনে লিখুন, বা তিন লাইনে, বা মাঝে দশটা স্পেস দিন — ফল একই। অনলাইন জাজের ইনপুট এ কারণেই লাইন ভেঙে বা এক লাইনে দেওয়া হোক, >> দিয়ে পড়লে সাধারণত সমস্যা হয় না।

int-এ “3.75” পড়তে দিলে কী হয়? >> “3” পড়ে বিন্দুতে থামে — এটা ব্যর্থতা নয়, সফলভাবে 3 পড়া হয়েছে! “.75” buffer-এ রয়ে যায়, আর পরের int পড়া সেখানে গিয়ে ব্যর্থ হয়। ভুলটা যেখানে ঘটে, ধরা পড়ে তার এক ধাপ পরে — বাগ খুঁজতে গেলে এটা মনে রাখবেন।

৩ · ভুল ইনপুট — stream-এর অবস্থা, clear আর ignore

ব্যবহারকারী ভুল করবেনই — বয়সের জায়গায় নাম লিখবেন, নম্বরের জায়গায় “N/A”. প্রশ্ন হলো, প্রোগ্রাম কীভাবে জানবে? উত্তর: প্রতিটি stream নিজের ভেতরে কয়েকটি flag রাখে, যাদের একসাথে বলে stream state.

Flagকখন ওঠেযাচাইয়ের ফাংশন
goodbitসব ঠিক আছে — কোনো flag ওঠেনিstd::cin.good()
failbitচাওয়া টাইপের মান পাওয়া যায়নি (যেমন int-এ “abc”), অথবা কিছু পড়ার আগেই ইনপুট শেষstd::cin.fail()
eofbitপড়তে গিয়ে ইনপুটের শেষে (EOF) পৌঁছেছেstd::cin.eof()
badbitগুরুতর সমস্যা — stream নিজেই ভেঙে গেছে (বিরল, যেমন হার্ডওয়্যার বা মেমরির ত্রুটি)std::cin.bad()

সবচেয়ে জরুরি নিয়ম: একবার failbit উঠলে stream “অভিমান করে” — পরের সব পড়া কিছু না করেই ফিরে আসে, যতক্ষণ না আপনি নিজে অবস্থা ঠিক করেন। আর ঠিক করতে লাগে দুটি আলাদা কাজ, কারণ সমস্যা দুটি:

🔄 std::cin.clear()

Flag নামিয়ে stream-কে আবার “good” করে। কিন্তু buffer-এ পড়ে থাকা “abc” সরায় না! শুধু clear() দিয়ে আবার পড়লে আবার সেই “abc”, আবার fail — চিরকালের লুপ।

অবস্থা ঠিক করে

🗑️ std::cin.ignore(n, '\n')

Buffer থেকে nটি পর্যন্ত অক্ষর, অথবা '\n' পাওয়া পর্যন্ত সব অক্ষর পড়ে ফেলে দেয়। “পুরো লাইন বাদ” বলতে n হিসেবে দেওয়া হয় সর্বোচ্চ সম্ভাব্য সংখ্যা: std::numeric_limits<std::streamsize>::max().

আবর্জনা সরায়

while (std::cin >> x) — কেন এটা কাজ করে

std::cin >> x নিজেই একটি মান — সেই stream. আর stream-কে শর্ত হিসেবে লেখা যায়: শর্তের জায়গায় stream মানে “শেষ কাজটি সফল হয়েছে?” — অর্থাৎ !fail(). তাই while (std::cin >> x) পড়তে হয় এভাবে: “একটি x পড়ার চেষ্টা করো; পারলে লুপের ভেতরে যাও, না পারলে থামো।” ইনপুট শেষ হলে বা ভুল ইনপুট এলে — দুই ক্ষেত্রেই লুপ থামে। কতগুলো সংখ্যা আসবে না জেনেও সব পড়ার এটাই C++-এর স্বাভাবিক রীতি। (while আর if-এর পূর্ণ আলোচনা পরের পর্বে, পর্ব ০৮-এ।)

কিন্তু শুধু থামলেই চলে না, কখনো ভুল লাইনটি বাদ দিয়ে এগোতে হয়। নিচের প্রোগ্রাম সংখ্যা যোগ করে; ভুল লাইন পেলে std::cerr-এ জানায়, লাইনটি বাদ দেয়, আর EOF পর্যন্ত চলতে থাকে। ইনপুট আগে থেকে দেওয়া আছে: 10 20, abc 99, 12.5, 30 — চারটি লাইন। চালানোর আগে অনুমান করুন count আর sum কত হবে:

#include <iostream>
#include <limits>

int main() {
    int x{}, sum{}, count{};
    while (true) {
        if (std::cin >> x) {                  // ekta int pawa gelo
            sum += x;
            ++count;
        } else if (std::cin.eof()) {          // input shesh -- thamo
            break;
        } else {                              // "abc" -- fail state
            std::cerr << "bad input, line skipped\n";
            std::cin.clear();                 // state abar good
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
    }
    std::cout << "count = " << count << ", sum = " << sum << '\n';
}

ফল count = 4, sum = 72 — অবাক হলেন? ধাপে ধাপে: 10 আর 20 পড়া হলো। “abc”-তে fail; clear() অবস্থা ঠিক করল, ignore পুরো লাইন ফেলে দিল — সাথে 99-ও, কারণ সেটা একই লাইনে ছিল। তারপর “12.5”: int হিসেবে 12 সফলভাবে পড়া হলো, “.5”-এ গিয়ে পরের পড়া fail, আর সেই লাইনের বাকিটা বাদ। শেষে 30. তারপর ইনপুট শেষ — eof() সত্য, লুপ থামল। মোট 10 + 20 + 12 + 30 = 72. দুটি “bad input” বার্তা আউটপুটের নিচে আলাদা দেখাবে, কারণ সেগুলো গেছে std::cerr-এ — অনলাইন রানার stdout আর stderr আলাদা অংশে দেখায়। এখানেই শিক্ষা: “ভুল লাইন বাদ” নীতি সহজ, কিন্তু আপনার প্রোগ্রামের জন্য সেটা ঠিক নীতি কি না, আপনাকেই ভাবতে হবে।

সংখ্যা সীমার বাইরে হলে? int-এ 99999999999 পড়তে দিলে C++11 থেকে stream ভ্যারিয়েবলে বসায় int-এর সর্বোচ্চ মান (2147483647), আর failbit তোলে। অর্থাৎ ভুল নিঃশব্দে হারিয়ে যায় না — যদি আপনি অবস্থা যাচাই করেন। যাচাই না করলে প্রোগ্রাম 2147483647 নিয়ে দিব্যি হিসাব চালিয়ে যাবে।

৪ · পুরো লাইন পড়া — std::getline আর হারানো Enter

নাম, ঠিকানা, পণ্যের বিবরণ — বাস্তবের অনেক লেখায় স্পেস থাকে। >> স্পেসে থামে, তাই “Rafiul Islam” দুই টুকরো হয়ে যায়। পুরো লাইন পড়ার জন্য আছে std::getline(std::cin, line) (header <string>): এটি \n পর্যন্ত সব অক্ষর — স্পেসসহ — line-এ রাখে, আর শেষের \n-টি buffer থেকে খেয়ে ফেলে, কিন্তু line-এ রাখে না।

getline নিজে সরল। সমস্যা হয় যখন এর আগে >> চলে। প্রথমে রোল নম্বর, তারপর পুরো নাম চাইছেন — ইনপুট 42, Enter, Rafiul Islam, Enter. ধাপে ধাপে দেখুন কী হয়:

1std::cin >> roll;2std::getline(std::cin, name);              // ভুল3std::getline(std::cin >> std::ws, name);  // ঠিক
buffer আর ভ্যারিয়েবল
buffer
roll
name
  1. ইনপুট এসেছে। ব্যবহারকারী দুই লাইন লিখেছেন। buffer-এ এখন "42\nRafiul Islam\n" — দুটি Enter-ই সেখানে অক্ষর হিসেবে আছে।
  2. cin >> roll. অঙ্ক পড়ে 42 পেল, \n দেখে থামল। নিয়ম মনে আছে? থামার অক্ষরটি buffer-এই থাকে। তাই buffer-এর একেবারে সামনে এখন একটি \n.
  3. getline — বাগ! getline-এর নিয়ম: \n পর্যন্ত পড়ো। আর buffer-এর প্রথম অক্ষরটিই \n! তাই সে শূন্য অক্ষর পড়ল, \n-টি খেল, আর ফিরে গেল। name খালি, অথচ ব্যবহারকারীর মনে হলো প্রোগ্রাম নাম চাওয়াই বাদ দিয়েছে। “Rafiul Islam” এখনো buffer-এ বসে আছে।
  4. সঠিক সংস্করণ — আবার শুরু থেকে। এবার ধরুন লাইন ২-এর বদলে লাইন ৩ চলবে। cin >> roll-এর পরে আগের মতোই buffer-এর সামনে \n. লাইন ৩-এ আগে চলে std::cin >> std::ws — std::ws একটি manipulator, যা সামনের সব whitespace খেয়ে ফেলে।
  5. এবার getline ঠিক কাজ করল। std::ws রয়ে যাওয়া \n সরিয়েছে, তাই getline পেল “Rafiul Islam”, শেষের \n খেল, buffer খালি। std::cin >> std::ws নিজেও stream ফেরত দেয় বলে এটি সরাসরি getline-এর ভেতরে লেখা যায়।

তিনটি সমাধান, তিনটি ভিন্ন অর্থ

লেখাযা করেসতর্কতা
std::getline(std::cin >> std::ws, s)সামনের সব whitespace বাদ, তারপর লাইনইচ্ছাকৃত খালি লাইন বা লাইনের শুরুর স্পেসও বাদ যায়
std::cin.ignore(max, '\n') তারপর getlineআগের লাইনের বাকি অংশ (সাথে \n) পুরোটা ফেলে দেয়“42 extra” লিখলে “extra” নিঃশব্দে হারায়
সবকিছু getline দিয়ে পড়া, পরে সংখ্যায় রূপান্তরপ্রতিটি লাইন পুরো পড়ে, তারপর নিজে যাচাইএকটু বেশি কোড, কিন্তু সবচেয়ে নিয়ন্ত্রিত — বড় প্রোগ্রামে পেশাদার পথ

তৃতীয় পথে লাইন থেকে সংখ্যা বের করতে লাগে std::stoi বা std::from_chars-এর মতো ফাংশন; string নিয়ে কাজ শিখবেন পর্ব ১৪-এ। getline তৃতীয় একটি আর্গুমেন্টও নেয় — থামার অক্ষর। std::getline(std::cin, field, ',') কমা পর্যন্ত পড়ে, যা CSV ফাইলের মতো কমা-বিভক্ত ডেটায় কাজে লাগে।

৫ · cout সাজানো — <iomanip>-এর manipulator

এবার উল্টো দিক। একটি মার্কশিট ছাপাতে চান, যেখানে বিষয়ের নাম বাঁয়ে, নম্বর ডানে, আর গড় দশমিকের পর ঠিক দুই ঘর। std::cout << নিজে থেকে প্রস্থ বা দশমিক ঘর জানে না; এগুলো বলে দিতে হয় manipulator দিয়ে — এমন জিনিস, যা stream-এ পাঠালে কিছু ছাপে না, বরং stream-এর আচরণ বদলায়। বেশিরভাগ থাকে header <iomanip>-এ।

#include <iomanip>
// ...
std::cout << std::left  << std::setw(10) << "Subject"
          << std::right << std::setw(8)  << "Marks" << '\n';
std::cout << std::left  << std::setw(10) << "Bangla"
          << std::right << std::setw(8)  << 78 << '\n';
std::cout << std::fixed << std::setprecision(2);
std::cout << std::left  << std::setw(10) << "Average"
          << std::right << std::setw(8)  << 84.6667 << '\n';
Subject      Marks
Bangla          78
Average      84.67
Manipulatorকাজকতক্ষণ থাকে
std::setw(n)পরের আউটপুটের ন্যূনতম প্রস্থ n; কম হলে ফাঁকা দিয়ে ভরে, বেশি হলে কাটে নাশুধু পরের একটি আউটপুট
std::left / std::rightপ্রস্থের ভেতরে বাঁয়ে না ডানে বসবে (ডিফল্ট ডানে)বদলানো পর্যন্ত
std::setfill(c)ফাঁকার বদলে অক্ষর c দিয়ে ভরা, যেমন '.' বা '0'বদলানো পর্যন্ত
std::fixedদশমিক সংখ্যা সবসময় সাধারণ দশমিক রূপে, ঘর-সংখ্যা precision অনুযায়ীবদলানো পর্যন্ত
std::setprecision(n)fixed-সহ: দশমিকের পর n ঘর; একা: মোট nটি significant অঙ্কবদলানো পর্যন্ত
std::boolalphabool-কে 1/0-এর বদলে true/false লেখাবদলানো পর্যন্ত

দুটি চিরচেনা ফাঁদ। (১) setw একবার ব্যবহারেই মুছে যায়: std::cout << std::setw(5) << 1 << 2; ছাপে " 12" — 2-এর জন্য কোনো প্রস্থ নেই। প্রতিটি ঘরের আগে আবার setw লিখতে হয়। (২) বাকিগুলো stream-এ থেকে যায়। একটি ফাংশন std::fixed << std::setprecision(2) করে দিলে প্রোগ্রামের অন্য প্রান্তের সব দশমিকও দুই ঘরে ছাপা হবে — যে লিখেছে সে হয়তো জানেই না। আর fixed ছাড়া setprecision(3) দিলে 84.6667 ছাপে 84.7 — তিনটি significant অঙ্ক, দশমিকের পরের তিন ঘর নয়।

ডিফল্টে std::cout দশমিক সংখ্যা মোট ৬টি significant অঙ্কে দেখায়: 254 / 3.0 ছাপে 84.6667, আর 1234567.0 ছাপে 1.23457e+06! রসিদ বা মার্কশিটে তাই std::fixed প্রায় সবসময় লাগে।

৬ · আধুনিক পথ — std::format আর std::println

উপরের কোড কাজ করে, কিন্তু দেখুন কতটা জট: একটি লাইনের আকার বোঝাতে চারটি manipulator, আর stream-এর লুকানো “মনে রাখা” অবস্থা। পর্ব ০৪-এ আপনি std::println-এর {} দেখেছেন। সেই {}-এর ভেতরে একটি : দিয়ে লেখা যায় পুরো চেহারার নির্দেশ — একে বলে format specification. উপরের পুরো মার্কশিট-লাইন হয়ে যায় এক লাইনে:

std::println("{:<10}{:>8.2f}", "Average", 84.6667);   // Average      84.67

std::format (C++20, header <format>) একই নিয়মে লেখা বানিয়ে একটি std::string ফেরত দেয় — ছাপে না; আর std::print/std::println (C++23, header <print>) সরাসরি ছাপে। সবচেয়ে বড় সুবিধা: কোনো অবস্থা মনে থাকে না। প্রতিটি placeholder-এর নির্দেশ শুধু সেই ঘরের জন্য। এবার একটি format spec টুকরো টুকরো করে পড়ি — মান 3.14159:

{ : * > + 10 .2 f }
  1. { } — ফাঁকা ঘর। শুধু {} হলে মানটি তার “স্বাভাবিক” রূপে বসে। double-এর জন্য স্বাভাবিক রূপ হলো সবচেয়ে ছোট লেখা, যা থেকে হুবহু একই মান ফিরে পাওয়া যায় — তাই 3.14159. (cout-এর মতো ৬ অঙ্কের নিয়ম এখানে নেই।) বন্ধনী [ ] শুধু ফলের সীমা দেখাতে; আসল লেখায় নেই।
  2. : — নির্দেশ শুরু। কোলনের আগে বসতে পারে আর্গুমেন্টের নম্বর ({0:}, {1:}), পরে বসে চেহারার নির্দেশ। শুধু কোলন দিলে নির্দেশ খালি — ফল বদলায় না।
  3. *> — fill আর align. > মানে ডানে বসাও, < বাঁয়ে, ^ মাঝে। তার আগের * হলো fill — ফাঁকা জায়গা কোন অক্ষরে ভরবে (না দিলে স্পেস)। কিন্তু প্রস্থ এখনো বলা হয়নি, তাই ভরার মতো ফাঁকাই নেই — ফল একই। fill একা লেখা যায় না, সবসময় align-এর ঠিক আগে বসে।
  4. + — চিহ্ন। ধনাত্মক সংখ্যার আগেও + দেখাও। (ডিফল্ট -: শুধু ঋণাত্মকে চিহ্ন। আর একটি স্পেস দিলে ধনাত্মকের আগে স্পেস — কলামে চিহ্ন মেলাতে।) এখন লেখা +3.14159, ৮ অক্ষর।
  5. 10 — প্রস্থ। মোট অন্তত ১০ ঘর। লেখা ৮ অক্ষরের, তাই ২ ঘর ফাঁকা — * দিয়ে ভরা, বাঁ দিকে, কারণ লেখা ডানে বসানো। setw-এর মতোই প্রস্থ ন্যূনতম; বড় লেখা কাটা হয় না।
  6. .2 — precision, কিন্তু type ছাড়া। ফল 3.1! কারণ type না দিলে double-এ precision মানে মোট significant অঙ্ক — ঠিক fixed-ছাড়া setprecision-এর মতো। অনেকেই এখানে ভুল করেন।
  7. f — type: fixed দশমিক। এবার .2 মানে দশমিকের পর ঠিক দুই ঘর: +3.14 (৫ অক্ষর), বাকি ৫ ঘর তারা — *****+3.14. অন্য type: e (বৈজ্ঞানিক রূপ), d/x/b (পূর্ণসংখ্যা দশমিক/hex/বাইনারি)। পুরো ব্যাকরণের ক্রম: [fill]align · sign · # · 0 · width · .precision · type.
  8. শূন্য দিয়ে ভরা — {:08.3f}. প্রস্থের আগে 0 মানে ফাঁকা জায়গা শূন্যে ভরো, তবে চিহ্নের পরে: 3.14159 → 0003.142, −3.14159 → -003.142. ৩ ঘরে round হয়ে .142. রসিদ নম্বর, সময় ({:02}:{:02} → 09:05) লিখতে এটি খুব কাজের।

এবার পুরো একটি মার্কশিট — নাম আর তিন বিষয়ের নম্বর ইনপুট থেকে পড়ে। ইনপুট দেওয়া আছে Rafi 78 85 91:

#include <format>
#include <iostream>
#include <print>
#include <string>

int main() {
    std::string name;
    int bn{}, en{}, math{};
    std::cin >> name >> bn >> en >> math;
    int total = bn + en + math;
    double avg = total / 3.0;
    std::string bar = std::format("+{:-<20}+", "");    // 20 ta '-'
    std::println("{}\n|{:^20}|\n{}", bar, "MARKSHEET: " + name, bar);
    std::println("| {:<10}{:>8} |", "Bangla", bn);
    std::println("| {:<10}{:>8} |", "English", en);
    std::println("| {:<10}{:>8} |", "Math", math);
    std::println("{}", bar);
    std::println("| {:<10}{:>8} |", "Total", total);
    std::println("| {:<10}{:>8.2f} |", "Average", avg);
    std::println("{}", bar);
}

bar বানানো হয়েছে std::format দিয়ে: খালি লেখা ""-কে ২০ ঘর প্রস্থে বাঁয়ে বসিয়ে বাকিটা - দিয়ে ভরা — ফলে ২০টি ড্যাশ। এটি একটি std::string, তাই বারবার ব্যবহার করা গেল। শিরোনাম {:^20} দিয়ে মাঝখানে। প্রতিটি সারিতে বিষয় {:<10} (বাঁয়ে) আর নম্বর {:>8} (ডানে) — তাই কলাম নিখুঁত মেলে, কোনো sticky অবস্থা নেই। গড় 254 / 3.0 = 84.666…, {:>8.2f} দেখায় 84.67, কিন্তু avg-এর মান বদলায়নি — formatting শুধু উপস্থাপন। একটি সূক্ষ্ম কথা: std::format-এ ডিফল্টে লেখা বাঁয়ে আর সংখ্যা ডানে বসে, তাই এখানে < আর > না দিলেও ফল একই হতো — তবু স্পষ্ট করে লেখা ভালো অভ্যাস।

Format string ভুল হলে কী হয়?

std::println("{} {}", 5); — ঘর দুটি, মান একটি। C-এর printf("%d %d", 5)-এ এমন ভুল undefined behavior: চলার সময় আবর্জনা সংখ্যা ছাপে বা ক্র্যাশ করে। std::format-এ format string যদি কোডে সরাসরি লেখা থাকে, compiler compile-এর সময়েই তা যাচাই করে, আর error দেয়। {:d} দিয়ে লেখা (string) ছাপাতে চাইলেও একই — compile error. এই যাচাই কীভাবে compile-এর সময় হয়, তার কৌশল (consteval) দেখবেন পর্ব ৩৯-এ। ব্যবহারকারীর দেওয়া format string চালাতে চাইলে আলাদা ফাংশন std::vformat লাগে, যা ভুল হলে চলার সময় exception ছোড়ে।

C জানলে

stdin, stdout, stderr আর buffer-এর ধারণা C-এর মতোই; std::cin >> x হলো scanf-এর টাইপ-নিরাপদ রূপ, যেখানে & লাগে না (ভেতরে reference — পর্ব ১০)। Format spec-ও প্রায় চেনা: %8.2f হয় {:8.2f}, %-10s হয় {:<10}, %05d হয় {:05}. একটি পার্থক্য মনে রাখুন: printf("%8s") লেখাকে ডানে বসায়, কিন্তু std::format("{:8}") লেখাকে বাঁয়ে বসায়। C-এর বিস্তারিত — buffer, redirection, scanf-এর ফাঁদ — পাবেন C কোর্সের printf আর scanf পর্বে।

৭ · কোনটা কখন — cout, format/print, printf আর cerr

এখন আপনার হাতে চারটি হাতিয়ার। বাস্তব কোডে সবগুলোই দেখবেন, তাই বেছে নেওয়ার যুক্তিটা জানা দরকার:

হাতিয়ারশক্তিদুর্বলতাকখন
std::cout <<সব compiler-এ চলে; নিজের টাইপের জন্য << বানানো যায় (পর্ব ২৩); ফাইল আর string stream-এর একই নিয়মformatting-এ লম্বা ও sticky; বাক্য টুকরো হয়ে যায়পুরোনো কোড, পুরোনো compiler, সরল আউটপুট
std::format / std::printlnটাইপ-নিরাপদ, compile-এর সময় যাচাই, পুরো বাক্য এক জায়গায়, অবস্থাহীন<print>-এর জন্য C++23 compiler লাগে (GCC 14 বা নতুন)নতুন কোডে, যেখানে compiler সমর্থন করে
printfদ্রুত, C-এর সাথে মেলে, সবাই চেনেটাইপ যাচাই নেই — ভুল % মানে undefined behavior; std::string সরাসরি ছাপে নাC লাইব্রেরির সাথে কাজে; নতুন C++ কোডে এড়িয়ে চলুন
std::cerrbuffer নেই, তাই ক্র্যাশের আগেও লেখা পৌঁছায়; stdout থেকে আলাদা পথপ্রতি লেখায় সাথে সাথে পাঠানো, তাই বেশি লিখলে ধীরerror আর সতর্কবার্তা — ফলাফল নয়

std::cerr কেন আলাদা, একটি দৃশ্যে দেখুন। আপনার প্রোগ্রাম ফলাফল ছাপে, আর ভুল ইনপুটে সতর্কবার্তা দেয়। কেউ চালালেন ./safe_sum < data.txt > result.txt. cout-এর সব লেখা গেল result.txt-এ — পরিষ্কার ডেটা। কিন্তু cerr-এর সতর্কবার্তা ফাইলে মিশল না, সরাসরি স্ক্রিনে এলো, তাই ব্যবহারকারী সাথে সাথে দেখলেন। আর চাইলে 2> errors.txt দিয়ে সেগুলো আলাদা ফাইলেও পাঠানো যায়। অনলাইন জাজ শুধু stdout মেলায়, তাই কনটেস্টে debug-এর লেখা cerr-এ দিলে সাধারণত উত্তর নষ্ট হয় না — তবে সময় খায়, জমা দেওয়ার আগে সরিয়ে ফেলুন।

C++-এর stream লাইব্রেরি শুরু করেন Bjarne Stroustrup নিজেই, ১৯৮০-র দশকের মাঝামাঝি; পরে Jerry Schwarz একে নতুন করে নকশা করেন, যা iostream নামে পরিচিতি পায় আর ১৯৯৮ সালের প্রথম C++ standard-এ জায়গা নেয়। লক্ষ্য ছিল printf-এর টাইপ-ঝুঁকি দূর করা আর নিজের টাইপও ছাপানোর সুযোগ দেওয়া। প্রায় তিন দশক পরে C++20-এ std::format এসে formatting-এর সুবিধা ফিরিয়ে আনল — টাইপ-নিরাপত্তা না হারিয়ে।

৮ · গতি — sync_with_stdio আর cin.tie

কনটেস্টের সমাধানে প্রায় সবসময় main-এর প্রথম দুই লাইন এমন দেখবেন:

std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);

অনেকে মুখস্থ লেখেন, কারণ জানেন না। অথচ কারণটা জানলে কখন এটা বিপজ্জনক, সেটাও বোঝা যায়। একটি অনলাইন জাজে দশ লাখ সংখ্যার ইনপুট কীভাবে আপনার প্রোগ্রামে ঢোকে আর বের হয়, ধাপে ধাপে দেখুন:

  1. জাজের ফাইলinput.txtগোপন টেস্ট
  2. ইনপুট bufferstd::cinকতটা একবারে আসে
  3. আপনার কোডsolve()হিসাব
  4. আউটপুট bufferstd::coutকখন পাঠানো হয়
  5. জাজ মেলায়Accepted?সঠিক উত্তরের সাথে
জাজ যা চালায় (মোটামুটি)redirection
./solution < input.txt > output.txt
// আপনার প্রোগ্রাম জানেই না ইনপুট ফাইল থেকে আসছে
ডিফল্ট: sync চালুপ্রতিটি অক্ষরে C stdio-র মাধ্যমে
std::cin >> x   →  C-এর stdio-র সাথে তাল মিলিয়ে, অক্ষরে অক্ষরে
যাতে printf/scanf আর cout/cin মেশালেও ক্রম ঠিক থাকে
sync_with_stdio(false)  →  cin পায় নিজের বড় buffer
একবারে বড় টুকরো পড়ে, প্রতি অক্ষরে বাড়তি ধাপ নেই
যে ধরনের সমাধান সবচেয়ে ভোগেপড়া-লেখা পালা করে
for (int i = 0; i < q; ++i) {      // q = 10 লাখ প্রশ্ন
    std::cin >> a >> b;
    std::cout << a + b << '\n';
}
ডিফল্ট: cin বাঁধা cout-এর সাথেপ্রতিবার পড়ার আগে flush
cin >> a   →  আগে cout.flush()  →  অপারেটিং সিস্টেমকে ডাকা
১০ লাখ প্রশ্ন = প্রায় ১০ লাখ বার ছোট ছোট লেখা পাঠানো
cin.tie(nullptr)  →  বাঁধন খোলা, cout জমিয়ে বড় টুকরোয় পাঠায়
শেষেbuffer খালি হয় প্রোগ্রাম শেষে
main() শেষ  →  cout-এর বাকি লেখা flush  →  output.txt
জাজ মেলায় expected.txt-এর সাথে → Accepted / Wrong Answer
লুপে std::endl দিলে: প্রতি লাইনে flush — tie খোলার লাভই নষ্ট
  1. জাজের দিক। জাজ আপনার প্রোগ্রাম চালায় গোপন ইনপুট ফাইলকে stdin-এ জুড়ে দিয়ে, আর stdout ধরে রাখে একটি ফাইলে। আপনার কোডের কাছে এটা কিবোর্ডের মতোই — শুধু অনেক দ্রুত আর অনেক বড়।
  2. প্রথম ধীরতা: C-এর সাথে তাল মেলানো। ডিফল্টে C++ stream আর C-এর printf/scanf একই ক্রম মেনে চলার প্রতিশ্রুতি দেয়, যাতে দুটো মেশালেও লেখা উল্টাপাল্টা না হয়। GCC-র লাইব্রেরিতে এর জন্য cin/cout প্রতিটি অক্ষর C-এর stdio দিয়ে পাঠায় — নিজের buffer ব্যবহার করে না। sync_with_stdio(false) এই প্রতিশ্রুতি তুলে নেয়; stream নিজের buffer পায় আর বড় টুকরোয় পড়ে।
  3. আপনার কোড। কনটেস্টের অনেক সমস্যায় পড়া আর লেখা পালা করে চলে — একটি প্রশ্ন পড়ো, উত্তর লেখো, আবার পড়ো। এই ধরনের কোডেই দ্বিতীয় ধীরতাটি সবচেয়ে বেশি লাগে।
  4. দ্বিতীয় ধীরতা: tie. ডিফল্টে cin “বাঁধা” থাকে cout-এর সাথে: প্রতিবার পড়ার আগে cout flush হয়, যাতে প্রশ্ন (“Enter roll:”) ইনপুট চাওয়ার আগেই স্ক্রিনে আসে। মানুষের জন্য দরকারি, জাজের জন্য অর্থহীন — আর প্রতি flush মানে অপারেটিং সিস্টেমকে একবার ডাকা। cin.tie(nullptr) বাঁধনটি খুলে দেয়।
  5. শেষে মেলানো। প্রোগ্রাম শেষ হলে cout-এর বাকি লেখা নিজে থেকেই flush হয়, জাজ উত্তর মেলায়। কিন্তু লুপের ভেতরে std::endl লিখলে প্রতি লাইনে আবার flush — দুই লাইনের পুরো লাভ নষ্ট। কনটেস্টে তাই সবসময় '\n'. বিস্তারিত কৌশল আর মাপজোখ পর্ব ৫০-এ।

দুই লাইনের দাম। (১) sync_with_stdio(false)-এর পরে একই প্রোগ্রামে printf/scanf আর cin/cout মেশাবেন না — দুটির buffer এখন আলাদা, লেখা উল্টো ক্রমে আসতে পারে। খেয়াল করুন, std::println("...") ডিফল্টে লেখে C-এর stdout-এ, তাই এটাও cout-এর সাথে মেশানো ঝুঁকিপূর্ণ। (২) এই লাইন কোনো I/O করার আগে লিখতে হয়। (৩) মানুষের সাথে কথা বলা প্রোগ্রামে tie খুললে প্রশ্ন দেরিতে দেখা দিতে পারে — তখন প্রশ্নের পরে নিজে std::cout.flush() করুন, বা tie খুলবেন না।

৯ · বাংলা লেখা আর console — সাবধানে

C++-এর std::string লেখাকে শুধু byte-এর সারি হিসেবে রাখে; সেই byte-এর অর্থ কী, জানে না। বাংলা লেখা সাধারণত UTF-8-এ থাকে, যেখানে প্রতিটি বাংলা অক্ষর ৩ byte. তাই আপনার ফাইল UTF-8 হলে std::cout << "আমার সোনার বাংলা"; byte-গুলো ঠিকঠাক stdout-এ পাঠিয়ে দেয়। এরপর সেগুলো ঠিক দেখাবে কি না, তা নির্ভর করে terminal-এর ওপর — C++-এর ওপর নয়।

  • Linux আর macOS: আধুনিক terminal সাধারণত UTF-8-এ চলে, তাই বাংলা অক্ষর আসে; যুক্তাক্ষর ঠিক জোড়া লাগবে কি না, তা terminal-এর font আর লেখা সাজানোর ক্ষমতার ওপর নির্ভর করে।
  • Windows: পুরোনো console প্রায়ই অন্য একটি code page-এ চলে, তাই UTF-8 byte ভাঙা চিহ্ন হয়ে দেখায়। chcp 65001 কমান্ড console-কে UTF-8-এ নেয়; Windows Terminal সাধারণত ভালো দেখায়। C++23-এর std::print-এর নিয়মে আছে যে Unicode সমর্থন করে এমন terminal-এ লেখা ঠিকভাবে পৌঁছানোর চেষ্টা করবে, তবে compiler আর লাইব্রেরি ভেদে বাস্তবায়ন আলাদা — নিজে চালিয়ে যাচাই করুন।
  • ইনপুটে বাংলা আরও ঝামেলার — Windows console থেকে বাংলা পড়া অনেক সেটআপে ঠিকমতো কাজ করে না। শেখার সময় বাংলা ইনপুট ফাইল থেকে পড়ুন (পর্ব ৪৪)।

প্রস্থ মেলে না। std::setw(10) গোনে byte, অক্ষর নয়। “রহিম” চোখে ৩টি অক্ষর (র, হি, ম), Unicode-এ ৪টি code point (ি-কার আলাদা), কিন্তু UTF-8-এ ১২ byte — তাই setw(10) কোনো ফাঁকাই যোগ করবে না, কলাম ভেঙে যাবে। std::format লেখার “আনুমানিক প্রদর্শন-প্রস্থ” হিসাব করার চেষ্টা করে, কিন্তু বাংলার কার-চিহ্ন আর যুক্তাক্ষর terminal-এ কত জায়গা নেবে, তা সবসময় মেলে না। বাংলা টেবিল নিখুঁত সাজাতে চাইলে HTML বা GUI ভালো পথ। এজন্যই এই কোর্সের চালানো যায় এমন কোডে আউটপুট ইংরেজি বা Banglish — অনলাইন compiler-এ বাংলা অক্ষর প্রায়ই ভেঙে যায়।

পরীক্ষা ও ইন্টারভিউতে
  • std::endl আর '\n'-এর পার্থক্য? — দুটোই নতুন লাইন দেয়; endl সাথে flush-ও করে, তাই লুপে ধীর।
  • cin >> n;-এর পরে getline খালি লাইন দেয় কেন? — >> Enter-এর \n buffer-এ রেখে যায়, getline সেটাকেই লাইনের শেষ ধরে। সমাধান std::ws বা ignore.
  • while (std::cin >> x) কখন থামে? — পড়া ব্যর্থ হলে: ইনপুট শেষ (EOF) বা ভুল টাইপের ইনপুট এলে।
  • cin আর scanf-এর মধ্যে কোনটি টাইপ-নিরাপদ, কেন? — cin; টাইপ দেখে compile-এর সময় ঠিক >> বেছে নেওয়া হয়, format string মেলানোর ঝুঁকি নেই।
  • sync_with_stdio(false) কী করে? — C++ stream-কে C stdio-র সাথে তাল মেলানো থেকে মুক্ত করে নিজের buffer দেয়; দ্রুত হয়, কিন্তু তখন printf আর cout মেশানো যায় না।

১০ · AI-এর যুগে — I/O কোডে AI-এর চেনা ভুল

“ইনপুট নিয়ে ফলাফল দেখাও” — AI-এর কাছে এর চেয়ে সহজ কাজ কম। আর ঠিক সে কারণেই এখানে AI অসতর্ক হয়: কোড compile হয়, সুন্দর ইনপুটে নিখুঁত চলে, শুধু বাস্তব ব্যবহারকারী বা অনলাইন জাজের সামনে গিয়ে ভাঙে। AI-এর লেখা C++ I/O কোডে বারবার যা দেখা যায়:

AI যা করেসমস্যাযা হওয়া উচিত
std::cin >> n; — যাচাই নেই“abc” এলে n = 0, stream fail, পরের সব পড়া চুপচাপ বাদ; ভুল ফল বা চিরকালের লুপif (!(std::cin >> n)) দিয়ে ব্যর্থতা সামলানো
sync_with_stdio(false)-এর পরে printf আর cout মেশানোলেখা উল্টো ক্রমে আসতে পারে — Wrong Answerএকটি পরিবার বেছে নেওয়া, পুরো প্রোগ্রামে সেটাই
দশ লাখ বারের লুপে std::endlপ্রতি লাইনে flush; বড় আউটপুটে Time Limit Exceeded হতে পারে'\n'; দরকার হলে শেষে একবার flush
cin >>-এর পরে সরাসরি getlineখালি লাইন পড়ে — “নাম চাওয়াই হলো না”std::getline(std::cin >> std::ws, s)
while (!std::cin.eof()) লুপশেষ মানটি দুবার গোনা হয়, বা ভুল ইনপুটে লুপ থামেই নাwhile (std::cin >> x)
error মেসেজ cout-এফলাফলের ফাইলে আবর্জনা মেশে; জাজে Wrong Answerstd::cerr

AI-কে I/O কোড লিখতে বলার সময় ইনপুটের চেহারা আর ভুল ইনপুটের নীতি আগেই বলে দিন — না বললে AI ধরে নেয় ব্যবহারকারী কখনো ভুল করেন না:

আমি C++23 শিখছি (g++ -std=c++23 -Wall -Wextra). একটি প্রোগ্রাম লেখো, যা প্রথম লাইনে ছাত্রসংখ্যা n, তারপর প্রতিটি লাইনে "নাম নম্বর" পড়বে (নামে স্পেস থাকতে পারে, নম্বর শেষে), আর একটি সাজানো মার্কশিট ছাপবে। নিয়ম: ১. প্রতিটি পড়ার পর stream-এর অবস্থা যাচাই করবে; ভুল লাইন হলে std::cerr-এ লাইন নম্বরসহ জানাবে আর সেটি বাদ দেবে। ২. cin >> আর getline মেশালে রয়ে যাওয়া newline কীভাবে সামলাচ্ছ, comment-এ লিখবে। ৩. আউটপুট শুধু std::println আর format spec দিয়ে (যেমন {:<20}{:>6.2f}); printf বা iomanip নয়। ৪. লুপে std::endl ব্যবহার করবে না। ৫. ইনপুট আগেভাগে শেষ হয়ে গেলে (EOF) কী হবে, তা-ও সামলাবে। কোডের পরে তিনটি ভুল ইনপুটের উদাহরণ দাও, আর প্রতিটিতে প্রোগ্রাম ঠিক কী করবে বলো।

AI-এর I/O কোড যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১printf কেন টাইপ-নিরাপদ নয়, আর iostream কীভাবে টাইপ-নিরাপদ হলো? এই নিরাপত্তার দাম কী ছিল?

printf-এর নকশা ১৯৭০-এর দশকের C-এর। এটি একটি variadic ফাংশন — যত খুশি আর্গুমেন্ট নেয়, কিন্তু ফাংশনটি নিজে জানে না কতগুলো এসেছে বা কোনটির টাইপ কী। আর্গুমেন্টগুলো শুধু মেমরি বা রেজিস্টারে পরপর সাজানো থাকে। কোনটি কী, তা printf জানে শুধু format string পড়ে: %d দেখলে ধরে নেয় একটি int আছে, %s দেখলে ধরে নেয় একটি অক্ষরের ঠিকানা আছে। আপনি ভুল করে %s-এর জায়গায় একটি সংখ্যা দিলে printf সেই সংখ্যাকে ঠিকানা ভেবে মেমরির যেকোনো জায়গা পড়তে যায় — ক্র্যাশ, আবর্জনা বা নিরাপত্তা-ছিদ্র। C standard একে undefined behavior বলে।

এর একটি বিখ্যাত রূপ “format string attack”: ব্যবহারকারীর দেওয়া লেখা সরাসরি printf(user_text)-এ দিলে আক্রমণকারী সেখানে %x, %n লিখে মেমরি পড়তে, এমনকি লিখতেও পারত। ১৯৯০-এর দশকের শেষ আর ২০০০-এর শুরুতে এমন অনেক নিরাপত্তা-ত্রুটি পাওয়া গিয়েছিল। আধুনিক GCC আর Clang -Wformat দিয়ে literal format string যাচাই করে, কিন্তু সেটা compiler-এর বাড়তি সাহায্য, ভাষার নিশ্চয়তা নয়।

C++ এখানে একটি ভিন্ন হাতিয়ার ব্যবহার করল — function overloading. operator<<(std::ostream&, int), operator<<(std::ostream&, double), operator<<(std::ostream&, const std::string&) — প্রতিটি টাইপের জন্য আলাদা ফাংশন, আর কোনটি ডাকা হবে, compiler compile-এর সময়েই আর্গুমেন্টের টাইপ দেখে ঠিক করে। ভুল করার মতো কোনো format string-ই নেই। নিজের Student টাইপের জন্যও একটি << লিখে দিলে সেটাও একই নিয়মে চলে — printf-এ যা অসম্ভব।

দাম কী? প্রথমত, লম্বা লেখা আর টুকরো বাক্য। দ্বিতীয়ত, stream-এর ভেতরে লুকানো অবস্থা (precision, fill, alignment), যা একবার বদলালে অন্য কোডেও প্রভাব ফেলে। তৃতীয়ত, অনুবাদ কঠিন: বাংলা আর ইংরেজিতে বাক্যের শব্দক্রম আলাদা, কিন্তু <<-এর চেইনে ক্রম কোডের ভেতরে বাঁধা। আর ঐতিহাসিকভাবে iostream অনেক বাস্তবায়নে printf-এর চেয়ে ধীর ছিল। std::format এই দুই জগতের ভালোটা জুড়েছে: format string আছে, কিন্তু টাইপ যাচাই হয় compile-এর সময়, variadic template দিয়ে — প্রতিটি আর্গুমেন্টের আসল টাইপ ফাংশনের কাছে পৌঁছায়।

মূল উপলব্ধি: টাইপ-নিরাপত্তা মানে টাইপের তথ্য হারিয়ে না যাওয়া। printf-এ সেই তথ্য থাকে শুধু মানুষের লেখা একটি string-এ, তাই ভুল সম্ভব; C++ তথ্যটি compiler-এর হাতে দিয়ে ভুলকে compile error বানিয়েছে।

প্র ০২std::format কীভাবে একজন মানুষের open-source লাইব্রেরি থেকে C++ standard-এ এলো? এই গল্প ভাষার উন্নতি সম্পর্কে কী শেখায়?

C++ প্রোগ্রামাররা বহু বছর ধরে একটা দ্বিধায় ছিলেন: printf সুবিধাজনক কিন্তু অনিরাপদ, iostream নিরাপদ কিন্তু খটমটে। মাঝখানে কয়েকটি চেষ্টা হয়েছিল — যেমন Boost.Format, যা % অপারেটর দিয়ে নিরাপদ formatting দিত, কিন্তু ধীর ছিল বলে পরিচিত।

২০১০-এর দশকের শুরুর দিকে Victor Zverovich একটি লাইব্রেরি লিখতে শুরু করেন, যা পরে {fmt} নামে পরিচিত হয়। এর format ব্যাকরণ নেওয়া হয়েছিল Python-এর str.format থেকে — {}, {:>8.2f} — যা লাখো প্রোগ্রামার আগেই চিনতেন। লক্ষ্য ছিল তিনটি: printf-এর মতো পড়তে সহজ, iostream-এর মতো টাইপ-নিরাপদ ও নিজের টাইপে বাড়ানো যায়, আর দুটোর চেয়েই দ্রুত। লাইব্রেরিটি GitHub-এ open-source হিসেবে বড় হয়, আর বহু প্রজেক্ট এটি ব্যবহার করতে শুরু করে।

এরপর আসে C++ standard-এর প্রক্রিয়া। কোনো ফিচার standard-এ ঢোকাতে হলে একটি লিখিত প্রস্তাব (paper) জমা দিতে হয়, যা ISO C++ কমিটির সভায় বারবার আলোচনা, সমালোচনা আর সংশোধনের মধ্য দিয়ে যায়। Zverovich-এর “Text Formatting” প্রস্তাব (P0645) কয়েক বছরের সংশোধনের পর ২০১৯ সালে C++20-এর জন্য গৃহীত হয়। পরে std::print-এর প্রস্তাব (P2093) C++23-এ যোগ হয়, আর compile-এর সময় format string যাচাইয়ের নিয়মও শক্ত করা হয়।

এই পথটা লক্ষ করার মতো। কমিটি একটি ধারণা শূন্য থেকে আবিষ্কার করেনি; বাস্তব জগতে বছরের পর বছর পরীক্ষিত, হাজারো ব্যবহারকারীর হাতে ঝালাই করা একটি লাইব্রেরিকে standard বানিয়েছে। std::shared_ptr, std::optional, std::filesystem-এর মতো অনেক ফিচারও একইভাবে আগে Boost-এ বাস্তব ব্যবহারে পরীক্ষিত হয়েছিল। এই কারণেই standard লাইব্রেরি তুলনামূলক ধীরে বদলায়, কিন্তু যা আসে তা সাধারণত পরিপক্ব।

একটি বাস্তব ফলও আছে: যে compiler এখনো <format> বা <print> পুরো সমর্থন করে না, সেখানে প্রায় একই কোড {fmt} লাইব্রেরি দিয়ে লেখা যায় — std::-এর বদলে fmt::.

মূল উপলব্ধি: ভাষা শুধু কমিটির ঘরে তৈরি হয় না; open-source সম্প্রদায়ের বাস্তব পরীক্ষা থেকেই সবচেয়ে টেকসই ফিচারগুলো আসে। ভালো লাইব্রেরি লিখে বাংলাদেশের একজন প্রোগ্রামারও একদিন standard-কে প্রভাবিত করতে পারেন।

প্র ০৩C++ ডিফল্টে কেন ধীর পথ (sync_with_stdio(true)) বেছে রেখেছে? দ্রুত পথটাই ডিফল্ট হলো না কেন?

C++ জন্ম থেকেই একটি প্রতিশ্রুতি দিয়ে এসেছে — C কোডের সাথে পাশাপাশি চলবে। ১৯৮০-৯০-এর দশকে C++-এ লেখা প্রায় প্রতিটি প্রোগ্রাম কোনো না কোনো C লাইব্রেরি ব্যবহার করত, আর সেই লাইব্রেরিগুলো ছাপত printf দিয়ে। এখন ভাবুন, আপনার কোড std::cout << "Step 1\n"; লিখল, তারপর একটি C লাইব্রেরি printf("Step 2\n");, তারপর আবার cout << "Step 3\n";. দুটির buffer আলাদা হলে স্ক্রিনে আসতে পারে Step 1, Step 3, Step 2 — অথবা যেকোনো ক্রমে, buffer কখন ভরে তার ওপর নির্ভর করে। লগ পড়ে বাগ খোঁজা তখন অসম্ভব।

তাই standard বলে দিল: ডিফল্টে C++-এর standard stream আর C-এর stdio “synchronized” — যেকোনো ক্রমে মেশালেও লেখা ঠিক সেই ক্রমেই বের হবে। এটা নিশ্চিত করার সহজতম উপায় হলো C++ stream-কে আলাদা buffer না দিয়ে সব কাজ C-এর stdio দিয়ে করানো। GCC-র libstdc++ ঠিক এটাই করে: sync চালু থাকলে cin/cout প্রতিটি অক্ষর C-এর getc/putc-ধাঁচের ফাংশন দিয়ে পাঠায়। নিরাপদ, কিন্তু প্রতি অক্ষরে বাড়তি ফাংশন-ডাক — দশ লাখ সংখ্যায় সেটা চোখে পড়ার মতো।

sync_with_stdio(false) মানে আপনি কথা দিচ্ছেন: “আমি দুটো মেশাব না।” তখন stream নিজের buffer পায়, বড় টুকরোয় পড়ে-লেখে। দ্রুত পথটা ডিফল্ট হলে কী হতো? যে লাখো পুরোনো প্রোগ্রাম C আর C++ মিশিয়ে লেখা, সেগুলোর আউটপুট নিঃশব্দে উল্টাপাল্টা হয়ে যেত — compile error-ও নয়, শুধু ভুল আচরণ। C++-এর নকশার একটি মূলনীতি: পুরোনো সঠিক কোড নতুন version-এ নিঃশব্দে ভুল হবে না।

tie-এর গল্পও একই রকম। ইন্টারঅ্যাকটিভ প্রোগ্রামে “Enter your roll:” ছাপিয়ে cin দিয়ে পড়লে প্রশ্নটা অবশ্যই আগে দেখাতে হবে, নইলে ব্যবহারকারী ফাঁকা স্ক্রিনের দিকে তাকিয়ে থাকবেন। নতুন শিক্ষার্থী এটা জানেন না, তাই ডিফল্টে নিরাপদ আচরণ। কনটেস্টে কেউ প্রশ্ন দেখে না, তাই সেখানে এটি শুধুই খরচ।

মূল উপলব্ধি: ভালো ডিফল্ট হলো যা নতুনদের আর পুরোনো কোডকে রক্ষা করে; গতি তাদের জন্য, যারা জেনে-বুঝে একটি প্রতিশ্রুতি তুলে নেন। কোনো optimization-এর লাইন লেখার আগে জানুন, আপনি বিনিময়ে কোন নিশ্চয়তা ছেড়ে দিচ্ছেন।

প্র ০৪দশমিক বিন্দু সব দেশে ডট নয়। locale কীভাবে সংখ্যা পড়া-লেখায় বাগ আনতে পারে, আর std::format এখানে কোন অবস্থান নিয়েছে?

বাংলাদেশে আমরা লিখি ৩.৭৫, ইংরেজিতেও 3.75. কিন্তু জার্মানি, ফ্রান্সসহ ইউরোপের অনেক দেশে দশমিক চিহ্ন কমা — 3,75 — আর হাজারের বিভাজক ডট: 1.234.567,89. ভারতীয় উপমহাদেশে আবার লাখ-কোটির রীতি: ১২,৩৪,৫৬৭. দেশ আর ভাষা অনুযায়ী এই নিয়মগুলোর সেটকে বলে locale.

C আর C++-এ প্রোগ্রাম শুরু হয় “C” locale-এ — দশমিক চিহ্ন ডট, কোনো বিভাজক নেই। কিন্তু কোনো লাইব্রেরি বা কোডের কোথাও যদি global locale বদলে ব্যবহারকারীর কম্পিউটারের locale নেওয়া হয় (C-তে setlocale(LC_ALL, ""), C++-এ std::locale::global), তখন printf আর scanf-এর মতো C ফাংশনের আচরণ বদলে যেতে পারে। জার্মান কম্পিউটারে তখন 3.75 ছাপা হয় 3,75 — আর আপনার প্রোগ্রামের লেখা CSV ফাইল, যেখানে কমা দিয়ে কলাম ভাগ, সম্পূর্ণ ভেঙে যায়। উল্টোটাও হয়: ফাইলে লেখা “3.75” পড়তে গিয়ে প্রোগ্রাম শুধু 3 পায়। সবচেয়ে বিপজ্জনক দিক হলো, ডেভেলপারের কম্পিউটারে বাগটা দেখাই যায় না; শুধু অন্য দেশের গ্রাহকের কাছে ঘটে।

C++ stream-এর নিজস্ব locale থাকে (imbue দিয়ে বদলানো যায়), যা নমনীয় কিন্তু জটিল। std::format-এর নকশাকাররা এখান থেকে শিক্ষা নিয়ে একটি স্পষ্ট সিদ্ধান্ত নিলেন: ডিফল্টে locale মানা হবে না। std::format("{}", 3.75) সব কম্পিউটারে, সব দেশে 3.75. Locale চাইলে নিজে বলতে হয় L দিয়ে: {:L}. অর্থাৎ যন্ত্রের জন্য লেখা (ফাইল, নেটওয়ার্ক, লগ) সবসময় একই, আর মানুষের জন্য সাজানো লেখায় locale জেনে-বুঝে ব্যবহার।

আরেকটি গভীর বিষয়: যন্ত্র-পড়ার ডেটার জন্য C++17-এ এসেছে std::to_chars আর std::from_chars — locale-মুক্ত, exception-মুক্ত, আর খুব দ্রুত সংখ্যা-লেখা রূপান্তর। std::format-এর ভেতরেও double-এর “সবচেয়ে ছোট সঠিক রূপ” এই ধারণা থেকেই আসে।

মূল উপলব্ধি: লেখার দুই রকম পাঠক — মানুষ আর যন্ত্র। যন্ত্রের জন্য লেখা সবসময় একই নিয়মে হতে হবে, তাই locale থেকে দূরে; মানুষের জন্য লেখায় locale একটি সচেতন সিদ্ধান্ত। লুকানো global অবস্থা — locale হোক বা stream-এর precision — বাগের পুরোনো উৎস।

প্র ০৫অনলাইন জাজ আপনার প্রোগ্রামকে ইনপুট কীভাবে দেয়, আর উত্তর কীভাবে মেলায়? এটা জানলে কোন ভুলগুলো এড়ানো যায়?

Codeforces, AtCoder বা ICPC-ধাঁচের প্রতিযোগিতার জাজ সাধারণত একই নীতিতে কাজ করে। আপনার কোড জাজের সার্ভারে compile হয়। তারপর প্রতিটি টেস্টের জন্য প্রোগ্রামটি চালানো হয় একটি নিয়ন্ত্রিত পরিবেশে, যেখানে stdin জুড়ে দেওয়া থাকে গোপন ইনপুট ফাইলের সাথে, আর stdout ধরে রাখা হয়। সময় আর মেমরির সীমা মাপা হয়। শেষে আপনার আউটপুট সঠিক উত্তরের সাথে মেলানো হয় — বেশিরভাগ জাজ শব্দে শব্দে (token) মেলায় আর লাইনের শেষের বাড়তি স্পেস উপেক্ষা করে, কিছু সমস্যায় দশমিক উত্তরে নির্দিষ্ট ভুলসীমা মানা হয়, আর যে সমস্যায় একাধিক সঠিক উত্তর হতে পারে, সেখানে বিশেষ যাচাই-প্রোগ্রাম (checker) থাকে।

এই চিত্র থেকে কয়েকটি শিক্ষা বের হয়। এক: “Enter n:” এর মতো প্রশ্ন ছাপাবেন না — জাজের কাছে সেটা আপনার উত্তরের অংশ, তাই Wrong Answer. দুই: debug-এর লেখা cerr-এ দিন, কারণ জাজ সাধারণত শুধু stdout মেলায় — তবে সময় খায়, জমার আগে সরান। তিন: ইনপুট ফাইল থেকে আসে, তাই একসাথে লাখো সংখ্যা আসতে পারে; ধীর I/O একাই Time Limit Exceeded ঘটাতে পারে, অ্যালগরিদম ঠিক থাকলেও। এ কারণেই sync_with_stdio(false) আর '\n'.

চার: ইনপুটের তিন রকম চেহারা চিনুন — প্রথমে টেস্টের সংখ্যা t দেওয়া (সবচেয়ে প্রচলিত), নির্দিষ্ট শেষ-চিহ্ন (যেমন “0 0” এলে থামো), অথবা কিছুই না বলে ফাইল শেষ পর্যন্ত — পুরোনো UVa ধাঁচের সমস্যায় শেষেরটি বেশি দেখা যেত। তৃতীয়টির জন্যই while (std::cin >> a >> b). পাঁচ: দশমিক উত্তরে নির্দিষ্ট ঘর চাইলে {:.6f} বা std::fixed << std::setprecision(6) — ডিফল্ট cout-এর ৬ significant অঙ্ক বড় সংখ্যাকে 1.23457e+06 বানিয়ে উত্তর নষ্ট করতে পারে।

আর একটি সূক্ষ্ম দিক: জাজের ইনপুট ফাইল Linux-এ তৈরি হয় সাধারণত \n লাইন-শেষ দিয়ে, কিন্তু Windows-এ বানানো ফাইলে থাকে \r\n. >> দুটোকেই whitespace ধরে, কিন্তু getline দিয়ে পড়লে লাইনের শেষে একটি অদৃশ্য \r থেকে যেতে পারে — নাম মেলাতে গিয়ে অদ্ভুত অমিল।

মূল উপলব্ধি: আপনার প্রোগ্রামের আসল ব্যবহারকারী অনেক সময় মানুষ নয়, আরেকটি প্রোগ্রাম। যন্ত্রের সাথে কথা বলার I/O হতে হবে নিখুঁত, নীরব আর দ্রুত — আর সেটা নিশ্চিত করতে stream কীভাবে কাজ করে, তা জানতেই হয়।

অনুশীলন

বেশিরভাগ কাজে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর উত্তর মিলিয়ে নিন।

  1. buffer অনুমান: int a{}, b{}, c{}; std::cin >> a >> b >> c; — ইনপুট তিন লাইনে: প্রথম লাইনে 5 7, দ্বিতীয় লাইন একদম খালি, তৃতীয় লাইনে 9 11. a, b, c-এর মান কত, আর buffer-এ কী রয়ে গেল?

    a = 5, b = 7, c = 9. >> সামনের সব whitespace — অনেকগুলো স্পেস, প্রথম লাইনের \n, খালি লাইনের \n — বাদ দেয়, তাই লাইন-বিন্যাস কোনো প্রভাব ফেলে না। buffer-এ রয়ে গেল " 11\n" — পরের পড়া 11 পাবে।

  2. Format spec লিখুন: চা-দোকানের রসিদের একটি লাইন এমন চাই — পণ্যের নাম বাঁয়ে ১২ ঘরে, তারপর x আর পরিমাণ, তারপর দাম ডানে ৮ ঘরে দুই দশমিকে: Tea x2 30.00. std::println-এর format string কী হবে? আর রসিদ নম্বর 7-কে No. 0007 দেখাতে?

    std::println("{:<12}x{:<3}{:>8.2f}", "Tea", 2, 30.0); — “Tea” + ৯ স্পেস, তারপর “x”, পরিমাণ 2 বাঁয়ে ৩ ঘরে (“2” + ২ স্পেস), তারপর “30.00” ডানে ৮ ঘরে (৩ স্পেস + ৫ অক্ষর)। রসিদ নম্বর: std::println("No. {:04}", 7); → No. 0007. খেয়াল করুন, দাম 30.0 (double) দিতে হবে; 30 (int) দিলে .2f compile error দেবে — পূর্ণসংখ্যায় f চলে না। টাইপ-নিরাপত্তা এখানেও কাজ করছে।

  3. বাগ শিকার: একটি AI কনটেস্টের জন্য এই কোড দিয়েছে — শুরুতে std::ios::sync_with_stdio(false);, তারপর দশ লাখ বারের লুপে scanf("%d", &x); আর std::cout << x * 2 << std::endl;. কয়টি সমস্যা পান?

    তিনটি। (১) sync_with_stdio(false)-এর পরে C-এর scanf আর C++-এর cout মেশানো — এখানে একটি ইনপুটের, একটি আউটপুটের stream বলে ক্রম-সমস্যা কম দেখা দিতে পারে, কিন্তু নিয়মভঙ্গ; একটি পরিবার বেছে নিন (std::cin >> x). (২) std::endl প্রতি লাইনে flush — দশ লাখ বার অপারেটিং সিস্টেমকে ডাকা; '\n' দিন। (৩) cin.tie(nullptr) নেই — cin ব্যবহার করলে প্রতি পড়ার আগে flush. আর বোনাস: x * 2 বড় x-এ int overflow করতে পারে (পর্ব ০৫)।

  4. নিজে চালিয়ে দেখুন (কোড): উপরের safe_sum.cpp-এর ইনপুট বদলে 1e3 4 (প্রথম লাইন) আর 2 (দ্বিতীয় লাইন) দিলে count আর sum কত হবে, আগে অনুমান করুন। তারপর কোডটি নিজের কম্পিউটারে চালিয়ে ./safe_sum < in.txt 2> err.txt দিয়ে stdout আর stderr আলাদা করে দেখুন।

    count = 2, sum = 3. int হিসেবে “1e3”-এর শুধু “1” পড়া যায় (বৈজ্ঞানিক রূপ পূর্ণসংখ্যায় চলে না), তাই 1 সফল। পরের পড়া 'e'-তে fail — ignore লাইনের বাকিটা (“e3 4”) ফেলে দেয়, 4-সহ। তারপর 2. মোট 1 + 2 = 3. err.txt-এ থাকবে একটি “bad input” লাইন, আর স্ক্রিনে শুধু ফলাফল — এটাই cerr আলাদা রাখার সুবিধা। (double-এ পড়লে “1e3” পুরোটাই 1000 হতো।)

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমC++-এ int-এ “abc” পড়তে গিয়ে ব্যর্থ হলে আবার নিরাপদে পড়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. std::cin >> x চেষ্টা করা
  2. std::cin.fail() দিয়ে ব্যর্থতা ধরা
  3. std::cin.clear() দিয়ে অবস্থা আবার good করা
  4. std::cin.ignore(max, '\n') দিয়ে ভুল লাইন ফেলে দেওয়া
  5. আবার std::cin >> x দিয়ে পড়া

std::string name; std::cin >> name; — ইনপুট Rafiul Islam হলে name-এর মান কী?

  • "Rafiul Islam"
  • "Rafiul"
  • "Islam"
  • খালি string, কারণ পড়া ব্যর্থ হয়

ইনপুট 42⏎Rafi⏎ হলে std::cin >> roll; std::getline(std::cin, name); চলার পর name-এ কী থাকে?

  • "Rafi"
  • "42"
  • "" (খালি string)
  • প্রোগ্রাম চিরকাল অপেক্ষা করে

C++-এ std::format("{:>6}", 42)-এর ফল কোনটি? (· মানে একটি স্পেস)

  • "····42"
  • "42····"
  • "000042"
  • "······42"

std::cout << std::setw(5) << 1 << 2; কী ছাপে? (· মানে একটি স্পেস)

  • "····1····2"
  • "1····2"
  • "12"
  • "····12"

C++-এ std::cin.clear() ঠিক কী করে?

  • Buffer-এর সব অক্ষর মুছে ফেলে
  • Stream-এর error flag মুছে অবস্থা আবার good করে
  • Terminal-এর স্ক্রিন পরিষ্কার করে
  • শেষ পড়া ভ্যারিয়েবলকে শূন্য করে

C++ প্রোগ্রামে std::ios::sync_with_stdio(false); লেখার পর কোনটি এড়িয়ে চলা উচিত?

  • আউটপুটে '\n' ব্যবহার
  • while (std::cin >> x) দিয়ে EOF পর্যন্ত পড়া
  • একই প্রোগ্রামে printf আর std::cout মেশানো
  • এর পরে std::cin.tie(nullptr) লেখা

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Stream = buffer-যুক্ত পাইপ; ইনপুট মানে লেখা → মান, আউটপুট মানে মান → লেখা। Formatting শুধু উপস্থাপন, মান বদলায় না।
  2. >> whitespace বাদ দেয়, টাইপ অনুযায়ী পড়ে, প্রথম অমিলে থামে। ব্যর্থ হলে fail অবস্থা — clear() + ignore() ছাড়া আর পড়বে না। সব পড়ার রীতি while (std::cin >> x).
  3. cin >>-এর পরে getline হলে std::getline(std::cin >> std::ws, s) — রয়ে যাওয়া \n সরাতে।
  4. setw শুধু পরের একটি আউটপুটে, বাকি manipulator sticky. নতুন কোডে {:<10}{:>8.2f} — std::format/std::println, টাইপ-নিরাপদ ও অবস্থাহীন।
  5. Error যাবে std::cerr-এ; কনটেস্টে sync_with_stdio(false) + cin.tie(nullptr) + '\n' — আর তারপর printf/cout মেশানো নয়।

পরের পর্বে: এতক্ষণ যে while আর if একটু আগেভাগে ব্যবহার করলেন, এবার তাদের পুরো গল্প — সিদ্ধান্ত নেওয়া (if, switch), বারবার করা (for, while), আর আধুনিক C++-এর range-for.

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান