ট্র্যাক
/
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};

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

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

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

// 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++-এ পয়েন্টার অ্যারিথমেটিক সহজেই আনডিফাইনড বিহেভিয়ার ডেকে আনতে পারে। আনডিফাইনড বিহেভিয়ার অপ্রত্যাশিত প্রোগ্রাম আউটকাম, ক্র্যাশ, এমনকি নিরাপত্তা ঝুঁকি আকারে প্রকাশ পেতে পারে। আনডিফাইনড বিহেভিয়ারের পরিণতির একটি কুখ্যাত উদাহরণ ঘটেছিল ১৯৯৬ সালে অ্যারিয়েন ৫ রকেটের বিস্ফোরণে, যেখানে ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যাকে ১৬-বিট সাইনড ইন্টিজারে রূপান্তর করার কারণে সৃষ্ট একটি সফটওয়্যার এক্সেপশন ভয়াবহ ব্যর্থতার দিকে নিয়ে যায়।

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

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;

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

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

নির্দেশনা

স্বাগতম, ইঞ্জিনিয়ার! আপনি Speedywagon Foundation-এর শেষ কয়েকজন অভিজ্ঞ সদস্যের একজন, একটি গোপন সংস্থা, যা কয়েক দশক ধরে Pillar Men-এর মতো প্রাচীন শত্রুদের বিরুদ্ধে লড়াই করে আসছে। এই কাজের ধারাবাহিকতায় আপনি বছরের পর বছর ধরে Foundation-এর টেকনোলজিক্যাল সিস্টেম রক্ষণাবেক্ষণ করেছেন, যা অত্যাধুনিক টেক আর পুরনো লাইব্রেরির মিশ্রণে তৈরি।

তবে সাম্প্রতিক সময়ে Pillar Men-এর অ্যাক্টিভিটি ট্র্যাক করা সেন্সরগুলো ঠিকমতো কাজ করছে না। Foundation-এর সিস্টেমগুলো পুরনো, আর কোডটি এমন একটি লিগ্যাসি C++ লাইব্রেরির সাথে কাজ করে যা আপডেট করা যায় না। আপনার কাজ হলো চারটি মূল ফাংশন তৈরি করা, যা পুরনো ধাঁচের পয়েন্টার-ভিত্তিক লাইব্রেরি ব্যবহার করে Pillar Men সেন্সরের অ্যাক্টিভিটি মনিটর করে।

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

Note

যেহেতু সেন্সরের রিডিং বিশাল হতে পারে, তাই আসল লাইব্রেরিতে ব্যবহৃত একটি মকআপ struct আমরা দিয়ে দিয়েছি। কোডটি আপনার জন্য হেডার ফাইলে আগেই লিখে দেওয়া হয়েছে।

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. সেন্সর সংযোগ যাচাই করুন (connection_check)

আপনার প্রথম কাজ হলো নিশ্চিত করা যে Pillar Men সেন্সরটি ঠিকভাবে সংযুক্ত আছে। সংযোগহীন সেন্সরের কারণে যেন ভুল অ্যালার্ম না বাজে। আপনি একটি ফাংশন connection_check লিখবেন, যা nullptr পরীক্ষা করে দেখে যে সেন্সরের পয়েন্টারটি বৈধ কিনা।

  • এমন একটি ফাংশন ডিফাইন করুন যা একটি pillar_men_sensor struct-এর পয়েন্টার গ্রহণ করে।
  • সেন্সর পয়েন্টারটি নাল না হলে ফাংশনটি true রিটার্ন করবে, অন্যথায় false।
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. সেন্সরগুলোর অ্যাক্টিভিটি গণনা করুন (activity_counter)

Pillar Men ছায়ায় লুকিয়ে আছে, আর আমাদের জানতে হবে সেন্সরগুলো কোনো অ্যাক্টিভিটি শনাক্ত করেছে কিনা। আপনি activity_counter ফাংশনটি লিখবেন, যা সেন্সরগুলোর একটি অ্যারে এবং সেই অ্যারেতে সেন্সরের সংখ্যা নির্দেশ করে এমন একটি ধারণক্ষমতা নেয়।

  • এমন একটি ফাংশন ডিফাইন করুন যা একটি অ্যারের প্রথম এলিমেন্টের পয়েন্টার এবং অ্যারেটির ধারণক্ষমতা গ্রহণ করে।
  • পয়েন্টার অ্যারিথমেটিক ব্যবহার করে সেন্সর অ্যারের মধ্য দিয়ে লুপ চালান এবং অ্যাক্টিভিটি রিডিংগুলো জমা করুন।
  • জমা হওয়া অ্যাক্টিভিটি রিটার্ন করুন।
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. অ্যালার্ম নিয়ন্ত্রণ (alarm_control)

সত্যিকারের বিপদ না থাকলে প্রতিটি সেন্সরের অ্যালার্ম বাজানো উচিত নয়। alarm_control ফাংশনটি নিশ্চিত করে যে কোনো সেন্সরের অ্যাক্টিভিটি লেভেল 0-এর বেশি হলেই কেবল সেটি অ্যালার্ম বাজায়। সিস্টেম ক্র্যাশ ঠেকাতে এই ফাংশনের উচিত নাল সেন্সরও পরীক্ষা করা।

  • এমন একটি ফাংশন ডিফাইন করুন যা একটি pillar_men_sensor-এর পয়েন্টার গ্রহণ করে।
  • ফাংশনটি প্রথমে পরীক্ষা করবে সেন্সরটি nullptr কিনা। সেন্সরটি nullptr হলে false রিটার্ন করুন।
  • সেন্সরটি বৈধ এবং তার অ্যাক্টিভিটি 0-এর বেশি হলে true রিটার্ন করুন; অন্যথায় false রিটার্ন করুন।
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. uv_alarm ফাংশন দিয়ে ডেটায় অসঙ্গতি খোঁজা

এই কাজে আপনি uv_alarm ফাংশনটি তৈরি করবেন, যা UV আলোর এক্সপোজার লেভেল এবং সেন্সরের অ্যাক্টিভিটির ভিত্তিতে ঠিক করে অ্যালার্ম বাজানো উচিত কিনা। uv_alarm ফাংশনটির উচিত দেওয়া uv_light_heuristic ফাংশনটি ব্যবহার করা, যা ডেটার একটি ভেক্টরের উপর কাজ করে এবং নির্দিষ্ট কিছু থ্রেশহোল্ডের ভিত্তিতে একটি মান রিটার্ন করে। এটি প্রোডাকশনে চলা জটিল কোডের একটি মকআপ সংস্করণ, অনুগ্রহ করে ইন্টারফেসটি বদলাবেন না।

speedywagon নেমস্পেসে uv_alarm ফাংশনটি ডিফাইন করুন। এটি উচিত:

  • প্যারামিটার হিসেবে একটি pillar_men_sensor struct-এর পয়েন্টার নেওয়া।
  • সেন্সর পয়েন্টারটি নাল হলে false রিটার্ন করা।
  • সেন্সরের data অ্যারের অ্যাড্রেস পাস করে uv_light_heuristic ফাংশনটি কল করা।
  • uv_light_heuristic যে মান রিটার্ন করে তা sensor->activity লেভেলের চেয়ে বেশি হলে true রিটার্ন করা, অন্যথায় false রিটার্ন করা।
GitHub-এর মাধ্যমে সম্পাদনা করুন লিংকটি একটি নতুন উইন্ডো বা ট্যাবে খোলে
C++ Exercism

স্পিডিওয়াগন ফাউন্ডেশন শুরু করতে প্রস্তুত?

Exercism-এ সাইন আপ করুন, C++ ট্র্যাকের 19টি কনসেপ্ট100টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।