Lesson পড়তে ৮ মিনিট লাগবে

ফাংশন (Functions)

সি++ (C++) ফাংশনগুলো হলো অনেকটা সুইস আর্মি নাইফের (Swiss Army knives) মতো — এদের নাম একই থাকে, কিন্তু একেকটির ব্লেড (blades) একেক রকম, এবং এমনকি এরা চাইলে ডিফল্ট সেটিংসও (default settings) নিতে পারে

শুধুই "কল (Call) এবং রিটার্ন (Return)"-এর চেয়েও অনেক বেশি কিছু

বেশিরভাগ ভাষাতেই (languages), কোনো ফাংশনের নাম (function name) মূলত ইউনিক বা অদ্বিতীয় (unique) হয়ে থাকে — অর্থাৎ একটি নাম (one name), একটি কাজ বা আচরণ (one behavior)। কিন্তু সি++ (C++) মূলত এই নিয়মটিকে বেশ শক্তিশালী একটি উপায়ে ভেঙেছে। এক্ষেত্রে আপনার একই নামের একাধিক ফাংশন (multiple functions with the same name) থাকতে পারে, যেগুলো মূলত আপনি সেগুলোতে কী পাস (pass) করছেন তার ওপর ভিত্তি করে ভিন্ন ভিন্ন কাজ (different things) করতে পারে। একে মূলত ফাংশন ওভারলোডিং (function overloading) বলা হয়, আর এটিই হলো এখানকার অন্যতম একটি বৈশিষ্ট্য যা মূলত সি++-কে (C++) এতটা এক্সপ্রেসিব বা উন্নত ও সমৃদ্ধ (expressive) করে তোলে।

তবে এটি তো কেবল শুরু। সি++ (C++) মূলত আপনাকে এর যেকোনো ডিফল্ট আর্গুমেন্টের মানগুলোকেও (default argument values) সেট করার সুযোগ দেয়, এর ডেটাগুলোকে কপি (copy) বা রেফারেন্স (reference) করতে দেয়, এবং এমনকি কাজের মাঝখানেই (on the fly) কিছু ছোট ও নামহীন ফাংশনও (anonymous functions) তৈরি করার সুযোগ করে দেয়। চলুন, এদের প্রত্যেকটি টুল (tools) নিয়ে বিস্তারিত আলোচনা করা যাক।

ফাংশন ওভারলোডিং (Function Overloading) — একই নাম, তবে ব্লেড আলাদা (Same Name, Different Blades)

একটি print() ফাংশনের কথা চিন্তা করুন। ধরুন কখনো আপনি একটি ইনটিজারকে (integer) প্রিন্ট (print) করতে চান। আবার কখনো একটি স্ট্রিংকে (string)। কিংবা কখনো একটি ভেক্টরকে (vector)। এক্ষেত্রে সি-তে (C) ওই কাজগুলো করার জন্য আপনার আলাদা আলাদা করে printInt(), printStr(), printVec() নামের ফাংশনের প্রয়োজন হতো। কিন্তু সি++ (C++)-এ, এগুলো মূলত সবই শুধু print() নামেই কল বা ডাকা (called) হতে পারে — কারণ এখানকার কম্পাইলারটি (compiler) মূলত আপনার দেওয়া ওই আর্গুমেন্টগুলোর সংখ্যা এবং ধরন বা টাইপের (number and types of arguments) ওপর ভিত্তি করেই বুঝে ফেলে (figures out) যে এখানে ঠিক কোনটিকে কল (call) করতে হবে।

কার্যকর বা অ্যাকশনে থাকা ফাংশন ওভারলোডিং (Function Overloading in Action)

#include <iostream>
#include <string>
#include <vector>
using namespace std;
void print(int x) {
cout << "Integer: " << x << endl;
}
void print(const string& s) {
cout << "String: " << s << endl;
}
void print(const vector<int>& v) {
cout << "Vector: [";
for (size_t i = 0; i < v.size(); i++) {
if (i > 0) cout << ", ";
cout << v[i];
}
cout << "]" << endl;
}
int main() {
print(42);
print(string("hello"));
print(vector<int>{1, 2, 3, 4, 5});
return 0;
}
Output
Integer: 42
String: hello
Vector: [1, 2, 3, 4, 5]

ডিফল্ট আর্গুমেন্ট (Default Arguments) — নব বা নবগুলোকে আগে থেকেই সেট করা (Pre-Set the Knobs)

কখনো কখনো কোনো একটি ফাংশনের একাধিক প্যারামিটার (parameters) থাকতে পারে, যেগুলো বেশিরভাগ সময়ই (usually) একই মান (same value) নিয়ে কাজ করে থাকে। এক্ষেত্রে যিনি কল (callers) করছেন তাকে দিয়ে প্রতিবার সেগুলো টাইপ করানোর বদলে, আপনি চাইলে সেখানকার প্যারামিটারগুলোর জন্য কিছু ডিফল্ট ভ্যালু (default values) দিয়ে দিতে পারেন। তবে এই ডিফল্টগুলোর ব্যবহার অবশ্যই ডানদিকের একেবারে শেষের (rightmost) প্যারামিটারটি থেকে শুরু করে তারপর বাম দিকে (left) যেতে হতে হবে — অর্থাৎ আপনি মাঝখান থেকে (in the middle) কোনো প্যারামিটারকে স্কিপ বা এড়িয়ে (skip) যেতে পারবেন না।

ডিফল্ট আর্গুমেন্ট (Default Arguments)

#include <iostream>
#include <string>
using namespace std;
void greet(const string& name, const string& greeting = "Hello", int times = 1) {
for (int i = 0; i < times; i++) {
cout << greeting << ", " << name << "!" << endl;
}
}
int main() {
greet("Alice"); // এখানকার দুটি ডিফল্টকেই (defaults) ব্যবহার করে
greet("Bob", "Hey"); // শুধুমাত্র এখানকার times-এর ডিফল্টটিকে (default) ব্যবহার করে
greet("Charlie", "Yo", 3); // কোনো ডিফল্টই (defaults) ব্যবহার করা হয়নি
return 0;
}
Output
Hello, Alice!
Hey, Bob!
Yo, Charlie!
Yo, Charlie!
Yo, Charlie!

পাস বাই ভ্যালু (Pass by Value) বনাম পাস বাই রেফারেন্স (Pass by Reference)

এটি মূলত এমন একটি জায়গা যেখানে সি++ (C++) আপনাকে এমন কিছু নিয়ন্ত্রণ (control) দিয়ে থাকে, যা অন্যান্য প্রোগ্রামিং ভাষাগুলো (languages) বেশিরভাগ সময়ই লুকিয়ে (hide) রাখে। আপনি যখন কোনো একটি ফাংশনে কোনো আর্গুমেন্ট (argument) পাস করেন, তখন আপনি মূলত নির্ধারণ (choose) করতে পারেন যে:

  • ভ্যালুর সাহায্যে বা বাই ভ্যালু (By value) — এখানকার ফাংশনটি (function) মূলত মূল কাজটির একটি কপি (copy) রিসিভ করে বা পায়। এক্ষেত্রে এর ভেতরের কোনো পরিবর্তন (Changes inside) কখনোই মূল ডেটাটির (original) ওপর কোনো প্রভাব (affect) ফেলে না।
  • রেফারেন্সের সাহায্যে বা বাই রেফারেন্স (By reference - &) — এখানকার ফাংশনটি (function) মূলত এই আসল বা মূল (original) ডেটাটিকে পেয়ে থাকে। এক্ষেত্রে এর ভেতরের সমস্ত পরিবর্তনগুলো ওই কলারটির (caller's) ডেটাকে মডিফাই বা পরিবর্তন (modify) করে দেয়।
  • কনস্ট্যান্ট রেফারেন্সের সাহায্যে বা বাই কনস্ট রেফারেন্স (By const reference - const &) — এখানকার ফাংশনটি (function) মূলত এর আসল (original) ডেটাটিকে পেলেও সেটি কখনোই পরিবর্তন না করার প্রতিশ্রুতি (promises not to modify it) দেয়। রিড-ওনলি অ্যাক্সেসের (read-only access) ক্ষেত্রে এটি মূলত সবচেয়ে ভালো (Best of both worlds) উপায়।

এটিকে আপনি মূলত একটি বই ধার দেওয়ার (lending a book) মতো করে চিন্তা করতে পারেন। বাই ভ্যালুর (By value) মানে হলো আপনি বইটিকে ফটোকপি (photocopy) করে শুধু ওই কপিটিকেই (copy) দিচ্ছেন। বাই রেফারেন্স (By reference) মানে হলো আপনি মূলত সেই আসল বইটিকেই (actual book) হস্তান্তর করছেন। আর বাই কনস্ট রেফারেন্স (By const reference) মানে হলো আপনি বইটি হস্তান্তর করার সময় ওই লোকটিকে বলছেন যে "এটি কেবলই পড়ার জন্য, এতে কোনো কিছু লেখা (write) যাবে না।"

পাস বাই রেফারেন্স (Pass by Reference) — দ্য ক্লাসিক সোয়াপ (The Classic Swap)

#include <iostream>
using namespace std;
// By value বা ভ্যালু দিয়ে — কাজ করে না (does NOT work)
void badSwap(int a, int b) {
int temp = a;
a = b;
b = temp;
// শুধুমাত্র লোকাল কপিগুলোকে বা local copies-এ সোয়াপ (swaps) করে!
}
// By reference বা রেফারেন্সের মাধ্যমে — কাজ করে (works)!
void goodSwap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
badSwap(x, y);
cout << "After badSwap: x=" << x << " y=" << y << endl;
goodSwap(x, y);
cout << "After goodSwap: x=" << x << " y=" << y << endl;
return 0;
}
Output
After badSwap:  x=10 y=20
After goodSwap: x=20 y=10

const Reference বা কনস্ট রেফারেন্স — বড় অবজেক্টের (Large Objects) জন্য দ্রুত (Fast) এবং নিরাপদ (Safe)

#include <iostream>
#include <vector>
using namespace std;
// খারাপ (BAD): এটি সম্পূর্ণ ভেক্টরটিকে বা entire vector কপি বা copies করে (যা লাখ লাখ বা millions এলিমেন্ট বা elements হতে পারে!)
int sumByValue(vector<int> nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
// ভালো (GOOD): কোনো প্রকার কপি (copy) বা পরিবর্তন নেই, এবং const-টি মূলত যেকোনো দুর্ঘটনাবশত পরিবর্তনকে বাধা (prevents) দেয়
int sumByConstRef(const vector<int>& nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
int main() {
vector<int> data = {10, 20, 30, 40, 50};
cout << "Sum (by value): " << sumByValue(data) << endl;
cout << "Sum (by const ref): " << sumByConstRef(data) << endl;
// এদের উভয় ফাংশনই 150 ফেরত বা return দেয়, কিন্তু const ref-টি মূলত অনুলিপি বা copying করা এড়ায় (avoids)
return 0;
}
Output
Sum (by value):     150
Sum (by const ref): 150
Note: যেকোনো বড় অবজেক্টকে (large objects) সবসময়ই const রেফারেন্সের (যথা const std::string&, const std::vector<int>&) মাধ্যমেই পাস (Pass) করান — কারণ বাই ভ্যালুর (by value) মাধ্যমে পাস করালে এটি মূলত ওই সম্পূর্ণ অবজেক্টটিরই (entire object) একটি অনুলিপি বা কপি তৈরি করবে! তাই বাই ভ্যালুর (by value) এই ব্যবহারটিকে শুধু খুব ছোট, বা সহজেই কপি করা যায় এমন টাইপগুলো (cheap-to-copy types) যেমন int, char, bool, এবং double-এর ব্যাপারেই সীমিত রাখুন।

ইনলাইন ফাংশনগুলো (Inline Functions) এবং auto রিটার্ন টাইপ (Return Type)

inline হলো কম্পাইলারের (compiler) জন্য এক ধরনের সংকেত বা হিন্ট (hint): "এই ফাংশনটি (function) খুবই ছোট (tiny) — অনুগ্রহ করে একে সত্যিকারের কোনো ফাংশনে কল (call) করার পরিবর্তে, এর কল সাইটে (call site) সরাসরি এর বডিটিকে (body) পেস্ট (paste) করে দিন।" আধুনিক কম্পাইলারগুলো (Modern compilers) মূলত বেশিরভাগ সময়েই এগুলো নিজেরা (own) নিজেই সিদ্ধান্ত (decide) নিয়ে থাকে, তাই এই inline-টি বেশিরভাগ সময়ই শুধু বিভিন্ন লিঙ্কার এরর (linker errors) এড়ানোর (avoid) জন্যই বিভিন্ন হেডার-ফাইলের (header-file) ফাংশনে ব্যবহার করা হয়ে থাকে。

সি++১৪-এ (C++14) auto রিটার্ন টাইপগুলো (return types) নিয়ে আসা হয়েছিল — এটি মূলত এর return স্টেটমেন্ট (statement) থেকেই এর রিটার্ন টাইপটিকে (return type) অনুমান (deduces) করতে পারে। যা মূলত যেকোনো টেমপ্লেট (templates) এবং সাধারণ কিছু ফাংশনের (simple functions) জন্য বেশ ভালো কাজ (Handy) করে থাকে।

ল্যাম্বডাস (Lambdas) — উড়ন্ত ফাংশন বা Functions on the Fly

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
// খুব সাধারণ একটি ল্যাম্বডা (simple lambda)
auto add = [](int a, int b) { return a + b; };
cout << "3 + 4 = " << add(3, 4) << endl;
// ল্যাম্বডা উইথ ক্যাপচার (Lambda with capture) — যা বাইরের দিক (outside) থেকে কোনো একটি ভ্যারিয়েবলকে (variable) গ্র্যাব (grabs) বা ধরে রাখে
int factor = 3;
auto multiply = [factor](int x) { return x * factor; };
cout << "5 * 3 = " << multiply(5) << endl;
// ল্যাম্বডাসগুলো (Lambdas) মূলত এসটিএল অ্যালগরিদমগুলোর (STL algorithms) সাথেই বেশি মানায় বা শাইন (shine) করে
vector<int> nums = {5, 2, 8, 1, 9, 3};
// এখানকার একটি ল্যাম্বডা কম্প্যারেটর (lambda comparator) ব্যবহার করে একে বড় থেকে ছোট বা ডিসেন্ডিং অর্ডারে সেজে (descending) নিন
sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b;
});
cout << "Sorted: ";
for (int n : nums) cout << n << " ";
cout << endl;
// 4 এর চেয়ে বড় বা greater উপাদানগুলোকে কাউন্ট বা Count করে
int count = count_if(nums.begin(), nums.end(), [](int n) {
return n > 4;
});
cout << "Elements > 4: " << count << endl;
return 0;
}
Output
3 + 4 = 7
5 * 3 = 15
Sorted: 9 8 5 3 2 1
Elements > 4: 3

ল্যাম্বডা সিনট্যাক্স চিট শিট (Lambda Syntax Cheat Sheet)

এদের সাধারণ ফর্মগুলো বা রূপগুলো (general form) হলো: [capture](parameters) -> return_type { body }

  • [] — এটি মূলত বাইরের কোনো স্কোপ (outside scope) থেকে কিছুই ক্যাপচার (capture) করে না (nothing)
  • [x] — এটি মূলত ভ্যালুর সাহায্যে (by value) x-কে ক্যাপচার (capture) বা কপি করে নেয়
  • [&x] — এটি মূলত রেফারেন্সের সাহায্যে (by reference) x-কে ক্যাপচার করে নেয়
  • [=] — এটি মূলত ভ্যালুর সাহায্যে সবকিছুকেই (everything) ক্যাপচার করে নেয়
  • [&] — এটি মূলত রেফারেন্সের সাহায্যে সবকিছুকেই (everything) ক্যাপচার করে নেয়

ল্যাম্বডাগুলো (Lambdas) বিশেষত বিভিন্ন এসটিএল অ্যালগরিদমগুলোর (STL algorithms) সাথে বেশি কার্যকর (useful) হয়ে থাকে, যেমন sort, for_each, find_if, এবং count_if-এর মতো ফাংশনগুলো।

চ্যালেঞ্জ

ছোট কুইজ

ঠিক কোন ওভারলোডেড ফাংশনটিকে (overloaded function) কল (called) করা হবে, তা কীভাবে বা কী নির্ধারণ (determines) করে?
StringsClasses & Objects