Learn C Programming

lesson ১ / ৯ · String

Module ১০ · String

Character, ASCII আর কাজের মধ্যে char type

Freeপড়া

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

  • char-কে একটা ছোট সংখ্যা হিসেবে দেখতে পারবে: %c আর %d দিয়ে print করতে পারবে, আর একটা digit বা অক্ষর থেকে তার মান বের করে আবার character-এ ফিরতে পারবে।
  • হাতে লেখা range-এর বদলে ctype.h দিয়ে character যাচাই করতে আর বদলাতে পারবে, আর এর function-গুলো কেন int নেয়, বুঝিয়ে বলতে পারবে।
  • একটা লাইন এক character করে পড়তে পারবে, z পার হলে ঘুরিয়ে a-তে এনে অক্ষর সরাতে পারবে, আর Playground-এ 127-এর উপরের byte কী করে, বুঝিয়ে বলতে পারবে।

David একটা বাড়ির নম্বর এক character করে পড়ে, তাই 7-টা আসে একটা character হয়ে। ও পরের বাড়িটা চায়, তাই %d দিয়ে c + 1 print করে, আর screen-এ আসে 56। ওর program ভুল না: ও যে প্রশ্নটা করতে চায়নি, program ঠিক সেটারই উত্তর দিয়েছে। Character আসলে কী, আর কোন library দিয়ে ওকে নিরাপদে সামলানো যায়, এই lesson সেটাই দেখাবে।

Character আসলে একটা ছোট সংখ্যা

এই হলো David-এর program, শুধু যে লাইনটা ওকে অবাক করেছে, সেটুকু রেখে।

#include <stdio.h>

int main(void)
{
    char c = '7';

    printf("%d\n", c + 1);
    printf("%c\n", c + 1);
    return 0;
}
56
8

Character মানে একটা অক্ষর, digit, space বা চিহ্ন। C এটাকে রাখে একটা সংখ্যা হিসেবে, যার নাম character code, আর সংখ্যাটা আসে Module 1-এর ASCII table থেকে। '7'-এর code 55, তাই '7' + 1 হয় 56।

Module 4 lesson 6 দেখিয়েছিল, যোগের মধ্যে ঢুকলে একটা char আগে int হয়ে যায়। তাই c + 1 হলো int 56। %d সেটা সংখ্যা হিসেবে লেখে, আর %c লেখে সেই character, যার code 56, মানে 8। David চেয়েছিল পরের সংখ্যার মান, কিন্তু চেয়ে বসেছে পরের character-এর code।

তাই char রাখে একটা code, আর %c না %d, সেটা শুধু ঠিক করে code-টা কীভাবে দেখানো হবে।

Digit আর অক্ষরগুলো টানা সারিতে বসে

দশটা digit হলো code 48 থেকে 57, ক্রম মেনে, মাঝে কোনো ফাঁক নেই। বড় হাতের অক্ষর 65 থেকে 90, ছোট হাতের অক্ষর 97 থেকে 122, প্রতিটা সারি টানা। এখান থেকে তিনটা কৌশল আসে, আর তিনটাই সাধারণ যোগ-বিয়োগ।

  • c - '0' একটা digit character-কে তার মানে বদলে দেয়: 55 - 48 হলো 7। উল্টো দিকে, 0 থেকে 9-এর কোনো মানের সাথে '0' যোগ করলে আবার character ফিরে আসে।
  • c - 'A' হলো A থেকে একটা বড় হাতের অক্ষরের দূরত্ব, তাই c - 'A' + 1 হলো বর্ণমালায় তার ক্রম।
  • 'a' - 'A' হলো 32: প্রতিটা বড় হাতের অক্ষর থেকে তার ছোট হাতের অক্ষরের দূরত্ব।
#include <stdio.h>

int main(void)
{
    char digit = '7';
    int value = digit - '0';
    char next = value + 1 + '0';
    char letter = 'D';

    printf("%c has code %d and value %d\n", digit, digit, value);
    printf("value + 1 = %d, stored back as the character %c\n", value + 1, next);
    printf("%c is letter %d of the alphabet\n", letter, letter - 'A' + 1);
    printf("two letters on from %c: %c\n", letter, letter + 2);
    printf("'a' - 'A' = %d, so %c + 32 is %c\n", 'a' - 'A', letter, letter + 32);
    return 0;
}
7 has code 55 and value 7
value + 1 = 8, stored back as the character 8
D is letter 4 of the alphabet
two letters on from D: F
'a' - 'A' = 32, so D + 32 is d

C17 (section 5.2.1) টানা সারির কথা দেয় শুধু digit-এর বেলায়। IBM mainframe-এ এখনো EBCDIC নামে একটা character set চলে; সেখানে i হলো 137, j হলো 145, আর মাঝের code-গুলো কোনো অক্ষরই না। Playground চলে ASCII-তে, তাই এখানে অক্ষরের কৌশলগুলো খাটে।

ASCII-র পাড়া: digit, বড় হাতের আর ছোট হাতের অক্ষর Code 32 থেকে 127, 32টা করে এক সারিতে 32 sp ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? 64 @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ 96 ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ '0' থেকে '9' হলো 48 থেকে 57 'A' থেকে 'Z' হলো 65 থেকে 90 'a' থেকে 'z' হলো 97 থেকে 122 +32 +32 ড্যাশ দেওয়া: Z আর a-এর মাঝের ছয়টা চিহ্ন। c >= 'A' && c <= 'z' এদেরও ঢুকতে দেয়। প্রতিটা ছোট হাতের অক্ষর তার বড় হাতেরটার ঠিক এক সারি, মানে 32 code, নিচে।

32টা করে সারিতে সাজালে প্রতিটা ছোট হাতের অক্ষর বসে তার বড় হাতেরটার ঠিক এক সারি, মানে ঠিক 32 code, নিচে। আর Z আর a-এর মাঝে বসে ছয়টা চিহ্ন।

তাই digit-এর মান c - '0', আর ছোট-বড় হাতের দূরত্ব 'a' - 'A'।

ctype.h: character-টা কী, library-কে জিজ্ঞেস করো

অক্ষর কি না, check করতে Bob লেখে c >= 'a' && c <= 'z', আর বড় হাতের অক্ষরগুলো বাদ পড়ে যায়। ওর দ্বিতীয় চেষ্টা, c >= 'A' && c <= 'z', ওই ছয়টা চিহ্নকেও ঢুকতে দেয়। দুইটাই চুপচাপ compile হয়, আর ঠিক এই দুই ভুলের জন্যই ctype.h আছে।

Library function হলো এমন function, যেটা C-এর সাথেই আসে, আগে থেকে লেখা আর যাচাই করা, যেমন printf। <ctype.h> header একটা একটা character-এর জন্য এমন একগুচ্ছ function declare করে।

এই module যে ctype.h function-গুলো ব্যবহার করে

#include <ctype.h>

isalpha(c)   isdigit(c)   isspace(c)   isupper(c)   islower(c)
toupper(c)   tolower(c)
  • isalpha: অক্ষর কি? isdigit: '0' থেকে '9'-এর কোনোটা কি? isspace: whitespace কি?
  • isupper: বড় হাতের অক্ষর কি? islower: ছোট হাতের অক্ষর কি?
  • এই পাঁচটা না হলে return করে 0, আর হ্যাঁ হলে 0 ছাড়া অন্য কোনো সংখ্যা।
  • toupper আর tolower বদলানো character-টা return করে, আর বদলানোর কিছু না থাকলে c-টাই ফেরত দেয়।
  • c একটা int: এমন একটা মান, যেটা একটা unsigned char-এ ধরে, নয়তো EOF।

এবার 128টা code-এর প্রত্যেকটার উত্তর library নিজেই দিক।

#include <stdio.h>
#include <ctype.h>

#define CODES 128

int main(void)
{
    printf("isspace is true for codes:");
    for (int code = 0; code < CODES; code++) {
        if (isspace(code)) {
            printf(" %d", code);
        }
    }
    printf("\n");

    printf("from 'A' to 'z' but not letters:");
    for (int code = 'A'; code <= 'z'; code++) {
        if (!isalpha(code)) {
            printf(" %c", code);
        }
    }
    printf("\n");
    return 0;
}
isspace is true for codes: 9 10 11 12 13 32
from 'A' to 'z' but not letters: [ \ ] ^ _ `

Whitespace মানে যে character-গুলো print হলে ফাঁকা জায়গার মতো দেখায়। isspace এমন ছয়টা চেনে: tab, newline, vertical tab, form feed, carriage return আর space। হাতে লেখা test সাধারণত মনে রাখে দুইটা। দ্বিতীয় লাইনটা হলো Bob-এর দ্বিতীয় চেষ্টা, হাতেনাতে ধরা।

তাই library তিন দিক থেকে এগিয়ে। এটা প্রতিটা character-এর জন্য ঠিক, isdigit(c) পড়লেই বোঝা যায় কী জিজ্ঞেস করা হচ্ছে, আর যেখানে অক্ষরগুলো টানা সারিতে নেই, সেখানেও এটা ঠিক থাকে।

এর হ্যাঁ মানে শুধু "0 না", তাই লেখো if (isdigit(c)), কখনো if (isdigit(c) == 1) না।

তাই character যাচাই মানে একটা ctype.h call, আর তার উত্তর হয় শূন্য, নয়তো শূন্য না।

toupper আর tolower নতুন একটা character ফেরত দেয়

Alice সব উত্তর table-এ দেখতে ভালোবাসে, তাই ও একবারে পাঁচটা character-এর কথা জিজ্ঞেস করে, সেগুলো একটা char array-তে রেখে।

#include <stdio.h>
#include <ctype.h>

#define SAMPLES 5

int main(void)
{
    char samples[SAMPLES] = {'q', 'Q', '7', '?', ' '};

    printf("char  code  isalpha  isdigit  toupper  tolower\n");
    for (int i = 0; i < SAMPLES; i++) {
        char c = samples[i];

        printf(" '%c'  %4d  %7d  %7d      '%c'      '%c'\n",
               c, c, isalpha(c) != 0, isdigit(c) != 0, toupper(c), tolower(c));
    }
    return 0;
}
char  code  isalpha  isdigit  toupper  tolower
 'q'   113        1        0      'Q'      'q'
 'Q'    81        1        0      'Q'      'q'
 '7'    55        0        1      '7'      '7'
 '?'    63        0        0      '?'      '?'
 ' '    32        0        0      ' '      ' '

isalpha(c) != 0 একটা তুলনা, তাই এটা print করে 1 বা 0 (Module 4 lesson 2)। toupper ছোট হাতের অক্ষরের বড় হাতের রূপটা return করে, আর বাকি সবকিছু যেমন ছিল তেমনই ফেরত দেয়; tolower করে ঠিক উল্টোটা। কোনোটাই c-কে বদলায় না: Module 7-এর সব function-এর মতো, এরা একটা নতুন মান return করে। তাই character বদলাতে চাইলে লেখো c = toupper(c);।

তাই toupper আর tolower অক্ষর বদলায়, বাকিগুলো ছুঁয়েও দেখে না, আর ফলটা তোমার হাতে ফেরত দেয়।

একটা লাইন, এক character করে পড়া

Zara-র password-এর নিয়ম: অন্তত একটা বড় হাতের অক্ষর থাকতেই হবে। ওর program লাইনটা পড়ে এক character করে, Module 6 lesson 3 যে loop দিয়ে একটা খারাপ লাইন ফেলে দিয়েছিল, সেটা দিয়েই। এবার অবশ্য প্রতিটা character কাজে লাগে।

একটা লাইন, এক character করে পড়ার গড়ন

char c = 0;

while (scanf("%c", &c) == 1 && c != '\n') {
    /* use c */
}
  • "%c" ঠিক একটা character পড়ে, space হলেও, তাই এর আগে কোনো space বসে না।
  • == 1 loop থামায় যখন input ফুরিয়ে যায় আর scanf return করে EOF।
  • c != '\n' loop থামায় newline-এ, মানে Enter চাপলে যে character-টা যোগ হয়।
#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char c = 0;
    int capitals = 0;

    while (scanf("%c", &c) == 1 && c != '\n') {
        if (isupper(c)) {
            capitals++;
        }
    }
    printf("capitals: %d\n", capitals);
    if (capitals > 0) {
        printf("rule met\n");
    } else {
        printf("add a capital letter\n");
    }
    return 0;
}

Input Lima 42X, তারপর Enter দিয়ে এটা trace করো, প্রতি character-এ এক সারি।

Passcতার codeisupper(c)pass-এর পরে capitals
1L76হ্যাঁ1
2i105না1
3m109না1
4a97না1
5একটা space32না1
6452না1
7250না1
8X88হ্যাঁ2
9'\n'10check হয় না: loop শেষ2
capitals: 2
rule met

ওই output-টা input Lima 42X, তারপর Enter-এর জন্য।

Module 3 lesson 4 %c-এর আগে একটা space বসিয়েছিল, পড়ে থাকা newline-টা পার হওয়ার জন্য। এখানে space-গুলোই data, আর newline হলো থামার সংকেত, তাই কোনো space না। == 1-টাও দরকারি, কারণ তুমি input যেভাবে টাইপ করো, Playground ঠিক সেভাবেই পাঠায়। Enter না চাপলে কোনো newline আসে না, কিন্তু scanf return করে EOF, আর loop তবুও থামে।

এই input ASCII। 127-এর উপরের byte-এর জন্য যে একটা বাড়তি ধাপ লাগে, সেটা আসছে এই lesson-এর শেষ অংশে।

তাই loop প্রতি pass-এ একটা character পড়ে, আর থামে newline-এ, নয়তো input শেষ হলে।

Caesar shift: ঘুরে আসা অক্ষরের হিসাব

Caesar shift প্রতিটা অক্ষরকে বদলে দেয় বর্ণমালায় তার k ঘর পরের অক্ষর দিয়ে। রোমান লেখক Suetonius লিখে গেছেন, Julius Caesar k = 3 ব্যবহার করতেন। ঝামেলাটা বর্ণমালার শেষে: z-এর তিন ঘর পরে ঘুরে এসে c হওয়ার কথা।

ঘুরে আসার কাজটা করে Module 4-এর ভাগশেষের operator। প্রথমে অক্ষরটাকে 'a' থেকে তার দূরত্বে বদলাও, মানে 0 থেকে 25-এর একটা সংখ্যায়। তার সাথে k যোগ করো, তারপর 26 দিয়ে ভাগ করে ভাগশেষ নাও: এতে 28 ঘুরে হয়ে যায় 2। শেষে আবার 'a' যোগ করলে সেটা অক্ষর হয়ে যায়।

#include <stdio.h>

#define SHIFT 3

int main(void)
{
    char c = 0;

    while (scanf("%c", &c) == 1 && c != '\n') {
        int position = c - 'a';
        int moved = (position + SHIFT) % 26;

        printf("%c", 'a' + moved);
    }
    printf("\n");
    return 0;
}

Input lazy, তারপর Enter দিয়ে এটা trace করো।

cতার codepositionposition + SHIFT% 26-এর পরে movedযা print হয়
l108111414o
a97033d
z12225282c
y12124271b
'\n'10loop শেষ, আর শেষের printf লাইনটা শেষ করে
odcb

ওই output-টা input lazy, তারপর Enter-এর জন্য। Program ধরে নেয়, input-এ শুধু ছোট হাতের অক্ষর আছে; একটা বড় হাতের অক্ষর দিলে ভুল কিছু বের হবে। ছোট-বড় হাত ঠিক রাখা অনুশীলন 3-এর কাজ। আর decode করা, মানে পিছনের দিকে সরানো, তার নিজের একটা ফাঁদ আছে, সেটা পাবে problems lesson-এ।

তাই ঘুরে আসা মানে তিন ধাপ: দূরত্বে বদলাও, যোগ করে % 26 নাও, আবার অক্ষরে ফেরো।

127-এর উপরের byte: signed char আর একটা বাংলা অক্ষর

ASCII থামে 127-এ। অন্য লিপির লেখা সাধারণত রাখা হয় UTF-8-এ, যেখানে একটা character নেয় এক থেকে চার byte, আর একটা বাংলা অক্ষর নেয় তিন byte (Module 1 lesson 4)। একটা বাংলা অক্ষর এলে পড়ার loop কী দেখে, দেখো।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char c = 0;

    while (scanf("%c", &c) == 1 && c != '\n') {
        unsigned char byte = (unsigned char)c;

        printf("as char %4d   as unsigned char %3d   isalpha %d\n",
               c, byte, isalpha(byte) != 0);
    }
    return 0;
}
as char   65   as unsigned char  65   isalpha 1
as char  -32   as unsigned char 224   isalpha 0
as char  -90   as unsigned char 166   isalpha 0
as char -123   as unsigned char 133   isalpha 0

ওই output-টা input Aঅ, তারপর Enter-এর জন্য, Compiler Explorer-এ x86-64 Linux-এর GCC 12-এ। একই input-এ Playground-ও একই চারটা code পড়েছে: 65, -32, -90 আর -123।

অ হলো তিনটা byte: 224, 166 আর 133। সাধারণ char signed হতে পারে, unsigned-ও হতে পারে (Module 2 lesson 4), আর Playground-এ এটা signed। তাই 224 বের হয় -32 হয়ে, ঠিক যে পথে Module 4 lesson 6-এর teaser-এ 200 হয়ে গিয়েছিল -56।

এতে ctype.h-এর নিয়ম ভাঙে: argument হতে হবে এমন মান, যেটা একটা unsigned char-এ ধরে, নয়তো EOF (C17 section 7.4)। তাই -32-এর উপর isalpha(c) হলো undefined behaviour। Playground-এর GNU C Library কপালগুণে এটা মেনে নেয়; অন্য library হয়তো crash করবে। সমাধান একটা cast: (unsigned char)c লিখলে -32 আবার 224 হয়ে যায়।

Cast-এর পরেও তিনটা byte-এর একটাও অক্ষর না: প্রতিটা একটা অক্ষরের তিন ভাগের এক ভাগ। সত্যিকারের বাংলা লেখা সামলাতে লাগে এমন library, যেটা UTF-8 বোঝে, যেমন ICU (International Components for Unicode)। Module 1 lesson 4 যে কথা দিয়েছিল, সেটা এভাবেই রাখা হলো।

এখন থেকে যে program এমন byte পড়ে, যা সে নিজে বেছে নেয়নি, সে cast-টা লেখে। ASCII লেখার বেলায়, যেমন এই module-এর test-গুলো, সাধারণ call-ই নিরাপদ।

তাই Playground-এ 127-এর উপরের byte একটা negative char, আর (unsigned char) সেটাকে ctype.h-এর জন্য নিরাপদ করে।

Example 1: হ্যাঁ বা না, যে হাতেই লেখো

সবচেয়ে ছোট কাজের ctype.h program: একটা game জিজ্ঞেস করে save করবে কি না, আর player টাইপ করতে পারে y বা Y।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char answer = 0;
    int choice = 0;

    scanf(" %c", &answer);
    choice = tolower((unsigned char)answer);

    if (choice == 'y') {
        printf("game saved\n");
    } else if (choice == 'n') {
        printf("not saved\n");
    } else {
        printf("please type y or n\n");
    }
    return 0;
}
game saved

ওই output-টা input Y-এর জন্য। tolower দুই হাতকে এক করে দেয়, তাই ladder-এ চারটা না, দুইটা অক্ষর check করলেই চলে। এখানে " %c"-এর space-টা ঠিক আছে, কারণ এক অক্ষরের উত্তরের আগের space-গুলো নিছক বাড়তি।

Run in Compiler
Example 2: David-এর digit-গুলো একটা সংখ্যা হয়ে যায়

David-এর প্রথম চাওয়াটা, এবার ঠিকভাবে: digit character-গুলো মিলে যে সংখ্যাটা লেখে, loop সেটা গড়ে তোলে।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char c = 0;
    int value = 0;

    while (scanf("%c", &c) == 1 && isdigit((unsigned char)c)) {
        value = value * 10 + (c - '0');
    }
    printf("%d + 1 = %d\n", value, value + 1);
    return 0;
}
cতার codec - '0'pass-এর পরে value
25020 x 10 + 2 = 2
04802 x 10 + 0 = 20
250220 x 10 + 2 = 202
6546202 x 10 + 6 = 2026
'\n'10digit না: loop শেষ2026
2026 + 1 = 2027

ওই output-টা input 2026, তারপর Enter-এর জন্য। 10 দিয়ে গুণ করলে আগের digit-গুলো এক ঘর বাঁয়ে সরে যায়। scanf("%d") ভিতরে ঠিক এই পথেই হাঁটে, সাথে চিহ্ন (sign) আর overflow-এর check; এখানে দুইটার একটাও নেই, তাই input 9টা digit-এর মধ্যে রাখো।

Run in Compiler
Example 3: Kenji-র game-এর control

Kenji-র maze game এক লাইন move পড়ে: w উপরে, s নিচে, a বাঁয়ে, d ডানে, ছোট বা বড় যে হাতেই হোক। অন্য যেকোনো character বাদ যায়, তবে গোনা হয়। নতুন কেউ ঠিক এই program-টাই লেখে, আর এটা ভালো program।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char c = 0;
    int x = 0;
    int y = 0;
    int ignored = 0;

    while (scanf("%c", &c) == 1 && c != '\n') {
        switch (tolower((unsigned char)c)) {
        case 'w':
            y++;
            break;
        case 's':
            y--;
            break;
        case 'a':
            x--;
            break;
        case 'd':
            x++;
            break;
        default:
            ignored++;
            break;
        }
    }
    printf("position (%d, %d), ignored %d\n", x, y, ignored);
    return 0;
}
ctolower-এর পরেMovexyignored
Wwউপরে010
ddডানে110
Ddডানে210
ssনিচে200
aaবাঁয়ে100
??কিছু না101
wwউপরে111
'\n'loop শেষ111
position (1, 1), ignored 1

ওই output-টা input WdDsa?w, তারপর Enter-এর জন্য। tolower আছে বলে switch-এ প্রতিটা move-এর জন্য দুইটা না, একটা case লাগে। default বাকি সব ধরে ফেলে, তাই টাইপে ভুল হলেও player নড়ে না।

Run in Compiler

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

  • Database। SQLite-এর SQL পড়ার অংশ ctype.h ব্যবহার করে না। কোন byte কী ধরনের, সেটা ঠিক করে নিজের 256 ঘরের table, sqlite3CtypeMap দিয়ে, আর প্রতিটা byte-কে আগে unsigned char-এ cast করে নেয়। Table নিজের বলে machine-এর ভাষার setting যা-ই হোক, উত্তর একই থাকে।
  • Password-এর নিয়ম। Linux-এর pam_pwquality নতুন password-এর digit, বড় হাতের অক্ষর, ছোট হাতের অক্ষর আর বাকি character গোনে। এর dcredit, ucredit, lcredit আর ocredit setting-গুলো ওই চার দলেরই নাম।
  • Cipher। ROT13 হলো k = 13-এর Caesar shift; Usenet-এ গল্পের শেষটা (spoiler) লুকাতে এটা চলত। দুইবার চালালে লেখাটা ফিরে আসে, আর Unix-এর এক command, tr 'A-Za-z' 'N-ZA-Mn-za-m', এক লাইনেই কাজটা সারে।
  • Terminal। Ctrl-এর সাথে একটা অক্ষর চাপলে যায় বর্ণমালায় সেই অক্ষরের ক্রম: Ctrl+C হলো 3, Ctrl+D হলো 4। লাইনের শুরুতে Ctrl+D পেলে Unix terminal সেটাকে input-এর শেষ ধরে, আর scanf return করে EOF।

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

১. হাতে লেখা range, যেটা বড় হাতের অক্ষর বাদ দেয়।

if (c >= 'a' && c <= 'z') {
    letters++;
}

কোনো command line-এই কোনো বার্তা নেই। পড়ার loop-এর ভিতরে input Dhaka Lima দিলে আসে letters: 7, 9 না, কারণ D আর L কখনো গোনা হয় না। লেখো isalpha((unsigned char)c)। তুমি range-টা লিখবে, কারণ বর্ণমালা বললে তোমার চোখে ছোট হাতের অক্ষরই ভাসে।

২. একটা character-এর দুই পাশে double quote।

char c = "A";

Playground-ও এটা দেখায়, Compile output tab-এ: warning: initialization of 'char' from 'char *' makes integer from pointer without a cast [-Wint-conversion]। বাংলা page-এ tab-টার নাম বাংলায় লেখা থাকে, ভিতরের লেখা একই। -Wall আর -Wall -Wextra-ও একই কথা বলে, আর GCC 14 এটাকে error বানিয়ে দেয়। "A" হলো একটা string, character-এর একটা সারি, যেটা খুলবে lesson 02; char * হলো Module 11-এর লেখার ধরন। Compiler Explorer-এ একবার run করে print হয়েছে একটা অদৃশ্য character, code 4, A না। লেখো 'A'; তুমি double quote-এর দিকে হাত বাড়াবে, কারণ printf লেখা রাখে double quote-এর ভিতরে।

৩. toupper call করে উত্তরটা ফেলে দেওয়া।

char c = 'q';

toupper(c);
printf("%c\n", c);

কোনো command line-এই কোনো বার্তা নেই, আর print হয় q। toupper বড় হাতের অক্ষরটা return করে, আর কিছুই বদলায় না, তাই সেটা রেখে দাও: c = toupper((unsigned char)c);। তুমি ভাববে call-টাই c-কে বদলে দেবে, কারণ নামটা শুনতে একটা হুকুমের মতো।

৪. পড়ার loop-এর ভিতরে %c-এর আগে space।

while (scanf(" %c", &c) == 1 && c != '\n') {
    if (c == ' ') {
        spaces++;
    }
    read++;
}

কোনো command line-এই কোনো বার্তা নেই। Format-এর space-টা প্রতিটা space পার করে যায়, newline-ও। Input Go on, Enter, তারপর no দিলে এই loop print করেছে spaces: 0, characters read: 6। Space-টা সে দেখেইনি, আর পড়তে পড়তে দ্বিতীয় লাইনে ঢুকে গেছে। ওই space ছাড়া print হয়েছে spaces: 1, characters read: 5। তুমি space-টা বসাবে, কারণ Module 3 lesson 4 এটাকে সমাধান হিসেবে শিখিয়েছিল, আর একটা সংখ্যার পরে এটা সত্যিই সমাধান।

মাথা খাটাও

Kenji খেয়াল করে, 32 হলো 2-এর একটা ঘাত, তাই ও + 32-এর বদলে বসায় Module 4-এর exclusive or, ^।

#include <stdio.h>

int main(void)
{
    printf("%c %c\n", 'A' ^ 32, 'a' ^ 32);
    printf("%d\n", '5' ^ 32);
    return 0;
}

এটা compile হয়, কোনো command line-এই কোনো বার্তা আসে না। এটা কী print করে? ^ 32 কেন একটা অক্ষরের ছোট-বড় হাত বদলে দেয়, আর '5'-এর কী হয়?

65 আর 97-কে binary-তে লেখো, আট অঙ্ক করে, তারপর bit ধরে ধরে মেলাও। তারপর diagram-এ প্রতিটা character কোন সারিতে বসে, খুঁজে দেখো।

অনুশীলন ১সহজ

Zara দেখতে চায়, একটা password-এর লাইন কী কী দিয়ে তৈরি। লাইনের অক্ষর, digit, space আর বাকি character গোনো।

Input. Printable ASCII character-এর একটা লাইন, space থাকতে পারে।

Output. চার লাইন: letters N, digits N, spaces N আর others N। অক্ষর আর digit মানে isalpha আর isdigit যা বলে, space মানে ' ', আর newline গোনা হয় না।

Constraints. লাইনে 0 থেকে 100টা character, প্রতিটার code 32 থেকে 126।

Sample. Input Dhaka 2026, go! দিলে চার লাইনে letters 7, digits 4, spaces 2 আর others 2।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char c = 0;
    int letters = 0;
    int digits = 0;
    int spaces = 0;
    int others = 0;

    while (scanf("%c", &c) == 1 && c != '\n') {
        /* Add 1 to the counter that c belongs to. */
    }
    printf("letters %d\n", letters);
    printf("digits %d\n", digits);
    printf("spaces %d\n", spaces);
    printf("others %d\n", others);
    return 0;
}

char-classes নামে গ্রেড হয়। Hidden test-এ আছে একটা ফাঁকা লাইন আর 100 character-এর একটা লাইন। বড় হাতের অক্ষর বাদ দেওয়া range, আর newline গুনে ফেলা, দুইটাই সেখানে ধরা পড়ে।

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

David একটা লাইন যাচাই করে তার digit-গুলো যোগ করে, প্রতিটা আলাদা করে: 12 মানে 1, তারপর 2।

Input. Printable ASCII character-এর একটা লাইন।

Output. লাইনের digit-গুলোর মানের যোগফল, কোনো digit না থাকলে 0।

Constraints. লাইনে 0 থেকে 100টা character, প্রতিটার code 32 থেকে 126।

Sample. Input Room 12, floor 3 দিলে 6।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    char c = 0;
    int sum = 0;

    while (scanf("%c", &c) == 1 && c != '\n') {
        /* If c is a digit, add its value to sum. */
    }
    printf("%d\n", sum);
    return 0;
}

আলাদা করে গ্রেড হয় না। একটা লাইনের উপর দিয়ে এই একই হাঁটা char-classes-ই গ্রেড করে।

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

David-এর ক্লাব নোট লেখে সংকেতে: প্রতিটা অক্ষর k ঘর সামনে সরে, z থেকে ঘুরে a-তে। বড় হাতের অক্ষর বড় হাতেরই থাকে, আর বাকি সব character যেমন আছে তেমনই থাকে।

Input. প্রথম লাইনে k, তারপর এক লাইন লেখা।

Output. লেখাটা, যেখানে প্রতিটা অক্ষর সরানো, আর বাকি সব আগের মতো।

Constraints. 0 <= k <= 25। লেখায় 0 থেকে 100টা character, প্রতিটার code 32 থেকে 126।

Sample. Input 3, তারপর পরের লাইনে Zoo 42! দিলে Crr 42!।

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    int k = 0;
    char c = 0;

    scanf("%d", &k);
    while (scanf("%c", &c) == 1 && c != '\n') {
        /* the rest of the first line: nothing to do */
    }

    while (scanf("%c", &c) == 1 && c != '\n') {
        /* Shift c forward by k places if it is a letter, keeping
           its case, then print it. Print any other c unchanged. */
    }
    printf("\n");
    return 0;
}

আলাদা করে গ্রেড হয় না। Problems lesson-এর caesar-decode একই shift গ্রেড করে, উল্টো দিকে, অনেকগুলো লাইনে।

Run in Compiler

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

  • isdigit('7') কেন 1 না দিয়ে 2048 দেয়?

    কারণ হ্যাঁ মানে 0 ছাড়া যেকোনো সংখ্যা, Module 4 lesson 2 যেমন সাবধান করে দিয়েছিল। Playground-এর GNU C Library যে bit flag-টা check করেছে, সেটাই return করে: Compiler Explorer-এ একবার run করে isdigit('7')-এর জন্য পাওয়া গেছে 2048, isalpha('q')-এর জন্য 1024, আর isspace(' ')-এর জন্য 8192। তাই ওখানে isdigit(c) == 1 কখনো সত্যি হয় না।

  • Character তো char, তাহলে এই function-গুলো int নেয় কেন?

    যাতে এরা EOF-ও নিতে পারে, যেটা কোনো character না। এক character পড়ার C-র আরেকটা উপায়, getchar(), একটা int return করে, যেটা হয় একটা unsigned char-এর মান, নয়তো EOF। ctype.h ঠিক এই পরিসরটাই মেনে নেয়, আর একটা negative char পড়ে যায় এর বাইরে।

  • %c কি একটা বাংলা অক্ষর print করতে পারে?

    এক call-এ না: %c print করে একটা byte। তবে তিনটা byte পরপর print করো, UTF-8 পড়তে পারে এমন screen অক্ষরটা আবার এঁকে দেবে। Compiler Explorer-এ একবার run করে পড়ার loop দিয়ে অ পড়া হয়েছে, প্রতিটা byte %c দিয়ে আবার print হয়েছে, আর দেখা গেছে অ, সাথে 3 byte-এর হিসাব।

মূল কথা

  • একটা char একটা code রাখে: %c সেটা এঁকে দেখায়, %d সংখ্যাটা print করে, আর হিসাব চলে সংখ্যাটার উপর।
  • ASCII-তে digit আর অক্ষরগুলো টানা সারিতে বসে, তাই c - '0', c - 'A' + 1 আর % 26 দিয়ে shift, সবই সাধারণ হিসাব।
  • Character যাচাই করো ctype.h দিয়ে, range দিয়ে না: এটা প্রতিটা character-এর জন্য ঠিক, আর এর হ্যাঁ মানে "0 না"।
  • toupper আর tolower নতুন character-টা return করে; সেটা রেখে দাও, নইলে কিছুই বদলায় না।
  • while (scanf("%c", &c) == 1 && c != '\n') একটা লাইন এক character করে পড়ে, space-সহ।
  • Playground-এ 127-এর উপরের byte একটা negative char: ctype.h-এর জন্য সেটাকে unsigned char-এ cast করো, আর বাংলা লেখা ছেড়ে দাও একটা library-র হাতে।

এরপর Bob, Maria-র নামটা ঠিক পাঁচটা বাক্সে রাখে, আর print করলে নামের শেষে Dhaka জুড়ে বসে। হারিয়ে যাওয়া শূন্যটা খুঁজে বের করবে lesson 02।

lesson ১ শেষ

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

পরেরটা: String: যে array শূন্য দিয়ে শেষ হয়