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

সি (C) অ্যারে (C Arrays)

লম্বা বারান্দায় সারি সারি লকার — প্রত্যেকটির ওপর আলাদা আলাদা নম্বর দেওয়া, এবং তাদের প্রত্যেকটির আকার একেবারে হুবহু এক

সারি সারি লকার (A Row of Lockers)

ধরা যাক, একটি লম্বা বারান্দা বা হলওয়েতে (hallway) সারি সারি অনেকগুলো লকার (lockers) রাখা আছে। এখানকার প্রতিটি লকারের (locker) আকার বা সাইজ (size) একেবারেই সমান, প্রত্যেকটি লকারের দরজার ওপর একটি নির্দিষ্ট নম্বর বা নাম্বার (number) লেখা রয়েছে এবং এই লকারগুলো মেমোরির (memory) ভেতর পরপর একসাথে (side by side) সাজানো আছে। মূলত একটি সি অ্যারেও (C array) ঠিক এমনভাবেই কাজ করে।

যখন আপনি কোনো অ্যারে (array) ডিক্লেয়ার বা ঘোষণা করেন, তখন আপনি মূলত এর কম্পাইলারকে (compiler) এই নির্দেশটি দেন: "আমার জন্য পরপর ৫টি লকার রিজার্ভ (Reserve) বা সংরক্ষণ করো, যার সবগুলোতেই শুধুমাত্র ইন্টিজার বা পূর্ণসংখ্যা (integers) রাখা হবে।" এরপর এই কম্পাইলারটি মেমোরির মধ্যে একসাথে বা কন্টিগুয়াস (contiguous) একটি জায়গার খোঁজ করে, এই লকারগুলোর দরজায় ০ (0) থেকে শুরু করে ৪ (4) পর্যন্ত নাম্বার বসিয়ে দেয় এবং সবচেয়ে শেষে আপনার হাতে এর ০ (0) নম্বর লকারের চাবিটি ধরিয়ে দেয়।

ডিক্লেয়ারিং এবং ইনিশিয়ালাইজিং (Declaring and Initializing)

সি (C)-তে মূলত বেশ কয়েকটি উপায়ে একটি অ্যারে তৈরি করা যায়:

  • int scores[5]; — ৫টি লকার, তবে এর ভেতরের জিনিসগুলো নিয়ে আমাদের কোনো ধারণা (unknown) নেই (হয়তো কিছু গারবেজ ভ্যালু বা garbage values হতে পারে!)
  • int scores[5] = {90, 85, 77, 92, 88}; — ৫টি লকার, এবং এর প্রত্যেকটিকেই পূরণ করে বা ভরে (filled) দেওয়া হয়েছে
  • int scores[] = {90, 85, 77, 92, 88}; — এখানকার এই হিসাব-নিকাশের কাজটুকু কম্পাইলার নিজেই নিজের মতো করে গুনে নেবে

এখানকার ইনডেক্সগুলো (index) মূলত ১ (1) থেকে নয় বরং ০ (0) থেকে শুরু হয়। অর্থাৎ একটি ৫-উপাদান বিশিষ্ট বা ৫-এলিমেন্টের (5-element) অ্যারের মধ্যে scores[0] হলো এর সবচেয়ে প্রথম উপাদান বা এলিমেন্ট (first element) এবং scores[4] হলো এর সবচেয়ে শেষের উপাদান (last)।

অ্যারে ডিক্লেয়ার বা ঘোষণা করা এবং তা ব্যবহার করা (Declaring and Accessing Arrays)

#include <stdio.h>
int main() {
int scores[5] = {90, 85, 77, 92, 88};
// যেকোনো নির্দিষ্ট উপাদান বা এলিমেন্টকে (elements) অ্যাক্সেস (Access) করা
printf("First score: %d\n", scores[0]);
printf("Third score: %d\n", scores[2]);
printf("Last score: %d\n", scores[4]);
// যেকোনো উপাদান বা এলিমেন্টকে মডিফাই (Modify) বা পরিবর্তন করা
scores[2] = 80;
printf("Updated third: %d\n", scores[2]);
return 0;
}
Output
First score:  90
Third score:  77
Last score:   88
Updated third: 80

অ্যারের ভেতর দিয়ে লুপ বা লুপিং করা (Looping Through an Array)

লুপ (loops) আর অ্যারেগুলোর (Arrays) মধ্যে মূলত দারুন বন্ধুত্ব রয়েছে। সাধারণত আমরা কোনো একটি এলিমেন্ট বা উপাদানকে নিজে থেকে ম্যানুয়ালি অ্যাক্সেস (access) করি না — বরং এর বদলে আমরা একটি for লুপকে ওই বারান্দা দিয়ে এক এক করে প্রতিটি লকারের কাছে পাঠিয়ে দেই, যাতে করে এটি নিজে থেকেই এর প্রতিটি লকার খুলে ভেতরের জিনিসগুলো দেখে আসতে পারে।

এক্ষেত্রে সবচেয়ে গুরুত্বপূর্ণ বা ক্রিটিকাল (critical) ব্যাপার হলো: সি (C) কখনোই এর কোনো অ্যারের সাইজ বা দৈর্ঘ্যকে (length) কোথাও সেভ (store) করে রাখে না। এই জিনিসটিকে আপনাকে নিজেই ট্র্যাক (track) করতে হবে। তবে এর জন্য সবচেয়ে সাধারণ বা কমন একটি ট্রিক (trick) বা পদ্ধতি হলো এই sizeof(arr) / sizeof(arr[0]) ব্যবহার করা, কিন্তু এটি শুধুমাত্র তখনই কাজ করে যখন আপনার অ্যারেটি ওই একই স্কোপের (same scope) ভেতরে থাকে — অর্থাৎ আপনি যখনই অ্যারেটিকে কোনো ফাংশনের (function) ভেতরে পাস (pass) করে দেবেন, ঠিক তখনই এর এই নিয়ম আর কাজ করবে না (breaks)।

লুপ ব্যবহার করে অ্যারে পূরণ করা (Filling) এবং প্রিন্ট করা (Printing)

#include <stdio.h>
int main() {
int nums[5];
// এর ভেতর সবগুলো স্কয়ার বা বর্গ (squares) দিয়ে পূরণ করুন: 0, 1, 4, 9, 16
for (int i = 0; i < 5; i++) {
nums[i] = i * i;
}
// এগুলোকে প্রিন্ট (Print) করুন
int length = sizeof(nums) / sizeof(nums[0]);
printf("Array length: %d\n", length);
for (int i = 0; i < length; i++) {
printf("nums[%d] = %d\n", i, nums[i]);
}
return 0;
}
Output
Array length: 5
nums[0] = 0
nums[1] = 1
nums[2] = 4
nums[3] = 9
nums[4] = 16
Note: সি (C) কখনোই এর যেকোনো অ্যারে বাউন্ডগুলো (array bounds) বা সীমানাগুলো নিজে থেকে চেক বা যাচাই (check) করে না। আপনি যদি একটি ৫-এলিমেন্ট বিশিষ্ট (5-element) অ্যারেতে গিয়ে scores[10] লেখেন, তবে কম্পাইলার (compiler) আপনাকে কখনো বাধা দেবে না। আপনি তখন সাইলেন্টলি (silently) মেমোরির যেকোনো জায়গা রিড বা পড়তে (read) কিংবা সেটিকে করাপ্ট (corrupt) বা নষ্ট পর্যন্ত করে ফেলতে পারবেন। আর এটিই মূলত সি (C) প্রোগ্রামগুলোর অন্যতম একটি প্রধান সোর্স অফ বাগ (source of bugs) বা সাধারণ ভুল এবং সিকিউরিটি ভালনেরাবিলিটির (security vulnerabilities) মূল কারণ হয়ে থাকে।

মাল্টি-ডাইমেনশনাল অ্যারে (Multi-Dimensional Arrays)

আপনার কি একটি গ্রিড (grid) বা ছক দরকার? একটি 2D অ্যারে বা দ্বিমাত্রিক অ্যারে (2D array) হলো মূলত একটি অ্যারেকে ধারণ করতে পারা আরেকটি অ্যারে (array of arrays) — ঠিক একটি স্প্রেডশিটের (spreadsheet) মতো যার মধ্যে অসংখ্য সারি বা রো (rows) এবং কলাম (columns) থাকে। আপনি চাইলে দুটি ব্র্যাকেটের (brackets) সেট ব্যবহার করে একে ডিক্লেয়ার (declare) বা ঘোষণা করতে পারেন: যেমন এই int grid[3][4] মূলত আপনাকে মোট ৩টি সারি (rows) এবং ৪টি কলাম (columns) দিয়ে থাকে।

তবে মেমোরিতে (memory) এটি মূলত একটি সম্পূর্ণ ব্লক (contiguous block) হিসেবেই পরপর অবস্থান করে। এর সবার প্রথমে 0 নম্বর সারি (row 0), তারপর 1 নম্বর সারি (row 1), এবং তারপর 2 নম্বর সারি (row 2) আসে — অর্থাৎ এখানকার সবকিছুকে এর মধ্যে পরপর সম্পূর্ণ ফ্ল্যাট (flat) বা সমানভাবে বিন্যস্ত করা থাকে। কারণ, সি (C) প্রোগ্রামিং মূলত একটি রো-মেজর (row-major) বা সারি-প্রধান ভাষা।

2D অ্যারে — একটি টিক-ট্যাক-টো গ্রিড (Tic-Tac-Toe Grid)

#include <stdio.h>
int main() {
// 3x3 গ্রিড (grid): 1=X, 2=O, 0=empty বা ফাঁকা
int board[3][3] = {
{1, 0, 2},
{0, 1, 0},
{2, 0, 1}
};
// বোর্ডটিকে (board) প্রিন্ট করুন
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
if (board[row][col] == 1)
printf(" X ");
else if (board[row][col] == 2)
printf(" O ");
else
printf(" . ");
}
printf("\n");
}
return 0;
}
Output
 X  .  O 
 .  X  . 
 O  .  X 

ফাংশনে অ্যারে পাস করা (Passing Arrays to Functions)

যখন আপনি কোনো ফাংশনের (function) মধ্যে একটি অ্যারেকে (array) পাস (pass) করেন, তখন আপনি মূলত এর কোনো কপি বা অনুলিপি (copy) পাস করেন না। বরং আপনি মূলত এর প্রথম উপাদান বা ফার্স্ট এলিমেন্টকে (first element) নির্দেশ করা একটি পয়েন্টার (pointer) পাস করেন। এর মানে হলো, এখান থেকে ফাংশনটি চাইলে এই আসল অ্যারেটিকে (original array) নিজেই পরিবর্তন বা মডিফাই (modify) করে ফেলতে পারবে — অর্থাৎ এটিকে সুরক্ষা দেওয়ার (protection) বা বাঁচানোর আর কোনো উপায় এখানে থাকে না।

এছাড়াও এখান থেকে এর সাইজ বা আকার সম্পর্কিত সমস্ত তথ্য হারিয়ে যাওয়ার (lost) কারণে, আপনাকে অবশ্যই এর দৈর্ঘ্য বা লেন্থকে (length) একটি আলাদা আর্গুমেন্ট (argument) হিসেবে পাস করতে হয়। মূলত এটি সি (C)-এর অনেক সাধারণ বা ক্লাসিক (classic) একটি নিয়ম যা আপনি এর প্রায় সব জায়গাতেই দেখতে পাবেন।

ফাংশন প্যারামিটার (Function Parameter) হিসেবে অ্যারে

#include <stdio.h>
void doubleAll(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // আসল বা অরিজিনাল (original) অ্যারেটিকে পরিবর্তন বা মডিফাই (modifies) করে!
}
}
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int nums[] = {1, 2, 3, 4, 5};
int len = sizeof(nums) / sizeof(nums[0]);
printf("Before: ");
printArray(nums, len);
doubleAll(nums, len);
printf("After: ");
printArray(nums, len);
return 0;
}
Output
Before: 1 2 3 4 5 
After:  2 4 6 8 10 
চ্যালেঞ্জ

ছোট কুইজ

এই int data[8];-এর মধ্যে থাকা সবচেয়ে শেষের (LAST) এলিমেন্ট বা উপাদানটির ইনডেক্স (index) কত?
LoopsStrings & Characters