আপনি ইতিমধ্যে কয়েকটি কনসেপ্টে C++ ফাংশন ব্যবহার করেছেন এবং লিখেছেন। এখন একটু টেকনিক্যাল হওয়ার সময়। নিচের কোড স্নিপেটে সহজে দেখে নেওয়ার জন্য সবচেয়ে বেশি ব্যবহৃত পরিভাষাগুলো দেওয়া হলো। C++ হোয়াইটস্পেস উপেক্ষা করে, তাই ফরম্যাটিং বদলে প্রতিটি এলিমেন্টকে একটি করে লাইনে বসানো হয়েছে।
// Function declaration:
bool // Return type
admin_detected(string user, string password) // Type signature
; // Don't forget the ';' for the declaration
// Function definition:
bool // Return type
admin_detected // Function name
(string user, string password) // Parameter list
{ return user == "admin" && password == "1234"; } // Function body
ডিক্লারেশন কম্পাইলারের কাছে এক ধরনের নোটের মতো কাজ করে, যেখানে জানানো হয় যে ওই নাম, রিটার্ন টাইপ ও প্যারামিটার লিস্টের একটি ফাংশন আছে। ডেফিনিশন না থাকলে কোড কাজ করবে না। ডিক্লারেশন ঐচ্ছিক; ফাংশনের ডেফিনিশনের আগে সেটি ব্যবহার করলে ডিক্লারেশন দরকার হয়। ডিক্লারেশন সাইক্লিক রেফারেন্সের মতো সমস্যা সমাধান করতে পারে এবং ইন্টারফেসকে ইমপ্লিমেন্টেশন থেকে আলাদা করতেও ব্যবহার করা যায়।
কখনও কখনও আপনি নিশ্চিত করতে চান যে কোনো মান ইনিশিয়ালাইজ হওয়ার পর আর বদলানো যাবে না।
C++ ধ্রুবকের কোয়ালিফায়ার হিসেবে const কিওয়ার্ডটি ব্যবহার করে।
const int number_of_dragon_balls{7};
number_of_dragon_balls--; // compilation error
ধ্রুবকগুলো প্রায়শই UPPER_SNAKE_CASE-এ লেখা দেখতে পাবেন। অন্য কোনো কনভেনশন না থাকলে এই কেসিংটি ম্যাক্রোর জন্য সংরক্ষিত রাখার সুপারিশ করা হয়।
সেট করার পর কোনো ধ্রুবক ভ্যারিয়েবল বদলাতে চাইলে আপনার কোড কম্পাইল হবে না। এতে অনিচ্ছাকৃত পরিবর্তন এড়ানো যায়, আবার কম্পাইলারের জন্য অপ্টিমাইজেশনের সুযোগও তৈরি হয়। একজন মানুষ হিসেবে কোড নিয়ে চিন্তা করাও সহজ হয়ে যায়, যদি জানা থাকে যে নির্দিষ্ট কিছু অংশ বদলাবে না।
ফাংশন প্যারামিটারের কোয়ালিফায়ার হিসেবেও আপনি const ব্যবহার করতে পারেন।
string guess_number(const int& secret, const int& guess) {
if (secret < guess) return "lower.";
if (secret > guess) return "higher.";
return "exact!";
}
ফাংশনে const রেফারেন্স পাস করলে আপনি নিশ্চিত থাকতে পারেন যে সেটি অপরিবর্তিত থাকবে।
কপি করা খরচসাপেক্ষ হতে পারে এমন অবজেক্ট, যেমন বড় স্ট্রিং, এর জন্য প্রায়শই const রেফারেন্স দেখতে পাবেন।
const কোয়ালিফায়ারের তৃতীয় ব্যবহার হলো এমন মেম্বার ফাংশন, যারা ক্লাসের ইনস্ট্যান্স বদলায় না।
class Stubborn {
public:
Stubborn(string reply) {
response = reply;
}
string answer(const string& question) const {
if (question.length() == 0) { return ""; }
return response;
}
private:
string response{};
};
Stubborn ক্লাসের মেম্বার ফাংশন answer প্যারামিটার হিসেবে একটি const string& রেফারেন্স ব্যবহার করে।
এতে ফাংশনে পাস করা মূল অবজেক্ট থেকে কপি হওয়ার কাজটি এড়ানো যায়।
প্যারামিটার লিস্ট আলাদা হলে একই নামে একাধিক ফাংশন থাকতে পারে। এটাকে ফাংশন ওভারলোডিং বলা হয়, এবং সাধারণত তখনই করা হয় যখন এই ফাংশনগুলো খুব কাছাকাছি ধরনের কাজ করে।
রিটার্ন টাইপ বাদে ফাংশনের হেডারটাই হলো ফাংশনের টাইপ সিগনেচার। টাইপ সিগনেচারে কোনো পরিবর্তন হলে সেটি নতুন একটি ফাংশন।
play_sound উদাহরণটিতে ভিন্ন ভিন্ন পরিস্থিতি সামলানোর জন্য ছয়টি আলাদা ওভারলোড আছে:
// different argument types:
void play_sound(char note); // C, D, E, ..., B
void play_sound(string solfege); // do, re, mi, ..., ti
void play_sound(int jianpu); // 1, 2, 3, ..., 7
// different number of arguments:
void play_sound(string solfege, double duration);
// different qualifiers:
void play_sound(vector<string>& solfege);
void play_sound(const vector<string>& solfege);
টাইপ সিগনেচার নির্ধারিত হয় ফাংশনের নাম, প্যারামিটারের সংখ্যা, তাদের টাইপ আর তাদের কোয়ালিফায়ার দিয়ে (তবে তাদের নাম দিয়ে নয়)। রিটার্ন টাইপ স্পষ্টভাবে টাইপ সিগনেচারের অংশ নয়, আর শুধু রিটার্ন টাইপে আলাদা এমন দুটি ফাংশন থাকলে আপনি কম্পাইল এরর পাবেন। কম্পাইলার অভিযোগ করবে, কারণ দুটির মধ্যে কোনটি ব্যবহার করা উচিত তা স্পষ্ট নয়।
কিছু ফাংশন বেশ লম্বা হয়ে যেতে পারে, আর তাদের অনেক কলেই হয়তো বেশিরভাগ প্যারামিটারের জন্য একই মান ব্যবহার করা হয়। ডিফল্ট আর্গুমেন্ট দিয়ে সেই কলগুলোর পুনরাবৃত্তি এড়ানো যায়।
void record_new_horse_birth(string name, int weight, string color="brown-ish", string dam="Alruccaba", string sire="Poseidon");
record_new_horse_birth("Urban Sea", 130); // color will be brown, dam "Alruccabam", sire "Poseidon"
record_new_horse_birth("Highclere", 175, "off-white", "Fall Aspen"); // sire will be "Poseidon"
ফাংশন ডিক্লারেশন প্রায়ই ডেফিনিশনের আগে পড়া হয়, তাই ডিফল্ট আর্গুমেন্ট সেট করার জন্য এটি ভালো জায়গা। কোনো প্যারামিটারের ডিফল্ট ডিক্লারেশন থাকলে তার ডান দিকের সব প্যারামিটারেরও ডিফল্ট ডিক্লারেশন থাকতে হবে। মাঝে মাঝে জটিল ফাংশন ওভারলোডগুলোকে ডিফল্ট আর্গুমেন্টসহ কম সংখ্যক ফাংশনে রিফ্যাক্টর করা যায়, যাতে রক্ষণাবেক্ষণযোগ্যতা বাড়ে।
এই অনুশীলনীতে আপনি আপনার প্রিয় রান্নার বই থেকে নেওয়া দুর্দান্ত লাসাগনা প্রস্তুত ও রান্নার সাথে সম্পর্কিত আরও কিছু কোড লিখবেন।
আপনার সামনে পাঁচটি কাজ আছে। প্রথমটি রান্নার সাথেই সম্পর্কিত, বাকি চারটি নিখুঁত প্রস্তুতি নিয়ে।
আগামীবার যে লাসাগনা তৈরি করবেন, তার জন্য যথেষ্ট সময় রাখা আছে কিনা তা নিশ্চিত করতে চান, যাতে রান্নাটা উপভোগ করতে পারেন। আপনার লাসাগনায় কোন কোন স্তর থাকবে তা আপনি আগেই পরিকল্পনা করেছেন। এখন সেটির ভিত্তিতে প্রস্তুতিতে কত সময় লাগবে তা হিসাব করতে চান।
এমন একটি ফাংশন preparationTime বাস্তবায়ন করুন, যা string হিসেবে স্তরের একটি ভেক্টর এবং প্রতি স্তরের গড় প্রস্তুতির সময় মিনিটে int হিসেবে গ্রহণ করে।
ফাংশনটি স্তরের সংখ্যার ভিত্তিতে মোট প্রস্তুতির সময়ের হিসাব int হিসেবে রিটার্ন করবে।
গড় প্রস্তুতির সময় পাঠানো না হলে 2 ডিফল্ট মান হিসেবে ব্যবহার করা হবে।
std::vector<std::string> layers{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"};
preparationTime(layers, 3)
// => 18
preparationTime(layers)
// => 12
সময় আলাদা রাখার পাশাপাশি, আপনার স্বপ্নের লাসাগনা রান্নার জন্য যথেষ্ট সস ও নুডলস আছে কিনা তাও নিশ্চিত করতে চান। আপনার লাসাগনার প্রতিটি নুডলস স্তরের জন্য ৫০ গ্রাম নুডলস লাগবে। আপনার লাসাগনার প্রতিটি সস স্তরের জন্য ০.২ লিটার সস লাগবে।
quantities ফাংশনটি ডিফাইন করুন, যা প্যারামিটার হিসেবে string আকারে স্তরের একটি ভেক্টর নেয়।
এরপর ফাংশনটি আপনার খাবার তৈরির জন্য প্রয়োজনীয় নুডলস ও সসের পরিমাণ নির্ধারণ করবে।
ফলাফলটি একটি amount struct হিসেবে রিটার্ন করা উচিত, যা আপনার জন্য আগেই ডিফাইন করা আছে।
amount-এ দুটি মেম্বার ভ্যারিয়েবল আছে, একটি noodles-এর ওজন int হিসেবে, আর দ্বিতীয়টি sauce-এর আয়তন double হিসেবে।
struct amount { int noodles; double sauce; };
amount needed{quantities({"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})};
// needed.noodles => 100
// needed.sauce => 0.4
কিছুদিন আগে আপনি এক বন্ধুর বাড়িতে গিয়েছিলেন এবং সেখানে লাসাগনা খেয়েছিলেন। এটি ছিল অসাধারণ এবং এতে বিশেষ কিছু ছিল। বন্ধুটি আপনাকে উপকরণের তালিকা পাঠিয়েছিলেন এবং বলেছিলেন, তালিকার শেষ জিনিসটিই হলো সেই "গোপন উপকরণ", যা খাবারটিকে এত বিশেষ করে তুলেছিল। এখন আপনি সেই গোপন উপকরণটিও আপনার রেসিপিতে যোগ করতে চান।
এমন একটি ফাংশন addSecretIngredient লিখুন, যা প্যারামিটার হিসেবে দুটি vector of strings গ্রহণ করে।
প্রথম প্যারামিটারটি আপনার নিজের রেসিপির উপকরণের একটি রেফারেন্স, আর দ্বিতীয় ভেক্টর-টিতে স্ট্রিং হিসেবে আছে আপনার বন্ধু যে উপকরণগুলো পাঠিয়েছেন।
আপনার উপকরণ তালিকার শেষ এলিমেন্টটি সবসময় "?"।
ফাংশনটি সেটিকে আপনার বন্ধুর তালিকার শেষ জিনিস দিয়ে প্রতিস্থাপন করবে।
নোট: addSecretIngredient কিছুই রিটার্ন করে না, আপনাকে সরাসরি আপনার উপকরণের তালিকাটি পরিবর্তন করতে হবে।
আপনার বন্ধুর উপকরণের তালিকাটিও একটি রেফারেন্স হতে পারে (পারফরম্যান্স উন্নত করার জন্য), কিন্তু এটি পরিবর্তন করা যাবে না।
const রেফারেন্স দিয়ে যেন তালিকাটি পরিবর্তন না হয় তা নিশ্চিত করুন।
std::vector<std::string> friendsList{"noodles", "sauce", "mozzarella", "kampot pepper"};
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
addSecretIngredient(myList, friendsList);
// myList => {"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}
আপনার রান্নার বইয়ে দেওয়া পরিমাণ দিয়ে কেবল দুই ভাগের জন্য যথেষ্ট লাসাগনা তৈরি হয়। আগামীবার আরও মানুষের জন্য রান্না করতে চান বলে, বিভিন্ন সংখ্যক ভাগের জন্য পরিমাণ হিসাব করতে চান।
এমন একটি ফাংশন scaleRecipe বাস্তবায়ন করুন, যা দুটি প্যারামিটার নেয়।
double পরিমাণের একটি ভেক্টর।ফাংশনটি আপনার কাঙ্ক্ষিত সংখ্যক ভাগের জন্য প্রয়োজনীয় পরিমাণের একটি নতুন ভেক্টর রিটার্ন করবে।
তবে আপনি মূল রেসিপিটি অপরিবর্তিত রাখতে চান।
এর মানে হলো এই ফাংশনে quantities আর্গুমেন্টটি পরিবর্তন করা উচিত নয়।
std::vector<double> quantities{1.2, 3.6, 10.5};
scaleRecipe(quantities, 4);
// => { 2.4, 7.2, 21 }
আপনার শৈশবের গল্প মনে পড়ে, আপনার প্রপিতামহীর কাহিনি, যিনি পরিবারের গভীরতম রান্নার রহস্যের রক্ষক। কথিত আছে, কল্পনাতীত স্বাদ উন্মোচনের চাবিকাঠি তাঁর কাছেই আছে, তবে তাঁর পদ্ধতি অপ্রচলিত। তিনি তালিকা বা মাপ নিয়ে কাজ করেন না; তিনি ফিসফিস করে বলেন, একবারে কেবল একটি উপকরণ প্রকাশ করেন।
উত্তেজনা আর শঙ্কার মিশ্রণে আপনি আপনার প্রপিতামহীর সাথে যোগাযোগ করার সিদ্ধান্ত নেন। প্রত্যাশামতোই, তিনি তাঁর রহস্যময় ভঙ্গিতে উত্তর দেন, রহস্যে মোড়া একটি মাত্র গোপন উপকরণ দেন।
আপনার প্রপিতামহীর অবদান জটিলতার একটি স্তর যোগ করে, যা আপনি আপনার রেসিপিতে ব্যবহার করতে চান।
addSecretIngredient ফাংশনের আরেকটি সংস্করণ বাস্তবায়ন করুন, যা দুটি আর্গুমেন্ট গ্রহণ করে: আপনার উপকরণ তালিকা উপস্থাপন করা একটি ভেক্টরের রেফারেন্স এবং আপনার প্রপিতামহীর কাছ থেকে পাওয়া রহস্যময় উপহার উপস্থাপন করা একটি স্ট্রিং।
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
std::string secretIngredient{"spice melange"};
addSecretIngredient(myList, secretIngredient);
// myList => {"noodles", "meat", "sauce", "mozzarella", "spice melange"}
শুভকামনা, সাহসী রাঁধুনি!
Exercism-এ সাইন আপ করুন, C++ ট্র্যাকের 19টি কনসেপ্ট100টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।