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.
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 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?
| Bayrak | Anlamı |
|---|---|
-Wall | "Warning all" — tüm bilinen uyarıları aç |
-Wextra | Ekstra uyarılar, daha sıkı |
-Werror | Uyarı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ı.
- 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
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).
ft_putchar — sen koyuyorsun. ft_ 42'nin geleneği, "function" demek. Standart C fonksiyonlarıyla karışmasın diye.
(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).
{ ... } — süslü parantezler arasındaki kısım fonksiyonun yapacağı iş. Tüm ifadeler burada.
; — 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ı.
3 parametre
Nereye yazacağını söyleyen bir tam sayı. Standart kanallar:
| fd | Anlamı |
|---|---|
0 | stdin — klavye girişi (write için kullanılmaz) |
1 | stdout — normal ekran çıktısı |
2 | stderr — hata çıktısı (yine ekran ama farklı kanal) |
C00'da hep 1 yazacaksın çünkü ekrana yazıyorsun.
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
Yazılacak byte sayısı. "hello" için 5. "\n" için 1.
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"
| İfade | Tip | Boyut | İçerik |
|---|---|---|---|
'a' | char | 1 byte | 97 (ASCII) |
"a" | char dizisi | 2 byte | 'a', '\0' |
Tek tırnak = bir karakter. Çift tırnak = string. Asla karıştırma.
ASCII tablosundaki kritik aralıklar
| Karakter | ASCII başlangıç | ASCII bitiş |
|---|---|---|
| Kontrol karakterleri | 0 | 31 |
| Boşluk (' ') | 32 | 32 |
| Rakamlar ('0'–'9') | 48 | 57 |
| Büyük harfler ('A'–'Z') | 65 | 90 |
| Küçük harfler ('a'–'z') | 97 | 122 |
| Yazdırılabilir | 32 | 126 |
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
| İfade | Tip | Anlamı |
|---|---|---|
argv | char ** | String'lerin dizisi |
argv[i] | char * | i. string |
argv[i][j] | char | i. stringin j. karakteri |
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.
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>—writefonksiyonu burada tanımlı, dahil etmezsek derleyici tanımazvoid— fonksiyon değer döndürmez, sadece iş yapar(char c)— dışarıdan bir karakter alır, içeridecder&c— c'nin adresi. write değer değil adres ister1, ..., 1— 1. kanal (stdout), 1 byte yaz
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ırakmaktansavoidyazmak C'de daha doğruchar c;— değişken tanımı. Henüz değer yokc = 'a';— değişkene değer atama. 42 Norm tanım ve atamayı ayırmanı önerirwhile (c <= 'z')— c, z'den küçük veya eşit olduğu sürecec++— 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++;
}
}
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ışırelse— if yanlışsa burası çalışırn < 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]vefirst[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 = 4 → 4 + '0' = '4'
a % 10 = 7 → 7 + '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"
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:
- Recursion ile n derinliğine in
- 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++;
}
}
}
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
| Sembol | Adı | 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.
* 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;
}
Şö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!)
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'.
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 - 1karakter 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 / 16vec % 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:
- 16-haneli hex adres +
: - 16 byte'lık içerik (her 2 byte arası boşluk)
- 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.
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
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 < nkontrolü eklendi — n karakter kıyaslandıktan sonra durif (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
- İlk döngü — dest'in sonunu (null'ı) bul
- İkinci döngü — src'yi oraya kopyala
- Sona null koy
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ıç adresireturn (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
- dest ve src uzunluklarını hesapla
- Eğer size ≤ dest_len → hiç ekleme, sadece
size + src_lendöndür (özel durum) - Aksi halde, src'yi dest'in sonuna ekle ama
size - 1'i geçme (null için yer bırak) - Sona null koy
dest_len + src_lendöndür
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
| Karakter | ASCII |
|---|---|
'\0' | 0 |
'\n' (newline) | 10 |
' ' (boşluk) | 32 |
'0' | 48 |
'9' | 57 |
'A' | 65 |
'Z' | 90 |
'a' | 97 |
'z' | 122 |
Pointer tablosu
| İfade | Tipte | Kullanımda |
|---|---|---|
int x | tam sayı | x'in değeri |
int *p | int gösteren pointer | — |
&x | — | x'in adresi |
*p | — | p'nin gösterdiği değer |
p[i] | — | *(p+i) ile aynı |
Limit değerler
| Tip | Min | Max |
|---|---|---|
int | -2147483648 | 2147483647 |
unsigned int | 0 | 4294967295 |
char (signed) | -128 | 127 |
unsigned char | 0 | 255 |
// !Yaygın hatalar & tuzaklar
Null terminator unutmak. strcpy/strcat gibi fonksiyonlarda döngü null'ı kopyalamadan biter. Sen elle eklemen gerek: dest[i] = '\0';
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.
INT_MIN sorunu. ft_putnbr'da -2147483648'i -nb ile pozitife çeviremezsin (overflow). Özel durum yaz.
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.
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.
signed char tuzağı. Karakter karşılaştırma yaparken bazı sistemlerde char negatif olabilir. (unsigned char)c ile cast et.
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.
Yasak fonksiyon kullanmak. Subject "Allowed functions: write" diyorsa sadece write kullanabilirsin. printf, malloc, strlen — hepsi yasak. Yasak fonksiyon = -42.
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.
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