Learn C Programming

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

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

Relational আর logical operator, আর short circuit

Freeপড়া

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

  • <, <=, >, >=, == আর != দিয়ে দুইটা মান মেলাতে পারবে।
  • && আর || দিয়ে দুইটা উত্তর জোড়া দিতে পারবে, আর বলতে পারবে কোন পাশটা চলবেই না।
  • Ternary operator দিয়ে এক expression-এই দুইটার একটা মান বেছে নিতে পারবে।

Zara জানতে চায় একটা মার্ক 1 আর 10-র মাঝে পড়ে কি না। ও লেখে 1 < mark < 10, কাগজে যেভাবে সীমাটা লেখা হয়।

ওটা compile হয়। Playground একটা কথাও বলে না। আর 42 মার্কের জন্য, 900 মার্কের জন্য, ও যত সংখ্যা দেয় সবটার জন্যই উত্তর আসে 1।

লাইনটা ভাঙা না। ওটা পুরোপুরি সাধারণ একটা expression, যেটার মানে ও যা পড়েছিল তার চেয়ে আলাদা।

তুলনা একটা int, যার মান 1 বা 0

Module 2-র lesson 4 কথাটা একবার বলেছিল। এই lesson ওটার উপরই দাঁড়ানো, তাই ছয়টা operator সহ আবার বলি।

ছয়টা relational operator

a <  b     a কি b-র চেয়ে ছোট
a <= b     a কি b-র চেয়ে ছোট বা সমান
a >  b     a কি b-র চেয়ে বড়
a >= b     a কি b-র চেয়ে বড় বা সমান
a == b     a কি b-র সমান            সব সময় দুইটা সমান-চিহ্ন
a != b     a কি b-র সমান নয়
  • প্রতিটাই একটা int বানায়, আর সেই int-টা 1 বা 0। আর কিছুই কখনো না।
  • = হলো assignment। == হলো প্রশ্ন। দেখতে একরকম, কাজে আলাদা দুইটা operator।
  • উত্তরটা একটা মান, তাই ওটা জমাও করা যায়, ছাপাও যায়, আর ওটা দিয়ে অঙ্কও করা যায়।
#include <stdio.h>

int main(void)
{
    int mark = 42;

    printf("mark < 50   %d\n", mark < 50);
    printf("mark >= 40  %d\n", mark >= 40);
    printf("mark == 42  %d\n", mark == 42);
    printf("mark != 42  %d\n", mark != 42);
    return 0;
}
mark < 50   1
mark >= 40  1
mark == 42  1
mark != 42  0

এখানে কেউ কোনো সিদ্ধান্ত নিচ্ছে না। চারটা প্রশ্ন করা হলো আর চারটা সংখ্যা ফিরে এলো।

1 < mark < 10 কেন compile হয় আর মিথ্যা বলে

Relational operator বাঁ দিক থেকে দল বাঁধে। তাই C Zara-র লাইনটা পড়ে (1 < mark) < 10 হিসেবে।

বন্ধনীর ভিতরের অংশটা একটা তুলনা, তাই ওটার মান 1 বা 0। তারপর C জিজ্ঞেস করে ওই 1 বা 0 কি 10-র চেয়ে ছোট। সব সময়ই ছোট।

#include <stdio.h>

int main(void)
{
    int mark = 900;

    printf("1 < mark        %d\n", 1 < mark);
    printf("that, < 10      %d\n", (1 < mark) < 10);
    printf("written chained %d\n", 1 < mark < 10);
    return 0;
}
1 < mark        1
that, < 10      1
written chained 1

শেষ দুই লাইন একই expression, দুইবার লেখা। বন্ধনী শুধু পড়ার ধরনটা চোখে আঙুল দিয়ে দেখায়।

Playground-র command line-এ এটা পুরো নীরব, কারণ পরীক্ষাটা -Wall-র পিছনে থাকে। স্থানীয় একটা gcc -Wall দুইটা warning দেয়: comparisons like 'X<=Y<=Z' do not have their mathematical meaning আর comparison of constant '10' with boolean expression is always true।

তাই একটা সীমা মেলাতে দুইটা আলাদা তুলনা লাগে, আর পরের অংশের operator ওদের জোড়া দেয়।

&& আর ||, আর যে truth table একটা program ছেপে দিতে পারে

&& মানে "আর": দুই পাশই সত্য হলে 1। || মানে "অথবা": অন্তত এক পাশ সত্য হলে 1।

এখানে "সত্য" মানে "শূন্য না", Module 2-র নিয়ম। ফলটা আবারও সাধারণ একটা int, যার মান 1 বা 0।

#include <stdio.h>

int main(void)
{
    printf("p q   p&&q  p||q  !p\n");
    printf("0 0    %d     %d    %d\n", 0 && 0, 0 || 0, !0);
    printf("0 1    %d     %d    %d\n", 0 && 1, 0 || 1, !0);
    printf("1 0    %d     %d    %d\n", 1 && 0, 1 || 0, !1);
    printf("1 1    %d     %d    %d\n", 1 && 1, 1 || 1, !1);
    return 0;
}
p q   p&&q  p||q  !p
0 0    0     0    1
0 1    0     1    1
1 0    0     1    0
1 1    1     1    0

Zara-র সীমা-পরীক্ষাটা এখন নিজেই লেখা হয়ে যায়: mark > 1 && mark < 10। দুইটা প্রশ্ন, একটা জোড়া দেওয়ার শব্দ।

তাই truth table মুখস্থ করার জিনিস না। ওটা চার লাইন, যা একটা program দশ সেকেন্ডে তোমাকে ছেপে দেবে।

Short circuit: ডান পাশটা হয়তো চলবেই না

C ওই table-র চেয়ে বেশি কথা দেয়। ও একটা ক্রম কথা দেয়, আর আগেভাগে থেমে যাওয়ার সুযোগ দেয়।

a && b-তে C আগে a হিসাব করে। a যদি 0 হয়, উত্তর তো 0 হয়েই গেছে, তাই b আর হিসাবই হয় না।

a || b-তে C আগে a হিসাব করে। a শূন্য না হলে উত্তর তো 1 হয়েই গেছে, তাই b বাদ পড়ে যায়।

Short circuit, দুইটা গেট হিসেবে আঁকা বাঁ পাশ সব সময় চলে; ডান পাশকে নিজের পালা অর্জন করতে হয় a && b a হিসাব করো a শূন্য উত্তর 0, b বাদ a শূন্য না b হিসাব করো, উত্তর b a || b a হিসাব করো শূন্য না উত্তর 1 a শূন্য b হিসাব করো, উত্তর b এটা ভাষার নিয়ম, compiler চাইলে বাদ দিতে পারে এমন কোনো optimisation না।

তুমি নিজের চোখে দেখতে পারো। printf কতগুলো অক্ষর ছেপেছে সেই সংখ্যাটা ফেরত দেয়, তাই একটা printf ডান পাশের মান হিসেবে চলে।

#include <stdio.h>

int main(void)
{
    int stock = 0;

    printf("start\n");
    int a = stock && printf("  the right side of && ran\n");
    int b = 1 || printf("  the right side of || ran\n");
    printf("a %d, b %d\n", a, b);
    return 0;
}
start
a 0, b 1

কোনো বার্তাই এলো না। stock ছিল 0, তাই && থেমে গেল; 1 তো আগেই সত্য, তাই || থেমে গেল।

তাই বিপজ্জনক কাজ আটকানোর উপায় হলো short circuit: পরীক্ষাটা &&-র বাঁ পাশে রাখো, তাহলে ডান পাশের বিপজ্জনক অংশটা কেবল পরীক্ষা পাশ করলেই চলতে পারে।

! সংখ্যাকে উত্তর বানায়, আর !! উত্তরটা সমান করে

! মানে "না"। শূন্য না এমন যেকোনো মানের জন্য ও দেয় 0, আর শূন্যের জন্য দেয় 1।

দুইবার লাগালে এমন একটা মান পাবে যেটা 1 বা 0, আর কিছুই না। !!7 হলো 1, !!0 হলো 0।

#include <stdio.h>

int main(void)
{
    int credits = 7;

    printf("credits      %d\n", credits);
    printf("!credits     %d\n", !credits);
    printf("!!credits    %d\n", !!credits);
    return 0;
}
credits      7
!credits     0
!!credits    1

!! জোড়াটা "এটাকে ঠিক 1 বা 0-তে নামাও" বোঝানোর চেনা একটা idiom। ওটা আলাদা কোনো operator না; ওটা !-কে দুইবার লেখা।

তাই "credit আছে কি না" আর "কত credit আছে" আলাদা দুইটা মান হয়ে যায়, আর তার একটাকেই কেবল 1-র সাথে মেলানো নিরাপদ।

Ternary: এক expression, দুইটা সম্ভাব্য মান

এতক্ষণের প্রতিটা operator দুইটা মান নিত। এটা নেয় তিনটা, আর C-তে এমন operator আর একটাও নেই।

Conditional operator

condition ? value_if_true : value_if_false
  • condition আগে হিসাব হয়। শূন্য মানে মিথ্যা, বাকি সব মানে সত্য।
  • দুইটা মানের ঠিক একটাই হিসাব হয়, সেই short circuit-র কথাই।
  • পুরোটা নিজেই একটা মান, তাই ওটা =-র ডান পাশে বা printf-র ভিতরে বসতে পারে।
#include <stdio.h>

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

    int larger = (a > b) ? a : b;

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

ওই output-টা 91 47 input-র জন্য। Module 5 একই জিনিস if দিয়ে চার লাইনে লিখবে; এখানে ওটা এক লাইন।

Track-র পরামর্শ এক লাইনের: দুই দিকের ফলই যখন একই ধরনের মান, তখন ternary নাও, আর যেই কোনো এক দিক মান বানানোর বদলে কাজ করতে শুরু করে, তখনই if-এ যাও।

Example 1: উত্তর জমা করার সবচেয়ে ছোট program

একটা তুলনা একটা বাক্সে ঢোকে, আর বাক্সটা %d দিয়ে ছাপা হয়।

#include <stdio.h>

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

    int adult = age >= 18;

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

ওই output-টা 18 input-র জন্য। সীমানাটা operator-এই বসে আছে: >= 18-কে ধরে, > ধরত না।

Run in Compiler
Example 2: Zara যে সীমা-পরীক্ষাটা চেয়েছিল

দুইটা তুলনা, && দিয়ে জোড়া, এক সংখ্যা হিসেবে ছাপা।

#include <stdio.h>

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

    int in_range = mark >= 1 && mark <= 10;

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

ওই output-টা 42 input-র জন্য। 1, 10 আর 11 দিয়ে চালাও: দুই প্রান্তই চালিয়ে দেখার মতো, আর তুমি কোন operator নিয়েছ সেটাই ওদের ঠিক করে।

Run in Compiler
Example 3: যে ভাগ শূন্য দিয়ে ভাগ করতেই পারে না

Short circuit ইচ্ছে করে কাজে লাগানো। ভাজকটা বাঁ পাশে পরীক্ষা হয়, তাই ডান পাশের ভাগটা তখনই অচল থাকে যখন ওটা undefined হতো।

#include <stdio.h>

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

    int big = (d != 0) && (n / d > 10);

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

ওই output-টা 500 0 input-র জন্য। ভাগটা ঘটেইনি, তাই ভুল হওয়ার কিছু ছিল না। দুই পাশ উল্টে দাও, আর ওই একই input-এ program-টা undefined হয়ে যায়।

Run in Compiler

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

  • তুমি যত form পূরণ করেছ। একটা signup form দৈর্ঘ্য আর অক্ষরের সীমা একই && শিকল দিয়ে মেলায়, server-এ, মানটা কোনো database-এ পৌঁছানোর আগে।
  • Leap year। নিয়মটা তিনটা তুলনা, && আর || দিয়ে জোড়া, আর একই expression Linux-র date utility-তে, একটা spreadsheet-এ আর নিচের অনুশীলন ২-তে।
  • Feature flag। একটা release পরীক্ষা পড়ে flag_on && user_is_beta। Short circuit-র কারণে দ্বিতীয়, ধীর খোঁজটা কেবল সেই ছোট দলটার জন্যই হয় যাদের flag আদৌ চালু।
  • Pointer পাহারা দেওয়া। আসল C ভরা p != NULL && p->ready দিয়ে। তিরচিহ্নটা Module 11 বোঝাবে; গড়নটা হুবহু Example 3, আর এটা যে crash আটকায় সেটা আরও খারাপ।

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

১. অঙ্কের বই যেভাবে সীমা লেখে সেভাবে লেখা।

int ok = 1 < mark < 10;
printf("%d\n", ok);

Playground-এ নীরব, কারণ এর দুইটা পরীক্ষাই -Wall-র পিছনে; স্থানীয় gcc -Wall বলে comparisons like 'X<=Y<=Z' do not have their mathematical meaning। এটা প্রতিটা মানের জন্য 1 ছাপে। mark > 1 && mark < 10 লেখো।

২. যেখানে দুইটা সমান-চিহ্ন দরকার ছিল সেখানে একটা।

int matched = code = 5;
printf("matched %d\n", matched);

কোনো command line-এই বার্তা নেই। ভিতরের = একটা assignment যার মান 5, তাই matched সব সময় 5 আর code-র পুরনো মানটা মুছে গেছে। দুইটা সমান-চিহ্ন জিজ্ঞেস করে; একটা সমান-চিহ্ন বলে দেয়।

৩. &&-র জায়গায় & লেখা।

int big = (d != 0) & (n / d > 10);

কোনো command line-এই বার্তা নেই, আর বেশির ভাগ input-এ উত্তরটা ঠিকও আসে। কিন্তু &-তে short circuit নেই, তাই দুই পাশই চলে আর d শূন্য থাকা অবস্থায় ভাগটা হয়, যেটা undefined behaviour। এক অক্ষরই ঠিক করে দেয় program-টা নিরাপদ কি না।

৪. দুইটা double-কে == দিয়ে মেলানো।

printf("%d\n", 0.1 + 0.2 == 0.3);

কোনো command line-এই বার্তা নেই, আর ছাপে 0। Module 2-র lesson 3 দেখিয়েছে ওই দুই মান শেষ কয়েকটা bit-এ কেন আলাদা। তার বদলে পার্থক্যটাকে একটা ছোট সহনসীমার সাথে মেলাও।

মাথা খাটাও

Bob গুনছে দুইটা উত্তরের কয়টা 5 ছিল, আর একটাও if ছাড়াই লিখছে।

#include <stdio.h>

int main(void)
{
    int x = 5;
    int y = 3;

    int t = (x == 5) + (y == 5);

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

এটা ছাপে 1। x আর y-র কোন মানে এটা 0 ছাপবে আর কোন মানে 2, সেটা বলো। তারপর কঠিন অংশটা: +-টাকে || করে দাও আর বলো তিনটা সম্ভাব্য উত্তর কী হয়ে যায়, আর তার একটা কেন হারিয়ে যায়।

প্রতিটা বন্ধনীর মান ঠিক 1 বা 0, তাই যোগফলটা একটা গণনা। || গোনে না; ওটা ওই দুই বন্ধনী নিয়ে অন্য একটা প্রশ্নের উত্তর দেয়।

অনুশীলন ১সহজ

Amara এমন একটা মার্ক যাচাই করছে যেটা দেওয়া দুই সীমার মধ্যে থাকতে হবে, দুই সীমাসহ।

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

Output. এক লাইনে 1, যদি x low আর high-র মধ্যে থাকে, দুই সীমাসহ, নয়তো 0।

Constraints. -1000 <= low <= high <= 1000, আর -2000 <= x <= 2000।

Sample. Input 1 10 10 দিলে 1। Input 1 10 11 দিলে 0।

#include <stdio.h>

int main(void)
{
    int low = 0;
    int high = 0;
    int x = 0;
    scanf("%d %d %d", &low, &high, &x);

    /* Two comparisons and one &&. Both ends are included. */

    return 0;
}

এই module-এ গ্রেড হয় না। বিশ্বাস করার আগে দুই সীমাতেই চালিয়ে দেখো।

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

David সেই calendar পরীক্ষাটা লিখছে যেটা শেষ পর্যন্ত প্রতিটা project-এ লাগে।

একটা বছর leap year, যদি সেটা 4 দিয়ে ভাগ যায় আর 100 দিয়ে না যায়, অথবা 400 দিয়ে ভাগ যায়।

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

Output. এক লাইনে 1, যদি y leap year হয়, নয়তো 0।

Constraints. 1 <= y <= 999999।

Sample. Input 2024 দিলে 1। Input 1900 দিলে 0। Input 2000 দিলে 1।

#include <stdio.h>

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

    /* Three remainders, two comparisons joined by &&, then one ||. */

    return 0;
}

leap-year নামে গ্রেড হয়। পুরো নিয়মটা এক expression-এ ধরে; 1900 আর 2000 এই দুইটা বছরই বলে দেয় তুমি ঠিক লিখেছ কি না।

Run in Compiler
অনুশীলন ৩সহজ

Kenji দুইটা স্কোরের বড়টা চায়, একটাও if না লিখে।

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

Output. এক লাইনে দুইটার বড়টা। সমান হলে সেই মানটাই ছাপো।

Constraints. প্রতিটা মান -1000000 আর 1000000-র মধ্যে।

Sample. Input 91 47 দিলে 91।

#include <stdio.h>

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

    /* One ternary. Equal values make both branches correct. */

    return 0;
}

এই module-এ গ্রেড হয় না। তারপর ছোটটাও লেখো, ঠিক এক অক্ষর বদলে।

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

Zara একটা expression-র পুরো truth table দেখতে চায়, program নিজেই ছাপবে।

Input. এক লাইনে দুইটা পূর্ণসংখ্যা p আর q, প্রতিটা 0 বা 1।

Output. চার লাইন: p && q, p || q, !p আর p != q-র মান, এক লাইনে একটা, এই ক্রমে।

Constraints. p আর q প্রতিটা 0 বা 1।

Sample. Input 1 0 দিলে চার লাইন 0, 1, 0, 1।

#include <stdio.h>

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

    /* Four printf lines, each one expression. No loop, no condition. */

    return 0;
}

এই module-এ গ্রেড হয় না। চারটা input জোড়াতেই চালাও, তাহলেই এই lesson-র তৃতীয় অংশের table-টা তুমি নিজে লিখে ফেললে।

Run in Compiler

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

  • উত্তরটা কি সব সময় ঠিক 1, না শূন্য-নয় যেকোনো মান?

    ঠিক 1। C কথা দেয় যে <, ==, &&, || আর ! সবাই 1 বা 0 বানায়। অন্যের লেখা function সত্যের জন্য শূন্য-নয় যেকোনো মান ফেরত দিতে পারে, আর সেই জন্যই !!-র দরকার।

  • & আর &&, দুইটাই কেন আছে?

    দুইটার কাজ আলাদা। && দুইটা উত্তর জোড়া দেয় আর আগেভাগে থামে; & দুইটা সংখ্যার bit নিয়ে কাজ করে আর সব সময় দুই পাশই চালায়। Lesson 4 পুরোটাই দ্বিতীয়টা নিয়ে।

  • একটা char-কে কি সংখ্যার সাথে মেলানো যায়?

    যায়, কারণ char নিজেই একটা সংখ্যা। ch >= '0' && ch <= '9' হলো অঙ্ক চেনার চেনা পরীক্ষা, আর ওটা আসলে 48 আর 57 মেলাচ্ছে।

  • Ternary-র দুই পাশে আলাদা type দিলে চলে?

    Compile হয়, আর দুইটা মানকে আগে একটা সাধারণ type-এ বদলে নেওয়া হয়। Lesson 6 রূপান্তরের নিয়ম বলার আগ পর্যন্ত দুই পাশ একই type রাখো।

  • Short circuit কি শুধু একটা optimisation?

    না। ওটা standard-এ লেখা, তাই প্রতিটা C compiler-কে ওটা করতেই হয়। এই জন্যই &&-র বাঁ পাশে পাহারা বসানো নিরাপদ।

মূল কথা

  • প্রতিটা তুলনা এমন একটা int বানায় যেটা ঠিক 1 বা 0।
  • 1 < x < 10 compile হয়, সব সময় 1, আর Playground-এ নীরব।
  • && আর || উত্তর জোড়া দেয়; শূন্য মিথ্যা আর বাকি সব সত্য।
  • Short circuit ভাষারই নিয়ম: ডান পাশের মান হয়তো কখনোই হিসাব হবে না।
  • ! উত্তর উল্টে দেয় আর !! যেকোনো মানকে 1 বা 0-তে নামায়।
  • Ternary নিজেই একটা মান, আর দুই পাশই মান হলে ওটাই সবচেয়ে ভালো।

এরপর তুমি total = total + x লেখা ছেড়ে ছোট রূপগুলো শিখবে, যার একটার ভিতরে undefined behaviour লুকিয়ে আছে।

lesson ২ শেষ

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

পরেরটা: Assignment-র শর্টকাট, ++ আর --