Learn C Programming

lesson ১ / ৯ · Structure, union আর enum

Module ১২ · Structure, union আর enum

struct: যে তথ্যগুলো একসঙ্গে থাকে, তাদের এক করা

Freeপড়া

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

  • নানা type-এর field দিয়ে একটা struct বানাতে পারবে, আর ওই type-এর variable-কে ক্রম ধরে বা field-এর নাম ধরে initialise করতে পারবে।
  • Dot দিয়ে field পড়তে, লিখতে আর scanf দিয়ে ভরতে পারবে, = দিয়ে পুরো record copy করতে পারবে, আর নিজের লেখা function দিয়ে দুইটা record তুলনা করতে পারবে।
  • Memory-তে একটা struct এঁকে দেখাতে পারবে, field-গুলো declare করার ক্রমে, আর sizeof দিয়ে ওর size মিলিয়ে নিতে পারবে।

Maria ওর দোকানের জিনিসের হিসাব রাখে তিনটা array-তে: নাম, দাম আর stock। প্রতিটা array-র box 0-তে rice, box 1-এ oil, আর তিনটাকে এক তালে রাখে শুধু Maria-র সাবধানতা।

এক সন্ধ্যায় ও নামগুলো বর্ণমালার ক্রমে sort করে, কিন্তু দামগুলো সরাতে ভুলে যায়। এখন list বলছে oil-এর দাম 65 টাকা, আর প্রথম যে খদ্দের oil কিনবে, তার তো আজ কপাল খুলে গেল।

এই lesson Maria-কে প্রতিটা জিনিসের জন্য একটাই variable দেয়, যেটা তিনটা মানই ধরে রাখে। তাই ওরা একসাথে ছাড়া নড়তেই পারে না।

Struct একটা record-এর field-গুলো একসাথে রাখে

Sort-এর আগে আর পরে তিনটা array দেখতে এমন, প্রতি index-এ এক সারি।

Indexআগে: নাম, দাম, stockশুধু নাম sort করার পরে
0rice, 65, 40oil, 65, 40
1oil, 180, 12rice, 180, 12
2salt, 40, 30salt, 40, 30

C-এর কোনো নিয়ম names[1]-কে prices[1]-এর সাথে বেঁধে রাখে না; ওরা পাশাপাশি আছে শুধু Maria রেখেছে বলে। একটা জিনিসের নাম, দাম আর stock মিলে একটা record। আর একটা record-এর জায়গা একটাই variable-এ।

C এটা দেয় structure দিয়ে, লেখা হয় struct। এটা এমন একটা type, যেটা তুমি নিজেই বানাও: কয়েকটা মান এক করে, যাদের প্রত্যেকের নিজের নাম আর নিজের type আছে। ভিতরের প্রতিটা মানকে বলে field; C standard একে বলে member।

Struct-এর definition

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};
  • struct product হলো নতুন type-এর নাম। struct-এর পরের শব্দটা, মানে product, হলো ওর tag।
  • Braces-এর ভিতরের প্রতিটা লাইন একটা field declare করে: আগে type, তারপর নাম। একেক field-এর type একেক রকম হতে পারে।
  • char name[NAME_LEN]; হলো একটা char array field: 19 অক্ষর পর্যন্ত একটা শব্দ, সাথে শেষের শূন্যটা (Module 10)।
  • }-এর পরের ; definition শেষ করে। ওটা বাদ দিলে error আসে পরের লাইনে।
#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

int main(void)
{
    struct product rice = { "rice", 65, 40 };

    printf("%s: %d taka, %d in stock\n", rice.name, rice.price, rice.stock);
    return 0;
}
rice: 65 taka, 40 in stock

Definition-টা বসে main-এর উপরে, তাই file-এর প্রতিটা function type-টা ব্যবহার করতে পারে। এটা কোনো variable বানায় না, memory-ও আলাদা করে রাখে না। শুধু একটা গড়ন বলে দেয়, যেন তিন ঘরের একটা ফাঁকা ফর্ম।

main-এর লাইনটা struct product type-এর একটা variable বানায়, নাম rice; type-এর নাম দুইটা শব্দ মিলেই। Braces-এর ভিতরের অংশটা initialiser, মানে field-গুলোর শুরুর মান।

একটা field-এ পৌঁছাতে লেখো variable, একটা dot, তারপর field-এর নাম। এটাই dot operator: rice.price জোরে পড়লে দাঁড়ায় "rice-এর price field", আর এটা একদম সাধারণ একটা int।

তাই struct-এর definition গড়নটা একবারই বলে দেয়, আর ওই type-এর প্রতিটা variable হলো একটা পুরো record।

Initialiser: ক্রম ধরে, নাম ধরে, আর শূন্যের নিয়ম

{ "rice", 65, 40 }-এর braces field-গুলো ভরে ঠিক যে ক্রমে ওরা declare করা হয়েছে, সেই ক্রমে। একে বলে positional initialiser: ছোট, কিন্তু field-এর ক্রমটা তোমাকে মনে রাখতে হয়।

Designated initialiser প্রতিটা field-এর নাম বলে দেয় একটা dot দিয়ে: { .price = 180, .name = "oil" }। তখন ক্রমে আর কিছু যায় আসে না, আর যে পড়ে সে দেখেই বোঝে কোন মান কোথায় যাচ্ছে। Module 9-এর lesson 1 array-র box-এর বেলায় ঠিক এটাই করেছিল।

শূন্যের নিয়মটাও array থেকেই আসে: যে field বাদ রাখো, সেটা শুরু হয় শূন্য দিয়ে। সংখ্যা হলে 0, আর char array হলে ফাঁকা string। পুরো record শূন্য দিয়ে শুরু করতে এই track লেখে = { 0 }। Maria চারটা ধরনই চালিয়ে দেখে।

#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

int main(void)
{
    struct product rice = { "rice", 65, 40 };
    struct product oil = { .price = 180, .name = "oil" };
    struct product salt = { "salt" };
    struct product blank = { 0 };

    printf("%s %d %d\n", rice.name, rice.price, rice.stock);
    printf("%s %d %d\n", oil.name, oil.price, oil.stock);
    printf("%s %d %d\n", salt.name, salt.price, salt.stock);
    printf("[%s] %d %d\n", blank.name, blank.price, blank.stock);
    return 0;
}
rice 65 40
oil 180 0
salt 0 0
[] 0 0

oil কোনো stock পায়নি, তাই ওর stock 0। salt পেয়েছে শুধু নাম, তাই ওর দাম আর stock দুটোই 0। শেষ লাইনের bracket দুটো blank-এর ফাঁকা নামটাকে ঘিরে রেখেছে।

Playground-এ কোনো বার্তা নেই, -Wall-এও নেই। -Wextra শুধু salt-এর জন্য একটা warning দেয়: warning: missing initializer for field 'price' of 'struct product' [-Wmissing-field-initializers]। ছোট একটা positional list ভুল করেও হতে পারে, তাই GCC জিজ্ঞেস করে নেয়। { 0 } আর designated oil যা বোঝাতে চায় স্পষ্ট বলে দেয়, তাই ওরা কোনো warning পায় না।

তাই যে field বাদ রাখো সেটা শূন্য দিয়ে শুরু হয়, আর designated initialiser যেকোনো ক্রমে field-এর নাম ধরে মান বসাতে দেয়।

Dot দিয়ে field-এ লেখা, আর scanf দিয়ে ভরা

Dot =-এর বাঁ দিকেও চলে: item.price = item.price + 5; একটা field পড়ে, 5 যোগ করে, আবার ওখানেই লিখে দেয়। বাকি field-গুলো নড়ে না।

scanf-এর দরকার, যে বাক্সে লিখবে তার ঠিকানা (Module 3, lesson 4)। একটা int field-এর বেলায় সেটা &item.price: dot আগে কাজ করে, তাই & ঠিক ওই field-টার ঠিকানা নেয়।

নামের field-এ কোনো & লাগে না, কারণ expression-এর ভিতরে array-র নাম নিজেই একটা ঠিকানা (Module 11, lesson 3)। %19s-এর 19 শেষের শূন্যটার জন্য জায়গা রাখে (Module 10, lesson 3)।

#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

int main(void)
{
    struct product item = { 0 };

    scanf("%19s %d %d", item.name, &item.price, &item.stock);
    item.price = item.price + 5;
    printf("%s now costs %d taka, %d in stock\n", item.name, item.price, item.stock);
    return 0;
}
lentils now costs 125 taka, 25 in stock

ওই output-টা lentils 120 25 input-এর জন্য।

তাই item.price আর যেকোনো int-এর মতোই একটা বাক্স: পড়ো, লেখো, বা ওর ঠিকানা scanf-এর হাতে দাও।

Assignment পুরো record-টাই copy করে

Maria একটা weekend offer চায়: একই rice, দাম 60 টাকা, আলাদা নামে। ও একটা = দিয়ে record-টা copy করে, তারপর copy-টা বদলায়।

#include <stdio.h>
#include <string.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

int main(void)
{
    struct product rice = { "rice", 65, 40 };
    struct product offer = { 0 };

    offer = rice;
    strcpy(offer.name, "rice offer");
    offer.price = 60;

    printf("%s: %d taka, %d in stock\n", rice.name, rice.price, rice.stock);
    printf("%s: %d taka, %d in stock\n", offer.name, offer.price, offer.stock);
    return 0;
}

প্রতিটা সারি দেখায়, ওই লাইন run হওয়ার পরে দুইটা record-এর অবস্থা।

যে লাইনের পরেriceoffer
struct product offer = { 0 };rice, 65, 40(ফাঁকা), 0, 0
offer = rice;rice, 65, 40rice, 65, 40
strcpy(offer.name, "rice offer");rice, 65, 40rice offer, 65, 40
offer.price = 60;rice, 65, 40rice offer, 60, 40
rice: 65 taka, 40 in stock
rice offer: 60 taka, 40 in stock

একটা = তিনটা field-ই copy করেছে, নামের 20টা char বাক্সসহ। এরপর থেকে record দুইটা আলাদা: offer বদলালে rice-এ হাতই পড়ে না, ঠিক দুইটা int variable-এর মতো (Module 2, lesson 1)।

Array একা থাকলে = দিয়ে assign করা যায় না (Module 11, lesson 3), অথচ এই struct-এর ভিতরের array-টা ঠিকই copy হয়ে গেল। কারণ struct নিজে একটা মান, আর = একটা মানকে পুরোটাই copy করে, ভিতরে যা-ই থাকুক।

তবে field-টা একা এখনো একটা array। Bob offer-এর নাম বদলাতে লেখে offer.name = "rice offer";। প্রতিটা command line-এ error, Playground-সহ: error: assignment to expression with array type, সাথে badge Compile error (বাংলা page-এ একই badge বাংলায় দেখায়)। char array field-এ লেখা বসাতে লাগে <string.h>-এর strcpy (Module 10, lesson 4), যেমনটা Maria করেছে।

তাই = struct-এর সব field একবারে copy করে, কিন্তু একা একটা char array field-এর জন্য এখনো strcpy লাগে।

Struct-এ == চলে না: তুলনাটা নিজে লেখো

= যদি পুরো record copy করতে পারে, তাহলে == নিশ্চয়ই দুইটা record তুলনা করতে পারবে? Copy ঠিক হলো কি না দেখতে Maria লেখে if (rice == offer)। প্রতিটা command line-এ error, Playground-সহ: error: invalid operands to binary == (have 'struct product' and 'struct product')।

Operand হলো সেই মান, যার উপর operator কাজ করে, আর binary operator নেয় দুইটা। GCC বলছে, struct product type-এর দুইটা মানের জন্য ==-এর কোনো মানেই নেই। C এটা ইচ্ছা করেই রাখেনি: তোমার type-এর বেলায় "সমান" মানে কী, সেটা শুধু তুমিই জানো। Lesson 6 আরেকটা কারণ দেখাবে, যেটা লুকিয়ে আছে field-গুলোর মাঝখানের byte-এ।

তাই তুলনাটা তুমি লেখো একটা function হিসেবে। Zara মিলিয়ে দেখে, তাকের জিনিসটা Maria-র stock খাতার সাথে প্রতিটা field-এ মেলে কি না। সংখ্যা তুলনা হয় == দিয়ে; নাম তুলনা হয় strcmp দিয়ে, দুইটা string মিললে যেটা 0 return করে।

#include <stdio.h>
#include <string.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

int equal_products(struct product a, struct product b);

int main(void)
{
    struct product shelf = { "oil", 180, 12 };
    struct product book = { "oil", 180, 12 };
    struct product label = { "oil", 185, 12 };

    printf("shelf and book: %d\n", equal_products(shelf, book));
    printf("shelf and label: %d\n", equal_products(shelf, label));
    return 0;
}

int equal_products(struct product a, struct product b)
{
    return strcmp(a.name, b.name) == 0
        && a.price == b.price
        && a.stock == b.stock;
}
shelf and book: 1
shelf and label: 0

তিনটা field-ই মিললে function 1 return করে, নইলে 0; তাকের লেবেলে দাম লেখা 185 টাকা। পরে Maria যদি ঠিক করে stock ধরা হবে না, ও শুধু একটা function বদলাবে, program-এর প্রতিটা if না।

তাই C struct-কে = দেয়, == দেয় না, আর "সমান" মানে তোমার function যা বলে তা-ই।

যে function একটা struct নেয়

Maria অনেক জায়গায় জিনিসের হিসাব print করে, আর সব জায়গায় একই printf copy করলেই ভুল ঢুকে পড়ে। তাই Alice ওটা একবারই লেখে, একটা function হিসেবে। int-এর মতো একটা struct type-ও parameter হতে পারে।

#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

void print_product(struct product p);

int main(void)
{
    struct product rice = { "rice", 65, 40 };
    struct product oil = { "oil", 180, 12 };
    struct product lentils = { "lentils", 120, 25 };

    print_product(rice);
    print_product(oil);
    print_product(lentils);
    return 0;
}

void print_product(struct product p)
{
    printf("%-8s %4d taka %4d in stock\n", p.name, p.price, p.stock);
}
rice       65 taka   40 in stock
oil       180 taka   12 in stock
lentils   120 taka   25 in stock

Prototype আসে struct-এর definition-এর পরে, কারণ ওটা type-টা ব্যবহার করে। %-8s প্রতিটা নামকে 8 ঘর জায়গা দেয়, আর %4d প্রতিটা সংখ্যাকে 4 ঘর, তাই কলামগুলো সোজা থাকে (Module 3, lesson 3)।

Parameter হলো নতুন একটা বাক্স, যেটায় থাকে argument-এর একটা copy (Module 7, lesson 3)। Struct-এর বেলাতেও তা-ই: p একটা নতুন record, যাতে প্রতিটা field-এর copy আছে। ওই lesson বলেছিল, structure-এর বেলায় copy দামি হয়ে যায়। খরচটা মাপবে lesson 2, আর সেটা এড়ানোর পথ দেখাবে lesson 3।

তাই একটা function পুরো একটা record-কে একটা parameter হিসেবে নিতে পারে, আর কাজ করে নিজের copy নিয়ে।

Memory-তে struct

একটা struct variable মানে memory-র একটাই টানা টুকরো। C17 কথা দেয়, field-গুলো বসবে তুমি যে ক্রমে declare করেছ সেই ক্রমে, একটার পর একটা বড় ঠিকানায়। প্রথম field শুরু হয় ঠিক যেখানে struct শুরু হয়। Kenji sizeof-কে জিজ্ঞেস করে (Module 2, lesson 5), আর সাথে নিজের একটা ছোট record-ও জুড়ে দেয়।

#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

struct result {
    char grade;
    int marks;
};

int main(void)
{
    struct product rice = { "rice", 65, 40 };
    struct result kenji = { 'A', 91 };

    printf("name %zu + price %zu + stock %zu\n",
           sizeof rice.name, sizeof rice.price, sizeof rice.stock);
    printf("sizeof rice: %zu\n", sizeof rice);
    printf("grade %zu + marks %zu\n", sizeof kenji.grade, sizeof kenji.marks);
    printf("sizeof kenji: %zu\n", sizeof kenji);
    return 0;
}
name 20 + price 4 + stock 4
sizeof rice: 28
grade 1 + marks 4
sizeof kenji: 8
Memory-তে rice আর kenji: declare করার ক্রমে field, আর kenji-তে 3 byte padding struct product rice: 28 byte, field-গুলো declare করার ক্রমে name 20 byte price 4 byte stock 4 byte r i c e \0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 65 40 0 20 24 28 Offset মানে rice-এর শুরু থেকে কত byte দূরে; ঠিকানা বাড়ে ডান দিকে। 20 + 4 + 4 = 28 = sizeof rice: এই field-গুলোর মাঝে কোনো ফাঁক নেই। struct result kenji: 5 না, 8 byte grade 1 byte padding 3 byte marks 4 byte 'A' ফাঁকা 91 0 1 4 8 field মিলিয়ে 1 + 4 = 5 byte, তবু sizeof kenji হলো 8: grade-এর পরে 3 byte ফাঁকা। Field-এর মাঝের এমন ফাঁকা byte-কে বলে padding, compiler নিজেই বসায়; কোথায় বসে আর কেন, সেটা মাপবে lesson 6।

rice-এর বেলায় যোগফল মিলে যায়: 20 + 4 + 4 = 28। কোনো field struct-এর শুরু থেকে কত byte দূরে বসে আছে, সেটাই ওর offset। Compiler Explorer-এর GCC 12-এ, Playground-এর -O2-তে মেপে দেখা গেছে: name আছে 0-তে, price 20-এ আর stock 24-এ।

Kenji-র record-এ হিসাব মেলে না: একটা char 1 byte আর একটা int 4 byte, তবু sizeof kenji হলো 8। Compiler grade-এর পরে 3টা byte ফাঁকা রেখে দিয়েছে, তাই marks শুরু হয় offset 4 থেকে। এমন ফাঁকা byte-কে বলে padding, আর এর কারণ বুঝিয়ে বলবে lesson 6।

তাই field-গুলো বসে declare করার ক্রমে, আর একটা struct-এর sizeof কমপক্ষে ওর field-গুলোর যোগফল, কখনো কখনো তার চেয়েও বেশি।

Example 1: সবচেয়ে ছোট struct, map-এর একটা বিন্দু

Kenji-র map-এ একটা বিন্দু মানে দুইটা পূর্ণসংখ্যা, x আর y। লেখার মতো সবচেয়ে ছোট struct ঠিক এই দুইটাই ধরে।

#include <stdio.h>

struct point {
    int x;
    int y;
};

int main(void)
{
    struct point shop = { 3, 4 };

    printf("the shop is at (%d, %d)\n", shop.x, shop.y);
    shop.x = 5;
    printf("after the move: (%d, %d)\n", shop.x, shop.y);
    return 0;
}
the shop is at (3, 4)
after the move: (5, 4)

একই type-এর দুইটা field হলেও struct বানানো কাজের: একটাই নাম, shop, পুরো বিন্দুটাকে বোঝায়। shop.x-এ লিখলে shop.y-তে কোনো হাত পড়ে না।

Run in Compiler
Example 2: Kenji-র মাঝপথের বিন্দু

Kenji-র drone depot থেকে Maria-র দোকানে যায়, আর মাঝপথে একবার battery বদলাতে থামে। মাঝের বিন্দুর x হলো দুইটা x field-এর গড়; y-এর বেলাতেও একই কথা।

#include <stdio.h>

struct point {
    int x;
    int y;
};

void print_point(struct point p);

int main(void)
{
    struct point depot = { -40, 10 };
    struct point shop = { 120, 70 };
    struct point halfway = { 0 };

    halfway.x = (depot.x + shop.x) / 2;
    halfway.y = (depot.y + shop.y) / 2;

    printf("depot:   ");
    print_point(depot);
    printf("shop:    ");
    print_point(shop);
    printf("halfway: ");
    print_point(halfway);
    return 0;
}

void print_point(struct point p)
{
    printf("(%d, %d)\n", p.x, p.y);
}
depot:   (-40, 10)
shop:    (120, 70)
halfway: (40, 40)

উত্তরটা রাখে তৃতীয় একটা struct point, একটা একটা করে field ভরে। দুইটা যোগফলই জোড়, তাই / 2-এ কিছু হারায় না; বিজোড় হলে অর্ধেকটা বাদ পড়ত (Module 4)।

Run in Compiler
Example 3: দোকানের counter-এ একটা বিক্রি, আগে আর পরে

Maria counter-এ এক লাইনে লেখে: জিনিসের নাম, দাম, stock, আর কয়টা বিক্রি হলো। Program বিক্রির আগে আর পরে record-টা print করে, তারপর কত টাকা এলো সেটা।

#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

void print_product(struct product p);

int main(void)
{
    struct product item = { 0 };
    int sold = 0;

    scanf("%19s %d %d %d", item.name, &item.price, &item.stock, &sold);

    printf("before: ");
    print_product(item);
    item.stock = item.stock - sold;
    printf("after:  ");
    print_product(item);
    printf("takings: %d taka\n", sold * item.price);
    return 0;
}

void print_product(struct product p)
{
    printf("%s, %d taka, %d in stock\n", p.name, p.price, p.stock);
}
before: tea, 120 taka, 30 in stock
after:  tea, 120 taka, 26 in stock
takings: 480 taka

ওই output-টা tea 120 30 4 input-এর জন্য। দুই লাইনের মধ্যে বদলেছে শুধু stock। Zara আগে 0 বিক্রি দিয়ে চালিয়ে দেখে, আর দুইটা লাইন হুবহু এক আসে।

Run in Compiler

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

  • C-এর নিজের calendar। <time.h> তারিখ আর সময় রাখে একটা struct tm-এ। ওর field-গুলোর মধ্যে আছে tm_mday (মাসের কত তারিখ), tm_mon (0 থেকে 11) আর tm_year (1900-এর পরে কত বছর)।
  • C-এর নিজের ভাগ। <stdlib.h>-এর div(17, 5) return করে একটা div_t, যার দুইটা field: quot হলো 3, আর rem হলো 2।
  • File-এর খুঁটিনাটি। POSIX-এর stat একটা file-এর জন্য struct stat ভরে দেয়: size থাকে st_size-এ, ভিতরের লেখা শেষ কবে বদলেছে সেটা st_mtime-এ, আরও অনেক কিছু। ls -l ঠিক এই field-গুলোই size আর তারিখের কলামে দেখায়।
  • Game। SDL2 হলো game আর graphics-এর একটা C library। ও একটা আয়তক্ষেত্র বোঝায় SDL_Rect দিয়ে, যার int field হলো x, y, w আর h। SDL_RenderFillRect এমন একটা record থেকে screen-এ ভরাট একটা বাক্স আঁকে।

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

১. Bob শেষ brace-এর পরে semicolon দিতে ভুলে যায়।

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
}

void print_product(struct product p);

প্রতিটা command line-এ error, Playground-সহ: error: expected ';', identifier or '(' before 'void'। GCC আঙুল তোলে prototype-এর দিকে, মানে struct-এর পরের লাইনে। Definition চাইলে নিজের ;-এর আগে variable-ও declare করতে পারে, তাই GCC পড়তে পড়তে এগিয়ে যায়। প্রতিটা struct definition শেষ করো }; দিয়ে। এটা তুমি ভুলবে, কারণ function-এর শেষ brace-এর পরে কিছুই লাগে না।

২. ভুল ক্রমে positional initialiser।

struct product oil = { "oil", 12, 180 };

কোনো command line-এই বার্তা নেই। Bob বোঝাতে চেয়েছিল stock 12, দাম 180 টাকা। কিন্তু field-এর ক্রম হলো name, price, stock, তাই program print করে oil: 12 taka, 180 in stock। লেখো { .name = "oil", .price = 180, .stock = 12 }, তাহলে ক্রমের ঝামেলাই থাকে না। এই ভুলটা হবে, কারণ দুইটা সংখ্যাই আলাদা করে দেখলে ঠিকঠাক লাগে।

৩. একটা conversion দিয়ে পুরো struct print করা।

struct point shop = { 3, 4 };

printf("%d\n", shop);

Playground-এ নীরব। নিজের মেশিনে GCC 12-এর gcc -Wall বলে warning: format '%d' expects argument of type 'int', but argument 2 has type 'struct point' [-Wformat=]। Struct-এর জন্য printf-এর কোনো conversion নেই, তাই এটা undefined behaviour, আর যা-ই print হোক তার কোনো মানে নেই। প্রতিটা field ওর নিজের conversion দিয়ে print করো, অথবা একটা print function লেখো। এটা তুমি চেষ্টা করবে, কারণ এতদিন %d বাকি সব কিছুই print করে এসেছে।

মাথা খাটাও

Kenji এমন একটা struct লেখে, যার কোনো tag-ই নেই, আর ওর দুইটা variable declare করে একটাই declaration-এ।

#include <stdio.h>

int main(void)
{
    struct { int a; } x = { 1 }, y = { 2 };

    x = y;
    printf("x.a is %d\n", x.a);
    if (x == y) {
        printf("equal\n");
    }
    return 0;
}

x = y; কী copy করে? GCC 12 এই program-এর একটা লাইন মেনে নেয় না: কোন লাইন, আর ওর বার্তায় কোন type-এর নাম আসে? ওই type-এর নাম তুমি কোথাও লেখোনি।

rice == offer নিয়ে GCC কী বলেছিল, আবার পড়ো। তারপর ভাবো, tag নেই এমন struct type-এর নাম compiler কীভাবে বলবে।

অনুশীলন ১সহজ

Maria প্রতিটা তাকের জন্য একটা card print করে। void print_product(struct product p) লেখো, যেটা একটা জিনিস এক লাইনে print করে।

Input. এক লাইনে একটা নাম, একটা দাম আর stock-এর সংখ্যা, space দিয়ে আলাদা। নামটা একটাই শব্দ।

Output. এক লাইন: name: <name>, price: <price> taka, stock: <stock>।

Constraints. নামে 1 থেকে 19টা অক্ষর; 1 <= price <= 100000; 0 <= stock <= 100000।

Sample. Input rice 65 40 দিলে name: rice, price: 65 taka, stock: 40।

#include <stdio.h>

#define NAME_LEN 20

struct product {
    char name[NAME_LEN];
    int price;
    int stock;
};

void print_product(struct product p);

int main(void)
{
    struct product item = { 0 };

    scanf("%19s %d %d", item.name, &item.price, &item.stock);
    print_product(item);
    return 0;
}

void print_product(struct product p)
{
    /* Print the three fields of p on one line, in the exact words
       of the Output section: the name, the price, then the stock. */
}

আলাদা করে গ্রেড হয় না। তিনটা মান ধরে রাখা struct-টা judge দেখতে পায় না, আর এমন লাইন Module 3-এর problem-গুলোতেই print করা হয়ে গেছে।

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

Kenji-র delivery drone শহরের map-এ দুইটা বিন্দুর মধ্যে সোজা উড়ে যায়, মিটারের হিসাবে। double distance(struct point a, struct point b) লেখো। বর্গমূল দেয় <math.h>-এর sqrt (Module 7, lesson 2)।

Input. প্রতি লাইনে চারটা পূর্ণসংখ্যা x1 y1 x2 y2, input-এর শেষ পর্যন্ত। অন্তত একটা লাইন থাকবে, আর সর্বোচ্চ 1000টা।

Output. প্রতিটা লাইনের জন্য (x1, y1) থেকে (x2, y2)-এর সোজা দূরত্ব, দশমিকের পরে ঠিক দুইটা অঙ্ক।

Constraints. -10000 <= প্রতিটা coordinate <= 10000।

Sample. Input 0 0 3 4 দিলে 5.00।

#include <stdio.h>
#include <math.h>

struct point {
    int x;
    int y;
};

double distance(struct point a, struct point b);

int main(void)
{
    struct point a = { 0 };
    struct point b = { 0 };

    while (scanf("%d %d %d %d", &a.x, &a.y, &b.x, &b.y) == 4) {
        printf("%.2f\n", distance(a, b));
    }
    return 0;
}

double distance(struct point a, struct point b)
{
    /* How far apart are the two x fields, and the two y fields?
       Pythagoras joins them: the square root of the sum of their squares. */
    return 0.0;
}

point-distance নামে গ্রেড হয়। Hidden test-এ আছে একই বিন্দু দুইবার, যেখানে 0.00 print করতে হবে, আর দূরের দুই কোণ -10000 -10000 10000 10000, যাদের দূরত্ব 28284.27।

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

Amara-র booking form একটা তারিখ রাখে একটা record হিসেবে, তিনটা আলগা সংখ্যা হিসেবে না। int is_valid(struct date d) লেখো: তারিখটা সত্যিই থাকলে 1 return করবে, না থাকলে 0। Module 5-এর calendar-এর নিয়মই চলবে। April, June, September আর November-এ 30 দিন। February-তে 28, leap year হলে 29, আর বাকি সব মাসে 31। কোনো বছর 4 দিয়ে ভাগ গেলে কিন্তু 100 দিয়ে না গেলে leap year, আর 400 দিয়ে ভাগ গেলেও leap year।

Input. এক লাইনে তিনটা পূর্ণসংখ্যা: day, month আর year।

Output. এক লাইন: তারিখটা থাকলে valid, নইলে invalid।

Constraints. -100 <= day <= 100, -100 <= month <= 100, আর 1 <= year <= 9999।

Sample. Input 29 2 2024 দিলে valid। Input 31 4 2025 দিলে invalid।

#include <stdio.h>

struct date {
    int day;
    int month;
    int year;
};

int is_valid(struct date d);

int main(void)
{
    struct date d = { 0 };

    scanf("%d %d %d", &d.day, &d.month, &d.year);
    if (is_valid(d)) {
        printf("valid\n");
    } else {
        printf("invalid\n");
    }
    return 0;
}

int is_valid(struct date d)
{
    /* First the month: is d.month between 1 and 12?
       Then the day: how many days does that month have in d.year? */
    return 0;
}

আলাদা করে গ্রেড হয় না। Module 5-এর date-check ঠিক এই output-টাই গ্রেড করে; এখানে আসল কথা হলো struct-টা, আর যে function ওটা নেয়।

Run in Compiler

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

  • Function-এর শেষে তো semicolon লাগে না, তাহলে struct definition কেন semicolon দিয়ে শেষ হয়?

    Struct definition আসলে একটা declaration, আর declaration শেষ হয় ; দিয়ে। } আর ;-এর মাঝে তুমি চাইলে ওই type-এর variable-ও declare করে ফেলতে পারো, যেমনটা brain teaser-এ করা হয়েছে। Function-এর body কোনো declaration না, তাই ওটা brace-এই শেষ।

  • Struct definition কোথায় লিখব?

    Prototype আর main-এর উপরে, সব function-এর বাইরে। একটা type চেনা যায় ওর definition থেকে file-এর শেষ পর্যন্ত (Module 7, lesson 4)। main-এর ভিতরে define করলে অন্য কোনো function ওটা ব্যবহারই করতে পারবে না।

  • কোনো field কি সংখ্যার array, বা আরেকটা struct হতে পারে?

    দুটোই পারে। int marks[3]; দিব্যি একটা field। Lesson 4 একটা record-এর ভিতরে একটা তারিখ বসায়, আর তখন dot-গুলো পরপর চলে: loan.due.day।

  • = struct-এর ভিতরের array copy করতে পারে, কিন্তু একা একটা array পারে না কেন?

    Struct একটা মান, আর = একটা মানকে পুরোটা copy করে। Expression-এর ভিতরে array-র নাম হয়ে যায় ওর প্রথম বাক্সের ঠিকানা (Module 11, lesson 3)। তাই copy করার মতো পুরো কোনো array মান থাকেই না। এজন্যই offer.name = "rice offer"; চলে না।

মূল কথা

  • Struct নানা type-এর field এক করে একটা type-এর নামে; ওর definition শেষ হয় }; দিয়ে, আর কোনো variable বানায় না।
  • Initialise করো ক্রম ধরে, { "rice", 65, 40 }, বা নাম ধরে, { .price = 180, .name = "oil" }; বাদ পড়া field শুরু হয় শূন্য দিয়ে।
  • Dot একটা field পড়ে আর লেখে; scanf নেয় &item.price, কিন্তু item.name নেয় & ছাড়া।
  • = পুরো struct copy করে, char array field-সহ; একা একটা char array field-এর লাগে strcpy।
  • Struct-এর জন্য == compile হয় না: একটা function-এ field ধরে ধরে তুলনা করো, নামের জন্য strcmp।
  • Field বসে declare করার ক্রমে: struct product হলো 20 + 4 + 4 = 28 byte, কিন্তু Kenji-র char আর int নেয় 8 byte, মাঝে 3 byte padding।

এরপর Amara পুরো একটা ক্লাসের record রাখে একটা array-তে, আর সেগুলো function-এর হাতে তুলে দেয়।

lesson ১ শেষ

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

পরেরটা: struct-এর array, আর function-এ struct