C Piscine
notlarım

Sıfırdan başlayan biri için C dilinin temelleri. Her egzersiz, her fonksiyon, her karakter neden orada — hepsini açıklayacağım.

// nasıl okunur

Her egzersiz başlangıçta kapalı görünür. Üstüne tıklayınca açılır. Bilmediğin bir şey gördüğünde dur, kavramı anla, sonra geç. Hızlı geçmek bir şey öğretmez — kafan karışırsa geri dön.

// 01C dili nedir?

C, 1972'de Bell Labs'ta Dennis Ritchie tarafından geliştirilen bir programlama dilidir. Hâlâ yaşıyor olmasının nedeni: hızlı, taşınabilir, ve makineye yakın. İşletim sistemleri (Linux, Windows çekirdeği), gömülü sistemler, oyun motorları, hatta Python ve JavaScript'in yorumlayıcıları C ile yazılmıştır.

C neden önemli?

  • Düşük seviyeli — bellekle direkt çalışır, soyutlama az
  • Manuel bellek yönetimi — kendin tahsis eder, kendin temizlersin
  • Hızlı — derlenmiş, çöp toplayıcı yok
  • Eğitici — bilgisayarın gerçekte nasıl çalıştığını öğretir

C'nin felsefesi

C sana güvenir. "Ne yaptığını biliyorsun" der. Belleğin sınırını aşmana izin verir, sıfıra bölmene izin verir, kendini ayağından vurmana izin verir. Bu yüzden öğrenmesi zor ama bir kez öğrenince başka diller kolay gelir.

// 42 felsefesi

42 Piscine'de C ile başlamanın bir nedeni var. Eğer pointer'ları, belleği, döngüleri C'de anlarsan, JavaScript veya Python'da çok daha derin yazılım yazabilirsin. Çünkü perde arkasını biliyor olursun.

// 02Derleme süreci

Yazdığın .c dosyası bilgisayarın anladığı bir şey değildir — sadece metin. Bilgisayar makine kodunu (1'ler ve 0'lar) anlar. Aradaki çeviri işine derleme (compilation) denir.

Adımlar

# 1. Derle: kaynak kod → çalıştırılabilir dosya
cc dosya.c

# 2. Çalıştır
./a.out

# Bayraklarla derle (42 standardı):
cc -Wall -Wextra -Werror dosya.c

Bayraklar ne işe yarar?

BayrakAnlamı
-Wall"Warning all" — tüm bilinen uyarıları aç
-WextraEkstra uyarılar, daha sıkı
-WerrorUyarıları hata olarak gör (uyarı varsa derleme başarısız olur)

42'de Moulinette tam bu üç bayrakla derler. Yani tek bir uyarı bile kabul edilmez. Kodun mükemmel temiz olmalı.

// kritik kurallar
  • Kod derlenmezse → not = 0
  • Yasak fonksiyon kullanırsan → not = -42
  • Norminette geçmezse → kod hiç değerlendirilmez
  • Kolay egzersiz çalışmıyorsa, zoru yapsan da sayılmaz

// 03Fonksiyon anatomisi

Fonksiyon = bir iş yapan, isimlendirilmiş kod bloğu. C programları fonksiyonlardan oluşur. Bir fonksiyona şöyle bakabilirsin: girdi alan, çıktı veren bir makine.

void ft_putchar(char c)
{
    write(1, &c, 1);
}

Parça parça

01 // dönüş tipi

void — bu fonksiyon hiçbir şey döndürmez. Eğer int olsaydı, fonksiyondan bir tam sayı geri alacaktın. Diğer örnekler: char * (string döner), int (sayı döner).

02 // fonksiyon adı

ft_putchar — sen koyuyorsun. ft_ 42'nin geleneği, "function" demek. Standart C fonksiyonlarıyla karışmasın diye.

03 // parametre listesi

(char c) — fonksiyon dışarıdan ne alacak? Burada bir char alıyor ve içeride c ismiyle kullanıyor. Birden fazla parametre virgülle ayrılır: (int a, int b, char *str).

04 // gövde

{ ... } — süslü parantezler arasındaki kısım fonksiyonun yapacağı iş. Tüm ifadeler burada.

05 // ifade sonu

; — her ifadenin sonunda noktalı virgül. C'de cümle sonu nokta gibi. Unutursan derleme hatası alırsın.

Çağırmak

ft_putchar('A');  // 'A' karakterini fonksiyona gönder

Çağrıldığında program akışı fonksiyona zıplar, içerisi çalışır, sonra çağıran yere geri döner.

// 04write fonksiyonu

C00'da hep write kullanacaksın. Bu, işletim sistemine "şu veriyi şuraya yaz" diyen bir sistem çağrısı. unistd.h kütüphanesinde tanımlı.

prototip
ssize_t write(int fd, const void *buf, size_t count);

3 parametre

01 // fd — file descriptor (kanal numarası)

Nereye yazacağını söyleyen bir tam sayı. Standart kanallar:

fdAnlamı
0stdin — klavye girişi (write için kullanılmaz)
1stdout — normal ekran çıktısı
2stderr — hata çıktısı (yine ekran ama farklı kanal)

C00'da hep 1 yazacaksın çünkü ekrana yazıyorsun.

02 // buf — verinin adresi

Yazılacak verinin bellekteki adresi. Veri değil, adres. Bu yüzden tek karakter yazarken &c yazıyoruz — c'nin adresini almak için.

String'lerde & gerekmez çünkü string zaten dizidir, dizinin adı zaten ilk elemanın adresidir:

write(1, &c, 1);          // tek karakter — & gerekli
write(1, "hello", 5);     // string — & yok
03 // count — kaç byte

Yazılacak byte sayısı. "hello" için 5. "\n" için 1.

// yaygın hata

Bir string'in null terminator'ını (\0) yazma. "hello" bellekte 6 byte ama write için 5 byte yazılır. write(1, "hello", 6) yazarsan ekrana görünmez bir null karakter de basarsın — bu yanlış.

// 05String mantığı (en önemli)

C'de "string" diye bir veri tipi yoktur. C'deki string aslında bir karakter dizisidir, ve sonu '\0' (null terminator) ile işaretlenir.

"hello" bellekte ne?

İndeks:    0    1    2    3    4    5
İçerik:   'h'  'e'  'l'  'l'  'o'  '\0'
ASCII:    104  101  108  108  111  0

           ↑
       str pointer'ı bu adresi tutar

Yani "hello" aslında 6 byte: 5 görünür karakter + 1 görünmez null. '\0' stringin bittiğini söyler. Olmazsa, döngülerin nerede duracağını bilemez ve belleği taşırırsın.

'a' vs "a"

İfadeTipBoyutİçerik
'a'char1 byte97 (ASCII)
"a"char dizisi2 byte'a', '\0'

Tek tırnak = bir karakter. Çift tırnak = string. Asla karıştırma.

ASCII tablosundaki kritik aralıklar

KarakterASCII başlangıçASCII bitiş
Kontrol karakterleri031
Boşluk (' ')3232
Rakamlar ('0'–'9')4857
Büyük harfler ('A'–'Z')6590
Küçük harfler ('a'–'z')97122
Yazdırılabilir32126
// hap bilgi

Büyük harf ile küçük harf arasındaki fark her zaman 32'dir. 'a' - 'A' = 97 - 65 = 32. Bu yüzden büyütmek için -32, küçültmek için +32.

String'i indekslemek

char *str = "hello";

str[0]  // 'h'
str[1]  // 'e'
str[5]  // '\0'

// str[i] aslında *(str + i) ile aynı şey

Bir string boyunca dolaşmak için tipik döngü:

int i = 0;
while (str[i] != '\0')
{
    // str[i] ile bir şey yap
    i++;
}

Bu kalıbı C02, C03'te yüzlerce kez göreceksin.

// 06main argümanları

Bir programı terminalden çalıştırırken yanına ek bilgiler verebilirsin:

./program merhaba 42 dünya

Programın bu kelimelere ulaşması için main'i argümanlı yazarsın:

int main(int argc, char **argv)
{
    return (0);
}

argc — argument count

Argüman sayısı. Program adı dahil. Yukarıdaki örnekte argc = 4.

argv — argument vector

char **argv = "stringlerin dizisi". Her argüman bir string.

argc = 4

argv → [0]→"./program"
       [1]→"merhaba"
       [2]→"42"
       [3]→"dünya"
       [4]→NULL          ← dizi sonu işareti

Tipler net olsun

İfadeTipAnlamı
argvchar **String'lerin dizisi
argv[i]char *i. string
argv[i][j]chari. stringin j. karakteri
// tuzak

Komut satırından gelen her şey string'dir. ./program 42 yazınca argv[1] sayı 42 değil, "42" string'idir. Sayıya çevirmek için ft_atoi gibi bir fonksiyon yazman gerekir.

Çıkış kodu

return (0) = "her şey yolunda". return (1) = "bir hata var". Terminal son komutun çıkış kodunu $? ile gösterir.

// C00Çıktı & Döngüler

Bu modül C dilinin temel iskeleti: değişkenler, koşullar, döngüler, recursion. 9 egzersiz, hepsi sadece write fonksiyonuyla çözülür. C00'da string yazdırmazsın bile — sadece tek karakter yazarsın, gerisini sen kurarsın.

// foreword'dan

Subject'in foreword'ünde balık yağı var ama dikkate alma — projeye giriş yumuşatma havası. Önemli olan bir alttaki "Today's threshold" yok bu modülde, hepsini yapman beklenir.

ex00 ft_putchar void ft_putchar(char c);

Ne istiyor?

Tek bir karakteri ekrana yazdıracak fonksiyon.

Mantığı

write fonksiyonu adres ister. Karakterimiz c'nin adresini &c ile alıp, 1 byte yazmasını söyleriz.

#include <unistd.h>

void ft_putchar(char c)
{
    write(1, &c, 1);
}

Satır satır

  • #include <unistd.h>write fonksiyonu burada tanımlı, dahil etmezsek derleyici tanımaz
  • void — fonksiyon değer döndürmez, sadece iş yapar
  • (char c) — dışarıdan bir karakter alır, içeride c der
  • &c — c'nin adresi. write değer değil adres ister
  • 1, ..., 1 — 1. kanal (stdout), 1 byte yaz
// ipucu

Subject "intermediate evaluation tetiklemekten çekinme" diyor. İlk egzersiz olduğu için, çalıştığından emin ol ve hemen değerlendirmeye gönder. İlk başarı motivasyon yükseltir.

ex01 ft_print_alphabet void ft_print_alphabet(void);

Ne istiyor?

a'dan z'ye küçük harf alfabe, tek satırda, alt alta değil yan yana.

Mantığı

Karakterler aslında sayıdır. 'a' = 97, 'b' = 98, ..., 'z' = 122. Yani c değişkenini 'a''dan başlatıp 'z''ye kadar artırırsak alfabeyi yazdırmış oluruz.

#include <unistd.h>

void ft_print_alphabet(void)
{
    char c;

    c = 'a';
    while (c <= 'z')
    {
        write(1, &c, 1);
        c++;
    }
}

Satır satır

  • (void) — parametre yok demek. Boş bırakmaktansa void yazmak C'de daha doğru
  • char c; — değişken tanımı. Henüz değer yok
  • c = 'a'; — değişkene değer atama. 42 Norm tanım ve atamayı ayırmanı önerir
  • while (c <= 'z') — c, z'den küçük veya eşit olduğu sürece
  • c++ — c'yi 1 artır. Bir sonraki harf

Döngü adımları

c='a' → yaz → c=98 ('b')
c='b' → yaz → c=99 ('c')
...
c='z' → yaz → c=123 ('{')
c='{' → koşul yanlış, dur
ex02 ft_print_reverse_alphabet void ft_print_reverse_alphabet(void);

Ne istiyor?

Aynısı ama z'den a'ya doğru.

void ft_print_reverse_alphabet(void)
{
    char c;

    c = 'z';
    while (c >= 'a')
    {
        write(1, &c, 1);
        c--;
    }
}

Tek fark: başlangıç z, bitiş a, ve c-- (azalt). Mantık aynı.

ex03 ft_print_numbers void ft_print_numbers(void);

Ne istiyor?

0123456789 — tek satırda.

void ft_print_numbers(void)
{
    char c;

    c = '0';
    while (c <= '9')
    {
        write(1, &c, 1);
        c++;
    }
}
// kritik nüans

Burada '0' karakter (ASCII 48), 0 sayısı değil. Eğer int n = 0; write(1, &n, 1); yazsaydın ekrana hiçbir şey çıkmazdı çünkü ASCII 0 = null karakter (görünmez). Sayıyı karaktere çevirmek için n + '0' hilesini kullanırız.

ex04 ft_is_negative void ft_is_negative(int n);

Ne istiyor?

Sayı negatifse 'N', pozitif veya sıfırsa 'P' yazdır.

void ft_is_negative(int n)
{
    char c;

    if (n < 0)
        c = 'N';
    else
        c = 'P';
    write(1, &c, 1);
}

Yeni şeyler

  • int n — tam sayı parametresi (pozitif veya negatif olabilir)
  • if (...) — koşul. Doğruysa altındaki ifade çalışır
  • else — if yanlışsa burası çalışır
  • n < 0 — "n sıfırdan küçük mü?"

Sıfır pozitif tarafta sayılır (subject böyle istiyor). n <= 0 yazsan yanlış olur — sıfır için 'N' basar.

ex05 ft_print_comb void ft_print_comb(void);

Ne istiyor?

Üç farklı rakamın artan sıralı kombinasyonları: 012, 013, 014, ..., 789.

987 yok çünkü 789 zaten o kombinasyonu kapsıyor (rakamlar artan sırada). 999 yok çünkü farklı rakam olmalı.

Mantığı

Üç rakam = üç döngü (iç içe). Birinci 0-7, ikinci her zaman birinciden büyük (max 8), üçüncü her zaman ikinciden büyük (max 9).

void ft_print_comb(void)
{
    char a;
    char b;
    char c;

    a = '0';
    while (a <= '7')
    {
        b = a + 1;
        while (b <= '8')
        {
            c = b + 1;
            while (c <= '9')
            {
                write(1, &a, 1);
                write(1, &b, 1);
                write(1, &c, 1);
                if (a != '7' || b != '8' || c != '9')
                    write(1, ", ", 2);
                c++;
            }
            b++;
        }
        a++;
    }
}

Yeni şeyler

  • Nested loop (iç içe döngü) — bir döngünün içinde başka döngü
  • b = a + 1 — b her zaman a'dan büyük başlasın diye
  • "‍, " — virgül + boşluk = 2 byte string
  • != — "eşit değil"
  • || — mantıksal "veya". Biri doğruysa toplam doğru

Son virgül kontrolü

Çıktının son kombinasyonu 789. Ondan sonra virgül olmasın. Bunun için: "789 değilsek virgül koy".

a != '7' || b != '8' || c != '9' = "a 7 değil VEYA b 8 değil VEYA c 9 değil". 789 olduğu an üç koşul da yanlış olur, virgül atlanır.

ex06 ft_print_comb2 void ft_print_comb2(void);

Ne istiyor?

İki tane iki basamaklı sayı çiftleri: 00 01, 00 02, ..., 98 99. İkincisi her zaman birinciden büyük.

void ft_print_comb2(void)
{
    int a;
    int b;
    char first[2];
    char second[2];

    a = 0;
    while (a <= 98)
    {
        b = a + 1;
        while (b <= 99)
        {
            first[0] = (a / 10) + '0';
            first[1] = (a % 10) + '0';
            second[0] = (b / 10) + '0';
            second[1] = (b % 10) + '0';
            write(1, first, 2);
            write(1, " ", 1);
            write(1, second, 2);
            if (a != 98 || b != 99)
                write(1, ", ", 2);
            b++;
        }
        a++;
    }
}

Yeni şeyler

  • char first[2] — 2 elemanlı dizi. first[0] ve first[1]
  • a / 10 — tam sayı bölme. 47/10 = 4 (onlar basamağı)
  • a % 10 — modulo (kalan). 47%10 = 7 (birler basamağı)
  • + '0' — sayıyı karaktere çeviren hile

Sayı→2 karakter dönüşümü

a = 47
a / 10 = 44 + '0' = '4'
a % 10 = 77 + '0' = '7'

first[0] = '4'
first[1] = '7'

write(1, first, 2) → "47"
ex07 ft_putnbr void ft_putnbr(int nb);

Ne istiyor?

Bir int'i ekrana yazdır. Tüm int değerleri desteklenecek: -2147483648 ile 2147483647 arası.

Mantığı

Sayıyı tek seferde yazdıramazsın çünkü write tek byte yazar, sayı çok basamaklı olabilir. Çözüm: recursion (özyineleme). Fonksiyon kendini çağırır.

Mantık: 423 yazdırmak için önce 42'yi yazdır, sonra son rakam 3'ü.

void ft_putnbr(int nb)
{
    char c;

    if (nb == -2147483648)
    {
        write(1, "-2147483648", 11);
        return;
    }
    if (nb < 0)
    {
        write(1, "-", 1);
        nb = -nb;
    }
    if (nb >= 10)
        ft_putnbr(nb / 10);
    c = (nb % 10) + '0';
    write(1, &c, 1);
}

Akış: ft_putnbr(423)

ft_putnbr(423)
├─ nb=423, >=10 ise ft_putnbr(42) çağır
│  ├─ nb=42, >=10 ise ft_putnbr(4) çağır
│  │  └─ nb=4, <10, sadece '4' yazdır
│  └─ '2' yazdır (42%10=2)
└─ '3' yazdır (423%10=3)

Sonuç: 4, 2, 3 → "423"
// neden -2147483648 özel?

int'in en küçük değeri -2147483648. En büyük değeri ise +2147483647 (1 fazlası). Yani -(-2147483648) yapmaya çalışırsan 2147483648 olur ama bu değer int'e sığmaz — overflow olur ve değer yanlış kayar. Bu yüzden onu önceden yakalayıp string olarak yazdırırız.

ex08 ft_print_combn void ft_print_combn(int n);

Ne istiyor?

n adet farklı rakamın artan kombinasyonları. n=2 için 01, 02, ..., 89. n=3 için ex05 gibi.

Mantığı

n döngü gerekecek ama n çalışma anında değişiyor — C'de döngü sayısını dinamik yapamazsın. İki çözüm var:

  1. Recursion ile n derinliğine in
  2. Dizi ile manuel kombinasyon üret

Dizi ile yaklaşım daha sezgisel. Bir nums[] dizisinde mevcut kombinasyonu tut, her seferinde bir sonraki kombinasyona geç.

void ft_print_combn(int n)
{
    int nums[10];
    int i;
    int last;
    char c;

    i = 0;
    while (i < n)
    {
        nums[i] = i;
        i++;
    }
    while (1)
    {
        i = 0;
        while (i < n)
        {
            c = nums[i] + '0';
            write(1, &c, 1);
            i++;
        }
        if (nums[0] == 10 - n)
            break;
        write(1, ", ", 2);
        last = n - 1;
        while (last >= 0 && nums[last] == 9 - (n - 1 - last))
            last--;
        nums[last]++;
        i = last + 1;
        while (i < n)
        {
            nums[i] = nums[i - 1] + 1;
            i++;
        }
    }
}
// strateji

Bu en zor egzersiz. Eşik %50 değil ama yine de bunu son sırada bırak. ex00-ex07'yi yapmadan ex08'i yapma.

// C01Pointer'lar

Bu modülün konusu C dilinin kalbi: pointer'lar. C00'da &c kullandın ama derinine inmedin. C01'de adres ve değer kavramı tam oturacak. Eşik %50.

Bellek modeli (yine)

Pointer'ları anlamak için bellek modelini kafanda canlandırmalısın. Bellek = numaralandırılmış kutular. Her değişken bir kutuda durur, kutunun adresi vardır.

int x = 42;        int *p = &x;

┌─────────┐         ┌─────────┐
│  x      │         │   p     │
│ 0x1000  │ <────── │ 0x2000  │
│  [42]   │         │[0x1000] │
└─────────┘         └─────────┘
                    p, x'i gösterir

Pointer = "başka bir şeyin adresini tutan değişken".

İki kritik operatör

SembolAdıAnlamıÖrnek
&address-of"Bunun adresi nedir?"&x = x'in adresi
*dereference"Bu adreste ne var?"*p = p'nin gösterdiği değer

İkisi birbirinin tersi: *&x = x.

// yıldız iki yüzlü

* sembolü iki farklı anlamda kullanılır:

  • Tanımda (int *p) — "p, pointer tipinde"
  • Kullanımda (*p = 5) — "p'nin gösterdiği yere yaz"

Aynı sembol, farklı bağlam. Bu kafa karıştırıcı ama alışırsın.

ex00 ft_ft void ft_ft(int *nbr);

Ne istiyor?

Bir int pointer'ı al, gösterdiği değeri 42 yap.

void ft_ft(int *nbr)
{
    *nbr = 42;
}

Çağrılışı

int sayi = 0;
ft_ft(&sayi);     // sayi'nin adresini gönder
// şimdi sayi == 42

Neden böyle?

C'de fonksiyonlara değer kopya olarak geçer. Yani void ft_ft(int n) { n = 42; } yazsaydık, sadece kopya değişirdi, dışarıdaki sayı etkilenmezdi.

Adresi geçince, fonksiyon gerçek değişkenin bellekteki yerine ulaşıyor ve oraya yazıyor. Pointer'ın varlık nedeni bu.

ex01 ft_ultimate_ft void ft_ultimate_ft(int *********nbr);

Ne istiyor?

9 katmanlı pointer al. En sondaki int'e 42 yaz. Şaka gibi ama gerçek.

Mantığı

Pointer'a pointer'a pointer... 9 kez. x'e ulaşmak için 9 kez dereferans yap.

void ft_ultimate_ft(int *********nbr)
{
    *********nbr = 42;
}

Anlam

Tanımdaki ********* = "9 katmanlı pointer tipi". Kullanımdaki *********nbr = "9 kez dereferans et, en içteki değere ulaş".

Bu egzersiz aslında pointer kavramının özeti. 1 katman da olsa 9 katman da olsa mantık aynı: yıldız sayısı kadar dereferans.

ex02 ft_swap void ft_swap(int *a, int *b);

Ne istiyor?

İki int'in değerini takas et. Adresleri verilir.

void ft_swap(int *a, int *b)
{
    int temp;

    temp = *a;
    *a = *b;
    *b = temp;
}

Neden temp?

Geçici depo lazım. Eğer *a = *b deyip sonra *b = *a dersen, ilk değeri kaybedersin (ikisi aynı olur).

Adım adım

Başlangıç: *a=5, *b=10

temp = *a    → temp=5, *a=5, *b=10
*a = *b      → temp=5, *a=10, *b=10
*b = temp    → temp=5, *a=10, *b=5

Sonuç: takas tamam ✓
ex03 ft_div_mod void ft_div_mod(int a, int b, int *div, int *mod);

Ne istiyor?

a'yı b'ye böl. Sonucu *div'e, kalanı *mod'a yaz.

void ft_div_mod(int a, int b, int *div, int *mod)
{
    *div = a / b;
    *mod = a % b;
}

Neden böyle bir tasarım?

Bir fonksiyon C'de tek değer döndürür (return ile). Ama burada iki sonuç lazım — bölüm ve kalan. Pointer kullanarak bu sınırı aşıyoruz.

ex04 ft_ultimate_div_mod void ft_ultimate_div_mod(int *a, int *b);

Ne istiyor?

a'yı b'ye böl, ama sonuçları yeni yerlere değil, a ve b'nin kendisine yaz.

void ft_ultimate_div_mod(int *a, int *b)
{
    int div;
    int mod;

    div = *a / *b;
    mod = *a % *b;
    *a = div;
    *b = mod;
}
// tuzak

Şöyle yazsan yanlış olur:

*a = *a / *b;     // *a değişti
*b = *a % *b;     // artık *a yanlış değer

İkinci satırda *a ilk satırda değiştiği için doğru kalan hesaplanamaz. Önce iki sonucu da hesapla, sonra ata.

ex05 ft_putstr void ft_putstr(char *str);

Ne istiyor?

Bir string'i ekrana yazdır. Standart C'deki puts gibi ama satır sonu eklemez.

#include <unistd.h>

void ft_putstr(char *str)
{
    int i;

    i = 0;
    while (str[i] != '\0')
    {
        write(1, &str[i], 1);
        i++;
    }
}

Yeni şeyler

  • char *str — string'in başlangıç adresi (string = char dizisi)
  • str[i] — i. karakter. *(str + i) ile aynı
  • '\0' — null terminator, stringin sonu
  • &str[i] — i. karakterin adresi (write için)

Mantık: i=0'dan başla, null terminator'a kadar her karakteri yazdır.

ex06 ft_strlen int ft_strlen(char *str);

Ne istiyor?

Bir stringin uzunluğunu say (null hariç).

int ft_strlen(char *str)
{
    int i;

    i = 0;
    while (str[i] != '\0')
        i++;
    return (i);
}

Null terminator'a kadar say. "hello" için 5 döner. Boş string için 0 döner.

return fonksiyondan değer döndürür ve çıkar. Norm parantez ister: return (i);.

ex07 ft_rev_int_tab void ft_rev_int_tab(int *tab, int size);

Ne istiyor?

Bir int dizisini ters çevir. [1,2,3,4,5][5,4,3,2,1].

void ft_rev_int_tab(int *tab, int size)
{
    int i;
    int temp;

    i = 0;
    while (i < size / 2)
    {
        temp = tab[i];
        tab[i] = tab[size - 1 - i];
        tab[size - 1 - i] = temp;
        i++;
    }
}

Mantığı

İlk eleman ↔ son eleman, ikinci ↔ sondan ikinci, ortaya kadar git. Diziyi iki taraftan sıkıştırarak ilerle.

size / 2 önemli — orta noktayı geçersen tekrar takas yapıp eski hâline çevirirsin.

ex08 ft_sort_int_tab void ft_sort_int_tab(int *tab, int size);

Ne istiyor?

Bir int dizisini artan sıraya koy.

Yöntem: Bubble Sort

En basit sıralama: yan yana iki elemana bak, yanlış sıradaysa takas et. Sürekli baştan başla. Bir tur boyunca hiç takas yapmadıysan dizi sıralanmıştır.

void ft_sort_int_tab(int *tab, int size)
{
    int i;
    int temp;
    int swapped;

    swapped = 1;
    while (swapped == 1)
    {
        swapped = 0;
        i = 0;
        while (i < size - 1)
        {
            if (tab[i] > tab[i + 1])
            {
                temp = tab[i];
                tab[i] = tab[i + 1];
                tab[i + 1] = temp;
                swapped = 1;
            }
            i++;
        }
    }
}

Örnek: [3, 1, 2]

Tur 1:
  3>1 takas → [1,3,2], swapped=1
  3>2 takas → [1,2,3], swapped=1

Tur 2:
  1>2? hayır
  2>3? hayır
  swapped=0, dur

Sonuç: [1,2,3] ✓

// C02String Manipülasyonu

Bu modülde string'lerle yoğun çalışacaksın. 13 egzersiz, eşik %50. Yani hepsini yapmak zorunda değilsin ama hangilerini yapacağına stratejik karar ver.

Subject "reproduce the behavior of strXXX" diyor — yani standart C kütüphanesindeki orijinal fonksiyonların aynısını yaz. Önce man strcpy gibi komutlarla davranışı oku, sonra yaz.

ex00 ft_strcpy char *ft_strcpy(char *dest, char *src);

Ne istiyor?

src string'ini dest'e kopyala. dest'i döndür.

char *ft_strcpy(char *dest, char *src)
{
    int i;

    i = 0;
    while (src[i] != '\0')
    {
        dest[i] = src[i];
        i++;
    }
    dest[i] = '\0';
    return (dest);
}

Kritik detaylar

  • Döngü null'ı kopyalamadan biter — elle eklemen gerek
  • return (dest); — orijinal strcpy bunu döndürür
  • dest'in yeterli yere sahip olduğu varsayılır (güvensiz!)
// güvensizlik

Eğer dest 5 byte ama src 100 byte ise, fonksiyon dest'in sınırını aşar ve başka belleği bozar. Bu strcpy'nin ünlü problemidir. Gerçek hayatta strlcpy kullanılır.

ex01 ft_strncpy char *ft_strncpy(char *dest, char *src, unsigned int n);

Ne istiyor?

strcpy gibi ama en fazla n karakter. src n'den kısaysa kalan yerleri null ile doldur.

char *ft_strncpy(char *dest, char *src, unsigned int n)
{
    unsigned int i;

    i = 0;
    while (i < n && src[i] != '\0')
    {
        dest[i] = src[i];
        i++;
    }
    while (i < n)
    {
        dest[i] = '\0';
        i++;
    }
    return (dest);
}

İki döngü

  • Birinci — src bitene VEYA n'e ulaşana kadar kopyala
  • İkinci — n'e ulaşmadıysak kalanı null doldur

Yeni şey: unsigned int

Sadece pozitif int. Boyut/uzunluk için kullanılır çünkü negatif olmaz. && = mantıksal "ve".

ex02 ft_str_is_alpha int ft_str_is_alpha(char *str);

Ne istiyor?

String sadece harf içeriyorsa 1, değilse 0 döndür. Boş string için 1.

int ft_str_is_alpha(char *str)
{
    int i;

    i = 0;
    while (str[i] != '\0')
    {
        if (!((str[i] >= 'a' && str[i] <= 'z')
            || (str[i] >= 'A' && str[i] <= 'Z')))
            return (0);
        i++;
    }
    return (1);
}

Mantığı

Karakter 'a'-'z' arasında VEYA 'A'-'Z' arasında → harf demek. Bir tane bile harf olmayan bulursan hemen 0 döndür (early return).

Boş string için döngü hiç çalışmaz, direkt 1 döner. Bu da subject'in istediği davranış.

ex03-06 Diğer is_* fonksiyonları // numeric, lowercase, uppercase, printable

Hepsi aynı şablonda — sadece kontrol koşulu değişir.

ft_str_is_numeric

if (str[i] < '0' || str[i] > '9')
    return (0);

ft_str_is_lowercase

if (str[i] < 'a' || str[i] > 'z')
    return (0);

ft_str_is_uppercase

if (str[i] < 'A' || str[i] > 'Z')
    return (0);

ft_str_is_printable

Yazdırılabilir karakterler: ASCII 32-126 arası.

if (str[i] < 32 || str[i] > 126)
    return (0);

Bu beş egzersiz toplam ~20 dakikada biter. Hızlı puan.

ex07-08 ft_strupcase & ft_strlowcase char *ft_strupcase(char *str);

Ne istiyor?

Tüm harfleri büyüt (veya küçült).

char *ft_strupcase(char *str)
{
    int i;

    i = 0;
    while (str[i] != '\0')
    {
        if (str[i] >= 'a' && str[i] <= 'z')
            str[i] = str[i] - 32;
        i++;
    }
    return (str);
}

Neden -32?

ASCII'de küçük harfler büyüklerinden tam 32 fazla. 'a'=97, 'A'=65. Fark 32. Küçükten 32 çıkar = büyük olur.

strlowcase tersi: +32, kontrol 'A'-'Z'.

// daha okunaklı

str[i] - 32 yerine str[i] - 'a' + 'A' da yazabilirsin. Sayısal sonuç aynı (97-32=65) ama mantığı gösteriyor.

ex09 ft_strcapitalize char *ft_strcapitalize(char *str);

Ne istiyor?

Her kelimenin ilk harfini büyüt, gerisi küçük. Kelime = alfanumerik karakter dizisi.

hi, how are you? 42words forty-two; fifty+and+one
↓
Hi, How Are You? 42words Forty-Two; Fifty+And+One

Dikkat: 42words aynen kalıyor çünkü kelime bir rakamla başladı, 'w' kelimenin başı sayılmıyor.

char *ft_strcapitalize(char *str)
{
    int i;
    int yeni_kelime;

    i = 0;
    yeni_kelime = 1;
    while (str[i] != '\0')
    {
        if ((str[i] >= 'a' && str[i] <= 'z')
            || (str[i] >= 'A' && str[i] <= 'Z')
            || (str[i] >= '0' && str[i] <= '9'))
        {
            if (yeni_kelime == 1 && str[i] >= 'a' && str[i] <= 'z')
                str[i] = str[i] - 32;
            else if (yeni_kelime == 0 && str[i] >= 'A' && str[i] <= 'Z')
                str[i] = str[i] + 32;
            yeni_kelime = 0;
        }
        else
            yeni_kelime = 1;
        i++;
    }
    return (str);
}

Bayrak (flag) mantığı

yeni_kelime bir bayrak — bir sonraki alfanumerik karakter kelime başı mı?

  • Başlangıçta 1 (ilk karakter kelime başı)
  • Karakter alfanumerikse → kelime başı işle, sonra bayrağı 0 yap
  • Karakter alfanumerik değilse (boşluk, virgül, vs) → bayrağı 1 yap

42words'te '4' alfanumerik, kelime başı, ama harf değil. Bayrak 0 olur. Sonra 'w' geldiğinde kelime başı sayılmaz, küçük kalır.

ex10 ft_strlcpy unsigned int ft_strlcpy(char *dest, char *src, unsigned int size);

Ne istiyor?

strncpy'nin güvenli versiyonu:

  • En fazla size - 1 karakter kopyala
  • Sona her zaman '\0' koy
  • src'nin uzunluğunu döndür (kopyalanan değil)
  • size 0 ise hiç yazma, sadece src_len döndür
unsigned int ft_strlcpy(char *dest, char *src, unsigned int size)
{
    unsigned int i;
    unsigned int src_len;

    src_len = 0;
    while (src[src_len] != '\0')
        src_len++;
    if (size == 0)
        return (src_len);
    i = 0;
    while (src[i] != '\0' && i < size - 1)
    {
        dest[i] = src[i];
        i++;
    }
    dest[i] = '\0';
    return (src_len);
}

Neden src_len döndürüyor?

Çağıran kod, src_len >= size ise "yer yetmedi, kesildi" anlayabilsin diye. Eğer src_len < size ise tamamı sığdı.

ex11 ft_putstr_non_printable void ft_putstr_non_printable(char *str);

Ne istiyor?

String yazdır. Yazdırılamayan karakterleri (ASCII 0-31, 127+) hex formatında göster: \xx.

Girdi:  Hello\nHow are you?
        (gerçek bir newline var)

Çıktı:  Hello\0aHow are you?
        (newline yerine \0a yazılı)
void ft_print_hex(unsigned char c)
{
    char hex[] = "0123456789abcdef";

    write(1, "\\", 1);
    write(1, &hex[c / 16], 1);
    write(1, &hex[c % 16], 1);
}

void ft_putstr_non_printable(char *str)
{
    int i;

    i = 0;
    while (str[i] != '\0')
    {
        if (str[i] >= 32 && str[i] <= 126)
            write(1, &str[i], 1);
        else
            ft_print_hex((unsigned char)str[i]);
        i++;
    }
}

Yeni şeyler

  • char hex[] = "0123456789abcdef" — lookup tablosu. hex[10] = 'a'
  • c / 16 ve c % 16 — bir byte'ı iki hex basamağa böler
  • "\\" — ekrana tek \ basmak için (escape)
  • (unsigned char)c — tip dönüşümü (cast). signed char negatif olabilir, unsigned 0-255

Hex hesabı

'\n' = 10 (ASCII)
10 / 16 = 0   → hex[0] = '0'
10 % 16 = 10  → hex[10] = 'a'
Çıktı: \0a ✓
ex12 ft_print_memory void *ft_print_memory(void *addr, unsigned int size);

Ne istiyor?

Bir bellek bölgesini "hex dump" formatında yazdır. xxd komutu gibi:

000000010a161f40: 426f 6e6a 6f75 7220 6c65 7320 616d 696e Bonjour les amin

Üç sütun:

  1. 16-haneli hex adres + :
  2. 16 byte'lık içerik (her 2 byte arası boşluk)
  3. Yazdırılabilir hâli (yazdırılamayanlar nokta)

void * nedir?

Tipsiz pointer. "Herhangi bir tipte adres" demek. Kullanmadan önce cast lazım:

unsigned char *p = (unsigned char *)addr;
// şimdi p'yi byte byte gezebiliriz

Bu egzersiz uzun, eşik geçmek için zorunlu değil. Aklında olsun, atlayabilirsin.

// strateji

C02'de eşik %50. Hızlı yol: ex00, ex01, ex02-06 (5 kontrol fonksiyonu), ex07, ex08 yap. Bu 9 egzersiz = %50+ tamamen. Ardından zorlular için zaman varsa.

// C03String Karşılaştırma & Birleştirme

C02'nin direkt devamı. C02 string'leri kopyaladı/dönüştürdü, C03 karşılaştıracak ve birleştirecek. Sadece 6 egzersiz, hepsi standart C kütüphanesinden.

Bu modülde eşik bilgisi yok — hepsini tamamlamak hedef. Ama egzersizler kısa ve birbirine benzer, hızlı geçer.

ex00 ft_strcmp int ft_strcmp(char *s1, char *s2);

Ne istiyor?

strcmp'in davranışını taklit et. İki stringi karakter karakter karşılaştır:

  • Eşitse → 0
  • s1, s2'den önce gelirse (alfabetik) → negatif
  • s1, s2'den sonra gelirse → pozitif

Aslında dönen değer iki farklı karakterin ASCII farkıdır.

int ft_strcmp(char *s1, char *s2)
{
    int i;

    i = 0;
    while (s1[i] != '\0' && s1[i] == s2[i])
        i++;
    return (s1[i] - s2[i]);
}

Mantığı

  • Karakterler eşit olduğu sürece ilerle
  • Bir fark bulunca veya s1 bitince dur
  • O an ki iki karakterin farkını döndür

Örnekler

ft_strcmp("abc", "abc")  → 'a'-'a' = 0 (eşit)
ft_strcmp("abc", "abd")  → 'c'-'d' = -1
ft_strcmp("abc", "ab")   → 'c'-'\0' = 99
ft_strcmp("ab", "abc")   → '\0'-'c' = -99
// neden döngü 'a' bitince durur ama 'b' bitince durmaz?

Döngü koşulu s1[i] != '\0' && s1[i] == s2[i]. Eğer s1 biterse s1[i] = '\0' olur, döngü durur. Sonra s1[i] - s2[i] = '\0' - s2[i] hesaplanır — eğer s2 hâlâ devam ediyorsa negatif çıkar (s1 daha kısa). Eğer s2 de bitti ise '\0' - '\0' = 0 (eşitler).

ex01 ft_strncmp int ft_strncmp(char *s1, char *s2, unsigned int n);

Ne istiyor?

strcmp gibi ama en fazla n karakter kıyasla. n=0 ise hiç bakma, 0 döndür.

int ft_strncmp(char *s1, char *s2, unsigned int n)
{
    unsigned int i;

    i = 0;
    while (i < n && s1[i] != '\0' && s1[i] == s2[i])
        i++;
    if (i == n)
        return (0);
    return ((unsigned char)s1[i] - (unsigned char)s2[i]);
}

Yeni detaylar

  • i < n kontrolü eklendi — n karakter kıyaslandıktan sonra dur
  • if (i == n) return (0) — n karakter eşit geldiyse stringler eşit (n kadarı için)
  • (unsigned char) cast'i — bazı sistemlerde char signed olur, negatif değerler hatalı kıyaslama yapar

Örnekler

ft_strncmp("abcdef", "abcxxx", 3) → 0 (ilk 3 eşit)
ft_strncmp("abcdef", "abcxxx", 4) → 'd'-'x' = negatif
ft_strncmp("ab", "abc", 5)       → '\0'-'c' = negatif
ex02 ft_strcat char *ft_strcat(char *dest, char *src);

Ne istiyor?

src string'ini dest'in sonuna ekle. dest'i döndür.

dest = "Merhaba "
src  = "dünya"
ft_strcat(dest, src)
→ dest = "Merhaba dünya"
char *ft_strcat(char *dest, char *src)
{
    int i;
    int j;

    i = 0;
    while (dest[i] != '\0')
        i++;
    j = 0;
    while (src[j] != '\0')
    {
        dest[i] = src[j];
        i++;
        j++;
    }
    dest[i] = '\0';
    return (dest);
}

İki adım

  1. İlk döngü — dest'in sonunu (null'ı) bul
  2. İkinci döngü — src'yi oraya kopyala
  3. Sona null koy
// güvenlik

strcpy gibi strcat de dest'in yeterli yeri olduğunu varsayar. Kontrol etmez. Eğer dest 10 byte ama "Merhaba "+"dünya" 14 byte ediyorsa, sınırı aşar.

ex03 ft_strncat char *ft_strncat(char *dest, char *src, unsigned int nb);

Ne istiyor?

strcat gibi ama src'den en fazla nb karakter ekle. Sona her zaman null koy.

char *ft_strncat(char *dest, char *src, unsigned int nb)
{
    unsigned int i;
    unsigned int j;

    i = 0;
    while (dest[i] != '\0')
        i++;
    j = 0;
    while (src[j] != '\0' && j < nb)
    {
        dest[i] = src[j];
        i++;
        j++;
    }
    dest[i] = '\0';
    return (dest);
}

strcat'ten tek farkı

Kopyalama döngüsünde j < nb koşulu eklendi. nb karakteri aştın mı, dur.

Örnekler

dest = "Hello "
src  = "World!!!"
nb   = 5

→ dest = "Hello World" (sadece 5 karakter eklendi)
ex04 ft_strstr char *ft_strstr(char *str, char *to_find);

Ne istiyor?

str içinde to_find ilk nerede geçiyor — o noktanın adresini döndür. Yoksa NULL. to_find boş ise str'nin başını döndür.

ft_strstr("hello world", "world")
→ "world" başlangıcına pointer (str+6)
char *ft_strstr(char *str, char *to_find)
{
    int i;
    int j;

    if (to_find[0] == '\0')
        return (str);
    i = 0;
    while (str[i] != '\0')
    {
        j = 0;
        while (str[i + j] == to_find[j] && to_find[j] != '\0')
            j++;
        if (to_find[j] == '\0')
            return (&str[i]);
        i++;
    }
    return (0);
}

Mantığı

İki içe iç döngü:

  • Dış döngü — str'nin her pozisyonunu tara
  • İç döngü — o pozisyondan başlayarak to_find ile eşleşiyor mu kontrol et
  • İç döngü to_find'ın sonuna kadar gittiyse → bulundu, adresi döndür

Yeni detaylar

  • &str[i] — i. karakterin adresi. Bulunan yerin başlangıç adresi
  • return (0) — NULL döndür ("bulunamadı"). 0 ile NULL aynı şey C'de
  • Boş to_find özel durumu — strstr orijinali bunu böyle ele alır

Örnek akış: ft_strstr("ababc", "abc")

i=0: str="ababc", to_find="abc"
       j=0: 'a'=='a' ✓
       j=1: 'b'=='b' ✓
       j=2: 'a'!='c' ✗ → eşleşmedi
i=1: str="babc"
       j=0: 'b'!='a' ✗
i=2: str="ababc"... aslında "abc" arıyor
       j=0: 'a'=='a' ✓
       j=1: 'b'=='b' ✓
       j=2: 'c'=='c' ✓
       j=3: to_find[3]='\0' → bulundu!
       return &str[2] = "abc" başlangıcı
ex05 ft_strlcat unsigned int ft_strlcat(char *dest, char *src, unsigned int size);

Ne istiyor?

strcat'in güvenli versiyonu. Davranışı tam okuman gerek (man strlcat):

  • dest sonuna src'yi ekle
  • Toplam boyut size'ı geçmesin
  • Sona her zaman null
  • Döndürülen değer: dest'in başlangıç uzunluğu + src uzunluğu (yani oluşturmak istediği toplam uzunluk)

Eğer size, dest_len'den küçük veya eşitse → hiç ekleme yapma, sadece size + src_len döndür.

unsigned int ft_strlen_local(char *s)
{
    unsigned int i;

    i = 0;
    while (s[i] != '\0')
        i++;
    return (i);
}

unsigned int ft_strlcat(char *dest, char *src, unsigned int size)
{
    unsigned int dest_len;
    unsigned int src_len;
    unsigned int i;

    dest_len = ft_strlen_local(dest);
    src_len = ft_strlen_local(src);
    if (size <= dest_len)
        return (size + src_len);
    i = 0;
    while (src[i] != '\0' && dest_len + i < size - 1)
    {
        dest[dest_len + i] = src[i];
        i++;
    }
    dest[dest_len + i] = '\0';
    return (dest_len + src_len);
}

Adım adım

  1. dest ve src uzunluklarını hesapla
  2. Eğer size ≤ dest_len → hiç ekleme, sadece size + src_len döndür (özel durum)
  3. Aksi halde, src'yi dest'in sonuna ekle ama size - 1'i geçme (null için yer bırak)
  4. Sona null koy
  5. dest_len + src_len döndür
// neden bu kadar karmaşık?

strlcat'in davranışı kafa karıştırıcı çünkü çağıranın ne kadar yer ihtiyacı olduğunu bilmesi için tasarlanmış. Eğer dönen değer ≥ size ise, "kesildi, tam yer için dönen_değer + 1 byte lazım" demek. man strlcat okumak zorunlu.

// ★Cheat sheet

Temel kalıplar

String boyunca dolaşma

i = 0;
while (str[i] != '\0')
{
    // str[i] ile bir şey yap
    i++;
}

Tek karakter yazdırma

char c = 'A';
write(1, &c, 1);

String yazdırma

write(1, "hello", 5);     // & yok, dizi adı zaten adres

Sayı → karakter

char c = n + '0';     // 0-9 arası sayı için

2 takas

temp = a;
a = b;
b = temp;

ASCII hap bilgileri

KarakterASCII
'\0'0
'\n' (newline)10
' ' (boşluk)32
'0'48
'9'57
'A'65
'Z'90
'a'97
'z'122

Pointer tablosu

İfadeTipteKullanımda
int xtam sayıx'in değeri
int *pint gösteren pointer
&xx'in adresi
*pp'nin gösterdiği değer
p[i]*(p+i) ile aynı

Limit değerler

TipMinMax
int-21474836482147483647
unsigned int04294967295
char (signed)-128127
unsigned char0255

// !Yaygın hatalar & tuzaklar

// 01

Null terminator unutmak. strcpy/strcat gibi fonksiyonlarda döngü null'ı kopyalamadan biter. Sen elle eklemen gerek: dest[i] = '\0';

// 02

Tek tırnak vs çift tırnak. 'a' bir char (97). "a" bir string (2 byte: 'a', '\0'). write(1, 'a', 1) yazarsan derleyici hata verir.

// 03

INT_MIN sorunu. ft_putnbr'da -2147483648'i -nb ile pozitife çeviremezsin (overflow). Özel durum yaz.

// 04

Pointer'ı bağlamadan kullanmak. int *p; *p = 5; derlenir ama çalıştırınca segfault verir. p hangi adresi gösteriyor belli değil — rastgele belleğe yazıyorsun.

// 05

Adres yerine değer geçirmek. ft_swap için ft_swap(x, y) yanlış, ft_swap(&x, &y) doğru. Fonksiyon adresi alıyor.

// 06

signed char tuzağı. Karakter karşılaştırma yaparken bazı sistemlerde char negatif olabilir. (unsigned char)c ile cast et.

// 07

Norminette hataları. Her fonksiyon ≤25 satır. Değişkenler fonksiyonun başında. Tab kullan, space değil. 42 header her dosyada. norminette dosya.c komutuyla kontrol et.

// 08

Yasak fonksiyon kullanmak. Subject "Allowed functions: write" diyorsa sadece write kullanabilirsin. printf, malloc, strlen — hepsi yasak. Yasak fonksiyon = -42.

// 09

Egzersiz sırasını atlatmak. Subject "Kolay yapılmazsa zoru sayılmaz" diyor. ex02 çalışmıyorsa, ex05 mükemmel olsa bile sıfır puan.

// 10

Ekstra dosya bırakmak. Subject sadece istenen dosyaları bekler. Test main'in, .o dosyalar, README — hiçbirini submit etme. Sadece istenen .c'ler.

// end of notes   ·   happy coding