Lesson ১১পড়তে ৮ মিনিট লাগবে

পয়েন্টার এবং রেফারেন্স (Pointers & References)

জিপিএস কো-অর্ডিনেটগুলো (GPS coordinates) বনাম ডাকনাম (nicknames) — একই ডেটায় (data) পৌঁছানোর দুটি ভিন্ন উপায়

জিপিএস (GPS) এবং ডাকনাম (Nickname)

ধরুন আপনি ৪২ এলম স্ট্রিটে (42 Elm Street) থাকেন। একটি পয়েন্টার (pointer) হলো অনেকটা ওই ঠিকানাটিকে (address) কোনো একটি স্টিকি নোটে (sticky note) লিখে রাখার মতো — এটি কখনোই আপনার বাড়িটিকে নিজের ভেতরে ধারণ করে না (contain), এটি শুধু অন্য কাউকে বলে দেয় যে আপনাকে ঠিক কোথায় পাওয়া যাবে (where to find it)। অন্যদিকে একটি রেফারেন্স (reference) হলো অনেকটা আপনার কোনো একটি ডাকনামের (nickname) মতো — যদি আপনার বন্ধুরা আপনাকে "অ্যালেক্সান্ডার" নামের পরিবর্তে শুধু "অ্যাল" নামে ডাকে, তবে তারা কিন্তু ওই একই মানুষটির কথাই বলছে। এক্ষেত্রে আপনার ওই জিপিএসের মতো কোনো ঠিকানা বা অ্যাড্রেসের (address) প্রয়োজন নেই, শুধু আরেকটি নামের (another name) প্রয়োজন।

সি++ (C++)-এর ক্ষেত্রে মেমোরির সাথে সরাসরি কাজ করার জন্য মূলত এই দুটি ধারণা — তথা পয়েন্টার (pointers) এবং রেফারেন্সই (references) — হলো এর প্রধান ভিত্তি বা ফাউন্ডেশন (foundation)। চলুন এগুলো সম্পর্কে আরও বিস্তারিত জেনে নেওয়া যাক।

র পয়েন্টার (Raw Pointers): * এবং &

পয়েন্টার (pointer) হলো এমন এক ধরনের ভ্যারিয়েবল (variable) যা মূলত মেমোরির একটি ঠিকানা বা অ্যাড্রেসকে (memory address) স্টোর করে বা সংরক্ষণ করে রাখে। আপনি চাইলে * দিয়ে একটি পয়েন্টার ডিক্লেয়ার বা ঘোষণা (declare) করতে পারেন এবং & দিয়ে তার মেমোরির ঠিকানাটি (address) পেতে পারেন।

  • int* p — "এখানকার p হলো একটি int-এর পয়েন্টার (pointer to an int)"
  • &x — "এটির মানে হলো x-এর ঠিকানা বা address"
  • *p — "p-তে থাকা ওই ঠিকানার ভেতরের আসল মানটি বা value" (একে ডি-রেফারেন্সিং বা dereferencing বলা হয়)

এক্ষেত্রে আপনি &-কে "আপনি কোথায় থাকেন (where do you live)?" জিজ্ঞেস করা হিসেবে এবং *-কে "আমাকে আপনার ওই ঠিকানাটিতে যেতে দিন (let me visit that address)" বলা হিসেবে চিন্তা করতে পারেন।

বেসিক পয়েন্টার বা Pointer Basics

#include <iostream>
using namespace std;
int main() {
int x = 42;
int* p = &x; // p মূলত x-এর মেমোরি ঠিকানাটিকে (address) সংরক্ষণ করে বা stores
cout << "x = " << x << endl;
cout << "&x = " << &x << endl; // x-এর মেমোরি ঠিকানা বা address
cout << "p = " << p << endl; // একই মেমোরি ঠিকানা বা same address
cout << "*p = " << *p << endl; // ডি-রেফারেন্স (dereference): ওই ঠিকানাটিতে বা address-এ থাকা আসল মান বা value
*p = 99; // পয়েন্টারের (pointer) মাধ্যমে x-কে মডিফাই (modify) করা
cout << "x after *p = 99: " << x << endl; // *p = 99-এর পর x-এর মান:
return 0;
}
Output
x  = 42
&x = 0x7ffeeb24a8dc
p  = 0x7ffeeb24a8dc
*p = 42
x after *p = 99: 99

রেফারেন্স (References): একই জিনিসের অন্য নাম (Another Name for the Same Thing)

একটি রেফারেন্সকে (reference) মূলত & চিহ্নের সাহায্যে ঘোষণা (declared) করা হয় (হ্যাঁ, এটি ওই একই বা same symbol — তবে এখানে কন্টেক্সট বা context ভিন্ন)। একবার এটি কোনো কিছুর সাথে যুক্ত (bound) হয়ে গেলে, এর ওই রেফারেন্সটিই (reference) মূলত তার আসল বা মূল ভ্যারিয়েবল (original variable) হয়ে যায়। তাই এখানে পরিচালনা করার মতো কোনো আলাদা মেমোরি ঠিকানা (address) বা ডি-রেফারেন্সিংয়ের (dereferencing) প্রয়োজন পড়ে না।

বেসিক রেফারেন্স (Reference Basics)

#include <iostream>
using namespace std;
int main() {
int x = 42;
int& ref = x; // ref হলো মূলত x-এর আরেকটি নাম বা another name
cout << "x = " << x << endl;
cout << "ref = " << ref << endl;
ref = 100; // ref-কে মডিফাই (modifying) করলে তা মূলত x-কেই মডিফাই করে
cout << "x after ref = 100: " << x << endl; // ref = 100-এর পর x-এর মান:
x = 7;
cout << "ref after x = 7: " << ref << endl; // x = 7-এর পর ref-এর মান:
return 0;
}
Output
x   = 42
ref = 42
x after ref = 100: 100
ref after x = 7:   7

পয়েন্টার ভার্সন (Pointer Version) বনাম (vs) রেফারেন্স ভার্সনের (Reference Version) অদলবদল (Swap)

কোনো কিছুর অদলবদল করা বা সোয়াপ (swap) করার একটি সাধারণ বা ক্লাসিক (classic) ফাংশন মূলত এই দুই ভার্সনের পার্থক্যটিকে খুব সুন্দরভাবে ফুটিয়ে তোলে। পয়েন্টারের (pointers) ক্ষেত্রে আপনাকে এর ঠিকানাগুলোকে (addresses) পাস (pass) করতে হয় এবং ডি-রেফারেন্স (dereference) করতে হয়। কিন্তু রেফারেন্সের (references) ক্ষেত্রে এর সিনট্যাক্সটি (syntax) অনেক বেশি ক্লিনার (cleaner) বা পরিষ্কার — এখানকার কম্পাইলারটিই (compiler) আপনার জন্য এর ভেতরের বিভিন্ন অপ্রত্যক্ষ (indirection) কাজগুলোকে সামলে (handles) নেয়।

পয়েন্টার (Pointers) বনাম (vs.) রেফারেন্সের (References) অদলবদল (Swap)

#include <iostream>
using namespace std;
// পয়েন্টার ভার্সন (Pointer version): कॉলারকে (caller) অবশ্যই এর ঠিকানাগুলো বা addresses পাস (pass) করতে হবে
void swapPtr(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// রেফারেন্স ভার্সন (Reference version): তুলনামূলক ক্লিনার সিনট্যাক্স (cleaner syntax), কিন্তু কাজ একই (same effect)
void swapRef(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 1, y = 2;
swapPtr(&x, &y); // এক্ষেত্রে অবশ্যই (must) &x এবং &y পাস (pass) করতে হবে
cout << x << " " << y << endl;
int a = 10, b = 20;
swapRef(a, b); // এক্ষেত্রে শুধু a এবং b পাস (pass) করলেই হবে
cout << a << " " << b << endl;
return 0;
}
Output
2 1
20 10

nullptr — এটি কোনো সাধারণ NULL নয় (Not NULL)

আধুনিক সি++ (C++)-এর ক্ষেত্রে NULL বা 0-এর পরিবর্তে সব সময় nullptr ব্যবহার করুন। nullptr মূলত টাইপের দিক থেকে অত্যন্ত নিরাপদ (type-safe) — এটি নির্দিষ্টভাবে একটি নাল (null) পয়েন্টার, যেখানে NULL-টি হলো মূলত ছদ্মবেশধারী (wearing a disguise) একটি সাধারণ ইন্টিজার (integer) 0, যা বিভিন্ন ফাংশন ওভারলোডে (function overloads) অস্পষ্টতার (ambiguity) কারণ হতে পারে।

nullptr চেক (Check) করা

#include <iostream>
using namespace std;
void process(int* data) {
if (data == nullptr) {
cout << "No data — pointer is null!" << endl; // এখানে কোনো ডেটা নেই (No data) — পয়েন্টারটি নাল (null)!
return;
}
cout << "Processing: " << *data << endl; // প্রসেসিং বা Processing:
}
int main() {
int value = 42;
process(&value); // এটি একটি ভ্যালিড (valid) বা সঠিক পয়েন্টার
process(nullptr); // এটি একটি নাল (null) পয়েন্টার
return 0;
}
Output
Processing: 42
No data — pointer is null!

কনস্ট্যান্ট (const) পয়েন্টার বনাম (vs.) পয়েন্টার টু কনস্ট্যান্ট (Pointer to const)

এই বিষয়টি প্রথমদিকে সব সময়ই আপনাকে দ্বন্দ্বে ফেলে দিতে পারে। তাই এর ডিক্লারেশন বা ঘোষণাকে (declaration) সব সময় ডান থেকে বাম দিকে (right to left) পড়ার চেষ্টা করুন:

  • const int* p — এটি একটি কনস্ট্যান্ট ইন্ট বা const int-এর দিকে নির্দেশ করা একটি পয়েন্টার (আপনি এখানকার মানটি বা value পরিবর্তন করতে পারবেন না, শুধু পয়েন্টারটিকে অন্য কোনো ঠিকানায় পরিবর্তন বা change করতে পারবেন)
  • int* const p — এটি একটি ইন্ট বা int-এর দিকে নির্দেশ করা একটি কনস্ট্যান্ট বা const পয়েন্টার (আপনি এখানকার মানটি বা value পরিবর্তন করতে পারবেন, কিন্তু পয়েন্টারটিকে অন্য কোনো ঠিকানায় পরিবর্তন বা change করতে পারবেন না)
  • const int* const p — এটি একটি কনস্ট্যান্ট ইন্ট বা const int-এর দিকে নির্দেশ করা একটি কনস্ট্যান্ট বা const পয়েন্টার (আপনি এর কোনো কিছুই পরিবর্তন করতে পারবেন না)

কনস্ট্যান্ট বা const পয়েন্টারের বিভিন্ন ধরন (Variations)

#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
// পয়েন্টার টু কনস্ট (Pointer to const): আপনি *p1-কে মডিফাই (modify) করতে পারবেন না, কিন্তু p1-কে রি-অ্যাসাইন (reassign) করতে পারবেন
const int* p1 = &a;
// *p1 = 50; // এরর বা ERROR: আপনি এর মান বা value মডিফাই (modify) করতে পারবেন না
p1 = &b; // ওকে বা OK: আপনি অন্য কোনো ঠিকানায় (somewhere else) পয়েন্ট করতে পারবেন
cout << "p1 -> " << *p1 << endl;
// কনস্ট পয়েন্টার (Const pointer): আপনি *p2 কে মডিফাই (modify) করতে পারবেন, কিন্তু p2-কে রি-অ্যাসাইন (reassign) করতে পারবেন না
int* const p2 = &a;
*p2 = 50; // ওকে বা OK: আপনি এর মান বা value মডিফাই (modify) করতে পারবেন
// p2 = &b; // এরর বা ERROR: আপনি এটিকে পুনরায় পয়েন্ট (repoint) করতে পারবেন না
cout << "a = " << a << endl;
// কনস্ট পয়েন্টার টু কনস্ট (Const pointer to const): আপনি এর কোনোটিই করতে পারবেন না (can't do either)
const int* const p3 = &b;
cout << "p3 -> " << *p3 << endl;
// *p3 = 99; // ERROR
// p3 = &a; // ERROR
return 0;
}
Output
p1 -> 20
a = 50
p3 -> 20

পয়েন্টার (Pointer) বনাম (vs.) রেফারেন্স (Reference): কখন কোনটি ব্যবহার করবেন (When to Use Which)

এখানে এর একটি দ্রুত নির্দেশিকা বা গাইড (guide) দেওয়া হলো:

বৈশিষ্ট্য (Feature)পয়েন্টার (Pointer)রেফারেন্স (Reference)
নাল বা null হতে পারে কি না?হ্যাঁ (Yes)না (No)
নতুন করে অ্যাসাইন (reassigned) করা যায় কি না?হ্যাঁ (Yes)না (তৈরির সময় যুক্ত থাকে বা bound at creation)
সিনট্যাক্স ওভারহেড (Syntax overhead)?অনেক বেশি (* এবং &)অনেক কম (স্বয়ংক্রিয় বা automatic)
গাণিতিক কাজগুলো (Arithmetic) করা যায় কি না?হ্যাঁ (p++, p+3)না

সাধারণ নিয়ম বা রুল অব থাম্ব (Rule of thumb): আপনার যখন সুযোগ বা সাধ্য থাকবে তখন রেফারেন্স (references) ব্যবহার করুন, আর যখন সত্যিই এর প্রয়োজন পড়বে ঠিক তখনই কেবল পয়েন্টার (pointers) ব্যবহার করুন। বিভিন্ন ঐচ্ছিক মানের (optional values) (যেমন নালেবল বা nullable), ডায়নামিক মেমোরির (dynamic memory) ক্ষেত্রে এবং লিঙ্কড লিস্টের (linked lists) মতো বিভিন্ন ডেটা স্ট্রাকচারের (data structures) ক্ষেত্রে মূলত এই পয়েন্টারগুলো (pointers) ব্যবহার করার প্রয়োজন পড়ে。

Note: রেফারেন্স (References) কখনোই নাল (null) হতে পারে না এবং একে কোনোভাবেই পুনরায় নতুন কোনো ঠিকানায় বসানো বা রিসেট (reseated) করা যায় না (যেমন ইনিশিয়ালাইজ বা initialization করার পর এটিকে অন্য কোথাও পয়েন্ট বা pointed করা)। আর এই ব্যাপারটিই এটিকে পয়েন্টারের (pointers) তুলনায় অনেক বেশি নিরাপদ (safer) করে তোলে। তাই সুযোগ পেলেই রেফারেন্স ব্যবহার করুন, এবং যখন সত্যিই প্রয়োজন পড়বে তখন পয়েন্টার (pointers) ব্যবহার করুন — অনেকটা ঠিক যখন আপনার পয়েন্টারের গাণিতিক হিসেব (pointer arithmetic) বা কোনো নালেবিলিটির (nullability) প্রয়োজন হয়।
চ্যালেঞ্জ

ছোট কুইজ

p একটি পয়েন্টার (pointer) হলে *p এক্সপ্রেশনটি মূলত কী কাজ করে?
Inheritance & PolymorphismSmart Pointers