Header <ctype.h> menyediakan fungsi klasifikasi dan konversi karakter. Berbeda dengan membandingkan kode ASCII manual (c >= 'A' && c <= 'Z'), fungsi isalpha, isdigit, dan kawan-kawan bekerja portabel di semua locale dan tahan terhadap karakter non-ASCII. Header ini sangat berguna saat kita memvalidasi input pengguna, mem-parse token, atau mengubah case string. Pada chapter ini kita akan meninjau fungsi klasifikasi (isalpha, isdigit, isalnum, isspace) dan fungsi konversi (toupper, tolower) beserta idiom penanganannya.

Klasifikasi Karakter: isalpha, isdigit, isalnum, isspace #

Semua fungsi klasifikasi di <ctype.h> menerima argumen bertipe int yang harus berupa unsigned char (nilai 0-255) atau konstanta EOF (-1). Memasukkan char bertanda secara langsung berbahaya: bila char bertanda dan karakter memiliki bit tertinggi 1 (misalnya karakter Latin-1 di atas 127), nilainya menjadi negatif dan dapat memicu undefined behavior—karena itu kita selalu cast ke unsigned char lebih dulu.

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

int main(void) {
    /* isalpha, isdigit, isalnum, isspace */
    const char *s = "Budi berusia 25 tahun.";
    printf("Karakter di string \"%s\":\n", s);
    printf("%-4s | %-7s | %-7s | %-7s | %-7s\n",
           "char", "alpha", "digit", "alnum", "space");

    for (size_t i = 0; s[i] != '\0'; i++) {
        unsigned char c = (unsigned char)s[i];
        printf("'%c'  | %-7s | %-7s | %-7s | %-7s\n",
               (isprint(c) ? c : '.'),
               isalpha(c) ? "ya" : "tidak",
               isdigit(c) ? "ya" : "tidak",
               isalnum(c) ? "ya" : "tidak",
               isspace(c) ? "ya" : "tidak");
    }

    return 0;
}

Output yang Dihasilkan:

text
Karakter di string "Budi berusia 25 tahun.":
char | alpha   | digit   | alnum   | space  
'B'  | ya      | tidak   | ya      | tidak  
'u'  | ya      | tidak   | ya      | tidak  
'd'  | ya      | tidak   | ya      | tidak  
'i'  | ya      | tidak   | ya      | tidak  
' '  | tidak   | tidak   | tidak   | ya     
'b'  | ya      | tidak   | ya      | tidak  
'e'  | ya      | tidak   | ya      | tidak  
'r'  | ya      | tidak   | ya      | tidak  
'u'  | ya      | tidak   | ya      | tidak  
's'  | ya      | tidak   | ya      | tidak  
'i'  | ya      | tidak   | ya      | tidak  
'a'  | ya      | tidak   | ya      | tidak  
' '  | tidak   | tidak   | tidak   | ya     
'2'  | tidak   | ya      | ya      | tidak  
'5'  | tidak   | ya      | ya      | tidak  
' '  | tidak   | tidak   | tidak   | ya     
't'  | ya      | tidak   | ya      | tidak  
'a'  | ya      | tidak   | ya      | tidak  
'h'  | ya      | tidak   | ya      | tidak  
'u'  | ya      | tidak   | ya      | tidak  
'n'  | ya      | tidak   | ya      | tidak  
'.'  | tidak   | tidak   | tidak   | tidak  

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (const char *s = "Budi berusia 25 tahun.";): string uji berisi campuran huruf, spasi, digit, dan tanda baca.
  2. Baris 12 (unsigned char c = (unsigned char)s[i];): cast wajib. Tanpa cast ini, karakter dengan bit tertinggi 1 akan menjadi nilai negatif bila char bertanda—lalu fungsi is* dapat mengakses array di luar batas dan undefined behavior.
  3. Baris 14-19 (isalpha, isdigit, isalnum, isspace): isalpha mengembalikan nilai non-zero bila karakter adalah huruf; isdigit untuk '0'-'9'; isalnum adalah gabungan isalpha || isdigit; isspace untuk ' ', '\t', '\n', '\r', '\f', '\v'.
  4. Baris 15 (isprint(c) ? c : '.'): tampilkan karakter hanya bila bisa dicetak (isprint), selain itu tampilkan '.'—berguna bila string mengandung karakter kontrol.

Konversi Case: toupper dan tolower #

Fungsi toupper mengubah huruf kecil menjadi huruf besar; tolower sebaliknya. Karakter yang bukan huruf dikembalikan apa adanya. Return value bertipe int, jadi kita perlu cast ke char bila ingin disimpan di array char.

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

/* Ubah string menjadi huruf besar */
static void ke_huruf_besar(char *s) {
    for (size_t i = 0; s[i] != '\0'; i++) {
        s[i] = (char)toupper((unsigned char)s[i]);
    }
}

/* Ubah string menjadi huruf kecil */
static void ke_huruf_kecil(char *s) {
    for (size_t i = 0; s[i] != '\0'; i++) {
        s[i] = (char)tolower((unsigned char)s[i]);
    }
}

int main(void) {
    char asli[] = "Belajar Pemrograman C, 2024!";
    char buf1[64], buf2[64];

    strcpy(buf1, asli);
    strcpy(buf2, asli);
    ke_huruf_besar(buf1);
    ke_huruf_kecil(buf2);

    printf("Asli     : %s\n", asli);
    printf("Besar    : %s\n", buf1);
    printf("Kecil    : %s\n", buf2);

    /* Demonstrasikan toupper pada karakter non-huruf */
    printf("\n toupper('5') = '%c' (tidak berubah)\n",
           (char)toupper((unsigned char)'5'));
    printf(" toupper('a') = '%c'\n",
           (char)toupper((unsigned char)'a'));

    return 0;
}

Output yang Dihasilkan:

text
Asli     : Belajar Pemrograman C, 2024!
Besar    : BELAJAR PEMROGRAMAN C, 2024!
Kecil    : belajar pemrograman c, 2024!

 toupper('5') = '5' (tidak berubah)
 toupper('a') = 'A'

Fungsi ke_huruf_besar dan ke_huruf_kecil menelusuri string karakter per karakter dan memanggil toupper/tolower untuk setiapnya. Karakter '5' tidak berubah karena bukan huruf—konversi dilewatkan. Perhatikan bahwa asli tetap utuh karena kita menyalinnya ke buf1 dan buf2 lebih dulu dengan strcpy; kedua fungsi mengubah buffer tujuan in-place.

Studi Kasus: Menghitung Kata dalam String #

Mari kita rangkum dengan kasus nyata: menghitung jumlah kata dalam sebuah kalimat. Kita definisikan “kata” sebagai run karakter alfabet yang dipisahkan oleh karakter lain. State machine sederhana dengan flag dalam_kata cukup untuk masalah ini.

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

/* Hitung jumlah kata dalam sebuah string */
static int hitung_kata(const char *s) {
    int jumlah = 0;
    int dalam_kata = 0;
    for (size_t i = 0; s[i] != '\0'; i++) {
        unsigned char c = (unsigned char)s[i];
        if (isalpha(c)) {
            if (!dalam_kata) {
                jumlah++;
                dalam_kata = 1;
            }
        } else {
            dalam_kata = 0;
        }
    }
    return jumlah;
}

int main(void) {
    const char *kalimat[] = {
        "Halo dunia!",
        "Saya   belajar   C    hari ini.",
        "12345 6789",
        ""
    };
    size_t n = sizeof(kalimat) / sizeof(kalimat[0]);

    for (size_t i = 0; i < n; i++) {
        printf("\"%s\" -> %d kata\n", kalimat[i], hitung_kata(kalimat[i]));
    }
    return 0;
}

Output yang Dihasilkan:

text
"Halo dunia!" -> 2 kata
"Saya   belajar   C    hari ini." -> 5 kata
"12345 6789" -> 0 kata
"" -> 0 kata

Logika kuncinya: setiap transisi dari “tidak dalam kata” ke “dalam kata” (yaitu saat bertemu huruf setelah karakter non-huruf) menandai awal satu kata baru. Beberapa spasi berturut-turut diabaikan karena dalam_kata sudah 0 setelah spasi pertama. String "12345 6789" menghasilkan 0 karena isalpha mengembalikan false untuk digit—bila kita ingin digit dihitung sebagai kata, ganti isalpha(c) menjadi isalnum(c).