- Tarihçe
- Yaygınlık
- Neden C Öğrenmelisiniz?
- Merhaba Dünya
- The C Programming Language
- Fahrenhayt-Santigrad dönüşümü
- char: Karakter veri tipi
- ASCII Tablo
- Ayrıntılar
- Girdiyi Çıktıya kopyala
- scanf
- Zorunlu Uyarı
- Veri Girişi/Çıkışı
- Standart Girdi/Çıktı modeli
- Yönlendirme
- Kabuk
- Örnekler
- main fonksiyonunun dönüş değeri
- cat
- EOF
- getchar ve girdi sonu
- scanf ve girdi sonu
- scanf dönüş değeri
- getchar/scanf ve girdi tamponu
- |: Borulama
- getchar tekrar
- Karakter sayısı
- Satır sayısı
- if
- Kelime istatistiği
- Karakter istatistiği
- Diziler
- Fonksiyonlar
- Değerle çağırmak
- Dizgiler
- Dizgi veri tipi
- strlen
- Veri tipleri
- Mantıksal veri tipi
- Mantıksal doğruluk/yanlışlık
- Sayı sabitleri
- Dizgi sabitleri
- Ön ekler
- Son ekler
- Enum sabitler
- Tanımlayıcılar
- Artık yıl (Leap year)
- Short circuit
- Tip dönüşümleri
- "Casting"
- atoi
- lower
- Arttırma/Azaltma operatörleri
- squeeze
- strcat
- Üçlü (ternary) ifade işleci
- Operatör Önceliği
- Akış denetimi
- Fonksiyonlar ve Program organizasyonu
- Kapsam
- İşaretçiler
Tarihçe
1972, AT&T Bell laboratuvarları
Dennis Ritchie (Ken Thompson, Brian Kernighan)
Unix araçlarının ve işletim sisteminin gerçeklenmesi
Yaygınlık
2022 itibarıyla 50 yaşında bir dil
TIOBE indeksine göre 2022 Şubat ayı itibarıyla 2'nci sırada
Bir yıl önce (2021) 1'nci sırada
Neden C Öğrenmelisiniz?
Ustalaşmanız gerekmiyor, ama en azından temel okur/yazarlık seviyesinde öğrenmeniz gerekiyor
"Lingua Franca"
C Programlama dili günümüzde kullanılan bilgisayar mimarisini en "gerçekçi" modelleyen dil
Mimari: CPU, bellek, girdi/çıktı
Gerçekçi? Minimal soyutlama, donanıma ("metal"e) yakınlık
Geleceği bilemiyoruz (Kuantum Hesaplama vs)
Mimaride köklü bir değişiklik olursa bu argümanın geçerliliği azalır
Sonuç: C'yi öğrendiğinizde günümüzün bilgisayar mimarisine daha fazla hakim olacaksınız
Donanım kısıtları nedir?
Başarımı iyileştirmek için neler yapılabilir?
(Veya) Başarımı iyileştiren bir düzenleme bunu nasıl başarıyor?
Falanca sorun neden bir güvenlik sorunu?
İşletim sistemleri nasıl gerçekleniyor?
Dinamik programlama dilleri arka planda nasıl gerçekleniyor?
Tüm bu soruları cevaplayabilmek sizi daha iyi bir programcı yapar
Merhaba Dünya
01 #include <stdio.h>
02
03 int main(void)
04 {
05 printf("Hello, World!\n");
06
07 return 0;
08 }
Derle ve çalıştır
cc -o helloworld helloworld.c
./helloworld
Hello, World!
cc
: Sisteminizde kurulu C derleyicisini (C Compiler) gösteren bir sembolik linkLinux'ta genel olarak
gcc
,clang
vb seçenekler de var-o
: Derleme sonucunda üretilen çalıştırılabilir dosyanın adı
cc helloworld.c
./a.out
Hello, World!
-o
ile çıktı program verilmezse öntanımlı olaraka.out
adında bir dosya üretilirTarihsel olarak seçilen bir isim
a.out
? "Assembler Output", ama çıktı "Assembler" formatında değilÇalıştırılabilir dosya formatı işletim sistemine bağlı olarak değişir
Linux'ta ELF (Executable and Linkable Format)
Windows'da PE (Portable Executable)
Kaçış karakteri | Açıklama |
---|---|
\n | Yeni satır (newline) |
\t | Sekme (tab) |
\r | Satır başı (carriage return) |
\0 | NUL karakter |
\\ | \ karakterinin kendisi |
The C Programming Language
Brian Kernighan ve Dennis Ritchie tarafından yazılmış "efsane" kitap
Teknik literatürde altın referans
"Hello, World!" bu kitapla başlayan bir gelenek
Her programcının okuması gereken 3 kaynak istense biri bu
Şimdi bu kitaptan örneklerle C programlama dilinde gezineceğiz
Fahrenhayt-Santigrad dönüşümü
0, 20, 40, ..., 300
fahrenhayt sıcaklıklık değerlerinin santigrad karşılıklarını görüntüle
while
#include <stdio.h>
/* print Fahrenheit-Celsius table for fahr = 0, 20, ..., 300 */
int main(void)
{
int fahr, celsius;
int lower, upper, step;
lower = 0; /* lower limit of temperature scale */
upper = 300; /* upper limit */
step = 20; /* step size */
fahr = lower;
while (fahr <= upper) {
celsius = 5 * (fahr-32) / 9;
printf("%d\t%d\n", fahr, celsius);
fahr = fahr + step;
}
return 0;
}
Tip | Açıklama |
---|---|
char | Karakter |
short | Kısa tamsayı, int |
long | Uzun tamsayı, long int |
float | Gerçel sayı |
double | Çift hassasiyetli gerçel sayı |
Bakın bu örnekte sık karşılaşılan bir patern var
önce "bir şey"i ilkle
döngü başında "bir şey"i sına: yanlışsa döngüyü sonlandır, doğruysa dön
her döngü sonunda "bir şey"i değiştir
i'yi ilkle
i falan koşulu sağladığı sürece
...
i'yi değiştir
yani
i = lower;
while (i <= upper) {
...
i = i + step;
}
for
#include <stdio.h>
/* print Fahrenheit-Celsius table */
int main(void)
{
int fahr;
for (fahr = 0; fahr <= 300; fahr = fahr + 20)
printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
return 0;
}
Sabitler
Bu programda 3 sabit var: bu sabitleri koda gömmek doğru bir pratik değil
Başlangıç değeri (alt sınır):
0
Bitiş değeri (üst sınır):
300
Artış miktarı (adım):
20
#include <stdio.h>
#define LOWER 0 /* lower limit of table */
#define UPPER 300 /* upper limit */
#define STEP 20 /* step size */
/* print Fahrenheit-Celsius table */
int main(void)
{
int fahr;
for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP)
printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
return 0;
}
Belirtim | Açıklama |
---|---|
%d | Desimal tamsayı olarak görüntüle |
%6d | En az 6 hane uzunluğunda desimal tamsayı görüntüle |
%f | Gerçel sayı olarak görüntüle |
%6f | En az 6 hane uzunluğunda gerçel sayı görüntüle |
%.2f | Ondalık noktasından sonra iki hane ile görüntüle |
%6.2f | En az 6 hane, ondalık noktasından sonra 2 hane ile görüntüle |
Belirtim | Açıklama |
---|---|
%c | Karakter görüntüle |
%s | Dizgi (string) görüntüle |
%x | Onaltılı tabanda görüntüle |
%o | Sekizli tabanda görüntüle |
%% | % karakterinin kendisi |
char
: Karakter veri tipi
Bilgisayar için her şey bir sayı
Ya bir matematiksel işleme giren olağan bir sayı, örneğin bir tam sayı veya gerçel sayı
Ya da bir şeyi temsil eden (onu kodlayan bir) "kod", örneğin bir renk, bir komut
Bilgisayarın dünyasındaki (tek bildiği şey olan) sayıların büyük bir kısmı bu ikinci grupta
O halde "karakter" de bir sayı, bir "kod" sayısı
En basit kodlama: ASCII
Her karakter bu tabloda bir doğal sayı (pozitif tam sayı)
ASCII Tablo
Dec Char Dec Char Dec Char Dec Char
--------- --------- --------- ----------
0 NUL (null) 32 SPACE 64 @ 96 `
1 SOH (start of heading) 33 ! 65 A 97 a
2 STX (start of text) 34 " 66 B 98 b
3 ETX (end of text) 35 # 67 C 99 c
4 EOT (end of transmission) 36 $ 68 D 100 d
5 ENQ (enquiry) 37 % 69 E 101 e
6 ACK (acknowledge) 38 & 70 F 102 f
7 BEL (bell) 39 ' 71 G 103 g
8 BS (backspace) 40 ( 72 H 104 h
9 TAB (horizontal tab) 41 ) 73 I 105 i
10 LF (NL line feed, new line) 42 * 74 J 106 j
11 VT (vertical tab) 43 + 75 K 107 k
12 FF (NP form feed, new page) 44 , 76 L 108 l
13 CR (carriage return) 45 - 77 M 109 m
14 SO (shift out) 46 . 78 N 110 n
15 SI (shift in) 47 / 79 O 111 o
16 DLE (data link escape) 48 0 80 P 112 p
17 DC1 (device control 1) 49 1 81 Q 113 q
18 DC2 (device control 2) 50 2 82 R 114 r
19 DC3 (device control 3) 51 3 83 S 115 s
20 DC4 (device control 4) 52 4 84 T 116 t
21 NAK (negative acknowledge) 53 5 85 U 117 u
22 SYN (synchronous idle) 54 6 86 V 118 v
23 ETB (end of trans. block) 55 7 87 W 119 w
24 CAN (cancel) 56 8 88 X 120 x
25 EM (end of medium) 57 9 89 Y 121 y
26 SUB (substitute) 58 : 90 Z 122 z
27 ESC (escape) 59 ; 91 [ 123 {
28 FS (file separator) 60 < 92 \ 124 |
29 GS (group separator) 61 = 93 ] 125 }
30 RS (record separator) 62 > 94 ^ 126 ~
31 US (unit separator) 63 ? 95 _ 127 DEL
Gözlemler
Bir metin dosyasında klavyeden girdiğiniz hemen hemen tüm karakterler
32-126
aralığındaSekme (
\t
) ve satır sonu (\n
) ilk 32'lik bloktaRakamlar, büyük harfler ve küçük harfler peşpeşe sıralı
Örneğin
9
'a ait kod5
'e ait koddan büyükÖrneğin
Z
harfine ait kodz
harfine ait koddan küçük
Tabloya bakarak yorumlayın
#include <stdio.h>
int main(void)
{
char c;
printf("%d\n", sizeof(char));
c = 'a';
printf("'%c' -> %d\n", c, c);
c = ' ';
printf("'%c' -> %d\n", c, c);
return 0;
}
cc -o main main.c
./main
1
'a' -> 97
' ' -> 32
Ayrıntılar
C standardına göre
char
bir karakteri temsil eden en az (ve çoğunlukla tam olarak) 8 bit uzunluğunda bir veri tipiBir
char
'ın bir bayt olduğunu ve 1 bayt'ın en az 8 bit olduğunu garanti edilir
Fakat
1 bayt'ın tam olarak 8 bit olduğu söylenmez (tarihsel olarak 1 bayt = 9 bit gibi örnekler de var)
Tipik ve yaygın olarak 1 bayt = 1 karakter = 8 bit
Karakter bir tam sayıya dönüştürüldüğünde daima pozitif bir tam sayı elde edileceği söylenmez
Ama tipik ve yaygın olarak
0-255
aralığında (uçlar dahil) pozitif bir tam sayıdır
Girdiyi Çıktıya kopyala
bir karakter oku
okunan karakter dosya sonunu belirtmediği sürece
karakteri yaz
yeni karakter oku
#include <stdio.h>
int main(void)
{
int c;
c = getchar();
while (c != EOF) {
putchar(c);
c = getchar();
}
return 0;
}
Standart Girdi/Çıktı modeli üzerinde ayrıntılı duracağız
getchar
: standart girdiden bir karakter okuputchar
: standart çıktıya bir karakter yaz
Daha kısa nasıl yazabiliriz?
#include <stdio.h>
int main(void)
{
int c;
while ((c = getchar()) != EOF)
putchar(c);
return 0;
}
Bakın bu örnekte
c
birchar
değil birint
, neden?Çünkü
getchar
daima bir tam sayı dönüyor (negatif değer de alabilen)İsmiyle döndüğü değer arasında büyük uyumsuzluk olan standart kitaplık fonksiyonlarının başında geliyor
getchar
birchar
dönmüyor,int
dönüyor, neden?
Çünkü:
getchar
dosya sonuna geldiği bilgisini çağıran tarafa "bir şekilde" iletmeli, nasıl? dönüş değeriyleEOF
stdio.h
içinde tanımlı bir sabit, değeri "çoğunlukla"-1
Tipik olarak geçerli bir
char
daima bir doğal sayı, bk. ASCII tabloYani
EOF
geçerli bir karakter değil (bu konuyu daha ayrıntılı anlatacağız)Bu sayede dosya sonuna gelindiği bilgisi geçerli bir karakter kodu kullanmadan iletebiliyor
getchar
'ın girdiyi klavyeden okuduğuna dikkat edinAslında
stdin
adı verilen standart girdiyi okuyorGirdiyi böyle hep karakter karakter mi alacağız? Bir sayı veya dizgi okumak istesek?
scanf
printf
'in kardeşi
Standart girdiden (
stdin
) bir biçim şablonuna uygun şekilde veri okumakta kullanılıyor"Scan formatted" →
scanf
#include <stdio.h>
int main(void)
{
int x, y;
scanf("%d %d", &x, &y);
printf("x: %d y: %d\n", x, y);
return 0;
}
&x
, &y
?
Değişken adının başındaki
&
karakterinin anlamı&
: "adresi" operatörü,&x
→ x değişkeninin adresiBu ileri bir konu, "işaretçiler"i anlatırken ayrıntılı değineceğiz
int x;
scanf("%d", &x);
Şimdilik şöyle bir metaforla anlayalım:
Bir tamsayı hacimli
x
kovasını, kovanın&
ile gösterilen sapından tutupscanf
fonksiyonunun içine sarkıtıyoruzscanf
dış dünyadan bir tamsayı okuyor ve kovayı dolduruyorKovayı çektiğimizde içinde artık bir tamsayı var →
x
Zorunlu Uyarı
scanf
ailesindeki fonksiyonları sadece eğitsel amaçlarla kullanın
Bu fonksiyonların gerçek programlarda bir yeri yok
printf
ailesi bu önermenin dışında
Neden?
Bu fonksiyonlar "güvenli" değil, duruma göre taşma "overflow" yaşanabilir, engellemek zor
Veri girişi komplekstir,
scanf
gibi basit bir fonksiyonla bu içsel kompleksiteyi karşılayamazsınız
Veri Girişi/Çıkışı
Programın dış dünyayla kontak noktaları
C programlarında UNIX'in girdi/çıktı modelini iyi kavramamız lazım
Bu model
stdio.h
içinde tanımlanan fonksiyonlarla gerçekleniyorStandart C çalışma ortamına sahip her işletim sisteminde bu modeli bulabilirsiniz
Standart Girdi/Çıktı modeli
Program komut satırında adını yazarak (veya Grafik ortamda tıklayarak/taplayarak) "çalıştırılıyor"
İşletim sistemi program imajını diskten belleğe yüklüyor
Program işletim sistemi denetiminde bir "proses"e dönüştürülüyor
Standart modelde her proses için 3 adet "dosya" veya "akım" ("stream") sunuluyor (ata prosesten miras yoluyla)
Bunlardan ikisi çıktı: sırasıyla
1
ve2
tamsayıları ile veyastdout
vestderr
isimleriyle gösteriliyorİlki ise girdi:
0
tamsayısı ile veyastdin
ismiyle gösteriliyor
Etkileşimli ("interactive") bir terminalin başındasınız
Prosesin girdisi klavye ile sizin tarafınızdan sağlanıyor
Proses çıktı olarak ekranı kullanıyor; işlem sonuçları ekranda görüntüleniyor
Özetle "etkileşimli" bir terminalde:
klavye →
stdin
stdout
→ ekranstderr
→ ekran (okumaya devam edin)
stdin
Proses, örneğin verileri stdin
'den okuyor, nasıl? Pek çok yolu var, bir örnek:
int x;
scanf("%d", &x);
Bir başka örnek:
int c;
c = getchar();
Programlama dillerinde klavyeden veri okuyan neredeyse tüm standart fonksiyonlar aslında
stdin
'den veri okurStandart fonksiyonlar:
getchar
,scanf
vbklavye →
stdin
stdout
Proses, örneğin yararlı işlem sonuçlarını stdout
'a yazıyor, nasıl? Özel bir şey yapmasına gerek yok:
printf("Result, %d\n", result);
Bir başka örnek:
putchar(c);
Programlama dillerinde ekranda görüntüleme yapan neredeyse tüm standart fonksiyonlar aslında
stdout
'a yazarStandart fonksiyonlar:
putchar
,printf
vbstdout
→ ekran
stderr
?
Prosese başlangıçta her ikisi de ekrana "yönlendirilmiş" olarak verilen iki çıktı dosyasından biri
Programın hata iletilerini
stderr
'e yazacak şekilde gerçeklendiğini düşünelim, nasıl?
fprintf(stderr, "Bu bir hata\n");
Yönlendirme
Her iki çıktı dosyası da ekrana yönlendirildiğinden sonuçlar ve hata iletileri hepsi birlikte ekranda
Standart girdi/çıktı dosyaları farklı yerlere yönlendirilebilir
stderr
'i ekran yerine bir başka yere yönlendirirsek ne olur?Tüm hata iletilerini "günlük" niteliğinde kaydetmiş oluruz
Kabuk
Programları "adlarını yazarak" çalıştırdığımız yönetici bir program var: "kabuk"
Kabuk, yönlendirme yapmak için olanaklar sunuyor, bk. önceki örnekte
>
ls olmayan_bir_dosyanın_listenmesi_bir_hatadır 2>error.txt
cat error.txt
ls: cannot access 'olmayan_bir_dosyanın_listenmesi_bir_hatadır': No such file or directory
Neden
2>error.txt
?2
?Hatırlayın
stderr
'e2
tamsayısı atanmıştıBu kabukta kullanılan özel bir notasyon:
2>«dosya»
Dikkat!
2
ile>
arasında boşluk yok, boşluk olursa sonuç ne oluyor?
Bu şekilde standart girdiyi de (stdin
) ön tanımlı klavye yerine bir başka yerden, örneğin bir dosyadan alabiliriz:
#include <stdio.h>
int main(void)
{
int x, y;
scanf("%d %d", &x, &y);
printf("x: %d y: %d", x, y);
return 0;
}
Klavyeden:
cc -o main main.c
./main
Dosyadan
./main <veri.txt
Bunun başka yolları da var, "heredoc notasyonu":
echo 9 | ./main
./main <<IN
9
IN
"heredoc"
<<
yönlendirme karakterlerinden sonra hangi tanımlayıcı yazılmışsa onunla kapatınÖrnekte
IN
Tüm bu örneklerde veri klavyeden değil dosyadan geliyor: zarif ve basit bir model
Bu sayede prosesin nereden veri alıp nereye veri yazacağını, program kodunda bir düzenleme yapmadan ayarlayabiliyoruz
Örnekler
Düzenleyelim (main.c
)
#include <stdio.h>
int main(void)
{
int a;
scanf("%d", &a);
if (a <= 0) {
fprintf(stderr, "Error: Number must be a positive integer\n");
return 1;
}
printf("%d\n", a);
return 0;
}
Derleyelim
cc -o main main.c
./main
./main >out.txt
./main >out.txt <<IN
19
IN
./main <<IN
-1
IN
./main 2>err.txt <<IN
-1
IN
./main >/dev/null <<IN
19
IN
./main 2>/dev/null <<IN
-1
IN
main
fonksiyonunun dönüş değeri
#include <stdio.h>
int main(void)
{
...
if (a <= 0) {
fprintf(stderr, "Error: Number must be a positive integer\n");
return 1;
}
...
return 0;
}
Programın başarılı olup olmadığını çağıran tarafa (kabuk) iletmemizde yarar var
Bu sayede programın başarılı veya başarısız olması durumuna göre farklı işlemler yapabiliriz
İşte bu nedenle
main
bir tamsayı döner0
→ başarı (çünkü başarı tek)!= 0
(tipik olarak1
) → başarısız (pek çok şekilde başarısız olabilirsiniz, her birine farklı sayı atayarak)
cat
concatenation
yapıyor; ismini bu kelimedekicat
'ten almışconcatenation
birleştirmecat
terimini gördüğünüzde aklınıza daima bir tür "birleştirme" anlamı gelsin
cat /etc/passwd /etc/fstab
İki dosyanın içeriği "birleştirilerek" ekran görüntüleniyor
İstediğiniz sayıda dosya verebilirsiniz
cat
argüman olarak verilen dosyaları birleştirerekstdout
'ta görüntüler
UNIX sistemlerde dosya içeriğini görüntülemenin en basit ve kanonik yolu cat
kullanmak
cat /etc/passwd
Neden? Tek dosya argümanı verirseniz (birleştirecek bir şey olmadığından) sadece bu dosyayı görüntüler
Peki,
cat
'e hiç bir argüman verilmezse?
cat
programı argüman yoksa veriyi stdin
'den, yani ön tanımlı olarak klavyeden okumaya çalışır
cat
Bilgisayar kilitlendi mi?
Hayır, sizden veri girmenizi bekliyor
Veri girdik, her yeni satırda
cat
satırı görüntülerPeki, nasıl çıkacağız? Cevap
Ctrl-C
değil, bu sadece işlemi iptal edercat
'e dosya sonunda olduğu bilgisini iletmemiz lazım
Etkileşimli terminallerde girdi sonunu "sinyallemek" için Ctrl-D
tuş kombinasyonunu kullanıyoruz
Bu tuş kombinasyonuna bastığımızda girdiye (
Ctrl-D
'ye karşı düşeceğini varsaydığımız) bir karakter eklenmiyorCtrl-D
bir karakter değilCtrl-D
işletim sisteminin klavye sürücüsü tarafından yakalanarak programa "sözde"EOF
değeri ileten bir tuştur
EOF
"End Of File"
Negatif bir tamsayı: genel olarak
-1
, ilgili platformdaki standart C kitaplığında tanımlıİşletim sisteminin girdi/çıktı fonksiyonlarına "akımın sonundayız" durumunu iletmek için kullanılan bir değer
EOF
dosyaların sonunda bulunan "özel bir karakter" değilNegatif bir değer olması nedeniyle
EOF
'nin (pozitif tamsayı değerli) geçerli bir karakter olamayacağını not edin
Standart girdinden veri okuyoruz
Verinin sonuna geldiğimizi nasıl anlayacağız?
Standart C kitaplığında dosyadan veri okuyan hemen hemen tüm fonksiyonlar dosya sonunda
EOF
değeri dönerNeden?
İşletim sisteminin girdi/çıktı fonksiyonlarına "akımın sonundayız" durumunu iletmek için kullandığı değer
EOF
getchar
ve girdi sonu
Örneği hatırlayalım:
#include <stdio.h>
int main(void)
{
int c;
while ((c = getchar()) != EOF)
putchar(c);
return 0;
}
Bu örnekte stdin
'den sürekli bir karakter okuyoruz; çalıştırıp deneyin
Girdiyi nasıl sonlandıracağız?
cat
'teki gibi:Ctrl-D
Ama unutmayın ki girdi klavye yerine dosyadan alınıyorsa dosya sonunda doğal olarak sonlanacak
cat chars.txt
a
b
c
./main <chars.txt
a
b
c
Dosyalarla çalışırken kullandığımız notasyonla ilgili önemli bilgilendirme
"Bakın
chars.txt
dosyasının içeriği budur" anlamında "cat chars.txt
" komutunu kullanıyoruzBu sayede ayrıca dosya adı, dosya içeriği yazmamız gerekmiyor
Kodu denerken de kolaylık sağlıyor
scanf
ve girdi sonu
#include <stdio.h>
int main(void)
{
int num;
while (scanf("%d", &num) != EOF)
printf("Read: %d\n", num);
return 0;
}
Bu örnekte stdin
'den dosya sonuna gelinceye kadar bir sayı okuyoruz; çalıştırıp deneyin
Girdiyi nasıl sonlandıracağız?
cat
'teki gibi:Ctrl-D
Ama unutmayın ki girdi klavye yerine dosyadan alınıyorsa dosya sonunda doğal olarak sonlanacak
scanf
dönüş değeri
scanf
fonksiyonunun standart kitaplık dokümanlarında açıklanmış olan dönüş değerlerine dikkat edin
getchar
gibi dönüş değeri bir tamsayıgetchar
okunan karakteri dönerken,scanf
okuma durumuyla ilgili bir durum bilgisi dönüyor, okunan veriyi değilscanf
okunan veriyi adresini aldığı değişkene yazar"Akım" sonuna gelindiğinde
EOF
Başarılı bir okumada (eşleştirmeler hatasız, dönüştürme hatası yok) okunan değişken sayısı
Dönüşüm hatasında
0
cat numbers.txt
3
5
8
./main <numbers.txt
Read: 3
Read: 5
Read: 8
Bunu başka nasıl çalıştırabiliriz?
cat numbers.txt | ./main
Read: 3
Read: 5
Read: 8
veya
./main <<EOF
2
4
6
EOF
Read: 2
Read: 4
Read: 6
Hatta şöyle:
echo -e "2\n4\n8" | ./main
Read: 2
Read: 4
Read: 6
getchar
/scanf
ve girdi tamponu
Standart girdiden okuma yapan girdi fonksiyonları doğrudan klavyeyi değil bir girdi tamponunu ("buffer") okur
Klavyeden gelen karakterler sırayla bir tampona giriyor
Tampona
'\n'
satır sonu karakteri girdiğinde girdi fonksiyonları çağrılıyorGirdi fonksiyonları her seferinde bir karakteri çıkartarak tamponu tüketiyor
Buna tamponlanmış girdi ("buffered input") diyoruz
Sürprizlere dikkat!
Unutmayın
stdio.h
'ta tanımlıgetchar/scanf
vs daima tampon okuyor, tamponda o sırada ne varsaTamponda beklentinizin dışında karakterler bulunabilir
|
: Borulama
Bir prosesin stdout
'a yazdığı veriyi bir başka prosesin stdin
'ine yazar
İki proses arasında bir tür boru var (
|
: "pipe" karakteri)Örnekte
echo
'nun çıktısımain
'in girdisi oluyorBu işlemi "kabuk" (doğal olarak işletim sistemi yoluyla) yapar
Kabuğun buradaki işlevi? Gerekli sözdizimini sağlamak
getchar
tekrar
Standart girdi/çıktı hakkında kısa bir bilgilendirme yaptıktan sonra örneklerimize devam edelim
Karakter sayısı
#include <stdio.h>
int main(void)
{
long nc;
nc = 0;
while (getchar() != EOF)
++nc;
printf("%ld\n", nc);
return 0;
}
Peki ya çok büyük bir dosya ise?
#include <stdio.h>
int main(void)
{
unsigned long long nc;
for (nc = 0; getchar() != EOF; ++nc)
; /* nop */
printf("%ld\n", nc);
return 0;
}
unsigned long long
#include <stdio.h>
int main(void)
{
unsigned long long nc;
printf("%d\n", sizeof(nc));
return 0;
}
cc -o main main.c
./main
8
Sonuç platforma göre değişir, burada 8 bayt
unsigned
işaretsiz, bu sayede pozitif sayı limiti artıyor, neden? negatif sayılar kodlanmıyorC99 standardında tanımlı, en az 64 bit garanti ediliyor (ki bu örnekte de 64 bit)
Yani? 0'dan başlayıp +18,446,744,073,709,551,615'a kadar çıkabiliyoruz
Satır sayısı
#include <stdio.h>
int main(void)
{
int c, nl;
nl = 0;
while ((c = getchar()) != EOF)
if (c == '\n')
++nl;
printf("%d\n", nl);
return 0;
}
if
Bir anahtar kelime
Koşul deyimi
==
: Eşitlik denetimi yapan operatör
Kelime istatistiği
#include <stdio.h>
#define IN 1
#define OUT 0
int main(void)
{
int c, nl, nw, nc, state;
state = OUT;
nl = nw = nc = 0;
while ((c = getchar()) != EOF) {
++nc;
if (c == '\n')
++nl;
if (c == ' ' || c == '\n' || c == '\t')
state = OUT;
else if (state == OUT) {
state = IN;
++nw;
}
}
printf("%d %d %d\n", nl, nw, nc);
return 0;
}
Bu örnekte yeni olarak karşımıza çıkanlar:
else if
/else
||
: Mantıksal VEYA bağlacı
Karakter istatistiği
#include <stdio.h>
int main(void)
{
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother = 0;
for (i = 0; i < 10; ++i)
ndigit[i] = 0;
while ((c = getchar()) != EOF)
if (c >= '0' && c <= '9')
++ndigit[c - '0'];
else if (c == ' ' || c == '\n' || c == '\t')
++nwhite;
else
++nother;
printf("digits = ");
for (i = 0; i < 10; ++i)
printf(" %d", ndigit[i]);
printf(", white space = %d, other = %d\n",
nwhite, nother);
return 0;
}
Bu örnekte yeni olarak karşımıza çıkanlar:
&&
: Mantıksal VE bağlacıEn önemlisi
int ndigit[10]
bu bir dizi
Diziler
int ndigit[10];
10 tam sayı sığasında bir dizi → dizi uzunluğu 10
Dizide ilk tam sayı
ndigit[0]
, son tamsayındigit[9]
ndigit[10]
geçerli bir dizi erişimi değil değil mi?Dizi uzunluğu neden 10? Çünkü 10 tane rakam var:
0
,1
, ...,9
ndigit[c - '0']
???
c - '0'
işleminin0-9
aralığında bir sayı ürettiğini not edinBir rakam okunduğunda
ndigit
dizisinde nereye (hangi indise) karşı geldiğini belirlemenin zarif yoluASCII tabloda karakterlerin yerleştirilme düzeninden yararlanan bir yöntem
Yöntem neden çok zarif? Aksi halde uzun bir
if
/else if
merdiveni kullanacaktık
Fonksiyonlar
#include <stdio.h>
int power(int m, int n);
int main(void)
{
int i;
for (i = 0; i < 10; ++i)
printf("%d %d %d\n", i, power(2, i), power(-3, i));
return 0;
}
int power(int base, int n)
{
int i, p;
p = 1;
for (i = 1; i <= n; ++i)
p = p * base;
return p;
}
dönüş-tipi function-adı(argüman bildirimleri...)
{
yerel bildirimler
deyimler
dönüş deyimi
}
return <ifade>;
dönüş deyiminde<ifade>
bildirilen dönüş-tipinde bir değer üretirDönüş değeri yoksa dönüş tipi
void
void
fonksiyonlarda gerekirsereturn;
ile erken dönüş yapabilirsinizFonksiyon argüman kabul etmiyorsa argüman bildirimleri alanında
void
Fonksiyon bildirimiyle fonksiyon tanımının aynı şeyler olmadığına dikkat edin
Fonksiyon kullanılmadan önce bildirilmeli
Fonksiyon tanımı herhangi bir yerde olabilir (ama mutlaka tanımlanmalı)
Fonksiyon tanımlandığında takip eden satırlar için de bildirilmiş olur (ayrıca bildirim yapmak gerekmez)
#include <stdio.h>
int power(int base, int n)
{
int i, p;
p = 1;
for (i = 1; i <= n; ++i)
p = p * base;
return p;
}
int main(void)
{
int i;
for (i = 0; i < 10; ++i)
printf("%d %d %d\n", i, power(2, i), power(-3, i));
return 0;
}
Değerle çağırmak
"Call by value"
Fonksiyon çağrılırken argümanların kendisi değil birer kopyası fonksiyona geçirilir
Bu özellikten yararlanılabilirsiniz
int power(int base, int n)
{
int p;
for (p = 1; n > 0; --n)
p = p * base;
return p;
}
Öte yandan "değerle çağırma"nın belirli kısıtlamalar sunduğunu da not edin
#include <stdio.h>
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int main(void)
{
int a = 1, b = 2;
printf("before: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after: a = %d, b = %d\n", a, b);
return 0;
}
Dizgiler
Standart girdiden okunan satırlar arasında en uzun olanını görüntüleyin
girdide satır olduğu sürece
eğer satır bir önceki en uzun satırdan daha uzunsa
en uzun satır olarak kaydet
uzunluğunu kaydet
en uzun satırı görüntüle
#include <stdio.h>
#define MAXLINE 1000
int readline(char line[], int maxline);
void copy(char to[], char from[]);
int main(void)
{
int len;
int max;
char line[MAXLINE];
char longest[MAXLINE];
max = 0;
while ((len = readline(line, MAXLINE)) > 0)
if (len > max) {
max = len;
copy(longest, line);
}
if (max > 0)
printf("%s", longest);
return 0;
}
Satır bir "karakter dizisi"
Buna dizgi ("string") diyeceğiz
Okunan her satırı bir karakter dizisinde saklıyoruz
Dizi için önceden yer ayrılmalı
Ayrılan yerin kapasitesi →
MAXLINE
int readline(char s[], int lim)
{
int c, i;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
void copy(char to[], char from[])
{
int i;
i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
C'de dizgi için özel bir veri tipi yok
Dizgiler birer dizi, karakter dizisi
Dizgi sonu?
\0
karakteri
C'de dizgiler \0
"null karakteri" ile sonlanır
Bir dizide dolaşırken sona geldiğinizi nasıl anlarsınız?
- Ancak bu sayede kuracağınız bir "dolaşma" döngüsünün sonlanma koşulunu belirleyebilirsiniz
Dizinin uzunluğunu bilirim
Dizi sonuna ulaştığımı gösteren özel bir değere bakarım
Pascal programlama dilinde örneğin ilk yöntem kullanılıyor
Pascal'da her dizginin bir başlık bölümü var; dizgi uzunluğu bu başlık bölümünde kayıtlı
C'de birer karakter dizisi olan dizgilerde dolaşırken sonlanma koşulu olarak 2'nci yöntemi kullanıyoruz
Bu "özel değer"e sentinel diyoruz
Özel değer de
char
tipiyle temsil edilebilir bir şey olmalı, fakat...Özel değer geçerli bir karakter olmamalı değil mi? (aksi halde ondan sonraki geçerli karakterler dikkate alınmaz)
C'de seçilen bu "özel değer" → "null karakter"
\0
Dikkat! Bunu daha sonra göreceğiniz
NULL
işaretçi ile karıştırmayın (her ne kadar sayı değeri 0 olsa da)
Standart girdiden okunan bir hello
satırı
+----+----+----+----+----+----+----+
| h | e | l | l | o | \n | \0 |
+----+----+----+----+----+----+----+
Satır girdisi satır sonuyla alındığından sonda
\n
varDizgi sonunda
\0
Dizgi uzunluğu nedir?
İpucu: dizgi ve dizi farkına dikkat!
\0
dizgi uzunluğuna katkıda bulunmaz, ama dizi uzunluğunda hesaba katılır
Dizgi veri tipi
C'de dizgi (String) veri tipi yok
C'de "string" bir karakter dizisi (array)
Ama çoğunlukla dizi yerine işaretçilerle çalışacağız (daha sonra anlatılacak)
strlen
/* strlen: return length of s */
int strlen(char s[])
{
int i = 0;
while (s[i] != '\0')
++i;
return i;
}
Veri tipleri
Tip | Açıklama |
---|---|
char | Karakter |
short | Kısa tamsayı, int |
int | Tamsayı |
long | Uzun tamsayı, long int |
float | Gerçel sayı |
double | Çift hassasiyetli gerçel sayı |
long double | Çift hassasiyetli gerçel sayı |
Tip | Açıklama |
---|---|
unsigned char | İşaretsiz char |
unsigned int | İşaretsiz int |
unsigned long | İşaretsiz long |
unsigned long long | İşaretsiz long long |
Mantıksal veri tipi
Takip ettiğiniz C standardına göre mantıksal veri tipi olmayabilir
Örneğin C99 öncesindeki C standartlarında özel bir
bool
veri tipi yok (ama C++'da her zaman var)C99 ve daha yeni standartlarda
#include <stdbool.h>
ile kullanılabilirHalen yaygın olan ANSI C'de (C89) mantıksal veri tipi yok
Mantıksal doğruluk/yanlışlık
C'de bir ifadenin sonucu
0
olarak değerlendirilmişse bu mantıksal olarak yanlıştırYanlış olmayan her şey doğru
Örnekler
- Doğru →
-1
,""
,"0"
,EOF
Gelecekte daha ayrıntılı göreceğimiz konular:
Yanlış →
\0
(çünkü değerlendirme sonucu0
)Yanlış →
NULL
(çünkü değerlendirme sonucu0
)
Sayı sabitleri
char
:'a'
int
:1234
float
:123.4f
veya123.4F
,1e-2f
veya1e-2F
double
:123.4
veya1e-2
(f
kullanılmıyorsadouble
)
Dizgi sabitleri
"hello, world"
veya "hello, " "world"
Ön ekler
Sekizli tabanda tamsayı: 0
(sıfır) ön eki, 0h
h
:0
...7
Onaltılı tabanda tamsayı: \x
veya 0x
veya 0X
ön ekleri, ör. \xhh
hh
:0
...9
,a
...f
veyaA
...F
Son ekler
Uzun sayı (tamsayı veya gerçel sayı):
l
veyaL
son ekiİşaretsiz sayı:
u
veyaU
son ekiİşaretsiz uzun sayı:
ul
veyaUL
Gerçel sayı:
f
veyaF
Enum sabitler
enum boolean { NO, YES };
enum days { SUN = 1, MON, TUE, WED, THU, FRI, SAT };
Bunlar birer tamsayı listesi, ör.
TUE
→ 3`)Bu tür sabitler
#define
ile de tanımlanabilir,enum
'ın avantajı derleme zamanı tip denetimi
Tanımlayıcılar
Sürpriz yok
Artık yıl (Leap year)
Bir yıl 4 ile tam bölünebiliyor, fakat 100 ile bölünemiyorsa artık yıldır. Öte yandan 400 ile tam bölünenler daima artık yıldır.
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
printf("%d is a leap year\n", year);
else
printf("%d is not a leap year\n", year);
Short circuit
İfadeyi ele alalım:
E1:
year % 4 == 0
E2:
year % 100 != 0
E3:
year % 400 == 0
if ((E1 && E2) || E3)
printf("%d is a leap year\n", year);
else
printf("%d is not a leap year\n", year);
E1'in yanlış olduğu anlaşıldığında E2 değerlendirilmez
Fakat
E1 && E2
yanlış olsa bile E3 değerlendirilirÖte yandan
E1 && E2
doğru ise E3 değerlendirmez çünkü gerek yok
Buna "short circuit evaluation" deniliyor
Bir ifade grubunun değerlendirme sonucu diğerlerinin değerlendirilmesini gereksiz kılıyor
"İfade grubu kısa devre yaptığında diğer ifadelerin akımını hesaplamaya gerek yok!"
Tip dönüşümleri
İşaretli sayılarda dönüşüm kuralları
Bir işlenen
long double
ise diğerini delong double
yapAksi halde, bir işlenen
double
ise diğerini dedouble
yapAksi halde,
char
veyashort
tipleriint
yapBir işlenen
long
ise diğerini delong
yap
İşaretsizlerde dönüşüm kuralları basit değil
"Casting"
Değer tipinin olağan anlamın dışında bir tipte yorumlanmasını sağlamak için "casting" yapıyoruz
(tip) ifade
Örnek:
a = sqrt((double) n);
Fakat bu örnekte sqrt
fonksiyonu zaten double
argüman alacak şekilde tanımlanmışsa "casting" gerekmiyor
double sqrt(double);
...
a = sqrt(2);
atoi
/* atoi: convert s to integer */
int atoi(char s[])
{
int i, n;
n = 0;
for (i = 0; s[i] >= '0' && s[i] <= '9'; ++i)
n = 10 * n + (s[i] - '0');
return n;
}
lower
/* lower: convert c to lower case; ASCII only */
int lower(int c)
{
if (c >= 'A' && c <= 'Z')
return c + 'a' - 'A';
else
return c;
}
#include <ctype.h>
/* lower: convert c to lower case; ASCII only */
int lower(int c)
{
if (isdigit(c))
return c + 'a' - 'A';
else
return c;
}
Arttırma/Azaltma operatörleri
Ön ek ve son ek formunda: ++i
veya i++
- Semantik farka dikkat, ön ek formunda önce arttır sona değer dön, son ek formunda önce değer dön sonra arttır
n = 5;
k = ++n;
k = n++;
squeeze
/* squeeze: delete all c from s */
void squeeze(char s[], int c)
{
int i, j;
for (i = j = 0; s[i] != '\0'; i++)
if (s[i] != c)
s[j++] = s[i];
s[j] = '\0';
}
strcat
/* strcat: concatenate t to end of s; s must be big enough */
void strcat(char s[], char t[])
{
int i, j;
i = j = 0;
while (s[i] != '\0') /* find end of s */
i++;
while ((s[i++] = t[j++]) != '\0') /* copy t */
;
}
Üçlü (ternary) ifade işleci
if (a > b)
z = a;
else
z = b;
yerine
z = (a > b) ? a : b;
Üçlü operatörlü ifadeyi şöyle yazsak?
z = a > b ? a : b;
Önce hangi işlem yapılır? Bu ifadenin anlamı hangisi?
z = a > (b ? a : b) z = (a > b) ? a : b
Evet beklendildiği gibi
z = a > b ? a : b;
Buna "operatör önceliği" (operator precedence) deniliyor
Parantezler her zaman beklenildiği gibi çalışır, "parantezler arasındaki ifadeyi grup olarak işle"
Parantezler olmadığında "operatör önceliği" kuralları devreye giriyor
Operatör Önceliği
Bu kurallara uzun uzun girmek istemiyoruz
Genel olarak pek çok işlem "ilk bakışta umulan (makul)" bir öncelik sırasıyla yapılır
Şüphe halinde parantezlere başvurabilirsiniz
Ama lütfen çok açık durumlar için bunu yapmayın
İlave (gereksiz) parantezler "malumu ilam" oluyor ve bilakis okunurluğu zorlaştırıyor
Akış denetimi
Askıda else
"Dangling else
"
if (n > 0)
if (a > b)
z = a;
else
z = b;
Örnekteki
else
hangiif
'e ait?Girintiden de anlaşılacağı gibi en yakın
if
'e ait
Bu bazen istediğiniz bir şey olmayabilir!
if (n > 0)
if (a > b)
z = a;
else
z = b;
Girintiye göre niyetimiz bu, ama sonuç böyle değil (bir önceki slayta bakın)!
if (n > 0) {
if (a > b)
z = a;
} else
z = b;
switch
/case
deyimi
if
/else
"merdivenleri"ne bir alternatif
#include <stdio.h>
int main(void)
{
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother = 0;
for (i = 0; i < 10; ++i)
ndigit[i] = 0;
while ((c = getchar()) != EOF)
if (c >= '0' && c <= '9')
++ndigit[c - '0'];
else if (c == ' ' || c == '\n' || c == '\t')
++nwhite;
else
++nother;
printf("digits = ");
for (i = 0; i < 10; ++i)
printf(" %d", ndigit[i]);
printf(", white space = %d, other = %d\n",
nwhite, nother);
return 0;
}
#include <stdio.h>
int main(void)
{
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother = 0;
for (i = 0; i < 10; ++i)
ndigit[i] = 0;
while ((c = getchar()) != EOF)
switch (c) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
ndigit[c - '0']++;
break;
case ' ': case '\n': case '\t':
nwhite++;
break
default:
nother++;
break;
}
printf("digits = ");
for (i = 0; i < 10; ++i)
printf(" %d", ndigit[i]);
printf(", white space = %d, other = %d\n",
nwhite, nother);
return 0;
}
case
'lerde "fall through" yapılır, yanicase
işleminden sonra bir sonrakicase
'e düşülürBu istediğimiz bir davranış değil, bu nedenle
break
koyuyoruzBu örnekteki
break
switch
/case
deyimini kırıyor, dıştakiwhile
döngüsünü değilDuruma göre bazen
break
yerinereturn
tercih edilebilir
do
/while
döngüsü
while
döngülerinde döngü koşulu döngüye girerken mutlaka denetlenirVe duruma göre döngüye hiç girilmeyebilir!
Fakat bazı durumlarda döngünün en az bir kere çalışması gerektiğini biliriz
Bu tür durumlarda
do
/while
kullanıyoruz
do {
...
} while (ifade);
itoa
/* itoa: convert n to characters in s */
void itoa(int n, char s[])
{
int i, sign;
if ((sign = n) < 0) /* record sign */
n = -n; /* make n positive */
i = 0;
do {
/* generate digits in reverse order */
s[i++] = n % 10 + '0'; /* get next digit */
} while ((n /= 10) > 0);
/* delete it */
if (sign < 0)
s[i++] = '-';
s[i] = '\0';
reverse(s);
}
break
ve continue
break
: döngüyü bu noktada koşulsuz olarak sonlandır
- Akış döngü deyiminden sonraki ilk deyimle devam eder
continue
: bu noktada dur ve döngü koşuluyla devam et
- Akış döngü koşulunun sınanmasıyla devam eder (ve buna göre döngü sonlanır veya sonlanmaz)
trim
break
örneği
/* trim: remove trailing blanks, tabs, newlines */
int trim(char s[])
{
int n;
for (n = strlen(s)-1; n >= 0; n--)
if (s[n] != ' ' && s[n] != '\t' && s[n] != '\n')
break;
s[n+1] = '\0';
return n;
}
continue
örneği
for (i = 0; i < n; i++)
if (a[i] < 0) /* negatif değerleri atla */
continue;
... /* pozitif değerleri işle */
goto
Kodda bir yerden (etiketle belirtilmiş) bir başka yere atlama yapıyor
Yapısal olmayan programlama dillerinde çok yaygın
Kod okunurluğunu genel olarak çok azaltıyor → "spagetti programlama"
Fakat C'de bunun için yine de olanak sunulmuş, neden?
Bazen iç içe döngülerden çıkmanın en kestirme ve basit yolu
a
ve b
dizilerinde ortak bir eleman var mı?
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
if (a[i] == b[j])
goto found;
/* ortak bir eleman bulunamadı */
...
found:
/* a[i] == b[j] olan bir eleman bulundu */
Alternatif olarak şöyle yazılabilirdi:
found = 0;
for (i = 0; i < n && !found; i++)
for (j = 0; j < m && !found; j++)
if (a[i] == b[j])
found = 1;
if (found)
/* a[i] == b[j] olan bir eleman bulundu */
...
else
/* ortak bir eleman bulunamadı */
...
Fakat bu alternatif formun bir parça daha karışık olduğu görülüyor
İlave bir kaç denetim ve bir değişken pahasına
goto
kullanmaktan kaçındık"Asla
goto
kullanmayın" tutuculuğunda olmayalım
Fonksiyonlar ve Program organizasyonu
dönüş-tipi function-adı(argüman bildirimleri)
{
yerel bildirimler ve deyimler
}
Minimal fonksiyon
dummy() { }
veya
void dummy(void)
{
}
return
return ifade;
Dönüş değerinin literal bir değer olması gerekmiyor
İfade değerlendirilerek sonucu dönülüyor
İfade yoksa bu bir
void
fonksiyondurYani dönüş değeri olmayan fonksiyonlarda erken dönüşler için kullanılabilir
#include <ctype.h>
/* atof: convert string s to double */
double atof(char s[])
{
double val, power;
int i, sign;
for (i = 0; isspace(s[i]); i++) /* skip white space */
;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
for (val = 0.0; isdigit(s[i]); i++)
val = 10.0 * val + (s[i] - '0');
if (s[i] == '.')
i++;
for (power = 1.0; isdigit(s[i]); i++) {
val = 10.0 * val + (s[i] - '0');
power *= 10;
}
return sign * val / power;
}
/* atoi: convert string s to integer using atof */
int atoi(char s[])
{
double atof(char s[]);
return (int) atof(s);
}
Her iki fonksiyon da standart C kitaplığında stdlib.h
'da tanımlı
Program organizasyonu
Gerçel sayı okuyan bir fonksiyon yazalım, öyle ki:
Fonksiyon kendi başına ayrı bir dosyada bulunsun
Diğer kaynak kod dosyalarından buna erişilebilsin
Özetle programın gerçeklemesini birden fazla dosyaya yayalım
getnum.c
#include <stdio.h> /* BUFSIZ, getchar */
#include <stdlib.h> /* atof */
char line[BUFSIZ];
/* getnum: convert line to a double */
double getnum(void)
{
int c, i;
i = 0;
int lim = BUFSIZ;
while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
line[i++] = c;
if (c == '\n')
line[i++] = c;
line[i] = '\0';
return atof(line);
}
BUFSIZ
Gerçel sayıya çevrilecek dizgi için bellekte yer ayırmalıyız
Şimdilik bunu statik olarak yapacağız
Gelecekte dinamik olarak
malloc
yoluyla da yapılabileceğini göreceksinizStatik olarak ayrılacak bellek alanının boyutunu rastgele büyük bir değer seçmek yerine
BUFSIZ
olarak seçtikBUFSIZ
değeri kullandığınız platforma göre farklı değerler alabilirC standardına göre en az 256 olmalı, tipik olarak 4096 değeri alıyor
Bu değer nerede tanımlanmış?
stdio.h
dosyasında
Kapsam
C'de {}
blokları sözdizimsel kapsam oluşturur
#include <stdio.h>
int main(void)
{
int n = 19;
{
int n = 42;
printf("%d\n", n);
}
printf("%d\n", n);
return 0;
}
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 2; i++) {
printf("i: %d\n", i);
}
printf("%d\n", i);
return 0;
}
main.c
#include <stdio.h>
extern double getnum(void);
int main(void)
{
double num = 0.0;
while (1) {
printf("Sayı? ");
if ((num = getnum()) == 0.0)
break;
printf("Sayı: %f\n", num);
}
return 0;
}
extern
extern double getnum(void);
Nitelendirilmiş değişkenin bu dosyada değil "dışarıda" bir dosyada tanımlandığını belirtir
Kullanılmaması halinde derleyici bu dosyada hata verecektir; çünkü bildirilmemiş bir değişken var
Dosyaları bir arada derlememiz gerekiyor, sadece main.c
dosyasını derleyemezsiniz
cc -o getnum getnum.c main.c
./getnum
static
char line[BUFSIZ];
Bu değişken
getnum
kitaplığının "iç işi"Fakat istenirse diğer dosyalardan
extern char line[];
bildirimiyle erişilebilirBunu engellemeliyiz, nasıl?
static char line[BUFSIZ];
Statik olarak bildirilmiş değişkenler:
Sadece o dosyaya özeldir, bir başka dosyadan (ör.
extern
ile) erişilemezKapsamı o dosya olan "genel değişken" ("global variable") olarak davranır
Genel değişkenlerden mümkün olduğunca kaçınıyoruz
Kaçınılamazsa en azından
static
ile sadece dosya geneli yapın
getnum.c
#include <stdio.h>
#include <stdlib.h>
static char line[BUFSIZ];
/* getnum: convert line to a double */
double getnum(void)
{
int c, i;
i = 0;
int lim = BUFSIZ;
while (--lim > 0 && (c = getchar()) != EOF && c != '\n')
line[i++] = c;
if (c == '\n')
line[i++] = c;
line[i] = '\0';
return atof(line);
}
getnum
fonksiyonunu kullanan dosyalar her seferinde extern
yoluyla bunu bildirmek zorunda mı?
Gerçeklemeyi birden fazla dosyaya yaydığımızda bakım sorunları da başlıyor
Bu süreci kolaylaştıracak bir şey olmalı
getnum.h
double getnum(void);
main.c
#include <stdio.h>
#include "getnum.h"
int main(void)
{
double num = 0.0;
while (1) {
printf("Sayı? ");
if ((num = getnum()) == 0.0)
break;
printf("Sayı: %f\n", num);
}
return 0;
}
Başlık dosyaları
"Header files"
C derlenen bir dil
Derleme sırasında tip denetimi dahil statik denetimlerin yapılması için derleyiciyi önden bilgilendirmelisiniz
Fonksiyon ne tip değer dönüyor, ne tip değerler alıyor?
Gerçeklemeyi birden fazla dosyaya yaydığımızda bu tanımları ortak bir yerde toplamalıyız
Başlık dosyaları birbiriyle ilişkili tüm bildirimleri toplayan dosyalar
Geleneksel olarak
.h
uzantısı kullanılıyor
Başlık dosyalarını #include
yoluyla tüketiyoruz
#include
kullanıldığı noktada ilgili dosyanın içeriğini satır satır ekliyorBu işlem derlemenin ilk fazındaki "ön işleme"de ("preprocessing") gerçekleşiyor
Eklenen dosya nerede bulunuyor? <>
, ""
farkı
#include <stdio.h>
#include "getnum.h"
Üçgen parantezler içinde yazılan dosyalar derleyicinin öntanımlı "include" PATH'inde aranır
Örnekte
stdio.h
/usr/include
dizinindedir ve bu dizin öntanımlı dizin listesindeÇift tırnaklar içinde yazılan dosyalar göreceli olarak bulunduğunuz dizinde aranır
Örnekte
"getnum.h"
derleme yaptığınız dizindeÖntanımlı arama yollarını değiştirmek için
cc
komut satırına girilen seçenekleri veya ortam değişkenlerini kullanın
Başlık dosyalarında genel olarak sadece bildirimler ve sabitler (ör. BUFSIZ
) olmalı
Fonksiyon bildirimi
.h
başlık dosyasındaFakat fonksiyon tanımı
.c
dosyasında
cc -o getnum getnum.c main.c
./getnum
Başlık dosyalarının derlemeyi yapan komut satırına girmediğine dikkat edin, neden?
Çünkü ön işlemede bu dosyalar zaten kullanılıyor, derleme komutuna özel bir ekleme yapmanız gerekmez
Şimdi bir başka örnek üzerinde çalışalım
Basit bir "Stack" ("Yığın") gerçeklemesi
"Stack"? Bir tür dizi, sadece iki anlamlı işlem
push
,pop
Yeni ögeler diziye sadece bir taraftan, sondan veya baştan giriyor →
push
Mevcut ögelerden sadece yığın tepesindeki ögeyi çıkarabiliyoruz →
pop
stack.c
#include <stdio.h>
#define MAXITEM 100 /* maximum depth of stack */
static int sp; /* next free stack position */
static double stack[MAXITEM]; /* stack (of double number values) */
/* push: push f onto value stack */
void push(double f)
{
if (sp < MAXITEM)
stack[sp++] = f;
else
printf("Error: Stack full, can't push %g\n", f);
}
/* pop: pop and return top value from stack */
double pop(void)
{
if (sp > 0)
return stack[--sp];
else {
printf("Error: Stack empty\n");
return 0.0;
}
}
static int sp = 0;
Statik bildirilmiş veriler C standartı gereği daima sıfırlanır
Bu ilklemeye gerek yok (ama açık olmak adına böyle yapabilirsiniz)
stack.c
(devam)
/* dump: print stack items */
void dump(void)
{
if (sp > 0) {
int i = sp;
while (i-- > 0)
printf("%d: %f\n", i, stack[i]);
} else {
printf("Stack empty!\n");
return;
}
}
/* flush: reset stack while printing all the items */
void flush(void)
{
while (--sp >= 0)
printf("%d: %f\n", sp, stack[sp]);
}
"Off-by-one" hatalarına dikkat! --sp >= 0
yerine:
sp-- >= 0
yazılırsa?--sp > 0
yazılırsa?Çok sinsi hatalar
stack.h
void push(double);
void pop(void);
void dump(void);
void flush(void);
main.c
#include <stdio.h>
#include "getnum.h"
#include "stack.h"
int main(void)
{
double num = 0.0;
while (1) {
printf("Number [ENTER to stop]? ");
if ((num = getnum()) == 0.0)
break;
push(num);
}
dump();
flush();
dump();
return 0;
}
cc -o stack getnum.c stack.c main.c
./stack
İşaretçiler
Takip eden slaytlardaki anlatım Richard Buckland'ın UNSW'deki derslerinden neredeyse bire bir alıntılanmıştır
+---------+---------+---------+---------+
|0 |1 |2 |3 |
| | | | |
| 7 | 2 | 4 | 8 |
| | | | |
+---------+---------+---------+---------+
|4 |5 |6 |7 |
| | | | |
| 11 | 10 | 3 | 5 |
| | | | |
+---------+---------+---------+---------+
|8 |9 |10 |11 |
| | | | |
| 10 | 8 | 2 | 6 |
| | | | |
+---------+---------+---------+---------+
|12 |13 |14 |15 |
| | | | |
| 1 | 0 | 0 | 5 |
| | | | |
+---------+---------+---------+---------+
+---------+---------+---------+---------+
|0 |1 |2 |3 |
| | | | |
| 7 | 2 | 4 | 8 |
| | | | |
+---------+---------+---------+---------+
|4 |5 |6 |7 |
| | | | |
| 11 | 10 | 3 | 5 |
| | | | |
+---------+---------+---------+---------+
|8 |9 |10 |11 |
| | | | |
| 10 | 8 | 2 | 6 |
| | | X | |
+---------+---------+---------+---------+
|12 |13 |14 |15 |
| | | | |
| 1 | 0 | 0 | 5 |
| | | | |
+---------+---------+---------+---------+
printf("%d", x);
Değişken → bellek hücrelerine iliştirilen etiket
x
hücresi →10
hücre adresi,2
hücre değerix
denildiğinde ne anlıyoruz?2
değeri mi,10
adresi mi?
Bu örneğe göre
x
denildiğinde "doğal olarak"2
değerini anlıyoruzDeğişken üzerinden
10
adresini nasıl ifade ederiz?&
operatörü
+---------+---------+---------+---------+
|0 |1 |2 |3 |
| | | | |
| 7 | 2 | 4 | 8 |
| | | | |
+---------+---------+---------+---------+
|4 |5 |6 |7 |
| | | | |
| 11 | 10 | 3 | 5 |
| | | | |
+---------+---------+---------+---------+
|8 |9 |10 |11 |
| | | | |
| 10 | 8 | 2 | 6 |
| | | X | |
+---------+---------+---------+---------+
|12 |13 |14 |15 |
| | | | |
| 1 | 0 | 0 | 5 |
| | | | |
+---------+---------+---------+---------+
printf("%d", &x);
+---------+---------+---------+---------+
|0 |1 |2 |3 |
| | | | |
| 7 | 2 | 4 | 8 |
| | | | |
+---------+---------+---------+---------+
|4 |5 |6 |7 |
| | | | |
| 11 | 10 | 3 | 5 |
| | | | Y |
+---------+---------+---------+---------+
|8 |9 |10 |11 |
| | | | |
| 10 | 8 | 2 | 6 |
| | | X | |
+---------+---------+---------+---------+
|12 |13 |14 |15 |
| | | | |
| 1 | 0 | 0 | 5 |
| | | | |
+---------+---------+---------+---------+
7
adresindeki hücrede5
değeri varElimizde
y
adında bir değişken olmadığını varsayalımAdres üzerinden değere nasıl erişiriz?
*
operatörü ("pointer indirection operator")
+---------+---------+---------+---------+
|0 |1 |2 |3 |
| | | | |
| 7 | 2 | 4 | 8 |
| | | | |
+---------+---------+---------+---------+
|4 |5 |6 |7 |
| | | | |
| 11 | 10 | 3 | 5 |
| | | | Y |
+---------+---------+---------+---------+
|8 |9 |10 |11 |
| | | | |
| 10 | 8 | 2 | 6 |
| | | X | |
+---------+---------+---------+---------+
|12 |13 |14 |15 |
| | | | |
| 1 | 0 | 0 | 5 |
| | | | |
+---------+---------+---------+---------+
printf("%d", *7); // y
7
adresli hücrenin değeri → 5
+---------+---------+---------+---------+
|0 |1 |2 |3 |
| | | | |
| 7 | 2 | 4 | 8 |
| | | | |
+---------+---------+---------+---------+
|4 |5 |6 |7 |
| | | | |
| 11 | 10 | 3 | 5 |
| | | | |
+---------+---------+---------+---------+
|8 |9 |10 |11 |
| | | | |
| 10 | 8 | 2 | 6 |
| | | X | |
+---------+---------+---------+---------+
|12 |13 |14 |15 |
| | | | |
| 1 | 0 | 0 | 5 |
| | | | |
+---------+---------+---------+---------+
printf("%d", *x);
*x
operatörünün anlamıx
değerli adresin içeriği*2
→4
x
değeri olan2
bir adres olarak yorumlanıyor2
adresli hücrenin içeriği (değer) →4
???
printf("%d", *&&**x);
*
ve &
operatörleriyle kurulan bu dünyanın yararı ne?
Fonksiyonlar temel yapıtaşı
Karmaşıklığı yönetiyoruz
"Code reuse" için en temel birim
Fonksiyonlar arasındaki veri akışını göz önüne alalım
Fonksiyona nasıl veri aktarıyoruz?
Çağrılan bir fonksiyon çağıran tarafa nasıl veri (örneğin bir hesabın sonucu) aktarıyor?
void task(int x, int y)
{
x++;
y++;
}
int main(void)
{
int x = 19, y = 42;
task(x, y);
return 0;
}
Function call frame
Call stack
Fonksiyon çağrılarında argümanlar çağıran tarafından fonksiyonun yığınına kopyalanıyor
Bu semantiğe "değerle çağırma" ("pass by value") diyoruz
Fonksiyon değerlerin kopyası üzerinde çalışıyor, değerlerin kendisi üzerinde değil
Özgün değerler fonksiyon içinde yapılan değişikliklerden etkilenmiyor
"Değerle çağırma" semantiği çok yararlı
Fonksiyon çağrılarındaki "yan etki"leri azaltıyor
Veri akışını daha anlaşılır kılıyor
Örneği hatırlayalım
int power(int base, int n)
{
int p;
for (p = 1; n > 0; --n)
p = p * base;
return p;
}
Fakat ...
Bu örnekte
void
bir fonksiyon kullandık, fonksiyonun çağıran tarafa veri aktarabilmesi olanaksızArgüman dizi olsaydı ne olacaktı?
void task(int a[], size_t len)
{
for (int i = 0; i < len; i++)
a[i]++;
}
int main(void)
{
size_t len = 1000000;
int a[len];
for (int i = 0; i < len; i++)
a[i] = i;
task(a, len);
return 0;
}
Fonksiyonlara dizi argümanları geçirildiğinde ne olur?
ilk örnekte olduğu gibi tüm dizi baştan sona yığına kopyalanacak mı?
Yani "pass by value" semantiği dizi argümanlarında da kullanılıyor mu?
Bu iki sorunun da yanıtı evet ise ciddi problemlerimiz var
İlk örnekle benzer şekilde, çağrılan fonksiyon çağıran tarafa veri aktaramıyor
Neden? Çünkü dizinin kopyası üzerinde çalışıyor, kendisi üzerinde değil
Tüm bir dizi kopyalanıyorsa bu fonksiyon çağrısının maliyeti ne olur?
İşte işaretçiler bu aşamada sahne alıyor
Çağırma sırasında fonksiyona değerin kopyasını değil de kendisini versek?
Kendisi? Değerin bulunduğu bellek hücresinin adresi
Buna değer referansı diyoruz
Bu şekilde gerçekleşen veri aktarımı da "referansla çağırma" ("pass by reference") olarak adlandırılıyor
void task(int *px, int *py)
{
*px = *px + 1;
*py = *py + 1;
}
int main(void)
{
int x = 19, y = 42;
task(&x, &y);
return 0;
}
... Veya daha kısa olarak
void task(int *px, int *py)
{
(*px)++;
(*py)++;
}
int main(void)
{
int x = 19, y = 42;
task(&x, &y);
return 0;
}
int *px
→ "*px
bir tamsayıdır" olarak okuyunpx
bir işaretçi ("pointer"), bu örnekte bir tamsayı değere işaret eden bir işaretçiÇağırma sırasında
px
'in&x
ile değer aldığına dikkat edinpx
değeri "adres" olan bir değişken (buna tekrar değineceğiz)
*px++
ifadesinde iki operatör var:*
ve++
Hangisi önce çalışıyor? "Operator Precedence"
*px++
→*(px++)
O halde
(*px)++
: "px
adresindeki değeri 1 arttır"
Takas örneğini hatırlayalım
#include <stdio.h>
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int main(void)
{
int a = 1, b = 2;
printf("before: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after: a = %d, b = %d\n", a, b);
return 0;
}
Bakın artık böyle çalışır
#include <stdio.h>
void swap(int *pa, int *pb)
{
int temp = *pa;
*pa = *pb;
*pb = temp;
}
int main(void)
{
int a = 1, b = 2;
printf("before: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after: a = %d, b = %d\n", a, b);
return 0;
}
İşaretçiler sayesinde çağıran ve çağrılan arasındaki veri aktarımındaki sorunu çözmüş olduk
Hata yapmaya daha açık kodlar yazma pahasına
Çağırana hatalı bir adres aktarılırsa ne olur?
Şimdi diğer soruna, "dizinin kopyalanması" sorununa bakalım...
Bir deneme yapalım
void task(int a[], size_t len)
{
for (int i = 0; i < len; i++)
a[i]++;
}
int main(void)
{
size_t len = 1000000;
int a[len];
for (int i = 0; i < len; i++)
a[i] = i;
task(a, len);
for (int i = 0; i < 10; i++)
printf("%d\n", a[i]);
return 0;
}
Bakın bu kod doğru çalıştı
Dizinin her elemanı 1 arttı
Demek ki çağırma sırasında fonksiyona dizi kopyası değil, dizinin kendisi iletildi
Nasıl?
C'de fonksiyon çağrılarında diziler daima "referansla aktarılır"
Fonksiyona geçirilen dizi adı dizinin ilk elemanına ait adresi temsil eder
Yani aslında fonksiyona bir "pointer" geçirilir
Dizi adı "bir radyoaktif maddenin dönüşümü" gibi bir işaretçiye dönüşür
"Array to pointer decay"
void task(int a[], size_t len)
{
for (int i = 0; i < len; i++)
a[i]++;
}
int main(void)
{
size_t len = 1000000;
int a[len];
for (int i = 0; i < len; i++)
a[i] = i;
task(&a[0], len);
for (int i = 0; i < 10; i++)
printf("%d\n", a[i]);
return 0;
}
void task(int *a, size_t len)
{
for (int i = 0; i < len; i++)
a[i]++;
}
int main(void)
{
size_t len = 1000000;
int a[len];
for (int i = 0; i < len; i++)
a[i] = i;
task(&a[0], len);
for (int i = 0; i < 10; i++)
printf("%d\n", a[i]);
return 0;
}
void task(int *a, size_t len)
{
for (int i = 0; i < len; i++)
a[i]++;
}
veya
void task(int *a, size_t len)
{
for (int i = 0; i < len; i++)
*(a + i)++;
}
p
bir işaretçi ise
p[i]
ile*(p + i)
eşdeğerdir*
operatörü yerine indis parantezleri kullanabilirsinizp[0]
→*(p + 0)
→*p
strlen
/* strlen: return length of string s */
int strlen(char *s)
{
int n;
for (n = 0; *s != '\0'; s++)
n++;
return n;
}
/* strlen: return length of string s */
int strlen(char *s)
{
char *p = s;
while (*p != '\0')
p++;
return p - s;
}
Dizgi ilkleme
char amessage[] = "hello"; /* an array */
char *pmessage = "hello"; /* a pointer */
+---+---+---+---+---+---+
amessage ---------> | h | e | l | l | o |\0 |
+---+---+---+---+---+---+
+---+ +---+---+---+---+---+---+
pmessage | | ---> | h | e | l | l | o |\0 |
+---+ +---+---+---+---+---+---+
strcpy
/* strcpy: copy t to s; array subscript version */
void strcpy(char *s, char *t)
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
/* strcpy: copy t to s; pointer version */
void strcpy(char *s, char *t)
{
while ((*s = *t) != '\0') {
s++;
t++;
}
}
/* strcpy: copy t to s; pointer version 3 */
void strcpy(char *s, char *t)
{
while (*s++ = *t++)
;
}
strcmp
/* strcmp: return <0 if s<t, 0 if s==t, >0 if s>t */
int strcmp(char *s, char *t)
{
int i;
for (i = 0; s[i] == t[i]; i++)
if (s[i] == '\0')
return 0;
return s[i] - t[i];
}
/* strcmp: return <0 if s<t, 0 if s==t, >0 if s>t */
int strcmp(char *s, char *t)
{
for ( ; *s == *t; s++, t++)
if (*s == '\0')
return 0;
return *s - *t;
}
Çok boyutlu diziler
static char daytab[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
/* day_of_year: set day of year from month & day */
int day_of_year(int year, int month, int day)
{
int i, leap;
leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
for (i = 1; i < month; i++)
day += daytab[leap][i];
return day;
}
/* month_day: set month, day from day of year */
void month_day(int year, int yearday, int *pmonth, int *pday)
{
int i, leap;
leap = year%4 == 0 && year%100 != 0 || year%400 == 0;
for (i = 1; yearday > daytab[leap][i]; i++)
yearday -= daytab[leap][i];
*pmonth = i;
*pday = yearday;
}
/* month_name: return name of n-th month */
char *month_name(int n)
{
static char *name[] = {
"Illegal month",
"January", "February", "March",
"April", "May", "June",
"July", "August", "September",
"October", "November", "December"
};
return (n < 1 || n > 12) ? name[0] : name[n];
}
echo
#include <stdio.h>
/* echo command-line arguments; 1st version */
int main(int argc, char *argv[])
{
int i;
for (i = 1; i < argc; i++)
printf("%s%s", argv[i], (i < argc-1) ? " " : "");
printf("\n");
return 0;
}
#include <stdio.h>
/* echo command-line arguments; 2nd version */
int main(int argc, char *argv[])
{
while (--argc > 0)
printf("%s%s", *++argv, (argc > 1) ? " " : "");
printf("\n");
return 0;
}
Matrisler
static int a[2][2] = {
{1, 2},
{3, 4},
};
static int b[2][2] = {
{3, 5},
{7, 9},
};
void display(int a[][], int m, int n)
{
int i, j;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++)
printf("%d ", a[i][j]);
printf("\n");
}
}
void multiply(int a[][], int b[][], int c[][], int m, int n)
{
int i, j, k;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
c[i][j] = 0;
for (i = 0; i < m; i++)
for (j = 0; j < m; j++)
for (k = 0; k < n; k++)
c[i][j] += a[i][k] * b[k][j];
}
Dizilerin işaretçilere dönüşmesi
#include <stdio.h>
int main(void)
{
int numbers[] = { 3, 5, 8 };
printf("Size of integer array: %d\n", sizeof(numbers));
return 0;
}
Örnekte ekranda
12
görüntüleniyor (bu makinede)3 elemanlı bir dizide neden
12
?
#include <stdio.h>
int main(void)
{
int numbers[] = { 3, 5, 8 };
printf("Size of integer: %d\n", sizeof(int));
printf("Size of integer array: %d\n", sizeof(numbers));
return 0;
}
Tamsayı boyutu
4
bayt, dizi boyutu12
baytŞimdi anlamlı,
12/4 = 3
dizi boyutunu elde ettikGelin bunu bir makro yapalım
#include <stdio.h>
#define COUNT_OF(array) (sizeof(array)/sizeof(array[0]))
int main(void)
{
int numbers[] = { 3, 5, 8 };
printf("Count of array elements: %d\n", COUNT_OF(numbers));
return 0;
}
COUNT_OF
makrosu dizinin elemanlarının hangi tipte olduğuna bakmıyorDizi elemanlarının boyutunu örnekleme yaparak, örneğin ilk elemanın boytuna bakarak belirliyoruz
Güzel, fakat bu makro her kullanım koşulunda geçerli mi? Deneyelim...
#include <stdio.h>
#define COUNT_OF(array) (sizeof(array)/sizeof(array[0]))
void function(int numbers[])
{
for (int i = 0; i < COUNT_OF(numbers); i++)
printf("%d: %d\n", i, numbers[i]);
}
int main(void)
{
int numbers[] = { 3, 5, 8 };
function(numbers);
return 0;
}
Bir sorun var, dizinin sadece ilk iki elemanı görüntülendi, neden?
Sorunu görebiliyor musunuz?
Sanki
COUNT_OF
fonknsiyon içindeyken3
değil de2
sonucunu verdi gibi, neden?
#include <stdio.h>
#define COUNT_OF(array) (sizeof(array)/sizeof(array[0]))
void function(int numbers[])
{
printf("function: size of integer array: %d\n", sizeof(numbers));
printf("function: count of array elements: %d\n", COUNT_OF(numbers));
for (int i = 0; i < COUNT_OF(numbers); i++)
printf("%d: %d\n", i, numbers[i]);
}
int main(void)
{
int numbers[] = { 3, 5, 8 };
printf("main: size of integer: %d\n", sizeof(int));
printf("main: size of integer pointer: %d\n", sizeof(int *));
printf("main: size of integer array: %d\n", sizeof(numbers));
printf("main: count of array elements: %d\n", COUNT_OF(numbers));
function(numbers);
return 0;
}
Dizi değişkenleri fonksiyonlara geçirildiğinde birer işaretçiye dönüşür
Yani? Parametre aktarımı sırasında dizinin boyut bilgisi kaybolur
Dizinin tanımlandığı çağıran tarafta dizi boyutunu
COUNT_OF
ile öğrenebilirizFakat çağrılan tarafta bu bilgi yok
Ne yapmalıyız? C'de fonksiyonlara dizi aktarırken çoğunlukla dizinin eleman sayısını da açıkça aktarırız
struct
C programlama dilinde özel veri tipleri (!) oluşturmanın yolu
Birbiriyle ilişkili bir dizi niteliği bir yapı ("structure") içinde bir araya getiriyoruz
Kompozit veri yapıları oluşturuyoruz
Dilin çok önemli bir özelliği
Nesne yönelimli programlamada bunun bir adım ileri götürülerek
class
'a dönüşeceğini de göreceksiniz
#include <stdio.h>
struct point {
int x;
int y;
};
int main(void)
{
struct point a = { .x = 3, .y = 5 };
printf("(%d,%d)\n", a.x, a.y);
return 0;
}
(İşaretçi olmayan) struct
yapılarındaki alanlara .
operatörü ile erişiyoruz
#include <stdio.h>
struct point {
int x;
int y;
};
void dump(struct point a)
{
printf("(%d,%d)\n", a.x, a.y);
}
int main(void)
{
struct point a = { .x = 3, .y = 5 };
dump(a);
return 0;
}
Bu örnekte "değerle çağırma"nın maliyetini görebiliyor musunuz?
Her fonksiyon çağrısında iki tamsayıdan oluşan bir yapı kopyalanıyor
Belki bu örnek özelinde küçük, ama ya daha büyük
struct
'larda?"Referansla çağırma" kullanalım, yani işaretçiler
#include <stdio.h>
struct point {
int x;
int y;
};
void dump(const struct point *p)
{
printf("(%d,%d)\n", p->x, p->y);
}
int main(void)
{
struct point a = { .x = 3, .y = 5 };
dump(&a);
return 0;
}
struct
işaretçilerinde yapı içindeki alanlara ->
operatörü ile erişiyoruz
Dinamik bellek yönetimi
struct point a = { .x = 3, .y = 5 };
Bu örnekte statik olarak yer ayırıyoruz
main
fonksiyonunun yığıtında (stack
)Bu alan çok dar ve ayrıca her fonksiyon çağrısında ayırma yapılıyor (optimizasyonlar bir yana)
Dinamik bellek yönetimi: belleği çalışma zamanında yönetmek
Program çalışırken işletim sisteminden bellek talep ediyoruz:
malloc
"Dürüst vatandaşlık" gereği ayrılan kaynakla işimiz bittiğinde geri iade ediyoruz:
free
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
};
struct point *point_create(int x, int y)
{
struct point *this =
(struct point *)malloc(sizeof(struct point));
this->x = x;
this->y = y;
return this;
}
void point_destroy(struct point *this)
{
free(this);
}
void point_print(const struct point *this)
{
printf("(%d,%d)\n", this->x, this->y);
}
int main(void)
{
struct point *p = point_create(3, 5);
struct point *q = point_create(9, 7);
point_print(p);
point_print(q);
point_destroy(p);
point_destroy(q);
return 0;
}
struct
dizileri
#define COUNT_OF(array) (sizeof(array)/sizeof(array[0]))
void points_dump(const struct point points[], int nelem)
{
for (int i = 0; i < nelem; i++)
point_print(&points[i]);
}
int main(void)
{
struct point points[] = {
{ .x = 13, .y = 5 },
{ .x = -3, .y = 9 },
{ .x = 9, .y = 1 },
{ .x = 3, .y = -2 },
{ .x = 0, .y = 19 },
};
points_dump(points, COUNT_OF(points));
return 0;
}
Sıralama
Gelin bu noktaları x
koordinatlarına göre sıralayalım
int main(void)
{
struct point points[] = {
{ .x = 13, .y = 5 },
{ .x = -3, .y = 9 },
{ .x = 9, .y = 1 },
{ .x = 3, .y = -2 },
{ .x = 0, .y = 19 },
};
printf("Unsorted points\n---------------------\n");
points_dump(points, COUNT_OF(points));
points_sort(points, COUNT_OF(points));
printf("\nSorted points\n---------------------\n");
points_dump(points, COUNT_OF(points));
return 0;
}
qsort
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
Standart C kitaplığında bulunan bir fonksiyon
"Quick Sort" sıralama algoritmasını gerçekliyor
(Gerekli kılavuz sayfaları kurulmuşsa)
man qsort
ile dokümanlara erişebilirsiniz
İlk bakışta anlamakta güçlük çekeceğiniz iki husus var
void *
?void
tipinde bir işaretçi ne demek?int (*compar)(const void *, const void *)
? Bu bir fonksiyon işaretçisi?Fonksiyona işaret eden bir işaretçi, yani fonksiyon da bir tür veri?
İkisini birlikte anlatmaya çalışalım
int compare_by_x(const void *a, const void *b)
{
struct point *p = (struct point *)a;
struct point *q = (struct point *)b;
if (p->x > q->x)
return 1;
else if (p->x < q->x)
return -1;
else
return 0;
}
void points_sort(struct point points[], int nelem)
{
qsort(points, nelem, sizeof(struct point), compare_by_x);
}
void *
void *
işaretçilerinin yokluğunda qsort
nasıl yazılırdı, düşünelim?
C programlama dilinde "jenerik programlama" yapmakta kullanılan en önemli özellik
Bir süreliğine işaret edilen verinin tipiyle ilgilenmediğimiz işaretçiler için kullanılıyor
void *
işaretçileri bir noktada "cast" etmek zorundayızvoid *
işaretçilerle "aritmetik" yapamayız, neden? İşaret edilen verinin tipi bilinmiyorGenel olarak derleyicinin karşımıza çıkaracağı "tip denetimi" kısıtlarını aşmak için kullanıyoruz