সূচিপত্র
শুরুতেই আগের পর্বের একটু ফিরে আসা যাক। পয়েন্টার কি? সহজ কথায় পয়েন্টার হল এমন এক ধরণের ভ্যারিয়েবল, যা অন্য একটি ভ্যারিয়েবলের অ্যাড্রেস সংরক্ষন করে রাখে। আর পয়েন্টার ডিক্লারেশনের সিনট্যাক্স নিচের মতঃ
#include <cstdio> using namespace std; int main() { int p; int *ptr_p; double d; double *ptr_d; char c; char *ptr_c; }
আবার আমরা একটা ভ্যারিয়েবলের মান পয়েন্টারের সাহায্যে প্রিন্ট করতে পারি নিচের মতঃ
#include <cstdio> int main() { char c='x'; char *ptr; ptr = &c; printf("%c\n", *ptr); }
এখন আমি কিছু কোড দিব, আর তোমাকে সেটা রান না করে অনুমান করতে হবে তার আউটপুট কি হতে পারে।
প্রথমেই আমরা p নামের একটি ভ্যারিয়েবল ডিক্লেয়ার করে দিব এবং তার মান ইনিশিয়ালাইজ করব 7। এরপর আমরা চেষ্টা করবো পয়েন্টার ব্যবহার করে এর মানটা বদলে দিতে।
#include <cstdio> int main() { int p=7; int *ptr; ptr = &p; *ptr = 13; printf("%d\n", ptr); printf("%d\n",p); printf("%d\n", *ptr); return 0; }
এখানে আউটপুটের প্রথম লাইনে একটা অ্যাড্রেস প্রিন্ট হবে, সেটা যেকোনো কিছু হতে পারে, তাই এটা নিয়ে মাথা ঘামাতে হবে না। তোমাকে অনুমান করতে হবে, দ্বিতীয় এবং তৃতীয় লাইনে কি প্রিন্ট হবে।
আসলে শেষ দুই লাইনে একই জিনিস প্রিন্ট হবে। একবার আমরা সরাসরি ভ্যারিয়েবলের সাহায্যে প্রিন্ট করতিছি, আরেকবার প্রিন্ট করতিছি ডিরেফারেন্সিং-এর মাধ্যমে। প্রশ্ন হল, p ভ্যারিয়েবলে এখন কোন মানটা আছে?
আমরা ৬ নাম্বার লাইনে যখন লিখেছি *ptr = 13; তখন প্রথমেই *ptr-এর মাধ্যমে p ভ্যারিয়েবলটার ডাক পড়েছে। এরপর আমরা সেই ভ্যারিয়েবলের মান বদলে 13 করে দিয়েছি। তাই এখন p-এর মান হয়ে গিয়েছে 13, আর দুই লাইনেই প্রিন্ট হবে 13!
আবার আমরা যোগ, বিয়োগও করতে পারি এভাবে, যেমনঃ
#include <cstdio> int main() { int p=7; int *ptr; ptr = &p; *ptr = *ptr + 1; printf("%d\n", ptr); printf("%d\n",p); printf("%d\n", *ptr); return 0; }
এক্ষেত্রে p-ভ্যারিয়েবলের মান 1 বেড়ে দুই লাইনেই প্রিন্ট হত 8!
এখন আমরা আরেকটি কোড দেখি।
#include <cstdio> int main() { int p=7; int *ptr1, *ptr2; ptr1 = &p; printf("%d\n", ptr1); int b = 124; ptr2 = &b; *ptr1 = b; printf("%d\n", *ptr1); printf("%d\n", ptr1); }
প্রথমেই চিন্তা কর printf(“%d\n”, *ptr1); দিয়ে কি প্রিন্ট হবে। আমরা ৯ নাম্বার লাইনে ptr1 অ্যাড্রেসে থাকা মান বদলে দিছি, তাই এখন এখানে b-এর মানটা প্রিন্ট হবে, অর্থাৎ ১২৪। কিন্তু আর দুইটা printf দিয়ে কি একই জিনিস প্রিন্ট হবে?
হবে, কারণ আমরা পয়েন্টারের মান বদলে দি নাই। এখনো ptr1 ঠিক আগের অ্যাড্রেসটাকেই ধরে রাখছে। আমরা শুধু সেই অ্যাড্রেসে থাকা ভ্যারিয়েবলের মানটা বদলে দিয়েছি! এখন,
(১) আমরা *ptr1 না প্রিন্ট করে যদি p-এর মান প্রিন্ট করতাম, তাহলে কি হত? নিজেই ভেবে দেখ!
(২) আবার, ৯ নাম্বার লাইনের ptr1 = ptr2; লিখতাম, তাহলে কি হত? এবং এক্ষেত্রে p-এর মানের কি হত? এগুলো নিজেরাই কোড লিখে বুঝার চেষ্টা কর। আর কিছু না বুঝলে আমাকে বল!
______________________________________________________________
এখন, আমরা একটা গুরুত্বপূর্ণ জিনিস দেখবো। সাধারণ ভ্যারিয়েবলগুলোর ক্ষেত্রে আমরা num = num+1; কিংবা num++; ইত্যাদি স্টেটমেন্ট প্রায়ই দেখি। পয়েন্টারের ক্ষেত্রেও কিন্তু এগুলো করা যায়। তবে পার্থক্য হল, পয়েন্টারের ক্ষেত্রে শুধু যোগ আর বিয়োগ করা যায়, গুণ-ভাগ না! তো কথা না বাড়িয়ে কোড লিখে ফেলা যাক!
#include <cstdio> int main() { int num=7; int *ptr; ptr = # printf("%d\n", ptr); printf("%d\n", ptr+1); }
এই কোডটার আউটপুট হবে অনেকটা নিচের মতঃ
এখানে একটা মজার জিনিস খেয়াল করেছ? দুইটা সংখ্যার মধ্যে পার্থক্য হল ৪। এর কারণ হল পয়েন্টারগুলো জানে তারা ইন্টিজার ভ্যারিয়েবলের অ্যাড্রেস রাখছে। আর ইন্টিজারের সাইজ হল ৪ বাইট বা ৩২ বিট। তাই তারা একটা ইন্টিজারের জন্য ঠিক ৪ টি বাইট জায়গা রাখে!
/* এজন্যই পয়েন্টার ডিক্লেয়ার করার সময় কোন ডাটা টাইপের পয়েন্টার সেটা বলে দেওয়া খুবই গুরুত্বপূর্ন। */
এবার ডাবল, ক্যারেক্টার ইত্যাদি ডাটা টাইপের জন্য কোড লিখে সেগুলোর সাইজও পরীক্ষা করে দেখতে পার!
এখন আমরা যদি আমাদের ptr+1 অ্যাড্রেসে কি ভ্যালু আছে, সেটা প্রিন্ট করি তাহলে কি হবে বল তো?
#include <cstdio> int main() { int num=7; int *ptr; ptr = # printf("%d\n", *ptr); printf("%d\n", *(ptr+1)); }
এখানে, দ্বিতীয় লাইনে একটি গার্বেজ ভ্যালু প্রিন্ট হবে। কারণ আমরা ওই অ্যাড্রেসে কোনো ভ্যালু রাখি নাই। তাই ডিরেফারেন্স করলে যেকোনো কিছুই সেখান থেকে আসতে পারে!
একটু আগেই বলেছিলাম, পয়েন্টার ডিক্লেয়ার করার সময় কোন ডাটা টাইপের পয়েন্টার সেটা বলে দেওয়া খুবই গুরুত্বপূর্ন। তোমার মনে প্রশ্ন আসতে পারে, মেমরি তো মেমরিই। এটা সেভ করে রাখতে আবার টাইপের কি দরকার! এর কারন হল আমরা পয়েন্টারের সাহায্যে অ্যাড্রেস সেভ করে রাখার পাশাপাশি তা ডিরেফারেন্সও করে থাকি।
ধর, তোমার কাছে একটা ইন্টিজার আছে, 2049. এর বাইনারি রিপ্রেজেন্টেশন হল নিচের মতঃ
এই চারটা বক্স কে আমরা একেকটা বাইট ধরতে পারি, যাদের প্রতিটিতে আছে ৮ টি করে বীট। এখন বুঝার সুবিধার প্রতি বাইটের মেমরি অ্যাড্রেস হিসেবে আমরা একটা নাম দিয়ে নি। হিসাব শুরু করতে হবে লিস্ট সিগনিফিক্যান্ট বীট থেকে, অর্থাৎ সবচেয়ে ডানের বক্সটাকে যদি আমরা 1001 ধরে নি, তাহলেঃ
/* প্রথমটা 1001 হলে পরের গুলো অবশ্যই 1002, 1003, 1004 হবে। */
এখন, আমরা জানি ইন্টিজারের জন্য মেমরিতে ৪ টি বাইট সংরক্ষিত থাকে। তাই যখন বলে দেওয়া হয় যে, পয়েন্টারটি একটি ইন্টিজার টাইপের, তখন কম্পাইলার বুঝে নেয় যে, তাকে ৪ টি বাইট নিয়ে কাজ করতে হবে।
#include <cstdio> int main() { int num = 2049; int *ptr; ptr = # printf("Adress: %d\nNumber: %d\n", ptr, *ptr); return 0; }
কিন্তু যদি তুমি এখানে ইন্টিজার পয়েন্টার ডিক্লেয়ার না করে অন্য একটা করতা, তাহলে কি হত দেখঃ
#include <cstdio> int main() { int num = 2049; int *ptr; ptr = # printf("Adress: %d\nNumber: %d\n", ptr, *ptr); char *ptr2; ptr2 = # printf("Adress: %d\nNumber: %d\n", ptr2, *ptr2); return 0; }
এই কোড কম্পাইলার রান করতে পারবে না। কারণ তুমি এখানে ইন্টিজারের অ্যাড্রেসকে char পয়েন্টারে রাখতে চেয়েছ। আচ্ছা বুঝা গেল, রাখা যাবে না। কিন্তু আমরা তো এটা করলে কি হয় সেটা জানতে বদ্ধপরিকর!
তো এজন্য যা করতে হবে, তাহল টাইপকাস্ট! নিচের কোডটা দেখঃ
#include <cstdio> int main() { int num = 2049; int *ptr; ptr = # printf("Adress: %d\nNumber: %d\n", ptr, *ptr); char *ptr2; ptr2 = (char*)# printf("Adress: %d\nNumber: %d\n", ptr2, *ptr2); return 0; }
এই কোডের আউটপুট হবে নিচের মতঃ
এখানে দেখ, অ্যাড্রেসটা ঠিকই আছে। কিন্তু ভ্যালুটা বদলে গেছে! এর কারণ বুঝতে হলে আমাদের আগের বীট প্যাটার্নটা আবার দেখতে হবে!
আমরা এখানে যেটা 1001 ধরেছি, আমার উদাহারণে সেটা আসছে 2293524। তোমার অন্য কিছু আসতে পারে। এমনকি কয়েকবার রান করলে একেকবার একেক জিনিস আসবে! কথা সেটা না, কথা হল কেন এমন হল!
কারণ আমরা কম্পিউটারকে বলছি, যে আমাদের পয়েন্টারটা char ভ্যারিয়েবলের অ্যাড্রেস রাখবে। আর char ভ্যারিয়েবল তো ১ বাইটের, তাই কম্পিউটার কেন শুধু শুধু ৪ বাইট নিয়ে কাজ করবে! তাই সে শুধু 1001 নাম্বার বাইট নিয়ে কাজ করে আর বাকি তিনটাকে ইগনোর করে। আর 1001 নাম্বার বাইটে আছে, 0000 0001, যার মান হল 1. একারণেই প্রিন্ট হয়েছে 1!
আশা করি বুঝাতে পেরেছি। এখন আবার আরেকটা চিন্তা তোমার মাথায় আসতে পারে, সেটা হল float ভ্যারিয়েবলের জন্যও তো ৪ টা বাইট থাকে, তাহলে কি float পয়েন্টার ডিক্লেয়ার করেও আমরা কাজ চালাতে পারবো? নিচের কোডটা রান করে নিজেই দেখে নাও।
#include <cstdio> int main() { int num = 2049; int *ptr; ptr = # printf("Adress: %d\nNumber: %d\n", ptr, *ptr); float *ptr2; ptr2 = (float*)# printf("Adress: %d\nNumber: %d\n", ptr2, *ptr2); return 0; }
দেখবা আউটপুট ঠিকমত আসছে না। কারণ float ভ্যারিয়েবল আর int ভ্যারিয়েবল মেমরিতে থাকার প্রক্রিয়াটা এক না। float একটু ভিন্নভাবে মেমরিতে সংরক্ষিত হয়।
এখন, আমরা আরেক টাইপের পয়েন্টার দেখব, সেটা হল void পয়েন্টার। একে বলা হয় generic pointer. এটা যেকোনো ডাটা টাইপের ভ্যারিয়েবলের অ্যাড্রেস রাখতে পারে।
#include <cstdio> int main() { int num = 2049; void *ptr; ptr = # printf("Adress: %d\n", ptr); return 0; }
হঠাত দেখে খুশি হয়ে যেতে পার, কি মজা! এখন আর টাইপ দেখে দেখে ঠিক মত পয়েন্টার ডিক্লেয়ার না করলেও চলবে, void পয়েন্টার দিয়ে দিলেই কেল্লা ফতে!
কিন্তু সমস্যা হল, void পয়েন্টারের কাজ অ্যাড্রেস রাখা পর্যন্তই। এর বাইরে আর কিছুই করতে পারে না। তবে এর বেশ কিছু কাজ আছে। সেগুলো পরের পর্বগুলোতে দেখবে। আজকের মত এখানেই শেষ। 🙂