সি/সি++ প্রোগ্রামিং টিউটোরিয়াল (পর্ব ১৫) অন্যান্য পয়েন্টার

1
411

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

ভয়েড পয়েন্টার

সোজা কথায়, ভয়েড পয়েন্টার হলো এমন এক ধরনের বিশেষ পয়েন্টার, যার ডাটা হিসেবে ক্যাস্টিং ছাড়াই অন্য যেকোনো টাইপের ভেরিয়েবলের অ্যাড্রেস নির্ধারণ করা যায়। অর্থাৎ এ ধরনের পয়েন্টারের মাধ্যমে যেকোনো টাইপের ভেরিয়েবল ক্যাস্টিং ছাড়াই পয়েন্ট করা যায়। এ ধরনের পয়েন্টার ডিক্লেয়ার করার নিয়ম হলো :

void *poiter_name;

দেখা যাচ্ছে এ ধরনের পয়েন্টার ডিক্লেয়ার করার জন্য ডাটা টাইপ হিসেবে ভয়েড কীওয়ার্ড ব্যবহার করতে হয়। আমরা জানি যে int *p মানে হলো p পয়েন্টারটি যেকোনো ইন্টিজারকে পয়েন্ট করবে। একইভাবে double *p মানে হলো p পয়েন্টারটি যেকোনো ডাবলকে পয়েন্ট করবে। একইভাবে void *p মানে হলো p এমন একটি পয়েন্টার, যা কি না ইন্টিজার কিংবা ফ্লোট কিংবা ডাবল সব ধরনের ভেরিয়েবলকেই ক্যাস্টিং ছাড়া পয়েন্ট করবে। পয়েন্টার যদি ভয়েড টাইপ না হতো, সেক্ষেত্রে ক্যাস্টিংয়ের মাধ্যমে এক টাইপের পয়েন্টার দিয়ে অন্য টাইপের ভেরিয়েবলকে পয়েন্ট করা যেত। খেয়াল রাখতে হবে, সাধারণ ভেরিয়েবলের ডাটা টাইপ হিসেবে ভয়েড ব্যবহার করলে তার মাঝে কোনো ডাটা না থাকা বোঝায় (যেটি সাধারণত দেখা যায় না, কারণ ভেরিয়েবলের মূল উদ্দেশ্যই হলো ডাটা রাখা)। আর কোনো ফাংশনের ডাটা টাইপ ভয়েড হলে তা কোনো মান রিটার্ন করে না। অর্থাৎ তখনই ভয়েড ডাটা টাইপটি ব্যবহার করা হয়, যখন কোনো মানের দরকার হয় না। কিন্তু পয়েন্টারের ক্ষেত্রে সম্পূর্ণ ভিন্ন একটি ঘটনা ঘটছে। এক্ষেত্রে যখন সব ধরনের ডাটার দরকার হয়, তখন ভয়েড ব্যবহার করা হয়। এবার ভয়েড পয়েন্টারের উদাহরণস্বরূপ ছোট একটি প্রোগ্রাম দেয়া হলো :

int x=10;
double y=3.12;
void *ptr;
ptr=&x;

এখানে ptr হলো ভয়েড টাইপের একটি পয়েন্টার এবং এর জন্য ইন্টিজার টাইপের ভেরিয়েবল x-এর অ্যাড্রেস নির্ধারিত হবে, অর্থাৎ ptr পয়েন্টারটি x-কে পয়েন্ট করবে। একইভাবে,

ptr=&y;

এক্ষেত্রে ptr-এর জন্য ডাবল টাইপের ভেরিয়েবল y-এর অ্যাড্রেস নির্ধারিত হবে, তথা ptr পয়েন্টারটি y-কে পয়েন্ট করবে। আবার ভয়েড পয়েন্টারের জন্য অন্য টাইপের ডাটাকেও অ্যাসাইন করা যায়। যেমন :

int x=20;
int *ip;
void *vp;
ip=&x;
vp=ip;

এখানে প্রথমে x একটি ইন্টিজার ভেরিয়েবল ডিক্লেয়ার করা হয়েছে, যার মান হিসেবে ২০ নির্ধারিত করা হয়েছে। এরপরই ইন্টিজার এবং ভয়েড টাইপের দুটি পয়েন্টার যথাক্রমে ip এবং vp ডিক্লেয়ার করা হয়েছে। সুতরাং ip দিয়ে x-কে পয়েন্টার করা যাবে। এখন vp যেহেতু ভয়েড টাইপের পয়েন্টার, তাই এটি দিয়ে সবাইকেই পয়েন্ট করার কথা। আবার ভয়েড পয়েন্টার যে শুধু যেকোনো ভেরিয়েবলকেই পয়েন্ট করবে এমনটি নয়, সেটি যেকোনো টাইপের পয়েন্টারের পয়েন্টেড ডাটাকেও পয়েন্ট করতে সক্ষম। তাই ওপরের কোডের একদম শেষে vp পয়েন্টারটি ip-এর ডাটা তথা x-কে পয়েন্ট করছে। এক্ষেত্রে কম্পাইলার কোনো এরর দেখাবে না। এখানে vp-এর জায়গায় অন্য কোনো পয়েন্টার ব্যবহার করলে ক্যাস্টিংয়ের প্রয়োজন হতো। কিন্তু ভয়েড পয়েন্টারের বেলায় কিছুরই দরকার হয় না। এভাবে ক্যাস্টিং ছাড়াই ভয়েড পয়েন্টারের জন্য অন্য টাইপের পয়েন্টারকে কিংবা অন্য ডাটা টাইপের পয়েন্টারের জন্য ভয়েড পয়েন্টারকে অ্যাসাইন করা যায়। তবে ভয়েড পয়েন্টারের মাধ্যমে পয়েন্টেড অ্যাড্রেসের ডাটা নিয়ে কাজ করতে চাইলে পয়েন্টেড ডাটাকে এভাবে ক্যাস্ট করতে হবে :

*(pointed_data_type *) void_ptr;
যেমন :
int x=10,y;
void *ptr;
ptr=&x;
y=*(int*)ptr;

এখানে প্রথমে ptr-এর জন্য x-এর অ্যাড্রেস নির্ধারণ করা হয়েছে। পরে ptr-এর মাধ্যমে ইন্টিজার টাইপ ডাটা পড়ার জন্য ptr-কে int*-এ ক্যাস্ট করা হয়েছে। এভাবে ভয়েড পয়েন্টারের মাধ্যমে পয়েন্টেড অ্যাড্রেসের ডাটা নিয়ে কাজ করতে হলে পয়েন্টেড ডাটাকে উপযুক্ত পয়েন্টার টাইপে ক্যাস্ট করে নিতে হয়। তবে একটি উল্লেখযোগ্য বিষয় হলো, ভয়েড পয়েন্টারের মাধ্যমে পয়েন্টেড ডাটাকে যে টাইপে ক্যাস্ট করা হবে, আউটপুটে সেই টাইপ অনুযায়ী ডাটা পাওয়া যাবে। ভয়েড পয়েন্টারকে জেনেরিক পয়েন্টারও বলা হয়। ভয়েড পয়েন্টারকে অন্য কোনো টাইপে ক্যাস্ট না করা পর্যন্ত ইনক্রিমেন্ট, ডিক্রিমেন্ট কিংবা অন্য কোনো এক্সরেশনের অপারেন্ড হিসেবে ব্যবহার করা যায় না।

কনস্ট্যান্ট পয়েন্টার

const কীওয়ার্ডকে পয়েন্টার ডিক্লেয়ার করার সময় বিভিন্নভাবে ব্যবহার করা যায়। তবে প্রথমে দেখা যাক নন-পয়েন্টার ভেরিয়েবল ডিক্লেয়ার করার সময় এ কীওয়ার্ড ব্যবহার করলে কী হয়,

const i=10;

এখানে const কীওয়ার্ডের মাধ্যমে কম্পাইলারকে জানিয়ে দেয়া হচ্ছে, i হলো একটি কনস্ট্যান্ট ভেরিয়েবল। তাই একই স্কোপের মাঝে প্রোগ্রামের অন্য কোথাও এ ভেরিয়েবলের ডাটা পরিবর্তন করলে কম্পাইলের প্রোগ্রাম কম্পাইল করার সময় এরর দেখাবে। অর্থাৎ কনস্ট্যান্ট ভেরিয়েবলের মান সাধারণত পরিবর্তন করা যায় না।

এভাবে কোনো ভেরিয়েবলের মান অপরিবর্তনীয় রাখতে হলে তাকে কনস্ট্যান্ট হিসেবে ডিক্লেয়ার করতে হয়। একইভাবে প্রোগ্রামে পয়েন্টারকে কনস্ট্যান্ট করার জন্যও const কীওয়ার্ড ব্যবহার করা হয়। কনস্ট্যান্ট কীওয়ার্ড দুইভাবে ব্যবহার হতে পারে। যেমন :

const datatype *pointerName = value; A_ev
datatype * const pointerName = value;

অর্থাৎ const কীওয়ার্ডটি ডাটা টাইপের আগে বা পরে উভয় স্থানেই বসতে পারে। তবে এটি কিন্তু একই বিষয় নয়। দেখা যাক আগে বসালে কী হয় আর পরে বসালে কী হয়।

যদি ডাটা টাইপের আগে কীওয়ার্ড বসানো হয়, তাহলে পয়েন্টার যাকে পয়েন্ট করবে তার মান কনস্ট্যান্ট থাকবে। অর্থাৎ পয়েন্টারের নিজের মান পরিবর্তন করা যাবে, কিন্তু যাকে পয়েন্ট করা হচ্ছে তার মান পরিবর্তন করা যাবে না। অন্যভাবে বলতে গেলে, পয়েন্টারের মাধ্যমে পয়েন্টেড ডাটা পড়া যাবে, কিন্তু পরিবর্তন করা যাবে না। এভাবেই উইন্ডোজে কোনো ফাইলকে রিড অনলি করা হয়, যাতে ফাইলটি শুধু পড়া যাবে, কিন্তু পরিবর্তন করা যাবে না। যেমন :

int i=10,j;
const int *ptr;
ptr=&i;
j=*ptr;
*ptr=20;

এখানে j-এর জন্য i-এর ডাটা নির্ধারণ করা গেলেও *ptr=20; এই স্টেটমেন্টের মাধ্যমে i-এর জন্য ২০ নির্ধারণ করা যাবে না। কেননা পয়েন্টারকে ডিক্লেয়ার করার সময় কম্পাইলারকে জানিয়ে দেয়া হয়েছে পয়েন্টারটি যাকে পয়েন্ট করবে তার মান অপরিবর্তিত থাকবে। তাই শেষের লাইনে *ptr-এর মাধ্যমে পয়েন্টেড ডাটাকে পরিবর্তন করতে চাইলে কম্পাইলার এরর দেখাবে। সুতরাং শেষে বলা যায়, cons int *ptr; এর মানে হলো পয়েন্টারের জন্য যে ইন্টিজার ভেরিয়েবলের অ্যাড্রেস নির্ধারণ করা হবে, পয়েন্টারের মাধ্যমে সেই ভেরিয়েবলের ডাটা শুধু পড়া যাবে, কিন্তু পরিবর্তন করা যাবে না।

কিন্তু const কীওয়ার্ডটিকে যদি ডাটা টাইপের পরে ব্যবহার করা হয়, যেমন : Int * const ptr; তাহলে পয়েন্টারটি কনস্ট্যান্ট থাকবে। অর্থাৎ পয়েন্টারের নিজের মান পরিবর্তন করা যাবে না, কিন্তু পয়েন্টারটি যাকে পয়েন্ট করছে তাকে পরিবর্তন করা যাবে। একটি ছোট প্রোগ্রাম উদাহরণ হিসেবে দেয়া হলো :

int i=10,j;
int * const ptr=&i;
j=*ptr;
*ptr=20;
ptr=&j;

এখানে ব্যবহৃত পয়েন্টারটি কনস্ট্যান্ট, অর্থাৎ পয়েন্টারের জন্য অন্য কোনো ভেরিয়েবলের ডাটা নির্ধারণ করা যাবে না বা পয়েন্টারের ডাটা অপরিবর্তিত থাকবে। কিন্তু পয়েন্টারের মাধ্যমে পয়েন্টেড ডাটার মান পড়া যাবে (তৃতীয় লাইন) এবং প্রয়োজনে তা পরিবর্তন করা যাবে (চতুর্থ লাইন)। পরিশেষে বলা যায়, int * const ptr মানে হলো পয়েন্টার ভেরিয়েবলটি কনস্ট্যান্ট। তাই এ পয়েন্টার দিয়ে অন্য কাউকে পয়েন্টার করা যাবে না। আর const int *ptr মানে হলো পয়েন্টারটি যাকে পয়েন্ট করছে তার মান কনস্ট্যান্ট। তাই পয়েন্টেড ডাটাকে পরিবর্তন করা যাবে না, কিন্তু পয়েন্টারটি দিয়ে ইচ্ছে করলে অন্য কাউকে পয়েন্ট করা যাবে। কারণ পয়েন্টারটি কাকে পয়েন্ট করছে না করছে- সেটি পয়েন্টারের নিজের ডাটা, পয়েন্টেড ডাটা নয়। তবে এটিই শেষ নয়। ব্যবহারকারী প্রয়োজন হলে উভয় পাশেই const কীওয়ার্ড ব্যবহার করতে পারেন। যেমন const int * const ptr=&i; এক্ষেত্রে পয়েন্টারের মান এবং পয়েন্টেড ডাটার মান উভয়ই অপরিবর্তনীয় থাকবে। অর্থাৎ পয়েন্টার দিয়ে যেমন অন্য কাউকে পয়েন্ট করা যাবে না, তেমন পয়েন্টারটি দিয়ে যাকে পয়েন্ট করা হচ্ছে তার মানও পরিবর্তন করা যাবে না।

1 মন্তব্য

একটি উত্তর ত্যাগ