Learn C Programming

lesson ৫ / ৮ · Operator আর type কনভার্শন

Module ৪ · Operator আর type কনভার্শন

Precedence আর associativity: কখন bracket দেবে

Freeপড়া

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

  • Precedence-র table-টাকে মুখস্থ করার তালিকা না ভেবে দেখে নেওয়ার তালিকা হিসেবে পড়তে পারবে।
  • তুমি যত লাইন লিখবে তার প্রায় সবটার ক্রম যে পাঁচটা নিয়মে মিটে যায়, সেগুলো বলতে পারবে।
  • পাঠককে যেখানে থমকে ভাবতে হয়, সেখানেই bracket বসাতে পারবে।

Zara দেখছে একটা flag byte-র সবচেয়ে নিচের bit খালি কি না। ও লেখে x & 1 == 0, যেটা পড়তে হুবহু কথাটার মতো।

Playground একটা কথাও না বলে ওটা compile করে। x 4 হলে উত্তর 1 হওয়ার কথা, কিন্তু আসে 0। আর বাকি প্রতিটা মানেও 0 আসে।

Operator-গুলো ঠিক, ক্রমটা ভুল। & কাছে যাওয়ার আগেই == দুই পাশের মান দখল করে নেয়।

Precedence ঠিক করে কে মানটা পাবে

2 + 3 * 4-তে 3 সংখ্যাটা দুই operator-র মাঝে বসে আছে। ওদের একজনই ওকে পেতে পারে।

Precedence হলো সেই তালিকা যেটা ঝগড়াটা মিটিয়ে দেয়। *-র পদ +-র উপরে, তাই 3 যায় গুণের কাছে।

#include <stdio.h>

int main(void)
{
    printf("2 + 3 * 4   = %d\n", 2 + 3 * 4);
    printf("(2 + 3) * 4 = %d\n", (2 + 3) * 4);
    return 0;
}
2 + 3 * 4   = 14
(2 + 3) * 4 = 20

Bracket সবার উপরে। ওটা compiler-কে দেওয়া কোনো ইঙ্গিত না; ওটা বদলে দেয় কোন operator মানটা পাবে।

তাই precedence মানে সময়ের হিসাবে কোনটা আগে চলবে তা না। মানে হলো, কিছু চলার আগেই লাইনটা কীভাবে দল বাঁধবে।

Table, পনেরো সারি, উপরে সবচেয়ে উঁচুটা

C-র table-র যতটুকু এই track শিখিয়েছে বা শেখাবে, ততটুকু এখানে। এখানে দেখে নাও; মুখস্থ কোরো না।

সারিOperatorদল বাঁধেপ্রথম দেখা
1() [] . -> postfix ++ --বাঁ থেকে ডানেM1, M9, M12
2unary ! ~ + - prefix ++ -- (type) sizeofডান থেকে বাঁয়েM2, lesson 3
3* / %বাঁ থেকে ডানেlesson 1
4+ -বাঁ থেকে ডানেlesson 1
5<< >>বাঁ থেকে ডানেlesson 4
6< <= > >=বাঁ থেকে ডানেlesson 2
7== !=বাঁ থেকে ডানেlesson 2
8&বাঁ থেকে ডানেlesson 4
9^বাঁ থেকে ডানেlesson 4
10|বাঁ থেকে ডানেlesson 4
11&&বাঁ থেকে ডানেlesson 2
12||বাঁ থেকে ডানেlesson 2
13? :ডান থেকে বাঁয়েlesson 2
14= += -= *= /= %=ডান থেকে বাঁয়েM2, lesson 3
15,বাঁ থেকে ডানেএই lesson-র teaser

ওই table-র দুইটা জিনিস আরেকবার দেখার মতো, আর দুইটাই এই lesson-র ফাঁদ।

6 আর 7 নম্বর সারি বসে আছে 8 থেকে 10 নম্বরের উপরে। প্রতিটা তুলনা প্রতিটা bitwise operator-র চেয়ে শক্ত করে ধরে।

5 নম্বর সারি বসে আছে 3 আর 4 নম্বরের নিচে। প্রতিটা shift সাধারণ যোগ-বিয়োগের চেয়ে ঢিলে করে ধরে।

Precedence-র table মই হিসেবে আঁকা মইয়ে উপরে মানে মানটা আগে দখল করে ( ) [ ] . postfix ++ -- unary ! ~ - (type) sizeof * / % + - << >> + আর --র নিচে < <= > >= == != bitwise ধাপগুলোর উপরে & তারপর ^ তারপর | && তারপর || ? : = += -= *= /= %= , সবচেয়ে শক্ত সবচেয়ে ঢিলা

Associativity: এক সারি কোন দিকে হেলে

Precedence আলাদা সারির ঝগড়া মেটায়। Associativity এক সারির ভিতরের ঝগড়া মেটায়।

3 থেকে 12 নম্বর সারি বাঁ থেকে ডানে দল বাঁধে। 20 - 8 - 3 মানে (20 - 8) - 3, যেটা 9, 15 না।

2, 13 আর 14 নম্বর সারি ডান থেকে বাঁয়ে বাঁধে। তাই p = q = r = 7 কাজ করে: ডানের assignment-টা আগে হয় আর তার মান বাঁ দিকে বইতে থাকে।

#include <stdio.h>

int main(void)
{
    int a = 20;
    int b = 8;
    int c = 3;

    printf("a - b - c   = %d\n", a - b - c);
    printf("a - (b - c) = %d\n", a - (b - c));

    int p = 0;
    int q = 0;
    int r = 0;
    p = q = r = 7;
    printf("p %d q %d r %d\n", p, q, r);
    return 0;
}
a - b - c   = 9
a - (b - c) = 15
p 7 q 7 r 7

বিয়োগ অন্য দিকে হেললে স্কুলের অঙ্কই ভেঙে যেত, তাই বাঁ থেকে ডানেটাই তুমি আগে থেকে আশা করো।

তাই associativity তখনই গুরুত্ব পায় যখন তুমি একই পদের দুইটা operator পরপর লিখেছ, আর অঙ্কের বেশির ভাগ লাইনই তাই।

যে দুইটা ফাঁদ সত্যিই কামড় দেয়

বাস্তব C-র প্রায় প্রতিটা precedence bug দুইটা গড়নের একটা, আর table আগেই দুইটাই বলে দিয়েছে।

ফাঁদ এক: bitwise operator-র পাশে একটা তুলনা। 6 আর 7 নম্বর সারি 8 নম্বরের উপরে, তাই তুলনাটাই জেতে।

#include <stdio.h>

int main(void)
{
    unsigned int x = 4u;

    printf("x & 1u == 0u   %u\n", x & 1u == 0u);
    printf("(x & 1u) == 0u %u\n", (x & 1u) == 0u);
    return 0;
}
x & 1u == 0u   0
(x & 1u) == 0u 1

প্রথম লাইনটা আসলে x & (1u == 0u), মানে x & 0, মানে পৃথিবীর প্রতিটা x-র জন্য 0।

Playground কিছুই বলে না। স্থানীয় gcc -Wall বলে warning: suggest parentheses around comparison in operand of '&', মানে compiler তোমাকে table-র উত্তরটাই জানাচ্ছে।

ফাঁদ দুই: যোগের পাশে একটা shift। 5 নম্বর সারি 4 নম্বরের নিচে, তাই যোগটা আগে হয়।

#include <stdio.h>

int main(void)
{
    printf("1u << 2 + 1   = %u\n", 1u << 2 + 1);
    printf("(1u << 2) + 1 = %u\n", (1u << 2) + 1);
    return 0;
}
1u << 2 + 1   = 8
(1u << 2) + 1 = 5

প্রথমটা আসলে 1u << 3। দুই উত্তর 8 আর 5, আর তার একটাই শুধু 2-র ঘাত, আর সাধারণত এভাবেই এটা ধরা পড়ে।

তাই দুই ফাঁদের কারণ এক: C তার operator-গুলোকে এমন ক্রমে সাজিয়েছে যেটা কথাগুলো পড়ার ধরনের সাথে মেলে না।

পাঁচটা নিয়ম, আর bracket-র একটা নিয়ম

রোজকার code-এ তোমার table লাগে না। পাঁচটা কথায় তার প্রায় পুরোটাই মেটে।

পাঁচটা নিয়ম

1  binary-র আগে unary            -a * b   মানে   (-a) * b
2  + --র আগে * / %               a + b * c   মানে   a + (b * c)
3  তুলনা, তারপর &&, তারপর ||     a < b && c < d   -এ bracket লাগে না
4  assignment সবার শেষে, আর ডানে হেলে     p = q = 7
5  bracket সব সময় জেতে
  • পাঁচটার কোথাও bitwise operator বা shift-র কথা নেই। ওটা ইচ্ছাকৃত: ওগুলোই ফাঁদ।
  • নিয়ম 3-র কারণেই একটা সীমা-পরীক্ষা একটাও bracket ছাড়া পরিষ্কার পড়া যায়।
  • বাকি চারটা যখন তোমাকে দ্বিধায় রেখে দেয়, তখন নিয়ম 5 ব্যবহার করো।

ওই ফাঁক থেকেই track-র style নিয়মটা আসে: এক লাইনে অন্য কিছু থাকলে প্রতিটা bitwise operator আর প্রতিটা shift bracket-এ ঢাকো।

Bracket চালানোর সময় কিছুই খরচ করে না। Compiler হুবহু একই machine code বানায়, তাই ওদের একমাত্র পাঠক মানুষ।

Example 1: সবচেয়ে ছোট যে জোড়াটা মেলে না

এক লাইন, দুই রকম পড়া, দুইটাই ছাপা।

#include <stdio.h>

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

    printf("%d\n", 10 - 4 > n);
    printf("%d\n", 10 - (4 > n));
    return 0;
}
1
9

ওই output-টা 3 input-র জন্য। প্রথমটা একটা হ্যাঁ-না; দ্বিতীয়টা দশের কাছাকাছি একটা সংখ্যা। অঙ্কের পদ তুলনার উপরে, তাই প্রথম লাইনটাই C-র মানে।

Run in Compiler
Example 2: Zara-র flag পরীক্ষা, দুই বানানে

Bug আর সমাধান পাশাপাশি, তুমি যে মানই দাও না কেন।

#include <stdio.h>

int main(void)
{
    unsigned int x = 0;
    scanf("%u", &x);

    printf("no brackets %u\n", x & 1u == 0u);
    printf("bracketed   %u\n", (x & 1u) == 0u);
    return 0;
}
no brackets 0
bracketed   1

ওই output-টা 4 input-র জন্য। 5 দিলে দুই লাইনই 0 ছাপে, আর ঠিক এই জন্যই bug-টা পরীক্ষার ফাঁক গলে বেরিয়ে যায়।

Run in Compiler
Example 3: পদার্থবিজ্ঞানের বই থেকে একটা সূত্র, একবারেই লেখা

সমত্বরণে চলা বস্তুর দূরত্ব। সূত্রটা লেখা হয়েছে শুধু ততটুকু bracket দিয়ে যতটুকু বীজগণিতেরই দরকার।

#include <stdio.h>

int main(void)
{
    double u = 0.0;
    double t = 0.0;
    double a = 0.0;
    scanf("%lf %lf %lf", &u, &t, &a);

    double distance = u * t + 0.5 * a * t * t;

    printf("%.2f\n", distance);
    return 0;
}
80.10

ওই output-টা 12 3 9.8 input-র জন্য। পুরো কাজটা নিয়ম 2-ই করে: প্রতিটা গুণ ওই একটা যোগের আগে বাঁধে, আর বীজগণিত ঠিক তাই বলে।

Run in Compiler

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

  • Kernel-র প্রতিটা flag পরীক্ষা। Linux ভরা (flags & O_APPEND) != 0 ধরনের লাইনে। Bracket-গুলো আছে ঠিক এই lesson-র ফাঁদের কারণেই।
  • Coding standard। MISRA C, যেটা গাড়ি আর চিকিৎসার যন্ত্রে ব্যবহার হয়, মেশানো operator-র চারপাশে bracket বাধ্যতামূলক করে, ঠিক যে কারণে এই track করে।
  • Compiler-র warning। GCC-র -Wparentheses আছে শুধু এই দুই গড়নের জন্যই, আর প্রায় প্রতিটা বাস্তব project-এ -Wall ওটা চালু করে রাখে।
  • অন্যের code পড়া। "এই লাইনটা আসলে কী করে" এই প্রশ্নের উত্তর না চালিয়েই দেওয়ার উপায় হলো table, আর code review-র বেশির ভাগটাই এটাই।

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

১. Mask পরীক্ষার ভিতরে তুলনা লেখা।

unsigned int x = 4u;
printf("%u\n", x & 1u == 0u);

Playground-এ নীরব; স্থানীয় gcc -Wall বলে warning: suggest parentheses around comparison in operand of '&'। এটা প্রতিটা মানের জন্য 0 ছাপে। Mask-টা bracket-এ ঢাকো: (x & 1u) == 0u।

২. ভুল করে shift-র সংখ্যায় যোগ করে ফেলা।

printf("%u\n", 1u << 2 + 1);

Playground-এ নীরব; স্থানীয় gcc -Wall বলে warning: suggest parentheses around '+' inside '<<'। এটা 8 ছাপে, 5 না। Shift-টা bracket-এ ঢাকো।

৩. তিনটা bitwise operator bracket ছাড়া মেশানো।

printf("%d\n", 1 | 2 ^ 3 & 4);

Playground-এ নীরব; gcc -Wall দুইটা suggest parentheses warning দেয়। এটা 3 ছাপে, কারণ & সবচেয়ে শক্ত করে ধরে, তারপর ^, আর সবার শেষে |। চোখ বুলিয়ে কেউ এটা ঠিক পড়ে না, তাই bracket দাও।

৪. ভাবা যে precedence ঠিক করে সময়ে কোনটা আগে চলবে।

int r = f() + g() * h();

কোনো command line-এই বার্তা নেই। Precedence বলে g() আর h()-র গুণফল যোগে যোগ দেবে, আর কোন function আগে ডাকা হবে সে নিয়ে কিছুই বলে না। Lesson 3-র নিয়মটাই এখানে খাটে: ওই ক্রমের উপর ভরসা কোরো না।

মাথা খাটাও

David table-র 15 নম্বর সারি দেখে ওটা চালিয়ে দেখে।

#include <stdio.h>

int main(void)
{
    int a = 0;
    int b = 0;

    a = 1, b = 2;

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

এটা ছাপে a 1 b 2। Table-র সারির নম্বর ব্যবহার করে কেন সেটা বলো, আর পুরো a = 1, b = 2 expression-টার মান কত সেটা বলো। তারপর কঠিন অংশটা: int k = (1, 2); k-তে 2 রাখে, আর স্থানীয় gcc -Wall-র এ নিয়ে কিছু বলার আছে। বার্তাটা আগেই বলে দাও।

Comma সবচেয়ে ঢিলা operator, =-র চেয়েও ঢিলা। তাহলে ও এখানে কোন দুইটা জিনিস আলাদা করছে, আর তার কোনটা পুরো লাইনের মান বানায়?

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

Kenji মিলিয়ে দেখতে চায় ও একটা লাইন C যেভাবে পড়ে সেভাবেই পড়তে পারে কি না।

তিনটা পূর্ণসংখ্যা a, b আর c দেওয়া থাকলে এই পাঁচটা expression-র মান এই ক্রমে ছাপো, এক লাইনে একটা: a + b * c, a - b - c, a * b % c, a < b && b < c, a + b > c।

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

Output. পাঁচ লাইন, উপরের ক্রমে পাঁচটা মান।

Constraints. -1000 <= a, b <= 1000, আর 1 <= c <= 1000।

Sample. Input 2 3 4 দিলে পাঁচ লাইন 14, -5, 2, 1, 1।

#include <stdio.h>

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

    /* Type the five expressions exactly as written. Add no brackets. */

    return 0;
}

five-values নামে গ্রেড হয়। পাঁচটাই আগে কাগজে বের করো; লুকানো test-এ ঋণাত্মক মান আছে, যেখানে তৃতীয়টা মিলিয়ে দেখার মতো।

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

Amara ভাঙা তিনটা লাইন উত্তরাধিকারে পেয়েছে আর প্রতিটাকে তার মন্তব্য যা বলে তাই বোঝাতে হবে।

লাইন তিনটা লেখা আছে x & 1 == 0, 1 << 2 + n আর x ^ 1 != 0। ওদের বলার কথা: x-র bit 0 খালি কি না; চার যোগ n কত; x-র সবচেয়ে নিচের bit উল্টে দিলে ওটা এখনো শূন্য-নয় কি না।

Bracket যোগ করো, আর কিছুই বদলাবে না, যাতে প্রতিটা লাইন তার উদ্দিষ্ট মানেই দাঁড়ায়।

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

Output. তিন লাইন, দেওয়া ক্রমে প্রতিটা expression-র সংশোধিত মান।

Constraints. 0 <= x <= 1000, আর 0 <= n <= 20।

Sample. Input 4 3 দিলে তিন লাইন 1, 7, 1।

#include <stdio.h>

int main(void)
{
    unsigned int x = 0;
    unsigned int n = 0;
    scanf("%u %u", &x, &n);

    /* Each line needs exactly one pair of brackets, around the bitwise part. */

    return 0;
}

এই module-এ গ্রেড হয় না। প্রতিটা লাইন bracket সহ আর bracket ছাড়া চালাও আর দুইটা উত্তরই রাখো; ওই জোড়াটাই lesson।

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

Zara পদার্থবিজ্ঞানের বই থেকে একটা সূত্র টাইপ করছে আর প্রথমবারেই ঠিক চায়।

একটা বস্তুর গতিশক্তি হলো তার ভরের অর্ধেক গুণ বেগের বর্গ, আর ও ওই মানটাকে দেওয়া সময় দিয়ে ভাগ করেও চায়।

Input. এক লাইনে তিনটা দশমিক মান: ভর, বেগ আর সময়।

Output. দুই লাইন: গতিশক্তি, তারপর সেই শক্তি ভাগ সময়, প্রতিটা ঠিক দুই ঘর দশমিক পর্যন্ত।

Constraints. 0 < ভর <= 1000, 0 <= বেগ <= 1000, 0 < সময় <= 1000।

Sample. Input 2 10 4 দিলে 100.00, তারপর 25.00।

#include <stdio.h>

int main(void)
{
    double mass = 0.0;
    double speed = 0.0;
    double time = 0.0;
    scanf("%lf %lf %lf", &mass, &speed, &time);

    /* Rule 2 does the first line. The second needs one pair of brackets. */

    return 0;
}

এই module-এ গ্রেড হয় না। দ্বিতীয় লাইনটা bracket ছাড়া লিখলে শুধু শেষ গুণনীয়কটাই ভাগ হয়, আর উত্তরটা এমন একটা গুণিতকে ভুল হয় যেটা তুমি নাম ধরে বলতে পারবে।

Run in Compiler

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

  • বাড়তি bracket কি program-কে ধীর করে?

    না। ওরা শুধু লেখাতেই আছে; compiler দুইভাবেই একই নির্দেশ বানায়। ওদের খরচ কেবল টাইপ করার।

  • পনেরোটা সারিই কি মুখস্থ করতে হবে?

    না। পাঁচটা নিয়ম আর দুইটা ফাঁদ শেখো, বাকিটা দেখে নাও। পেশাদার C লেখকরাও table-টা দেখেন।

  • C bitwise operator-গুলোকে তুলনার নিচে রাখল কেন?

    ঐতিহাসিক দুর্ঘটনা। && আর || আসার আগেই ওই operator-গুলো ছিল, আর পরে ক্রম বদলালে চালু program-গুলো ভেঙে যেত।

  • a < b < c কি precedence-র সমস্যা?

    না, ওটা associativity-র। দুইটা <-ই 6 নম্বর সারির আর বাঁ থেকে ডানে বাঁধে, আর ওটাই lesson 2-র শিকল-তুলনা।

  • Precedence কি বলে দেয় কোন function আগে ডাকা হবে?

    না। দল বাঁধা আর হিসাবের ক্রম আলাদা দুইটা প্রশ্ন, আর C দ্বিতীয়টা খোলা রাখে। Lesson 3-র নিয়মটাই ওটা সামলায়।

মূল কথা

  • Precedence দল বাঁধা ঠিক করে, সময়ে কী আগে ঘটবে তা না।
  • তুলনা &, ^ আর |-র চেয়ে শক্ত করে ধরে, আর ওটাই ফাঁদ এক।
  • Shift + আর --র চেয়ে ঢিলে করে ধরে, আর ওটাই ফাঁদ দুই।
  • বেশির ভাগ সারি বাঁ থেকে ডানে বাঁধে; unary, ternary আর assignment ডানে হেলে।
  • পাঁচটা নিয়মেই রোজকার অঙ্ক আর যুক্তি table ছাড়াই চলে যায়।
  • এই track এক লাইনে থাকা প্রতিটা shift আর প্রতিটা bitwise operator bracket-এ ঢাকে।

এরপর জানবে, একটা operator-র দুই পাশের type একদমই আলাদা হলে কী ঘটে।

lesson ৫ শেষ

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

পরেরটা: Type conversion: promotion, casting আর চুপচাপ হারিয়ে যাওয়া মান