মডিউল ১৫ঃ পয়েন্টার
মডিউল ১৫-০ঃসূচনা
এই মডিউলে আমরা কি কি শিখবোঃ
- পয়েন্টার সম্পর্কে জানবো
- পাস বাই ভেলু এবং পাস বাই রেফারেন্স সম্পর্কে জানবো
- এরে এর সাথে পয়েন্টার এর সম্পর্ক জানবো
- ফাংশন এর মধ্যে এরে এবং স্ট্রিং নিয়ে কিভাবে কাজ হয় তা জানবো
মডিউল ১৫-১ঃ পয়েন্টার
আমরা অনেক ধরনের ভেরিয়েবল নিয়ে কাজ করেছি। আমরা জানি এই ভেরিয়েবলগুলো মেমরিতে সেইভ থাকে। প্রতিটি মেমরির একটি এড্রেস থাকে। আমরা যদি এড্রেসটি দেখতে চাই তাহলে এভাবে লিখতে পারিঃ
Code:
#include <stdio.h>
int main()
{
int x = 10;
printf("Value = %d\n", x); // x এর মান প্রিন্ট করছি।
printf("Address = %p\n", &x); // x ভেরিয়েবলটি মেমরিতে কোন এড্রেসে সেইভ আছে তা প্রিন্ট করছি।
}কোডটি রান করলে দেখব শুরুতে x এর মান প্রিন্ট হচ্ছে তারপর তার এড্রেস। এড্রেসটি হেক্সাডেসিমেল ফরম্যাটে প্রিন্ট হবে। এড্রেস প্রিন্ট করতে হলে আমাদের %p ব্যাবহার করতে হয়। এবং ভেরিয়েবল এর আগে & দিতে হয়।
এবার আমাদের যদি কখনো এই এড্রেসটি স্টোর রাখতে হয় তাহলে আমরা একটি পয়েন্টার ভেরিয়েবল নিতে পারি। পয়েন্টার ভেরিয়েবল অন্য একটি ভেরিয়েবল এর এড্রেসকে স্টোর করে। পয়েন্টার ভেরিয়েবল ডিক্লেয়ার করার সিন্টেক্সঃ
data_type ( যেই ভেরিয়েবল এর এড্রেস স্টোর করা হবে তার ডাটা টাইপ ) * pointer_variable_name = & variable_name ( যেই ভেরিয়েবল এর এড্রেস স্টোর করা হবে )
Code:
#include <stdio.h>
int main()
{
int x = 10;
printf("Address = %p\n", &x); // x এর এড্রেস সরাসরি প্রিন্ট করা হচ্ছে।
int* p = &x; // x এর এড্রেস একটি পয়েন্টার ভেরিয়েবলে রাখা হচ্ছে। এখানে পয়েন্টার ভেরিয়েবলটি x কে পয়েন্ট করবে তাই এর ডাটা টাইপ হিসেবে int* দেওয়া হয়েছে। পয়েন্টার এর নাম p দেওয়া হয়েছে।
printf("Address = %p\n", p); // পয়েন্টার ভেরিয়েবলটি প্রিন্ট করা হচ্ছে।
}কোডটি রান করলে আমরা দেখতে পাব সরাসরি x এর এড্রেস প্রিন্ট করা এবং x এর এড্রেস একটি পয়েন্টার ভেরিয়েবলে রেখে তারপর সেই পয়েন্টার প্রিন্ট করা দুইক্ষেত্রেই আমরা সেইম আউটপুট পাচ্ছি।
পয়েন্টার ভেরিয়েবল এর সুবিধা হচ্ছে পয়েন্টার যাকে পয়েন্ট করছে তাকে সরাসরি এক্সেস করা যায় এমনকি সেই ভেরিয়েবল এর মান ও চেঞ্জ করে দেওয়া যায়। এটিকে বলা হয় ডিরেফেরেন্সিং। পয়েন্টার ভেরিয়েবল এর আগে * এই সাইনটি দিয়ে যাকে পয়েন্ট করছে তার ভেলু এক্সেস করা যায়।
Code:
#include <stdio.h>
int main()
{
int x = 10;
int* p = &x; // x এর এড্রেস একটি পয়েন্টার ভেরিয়েবলে রাখা হচ্ছে।
printf("Value = %d\n", *p); // পয়েন্টার দিয়ে x এর ভেলু প্রিন্ট করা হচ্ছে।
*p = 20; // পয়েন্টার দিয়ে x এর মান চেঞ্জ করে দেওয়া হচ্ছে।
printf("Value = %d\n", x); // পয়েন্টার দিয়ে চেঞ্জ করার পর x কে প্রিন্ট করে দেখা যাচ্ছে x এর মান চেঞ্জ হয়ে গিয়েছে。
}এখানে আমরা শুরুতে পয়েন্টার দিয়ে x এর মান প্রিন্ট করে দেখলাম x এর মান যা ছিল তাই প্রিন্ট হয়েছে। তারপর পয়েন্টার দিয়ে x এর মান চেঞ্জ করে দেখা গেল x এর মানও চেঞ্জ হয়ে গিয়েছে।
মডিউল ১৫-২,১৫-৩ঃ পয়েন্টার ডিরেফারেন্সিং
আমরা আরেকটি কন্সেপ্ট দেখেছিলাম তা হলো ডিরেফারেন্সিং। পয়েন্টার যেই এড্রেস স্টোর করছে, সেই এড্রেস এ যেই ভেলুটি আছে তা এক্সেস করার পদ্ধতির নাম হলো ডিরেফারেন্সিং। শুধু এক্সেস না আমরা চাইলে পয়েন্টার এর মাধ্যমে ঐ ভেলুটি চেঞ্জও করে দিতে পারি।
Code:
#include <stdio.h>
int main()
{
int x = 10;
int* p = &x;
printf("x er value: %d\n", x); // x দিয়ে x এর ভেলু প্রিন্ট করা হচ্ছে।
printf("x er value: %d\n", *p); // পয়েন্টার দিয়ে x এর ভেলু প্রিন্ট করা হচ্ছে।
}কোড রান করলে দেখা যাবে সেইম আউটপুট প্রিন্ট হচ্ছে। তারমানে x এবং *p আসলে একই।
মডিউল ১৫-৪ঃ পাস বাই ভেলু
একটি ফাংশনের মধ্যে যখন আমরা কোন ভেরিয়েবল পাস করি তখন সেটি পাস বাই ভেলু হিসেবে যায়। ওই ফাংশনে পাস করার পর ওই ভেলুতে কোন চেঞ্জ করলে সেটি মেইন ফাংশন থেকে চেঞ্জ হয়ে যায় না। ভেলুটি পুরোপুরি একটি ভিন্ন ভেরিয়েবল হিসেবে পাস হয়।
Code:
#include <stdio.h>
void fun(int x) // ফাংশন x কে রিসিভ করছে প্যারামিটার হিসেবে।
{
x = 100; // x এর ভেলু চেঞ্জ করে ১০০ করে দেওয়া হচ্ছে।
}
int main()
{
int x = 10; // শুরুতে x এর মধ্যে ১০ রাখা হলো
fun(x); // তারপর এই x টিকে ফাংশনের মধ্যে পাস করে দেওয়া হলো।
printf("%d\n", x); // x এর ভেলু প্রিন্ট করা হচ্ছে।
}কোডটি রান করলে দেখতে পারব x এর মান ১০ প্রিন্ট হয়েছে। যদিও প্রিন্ট করার আগে আমরা একটি ফাংশন কল করে সেই ফাংশনে x কে পাঠিয়ে দিয়েছিলাম এবং সেই ফাংশনের মধ্যে x এর মান চেঞ্জ করে দিয়ে ১০০ করে দিচ্ছিলাম। তারপরও ফাংশন কল হওয়া শেষে যখন x প্রিন্ট করা হচ্ছে তখন দেখা গেল x এর ভেলু চেঞ্জ হয়নি। কারন মেইন এর x এবং ফাংশনের x দুটি ভিন্ন ভেরিয়েবল। আমরা যদি দুটি ভেরিয়েবল এর এড্রেস প্রিন্ট করে দেখিঃ
Code:
#include <stdio.h>
void fun(int x)
{
printf("fun er x er address: %p\n", &x); // ফাংশনের ভিতর x এর এড্রেস প্রিন্ট করা হচ্ছে।
}
int main()
{
int x = 10;
fun(x);
printf("main er x er address: %p\n", &x); // মেইন ফাংশনের ভিতর x এর এড্রেস প্রিন্ট করা হচ্ছে।
}কোডটি রান করলে দেখব দুই ভেরিয়েবল এর দুটি ভিন্ন এড্রেস প্রিন্ট হয়েছে। এতে বুঝা গেল ফাংশনের মধ্যে ভেলু হিসেবে পাস করলে মেমরিতে দুটি আলাদা আলাদা ভেরিয়েবল হিসেবে থাকে।
মডিউল ১৫-৫,১৫-৬ঃ পাস বাই রেফারেন্স + পাস বাই ভ্যালু পাস বাই রেফারেন্স এনিমেশন
একটি ভেরিয়েবল ফাংশনে পাস করার পর যদি তাতে কোন চেঞ্জ করা হয় আর সেই চেঞ্জটি যদি আমরা মেইন ফাংশন থেকেও এক্সেস করতে চাই তাহলে ভেরিয়েবলটিকে আমরা রেফারেন্স হিসেবে পাস করতে পারি অর্থাৎ সেই ভেরিয়েবলটির এড্রেস পাস করতে পারি এবং ফাংশনে সেই এড্রেসটি পয়েন্টার দিয়ে প্যারামিটার হিসেবে রিসিভ করতে পারি।
Code:
#include <stdio.h>
void fun(int* p) // যেহেতু এই ফাংশনে এড্রেস পাঠানো হচ্ছে আর আমরা জানি এড্রেস রিসিভ করার জন্য পয়েন্টার ব্যাবহার করতে হয় তাই এখানে প্যারামিটার হিসেবে একটি পয়েন্টার ভেরিয়েবল নেওয়া হয়েছে।
{
*p = 100; // পয়েন্টার এর সাহায্যে ডিরেফারেন্সিং করে x এর মান চেঞ্জ করা হচ্ছে।
}
int main()
{
int x = 10; // শুরুতে x এর ভেলু ১০ রাখা হয়েছে।
fun(&x); // ফাংশনকে কল করে x এর এড্রেস পাঠানো হয়েছে।
printf("x er value: %d\n", x); // x এর মান প্রিন্ট করা হচ্ছে।
}কোডটি রান করলে দেখতে পাব x এর মান ১০০ প্রিন্ট হচ্ছে। যদিও শুরুতে x এর মান ১০ ছিল। তারপর এই x কে আমরা রেফারেন্স হিসেবে পাস করে দিয়েছি ফাংশনের মধ্যে। সেই ফাংশনে যখন এই x এর মান চেঞ্জ করে ১০০ করে দেওয়া হলো তখন এই চেঞ্জটি মেইন ফাংশনেও হয়েছে। তাই ফাংশন কল হওয়া শেষে যখন x এর মান প্রিন্ট করা হচ্ছে তখন ১০০ প্রিন্ট হচ্ছে।
এটাই হচ্ছে কল বাই রেফারেন্স।
মডিউল ১৫-৭ঃ scanf এ কেনো & সাইন ব্যবহৃত হয়
scanf ফাংশনে ভ্যারিয়েবলের নামের আগে & দেয়ার কারণ হলো, আমরা জানি, যখন আমরা কোনো ভ্যারিয়েবলের ভ্যালু কোনো ফাংশনের মাধ্যমে পরিবর্তন করতে চাই, তখন আমাদের ভ্যারিয়েবলটি ফাংশনে পাস বাই রেফারেন্সের সাহায্যে পাঠাতে হয়। পাস বাই রেফারেন্সের সাহায্যে না পাঠালে উক্ত ফাংশনে ঐ ভ্যারিয়েবলের একটি কপি ক্রিয়েট হয়, যা মেইন ফাংশনের ভ্যারিয়েবলের ভ্যালুটি পরিবর্তন করতে পারে না।
অপরদিকে, printf() ফাংশনের সাহায্যে যেহেতু আমরা শুধুমাত্র ভ্যালুটি আউটপুটে দেখি এবং এক্ষেত্রে ভ্যারিয়েবলের ভ্যালু পরিবর্তনের কোনো প্রয়োজন হয় না, তাই printf এর সাথে & অর্থাৎ রেফারেন্স পাস হয় না।
মডিউল ১৫-৮ঃ এরে এবং পয়েন্টার
আমরা যখন একটি এরে ডিক্লেয়ার করি তখন এরের নামটি আসলে একটি পয়েন্টার হিসেবে কাজ করে কারন এরের নামটি প্রথম ইন্ডেক্স এর এড্রেসকে স্টোর করে। আমরা যদি প্রমান দেখতে চাইঃ
Code:
#include <stdio.h>
int main()
{
int a[5] = {10,20,30,40,50}; // ৫ সাইজের একটি এরে ডিক্লেয়ার করা হয়েছে।
printf("0th index er address : %p\n", &a[0]); // ০ তম ইন্ডেক্সের এড্রেস প্রিন্ট করা হচ্ছে।
printf("0th index er address : %p\n", a); // ০ তম ইন্ডেক্সের এড্রেস প্রিন্ট করা হচ্ছে।
}কোডটি রান করলে দেখতে পাব দুইক্ষেত্রেই সেইম আউটপুট আসছে। তারমানে ০ তম ইন্ডেক্সের এড্রেস প্রিন্ট করা আর এরের নামটি প্রিন্ট করা আসলে একই কথা। এতেই প্রমান হয় এরের নামটি আসলে একটি পয়েন্টার।
আমরা যখন একটি পয়েন্টার ভেরিয়েবল প্রিন্ট করি তখন সেটি যেই এড্রেস স্টোর করে আছে তা প্রিন্ট করে। এখানে আমরা যখন এরের নামটি প্রিন্ট করলাম তখন সে ০ তম ইন্ডেক্সের এড্রেসটি প্রিন্ট করল। তারমানে এরের নামটি একটি পয়েন্টার যা এরের ০ তম ইন্ডেক্সের এড্রেস স্টোর করে।
যেহেতু এরে একটি পয়েন্টার তাই একে আমরা ইন্ডেক্স ছাড়াই পয়েন্টার দিয়ে পরবর্তী ভেলু গুলো এক্সেস করতে পারি। এরে একটি পয়েন্টার যা এরের প্রথম ইন্ডেক্সের এড্রেস স্টোর করে তাই এরেকে যদি আমরা ডিরেফারেন্স করি তাহলে প্রথম ইন্ডেক্সের ভেলু পাওয়া যাবে। সেইমভাবে আমরা যদি এরের নামের সাথে ১ যোগ করে তারপর ডিরেফারেন্স করি তাহলে দ্বিতীয় ইন্ডেক্সের ভেলু পাওয়া যাবে।
Code:
#include <stdio.h>
int main()
{
int a[5] = {10,20,30,40,50};
printf("1st value = %d\n", *a); // প্রথম ভেলু (১০) প্রিন্ট হবে।
printf("2nd value = %d\n", *(a+1)); // দ্বিতীয় ভেলু (২০) প্রিন্ট হবে।
}মডিউল ১৫-৯,১৫-১০ঃ ফাংশন এবং এরে
এখন আমরা দেখব কিভাবে একটি ফাংশনে এরে পাস করতে হয়।
নরমালি আমরা একটি ভেরিয়েবল যখন ফাংশনে পাস করতে চাই তখন শুধু তার নাম দিয়ে পাস করলেই হয়ে যায় কিন্তু এরে শুধু নাম দিয়ে পাস করলে হয় না।
Code:
#include <stdio.h>
void func(int *a) // একটি পয়েন্টার দিয়ে এরে রিসিভ করা হচ্ছে
{
int sz = sizeof(a)/sizeof(int); // সাইজ বের করা হচ্ছে। সেইম মেইন ফাংশনের মতো করে।
printf("Size in func = %d\n", sz); // ফাংশনের ভিতর এরে সাইজ বের করা হচ্ছে।
}
int main()
{
int n;
scanf("%d", &n); // এরে সাইজ ইনপুট নেওয়া হচ্ছে।
int a[n];
for(int i=0;i<n;i++)
{
scanf("%d", &a[i]); // এরে এলিমেন্ট ইনপুট নেওয়া হচ্ছে।
}
int sz = sizeof(a)/sizeof(int);
printf("Size in main function = %d\n", sz); // এরে সাইজ প্রিন্ট করা হচ্ছে।
func(a); // ফাংশনকে কল করে এরে পাস করে দেওয়া হচ্ছে।
}কোডটি রান করে এরে ইনপুট দিয়ে দেখতে পাব মেইন ফাংশনে সাইজ ঠিকঠাক আছে কিন্তু ফাংশনে এরের সাইজ ১ প্রিন্ট হচ্ছে। এরে ইনপুট যাই দেওয়া হোক না কেন ফাংশনে এরের সাইজ ১ প্রিন্ট হবে।
এর কারন মেইন ফাংশন যেখানে এরে ইনপুট নেওয়া হয়েছে তা এরের সাইজ ট্র্যাক রাখে কিন্তু ফাংশনে পাস করার পর সেটি আর এরে সাইজ ট্র্যাক রাখতে পারে না। তাই ফাংশনে এরে পাস করতে হলে আমাদের সাইজ ও পাস করে দিতে হয়।
Code:
#include <stdio.h>
void func(int a[], int n) // প্যারামিটার হিসেবে প্রথমে এরে নেওয়া হচ্ছে, তারপর এরে সাইজ।
{
for(int i=0;i<n;i++)
{
printf("%d ", a[i]); // এরে এলিমেন্ট প্রিন্ট করা হচ্ছে।
}
}
int main()
{
int n;
scanf("%d", &n);
int a[n];
for(int i=0;i<n;i++)
{
scanf("%d", &a[i]); // এরে এলিমেন্ট ইনপুট নেওয়া হচ্ছে।
}
func(a, n); // ফাংশনকে কল করে একই সাথে এরে এবং এরের সাইজ পাস করে দেওয়া হচ্ছে।
}এই কোডটি রান করলে আমরা দেখব সব ঠিকঠাক ভাবে প্রিন্ট হয়েছে।
তাই ফাংশনে এরে পাস করতে হলে আমাদের সাইজও আলাদাভাবে পাস করে দিতে হয়।
এক্ষেত্রে ফাংশনের প্যারামিটারে আমরা int a[] ( এরে হিসেবে ) অথবা int *a ( পয়েন্টার হিসেবে ) দুইভাবেই নিতে পারি।
আমরা যখন কোন ফাংশনে কোন ভেরিয়েবল পাস করতাম তখন সেটি পাস বাই ভেলু হিসেবে পাস হতো অর্থাৎ কপি হয়ে পাস হতো। তাই পাস করার পর যদি সেই ভেরিয়েবলটির মান চেঞ্জ করে দেওয়া হতো তাহলে মেইন ফাংশন থেকে সেটি চেঞ্জ হয়ে যেত না। এবার আমরা একটি এরে পাস করে ফাংশন থেকে সেই এরের কোন একটি ভেলু চেঞ্জ করে দেখি মেইন ফাংশন থেকেও চেঞ্জ হয়ে যায় কিনা।
Code:
#include <stdio.h>
void func(int a[], int n) // এরে এবং সাইজ প্যারামিটার হিসেবে নেওয়া হচ্ছে।
{
a[0] = 100; // এরের প্রথম ইন্ডেক্সের ভেলু চেঞ্জ করে ১০০ করে দেওয়া হচ্ছে।
}
int main()
{
int a[5] = {10,20,30,40,50};
func(a, 5); // ফাংশন কল করে এরে এবং সাইজ পাস করে দেওয়া হচ্ছে।
for(int i=0;i<5;i++)
{
printf("%d ", a[i]); // এরে প্রিন্ট করা হচ্ছে।
}
}কোডটি রান করলে আমরা দেখব ফাংশনের মধ্যে এরের যেই চেঞ্জটি করা হয়েছিল তা মেইন ফাংশন থেকেও পাওয়া যাচ্ছে। কারন এরে আসলে কপি হয়ে পাস হয় না এরে পাস হয় রেফারেন্স হিসেবে। তাই রেফারেন্স হিসেবে পাস হওয়ার পর ডিরেফারেন্স করে যখন তাতে কোন চেঞ্জ করা হচ্ছে তখন সেটি অরিজিনাল এরে থেকেই চেঞ্জ হয়ে যাচ্ছে। তাই মেইন ফাংশন থেকেও চেঞ্জ হয়ে যাচ্ছে।
মডিউল ১৫-১১ঃ ফাংশন এবং স্ট্রিং
এখন আমরা দেখব কিভাবে একটি ফাংশনে স্ট্রিং পাস করতে হয়।
আমরা একটি ফাংশনে স্ট্রিং পাস করে দেখি তার সাইজ অর্থাৎ তার লেন্থ পাওয়া যায় কিনা।
Code:
#include <stdio.h>
#include <string.h>
void func(char s[]) // প্যারামিটার হিসেবে স্ট্রিং নিচ্ছি
{
int sz = strlen(s); // স্ট্রিং এর লেন্থ বের করা হচ্ছে।
printf("%d", sz); // লেন্থ প্রিন্ট করা হচ্ছে।
}
int main()
{
char s[10] = "Hello";
func(s); // ফাংশনে স্ট্রিং পাস করে দেওয়া হচ্ছে।
}কোডটি রান করলে আমরা দেখব ফাংশনের ভিতর স্ট্রিং পাস করে দেওয়ার পর ঠিকঠাক ভাবে তার লেন্থ প্রিন্ট হচ্ছে। যদিও আমরা এখানে আলাদা করে স্ট্রিং এর লেন্থ বা সাইজ কিছুই পাস করি নি। এটি কাজ করছে কারন আমরা এক্ষেত্রে লেন্থ বের করার জন্য strlen() ফাংশন ব্যাবহার করছি। strlen() ফাংশন যেকোন স্ট্রিং এর শুরু থেকে নাল এর আগে পর্যন্ত কাউন্ট করে লেন্থ বলে দেয় তাই এখানে লেন্থ পাস না করেও আমরা ঠিকঠাক ভাবে লেন্থ পাচ্ছি ফাংশনের ভিতর।
তাই ফাংশনে স্ট্রিং পাস করে কাজ করতে হলে আমাদের আলাদাভাবে ঐ স্ট্রিং এর লেন্থ পাস করার প্রয়োজন নেই।
