ট্র্যাক
/
C++
C++
/
সিলেবাস
/
পয়েন্টার
পয

পয়েন্টার মধ্যে C++

১টি অনুশীলনী

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

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

আপনি যদি পয়েন্টারে নতুন হন, তাহলে এগুলো একটু রহস্যময় লাগতে পারে, কিন্তু একবার অভ্যস্ত হয়ে গেলে এগুলো বেশ সহজ।

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

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

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

সাধারণ সিনট্যাক্স

C++-এ একটি পয়েন্টার ডিক্লারেশন করতে হয় এভাবে: পয়েন্টারটি যে ডেটা টাইপের দিকে নির্দেশ করছে সেটি লিখতে হয়, তারপর একটি অ্যাস্টেরিস্ক (*) আর পয়েন্টারটির নাম। পয়েন্টার ডিক্লেয়ার করা হলে সেগুলো স্বয়ংক্রিয়ভাবে ইনিশিয়ালাইজ হয় না। স্পষ্টভাবে অ্যাসাইন না করলে একটি পয়েন্টার সাধারণত একটি অনির্ধারিত মান ধারণ করে, যাকে প্রায়ই "গার্বেজ অ্যাড্রেস" বলা হয়। কিছু কম্পাইলার পয়েন্টারকে nullptr দিয়ে ইনিশিয়ালাইজ করতে পারে, তবে এই আচরণ সব কম্পাইলারে নিশ্চিত নয়, তাই এর উপর নির্ভর না করাই জরুরি। সম্ভাব্য সমস্যা এড়াতে র' পয়েন্টার স্পষ্টভাবে ইনিশিয়ালাইজ করা এবং ব্যবহারের আগে সেটি নাল নয় কিনা যাচাই করা একটি উত্তম অভ্যাস।

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

একটি ভ্যারিয়েবলের অ্যাড্রেস একটি পয়েন্টারে অ্যাসাইন করতে আপনি অ্যাড্রেস-অফ অপারেটর (&) ব্যবহার করেন। পয়েন্টার ডিরেফারেন্স করা হয় ইনডাইরেকশন অপারেটর (*) দিয়ে।

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

লক্ষ্য করুন: ডিরেফারেন্সিং স্পষ্টভাবে করতে হয়, অথচ রেফারেন্স তো অ্যালিয়াসের মতোই কাজ করত।

পয়েন্টার অ্যারিথমেটিক

Pয়েন্টার অ্যারিথমেটিক দিয়ে পয়েন্টারের উপর গাণিতিক কাজ করা যায়, যা বিশেষভাবে অ্যারে নিয়ে কাজ করার সময় কাজে লাগে। একটি পয়েন্টারের সাথে একটি ইন্টিজার যোগ করলে সেটি ভিন্ন একটি এলিমেন্টের দিকে নির্দেশ করে।

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

সাবধানে না সামলালে C++-এ পয়েন্টার অ্যারিথমেটিক সহজেই আনডিফাইন্ড বিহেভিয়ার-এ পৌঁছে দিতে পারে। আনডিফাইন্ড বিহেভিয়ার অপ্রত্যাশিত প্রোগ্রাম আউটপুট, ক্র্যাশ, এমনকি নিরাপত্তা দুর্বলতা হিসেবেও প্রকাশ পেতে পারে। আনডিফাইন্ড বিহেভিয়ারের পরিণতির একটি কুখ্যাত উদাহরণ ঘটেছিল ১৯৯৬ সালে Ariane 5 রকেটের বিস্ফোরণে, যেখানে একটি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যাকে ১৬-বিট সাইনড ইন্টিজারে রূপান্তরের কারণে সৃষ্ট একটি সফটওয়্যার এক্সেপশন ভয়াবহ ব্যর্থতার দিকে নিয়ে যায়।

মেম্বার ভ্যারিয়েবল অ্যাক্সেস করা

C++-এ -> অপারেটর দিয়ে কোনো অবজেক্টের পয়েন্টারের মাধ্যমে সেই অবজেক্টের মেম্বার অ্যাক্সেস করা হয়। এটি একটি শর্টহ্যান্ড, যা পয়েন্টার দিয়ে নির্দেশিত অবজেক্টের মেম্বার অ্যাক্সেস করা সহজ করে। যেমন, যদি ptr এমন একটি অবজেক্টের পয়েন্টার হয় যার একটি মেম্বার ভ্যারিয়েবল x আছে, তাহলে (*ptr).x ব্যবহার না করে আপনি সরাসরি ptr->x ব্যবহার করতে পারেন। অবজেক্টের পয়েন্টার নিয়ে কাজ করার সময় এই অপারেটর কোডের পাঠযোগ্যতা বাড়ায় এবং অপ্রয়োজনীয় দীর্ঘতা কমায়।

এখানে একটি সংক্ষিপ্ত উদাহরণ, যেখানে Superhero নামের একটি স্ট্রাক্ট-এর একটি মেম্বার ভ্যারিয়েবল superpower আছে। main ফাংশনটি একটি Superhero অবজেক্টের (যা Wonder Woman-কে বোঝায়) পয়েন্টার dianaPrince তৈরি করে। superpower মেম্বার ভ্যারিয়েবল অ্যাক্সেস করতে -> অপারেটর ব্যবহার করা হয়েছে, যা Wonder Woman-এর আইকনিক "Lasso of Truth" তুলে ধরে।

struct Superhero {
    std::string superpower;
};

Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";

// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

// Memory cleanup:
delete dianaPrince;

পয়েন্টার বনাম রেফারেন্স

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

Advanced

ডাইনামিক মেমরি অ্যালোকেশন ও নাল পয়েন্টার

C++-এ new দিয়ে রানটাইমে হিপে অবজেক্টের জন্য ডাইনামিক মেমরি অ্যালোকেট করা হয়। এটি অ্যালোকেট করা মেমরি অ্যাড্রেসের একটি পয়েন্টার রিটার্ন করে, যার ফলে অবজেক্টের আয়ু কোনো নির্দিষ্ট স্কোপের সাথে বাঁধা থাকে না। তবে মেমরি যথাযথভাবে ডি-অ্যালোকেট করতে new-এর সাথে সবসময় delete জুড়ে দেয়া জরুরি। মেমরি লিক ও ডি-অ্যালোকেট করা মেমরি অ্যাক্সেস করার মতো সাধারণ সমস্যা এড়াতে ডাইনামিক মেমরি অ্যালোকেশন সতর্কভাবে ব্যবহার করা উচিত।

পয়েন্টারকে নাল পয়েন্টার নামের একটি বিশেষ মানেও সেট করা যায়, যা বোঝায় যে তারা কোনো বৈধ অ্যাড্রেস নির্দেশ করছে না। বৈধ মান অ্যাসাইন করার আগে পয়েন্টার ইনিশিয়ালাইজ করতে সাধারণত নাল পয়েন্টার ব্যবহার করা হয়।

// There is a dark mark bearer, but who is it?
std::string* darkMarkBearer{nullptr};
// It is Bellatrix!
darkMarkBearer = new std::string{"Bellatrix Lestrange"};
// She has been defeated
delete darkMarkBearer;
// Reset pointer to null, symbolizing there is no more darkMarkBearer at the moment
darkMarkBearer = nullptr;

একটি পয়েন্টারের হয় একটি বৈধ অবজেক্টের অ্যাড্রেস ধারণ করা উচিত, নয় nullptr-এ সেট করা উচিত। তাতে আপনাকে পয়েন্টার কেবল নাল কিনা তা পরীক্ষা করলেই চলে, আর যেকোনো নন-নাল পয়েন্টারকে বৈধ ধরে নিতে পারেন। কোনো অবজেক্ট ধ্বংস হয়ে গেলে তার দিকে থাকা সব পয়েন্টার ড্যাঙ্গলিং অবস্থায় পড়ে থাকে (সেগুলো স্বয়ংক্রিয়ভাবে nullptr-এ সেট হয় না)। এই ধরনের ঘটনা শনাক্ত করা এবং সেই পয়েন্টারগুলো পরবর্তীতে nullptr-এ সেট করা নিশ্চিত করা আপনার দায়িত্ব।

পুরনো কোডে আপনি nullptr-এর দুটি বিকল্প দেখতে পারেন। প্রথমত, লিটারাল 0-কে পয়েন্টারের জন্য বিশেষভাবে একটি নাল মান হিসেবে ব্যাখ্যা করা হয়, যদিও এটি একমাত্র পরিস্থিতি যেখানে একটি ইন্টিগ্রাল লিটারাল পয়েন্টারে অ্যাসাইন করা যায়। দ্বিতীয়ত, preprocessor macro NULL, যা C থেকে উত্তরাধিকারসূত্রে পাওয়া এবং <cstddef> হেডারে ডিফাইন করা, নাল পয়েন্টারের আরেকটি রূপ, তবে আধুনিক C++ কোডে এর ব্যবহার কম দেখা যায়।

GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি নতুন একটি উইন্ডো বা ট্যাবে খুলবে

পয়েন্টার শিখুন

অনুশীলন লক করা আছে

পয়েন্টার অনুশীলন করতে আরও ১টি অনুশীলনী আনলক করুন