Learn C Programming

lesson ৬ / ৭ · C কেন, আর program জিনিসটা আসলে কী

Module ০ · C কেন, আর program জিনিসটা আসলে কী

Structured আর object oriented: program সাজানোর দুই রাস্তা

Freeপড়া

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

  • Structured programming-র সংজ্ঞা এক বাক্যে দিতে পারবে।
  • Object oriented programming বাড়তি কী দেয় আর কী খরচ করায়, বলতে পারবে।
  • এই track কেন সবার আগে structured C শেখায়, সেটা বলতে পারবে।

Kenji লাফিয়ে C++-এ চলে যেতে চায়। তার যুক্তি হলো object আধুনিক আর C না, তাহলে পুরোনো গড়নের পেছনে মাস খরচ করব কেন। Amara বলল, তার পছন্দের যেকোনো একটা C++ class খুলে একটা method লাইন ধরে ধরে পড়তে।

সে পড়ল। ওটা কয়েকটা ধাপের একটা sequence, মাঝখানে একটা if, শেষে একটা loop। প্রতিটা object-র method এখনো lesson 1-র সেই তিনটা move দিয়েই তৈরি। Object হলো code গুছিয়ে রাখার তাক, code নিজে না।

দুইটা গড়নই একটা প্রশ্নের উত্তর, আর এই lesson আসলে ওই প্রশ্নটা নিয়েই।

গোছানোর প্রশ্নটা

প্রতিটা বড় program একই সমস্যায় পড়ে। পঞ্চাশ হাজার লাইনকে অপাঠ্য জঞ্জাল হওয়া থেকে কীভাবে ঠেকাবে? Structured programming আর object oriented programming হলো দুইটা উত্তর।

Structured programming গোছায় কাজ ধরে। তুমি সমস্যাটাকে function-এ ভাগ করো, যেগুলো ছোট নামওয়ালা খণ্ড আর প্রতিটা একটা কাজ করে। Data একটা জিনিস। যে function-গুলো ওটা নিয়ে কাজ করে সেগুলো আলাদা জিনিস। তুমি data-টা function-কে পাঠিয়ে দাও।

Object oriented programming গোছায় জিনিস ধরে। তুমি সমস্যাটাকে object-এ ভাগ করো। প্রতিটা object নিজের data আর ওই data নিয়ে কাজ করা function-গুলো একসঙ্গে, এক খাপের ভেতরে বয়ে বেড়ায়।

আবার সেই তিনটা move

সংজ্ঞাটা এই, আর এটা তোমার ভাবনার চেয়ে ছোট। Structured programming মানে sequence, selection আর repetition দিয়ে program বানানো, এদিক সেদিক না লাফিয়ে।

এটা কোনো স্লোগান না। Corrado Böhm আর Giuseppe Jacopini 1966 সালে প্রমাণ করেন যে এই তিনটা গড়ন দিয়েই যেকোনো program প্রকাশ করা যায়। দুই বছর পর Edsger Dijkstra "Go To Statement Considered Harmful" নামে একটা ছোট চিঠি প্রকাশ করেন। তার যুক্তি ছিল, goto দিয়ে যেখানে সেখানে লাফানো program নিয়ে চিন্তা করাই অসম্ভব করে তোলে।

এর সঙ্গে একটা অভ্যাস আসে: প্রতিটা block-র ঢোকার এক পথ, বেরোনোর এক পথ। তুমি বাইরে থেকে একটা loop-র মাঝখানে লাফিয়ে পড়ো না। ছয় মাস পর কোনো মানুষ যে program পড়তে পারে, তার বেশিরভাগ কৃতিত্ব এই একটা নিয়মের।

মনে রেখো

C-তে এখনো goto আছে। সত্যিকারের code-এ এটা ব্যবহার হয়, বেশিরভাগ kernel-এ, একটাই সরু কাজে। মাঝপথে কিছু ব্যর্থ হলে এটা একটা cleanup block-এ লাফ দেয়। সেটা একটা মাত্র প্রস্থানে একবারই সামনে লাফ, তাই এক পথে বেরোনোর অভ্যাসটা থাকে। এর দেখা পাবে Module 6-এ।

একই program-র দুই গড়ন: data-র উপরে function, বনাম দুইটাই ধরে রাখা object Structured: কাজ ধরে গোছানো deposit() withdraw() print() struct Account owner, balance: যেকোনো function-র জন্য খোলা Data ভেতরে পাঠানো হয়। নতুন যেকোনো function অনুমতি না নিয়েই ওটা পড়তে পারে। Object oriented: জিনিস ধরে গোছানো class Account private: owner, balance বাইরে থেকে ছোঁয়া যায় না deposit withdraw print শুধু তিনটা method-এই পৌঁছানো যায়। Balance হাতে বদলানো যায় না।
ছবি 1। একই account, দুইভাবে তাকে তোলা।

ছোট একটা উদাহরণ, দুইবার

Amara-র account-এ 500 টাকা আছে। সে 300 জমা দেয়, তারপর 1000 তুলতে চায়, যেটা ব্যর্থ হওয়ার কথা। নিচে সেই program দুই গড়নে, একই output সহ।

Example 1: structured রাস্তা, C-তে
#include <stdio.h>

/* Data is a plain bundle of fields. It knows nothing about behaviour. */
struct Account {
    char owner[32];
    long balance;
};

/* Behaviour lives outside, as functions that receive the data they need. */
void deposit(struct Account *a, long amount)
{
    a->balance += amount;
}

int withdraw(struct Account *a, long amount)
{
    if (amount > a->balance) {
        return 0;
    }
    a->balance -= amount;
    return 1;
}

void print_account(struct Account a)
{
    printf("%s has %ld\n", a.owner, a.balance);
}

int main(void)
{
    struct Account amara = {"Amara", 500};

    deposit(&amara, 300);
    print_account(amara);

    if (withdraw(&amara, 1000) == 0) {
        printf("not enough money\n");
    }
    print_account(amara);

    return 0;
}
Amara has 800
not enough money
Amara has 800

ভাগটা খেয়াল করো। struct Account data ধরে রাখে আর ছাপার ব্যাপারে কিছুই জানে না। print_account আচরণটা ধরে রাখে আর তার হাতে data ধরিয়ে দেওয়া হয়। এই program-এ কোনো কিছুই কারো কাছ থেকে লুকানো না।

Compiler-এ চালাও
Example 2: object oriented রাস্তা, C++-এ

আজ তুমি C++ শিখছ না। এটা পড়ো এর গড়ন-র জন্য, আর output মিলিয়ে দেখো।

#include <iostream>
#include <string>

// Data and behaviour live inside one capsule, and the data is hidden.
class Account {
private:
    std::string owner;
    long balance;

public:
    Account(std::string name, long start) : owner(name), balance(start) {}

    void deposit(long amount)
    {
        balance += amount;
    }

    bool withdraw(long amount)
    {
        if (amount > balance) {
            return false;
        }
        balance -= amount;
        return true;
    }

    void print() const
    {
        std::cout << owner << " has " << balance << "\n";
    }
};

int main()
{
    Account amara("Amara", 500);

    amara.deposit(300);
    amara.print();

    if (!amara.withdraw(1000)) {
        std::cout << "not enough money\n";
    }
    amara.print();

    return 0;
}
Amara has 800
not enough money
Amara has 800

একই তিনটা লাইন বেরোল। ভেতরে দুইটা সত্যিকারের পার্থক্য গেল। Function-গুলো এখন class-র ভেতরে থাকে, তাই তুমি deposit(&amara, 300)-র বদলে amara.deposit(300) লেখো। আর balance হলো private, তাই class-র বাইরের কোনো code সেটা হাতে বদলাতে পারে না।

এবার Kenji যা আবিষ্কার করেছিল সেটা দেখো। withdraw-র ভেতরে শরীরটা একটা selection, তারপর পরপর দুইটা ধাপ। তিনটা move কোথাও যায়নি।

Compiler-এ চালাও
Example 3: দ্বিতীয় ধরনের জিনিস এলে কী হয়

বিশ্ববিদ্যালয়টার এখন ছাত্রছাত্রীর পাশাপাশি শিক্ষকও দরকার। শিক্ষকের একটা নাম আর একটা বেতন আছে, নম্বর নেই। Structured গড়নে তুমি দ্বিতীয় একটা bundle আর দ্বিতীয় একটা function যোগ করো।

#include <stdio.h>

/* Two kinds of thing. Each gets its own bundle and its own function. */
struct Student {
    char name[32];
    int marks;
};

struct Teacher {
    char name[32];
    long salary;
};

void print_student(struct Student s)
{
    printf("student %s scored %d\n", s.name, s.marks);
}

void print_teacher(struct Teacher t)
{
    printf("teacher %s earns %ld\n", t.name, t.salary);
}

/* The three moves, inside one ordinary function. */
int count_passing(struct Student list[], int n, int pass_mark)
{
    int passed = 0;                       /* sequence */

    for (int i = 0; i < n; i++) {         /* repetition */
        if (list[i].marks >= pass_mark) { /* selection */
            passed++;
        }
    }

    return passed;                        /* sequence */
}

int main(void)
{
    struct Student class_list[3] = {
        {"Kenji", 91},
        {"Bob", 38},
        {"Zara", 77}
    };
    struct Teacher maria = {"Maria", 48000};

    for (int i = 0; i < 3; i++) {
        print_student(class_list[i]);
    }
    print_teacher(maria);

    printf("%d of 3 passed\n", count_passing(class_list, 3, 40));
    return 0;
}
student Kenji scored 91
student Bob scored 38
student Zara scored 77
teacher Maria earns 48000
2 of 3 passed

প্রায় একই নামের দুইটা function, আর যে call করবে তাকেই ঠিকটা বেছে নিতে হবে। দুই ধরনের জিনিসে এটা ঠিকই আছে। কুড়ি ধরনের বেলায় object oriented programming তার খরচটা উসুল করতে শুরু করে। একটা object-কে সোজা বলে দেওয়া যায় নিজেকে ছেপে দিতে।

count_passing-টাও দেখো। ওটার গায়ে তিনটা move লেখা আছে। Module 6 থেকে তুমি যত loop লিখবে, ওটা তার হুবহু গড়ন।

Compiler-এ চালাও

তুলনাটা, রায় ছাড়াই

Structured (C)Object oriented (C++, Java)
মূল এককFunctionClass আর তার object
গোছানো হয়Program কী করে, সেটা ধরেProgram-র কী আছে, সেটা ধরে
Data আর আচরণআলাদা রাখাএকসঙ্গে বাঁধা
Data-য় পৌঁছানোসাধারণত যেকোনো function-র জন্য খোলাসাধারণত লুকানো, method দিয়ে পৌঁছাতে হয়
ধরনউপর থেকে নিচে: বড় সমস্যাকে ছোট function-এ ভাগনিচ থেকে উপরে: object বানিয়ে তারপর জোড়া
Code আবার ব্যবহারFunction-টা আবার call করোInheritance আর composition
কিসে ভালোSystem-র code, driver, contest, ছোট আর মাঝারি toolঅনেক ধরনের জিনিস যেখানে একসঙ্গে কাজ করে, এমন বড় application
বাড়তি খরচখুবই কমকিছুটা, memory আর বাড়তি ধাপে

Object oriented কখন জেতে, সৎভাবে

এটা জেতে যখন অনেক ধরনের জিনিস আছে আর সবাইকে একইভাবে সামলাতে হয়। বৃত্ত, বর্গ আর লেখা নিয়ে একটা drawing program প্রতিটা আকৃতিকে বলতে পারে নিজেকে এঁকে ফেলতে। আলাদা করে ব্যতিক্রমের তালিকা লাগে না।

এটা জেতে যখন লুকানোটা জরুরি। balance যদি শুধু deposit আর withdraw দিয়েই বদলাতে পারে, তাহলে "কখনো শূন্যের নিচে যাবে না" নিয়মটার থাকার জায়গা ঠিক একটাই।

এটা জেতে বড় দলে। ছোট একটা public মুখওয়ালা খাপ হলো একটা চুক্তি, আর চুক্তি থাকলে লোকে একে অন্যের ভেতরটা না পড়েই কাজ করতে পারে।

আর এর একটা দামও আছে। একটা বাড়তি স্তর, ছোট কাজের জন্য বেশি আনুষ্ঠানিকতা, আর বড় system-এ গভীর inheritance-র শেকল, যেগুলো অনুসরণ করা কঠিন। কোনো গড়নই বেশি ভালো না। ওরা আলাদা আকারের সমস্যার সমাধান করে।

এই track কেন structured C দিয়ে শুরু করে

তিনটা কারণ। প্রথমত, তিনটা move দুই গড়নেরই ভিত্তি। মাঝখানে আর কিছু না থাকলে ওগুলো দেখতে সহজ।

দ্বিতীয়ত, C-তে তুমি function-এ ভাববে, আর function দুই ভাষারই একক। তৃতীয়ত, গোছানোর জন্য class লাগে না। Linux kernel প্রায় তিন কোটি লাইনের structured C, আর পৃথিবীর সবচেয়ে ভালোভাবে রক্ষণাবেক্ষণ করা codebase-গুলোর একটা।

গোছানোর জন্য যা লাগে সেটা হলো অভ্যাস। ছোট function। পরিষ্কার নাম। এক function-এ এক কাজ। এগুলোই Module 7 আর 8-র দক্ষতা, আর এই ভাষার পুরো হৃদয়টাই এখানে।

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

  • Linux kernel. পৃথিবীর সবচেয়ে বড় মাপে structured C। এটা data রাখে struct-এ আর আচরণ রাখে function-এ। object-র মতো একটা কৌশল সে ব্যবহার করে, function pointer-র একটা struct। ওভাবেই একটা interface অনেক যন্ত্রকে সামলায়।
  • SQLite. Structured C, প্রায় আড়াই লাখ লাইন, ছোট একটা public interface সহ। বাকি সব ব্যক্তিগত রাখা হয় ভাষার নিয়মে না, অভ্যাসে। এটা ভালো একটা যুক্তি যে syntax-র চেয়ে শৃঙ্খলা বেশি জরুরি।
  • তোমার browser. Chromium আর Firefox বেশিরভাগটাই C++, object-এ ভরা, কারণ একটা browser-এ হাজার ধরনের জিনিস একসঙ্গে কাজ করে। নিচে ওরা zlib-র মতো C library call করে।
  • Python. দুইটাই, একসঙ্গে। তুমি সারা দিন সাধারণ function লিখতে পারো, আবার তুমি যা ছোঁও সবই object। আর দুইটাই চালাচ্ছে যে interpreter, সেটা structured C।

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

১. ভাবা যে class নেই বলে C গোছানো যায় না।

এর কোনো error message নেই, শুধু এলোমেলো code আছে। C গোছায় struct, function আর আলাদা file দিয়ে। Module 15 দেখায় header আর source-র সেই ভাগটা, যেটা বড় C প্রকল্পগুলো class-র বদলে ব্যবহার করে।

২. বদলাতে চেয়ে struct-টা মান হিসেবে পাঠানো।

void deposit(struct Account a, long amount) {
    a.balance += amount;    /* changes a copy, not the caller's account */
}

Compiler চুপ। Balance শুধু কখনো বদলায় না, আর নতুনরা এতে এক ঘণ্টা খোয়ায়। সমাধান Example 1-এ আছে: একটা pointer নাও, struct Account *a, আর a->balance ব্যবহার করো। কেন, সেটা Module 11 বলে।

৩. ভেবে নেওয়া যে object oriented code এমনিতেই বেশি গোছানো।

চল্লিশটা method আর বারোটা field-র একটা class গোছানো না, ওটা ঢাকনা দেওয়া জঞ্জাল। দুই গড়নেই ছোট ছোট টুকরো লাগে, প্রতিটার একটা করে কাজ। ওই অংশটা ভাষা দিয়ে দেয় না।

৪. Function ভালোভাবে আয়ত্তে আসার আগেই C++-এ লাফ দেওয়া।

এটাই Kenji-র ভুল। একটা C++ method হলো একটা function, যার থাকার জায়গা নিয়ে বাড়তি কিছু নিয়ম আছে। Function, scope আর pointer এখনো টলমলে থাকলে class দ্বিতীয় একটা গুলিয়ে যাওয়ার জিনিস যোগ করে।

মাথা খাটাও

নিচের টুকরোটা দেখো। ঠিক করো এটা structured, object oriented, নাকি দুইটাই। আর বলো ঠিক কোন জিনিসটা দেখে তুমি সিদ্ধান্ত নিলে।

struct Shape {
    int kind;
    double a;
    double b;
    double (*area)(struct Shape *self);
};

double rect_area(struct Shape *self) { return self->a * self->b; }

/* used as: s.area(&s); */

তারপর কঠিন অর্ধেকটা। এটা বৈধ C, কোথাও কোনো class নেই। Example 2-র class Account-র সঙ্গে এর কী মিল আছে, আর এখনো কী নেই?

টুকরোটাকে দুইটা প্রশ্ন করো। Data কি নিজের আচরণ বয়ে বেড়াচ্ছে? আর বাইরের কিছু কি a আর b-তে সরাসরি পৌঁছাতে পারে? দুইটার উত্তর এক না।

অনুশীলন ১সহজ

খাতায় structured programming-র এক বাক্যের সংজ্ঞাটা লেখো, তারপর তিনটা move-র তালিকা দাও, প্রতিটার জন্য একটা রোজকার উদাহরণ সহ।

নিয়ম। Lesson-এ ফিরে তাকাবে না। তোমার বাক্যে তিনটা move-র নামই থাকতে হবে, আর "ঢোকার এক পথ, বেরোনোর এক পথ" কথাটাও থাকতে হবে।

নিজে যাচাই করো। কাউকে বাক্যটা পড়ে শোনাও, আর তাকে তিনটা move-র নাম ফিরিয়ে বলতে বলো। সে না পারলে বুঝবে তোমার বাক্যটা বড্ড বেশি কাজ করছে।

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

Example 1 নাও আর চতুর্থ একটা function যোগ করো, transfer, যেটা এক account থেকে আরেক account-এ টাকা সরায়।

নিয়ম। এটা দুইটা account আর একটা অঙ্ক নেবে। যে পাঠাচ্ছে তার কাছে যথেষ্ট না থাকলে এটা মানা করবে। withdraw-র ভেতরের নিয়মটা আবার লিখবে না; বরং withdraw আর deposit call করবে।

নিজে যাচাই করো। Amara থেকে Zara-কে 200 পাঠাও, তারপর 5000 পাঠানোর চেষ্টা করো। ব্যর্থ transfer-র আগে আর পরে দুইটা account-র মোট টাকা একই থাকতে হবে।

অনুশীলন ৩কঠিন

ধরো Example 3-র বিশ্ববিদ্যালয়ে এখন দশ ধরনের মানুষ, আর প্রত্যেকের নিজের print দরকার। সোজা বাংলায় লেখো, structured গড়নে কী সমস্যা হয়।

নিয়ম। দুইটা অনুচ্ছেদ। প্রথমটায় সমস্যাটার নাম থাকবে, function-র নাম আর যে call করছে তার কাজের হিসাবে। দ্বিতীয়টায় এমন একটা সমাধান দেবে যেটা C-র ভেতরেই থাকে, আর সেটা এখনো কী পারে না সেটাও স্বীকার করবে।

নিজে যাচাই করো। শক্ত উত্তর গিয়ে পৌঁছায় মাথা খাটাও অংশের struct Shape-র মতো কিছুতে। পৌঁছালে বুঝবে, Linux kernel যন্ত্র সামলানোর যে উপায়টা ব্যবহার করে, তুমি সেটাই নিজে আবিষ্কার করলে।

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

  • Structured programming কি object oriented-র চেয়ে খারাপ?

    না, আর প্রশ্নটার ভেতরে সাধারণত আরেকটা প্রশ্ন লুকিয়ে থাকে: এই সমস্যার জন্য কোনটা মানায়? Driver, contest-র সমাধান আর ছোট tool প্রায় সব সময়ই structured। অনেক ধরনের জিনিসওয়ালা বড় application সাধারণত object oriented।

  • C-তে কি object oriented code লেখা যায়?

    কাছাকাছি যাওয়া যায়, function pointer সহ struct দিয়ে, যেটা উপরের মাথা খাটাও অংশ। Linux kernel ঠিক এটাই করে। C যা দেবে না সেটা হলো ভাষা নিজে লুকানোটা মানতে বাধ্য করা।

  • এই track-র পর কি C++ শিখব?

    Contest-র দিকে যেতে চাইলে হ্যাঁ, আর এই সিরিজের পরের track-টাই ওটা। এখানে শেখা C প্রায় পুরোটাই কাজে লাগবে। Function আর pointer সাধারণ মনে হওয়ার পর যাওয়াটা করো।

  • goto কি আসলেই নিষিদ্ধ?

    না, আর Linux kernel-এ এটা হাজার হাজার বার আছে। নিয়মটা "কখনো না" না, নিয়মটা "ঢোকার এক পথ, বেরোনোর এক পথ"। একটাই cleanup block-এ সামনে লাফ দিলে নিয়মটা থাকে; loop-র ভেতরে পেছনে লাফ দিলে ভাঙে।

  • Track যখন C নিয়ে, তখন এই lesson C++ দেখায় কেন?

    কারণ প্রতিটা interview আর প্রতিটা syllabus-এ তুমি দুইটা শব্দই শুনবে। একই program দুইবার দেখা, একই output সহ, যেকোনো সংজ্ঞার চেয়ে দ্রুত কাজ করে।

মূল কথা

  • Structured programming sequence, selection আর repetition দিয়ে program বানায়।
  • Böhm আর Jacopini 1966 সালে প্রমাণ করেন, যেকোনো program-র জন্য এই তিনটাই যথেষ্ট।
  • প্রতিটা block-র ঢোকার এক পথ, বেরোনোর এক পথ, আর এটাই code পড়ার যোগ্য রাখে।
  • Structured code গোছায় কাজ ধরে; object oriented code গোছায় জিনিস ধরে।
  • Object লুকানো আর অনেক ধরনের জিনিসকে একভাবে সামলানো যোগ করে, কিছু খরচের বিনিময়ে।
  • C ভাবে function-এ, আর function দুই গড়নেরই একক, তাই আমরা ওখান থেকেই শুরু করি।

এখানেই Part Zero শেষ। পরেরটা module test। তারপর Module 1 তোমাকে সত্যিকারের C-র সামনে বসাবে। সেখানে পাবে এর চিহ্নগুলো, পুরো ASCII table, আর লাইন ধরে ধরে খুলে দেখা তোমার প্রথম program।

Module test

এই module এর উপর দশটা প্রশ্ন। 70% পেলে pass, আর যতবার খুশি দেওয়া যায়।

Module test দিন

lesson ৬ শেষ

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

পরেরটা: Module test: এক লাইন code লেখার আগে