এই পর্বে যা শিখবেন
- C++-এর মৌলিক টাইপগুলো — কোনটি কত বড়, কত পর্যন্ত মান ধরে, আর কীভাবে নিজে মেপে দেখবেন
- ভ্যারিয়েবল বানানোর তিন রকম লেখা, আর কেন আধুনিক C++
{}পছন্দ করে autoকখন কাজের, কখন বিপদের — ধাপে ধাপে টাইপ অনুমানconst,constexprআর “magic number” থেকে মুক্তি- টাইপ বদলের নিয়ম, integer division-এর ফাঁদ আর AI-এর লেখা কোডে টাইপের ভুল ধরা
১ · ভ্যারিয়েবল — নাম আর টাইপওয়ালা একটি বাক্স
আগের পর্বে আপনি প্রথম C++ প্রোগ্রাম লিখেছেন, স্ক্রিনে লেখা ছাপিয়েছেন। কিন্তু শুধু লেখা ছাপিয়ে কোনো কাজের সফটওয়্যার হয় না। বেতনের হিসাব, পরীক্ষার ফল, bKash-এর ব্যালান্স — সবখানেই প্রোগ্রামকে কোনো মান মনে রাখতে হয়, বদলাতে হয়, হিসাব করতে হয়। এই মনে রাখার জায়গাটির নাম variable.
একটি গুদামঘরের কথা ভাবুন। সেখানে নানা আকারের বাক্স সাজানো। প্রতিটি বাক্সের গায়ে একটি লেবেল (নাম), আর লেখা আছে ভেতরে কী রাখা যাবে — “শুধু চাল”, “শুধু ডিম”। ছোট বাক্সে বড় জিনিস ঢোকে না। ডিমের বাক্সে চাল ঢালা নিষেধ। C++-এর ভ্যারিয়েবল ঠিক এমন বাক্স — তিনটি জিনিস দিয়ে তৈরি:
🏷️ নাম
বাক্সটিকে আপনি কী বলে ডাকবেন — marks, price, isPaid. নাম শুধু মানুষের জন্য; compiler পরে এটিকে মেমরির একটি ঠিকানায় বদলে নেয়।
📐 টাইপ
বাক্সটি কত বাইটের, কোন ধরনের মান ধরে (পূর্ণসংখ্যা, দশমিক, সত্য/মিথ্যা), আর এর ওপর কোন কাজ চলে।
বাক্সের আকার ও নিয়ম📦 মান
এই মুহূর্তে বাক্সের ভেতরে যা আছে — 89, 1200.50, true. মান বদলাতে পারে, নাম আর টাইপ বদলায় না।
C++ একটি statically typed ভাষা — প্রতিটি ভ্যারিয়েবলের টাইপ compile-এর সময়েই ঠিক হয়ে যায়, আর প্রোগ্রাম চলার সময় আর বদলায় না। Python-এ একই নাম একবার সংখ্যা, পরে লেখা রাখতে পারে; C++-এ int বাক্স সারাজীবন int-ই থাকবে। দাম একটু বেশি লেখা, কিন্তু লাভ বিশাল: অনেক ভুল প্রোগ্রাম চালানোর আগেই compiler ধরে ফেলে, আর compiler ঠিক জানে কত মেমরি লাগবে — তাই কোড দ্রুত চলে।
টাইপ কেন এত গুরুত্বপূর্ণ, একটি ছোট উদাহরণে দেখুন। কম্পিউটারের মেমরিতে শুধু বিট (0 আর 1) থাকে। 01000001 — এই আটটি বিট কী? সংখ্যা হিসেবে পড়লে 65, অক্ষর হিসেবে পড়লে 'A'. বিট একই, অর্থ ভিন্ন। টাইপ হলো সেই চশমা, যা দিয়ে compiler বিটগুলো পড়ে।
২ · মৌলিক টাইপ — কোন বাক্সে কী রাখবেন
C++ ভাষার সাথে কয়েকটি তৈরি টাইপ আসে, এদের বলে fundamental type. পরে আপনি নিজের টাইপও বানাবেন (পর্ব ১২-এ struct, পর্ব ২০-এ class), কিন্তু সব কিছুর ভিত্তি এগুলোই।
| টাইপ | কী রাখে | সাধারণ আকার* | বাস্তব উদাহরণ |
|---|---|---|---|
bool | true বা false | ১ বাইট | বিল পরিশোধ হয়েছে কি না |
char | একটি অক্ষর বা ছোট সংখ্যা | ১ বাইট (সবসময়) | গ্রেড 'A' |
short | ছোট পূর্ণসংখ্যা | ২ বাইট | ক্লাসের রোল নম্বর |
int | সাধারণ পূর্ণসংখ্যা | ৪ বাইট | পরীক্ষার নম্বর, পণ্যের সংখ্যা |
long long | অনেক বড় পূর্ণসংখ্যা | ৮ বাইট | দেশের মোট জনসংখ্যা × কিছু |
unsigned int | শুধু শূন্য বা ধনাত্মক | ৪ বাইট | বিটের হিসাব, বাইটের সংখ্যা |
float | দশমিক সংখ্যা, প্রায় ৭ অঙ্ক নির্ভুল | ৪ বাইট | গেমের গ্রাফিক্স |
double | দশমিক সংখ্যা, প্রায় ১৫–১৬ অঙ্ক নির্ভুল | ৮ বাইট | CGPA, তাপমাত্রা, বিজ্ঞানের হিসাব |
* তারকা চিহ্নটি খেয়াল করুন। C++ standard বেশিরভাগ টাইপের ঠিক আকার বলে দেয় না, শুধু ন্যূনতম সীমা বলে। যেমন int অন্তত ১৬ বিট, long অন্তত ৩২ বিট, long long অন্তত ৬৪ বিট। শুধু char-এর আকার নিশ্চিত — sizeof(char) সবসময় ১. বাকি সব নির্ভর করে কম্পিউটার আর compiler-এর ওপর। এর বিখ্যাত উদাহরণ long: 64-bit Linux-এ এটি ৮ বাইট, কিন্তু 64-bit Windows-এ মাত্র ৪ বাইট!
তাহলে নিশ্চিত হবেন কীভাবে? মেপে দেখে। sizeof বলে দেয় একটি টাইপ কত বাইট নেয়, আর <limits> হেডারের std::numeric_limits বলে দেয় সবচেয়ে ছোট আর সবচেয়ে বড় মান কত। নিচের কোডটি চালান — ফলাফল Wandbox-এর মতো 64-bit Linux সিস্টেমের; আপনার কম্পিউটারে আলাদা হতে পারে:
#include <cstdint>
#include <limits>
#include <print>
int main() {
std::println("bool : {} byte", sizeof(bool));
std::println("char : {} byte", sizeof(char));
std::println("short : {} bytes", sizeof(short));
std::println("int : {} bytes, max = {}", sizeof(int), std::numeric_limits<int>::max());
std::println("unsigned : {} bytes, max = {}", sizeof(unsigned), std::numeric_limits<unsigned>::max());
std::println("long : {} bytes", sizeof(long));
std::println("long long : {} bytes, max = {}", sizeof(long long), std::numeric_limits<long long>::max());
std::println("double : {} bytes, max = {}", sizeof(double), std::numeric_limits<double>::max());
std::println("int8_t : min = {}, max = {}", std::numeric_limits<std::int8_t>::min(),
std::numeric_limits<std::int8_t>::max());
}প্রতিটি লাইন একটি টাইপের মাপ নিচ্ছে। int-এর সর্বোচ্চ মান আসবে 2147483647 (প্রায় ২১৪ কোটি) — অর্থাৎ বাংলাদেশের মোট জনসংখ্যা int-এ ধরে, কিন্তু পুরো পৃথিবীর জনসংখ্যা ধরে না! unsigned-এ ঋণাত্মক অংশ নেই, তাই সর্বোচ্চ মান প্রায় দ্বিগুণ। long এখানে ৮ বাইট দেখাবে, Windows-এ দেখাত ৪. শেষ লাইনের std::int8_t ঠিক ৮ বিটের, তাই সীমা −128 থেকে 127.
যখন আকার নিশ্চিত হওয়া জরুরি — <cstdint>
ধরুন আপনি একটি ফাইল ফরম্যাট বা নেটওয়ার্ক প্রোটোকল বানাচ্ছেন, যেখানে একটি সংখ্যা ঠিক ৪ বাইট হতেই হবে — Windows, Linux বা মোবাইল যেখানেই চলুক। তখন int-এর ওপর ভরসা করা যায় না। এজন্য আছে fixed-width integer: std::int8_t, std::int16_t, std::int32_t, std::int64_t আর এদের u-যুক্ত (std::uint32_t) unsigned রূপ। নামের সংখ্যাটিই বিটের সংখ্যা — কোনো সন্দেহ নেই।
প্রতিদিনের নিয়ম: সাধারণ গণনায় int, দশমিকে double, সত্য/মিথ্যায় bool. আকার যেখানে চুক্তির অংশ (ফাইল, নেটওয়ার্ক, হার্ডওয়্যার), সেখানে std::int32_t-এর মতো fixed-width টাইপ। আর float শুধু তখন, যখন মেমরি বাঁচানো সত্যিই জরুরি (যেমন গ্রাফিক্সে লাখ লাখ বিন্দু)।
char আর বাংলা লেখা — char8_t
char মাত্র ১ বাইট, তাই এতে শুধু ইংরেজি অক্ষরের মতো ছোট চিহ্ন ধরে। বাংলা “ক” UTF-8-এ লিখতে লাগে ৩ বাইট — একটি char-এ ধরে না। C++20 থেকে UTF-8-এর প্রতিটি বাইটের জন্য আলাদা একটি টাইপ এসেছে, char8_t; u8"..." লেখার টাইপ এখন char8_t-এর array. আপাতত শুধু জেনে রাখুন: বাংলা লেখা নিয়ে কাজ করবেন std::string দিয়ে (পর্ব ১৪), একটি char দিয়ে নয়।
৩ · বাক্সের ভেতরে এক ঝলক — বিট দিয়ে সংখ্যা
বাক্সের আকার কেন সীমা ঠিক করে, বুঝতে হলে একবার ভেতরে তাকাতে হবে। মেমরিতে প্রতিটি সংখ্যা বিটের সারি। ৮ বিটে মোট ২৮ = ২৫৬ রকম নকশা হয়, তাই ১ বাইটের বাক্সে ২৫৬টির বেশি আলাদা মান কখনোই রাখা যায় না। একই সংখ্যা 100 তিনটি ভিন্ন বাক্সে কেমন দেখায়, নিচে ধাপে ধাপে দেখুন:
01100100 64 + 32 + 4 = 100
00000000 00000000 00000000 01100100 একই মান, বাকি ২৪ বিট শুধু শূন্য
0 10000000101 1001000000000000...0000 চিহ্ন | exponent (6) | mantissa (1.5625) 100 = 1.5625 x 2^6 (hex: 4059000000000000)
200 = 11001000 প্রথম বিট 1 = ঋণাত্মক চিহ্ন হিসেবে পড়া হয় w-এর মান হয়ে যায় -56 (200 - 256)
127 = 01111111 -1 = 11111111
1 = 00000001 -128 = 10000000
0 = 00000000
-x পেতে: সব বিট উল্টান, তারপর 1 যোগ করুন
- ১ বাইটের বাক্স।
std::int8_t-এ 100 রাখা হলো। বাইনারিতে 100 =01100100(৬৪ + ৩২ + ৪)। ৮টি ঘরেই ধরে গেছে। - ৪ বাইটের বাক্স। একই 100 এবার
std::int32_t-এ। ডান দিকের ৮ বিট হুবহু আগের মতো, বাকি ২৪ বিট শূন্য। বড় বাক্সে ছোট মান রাখা সবসময় নিরাপদ — শুধু জায়গা বেশি লাগে। - দশমিকের বাক্স সম্পূর্ণ আলাদা।
doubleসংখ্যাকে বিজ্ঞানের “বৈজ্ঞানিক প্রতীক”-এর মতো রাখে: একটি চিহ্ন-বিট, একটি ঘাত (exponent) আর একটি ভগ্নাংশ (mantissa). 100 = 1.5625 × 26. তাই একই 100-এর বিট-নকশাint-এর সাথে একদমই মেলে না — এজন্যই টাইপ ছাড়া বিট পড়া অর্থহীন। - ছোট বাক্সে বড় মান।
std::int8_t-এর সর্বোচ্চ 127. সেখানে 200 রাখতে চাইলে বিট-নকশা11001000ঠিকই বসে, কিন্তু signed টাইপে প্রথম বিটটি ঋণাত্মক চিহ্নের কাজ করে। ফল: 200 হয়ে যায় −56. এমন সরাসরি লেখা সংখ্যায় g++ হয়তো একটি warning দেবে, কিন্তু মানটি অন্য ভ্যারিয়েবল বা ইনপুট থেকে এলে compiler কিছুই বলে না — ভুল হিসাব নিঃশব্দে চলতে থাকে। - Two's complement — ঋণাত্মক সংখ্যার নিয়ম। প্রায় সব আধুনিক CPU ঋণাত্মক পূর্ণসংখ্যা রাখে two's complement পদ্ধতিতে: সব বিট উল্টে ১ যোগ। এতে ৮ বিটে সীমা দাঁড়ায় −128 থেকে 127, আর যোগ-বিয়োগের একই সার্কিট ধনাত্মক-ঋণাত্মক দুটোতেই কাজ করে। C++20 থেকে standard-ও এই পদ্ধতিকে বাধ্যতামূলক করেছে।
দশমিক সংখ্যা কখনো কখনো “প্রায়” সঠিক
দশমিক সংখ্যা রাখার পদ্ধতির নাম floating point (IEEE 754 standard). এর একটি অদ্ভুত কিন্তু জরুরি দিক আছে: বাইনারিতে ০.১-কে সঠিকভাবে লেখা যায় না, যেমন দশমিকে ১/৩-কে সঠিকভাবে লেখা যায় না (০.৩৩৩৩…)। ফলে 0.1 + 0.2-এর ফল double-এ আসে 0.30000000000000004. এটি C++-এর বাগ নয় — Python, JavaScript, Java সবখানে একই ফল, কারণ সবাই একই হার্ডওয়্যার-নিয়ম মানে।
দুটি দশমিক সংখ্যা == দিয়ে তুলনা করবেন না — 0.1 + 0.2 == 0.3 মিথ্যা হয়! আর টাকার হিসাব float-এ তো নয়ই। ব্যাংক বা মোবাইল ওয়ালেটের সফটওয়্যার সাধারণত টাকাকে পয়সায় বদলে পূর্ণসংখ্যা (std::int64_t) হিসেবে রাখে — ১২৫০.৫০ টাকা = 125050 পয়সা।
এখানে C আর C++ প্রায় হুবহু এক: একই টাইপ, একই sizeof, একই two's complement আর IEEE 754. পার্থক্য হলো C++-এ সীমা জানতে INT_MAX-এর মতো macro-র বদলে টাইপ-নিরাপদ std::numeric_limits<int>::max() ব্যবহার করা হয়, আর bool ভাষার নিজস্ব টাইপ (হেডার লাগে না)। বিট, বাইট আর floating point-এর গভীর ব্যাখ্যা চাইলে C কোর্সের ডেটা কীভাবে রাখা হয় আর ভ্যারিয়েবল ও টাইপ পর্ব দুটি দেখুন।
৪ · বাক্স বানানোর তিন উপায় — =, () আর {}
ভ্যারিয়েবল বানানোর মুহূর্তেই তাতে প্রথম মান বসানোকে বলে initialization. C++-এ এটি তিনভাবে লেখা যায়, আর তিনটির আচরণ পুরোপুরি এক নয়:
int a = 5; // C থেকে আসা পুরোনো রীতি
int b(5); // constructor-এর মতো লেখা
int c{5}; // brace initialization (C++11 থেকে)সাধারণ ক্ষেত্রে তিনটিই 5 রাখে। পার্থক্য দেখা যায় ভুল টাইপের মান দিলে। ধরুন একজন ছাত্রের নম্বর 89.5, কিন্তু বাক্সটি int. দশমিক থেকে পূর্ণসংখ্যায় গেলে তথ্য হারায় — একে বলে narrowing conversion. কোন লেখা এটা ধরে, ধাপে ধাপে দেখুন:
1int a = 89.5;2int b(89.5);3int c{89.5};4int d{};5int e;6double f{89.5};
| a | |
| b | |
| c | |
| d | |
| e | |
| f |
=দিয়ে — নিঃশব্দে কেটে ফেলা।int a = 89.5;সফলভাবে compile হয়, কিন্তুa-তে থাকে শুধু 89. দশমিকের .5 কোনো সতর্কবার্তা ছাড়াই হারিয়ে গেল (rounding নয়, সোজা কেটে ফেলা)। একজন ছাত্র অর্ধেক নম্বর হারালেন, কেউ টেরও পেল না।()দিয়ে — একই সমস্যা।int b(89.5);-ও চুপচাপ 89 রাখে। পুরোনো দুটি লেখাই C-এর নিয়ম মেনে চলে, আর C-তে narrowing বৈধ।{}দিয়ে — দরজায় পাহারাদার।int c{89.5};compile-ই হয় না। Compiler স্পষ্ট বলে দেয়: double থেকে int-এ narrowing. ভুলটা প্রোগ্রাম চালানোর আগেই ধরা পড়ল — এটাই brace initialization-এর সবচেয়ে বড় সুবিধা।- খালি
{}— শূন্য দিয়ে শুরু।int d{};লিখলেdনিশ্চিতভাবে 0 (doubleহলে 0.0,boolহলেfalse). মান কী হবে জানা না থাকলেও এভাবে নিরাপদে শুরু করা যায়। - কিছুই না দিলে — বিপদ। ফাংশনের ভেতরে
int e;লিখলে বাক্সে থাকে আগে থেকে পড়ে থাকা যেকোনো বিট — “আবর্জনা মান” (?)। মান না বসিয়েeপড়া undefined behavior: আজ 0, কাল 32767, অন্য compiler-এ অন্য কিছু। - সঠিক সমাধান — সঠিক বাক্স। দশমিক দরকার হলে বাক্সটিই
doubleকরুন:double f{89.5};. আর যদি সত্যিই কেটে ফেলতে চান, সেটা লিখে জানিয়ে দিন — পরের অংশেstatic_castদেখবেন। নিয়ম: সাধারণত{}দিয়ে initialize করুন, আর কখনো খালি বাক্স রেখে দেবেন না।
Uninitialized variable এমন বাগ, যা পরীক্ষার সময় দিব্যি ঠিক চলতে পারে, আর গ্রাহকের কম্পিউটারে গিয়ে ভুল ফল দেয়। -Wall চালু থাকলে GCC অনেক ক্ষেত্রে may be used uninitialized সতর্কবার্তা দেয়, তবে সব ক্ষেত্রে নয়। নিশ্চিত সমাধান একটাই — বানানোর সময়েই মান দিন।
দুটি ছোট বিস্ময়
১. Most vexing parse. int x(); দেখে মনে হয় “x নামের একটি int, মান শূন্য।” কিন্তু C++-এর ব্যাকরণ এটিকে পড়ে “x নামের একটি ফাংশন, যা কিছু নেয় না আর int ফেরত দেয়”! এর একটু জটিল রূপের নাম Scott Meyers দিয়েছিলেন most vexing parse — “সবচেয়ে বিরক্তিকর ব্যাকরণ-ব্যাখ্যা”; এই সরল রূপটিকেও প্রায়ই একই নামে ডাকা হয়। int x{}; লিখলে এই সমস্যা থাকেই না — brace আসার আরেকটি কারণ এটিই।
২. {} আর () সবসময় এক নয়। পর্ব ১৩-এ std::vector দেখবেন। সেখানে std::vector<int> v(5); মানে পাঁচটি শূন্য, আর std::vector<int> v{5}; মানে একটিমাত্র উপাদান, যার মান 5. Brace দেখলে container ধরে নেয় আপনি উপাদানের তালিকা দিচ্ছেন। এখন শুধু মনে রাখুন — container-এ দুটির অর্থ আলাদা হতে পারে।
৫ · auto — compiler-কে টাইপ বুঝে নিতে দেওয়া
অনেক সময় টাইপটা ডান পাশের মান দেখেই পরিষ্কার। int count = 10; — এখানে 10 তো পূর্ণসংখ্যাই, আবার int লেখার দরকার কী? C++11 থেকে আপনি লিখতে পারেন auto count = 10;, আর compiler নিজেই ডান পাশের মান দেখে টাইপ ঠিক করে নেয়। একে বলে type deduction, আর শব্দটি হলো auto.
দোকানে পার্সেল এলো, গায়ে কিছু লেখা নেই। অভিজ্ঞ কর্মচারী খুলে ভেতরটা দেখেই লেবেল লাগিয়ে দেন — “ডিম”, “চাল”। auto হলো সেই কর্মচারী। মনে রাখুন, লেবেল কিন্তু লাগছেই — auto মানে “টাইপ নেই” নয়, মানে “টাইপ compiler ঠিক করবে।” একবার ঠিক হলে সেটি আর বদলায় না।
কিন্তু compiler ঠিক কোন টাইপ বেছে নেয়? সবসময় আপনার অনুমানের সাথে মেলে না। প্রতিটি লাইনের জন্য নিজে আগে অনুমান করুন, তারপর “পরে” চাপুন:
1auto a = 5;2auto b = 5.0;3auto c = 5.0f;4auto d = 'x';5auto e = 10 / 4;6auto s = "hi";7auto t = "hi"s;
| a | |
| b | |
| c | |
| d | |
| e | |
| s | |
| t |
5→int. দশমিক বিন্দু ছাড়া পূর্ণসংখ্যার literal ডিফল্টভাবেint. সহজ।5.0→double. শুধু একটি.0যোগ করায় টাইপ বদলে গেল। দশমিক literal ডিফল্টভাবেdouble,floatনয়।5.0f→float. শেষেরfহলো suffix — “এটিকে float ধরো।” Suffix-এর বিস্তারিত অংশ ৮-এ।'x'→char. একক উদ্ধৃতিচিহ্নে (' ') একটি অক্ষর মানেchar. দ্বৈত উদ্ধৃতিচিহ্নে (" ") হলে সম্পূর্ণ ভিন্ন জিনিস — পরের ধাপের পরে দেখুন।10 / 4→int, মান 2!autoডান পাশের পুরো হিসাবের টাইপ নেয়। দুটিintভাগ করলে ফলওint, তাই 2.5 নয়, 2.autoএখানে ভুল ধরিয়ে দেয় না, বরং ভুলটাই নিখুঁতভাবে কপি করে।"hi"→const char*— সবচেয়ে বড় ফাঁদ। দ্বৈত উদ্ধৃতিচিহ্নের লেখা C থেকে আসা পুরোনো ধাঁচের অক্ষরের array;autoএকে বানায় তার প্রথম অক্ষরের ঠিকানা, অর্থাৎconst char*(পর্ব ১৫-এ pointer). এটিstd::stringনয় —s.size()বাs + " there"লিখলে compile error."hi"s→std::string. শেষেরssuffix বলে দেয় “এটি std::string.” এর জন্য উপরেusing namespace std::literals;লিখতে হয় (C++14 থেকে)। এখনt.size(),t + "!"সবই চলবে।
auto কখন ব্যবহার করবেন, কখন নয়
✅ যেখানে auto সাহায্য করে
টাইপের নাম খুব লম্বা, অথচ ডান পাশেই স্পষ্ট — যেমন auto p = std::make_unique<Student>(); (পর্ব ১৭), বা পরে দেখবেন এমন iterator (পর্ব ৩২), যার টাইপের নাম এক লাইনের চেয়েও লম্বা। auto দিলে ভ্যারিয়েবলে মান না দিয়ে বানানোও অসম্ভব — খালি বাক্সের বিপদ নেই।
⚠️ যেখানে auto ক্ষতি করে
auto total = calculate(); — পাঠক বুঝতেই পারেন না total কি int, double, না অন্য কিছু। টাকা, নম্বর বা মাপের মতো গুরুত্বপূর্ণ মানে টাইপ নিজে লিখে দেওয়াই ভালো, যাতে ভুল টাইপ হলে চোখে পড়ে।
auto শব্দটি আসলে নতুন নয়। C-তে (এবং ২০১১-এর আগের C++-এ) এটি ছিল একটি storage class — অর্থ “এই ভ্যারিয়েবল ফাংশনের ভেতরে স্বয়ংক্রিয়ভাবে জন্মায় ও মরে।” কিন্তু সেটা তো ডিফল্টই, তাই প্রায় কেউ লিখতেন না। C++11 এই অব্যবহৃত শব্দটিকে নতুন কাজ দিল — টাইপ অনুমান। নতুন শব্দ না এনে পুরোনো শব্দ নেওয়ায় কারও পুরোনো কোড ভাঙেনি, কারণ auto নামে কেউ ভ্যারিয়েবল রাখতে পারতেন না।
৬ · const আর constexpr — যে মান বদলাবে না
বাংলাদেশে বেশিরভাগ পণ্যে VAT ১৫%. ধরুন একটি দোকানের সফটওয়্যারে বিশ জায়গায় লেখা আছে price * 0.15. এখন দুটি সমস্যা। প্রথমত, নতুন প্রোগ্রামার কোড পড়ে বুঝবেন না 0.15 কী — ছাড়? কমিশন? দ্বিতীয়ত, হার বদলালে বিশ জায়গা খুঁজে খুঁজে বদলাতে হবে, একটি ভুলে গেলেই হিসাব গড়বড়। এমন অর্থহীন সংখ্যাকে বলে magic number.
সমাধান: মানটিকে একবার, একটি অর্থপূর্ণ নামে, অপরিবর্তনীয় করে লিখুন।
const double vatRate = 0.15; // চলার সময় একবার ঠিক হয়, আর বদলায় না
constexpr double VAT_RATE = 0.15; // compile-এর সময়েই জানা, compiler নিজে হিসাবে বসাতে পারে
vatRate = 0.10; // error: assignment of read-only variable| শব্দ | প্রতিশ্রুতি | কখন |
|---|---|---|
| const | “initialize-এর পর আমি বদলাব না।” মান চলার সময়েও আসতে পারে (যেমন ব্যবহারকারীর ইনপুট)। | যেকোনো মান, যা একবার ঠিক হলে আর বদলায় না |
| constexpr | “আমার মান compile-এর সময়েই জানা।” তাই এটি const-ও। | নিয়ম-নির্ধারিত ধ্রুবক: VAT হার, সর্বোচ্চ নম্বর, array-এর আকার |
| constinit | “আমার প্রথম মান compile-এর সময়েই বসবে” — কিন্তু পরে বদলাতে পারে (C++20). | বিশেষ ক্ষেত্রে global ভ্যারিয়েবল; এখন শুধু নামটা চিনে রাখুন |
বাস্তব নিয়ম খুব সহজ: যে মান বদলানোর কোনো কারণ নেই, তাকে const করুন; যদি compile-এর সময়েই জানা থাকে, constexpr করুন। এতে তিনটি লাভ — পাঠক নাম দেখেই অর্থ বোঝেন, ভুল করে বদলালে compiler আটকায়, আর হার বদলালে শুধু এক লাইন বদলাতে হয়। constexpr-এর আসল শক্তি — পুরো ফাংশন compile-এর সময়েই চালিয়ে ফেলা — দেখবেন পর্ব ৩৯-এ।
নামের রীতি: অনেক দল ধ্রুবকের নাম বড় হাতের অক্ষরে লেখেন (VAT_RATE), অনেকে kVatRate. কোনটি নিয়ম নয়, রুচি — যে প্রজেক্টে কাজ করছেন, সেখানকার রীতি মানুন। শুধু খেয়াল রাখুন, পুরো বড় হাতের নাম C-তে প্রায়ই macro-র জন্য রাখা হয়, তাই কিছু C++ style guide এটি এড়িয়ে চলে।
৭ · টাইপ বদল — নিজে থেকে, আর আপনার হুকুমে
ভিন্ন টাইপের দুটি মান একসাথে হিসাবে এলে compiler একটিকে অন্যটির টাইপে বদলে নেয়। একে বলে implicit conversion. সাধারণ নিয়ম — হিসাবটা হয় “বড়” টাইপে। int আর double যোগ করলে int-টি double হয়ে যায়, ফলও double. এটা নিরাপদ, কারণ ছোট বাক্স থেকে বড় বাক্সে যাওয়া।
বিপদ উল্টো দিকে — বড় থেকে ছোটতে (narrowing), আর একটি বিখ্যাত ফাঁদে: integer division. দুটি int ভাগ করলে ফলও int, দশমিক অংশ কেটে ফেলা হয়: 5 / 2 = 2, 2 / 3 = 0. বাম পাশে double লিখলেও বাঁচা যায় না, কারণ ভাগটা আগেই হয়ে গেছে। তিনজন ছাত্রের নম্বর 89, 76 ও 92 — মোট 257. এর গড় বের করার চারটি চেষ্টা দেখুন:
1int total = 257, count = 3;2double avg1 = total / count;3double avg2 = static_cast<double>(total / count);4double avg3 = static_cast<double>(total) / count;5double avg4 = total / 3.0;6int rounded = static_cast<int>(avg3 + 0.5);
| avg1 | |
| avg2 | |
| avg3 | |
| avg4 | |
| rounded |
- শুরু। মোট নম্বর আর ছাত্রসংখ্যা দুটোই
int. সঠিক গড় 85.67 (প্রায়)। - চেষ্টা ১ — ভুল। ডান পাশে আগে হিসাব হয়:
int / int= 85 (দশমিক কাটা গেল)। তারপর সেই 85double-এ বসল — 85.0. বাম পাশেরdoubleদেরিতে এসেছে, ক্ষতি আগেই হয়ে গেছে। - চেষ্টা ২ — cast আছে, তবু ভুল।
static_castপুরো ভাগফলের ওপর বসেছে, অর্থাৎ ভাগের পরে। 85-কে double বানালে 85.0-ই হয়। Cast কোথায় বসছে, সেটাই আসল। - চেষ্টা ৩ — সঠিক। ভাগের আগে
total-কেdoubleকরা হলো: 257.0 / 3. এখন একটিdoubleআছে, তাইcount-ও নিজে থেকে double হয়ে যায়, ভাগ হয় দশমিকে — 85.666… - চেষ্টা ৪ — literal দিয়ে।
3.0একটিdoubleliteral, তাই একই ফল। তবে সংখ্যাটি বসিয়ে দেওয়া মানে আবার magic number —countবদলালে এটা বদলাবে না। চেষ্টা ৩-ই ভালো। - উল্টো দিকে, জেনেশুনে। CGPA বা গড় পূর্ণসংখ্যায় দেখাতে চাইলে
static_cast<int>দশমিক কেটে ফেলে, তাই আগে 0.5 যোগ করে ধনাত্মক সংখ্যা কাছের পূর্ণসংখ্যায় আনা হলো: 86. (<cmath>-এরstd::roundএকই কাজ করে, ঋণাত্মক সংখ্যাতেও ঠিকভাবে।) এখানে narrowing হচ্ছে, কিন্তু লিখিত হুকুমে — পাঠক ও compiler দুজনেই জানেন এটা ইচ্ছাকৃত।
static_cast — C++-এর স্পষ্ট cast
C-তে টাইপ বদলাতে লেখা হতো (double)total. C++-এও এটা চলে, কিন্তু আধুনিক কোডে লেখা হয় static_cast: static_cast<double>(total). লম্বা, একটু বিরক্তিকর — আর সেটাই উদ্দেশ্য। কেন?
- উদ্দেশ্য স্পষ্ট: C-ধাঁচের
(T)xএকাই অনেক রকম কাজ করতে পারে — সংখ্যা বদল,constসরিয়ে ফেলা, এমনকি একটি pointer-কে সম্পূর্ণ অন্য টাইপের pointer বানানো। কোনটা হচ্ছে, লেখা দেখে বোঝা যায় না।static_castশুধু “যুক্তিসংগত” বদলগুলো করে; বিপজ্জনক কাজের জন্য আলাদা নামের cast আছে। - খুঁজে পাওয়া সহজ: বড় প্রজেক্টে সব cast খুঁজতে চাইলে শুধু
static_castলিখে search করুন।(int)খুঁজলে হাজারটা অপ্রাসঙ্গিক জিনিস আসবে। - চোখে পড়ে: প্রতিটি cast একটি ছোট সিদ্ধান্ত — “আমি জানি এখানে টাইপ বদলাচ্ছি।” লম্বা নাম কোড রিভিউতে সেটি চোখে পড়ায়।
এবার পুরো ব্যাপারটি নিজে চালিয়ে দেখুন — ভুল গড়, সঠিক গড়, আর একটি constexpr VAT হার:
#include <print>
int main() {
constexpr double VAT_RATE = 0.15; // 15% VAT, ek jaygay
int total = 89 + 76 + 92; // 257
int count = 3;
double wrong = total / count; // int / int, tarpor double
double right = static_cast<double>(total) / count;
std::println("wrong average = {}", wrong);
std::println("right average = {:.2f}", right);
int price = 1'200; // taka
double vat = price * VAT_RATE;
std::println("price = {}, VAT = {}, total = {}", price, vat, price + vat);
}প্রথম লাইনে আসবে wrong average = 85 — দশমিক নেই। দ্বিতীয় লাইনে 85.67, কারণ ভাগের আগেই total double হয়েছে ({:.2f} মানে দশমিকের পর দুই অঙ্ক)। শেষ লাইনে VAT 180 আর মোট 1380 — VAT_RATE নামটি দেখেই যে কেউ বোঝেন 0.15 কী। 1'200-এর apostrophe শুধু পড়ার সুবিধার জন্য; পরের অংশে দেখবেন। (বাস্তব হিসাবের সফটওয়্যারে টাকা পয়সায় পূর্ণসংখ্যা হিসেবে রাখা হয়; এখানে সহজ রাখতে double নেওয়া হয়েছে।)
Comment-এ বাংলা না লিখে Banglish লেখা হয়েছে, কারণ অনেক অনলাইন compiler-এ বাংলা অক্ষর ভেঙে যায়। নিজের কম্পিউটারে UTF-8 ফাইলে বাংলা comment লিখতে কোনো সমস্যা নেই। বিস্তারিত টাইপ-বদলের নিয়ম (integer promotion, signed-unsigned মেশানো) জানতে C কোর্সের টাইপ রূপান্তর পর্বটি দেখুন — নিয়মগুলো C++-এও একই।
৮ · Literal লেখার আধুনিক উপায় — পড়তে সহজ সংখ্যা
কোডে সরাসরি লেখা মানকে বলে literal — 42, 3.14, 'A', "hello". আধুনিক C++ এগুলো লেখার কয়েকটি সুবিধাজনক উপায় দেয়:
| লেখা | অর্থ | কখন কাজে লাগে |
|---|---|---|
1'000'000 | দশ লাখ — apostrophe শুধু চোখের জন্য, মান বদলায় না (digit separator, C++14) | বড় সংখ্যা: 2'50'000 টাকা বেতন — বাংলাদেশি রীতিতেও লেখা যায় |
0b1010 | বাইনারি, মান 10 (C++14) | বিট নিয়ে কাজ, হার্ডওয়্যার রেজিস্টার |
0xFF | hexadecimal, মান 255 | রং (0xFF8800), মেমরির ঠিকানা |
017 | octal, মান 15 — সাবধান! | শুরুতে 0 দিলে octal হয়; 017 আর 17 আলাদা |
10u | unsigned int | unsigned মানের সাথে তুলনায় |
10LL | long long | বড় গুণফলে overflow এড়াতে: 100000LL * 100000 |
3.5f | float | float ভ্যারিয়েবলে double থেকে narrowing এড়াতে |
"hi"s | std::string (C++14, std::literals) | auto-র সাথে আসল string পেতে |
int big = 100000 * 100000; — দুটি int গুণ হচ্ছে, ফল (১০০০ কোটি) int-এ ধরে না। একে বলে overflow, আর signed পূর্ণসংখ্যার overflow C++-এ undefined behavior. বাম পাশে long long লিখলেও লাভ নেই, কারণ গুণটা আগেই int-এ হয়ে গেছে — ঠিক integer division-এর মতো। সমাধান: অন্তত একটি সংখ্যাকে 100000LL লিখুন। কনটেস্ট প্রোগ্রামিংয়ে এই ভুলে অসংখ্য “Wrong Answer” হয় (পর্ব ৫০)।
sizeof(int)কত? — standard নির্দিষ্ট করে না; অন্তত ১৬ বিট, আজকের বেশিরভাগ সিস্টেমে ৪ বাইট। শুধুsizeof(char)সবসময় ১.7 / 2আর7 / 2.0-এর মান? — প্রথমটি3(integer division), দ্বিতীয়টি3.5.constআরconstexpr-এর পার্থক্য? —constমানে বদলানো যাবে না;constexprমানে মান compile-এর সময়েই জানা (এবং সেটি const-ও)।int x{3.7};কেন compile হয় না? — brace initialization narrowing conversion নিষেধ করে;int x = 3.7;হলে নিঃশব্দে 3 হতো।auto s = "hello";-এs-এর টাইপ? —const char*;std::stringপেতে"hello"sবাstd::string s = "hello";
৯ · AI-এর যুগে — টাইপের ভুল AI-ও করে
AI টুল কোড লেখে দ্রুত, আর দেখতেও সুন্দর। কিন্তু টাইপের ভুলগুলো এমন যে কোড compile হয়, চলেও — শুধু উত্তর ভুল আসে, বা কোনো এক বিরল ইনপুটে গিয়ে ভাঙে। তাই এগুলো ধরার দায়িত্ব আপনার চোখের। AI-এর লেখা C++ কোডে বারবার যে ভুলগুলো দেখা যায়:
| AI যা করে | সমস্যা | যা হওয়া উচিত |
|---|---|---|
সব জায়গায় auto | টাকা বা নম্বরের টাইপ বোঝা যায় না; auto x = 10 / 4;-এর মতো ভুল লুকিয়ে থাকে | গুরুত্বপূর্ণ মানে টাইপ নিজে লেখা |
C-ধাঁচের (int)x cast | কী ধরনের বদল হচ্ছে বোঝা যায় না, খুঁজেও পাওয়া যায় না | static_cast<int>(x) |
int sum; লিখে পরে যোগ | আবর্জনা মান থেকে যোগ শুরু — undefined behavior | int sum{}; বা int sum{0}; |
টাকার হিসাব float-এ | ৭ অঙ্কের নির্ভুলতা; বড় অঙ্কে পয়সা হারায় | পয়সায় std::int64_t, অন্তত double |
গড় বের করতে sum / n (দুটোই int) | দশমিক হারায় | static_cast<double>(sum) / n |
কোডে ছড়ানো 0.15, 100 | magic number — অর্থ অস্পষ্ট, বদলানো কঠিন | constexpr নামযুক্ত ধ্রুবক |
AI-কে কোড লিখতে বলার সময় টাইপের নিয়মগুলো আগেই বলে দিন। তাহলে ভুল কম আসে, আর যা আসে তা যাচাই করা সহজ হয়:
AI-এর কোডে টাইপ যাচাইয়ের চেকলিস্ট
১০ · মডিউল ১ শেষ — আপনি এখন কোথায়
অভিনন্দন! এই পর্বের সাথে কোর্সের প্রথম মডিউল “ভিত্তি — C++ দিয়ে ভাবা” শেষ হলো। পাঁচটি পর্বে আপনি কী কী জোগাড় করলেন, একবার ফিরে দেখা যাক:
| পর্ব | বিষয় | এখন আপনি যা পারেন |
|---|---|---|
| ০১ | C++ কেন? | বলতে পারেন C++ কোথায় ব্যবহার হয়, C থেকে কেন আলাদা, আর আধুনিক C++ বলতে কী বোঝায় |
| ০২ | বস্তু দিয়ে ভাবা | একটি সমস্যাকে দায়িত্বওয়ালা টুকরোয় ভাঙতে পারেন — কোড লেখার আগেই |
| ০৩ | টুলচেইন | g++ দিয়ে compile করতে পারেন, error দেখে বুঝতে পারেন সমস্যা compile-এর না link-এর |
| ০৪ | প্রথম প্রোগ্রাম | main, #include, namespace আর আউটপুট — প্রতিটি লাইনের অর্থ জানেন |
| ০৫ | টাইপ, auto আর const | সঠিক টাইপ বেছে নিরাপদে ভ্যারিয়েবল বানাতে পারেন, auto-র ফাঁদ আর integer division চেনেন |
এবার সেই ডেটা দিয়ে কাজ করার পালা।
মডিউল ২-এ আপনি ভাষার প্রতিদিনের যন্ত্রপাতি হাতে নেবেন — অপারেটর, ইনপুট-আউটপুট, সিদ্ধান্ত, লুপ আর ফাংশন। আজকের টাইপের জ্ঞান সেখানে প্রতি লাইনে লাগবে: কোন অপারেটর কোন টাইপে কী ফল দেয়, cin দিয়ে নেওয়া মান কোন বাক্সে যায়, ফাংশন কোন টাইপ নেয় আর ফেরত দেয়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১C++ কেন int-এর আকার ঠিক করে দেয়নি? Java যেমন বলে দেয় int সবসময় ৩২ বিট — C++ সেটা করল না কেন?
উত্তর লুকিয়ে আছে C-এর জন্মের সময়ের কম্পিউটারে। ১৯৭০-এর দশকে কম্পিউটারের “স্বাভাবিক” শব্দের আকার একরকম ছিল না। C যে PDP-11-এ জন্মায়, সেখানে int ছিল ১৬ বিট। একই সময়ে PDP-10 বা Honeywell-এর মতো কিছু মেশিনে শব্দ ছিল ৩৬ বিটের, আর কোথাও কোথাও এক বাইট ৯ বিটের। C-এর লক্ষ্য ছিল একটি অপারেটিং সিস্টেম (Unix) সব রকম মেশিনে দ্রুত চালানো।
তাই C-এর নকশাকাররা নিয়ম করলেন: int হবে সেই মেশিনের সবচেয়ে স্বাভাবিক, দ্রুততম পূর্ণসংখ্যা — শুধু ন্যূনতম সীমা (১৬ বিট) বেঁধে দেওয়া হলো। ৩৬-বিটের মেশিনে ৩২ বিটের হিসাব জোর করে করাতে হলে প্রতিটি যোগে বাড়তি নির্দেশ লাগত, প্রোগ্রাম ধীর হতো। C++ C-এর সাথে সামঞ্জস্য রাখতে এই নিয়মই নিল।
Java ১৯৯৫ সালে এসেছিল সম্পূর্ণ অন্য দর্শন নিয়ে — “একবার লেখো, সব জায়গায় চালাও।” Java চলে একটি virtual machine-এ, তাই বাস্তব হার্ডওয়্যার যা-ই হোক, ভাষা নিজের নিয়ম চাপিয়ে দিতে পারে। ততদিনে প্রায় সব কম্পিউটার ৮-বিট বাইট আর ৩২-বিট হিসাবে এক হয়ে গিয়েছিল, তাই দামটাও কম ছিল।
এই নমনীয়তার দাম এখনো দিতে হয়। ৩২ থেকে ৬৪ বিটে যাওয়ার সময় Linux বেছে নিল LP64 (long = ৬৪ বিট), Windows বেছে নিল LLP64 (long = ৩২ বিট) — যাতে পুরোনো Windows কোড না ভাঙে। ফলে long-এর ওপর ভরসা করা কোড এক OS থেকে আরেক OS-এ নিলে ভুল ফল দিতে পারে। এই সমস্যার সমাধান হিসেবেই C99-এ <stdint.h> আসে, যা C++11-এ <cstdint> হয় — std::int32_t সবখানে ঠিক ৩২ বিট।
আজও এমন ছোট microcontroller আছে (যেমন কিছু ৮-বিট বা ১৬-বিট চিপ), যেখানে int ১৬ বিট। গাড়ি, ওয়াশিং মেশিন বা মেডিকেল যন্ত্রের firmware এমন চিপে চলে, আর C/C++ সেখানে প্রধান ভাষা। আকার বেঁধে দিলে এসব জায়গায় C++ ধীর হয়ে যেত।
মূল উপলব্ধি: ভাষার নকশা একটি trade-off — portable গতি বনাম নিশ্চিত আকার। C++ গতি বেছে নিয়েছে, আর নিশ্চয়তা যাদের দরকার, তাদের জন্য <cstdint> রেখেছে। আকার যেখানে চুক্তির অংশ, সেখানে fixed-width টাইপ ব্যবহার করুন।
প্র ০২C++11-এ brace initialization কেন আনা হলো? “Uniform” বলা হলেও std::vector<int>{5} আর std::vector<int>(5) আলাদা কেন?
C++11-এর আগে ভ্যারিয়েবল বানানোর নিয়ম ছিল জগাখিচুড়ি। int-এ = বা (), array-তে = {1, 2, 3}, class-এ constructor-এর (), আর কিছু জিনিসে কোনোভাবেই প্রথম মান দেওয়া যেত না (যেমন std::vector-এ সরাসরি উপাদানের তালিকা)। নতুনদের জন্য এটা ছিল মুখস্থের বোঝা, আর অভিজ্ঞদের জন্য বাগের উৎস।
Bjarne Stroustrup ও অন্যরা প্রস্তাব করলেন একটি “uniform” লেখা — {} — যা সব জায়গায় চলবে। সাথে তিনটি বাড়তি সুবিধা এল: (১) narrowing নিষেধ, যাতে int x{3.7} compile-ই না হয়; (২) most vexing parse-এর অবসান, কারণ Widget w{}; কখনো ফাংশন ঘোষণা হতে পারে না; (৩) খালি {}-তে নিশ্চিত শূন্য।
কিন্তু একই সময়ে আনা হলো std::initializer_list, যাতে std::vector<int> v{1, 2, 3}; লেখা যায়। নিয়ম হলো — brace দেখলে compiler আগে initializer_list নেয় এমন constructor খোঁজে, পেলে সেটাকেই অগ্রাধিকার দেয়। তাই std::vector<int>{5} মানে “৫ মান-ওয়ালা একটি উপাদানের তালিকা”, আর std::vector<int>(5) মানে “৫টি উপাদান, সব শূন্য।” “Uniform” লেখা এখানে এসে অসম হয়ে গেল।
আরেকটি ঝামেলা ছিল auto-র সাথে: শুরুতে auto x{5}; বানাত std::initializer_list<int>, int নয়! এটা এত বিভ্রান্তিকর ছিল যে C++17-এ নিয়ম বদলে auto x{5};-কে int করা হয় (আর auto x = {5}; এখনো initializer_list). Scott Meyers-এর মতো বিশেষজ্ঞরা বইয়ে এই সূক্ষ্মতা নিয়ে আলাদা অধ্যায় লিখেছেন।
তাহলে বাস্তবে কী করবেন? সাধারণ ভ্যারিয়েবল আর নিজের struct-এ {} — narrowing ধরা আর শূন্য দিয়ে শুরু করার জন্য। Container-এ যখন “এতগুলো উপাদান” বোঝাতে চান, তখন (). সন্দেহ হলে ডকুমেন্টেশন দেখুন।
মূল উপলব্ধি: পুরোনো ভাষায় নতুন নিয়ম যোগ করলে সেটা পুরোনো নিয়মের সাথে মিশে নতুন কোণা তৈরি করে। C++-এর অনেক জটিলতা আসলে “পুরোনো কোড না ভেঙে উন্নতি” করার দাম। ভালো প্রোগ্রামার নিয়মের পাশাপাশি তার ব্যতিক্রমগুলোও চেনেন।
প্র ০৩auto শব্দটি C-তে অন্য অর্থে ছিল — C++11 কেন নতুন শব্দ না এনে এটিকেই নতুন কাজ দিল? আর auto কি কোডকে “কম typed” করে ফেলে?
C-তে প্রতিটি ভ্যারিয়েবলের একটি storage class থাকে: auto (ফাংশনের ভেতরে স্বয়ংক্রিয়ভাবে জন্মায়-মরে), static, register, extern. ফাংশনের ভেতরের ভ্যারিয়েবল ডিফল্টভাবেই auto, তাই auto int x; লেখা আর int x; লেখা একই। ফলে বাস্তব কোডে শব্দটি প্রায় কেউ লিখতেন না।
ভাষায় নতুন keyword আনা খুব ব্যয়বহুল। ধরুন C++11 টাইপ অনুমানের জন্য var শব্দটি keyword বানাত। পৃথিবীতে লাখ লাখ প্রোগ্রামে নিশ্চয়ই var নামে ভ্যারিয়েবল বা ফাংশন আছে — সবগুলো রাতারাতি compile error দিত। কিন্তু auto আগে থেকেই keyword, কেউ এ নামে কিছু রাখতে পারতেন না, আর এর পুরোনো ব্যবহার প্রায় শূন্য। তাই এটিকে নতুন অর্থ দিলে প্রায় কিছুই ভাঙবে না। কমিটি ঠিক এই হিসাব করেছিল। Bjarne Stroustrup জানিয়েছেন, তিনি C++-এর একেবারে শুরুর দিকে (১৯৮০-র দশকে) এই ধরনের টাইপ অনুমান তৈরি করেও C-এর সাথে সামঞ্জস্যের কারণে সরিয়ে নিতে বাধ্য হয়েছিলেন — অর্থাৎ ধারণাটি প্রায় তিন দশক অপেক্ষা করেছে।
মজার ব্যাপার, পরে C নিজেও এই পথে হেঁটেছে: C23-এ সীমিত আকারে auto দিয়ে টাইপ অনুমান যোগ হয়েছে।
এবার দ্বিতীয় প্রশ্ন — auto কি টাইপ-নিরাপত্তা কমায়? না। auto দিয়ে বানানো ভ্যারিয়েবলেরও compile-এর সময় একটি নির্দিষ্ট টাইপ থাকে, আর সেটা আর বদলায় না। Python বা JavaScript-এর মতো চলার সময় টাইপ বদলানো এখানে হয় না। যা কমে তা হলো পাঠকের কাছে তথ্য — টাইপটা চোখে দেখা যায় না, IDE-তে মাউস রেখে দেখতে হয়।
তাই বিশেষজ্ঞদের মধ্যে বিতর্ক আছে। Herb Sutter-এর মতো কেউ “প্রায় সবসময় auto” (AAA) পরামর্শ দিয়েছেন — কারণ এতে খালি ভ্যারিয়েবল অসম্ভব, আর অপ্রত্যাশিত টাইপ বদল কমে। অন্যরা বলেন, টাইপ লেখা থাকলে কোড রিভিউ সহজ। Google-এর C++ style guide মাঝামাঝি পথ নেয় — auto তখনই, যখন এটা কোডকে পড়তে সহজ করে।
মূল উপলব্ধি: auto টাইপ সরায় না, শুধু লেখা থেকে লুকায়। ব্যবহার করুন যখন টাইপ স্পষ্ট বা খুব লম্বা; এড়িয়ে চলুন যখন টাইপটাই পাঠকের জানা সবচেয়ে জরুরি তথ্য।
প্র ০৪গেমে float, বিজ্ঞানে double — কেন? নির্ভুলতার অভাব বাস্তবে কতটা বিপজ্জনক হতে পারে?
float ৪ বাইটে প্রায় ৭ দশমিক অঙ্ক নির্ভুল রাখে, double ৮ বাইটে প্রায় ১৫–১৬ অঙ্ক। প্রশ্ন হলো — বাড়তি নির্ভুলতার দাম কত, আর কোথায় সেটা দরকার।
গেম ও গ্রাফিক্স: একটি দৃশ্যে লাখ লাখ বিন্দু (vertex), প্রতিটির x, y, z, রং, আলো — প্রতি সেকেন্ডে ৬০ বার হিসাব। float নিলে মেমরি অর্ধেক লাগে, cache-এ দ্বিগুণ ডেটা ধরে, আর GPU সাধারণত ৩২-বিট দশমিকে অনেক দ্রুত হিসাব করে (অনেক সাধারণ গ্রাফিক্স কার্ডে ৬৪-বিট হিসাবের গতি বহুগুণ কম)। চোখে একটি পিক্সেলের ভগ্নাংশ ভুল ধরা পড়ে না। তাই গেমে float স্বাভাবিক পছন্দ। তবে বিশাল খোলা-জগতের গেমে বিপদ আছে — কেন্দ্র থেকে অনেক দূরে গেলে float-এর নির্ভুলতা কমে, বস্তু কাঁপতে থাকে। অনেক ইঞ্জিন তাই জগতের কেন্দ্র খেলোয়াড়ের সাথে সরিয়ে নেয়।
বিজ্ঞান ও প্রকৌশল: আবহাওয়ার পূর্বাভাস, উপগ্রহের কক্ষপথ বা ওষুধের অণুর simulation-এ লাখো ধাপ পরপর হিসাব হয়। প্রতি ধাপের ছোট ভুল জমতে জমতে বড় হয়। এখানে double ডিফল্ট, কখনো তারও বেশি।
বাস্তব দুর্ঘটনা: ১৯৯১ সালের উপসাগরীয় যুদ্ধে একটি Patriot ক্ষেপণাস্ত্র-প্রতিরক্ষা ব্যবস্থা সময় গুনত ০.১ সেকেন্ড এককে, আর ০.১-কে বাইনারিতে সঠিকভাবে রাখা যায় না (সেখানে ২৪-বিটের fixed-point রাখা হতো)। একটানা প্রায় ১০০ ঘণ্টা চলার পর ভুল জমে দাঁড়ায় প্রায় এক-তৃতীয়াংশ সেকেন্ড — একটি দ্রুতগামী ক্ষেপণাস্ত্র ততক্ষণে অনেক দূর চলে যায়। ব্যবস্থাটি আসন্ন একটি Scud ক্ষেপণাস্ত্র ধরতে ব্যর্থ হয়, আর ২৮ জন সৈন্য নিহত হন। টাইপ ছিল ভিন্ন, কিন্তু মূল শিক্ষা একই — সীমিত বিটে দশমিক রাখলে ভুল জমে।
টাকা: এখানে float বা double কোনোটিই আদর্শ নয়, কারণ ০.১০ টাকাও বাইনারিতে সঠিক নয়। কোটি লেনদেনে ভগ্নাংশ-পয়সার ভুল হিসাব মেলায় না। তাই ব্যাংকিং সফটওয়্যার পয়সায় পূর্ণসংখ্যা বা বিশেষ decimal টাইপ ব্যবহার করে।
মূল উপলব্ধি: “কোনটা ভালো” নয়, প্রশ্ন হলো “এই হিসাবে কত অঙ্ক নির্ভুল দরকার, আর কত বার ভুল জমবে?” সন্দেহ হলে double; গতি আর মেমরি যেখানে আসল সীমা, সেখানে মেপে তবেই float; আর টাকায় পূর্ণসংখ্যা।
প্র ০৫C++20 কেন signed পূর্ণসংখ্যার জন্য two's complement বাধ্যতামূলক করল? তাহলে overflow এখনো undefined behavior কেন?
ঋণাত্মক সংখ্যা বিটে রাখার তিনটি পরিচিত পদ্ধতি আছে: sign-magnitude (প্রথম বিট চিহ্ন, বাকিটা মান), ones' complement (সব বিট উল্টানো) আর two's complement (সব বিট উল্টে ১ যোগ)। প্রথম দুটিতে একটি অদ্ভুত সমস্যা — শূন্য দুই রকম, +0 আর −0. আর যোগ-বিয়োগের সার্কিটও জটিল হয়। Two's complement-এ শূন্য একটিই, আর ধনাত্মক-ঋণাত্মক দুটোর জন্য একই যোগের সার্কিট কাজ করে।
১৯৬০-৭০-এর দশকে তিন পদ্ধতিরই কম্পিউটার বাজারে ছিল (যেমন কিছু CDC আর UNIVAC মেশিন ones' complement ব্যবহার করত)। তাই C আর C++ standard তিনটিকেই অনুমতি দিত। ফলে অনেক সাধারণ প্রশ্নের উত্তর standard-এ “implementation-defined” ছিল — যেমন -1-কে unsigned-এ বদলালে বিট কেমন দেখাবে, বা ডানে shift করলে চিহ্ন-বিট কী হবে।
কিন্তু কয়েক দশক ধরে কার্যত সব জনপ্রিয় CPU — Intel, AMD, ARM, RISC-V — two's complement. ২০১৮ সালে JF Bastien একটি প্রস্তাব (P0907, “Signed Integers are Two's Complement”) দেন: বাস্তবে যা সবাই করে, standard-এ সেটাই লিখে দেওয়া হোক। আলোচনায় দেখা গেল ভিন্ন পদ্ধতির মেশিন প্রায় শুধু ইতিহাসে আছে, আর তাদের জন্য আধুনিক C++ compiler-ও নেই। C++20-এ প্রস্তাবটি গৃহীত হয়, পরে C23-ও একই পথ নেয়। এর ফলে আগের অনিশ্চয়তাগুলো দূর হয় — যেমন বড় মান ছোট signed টাইপে বদলালে (উপরের 200 → −56) এখন নিয়মমাফিক মডুলার ফল আসে।
তাহলে overflow কেন এখনো undefined? মূল প্রস্তাবে overflow-কেও “wrap around” করার কথা ছিল, কিন্তু কমিটি তা নেয়নি। কারণ optimizer এই নিয়মের ওপর নির্ভর করে। যেমন i + 1 > i — overflow অসম্ভব ধরলে compiler একে সরাসরি true বানাতে পারে, আর লুপে int সূচককে নিশ্চিন্তে ৬৪-বিট রেজিস্টারে বড় করে দ্রুত কোড বানাতে পারে। Wrap বাধ্যতামূলক হলে এই optimization হারাত। তাছাড়া overflow প্রায় সবসময়ই প্রোগ্রামারের ভুল — undefined থাকায় sanitizer (-fsanitize=undefined) একে বাগ হিসেবে ধরতে পারে। Wrap চাইলে unsigned টাইপ আছে, যেখানে overflow নিয়মমাফিক মডুলার।
মূল উপলব্ধি: ভালো standard বাস্তবতাকে স্বীকৃতি দেয়, কিন্তু যেখানে অনিশ্চয়তা দিয়ে গতি বা ভুল-ধরার সুবিধা কেনা যায়, সেখানে তা জেনেশুনে রেখে দেয়। Undefined behavior কোনো অবহেলা নয়, একটি নকশার সিদ্ধান্ত — পর্ব ৪৩-এ এ নিয়ে বিস্তারিত।
অনুশীলন
বেশিরভাগ কাজে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর উত্তর মিলিয়ে নিন।
-
টাইপ বেছে নিন: নিচের প্রতিটির জন্য সবচেয়ে উপযুক্ত টাইপ বলুন ও কারণ দিন — (ক) একটি ক্লাসের ছাত্রসংখ্যা (খ) একজনের CGPA (গ) ছাত্রটি উপস্থিত কি না (ঘ) একটি bKash অ্যাকাউন্টের ব্যালান্স (ঙ) একটি নেটওয়ার্ক প্যাকেটে ঠিক ৪ বাইটের একটি আইডি।
(ক)
int— পূর্ণসংখ্যা, সাধারণ আকার। (খ)double— দশমিক দরকার, নির্ভুলতা যথেষ্ট। (গ)bool— শুধু হ্যাঁ/না। (ঘ) পয়সায় হিসাব করেstd::int64_t— float/double-এ পয়সা হারাতে পারে, আর ৩২ বিট বড় অঙ্কে যথেষ্ট নাও হতে পারে। (ঙ)std::uint32_t— আকার চুক্তির অংশ, তাই fixed-width; আইডি ঋণাত্মক হয় না, তাই unsigned. -
auto অনুমান: প্রতিটির টাইপ ও মান বলুন —
auto a = 7 / 2;,auto b = 7 / 2.0;,auto c = 'A' + 1;,auto d = 3.5f * 2;a:int, 3 (integer division).b:double, 3.5.c:int, 66 — অবাক হলেন? হিসাবেcharপ্রথমেint-এ উন্নীত হয় (integer promotion), তাই'B'নয়, সংখ্যা 66.d:float, 7 — float আর int মিশলে float. -
বাগ শিকার: একটি AI এই কোড দিয়েছে —
int sum; for (...) sum += marks; float avg = sum / n;(nএকটি int). কয়টি টাইপ-সংক্রান্ত সমস্যা খুঁজে পান? ঠিক করে লিখুন।তিনটি: (১)
suminitialize করা হয়নি — আবর্জনা মান থেকে যোগ শুরু, undefined behavior. (২)sum / nদুটোই int — দশমিক হারায়। (৩)floatঅপ্রয়োজনীয়ভাবে কম নির্ভুল। ঠিক রূপ:int sum{};…double avg = static_cast<double>(sum) / n;(আরnশূন্য হলে কী হবে, সেটাও ভাবুন!) -
নিজে মেপে দেখুন (কোড): উপরের
sizes.cpp-এfloat,long doubleআরstd::int16_t-এর লাইন যোগ করুন। তারপর একই কোড নিজের কম্পিউটারে (বিশেষ করে Windows-এ) চালিয়েlong-এর আকার মিলিয়ে দেখুন।একটি লাইন যেমন:
std::println("float : {} bytes, max = {}", sizeof(float), std::numeric_limits<float>::max());. 64-bit Linux-এ সাধারণত float ৪, long double ১৬, int16_t ২ বাইট। Windows-এ MSVC দিয়ে চালালেlong৪ বাইট আরlong double৮ বাইট দেখাবে; MSYS2-এর GCC-তে long double ভিন্ন হতে পারে। শিক্ষা: আকার ধরে নেবেন না, মেপে দেখুন — বা fixed-width টাইপ নিন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএই fixed-width টাইপগুলোকে সবচেয়ে ছোট থেকে সবচেয়ে বড় বাক্স অনুযায়ী সাজান (▲▼ চেপে সরান)
std::int8_t— ১ বাইটstd::int16_t— ২ বাইটstd::int32_t— ৪ বাইটstd::int64_t— ৮ বাইট
C++-এ int marks{89.5}; লিখলে কী হয়?
- marks-এ 89 থাকে, কোনো বার্তা আসে না
- Compile error — narrowing conversion
- marks-এ 90 থাকে (round হয়ে)
- marks নিজে থেকে double হয়ে যায়
C++-এ auto s = "hello"; লিখলে s-এর টাইপ কী?
- std::string
- char
- const char*
- compile error, কারণ auto লেখার টাইপ বোঝে না
int total = 257, count = 3; হলে double avg = total / count;-এ avg-এর মান কত?
- 85.0
- 85.666…
- 86.0
- Compile error
C++-এ const আর constexpr-এর মধ্যে সঠিক পার্থক্য কোনটি?
- const বদলানো যায় না, constexpr বদলানো যায়
- const শুধু int-এ চলে, constexpr শুধু double-এ
- দুটি পুরোপুরি এক, শুধু নাম আলাদা
- const মানে বদলানো যাবে না; constexpr মানে মান compile-এর সময়েই জানা
C++ standard অনুযায়ী কোন টাইপের আকার সব সিস্টেমে নিশ্চিতভাবে ১ বাইট?
- bool
- char
- int
- short
আধুনিক C++-এ একটি double মান x-কে জেনেশুনে int-এ বদলানোর পছন্দের লেখা কোনটি?
- (int)x
- int y{x};
- static_cast<int>(x)
- auto y = x;
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- ভ্যারিয়েবল = নাম + টাইপ + মান; টাইপ ঠিক করে আকার, সীমা আর কোন কাজ চলবে। আকার নিশ্চিত দরকার হলে
std::int32_t-এর মতো fixed-width টাইপ। - বানানোর সময়েই মান দিন,
{}দিয়ে — narrowing ধরা পড়ে, খালি{}মানে শূন্য, আবর্জনা মানের বিপদ নেই। autoডান পাশের টাইপ হুবহু নেয় —"hi"হলোconst char*,10 / 4হলোint2.- বদলাবে না এমন মান
const, compile-এর সময় জানা হলেconstexpr— magic number নয়। int / intদশমিক হারায়; ভাগের আগেstatic_cast<double>. C-ধাঁচের(int)xনয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ০৬ · অপারেটর — হিসাব, তুলনা, যুক্তিপরবর্তী পর্বমডিউল ২-এর শুরু: টাইপের ওপর কোন অপারেটর কী করে।
- C++ প্রোগ্রামিং · কোর্স হোম৫৫টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- C প্রোগ্রামিং · ডেটা কীভাবে রাখা হয়গভীরে যানবিট, two's complement আর IEEE 754 floating point বিস্তারিত।
- C প্রোগ্রামিং · টাইপ রূপান্তরপাশের ট্র্যাকinteger promotion আর signed-unsigned মেশানোর পূর্ণ নিয়ম।