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

ফাংশন কি?

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

কেন ফাংশন ব্যবহার করব?

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

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

ফাংশন লেখা

ফাংশন লেখার ক্ষেত্রে দুইটা বিষয় চলে আসে, ১) ডেক্লারেশন (Declaration) ২) ডেফিনেশন (Definition)।

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

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

ফাংশন ডেক্লারেশনের সাধারন ফরম্যাটটা এরকমঃ

1
return_type function_name(arg_type arg_name, ... , arg_type arg_name);

return_type হচ্ছে ফাংশন কি টাইপের ডাটা রিটার্ন করবে সেটা। function_name হচ্ছে ফাংশনটার নাম। arg_type হচ্ছে ফাংশনের আর্গুমেন্টের টাইপ। arg_name হচ্ছে আর্গুমেন্টের ভ্যারিয়েবলের নাম। এগুলো নিয়ে নিচে বিস্তারিত আলোচনা করছি। তার আগে আমরা ফাংশন ডেফিনেশনের ফরম্যাটটা দেখে নেই।

1
2
3
4
5
6
7
8
9
return_type function_name(arg_type arg_name, ... , arg_type arg_name)
{
    statement 1;
    statement 2;
    ...;
    ...;
    statement n;
    return return_value;
}

ডেক্লারেশনের সাথে এখানে একটা ফাংশন বডি যোগ হয়েছে। বডি অংশটা { } দিয়ে ঘেরানো আছে। এর ভেতর কতগুলো স্টেটমেন্ট আছে। ফাংশন কল হলে এই স্টেটমেন্টগুলো একের পর এক এক্সিকিউট হবে। সবশেষে ফাংশন কোন একটা মান রিটার্ন করবে।

রিটার্ন

একটা ফাংশন তার কাজ শেষ হয়ে গেলে তাকে যে কল করেছে তার কাছে কোন একটা মান ফেরত পাঠাতে পারে। এটাই হচ্ছে ফাংশনটার রিটার্ন ভ্যালু। এটা int, char, double বা কোন ইউজার ডিফাইনড ডাটা টাইপ হতে পারে। এটা পয়েন্টারও হতে পারে। আবার যদি এমন হয় যে ফাংশনটা কোন ভ্যালু রিটার্ন করবে না, কেবল কিছু স্টেটমেন্ট এক্সিকিউট করবে তাহলে রিটার্ন টাইপ হবে void। সেক্ষেত্রে ফাংশনের শেষে return স্টেটমেন্ট না লেখলেও কোন অসুবিধা নেই। লেখলেও শুধু return; লেখতে হবে যেহেতু এর কোন রিটার্ন ভ্যালু নেই।

কোন ফাংশন এক্সিকিউট হওয়ার সময় যখনই return এর সম্মুখীন হবে তখনই সে রিটার্ন করে ফাংশন থেকে বের হয়ে আসবে। এরপর যদি কোন স্টেটমেন্ট থাকে সেখানে কখনই পৌঁছাবে না।

আর্গুমেন্ট

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

আমরা যখন কোন ফাংশনে কোন অ্যারে বা স্ট্রিং পাঠাতে চাই, তখন মূলত সেই অ্যারে বা স্ট্রিং এর পয়েন্টার পাঠাই। ফলে অ্যারে বা স্ট্রিং এ কোন পরিবর্তন আনলে সেটা মূল অ্যারে বা স্ট্রিং এ হয়।

ফাংশনের নাম

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

সি লাইব্রেরিতে sort নামের একটা ফাংশন আছে। এই ফাংশনের কাজ হচ্ছে সর্ট করা। ফাংশনটিকে আমরা যেকোন ডাটা টাইপের অ্যারে পাঠাতে পারি। যদি এমন হয় আমরা কোন ইউজার ডিফাইনড ডাটা টাইপের অ্যারে পাঠাই, তাহলে ফাংশন বুঝবে কিভাবে যে সেগুলোকে কিভাবে সর্ট করতে হবে? সহজভাবেই চিন্তা করি। যদি int অ্যারেই পাঠাই, তাও ফাংশন বুঝবে কিভাবে যে সেগুলোকে ছোট থেকে বড় করে সর্ট করবে, নাকি বড় থেকে ছোট? এজন্য যেটা করা হয়, sort ফাংশনের কাছে একটা compare ফাংশন পাঠানো হয়। অর্থাৎ আমরা নিজেরা একটা compare ফাংশন ডিফাইন করে দিব। তারপর সেই ফাংশনের পয়েন্টারটাকে তৃতীয় আর্গুমেন্ট হিসেবে পাঠাবো। sort ফাংশন এরপর নিজের কাজ করার জন্য আমাদের সেই compare ফাংশনটিকে ব্যবহার করবে। এখানে sort ফাংশনের উদাহরণ পাওয়া যাবে।

সম্পূর্ণ চিত্র

ফাংশন কল, রিটার্ন এগুলো কিভাবে কাজ করে একটা সম্পূর্ণ প্রোগ্রাম দিয়ে আলোচনা করা যাক।

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int add(int a, int b);
int main()
{
    printf("Enter two numbers to add: ");
    int a, b;
    scanf("%d %d", &a, &b);
    int sum = add(a, b);
    printf("Sum: %d\n", sum);
}
int add(int a, int b)
{
    return a+b;
}

এখানে add ফাংশনটা ডিক্লেয়ার করা হয়েছে যেন কম্পাইলার বুঝতে পারে যে পরবর্তিতে কোথাও add ফাংশনটিকে ডিফাইন করা আছে। main ফাংশনের পরে আমরা add ফাংশনটিকে ডিফাইন করছি। ফাংশনটির কাজ মূলত দুটি নাম্বার নিয়ে তাদের যোগফল বের করে দেওয়া। এখন এক্সিকিউশন কিভাবে হবে সেটা দেখা যাক।

প্রথমে main ফাংশনের ভেতরে প্রোগ্রাম শুরু হবে। শুরুতে printf ফাংশনটি আছে। এটি ইউজারকে নাম্বার ইনপুট দিতে বলবে। এখানে উল্লেখ্য যে printf এর ভেতর আমরা যে স্ট্রিং পাঠাই সেটিও আসলে একটি আর্গুমেন্ট। printf এই স্ট্রিং আর্গুমেন্ট নিয়ে নিজের মত করে কাজ করে সেটিকে প্রিন্ট করে। এরপর আমরা দুটি int ডিক্লেয়ার করে scanf কল দিচ্ছি। scanf ফাংশনকেও আমরা আর্গুমেন্ট হিসাবে একটা স্ট্রিং পাঠাচ্ছি। স্ট্রিং এর সাথে আমরা ভ্যারিয়েবল দুটোর পয়েন্টা পাঠাচ্ছি। এর কারণ হচ্ছে আমরা চাই scanf ফাংশনটি ইউজারের কাছ থেকে ইনপুট নিয়ে আমাদের সেই ভেরিয়েবলে রাখুক। অতএব আমরা যদি পয়েন্টার পাঠাই তাহলে scanf ফাংশন জানে কোন মেমরি লোকেশন ইনপুট নিয়ে রাখতে হবে। এজন্যই scanf এ ভেরিয়েবলে & দিতে হয়।

এরপর আমরা আমাদের লেখা add ফাংশন কল করছি। ফাংশনের প্যারামিটার হিসেবে a এবং b কে পাঠাচ্ছি। যখন ফাংশনটি কল হল, তখন এক্সিকিউশন সেই ফাংশনের ভেতর চলে যাবে। add ফাংশনের ভেতর আর্গুমেন্ট হিসেবে a এবং b আসছে। উল্লেখ্য যে add এর আর্গুমেন্ট হিসেবে যে a এবং b আছে সেগুলো main ফাংশনের a এবং b থেকে আলাদা। add এর ভেতরেরগুলো তার নিজস্ব লোকাল স্কোপে কাজ করবে। সেগুলো সেই ফাংশনের ভেতরেই থাকবে। আর main এর গুলো তার লোকাল স্কোপে কাজ করবে। দুইটার মধ্যে কোন সম্পর্ক নেই। আমাদের add ফাংশন কেবল একটা রিটার্ন স্টেটমেন্ট। সে a+b করে সেটাকে রিটার্ন করে দিবে।

রিটার্ন করা ব্যাপারটা যাদের বুঝতে অসুবিধা, তারা ব্যাপারটাকে এভাবে চিন্তা করতে পারে যে যেখানে থেকে ফাংশনটি কল হবে, সেখানে ফাংশনটি তার রিটার্ন ভ্যালু দিয়ে প্রতিস্থাপিত হয়ে যাবে। অর্থাৎ add যদি 5 রিটার্ন করে তাহলে main এর ভেতরে যেখানে add কল হয়েছে সেখানে add এর জায়গায় 5 বসে যাবে। তাহলে সেখানে sum = 5 হবে।

এরপর আমরা ভ্যালুটা প্রিন্ট করে দিচ্ছি।

ফাংশন স্ট্যাক

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

এই ছিল ফাংশনের বেসিক আলোচনা। ফাংশন সি প্রোগ্রামিং এর একটি অপরিহার্য অংশ। বিষয়টি অনেক গভীর। যেকোন ভালো প্রোগ্রামারের ফাংশন বিষয়ে স্পষ্ট ধারণা থাকা উচিত।