প্রোগ্রামিং সি এর পয়েন্টার, স্ট্রাকচার, ফাইল অপারেশন প্রদ্ধতি?
সি ল্যাঙ্গুয়েজের একটি বেশ শক্তিশালী ফিচার হল
পয়েন্টার। পয়েন্টার দিয়ে ফাংশনের ভেতর থেকে ভ্যারিয়েবল বা অ্যারে
পাল্টানো, মেমোরি অ্যালোকেশন সহ বিভিন্ন অ্যাডভান্সড কাজ করা যায়। আমরা
অ্যারের ব্যবহার দেখেছি আগের অধ্যায়ে। অ্যারের একটি বিষয় হল এটির সাইজ
প্রথমেই বলে দিতে হয়, পয়েন্টার ব্যবহার করে আমরা চাইলে ডায়নামিক অ্যারে
তৈরি করতে পারি যেটির সাইজ প্রোগ্রাম চলার সময়ে ঠিক হবে এবং সে অনুযায়ী
মেমোরি অ্যালোকেটেড হবে।
পয়েন্টার প্রোগ্রামিং এ দারুন একটি টুল। পয়েন্টার সম্পর্কে জানার আগে কিছু
ব্যাসিক জিনিস জানা যাক, যেগুলো বুঝতে কাজে দিবে।
ভ্যারিয়েবল গুলো কিভাবে কম্পিউটার মেমরিতে/ র্যাম এ স্টোর হয়?
র্যাম এর এক একটি সেল এক একটি বাইট। আর প্রত্যেকটা বাইট এর একটি করে এড্রেস রয়েছে। আর প্রতিটা বাইটে ৮টি করে বিট রয়েছে।
আমরা যখন বলি আমাদের র্যাম 8 Giga byte, তখন
আমাদের কম্পিউটারের র্যামে মোট 8 000 000 000 bytes ডেটা স্টোর করা যাবে,
এবং এদের প্রত্যেকের একটি করে এড্রেস রয়েছে। প্রথমটি ০ পরের টি 1, এর পরের
টির এড্রেস 2 এভাবে বাড়তে থাকে। যদিও কম্পিউটার এ এড্রেস গুলো রিপ্রেজেন্ট
করে হেক্সাডেসিমেল নাম্বার সিস্টেমে।
আমরা যখন একটি ভ্যারিয়েবল ডিক্লেয়ার করার পর
যখন প্রোগ্রামটি এক্সিকিউট/রান করি তখন কম্পিউটার ঐ ভ্যারিয়েবল এর জন্য
কিছু মেমরি এলোকেট করে। কত বাইট মেমরি এলোকেট করবে, তা নির্ভর করে ঐ
ভ্যারিয়েবল এর ডেটা টাইপ এবং কম্পাইলার এর উপর।
সাধারনত কম্পাইলার গুলো একটা int এর জন্য 2
byte মেমরি এলোকেট করে। তেমনি একটি char ভ্যারিয়েবলের জন্য 1 byte মেমরি
এলোকেট করে। floating-point নাম্বার এর জন্য 4 byte মেমরি এলোকেট করে।
যেমন যখন কম্পিউটার দেখে এমন একটি ডিক্লারেশন
int a; তখন এটি বুঝতে পারে এটি একটি ইন্টিজার ভ্যারিয়েবল এবং এর জন্য ২
বাইট মেমরি এলোকেট করা দরকার। তখন র্যাম এর খালি যায়গা থেকে এটি এই
ইন্টিজারের জন্য ২ বাইট মেমরি এলোকেট করে।
আমরা সহজেই একটি ভ্যারিয়েবলের মেমরি লোকেশন বের করতে পারি, নিচের প্রোগ্রামটি দেখা যাকঃ
#include <stdio.h>
int main()
{
int a =5;
printf("Memory address of variable a is: %x",&a);
return 0;
}
উপরের প্রোগ্রামটি রান করালে এমন কিছু দেখাবেঃ
Memory address of variable a is: 2686732 । এক কম্পিউটারে এক এক মান
দেখাবে। এবং একবার এক এক ভ্যালু দেখাবে। কারণ যতবারই আমরা প্রোগ্রামটি রান
করি, প্রতিবারই ভ্যারিয়েবলটির জন্য মেমরিতে একটা জায়গা বরাদ্ধ করা হয়। আর ঐ
জায়গার এড্রেসটা প্রতিবারই পরিবর্তন হয়।
কোন ভ্যারিয়েবল এর এর মেমরি এড্রেস জানার জন্য
& [ampersend] ব্যবহার করা হয়। যাকে address-of operator [&] ও
বলা হয়। যা দিয়ে আমরা অন্য একটি ভ্যারিয়েবল এর এড্রেস বা মেমরি লোকেশন
পেতে পারি।
যখন আমরা প্রোগ্রামটি রান করি, তখন কম্পিউটার
র্যাম এর খালি যায়গা থেকে ভ্যারিয়েবল a এর জন্য ২ বাইট মেমরি এলোকেট করে।
কম্পিউটার অটোমেটিকেলি তখন a এর জন্য 2686732 এবং 2686733 নং সেল এলোকেট
করে রাখে। আর মেমরি এড্রেস জানার জন্য শুধু মাত্র শুরুর এড্রেস জানলেই হয়।
আমরা যখন a এর মেমরি এড্রেস প্রিন্ট করেছি, তখন শুধু শুরুর এড্রেস 2686732 ই
পেয়েছি। যদি ও a ভ্যারিয়েবল এর জন্য 2686732 এবং 2686733 মেমরি এলোকেট
করা হয়েছে এবং এর মান 5 এই দুই সেলে স্টোর করে রাখা হয়েছে। এখন আমরা যদি a
এর মান পরিবর্তন করে অন্য আরেকটা ভ্যালু রাখি, যেমন 8, তখন র্যামের
2686732 এবং 2686733 এ দুটো সেল এর মান ও পরিবর্তন হয়ে যাবে এবং এ দুটো
সেলে 5 এর পরিবর্থে 8 স্টোর হবে। এবার পয়েন্টার কি জানা যাক।
পয়েন্টার হচ্ছে একটা ভ্যারিয়েবল যার ভ্যালু হচ্ছে আরেকটি ভ্যারিয়েবল এর মেমরি লোকেশন।
পয়েন্টার একটা ডেটা, অ্যারে বা ভ্যারিয়েবল এর কম্পিউটার মেমরি লোকেশন
রিপ্রেজেন্ট করে বা পয়েন্ট করে। অন্যান্য ভ্যারিয়েবল এর মত পয়েন্টার
ভ্যারিয়েবল ব্যবহার করার আগে কম্পিউটার/ কম্পাইলারকে বলতে হবে এটা একটি
পয়েন্টার ভ্যারিয়েবল। নিচের মত করে একটি পয়েন্টার ভ্যারিয়েবল
ডিক্লেয়ার করে।
data_type *name;
যেমন integer পয়েন্টারের জন্যঃ int *i;
asterisk [*] একটি ভ্যারিয়েবলের আগে ব্যবহার
করে পয়েন্টার হিসেবে ডিক্লেয়ার করা হয়। যাকে indirection operator বা
value-at-address operator বলা হয়। এখানে আরো কিছু ডেটা টাইপ এর পয়েন্টার
ডিক্লারেশন এর উদাহরন দেওয়া হলোঃ
int*ip;/* pointer to an integer */
double*dp;/* pointer to a double */
float*fp;/* pointer to a float */
char*ch /* pointer to a character */
আমরা এখন দেখব কিভাবে পয়েন্টার ব্যবহার করতে হয় একটি প্রোগ্রামে।
#include <stdio.h>
int main ()
{
int a = 5; /* variable declaration */
int *ip; /* pointer variable declaration */
ip = &a; /* store address of "a" in pointer variable*/
printf("Address of a variable: %xn", &a );
/* address stored in pointer variable */
printf("Address stored in ip variable: %xn", ip );
return 0;
}
এখানে আমরা একটি ভ্যারিয়েবল a ডিক্লেয়ার
করেছি। এরপর একটি পয়েন্টার ভ্যারিয়েবল ডিক্লেয়ার করেছি। তারপর পয়েন্টার
ভ্যারিয়েবলে a এর মেমরি এড্রেস রেখেছি। তারপর & অপারেটর দিয়ে a
ভ্যারিয়েবল এর এড্রেস প্রিন্ট করে দেখলাম। এবং পয়েন্টার ভ্যারিয়েবল এর
ভ্যালু প্রিন্ট করে দেখলাম। উভয় এর মান ই একই।
আমরা ইচ্ছে করলে এখন ip পয়েন্টার ভ্যারিয়েবল দিয়ে a এর মান বের করতে পারি।
#include <stdio.h>
int main ()
{
int a = 5;
int *ip;
ip = &a;
/* access the value using the pointer */
printf("Value of *ip variable: %dn", *ip );
return 0;
}
আমরা যখন প্রগ্রামটি রান করব, তখন ip যে
ভ্যারিয়েবলটির এড্রেস শো করবে, তার মান প্রিন্ট করবে। লক্ষকরি, যখন আমরা
পয়েন্টার ভ্যারিয়েবল দিয়ে কোন ভ্যারিয়েবল এর এড্রেস বের করতে চাইবো, তখন
শুধু পয়েন্টার ভ্যারিয়েবল লিখলেই হবে। কিন্তু যখন আমরা পয়েন্টার ভ্যারিয়েবল
দিয়ে মূল ভ্যারিয়েবল এর ভ্যালু বের করতে চাইবো, তখন পয়েন্টার ভ্যারিয়েবল
এর আগে * যোগ করতে হবে। যেমন প্রথম প্রোগ্রামে আমরা ip
[পয়েন্টার ভ্যারিয়েবল] প্রিন্ট করায় আমরা এড্রেস পেয়েছি। এবং পরের
প্রোগ্রামে ip এর আগে একটা দিয়ে ip প্রিন্ট করায় আমরা মূল ভ্যারিয়েবলের মান পেয়েছি।স্ট্রাকচার – structure
Structures সি প্রোগ্রামিং
এর দারুণ একটা বিষয়। আমরা ডেটা টাইপ সম্পর্কে জানি, int, char, float
ইত্যাদি। Structures দিয়ে আমরা নিজেদের মত করে ডেটা স্ট্রাকচার তৈরি করে
নিতে পারি। যেমন অ্যারে হচ্ছে একটা ডেটা স্ট্র্যাকচার। যেখানে শুধু আমরা
একই ডেটা টাইপ এর ডেটা রাখতে পারি। কিন্তু স্ট্র্যাকচার তৈরি করে আমরা এক
সাথে int, char, float ইত্যাদি ভিন্ন ভিন্ন ডেটা এক সাথে রাখতে পারি।
নিজেদের প্রয়োজন মত, ইচ্ছে মত।
int অ্যারেতে শুধু ইন্টিজার ভ্যালুই রাখতে
পারব। char অ্যারেতে শুধু কারেকটারই রাখতে পারব। কিন্তু Structures ব্যবহার
করে আমরা নিজেদের মত করে ডেটা স্ট্রাকচার তৈরি করতে পারব। এবং ঐখানে ইচ্ছে
মত একের অধিক ভিন্ন ভিন্ন ডেটা রাখতে পারব।
Structures নিচের মত করে ডিফাইন করা হয়।
struct book
{
int no;
char name;
};
struct কীওয়ার্ড দিয়ে Structures ডিফাইন করা
হয়। এরপর লিখছি book, যা হচ্ছে আমাদের নিজস্ব স্ট্র্যাকচারের নাম। এরপর
দ্বিতীয় ব্র্যাকেটের মধ্যে স্ট্র্যাকচারের মেম্বার গুলো।
এখানে একটা মেম্বার হচ্ছে ইন্টিজার, যেখানে আমরা বই এর ক্রমিক নাম্বার
রাখব। আরেকটা হচ্ছে কারেকটার, যেখানে আমরা বইটির নাম রাখব।
Structures ডিফাইন করার পর তা ব্যবহার করার
জন্য ডিক্লেয়ার করতে হয়। একটা ইন্টিজার ভ্যারিয়েবল ব্যবহারের জন্য যেমন আগে
তা ডিক্লেয়ার করতে হয়, তেমনি।
ডিক্লেয়ার করার জন্য নিচের মত করে লিখতে হয়ঃ
struct book myBook;
যেখানে struct দিয়ে বুঝায় আমরা একটা
স্ট্র্যাকচার ডিক্লেয়ার করতে যাচ্ছি, এরপর book, যা দিয়ে বুঝাচ্ছে আমরা কোন
struct টা ডিক্লেয়ার করতে যাচ্ছি। এরপর হচ্ছে আমরা কি নামে আমাদের তৈরি
স্ট্র্যাকচারটা ব্যবহার করব।
ডিক্লেয়ার করার পর স্ট্র্যাকচার ব্যবহার করতে হবে। তো আমরা আমাদের তৈরি myBook স্ট্র্যাকচার ব্যবহার করব।
myBook এর দুইটা মেম্বার। একটা হচ্ছে no আরেকটা name.
এখন আমরা বই এর নাম্বার এবং নাম সেট করব। তার জন্য লিখতে হবেঃ
myBook.no = 3;
myBook.name = ‘C’;
এবার আমাদের সেট করা বই এর নাম এবং নং প্রিন্ট করতে চাইলেঃ
printf( "Book No : %dn", myBook.no);
printf( "Book Name : %cn", myBook.name);
আমরা ছোট ছোট কোড লিখেছি, এবার পুরো প্রোগ্রামটি লিখে ফেলিঃ
typedef ব্যবহার করে আমরা আমাদের
স্ট্র্যাকচারের instanc তৈরি করার সময় struct কীওয়ার্ড ব্যবহার ছাড়াই
আমাদের তৈরি স্ট্র্যাকচার ব্যবহার করতে পারি। তার জন্য আমাদের উপরের
স্ট্যাকচারটা নিচের মত করে লিখতে হবেঃ
typedef struct {
int no;
char name;
} book;
আগের থেকে পার্থক্য হচ্ছে আমরা এখানে নতুন
একটা কীওয়ার্ড ব্যবহার করেছি, typedef। তারপর লিখছি struct। এবং আমাদের
স্ট্র্যাকচারের একবারে শেষের দিকে লিখেছি আমাদের স্ট্যাকচারের নাম।
এখন আমরা আমাদের এই book স্ট্র্যাকচারের instance তৈরি করার জন্য struct কীওয়ার্ড ব্যবহার ছাড়াই তৈরি করতে পারব, যেমনঃ
book myBook;
যেভাবে আমরা একটা ভ্যারিয়েবল ডিক্লেয়ার করি, ঠিক সেভাবে। আগের প্রোগ্রামটা typedef ব্যবহার করলে হয়ঃ
আচ্ছা, স্ট্রাকচার এর সুবিধে হচ্ছে একবার
ডিফাইন করার পর ইচ্ছে মত instance তৈরি করে নিতে পারি আমরা। আগের দুইটি
প্রোগ্রামে আমরা একটা স্ট্রাকচার ডিফাইন করেছি, এবং এরপর একটা মাত্র
স্ট্রাকচার ডিক্লেয়ার করেছি। এখন আমরা আরেকটা প্রোগ্রাম লিখব, যেখানে একটা
স্ট্রাকচারের তিনটে instance তৈরি করব।
book এর instance তৈরি করার সময় আমরা ভিন্ন ভিন্ন লাইনে না লিখে একই লাইনে লিখতে পারি। যেমনঃ
book book1;
book book2;
book book3;
এর পরিবর্তে আমরা লিখতে পারিঃ book book1,book2, book3;
আমরা book এর তিনটে instance তৈরি করেছি
মাত্র। এবং ম্যানুয়ালি সব গুলো মেম্বারে ডেটা সেট করেছি। কিন্তু আমাদের এমন
প্রোগ্রাম লিখতে হবে, যেখানে আমাদের ১০০ বা ১০০০ বা আরো বেশি instance
তৈরি করতে হবে। তখন কি করব? এমন প্রোগ্রামটা দেখতে কি বিশ্রিই না দেখাবে,
তাই না? কিন্তু না, আমরা এখানে কন্ডিশনাল ব্যবহার করতে পারব। লুপ দিয়ে সব
গুলোতে ডেটা সেট করতে পারব। আবার লুপ দিয়ে সব গুলো থেকে ডেটা বের করে
প্রিন্ট করতে পারব।
আমরা সিম্পল একটা প্রোগ্রাম লিখব এ জন্য। আসলে
এ প্রোগ্রামটির জন্য স্ট্র্যাকচার ব্যবহার করতে হয় না। তারপর ও লুপ
ব্যবহার করে কিভাবে স্ট্র্যাকচারের ভিন্ন ভিন্ন মেম্বার এক্সেস করতে হয়,
তার একটা উদাহরণ দেখব।
প্রোগ্রামটিতে আমরা ০ থেকে ১০০ এর বর্গমুল [square root] এর একটা চার্ট তৈরি করব।
এখানে sqrt() হচ্ছে একটা লাইব্রেরী ফাংশান।
যার মধ্যে একটা নাম্বার দিলে ঐ নাম্বারটির বর্গমূল রিটার্ন করে। বাকি কোড
গুলো তো সহজ। প্রথমে আমরা for লুপ ব্যবহার করে আমাদের squareRoot1 এর
বিভিন্ন মেম্বারে ডেটা সেট করেছি। এপর আবার লুপ দিয়ে সেগুলো প্রিন্ট করেছি।
এবার তো আমরা স্ট্র্যাকচার ব্যবহার করে কমপ্লেক্স কোড লিখতে পারব, তাই না?
শুভ প্রোগ্রামিং...
ফাইল থেকে ইনপুট এবং আউটপুট
একটা ফাইল নিয়ে কাজ করার জন্য তা ডিক্লেয়ার করতে হয়। ডিক্লেয়ার করা হয় FILE পয়েন্টার দিয়ে। যেমনঃ
FILE *MyFile;
FILE বড় হারের অক্ষরে লিখতে হয় এবং MyFile
হচ্ছে পয়েন্টার ভেরিয়েবল। এটা মুলত একটা বাফার তৈরি করে কম্পিউটার মেমরি
এবং ঐ ফাইল এর মধ্যে। পয়েন্টার ভেরিয়েবল তৈরি করার পর আমরা ফাইলটি ওপেন
করতে পারব। তার জন্য fopen ফাংশান ব্যবহার করতে হয়। যা সাধারনত লেখা হয়
এমনঃ MyFIle = fopen(file-name, file-type); । ফাইল এর নাম এবং টাইফ দুটি
স্টিং। এবং ফাইল টাইফ হচ্ছে ফাইলটা কোন মুড এ ওপেন হবে তা। যেমন Read Only,
Write Only অথবা দুটিই ইত্যাদি। যেমনঃ
MyFile = fopen (“myfile.txt”,”w”);
file-type নিচের টেবিলের যে কোন একটা হতে পারেঃ
r | শুধু মাত্র ফাইলটি রিড করার জন্য ওপেন করা। |
---|---|
w | ফাইলটিতে কিছু রাইট করার জন্য ওপেন করা। ফাইলে যদি আগে কিছু থাকে তাহলে তা মুছে যাবে। ফাইল যদি না থাকে, তাহলে অটোমেটিক ভাবে তৈরি হবে। |
a | ফাইলের শেষে কিছু রাইট করার জন্য ওপেন করা। যদি ফাইলে আগে কিছু থাকে, তাহলে তার শেষে রাইট হবে। ফাইল যদি না থাকে, তাহলে অটোমেটিক ভাবে তৈরি হবে। |
r+ | ফাইলটি রিড করা বা রাইট করার জন্য ওপেন করা। এবং ফাইলের শুরুতে কিছু লেখা। |
w+ | ফাইলটি রিড করা বা রাইট করার জন্য ওপেন করা। |
a+ | ফাইলটি রিড করা বা রাইট করার জন্য ওপেন করা। এবং ফাইলের শেষে কিছু লেখা। |
ফাইল ওপেন করার জন্য তাতে কিছু লেখার জন্য fputs ফাংশান ব্যবহার করা হয়। যেমনঃ
fputs (“Writing to a file using ‘fopen’ example.”,MyFile);
ফাইলটি রিড বা রাইট করা হলে ফাইলোটি বন্ধ বা ক্লোজ করতে হয়, তার জন্য ব্যবহার করা হয় fclose ফাংশানঃ
fclose (MyFile);
#include <stdio.h>
int main ()
{
FILE * MyFile;
MyFile = fopen ("myfile.txt","w");
if (MyFile!=NULL)
{
fputs ("Writing to a file using 'fopen' example.",MyFile);
fclose (MyFile);
}
return 0;
}
ফাইল থেকে ডেটা পড়াঃ
ফাইল থেকে ডেটা পড়ার জন্য fscanf ব্যবহার করা হয়।
নিচের কোড গুলো দেখুনঃ
#include <stdio.h>
int main ()
{
FILE * MyFile;
char string[10];
MyFile = fopen ("myfile.txt","r+");
while(! feof(MyFile))
{
fscanf(MyFile,"%s",string);
printf("%s ", &string);
}
fclose (MyFile);
return 0;
}
এখানে feof দিয়ে end-of-file চেক করা হয়েছে।
অর্থাৎ যতক্ষন পর্যন্ত ফাইলের মধ্যে কোন ডেটা থাকবে ততক্ষন পর্যন্ত
ফাইলটির ডেটা গুলো fscanf দিয়ে রিড করা হবে
।
fscanf এর তিনটা প্যারামিটার রয়েছে। fscanf(file-name, data-type,
variable);
file-name হচ্ছে ফাইলের নাম। যে ফাইল থেকে
ডেটা পড়া হবে। data-type হচ্ছে ফাইলের ডেটা টাইফ। বা কোন টাইফে ডেটা গুলো
পড়া হবে। এখানে char টাইফের ডেটা পড়া হয়েছে। ইচ্ছে করলে int অথবা
floating point ডেটা পড়া যাবে।
variable হচ্ছে ভেরিয়েবলের নাম, যেখানে ডেটা গুলো ফাইল থেকে পড়ে সংরক্ষিত থাকবে।
আমরা এ পর্যন্ত যে ফোল্ডারে আমাদের সি
প্রোগ্রাম টা রয়েছে তা থাকে ফাইলটি ওপেন করছি বা রিড করছি। ইচ্ছে করলে
আমরা যে কোন ডিরেক্ট্ররি থেকে ফাইলটি ওপেন করতে পারি। file-name এর জাগায়
পুরো ফাইল পাথ দিলেই হবে। নিচের উদাহরনটি দেখুনঃ
#include <stdio.h>
int main ()
{
FILE * MyFile;
char string[10];
MyFile = fopen ("myfile.txt","r+");
while(! feof(MyFile))
{
fscanf(MyFile,"%s",string);
printf("%s ", &string);
}
fclose (MyFile);
return 0;
}
ফাইল নিয়ে কাজ করা অনেক সহজ তাই না?
টেবিলে আগেই বলছি যে w মুডে ফাইলটি ওপেন করলে
তার মধ্যের সকল ডেটা মুছে যাবে এবং নতুন করে ডেটা রাইট হবে। কিন্তু আমরা
যদি আগের ডেটা না মুছে আগের ডেটার নিছে নতুন ডেটা লিখতে চাই তার জন্য
ব্যবহার করব a মুড।
নিচের কোড টি কয়েকবার রান করিয়ে দেখুনঃ
#include <stdio.h>
int main ()
{
FILE * MyFile;
MyFile = fopen ("F:\MyFolder\myfile.txt","a");
if (MyFile!=NULL)
{
fputs ("Writing to a file using 'fopen' example.",MyFile);
fclose (MyFile);
}
return 0;
}
এতটুকুই, অন্যান্য মুড আপনারা ট্রাই করে
দেখুন। অন্য কোন ডেটা টাইফ সংরক্ষন করে দেখুনন। অন্য ডেটা টাইফের ডেটা গুলো
আবার রিড করার চেষ্টা করুন।
পয়েন্টার কি?