Learn C Programming

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

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

Assignment-র শর্টকাট, ++ আর --

Freeপড়া

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

  • total = total + x-কে total += x করতে পারবে, আর জানবে এতে আর কী বদলায়।
  • বলতে পারবে n++-র মান কত, আর ++n-র সাথে তার তফাত কোথায়।
  • বোঝাতে পারবে i = i++ কেন undefined, আর তার বদলে নিরাপদ রূপটা লিখতে পারবে।

David-র একটা counter আছে আর ও পরের মানটা চায়। ও লেখে i = i++;, কারণ পড়তে মনে হয় "i হয়ে যাক i, এক বেড়ে"।

Playground একটা কথাও না বলে ওটা compile করে। ওর মেশিনে i একই থাকে। ওর এক বন্ধু একই file চালিয়ে অন্য উত্তর পায়।

কোনো মেশিনই নষ্ট না। ওই লাইনটা C-কে এক ধাপে একই বাক্সে দুইটা কাজ করতে বলে, আর standard বলতেই রাজি না কোনটা আগে জিতবে।

হাতে থাকা variable বদলানোর পাঁচটা শর্টকাট

একটা চলমান যোগফলে যোগ করা এত বেশি দরকার হয় যে C তার জন্য ছোট একটা বানান রেখেছে।

Compound assignment operator-গুলো

total += x      মানে   total = total + x
total -= x      মানে   total = total - x
total *= x      মানে   total = total * x
total /= x      মানে   total = total / x
total %= x      মানে   total = total % x
  • Variable-টার নাম একবার লেখা হয়, দুইবার না। দ্বিতীয় নামে টাইপো হওয়ার bug এই রূপে হতেই পারে না।
  • Operator বসে =-র আগে, কখনো পরে না। =+ আলাদা একটা জিনিস, যেটা বৈধ আর অকেজো।
  • ডান পাশটা পুরোপুরি হিসাব হয়ে যায় আগে, তারপর operator-টা লাগে।
#include <stdio.h>

int main(void)
{
    int total = 100;

    total += 25;
    printf("after +=  %d\n", total);
    total -= 5;
    printf("after -=  %d\n", total);
    total *= 2;
    printf("after *=  %d\n", total);
    total /= 7;
    printf("after /=  %d\n", total);
    total %= 10;
    printf("after %%=  %d\n", total);
    return 0;
}
after +=  125
after -=  120
after *=  240
after /=  34
after %=  4

চতুর্থ লাইনটা খেয়াল করার মতো। 240-কে 7 দিয়ে ভাগ করলে 34.28, আর total একটা int, তাই উত্তর 34।

তাই /= lesson 1-র integer division সঙ্গে নিয়েই আসে। শর্টকাট বানানটা বদলেছে, অঙ্কটা না।

শর্টকাটের ভিতরে লুকিয়ে থাকা রূপান্তর

total *= 1.5 ঠিক total = total * 1.5 না। ওটা total = (int)(total * 1.5), cast-টা তোমার হয়ে লেখা।

শেষ ধাপটা নীরব। এই জন্যই দুইটা রূপ হুবহু এক না, আর এই জন্যই একটা দাম চুপচাপ তার ভগ্নাংশ হারায়।

#include <stdio.h>

int main(void)
{
    int price = 7;

    price *= 1.5;

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

সাত গুণ 1.5 হলো 10.5। গুণটা সত্যিই double-এ হয়েছিল, তারপর 10.5-কে কেটে একটা int-এ আঁটানো হয়েছে।

কোনো command line-এই এটা নিয়ে কিছু বলা হয় না, এমনকি -Wall -Wextra-তেও না। স্থানীয় একটা gcc -Wconversion বলে warning: conversion from 'double' to 'int' may change value।

তাই compound assignment সব সময় বাঁ পাশের variable-র type-এ ফিরে আসে। Lesson 6 ওই রূপান্তরটার আসল নাম দেবে।

++ আর --, আর প্রতিটা রূপ যে মান ফেরত দেয়

n++ আর ++n দুইটাই n-এ 1 যোগ করে। তফাত হলো কাজটা করার সময় expression-টার মান কত।

n++ হলো postfix রূপ। ওটার মান পুরনো n। ++n হলো prefix রূপ, আর ওটার মান নতুনটা।

n++ আর ++n বাক্সটাকে একইভাবে বদলায় আর আলাদা মান ফেরত দেয় বাক্সে একই ফল, expression-এ আলাদা মান n++ n 5 ফেরত দেয় 5 n 6 পুরনো মানটা আগে বাক্স ছেড়েছে ++m m 5 ফেরত দেয় 6 m 6 বাক্সটা আগে বেড়েছে নিজের একটা লাইনে n++ আর ++n হুবহু একই কাজ করে।
#include <stdio.h>

int main(void)
{
    int n = 5;
    printf("n++ gives %d\n", n++);
    printf("n is now  %d\n", n);

    int m = 5;
    printf("++m gives %d\n", ++m);
    printf("m is now  %d\n", m);
    return 0;
}
n++ gives 5
n is now  6
++m gives 6
m is now  6

দুইটা variable-ই শেষে 6-তে দাঁড়ায়। তফাত শুধু printf-র কাছে কোন সংখ্যাটা পৌঁছাল।

তাই কোন রূপটা নেবে সেটা ঠিক তখনই গুরুত্বপূর্ণ যখন তুমি মানটা ব্যবহার করছ। নিজের statement-এ দুইটা রূপ এক।

এক expression-এ এক বাক্সে দুইটা বদল, মানে undefined

দুইটা statement-র মাঝে C কথা দেয় সব কাজ শেষ। এক expression-র ভিতরে ও অনেক কম কথা দেয়।

এই track-র নিয়মটা সোজা কথায়: একটা expression যদি একটা variable বদলায় আর সেটাকেই আবার পড়ে বা বদলায়, তাহলে আচরণটা undefined। unspecified না, compiler-ভেদে আলাদা না: undefined।

i = i++ ঠিক সেটাই করে। ওটা i-তে assign করে আর i-কে বাড়ায়, আর ওই দুই ঘটনার কোনো ক্রম নেই।

Undefined মানে standard program-র উপর কোনো শর্তই দেয় না। এই track-র কোথাও এমন লাইনের কোনো output লেখা নেই, কারণ লেখার মতো সঠিক কোনো output নেই।

int i = 3;
i = i++;

Playground এই নিয়ে কিছু বলে না, কারণ পরীক্ষাটা -Wall-র পিছনে। স্থানীয় gcc -Wall বলে warning: operation on 'i' may be undefined, আর "may" শব্দটা compiler-র সততা, কারণ এটা ধরা সত্যিই কঠিন।

এই track যে নিয়মটা মানে

নিয়মটা ছোট: ++ আর -- নিজের একটা লাইনে লেখো, কখনো বড় কোনো expression-র ভিতরে না।

এতে তুমি কিছুই হারাও না। প্রতিটা ব্যবহারের একটা দুই-লাইনের রূপ আছে, যেটা একই কথা বলে আর সব সময় defined।

#include <stdio.h>

int main(void)
{
    int i = 3;
    int copy = i;
    i++;

    printf("copy %d, i %d\n", copy, i);
    return 0;
}
copy 3, i 4

দুইটা statement, দুইটা নিশ্চিত ধাপ, আর একটাই পড়ার ধরন। Module 6 i++-কে ভাষার রাখা সেই একমাত্র জায়গায় বসাবে, একটা for header-র তৃতীয় ঘরে।

তাই শর্টকাটটা স্বাগত; ওটাকে অন্য একটা expression-র ভিতরে লুকানো না।

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

একটা বাক্স, তিনটা যোগ, শেষে একবার ছাপা।

#include <stdio.h>

int main(void)
{
    int total = 0;

    total += 10;
    total += 20;
    total += 30;

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

Module 6 তিন লাইনের জায়গায় একটা loop বসাবে। তখনো operator-টা বদলাবে না।

Run in Compiler
Example 2: যে counter পুরনো মানটা ফেরত দেয়

একটা টিকিটের লাইন। তোমাকে যে নম্বরটা দেওয়া হয়, counter সরার আগে সেটাই ছিল।

#include <stdio.h>

int main(void)
{
    int next_ticket = 41;

    int mine = next_ticket++;
    int yours = next_ticket++;

    printf("mine  %d\n", mine);
    printf("yours %d\n", yours);
    printf("next  %d\n", next_ticket);
    return 0;
}
mine  41
yours 42
next  43

প্রতিটা লাইন next_ticket-কে একবার ছোঁয়, তাই এখানে কিছুই undefined না। Postfix-ই ঠিক রূপ: তুমি নেওয়ার আগে যে টিকিটটা খালি ছিল, সেটাই তুমি চাও।

Run in Compiler
Example 3: শিক্ষানবিশ আসলে যে যোগফলটা লেখে

চারটা মার্ক ঢোকে, একটা যোগফল আর একটা গণনা বেরোয়, সবই compound assignment দিয়ে।

#include <stdio.h>

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

    int total = 0;
    int count = 0;

    total += a;
    count++;
    total += b;
    count++;
    total += c;
    count++;
    total += d;
    count++;

    printf("total %d over %d marks\n", total, count);
    printf("average %.2f\n", total / (double)count);
    return 0;
}
total 329 over 4 marks
average 82.25

ওই output-টা 75 82 82 90 input-র জন্য। (double)-টা lesson 6-র cast, যেটা শেষ লাইনে lesson 1-র কাজটাই করছে।

Run in Compiler

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

  • তুমি যত loop লিখবে। একটা for header-র তৃতীয় ঘরে প্রায় সব সময় i++ থাকে। Module 6 দুই lesson পরেই আসছে আর ওটা এই operator-র উপরই চলে।
  • Reference counting। CPython প্রতিটা object-এ একটা গোনা রাখে আর নাম আসা-যাওয়ার সাথে সাথে তাতে ++ আর -- চালায়। গোনাটা শূন্যে নামলে জায়গাটা ছেড়ে দেওয়া হয়।
  • Server-র byte counter। Nginx প্রতিটা লেখা টুকরাকে একটা চলমান যোগফলে += দিয়ে যোগ করে। তোমার bandwidth-র বিল ওই যোগফল থেকেই হিসাব হয়।
  • খেলার স্কোর আর health। score += 10 আর health -= damage যেকোনো game loop-র সবচেয়ে চেনা দুই লাইন, আর ওরা এই lesson-র প্রথম অংশ।

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

১. এক expression-এ একটা variable দুইবার বদলানো।

int i = 3;
i = i++;

Playground-এ নীরব; স্থানীয় gcc -Wall বলে warning: operation on 'i' may be undefined। এটা undefined behaviour, তাই এখানে কোনো output লেখা নেই। i++; নিজের লাইনে লেখো।

২. সমান-চিহ্নের পরে operator লেখা।

int total = 100;
total =+ 25;
printf("%d\n", total);

কোনো command line-এই বার্তা নেই, আর ছাপে 25। =+ মানে ধনাত্মক 25 assign করা, যেটা বৈধ আর তোমার উদ্দেশ্য না। Operator বসে =-র আগে।

৩. /= ভগ্নাংশ রাখবে বলে আশা করা।

int total = 7;
total /= 2;
printf("%d\n", total);

কোনো command line-এই বার্তা নেই, আর ছাপে 3। দুই পাশই পূর্ণসংখ্যা, তাই lesson 1-র integer division চলে। ভগ্নাংশ দরকার হলে একটা double variable নাও।

৪. Postfix-র মানকে নতুন মান ভাবা।

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

কোনো command line-এই বার্তা নেই, আর ছাপে 5, 6 না। বাড়াটা ঘটেছে ঠিকই; printf-র কাছে যাওয়া মানটা তার আগেই নেওয়া। নতুন মান চাইলে ++n লেখো।

মাথা খাটাও

Bob পুরনো একটা পরীক্ষার প্রশ্নে এই লাইনটা পায় আর জিজ্ঞেস করে এটা কী ছাপে।

int x = 3;
int y = x++ + ++x;
printf("%d %d\n", x, y);

সৎ উত্তরটা কোনো সংখ্যা না। লাইনটা কোন নিয়ম ভাঙছে বলো, আর কোন দুইটা ঘটনার মাঝে কোনো ক্রম নেই তার নাম বলো। তারপর কঠিন অংশটা: যে মেশিন সত্যিই 8 ছাপে, সেখানেও "prefix আগে চলে, তাই 8" উত্তরটা কেন ভুল সেটা বোঝাও।

ওই এক expression-এ x কতবার বদলাচ্ছে গোনো। তারপর জিজ্ঞেস করো ওই বদলগুলোর ক্রম নিয়ে standard কী কথা দেয়।

অনুশীলন ১সহজ

Maria-র দোকানের খাতা পাঁচটা বিক্রি একটা চলমান যোগফলে যোগ করে আর শেষে যোগফলটা ছাপে।

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

Output. এক লাইনে তাদের যোগফল।

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

Sample. Input 10 20 30 40 50 দিলে 150।

#include <stdio.h>

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

    int total = 0;
    /* Five += lines, one per value. */

    return 0;
}

running-total নামে গ্রেড হয়। total-কে নিজে 0 থেকে শুরু করো; খালি বাক্স Module 2-র সতর্কবার্তা।

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

Zara দেখাতে চায় যে ও না চালিয়েই ++ আর ---র ফল বলে দিতে পারে।

পড়া মান থেকে শুরু করে, ঠিক এই ক্রমের statement-গুলোতে n-র মান আর প্রতিটা expression-র মান লেখো।

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

Output. চার লাইন: n++-র মান, তারপর n, তারপর --n, তারপর আবার n।

Constraints. -1000 <= n <= 1000।

Sample. Input 5 দিলে চার লাইন 5, 6, 5, 5।

#include <stdio.h>

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

    /* Four printf lines. Each one touches n at most once. */

    return 0;
}

এই module-এ গ্রেড হয় না। চারটা উত্তর আগে কাগজে লেখো, তারপর চালাও।

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

Kenji মিলিয়ে দেখছে ও পরপর কয়েকটা compound assignment-র ক্রম ঠিকমতো বোঝে কি না।

পড়া মান থেকে শুরু করে, এই ক্রমে চালাও: *= 3, += 17, /= 4, %= 10। প্রতিটা ধাপের পরের মান ছাপো।

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

Output. চার লাইন, চারটা ধাপের পরে n-র মান।

Constraints. 0 <= n <= 1000000।

Sample. Input 9 দিলে চার লাইন 27, 44, 11, 1।

#include <stdio.h>

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

    /* Four steps, four printf lines, all on the same box. */

    return 0;
}

এই module-এ গ্রেড হয় না। তৃতীয় ধাপেই integer division চুপচাপ শেষ দুইটা উত্তর ঠিক করে দেয়।

Run in Compiler

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

  • ++n কি n++-র চেয়ে দ্রুত?

    C-তে, সাধারণ একটা int-এ, না। মানটা ব্যবহার না করলে compiler একই নির্দেশ বানায়। অভ্যাসটা C++-এ গুরুত্ব পায়, যেখানে দুই রূপ আলাদা function ডাকতে পারে।

  • double-এ কি += লেখা যায়?

    যায়, আর যেকোনো arithmetic type-এ। ব্যতিক্রম শুধু %=, কারণ % নিজেই floating type নেয় না।

  • total =+ 25 আদৌ compile হয় কেন?

    কারণ ওটা +25 মানটার সাধারণ একটা assignment। Unary plus সত্যিকারের একটা operator যেটা কিছুই করে না, তাই C কোনো সমস্যা দেখে না।

  • i = i++ undefined হলে আমার compiler থামে না কেন?

    সাধারণভাবে এটা ধরা অসম্ভব, তাই standard কোনো বার্তা চায় না। GCC সহজ ক্ষেত্রগুলো -Wall-এ ধরে আর কথাটা লেখে "may be undefined"।

  • a[i] = i++-ও কি undefined?

    হ্যাঁ, একই কারণে। Module 9 array আনবে; ওখানে যে নিয়মটা সঙ্গে নিয়ে যাবে সেটা এই lesson-রই।

মূল কথা

  • +=, -=, *=, /= আর %= variable-টার নাম একবার লেখে আর জায়গাতেই বদলায়।
  • Compound assignment ফলটাকে বাঁ পাশের variable-র type-এ ফিরিয়ে আনে, নীরবে।
  • n++ পুরনো মান ফেরত দেয়; ++n নতুনটা ফেরত দেয়।
  • দুইটা রূপই variable-টাকে একইভাবে বদলায়, তাই নিজের লাইনে ওরা এক।
  • এক expression-এ একটা variable দুইবার বদলানো undefined behaviour, উত্তরসহ কোনো ধাঁধা না।
  • এই track ++ আর ---কে নিজের statement হিসেবে লেখে।

এরপর তুমি পূর্ণসংখ্যা ছেড়ে তার ভিতরের bit-গুলো নিয়ে কাজ করবে, একটা একটা করে।

lesson ৩ শেষ

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

পরেরটা: Bitwise operator: এক বিট করে কাজ