Learn C Programming

lesson ১ / ৭ · Variable আর data type

Module ২ · Variable আর data type

Variable: memory-তে নাম লেখা বাক্স

Freeপড়া

এই lesson-এ যা শিখবে

  • Declare, initialise আর assign করতে পারবে, আর কোন লাইনটা কোনটা সেটাও বলতে পারবে।
  • %p দিয়ে একটা variable-র address ছাপতে পারবে, আর ওই সংখ্যাটা কী সেটা বোঝাতে পারবে।
  • যে variable-এ কিছু লেখোনি সেটা পড়া কেন bug, সেটা বলতে পারবে, 0 ছাপলেও।

Zara রান্নাঘরের প্রতিটা বয়ামে label লাগায়: চাল, চিনি, চা। Label-গুলো খাবার না। ওগুলো দিয়ে সে দশটা ঢাকনা না খুলেই জিনিসটা পেয়ে যায়।

তারপর সে এক লিটার দুধ একটা চায়ের কাপে ঢালতে যায়। কাপটা বড় হয় না। যতটুকু ধরে ততটুকুই ধরে, বাকিটা মেঝেয় পড়ে।

C-তে একটা variable হলো নির্দিষ্ট মাপের একটা label লাগানো বয়াম। এই lesson label নিয়ে, মাপ নিয়ে, আর তাকের নিচে লেখা address নিয়ে।

একটা নাম, একটা মাপ আর একটা address

নিচের এই এক লাইনটা লিখলে আসলে আলাদা চারটা কাজ হয়।

#include <stdio.h>

int main(void)
{
    int marks = 90;
    printf("%d\n", marks);
    return 0;
}
90
  1. Compiler memory-র একটা টুকরো সরিয়ে রাখে। Playground-এ একটা int-র জন্য সেটা 4 byte।
  2. ও নিজের খাতায় লিখে রাখে যে marks নামটা ওই টুকরোটাকে বোঝায়।
  3. ও লিখে রাখে যে ওই byte-গুলোকে পূর্ণসংখ্যা হিসেবে পড়তে হবে।
  4. ও 90-র bit pattern-টা ওখানে রেখে দেয়।

এই চারটার তিনটাই type-র কাজ। Type সাজসজ্জা না। ও ঠিক করে মাপ কত, কীভাবে পড়তে হবে, আর মানটা নিয়ে তুমি কী করতে পারবে।

তাই একটা variable একসঙ্গে তিন জিনিস: একটা নাম, একটা type, আর একটা জায়গা। নতুনরা নামটা শেখে, বাকি দুইটা ভুলে যায়, আর চমকগুলো ওখান থেকেই আসে।

Declaration, প্রতিটা অংশের নাম দিয়ে

int      marks      =      90     ;
 |         |        |       |     |
 |         |        |       |     statement এখানে শেষ
 |         |        |       প্রথম মান (না দিলেও চলে)
 |         |        assignment operator
 |         তোমার বেছে নেওয়া নাম
 type: বাক্সটা কত বড় আর ওটা কীভাবে পড়তে হবে
  • Type সবার আগে বসে, আর ওই নামের জন্য সেটা আর কখনো বদলায় না।
  • নামটা এই lesson-র নিচের নিয়মগুলো মানে।
  • এখানে = মানে "ভেতরে রাখো", "সমান" না।
  • = 90 বাদ দিলে তোমার হাতে থাকে কাজে লাগে না এমন কিছু ভরা একটা বাক্স।

Declare, initialise, assign: তিনটা আলাদা লাইন

তিনটা শব্দ, আর লোকে এগুলোকে একটা জিনিস ভেবে ব্যবহার করে। এরা এক না।

#include <stdio.h>

int main(void)
{
    int marks;            /* declaration: reserve the box, leave it alone */
    marks = 90;           /* assignment: put a value in an existing box */
    int total = 250;      /* declaration and initialisation, one line */

    printf("%d %d\n", marks, total);
    return 0;
}
90 250

Declare করা মানে compiler-কে একটা নাম আর একটা type বলা। Initialise করা মানে জন্মের মুহূর্তেই বাক্সটাকে প্রথম মানটা দেওয়া। Assign করা মানে আগে থেকে থাকা একটা বাক্সে মান রাখা।

Initialise আর assign দেখতে একদম এক, কারণ দুইটাই = ব্যবহার করে। তফাত হলো কখন হচ্ছে, আর মাঝখানে এমন কোনো মুহূর্ত ছিল কি না যখন বাক্সে অজানা কিছু ছিল।

তাই int total = 250; একটা statement, আর int total; total = 250; দুইটা। দ্বিতীয়টার মাঝখানে একটা ফাঁক আছে, আর ওই ফাঁকেই bug থাকে।

Assignment মানে সমান না

অঙ্কে x = x + 1 অর্থহীন। C-তে এটা সাধারণ একটা নির্দেশ।

=-কে ডান থেকে বাঁয়ে পড়ো: আগে ডান পাশটা পুরো হিসাব করো, তারপর উত্তরটা বাঁ পাশের বাক্সে রাখো। মনে মনে "পায়" বলো, কখনো "সমান" না।

#include <stdio.h>

int main(void)
{
    int x = 5;
    x = x + 1;        /* x gets the old x plus one */
    printf("%d\n", x);
    return 0;
}
6

পুরনো মানটা পড়া হয়, তার একটা copy-তে 1 যোগ হয়, আর উত্তরটা সেই একই বাক্সে ফিরে যায়। কোনো সমীকরণ কখনো সমাধান হয় না।

তাই একটা লাইনে দুই পাশেই একটা variable থাকতে পারে, আর ডান পাশ সবসময় লাইনটা চলার আগের মানটাই ব্যবহার করে।

Address জিনিসটা সত্যি, আর তুমি সেটা ছাপতে পারো

Compiler যে memory-র টুকরোটা সরিয়ে রেখেছিল, সেটা একটা নম্বর দেওয়া জায়গায় বসে আছে। ওই নম্বরটাই variable-র address।

&marks মানে "marks-র address"। না জেনেই তুমি এটা আগে ব্যবহার করেছ: scanf("%d", &n) address-টা scanf-কে দেয়, যাতে ও তোমার বাক্সে লিখতে পারে।

#include <stdio.h>

int main(void)
{
    int a = 1;
    int b = 2;
    int c = 3;

    printf("a = %d at %p\n", a, (void *)&a);
    printf("b = %d at %p\n", b, (void *)&b);
    printf("c = %d at %p\n", c, (void *)&c);
    return 0;
}

একটা মেশিনে একবার চালিয়ে এটা এসেছিল:

a = 1 at 0x7ffd1c4a2b9c
b = 2 at 0x7ffd1c4a2b98
c = 3 at 0x7ffd1c4a2b94

তোমার সংখ্যাগুলো অন্য হবে। মেশিনভেদে, এমনকি বারভেদেও বদলায়, তাই এই module-এ এটাই একমাত্র program যার output কেউ কথা দিয়ে বলতে পারে না।

ওই দৌড়ে দুইটা জিনিস খেয়াল করার মতো। Address-গুলো hexadecimal-এ লেখা, যেটা Module 1-র lesson 4 বুঝিয়েছে। আর ওরা 4 করে দূরে, কারণ একটা int 4 byte নেয় আর তিনটা বাক্স পাশাপাশি বসে আছে।

(void *) একটা cast, যেটা Module 4 ঠিকভাবে বোঝাবে। %p address-টাকে তার সবচেয়ে সাধারণ চেহারায় চায়, আর cast দিয়ে তুমি বলো "এটাকে নিছক একটা address ধরো"।

তাই & জাদুও না, নতুনও না। ওটা variable-র তৃতীয় সত্য, যেটা এতদিন তুমি দেখতে পাওনি।

তিনটা int variable: নাম, মান, মাপ আর address Memory, byte-র একটা সারি a b c 1 2 3 4 byte 4 byte 4 byte address n n + 4 n + 8 নাম মান মাপ, type ঠিক করে address, %p ছাপে প্রতিটা variable-র তিনটা সত্য: তুমি বেছেছ নাম, type বেছেছে মাপ, আর মেশিন বেছেছে address। তোমার হাতে কেবল প্রথমটা।
ছবি 1। পাশাপাশি তিনটা int variable। Address-গুলো 4 করে দূরে, কারণ type বলছে 4 byte।

যে variable-এ কখনো কিছু লেখোনি, সেটা পড়া

এই lesson-এর সবচেয়ে জরুরি অনুচ্ছেদ এটাই, তাই দুইবার পড়ো।

Declare করা যে variable-কে কখনো কোনো মান দেওয়া হয়নি, সেটা ধরে রাখে একটা অনিশ্চিত মান। ওটা শূন্য না। ওই address-এ আগে থেকে যে byte-গুলো পড়ে ছিল, ওটা সেগুলোই।

#include <stdio.h>

int main(void)
{
    int never_set;
    int set_properly = 0;

    printf("set:   %d\n", set_properly);
    printf("unset: %d\n", never_set);
    return 0;
}

Playground-র compiler এটা একটাও message ছাড়াই build করে, আর আমাদের দৌড়গুলোতে দ্বিতীয় লাইনটা 0 ছেপেছে।

ওই 0-টাই বিপজ্জনক ফল। দেখে মনে হয় প্রমাণ হয়ে গেল যে C variable-কে শূন্য করে রাখে। হয়নি। মানটা কথা দেওয়া না, আর যে program এটার উপর নির্ভর করে সেটা ভাঙা, আজকের দৌড় ঠিক হোক বা না হোক।

নিজের মেশিনের compiler-এ -Wall দিলে ও কিছু একটা বলে: warning: 'never_set' is used uninitialized। Playground-র command line-এ -Wall নেই, তাই তুমি পাও নীরবতা।

তাই নিয়মটা সোজা আর এর কোনো ব্যতিক্রম নেই: যে লাইনে variable-টা declare করছ, সেখানেই ওকে একটা মান দাও।

নাম রাখা: compiler কী দেখে, আর তোমার পাঠক কী দেখে

Compiler চারটা নিয়ম দেখে, আর চারটাতেই ও কড়া।

  • শুধু অক্ষর, অঙ্ক আর underscore। Space নেই, hyphen নেই, অন্য কোনো চিহ্নও নেই।
  • প্রথম অক্ষরটা অঙ্ক হতে পারবে না।
  • Keyword দখল করা। int, return আর double নাম হতে পারে না।
  • ছোট-বড় হাতের তফাত হয়। total, Total আর TOTAL তিনটা আলাদা variable।

তোমার পাঠক পঞ্চম একটা নিয়ম দেখে, আর code review-তে নম্বর কাটে ওটাতেই।

#include <stdio.h>

int main(void)
{
    /* Hard to read: nobody knows what these hold. */
    int a = 40, x1 = 35, temp = 75;

    /* Easy to read: the name is the explanation. */
    int maths_marks = 40;
    int physics_marks = 35;
    int total_marks = maths_marks + physics_marks;

    printf("%d %d %d\n", a, x1, temp);
    printf("%d\n", total_marks);
    return 0;
}
40 35 75
75

এই track লেখে total_marks, totalMarks না। ওই ধরনটার নাম snake_case, আর C standard library আর বেশির ভাগ C code চিরকাল ওটাই লিখে এসেছে।

একটা ধরন বেছে নাও আর এক file-এ দুইটা মিশিয়ো না। কোনটা বাছলে সেটার চেয়ে একরকম রাখাটা বেশি জরুরি।

এক লাইনে একটা variable

C-তে এক statement-এ কয়েকটা নাম declare করা যায়। এটা বৈধ, আর এই track এটা এড়িয়ে চলে।

#include <stdio.h>

int main(void)
{
    int a = 1, b, c = 3;      /* b has no value: easy to miss */

    b = 2;
    printf("%d %d %d\n", a, b, c);
    return 0;
}
1 2 3

ওই লাইনে b বসে আছে দুইটা initialise করা নামের মাঝখানে, আর ও নিজে initialise করা না। চোখ ওর উপর দিয়ে পিছলে যায়।

তাই এক লাইনে একটা declaration লেখো, প্রতিটার সঙ্গে তার মান। খরচ দুই লাইন, বাঁচে একটা বিকেল।

Example 1: variable দিয়ে সবচেয়ে ছোট যে কাজটা হয়

একটা সংখ্যা একটা নামের নিচে রাখো, তারপর ছাপো। সংখ্যাটা বদলে আবার চালাও।

#include <stdio.h>

int main(void)
{
    int stall_number = 7;

    printf("Stall %d\n", stall_number);
    return 0;
}
Stall 7

নামটা এখনই সত্যিকারের কাজ করছে। printf("Stall 7\n") একই লাইন ছাপত, আর সেটা অন্য একটা program হতো: যেটা কোনোদিন অন্য stall ছাপতে পারত না।

Run in Compiler
Example 2: তৃতীয় একটা বাক্স দিয়ে দুইটা মান বদল

Zara চায় সংখ্যা দুইটা জায়গা বদল করুক। কৌশলটা হলো assignment copy করে, তাই প্রথম copy-টা কোথাও রেখে দিতে হয়।

#include <stdio.h>

int main(void)
{
    int a = 10;
    int b = 20;
    int spare = 0;

    spare = a;        /* park the old a */
    a = b;            /* a gets b */
    b = spare;        /* b gets the parked value */

    printf("a = %d, b = %d\n", a, b);
    return 0;
}
a = 20, b = 10

spare-র লাইনটা মুছে বদলে লেখো a = b; b = a;। দুইটা বাক্সেই শেষে 20 থাকে, কারণ দ্বিতীয় লাইন পড়ার আগেই প্রথম লাইন পুরনো a-কে শেষ করে দিয়েছে।

Run in Compiler
Example 3: beginner আসলে যেটা লেখে

তিনটা নম্বর পড়ো, যোগ করো, মোট আর প্রতিটা নামের মাপ ছাপো। পরের module থেকে ধার করা কিছু ছাড়াই এটা একটা পুরো ছোট program।

#include <stdio.h>

int main(void)
{
    int maths = 0;
    int physics = 0;
    int chemistry = 0;

    scanf("%d %d %d", &maths, &physics, &chemistry);

    int total = maths + physics + chemistry;

    printf("Total: %d\n", total);
    printf("Each mark takes %d bytes.\n", (int)sizeof(maths));
    return 0;
}
Total: 252
Each mark takes 4 bytes.

ওই output 90 85 77 input-র জন্য। scanf ছোঁয়ার আগেই প্রতিটা variable নিজের লাইনে initialise করা, যাতে খরচ কিছু হয় না আর একটা গোটা শ্রেণির bug চলে যায়।

Run in Compiler

এটা কোথায় কাজে লাগছে

  • খেলার score। আসল Doom-র source প্রতিটা খেলোয়াড়ের health আর গুলি রাখে player_t নামের একটা struct-এ সাধারণ int হিসেবে। পর্দার score ওই বাক্সগুলোরই একটা, ছাপা অবস্থায়।
  • ব্যাংকের জের। Core banking-র code জের রাখে সবচেয়ে ছোট এককের পূর্ণসংখ্যা হিসেবে, পয়সা বা সেন্টে, একটা 64 bit integer-এ। কেন কখনো দশমিক type না, lesson 3 সেটা বলবে।
  • Sensor-র reading। একটা Arduino sketch তাপমাত্রা একটা int-এ পড়ে আর ছাপে, মানে তুমি যে তিন লাইন এইমাত্র লিখলে সেটাই, ডাকটিকিটের মাপের একটা chip-এ।
  • Loop-র গোনার ঘর। Linux kernel-র প্রায় প্রতিটা loop int i declare করে আর ওটা দিয়েই গোনে। ওই একটা নাম source-এ কয়েক হাজার বার আছে।

যে ভুলগুলো সবাই করে

১. ভেতরে কিছু রাখার আগেই variable ব্যবহার করা।

int total;
printf("%d\n", total);

Playground-র GCC 12 একটাও message দেয় না, কারণ এটা ধরার পরীক্ষাটা -Wall-র পিছনে থাকে আর Playground ওটা পাঠায় না। নিজের মেশিনে gcc -Wall বলে warning: 'total' is used uninitialized। সমাধান int total = 0;, আর আজকের দৌড়ে 0 ছেপেছে বলে ওটা ঐচ্ছিক হয়ে যায় না।

২. Declare না করা নামে assign করা।

total = 5;
printf("%d\n", total);

GCC 12 বলে error: 'total' undeclared (first use in this function) আর থেমে যায়। কিছু ভাষা assign করলেই variable বানিয়ে দেয়, C-তে সেই ব্যবস্থা নেই। প্রথম উল্লেখের আগেই type-টা বসাও।

৩. অঙ্ক দিয়ে নাম শুরু করা।

int 2nd_place = 3;

GCC 12 দুইটা message দেয়: error: invalid suffix "nd_place" on integer constant, তারপর error: expected identifier or '(' before numeric constant। প্রথমটা অদ্ভুত শোনায়, কারণ compiler 2-কে সংখ্যা ধরে পড়তে শুরু করেছিল আর তারপর তার গায়ে অক্ষর লাগানো পেয়েছে। লেখো second_place।

৪. Int-র বাক্সে লেখা রাখা।

int marks = "90";

Playground-র GCC 12-তে এটা একটা warning: initialization of 'int' from 'char *' makes integer from pointer without a cast, আর program build হয়ে একটা বড় অর্থহীন সংখ্যা ছাপে। GCC 14 একই লাইনকে error বানায়। জোড়া উদ্ধৃতিতে "90" হলো লেখা; উদ্ধৃতি ছাড়া 90 হলো সংখ্যা।

মাথা খাটাও

Zara এটা তিনবার চালিয়ে প্রতিবারই 0 পেল। ও খাতায় লিখল: "C-তে মান না দেওয়া int 0 থেকে শুরু হয়"।

#include <stdio.h>

int main(void)
{
    int counter;

    printf("%d\n", counter);
    return 0;
}

ওর সিদ্ধান্তে ঠিক কী ভুল, সেটা বলো। তারপর কঠিন অংশটা: এমন দুইটা বদলের নাম বলো, যার কোনোটাই counter-র লাইনে না, অথচ যেগুলো এই একই program-কে অন্য কিছু ছাপাতে পারে।

তিনবার একই ফল তিনবার সম্পর্কে একটা কথা প্রমাণ করে। main শুরুর ঠিক আগে ওই address-এ আর কী বসে থাকে, আর সেটা কে ঠিক করে, ভাবো।

অনুশীলন ১সহজ

Kenji একটা নতুন tool-এর input box পরীক্ষা করছে। ও চায় সবচেয়ে ছোট program-টা, যেটা দেখাবে সংখ্যাটা ভিতরে গিয়ে আবার বেরিয়ে এসেছে।

Input. এক লাইনে একটা পূর্ণসংখ্যা n।

Output. এক লাইনে ঠিক এইটা: n stored, n-র জায়গায় সংখ্যাটা।

Constraints. -1000000 <= n <= 1000000।

Sample. Input 42 দিলে 42 stored। শব্দটার আগে একটা space।

#include <stdio.h>

int main(void)
{
    int n = 0;
    scanf("%d", &n);

    /* One printf. Nothing else. */

    return 0;
}

এই module-র Problems lesson-এ echo-number নামে এটা judge করা হয়। সীমার ঋণাত্মক প্রান্তটা সত্যিকারের একটা লুকানো test।

Run in Compiler
অনুশীলন ২মাঝারি

Bob দুই বাক্স বই গোছাচ্ছে আর ওদের label বদলাতে চায়। Example 2-র মতো ঠিক একইভাবে তৃতীয় একটা variable ব্যবহার করো।

Input. এক লাইনে দুইটা পূর্ণসংখ্যা a b।

Output. এক লাইনে সংখ্যা দুইটা উল্টো ক্রমে, মাঝে একটা space।

Constraints. -1000000 <= a, b <= 1000000।

Sample. Input 10 20 দিলে 20 10।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int spare = 0;
    scanf("%d %d", &a, &b);

    /* Three assignments, then one printf. */

    return 0;
}

swap-with-third নামে judge করা হয়। এক printf-এ b তারপর a ছাপলেও পাশ, আর তাতে কিছু শেখা হয় না, তাই আগে লম্বা পথেই করো।

Run in Compiler
অনুশীলন ৩কঠিন

Maria চায় তিনটা নম্বরের গড়, দশমিকের পরে দুই ঘর পর্যন্ত। এটার জন্য এমন একটা ধারণা লাগে যেটা এই lesson কেবল ইঙ্গিত করেছে, আর lesson 3 পুরোটা বোঝায়।

Input. এক লাইনে তিনটা পূর্ণসংখ্যা a b c।

Output. এক লাইনে ওদের গড়, ঠিক দুই দশমিক ঘর পর্যন্ত।

Constraints. 0 <= a, b, c <= 100।

Sample. Input 90 85 77 দিলে 84.00। Input 1 1 2 দিলে 1.33।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;
    int c = 0;
    scanf("%d %d %d", &a, &b, &c);

    /* Careful: (a + b + c) / 3 throws the fraction away. */
    printf("%.2f\n", 0.0);
    return 0;
}

এই module-এ এটা judge করা হয় না। C-তে এক পূর্ণসংখ্যাকে আরেকটা দিয়ে ভাগ করলে পূর্ণসংখ্যাই আসে, আর ঠিক সেজন্যই 1 1 2 এখানে মজার input। সমাধানটা lesson 3 দেবে।

Run in Compiler

যে প্রশ্নগুলো সবার মনে আসে

  • Program শেষ হলে কি variable-টা আর থাকে না?

    থাকে না, আর তার অনেক আগেই যায়। ও বাঁচে declaration থেকে যে block-এ আছে তার শেষ brace পর্যন্ত। Module 7 এটা ঠিকভাবে বোঝাবে, scope নামে।

  • পরে কি variable-র type বদলাতে পারি?

    না। Type declaration-এ বাঁধা পড়ে আর বাঁধাই থাকে। মানটা অন্য type-র আরেকটা variable-এ copy করা যায়, যেটা Module 4-র বিষয়।

  • scanf-র & লাগে কেন, অথচ printf-র লাগে না?

    printf কেবল তোমার মানটা পড়ে, তাই একটা copy-ই যথেষ্ট। scanf-কে তোমার বাক্সে লিখতে হয়, তাই ওর address লাগে। যন্ত্রটা Module 11 খুলে দেখাবে।

  • int x = 0; কি int x;-র চেয়ে ধীর?

    মাপা যায় এমনভাবে না। Compiler সাধারণত শূন্যটা বিনা খরচে বসিয়ে দেয়, আর ভুল উত্তর যেকোনো instruction-র চেয়ে ধীর।

  • Variable-র নাম কত লম্বা হতে পারে?

    যথেষ্ট লম্বা। C17 কথা দেয় প্রথম 63টা অক্ষর আলাদা করে গোনা হবে, আর বুদ্ধিমান কোনো নাম ওতে পৌঁছায় না। এমন নাম বাছো যেটা মুখে বলা যায়।

মূল কথা

  • Variable তিন জিনিস: তোমার বাছা নাম, মাপ ঠিক করা type, আর একটা address।
  • Declare বাক্সটা সরিয়ে রাখে, initialise জন্মেই ভরে, assign পরে ভরে।
  • = মানে "পায়"। আগে ডান পাশ হিসাব হয়, তারপর বাঁ পাশে রাখা হয়।
  • &name হলো address, আর %p ওটা ছাপে; scanf চিরকাল ওটাই ব্যবহার করেছে।
  • Initialise না করা variable-এ অনিশ্চিত মান থাকে, আর Playground কিছুই বলে না।
  • এক লাইনে একটা declaration, প্রতিটার সঙ্গে মান, আর নাম snake_case-এ।

পরের lesson-এ জানবে ওই বাক্সটা আসলে কত বড়, আর যেদিন একটা সংখ্যা তাতে এক বেশি হয়ে যায় সেদিন কী হয়।

lesson ১ শেষ

শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।

পরেরটা: Integer type, তাদের range আর overflow