Learn C Programming

lesson ৪ / ৭ · Variable আর data type

Module ২ · Variable আর data type

Char, bool, আর যে type-গুলো আসলে সংখ্যা

Freeপড়া

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

  • একটা char-কে অক্ষর হিসেবেও পড়তে আর ছাপতে পারবে, সংখ্যা হিসেবেও।
  • <stdbool.h> থেকে একটা bool declare করতে পারবে, আর কোনটা সত্য সেটা বলতে পারবে।
  • একটা তুলনাকে মান হিসেবে ব্যবহার করতে পারবে, কারণ তার মান 1 বা 0 আর ওটা যোগ করা যায়।

Bob নিজের grade-টা %c-র বদলে %d দিয়ে ছাপে আর পায় 65। ও Playground আবার চালু করে, program আবার লেখে, আর আবার 65 পায়।

কিছুই ভাঙেনি। ও একই বাক্সকে অন্য জানালা দিয়ে দেখছে, আর বাক্সটায় চিরকালই 65 ছিল। Module 1-র lesson 4 বলেছিল একটা char ছোট একটা integer; এই lesson সেটাকে গুরুত্ব দিয়ে নেয়।

char মানে এক byte-র integer

char একটা integer type। ওটা এক byte চওড়া, ছোট একটা পূর্ণসংখ্যা ধরে, আর তুমি সংখ্যাটা দেখবে না চেহারাটা দেখবে সেটা printf ঠিক করে।

#include <stdio.h>

int main(void)
{
    char grade = 'A';
    char digit = '7';
    char space = ' ';

    printf("grade: %c is %d\n", grade, grade);
    printf("digit: %c is %d\n", digit, digit);
    printf("space: is %d\n", space);
    return 0;
}
grade: A is 65
digit: 7 is 55
space: is 32

একক উদ্ধৃতি বানায় একটা character constant, যেটা একটা অক্ষর আর একটা সংখ্যা। জোড়া উদ্ধৃতি বানায় একটা string, যেটা পুরো আলাদা জিনিস আর Module 10-র বিষয়।

এটা সংখ্যা বলেই এটা অঙ্ক করে। grade + 1 হলো 66, আর %c দিয়ে ছাপলে ওটা B।

তাই char C-র সবচেয়ে ছোট integer type, আর এতে সাধারণত একটা অক্ষর থাকে সেটা একটা অভ্যাস, নিয়ম না।

সাধারণ char কি signed না unsigned? Standard বলে না

Character-র type একটা না, তিনটা: char, signed char আর unsigned char। প্রথমটা বাকি দুইটার কোনোটার সমান না।

সাধারণ char ঋণাত্মক মান ধরতে পারে কি না, সেটা মেশিনের উপর ছেড়ে দেওয়া। Playground-এ, আর প্রতিটা Intel আর AMD desktop-এ, ওটা signed আর চলে -128 থেকে 127। একটা Raspberry Pi-তে ওটা unsigned আর চলে 0 থেকে 255।

#include <stdio.h>

int main(void)
{
    char plain = 200;
    signed char definitely_signed = -56;
    unsigned char definitely_unsigned = 200;

    printf("plain char      : %d\n", plain);
    printf("signed char     : %d\n", definitely_signed);
    printf("unsigned char   : %d\n", definitely_unsigned);
    return 0;
}
plain char      : -56
signed char     : -56
unsigned char   : 200

প্রথম দুইটা লাইন এখানে মেলে, আর একটা Arm board-এ মিলত না, যেখানে প্রথম লাইনটা হতো 200।

Compiler-র -funsigned-char flag-ও এটা বদলে দেয়, আর কিছু project ইচ্ছে করেই সেটা দেয়। Playground ওটা পাঠায় না, তাই তুমি signed পড়াটাই পাও।

তাই নিয়মটা হলো: অক্ষরের জন্য সাধারণ char, কাঁচা byte-র জন্য unsigned char, আর 127-র উপরের মানের জন্য কখনো সাধারণ char না।

bool, true আর false, আর যে header থেকে ওরা আসে

পুরনো C-তে সত্য-মিথ্যার কোনো type ছিল না। শূন্য মানে মিথ্যা আর বাকি সব মানে সত্য, আর লোকে int লিখে মনে রাখত।

C99 একটা সত্যিকারের type যোগ করে, নাম _Bool। নামটা ইচ্ছে করেই কুৎসিত, যাতে যেসব program bool-কে নিজেদের নাম হিসেবে ব্যবহার করত সেগুলো build হতেই থাকে। তারপর <stdbool.h> তোমাকে বন্ধুত্বপূর্ণ নামগুলো দেয়।

#include <stdio.h>
#include <stdbool.h>

int main(void)
{
    bool is_enrolled = true;
    bool has_paid = false;

    printf("enrolled : %d\n", is_enrolled);
    printf("paid     : %d\n", has_paid);
    printf("a bool takes %d byte\n", (int)sizeof(bool));
    return 0;
}
enrolled : 1
paid     : 0
a bool takes 1 byte

এক bit তথ্যের জন্য এক byte। মেশিন এর চেয়ে ছোট কিছুর ঠিকানা দিতে পারে না, তাই দামটা এটাই।

bool ছাপার জন্য কোনো %b নেই। ওটা একটা integer type, তাই %d-ই ঠিক, আর ওটা 1 বা 0 ছাপে।

সবচেয়ে নতুন standard C23-তে bool, true আর false keyword হয়ে গেছে আর header-টা আর লাগে না। এই track C17, তাই header থাকছে।

শূন্য ছাড়া সবকিছুই সত্য

একটা bool কেবল 1 বা 0 ধরে। এতে অন্য কিছু assign করলে সেটা ওই দুইটার একটায় গিয়ে দাঁড়ায়।

#include <stdio.h>
#include <stdbool.h>

int main(void)
{
    bool from_seven = 7;
    bool from_minus = -7;
    bool from_zero = 0;

    printf("7  becomes %d\n", from_seven);
    printf("-7 becomes %d\n", from_minus);
    printf("0  becomes %d\n", from_zero);
    return 0;
}
7  becomes 1
-7 becomes 1
0  becomes 0

মাঝের লাইনটা দুইবার পড়ো। ঋণাত্মক সংখ্যা সত্য। কেবল শূন্যই মিথ্যা, আর গোটা ভাষায় মিথ্যা মান ঠিক একটাই।

তাই "এটা কি সত্য" আর "এটা কি ধনাত্মক" দুইটা আলাদা প্রশ্ন, আর এই দুইটা গুলিয়ে ফেলাটা এমন একটা bug যেটা Module 5 তোমাকে লিখতে দেবে।

তুলনা একটা expression, আর তার মান 1 বা 0

এই অংশটা বেশির ভাগ beginner-কে কেউ বলে না, আর এটা C-র অর্ধেকটা পড়া সহজ করে দেয়।

a < b এমন বিশেষ কিছু না যেটা কেবল একটা if-র ভেতরে বাঁচে। ওটা মানসহ একটা expression, type হলো int, আর তুলনাটা মিললে মান 1, না মিললে 0।

#include <stdio.h>

int main(void)
{
    char ch = '5';

    printf("is it at least '0' ? %d\n", ch >= '0');
    printf("is it at most  '9' ? %d\n", ch <= '9');
    printf("both of those       ? %d\n", (ch >= '0') * (ch <= '9'));
    return 0;
}
is it at least '0' ? 1
is it at most  '9' ? 1
both of those       ? 1

তৃতীয় লাইনটা এমন দুইটা উত্তর গুণ করে, যাদের প্রতিটা 1 বা 0। গুণফল 1 হয় কেবল যখন দুইটাই 1, আর সেটাই ঠিক "আর"।

C-তে এর জন্য নিজস্ব operator আছে, লেখা হয় &&, আর Module 4 বাকি operator-র সঙ্গে ওটা বোঝাবে। গুণ আজই কাজ করে আর নিচে কী আছে সেটা দেখিয়ে দেয়।

তাই একটা তুলনা একটা সংখ্যা, আর সংখ্যা যোগ করা যায়, গুণ করা যায়, ছাপা যায়। নিচের অনুশীলনে তুমি সেটাই করবে।

enum নিয়ে এক অনুচ্ছেদ

সংখ্যা হয়েও সংখ্যা নয় এমন চতুর্থ একটা type আছে: enum। ও একগুচ্ছ পূর্ণসংখ্যাকে পড়ার মতো নাম দেয়, 0 থেকে শুরু করে উপরের দিকে।

#include <stdio.h>

int main(void)
{
    enum weekday { sunday, monday, tuesday, wednesday };
    enum weekday today = wednesday;

    printf("today = %d\n", today);
    return 0;
}
today = 3

wednesday চতুর্থ নাম, তাই ওটা 3। Module 12 এটাকে struct-র পাশে ঠিকভাবে বানাবে। এটা এখানে আছে যাতে "সংখ্যা হয়েও সংখ্যা নয় এমন type" কথাটা পুরো হয়।

Example 1: এক byte, দুই জানালা

একটা character যে সংখ্যা, তার সবচেয়ে ছোট প্রমাণ: একই variable দুইবার ছাপা।

#include <stdio.h>

int main(void)
{
    char ch = 'P';

    printf("as a character: %c\n", ch);
    printf("as a number   : %d\n", ch);
    printf("the next one  : %c\n", ch + 1);
    return 0;
}
as a character: P
as a number   : 80
the next one  : Q

একটা বাক্স, একটা মান, তিনটা লাইন। কেবল specifier-টাই বদলেছে।

Run in Compiler
Example 2: একটা তুলনা থেকে আসা bool

একটা নম্বর পড়ে সেটা পাশ কি না রাখা। কোথাও if নেই, কারণ লাগেই না।

#include <stdio.h>
#include <stdbool.h>

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

    bool has_passed = marks >= 40;

    printf("marks  : %d\n", marks);
    printf("passed : %d\n", has_passed);
    return 0;
}
marks  : 39
passed : 0

ওই output 39 input-র জন্য। তুলনাটা 0 বানাল, bool 0 রাখল, আর %d সেটা ছাপল। প্রতিটা ধাপে সাধারণ একটা মান সাধারণ একটা বাক্সে যাচ্ছে।

Run in Compiler
Example 3: তুলনা দিয়ে গোনা, beginner আসলে যেটা লেখে

তিনটা character আসে। ওদের কয়টা অঙ্ক? তিনটা তুলনা যোগ করলেই এক লাইনে উত্তর।

#include <stdio.h>

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

    int count = (a >= '0') * (a <= '9')
              + (b >= '0') * (b <= '9')
              + (c >= '0') * (c <= '9');

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

ওই output 4b9 input-র জন্য। তিনটা লাইনের প্রতিটা 1 বা 0, আর ওরা যোগ হয়ে গোনাটা বানায়।

Module 6 এটাই একটা loop দিয়ে করবে, যত খুশি character-র জন্য। ততদিন তিন লাইন তিন লাইনই।

Run in Compiler

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

  • Keyboard। একটা key চাপলে operating system-এ আসে একটা scan code, ছোট একটা unsigned integer। Layout-র table ওই সংখ্যাটাকে সেই character বানায়, যেটা শেষমেশ তোমার program পড়ে।
  • JSON parser। প্রতিটা ভাষার প্রতিটা parser লেখাটা এক char করে হাঁটে আর '{', '"' আর ':'-র সঙ্গে মেলায়। ওই তুলনাগুলোই পুরো parser।
  • SQLite-র flag। SQLite-র connection structure-এ কয়েক ডজন এক bit-র setting থাকে। ওগুলো আলাদা bool ঘর না হয়ে একটা integer-র ভেতরে bit হিসেবে থাকে, যাতে প্রতিটা খোলা database-এ memory বাঁচে।
  • খেলার is_alive। একটা game engine-র entity কয়েকটা bool ঘর বয়ে বেড়ায়: বেঁচে আছে, দেখা যাচ্ছে, মাটিতে আছে। প্রতিটা এক byte, আর main loop সেকেন্ডে ষাট বার ওগুলো পড়ে।

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

১. একটা মাত্র character-র চারপাশে জোড়া উদ্ধৃতি।

char grade = "A";
printf("%d\n", grade);

Playground-র GCC 12-তে এটা একটা warning: initialization of 'char' from 'char *' makes integer from pointer without a cast, আর program build হয়ে একটা অর্থহীন সংখ্যা ছাপে। GCC 14 এটাকে error বানায়। 'A' একটা character; "A" একটা string, যেটা কাকতালীয়ভাবে এক অক্ষরের।

২. এক জোড়া একক উদ্ধৃতির ভেতরে দুইটা character।

char grade = 'ab';
printf("%d\n", grade);

GCC 12 কোনো flag ছাড়াই দুইটা warning দেয়: warning: multi-character character constant আর warning: overflow in conversion from 'int' to 'char' changes value from '24930' to '98'। একটা character constant একটাই character ধরে। Program build হয় আর b-টা রাখে।

৩. Header ছাড়া bool ব্যবহার করা।

int main(void)
{
    bool ok = 1;
    return ok;
}

GCC 12 বলে error: unknown type name 'bool' আর সহায়কভাবে যোগ করে note: 'bool' is defined in header '<stdbool.h>'। Include-টা দাও। C23-তে একই program header ছাড়াই compile হয়, আর ওটা এই track-র standard না।

৪. সাধারণ char-এ 127-র উপরের মান রাখা।

char byte_value = 200;
printf("%d\n", byte_value);

দুইটা command line-র কোনোটাতেই message নেই, আর ছেপেছে -56। যে মেশিনে সাধারণ char unsigned, সেখানে একই লাইন 200 ছাপে। মানটা অক্ষর না হয়ে যদি কাঁচা byte হয়, unsigned char লেখো আর প্রশ্নটাই উবে যায়।

মাথা খাটাও

Bob দেখতে চায় একটা গোনার ঘর শূন্যে পৌঁছেছে কি না। একটা if তার brace-র ভেতরের code চালায় যখন bracket-র ভেতরের মান শূন্য না, আর Module 5 ওটা ঠিকভাবে শেখাবে।

#include <stdio.h>

int main(void)
{
    int x = 7;

    if (x = 0) {
        printf("x was zero\n");
    }

    printf("x is now %d\n", x);
    return 0;
}

if-টা কিছুই ছাপে না, আর শেষ লাইন বলে x is now 0। Bracket দুইটা আসলে কী করল, আর ভেতরের মানটা কত ছিল, বলো। তারপর কঠিন অংশ: Playground-র GCC 12 এই লাইনটা নিয়ে কী বলে, আর নিজের মেশিনে gcc -Wall কেন অন্য কিছু বলে?

Lesson 1-র = নিয়ে নিয়মটা জোরে পড়ো। তারপর সমান চিহ্ন গোনো। Assignment-ও একটা expression, আর তারও একটা মান আছে।

অনুশীলন ১সহজ

Maria একটা form-র ঘর এক character করে মেলাচ্ছে আর প্রতিটার জন্য সাদামাটা হ্যাঁ বা না চায়।

Input. এক লাইনে ঠিক একটা character।

Output. এক লাইনে: character-টা অঙ্ক হলে 1, নাহলে 0।

Constraints. Character-টা printable ASCII, code 32 থেকে 126।

Sample. Input 7 দিলে 1। Input k দিলে 0।

#include <stdio.h>

int main(void)
{
    char ch = 0;
    scanf("%c", &ch);

    /* Two comparisons multiplied together, printed with %d. */

    return 0;
}

এই module-এ judge করা হয় না। <ctype.h> ব্যবহার করো না; পুরো কথাটাই হলো তুমি এটার উত্তর এখনই অঙ্ক দিয়ে দিতে পারো।

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

তিনটা character আসে, মাঝে কিছু নেই। Example 3-র মতো ওদের কয়টা অঙ্ক গোনো।

Input. এক লাইনে ঠিক তিনটা printable character, মাঝে কোনো space নেই।

Output. এক লাইনে গোনাটা, যেটা 0, 1, 2 বা 3।

Constraints. প্রতিটা character printable ASCII, code 33 থেকে 126।

Sample. Input 4b9 দিলে 2।

#include <stdio.h>

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

    /* Three of Exercise 1's answers, added. */

    return 0;
}

এই module-এ judge করা হয় না। scanf-র লাইনটা যেমন আছে তেমনই রাখো; %c-র আগে একটা space দিলে যে character পড়ার কথা সেটাই বাদ পড়ে যাবে।

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

Kenji-র টেবিল-বাতিটা শুরুতে বন্ধ। কেউ switch-টা n বার টিপল। শেষে বাতিটা জ্বলছে কি?

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

Output. এক লাইনে: বাতি জ্বললে 1, নাহলে 0।

Constraints. 0 <= n <= 1000000000।

Sample. Input 0 দিলে 0। Input 7 দিলে 1। Input 1000000000 দিলে 0।

#include <stdio.h>

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

    /* No loop. What is left over when n is divided by 2? */

    return 0;
}

lamp-toggle নামে judge করা হয়। ভাগশেষের operator % আসে Module 4-এ; lesson 2-র অনুশীলন ৪ দেখিয়েছে /, * আর - দিয়ে ভাগশেষ কীভাবে পাওয়া যায়।

Run in Compiler

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

  • 'A' 65 কেন, 1 না কেন?

    কারণ ASCII তাই বলে, আর পুরো table-টা Module 1-র lesson 4-এ আছে। অক্ষরগুলো কাকতালীয়ভাবে ক্রমে বসানো, আর সেজন্যই ch - 'a' একটা অবস্থান দেয়।

  • হ্যাঁ বা না মানের জন্য bool নেব না int?

    নতুন code-এ bool। ওটা তোমার মনের কথাটা বলে, শূন্য ছাড়া সব মানকে 1-এ নামিয়ে আনে, আর একই এক byte খরচ করে।

  • একটা char কি একটা বাংলা অক্ষর ধরতে পারে?

    না। UTF-8-এ একটা বাংলা অক্ষর তিন byte নেয়, তাই ওর তিনটা char বাক্স লাগে। Module 1-র lesson 4 আরও বলে, আর Module 10 লেখা নিয়ে ঠিকভাবে কাজ করে।

  • sizeof(bool) কি সবসময় 1?

    মূলধারার প্রতিটা compiler-এ হ্যাঁ। Standard কেবল বলে একটা _Bool 0 আর 1 ধরার মতো বড়, তাই সংখ্যাটা ও কথা দেয় না।

  • দুইটা তুলনা গুণ করা কি আসল C, না কৌশল?

    এটা আসল, বৈধ, আর মাঝেমধ্যে ব্যবহারও হয়। Module 4 && দেওয়ার পরে ওটাই নিয়ো: পড়তে ভালো, আর প্রথম উত্তর 0 হলেই ও থেমে যায়।

মূল কথা

  • একটা char এক byte-র integer; %c আর %d একই মানের উপর দুইটা জানালা।
  • একক উদ্ধৃতি বানায় একটা character আর একটা সংখ্যা; জোড়া উদ্ধৃতি বানায় একটা string।
  • সাধারণ char signed-ও হতে পারে unsigned-ও; কাঁচা byte-র জন্য unsigned char নাও।
  • C17-তে bool-র জন্য <stdbool.h> লাগে, ওটা 1 বা 0 ধরে, আর %d দিয়ে ছাপে।
  • মিথ্যা মান কেবল শূন্য। ঋণাত্মক সংখ্যা সত্য।
  • তুলনা এমন একটা expression যার মান 1 বা 0, তাই সেটা রাখা যায়, ছাপা যায়, যোগ করা যায়।

পরের lesson-এ তুমি এই মাপগুলোর কোনোটাই আর বিশ্বাস করে নেবে না, বরং মেশিনকেই জিজ্ঞেস করবে।

lesson ৪ শেষ

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

পরেরটা: Sizeof আর limits.h: আন্দাজ না করে মেশিনকে জিজ্ঞেস করা