C Programlama Temelleri
Algoritma kurmadan bellek yönetimine, işaretçilerden dosya işlemlerine ve önişlemciye kadar C programlamanın temelini oluşturan ayrıntılı ders notları.
C ile ilgili bu notları programlamaya giriş ve sistem programlama temellerini çalışırken oluşturdum. İlk sürüm 2013-2014 döneminin ders akışını izliyordu. Daha sonraki düzenlemelerde dil standardındaki değişiklikleri yalnız gerekli yerlerde güncelleme notu olarak ekledim; C'nin temel semantiğini güncel kolaylıklarla geriye doğru yeniden yazmadım.
Ünite 1: Programlamaya Giriş
Problem çözme
Programlama, bir problemi bilgisayarın yürütebileceği kesin adımlara dönüştürme sürecidir. Kod yazmak bu sürecin son aşamalarından biridir. Önce problem anlaşılır, girdiler ve çıktılar belirlenir, çözüm yöntemi oluşturulur ve ancak bundan sonra programlama diline geçilir.
Temel süreç:
Problemi tanımla
↓
Girdi ve çıktıları belirle
↓
Algoritmayı oluştur
↓
Veri yapılarını seç
↓
Programı yaz
↓
Derle ve çalıştır
↓
Test et
↓
Sonucu değerlendirBir programın doğru çalışması yalnız sözdiziminin doğru olmasına bağlı değildir. Program:
- doğru problemi çözmeli,
- bütün gerekli durumları ele almalı,
- hatalı girdilere karşı belirlenen davranışı göstermeli,
- kaynakları uygun kullanmalı,
- bakım yapılabilecek kadar açık olmalıdır.
Algoritma
Algoritma, belirli girdilerden istenen çıktıyı elde etmek için tanımlanmış, açık ve sonlu işlem dizisidir.
Bir algoritmanın temel özellikleri:
- adımları belirsiz olmamalıdır,
- uygulanabilir işlemlerden oluşmalıdır,
- sonlu bir noktada tamamlanmalıdır,
- aynı koşullarda tanımlı bir davranış göstermelidir,
- problemin gerekli bütün durumlarını kapsamalıdır.
Örneğin üç sayıdan en büyüğünü bulmak için:
enBuyuk <- a
eğer b > enBuyuk ise
enBuyuk <- b
eğer c > enBuyuk ise
enBuyuk <- c
enBuyuk değerini döndürBu algoritma C veya C++'tan bağımsızdır. Programlama dili yalnız algoritmanın gerçeklenme aracıdır.
Sözde kod
Sözde kod, algoritmayı belirli bir dilin ayrıntılarına bağlamadan ifade eder.
Örnek:
ortalama(liste):
toplam <- 0
listedeki her eleman için:
toplam <- toplam + eleman
döndür toplam / eleman_sayısıSözde kod:
- kontrol akışını,
- veri ilişkilerini,
- işlem sırasını
göstermelidir. Derlenebilir olmak zorunda değildir.
Atama
Atama:
x <- 5veya programlama dilinde:
x = 5;biçiminde ifade edilir.
Buradaki = matematikteki eşitlik işareti değildir. Sağ taraftaki ifade değerlendirilir ve sonuç sol taraftaki nesneye atanır.
Bu nedenle:
x = x + 1;programlama açısından anlamlıdır. Önce eski x okunur, bir artırılır ve yeni değer tekrar x içine yazılır.
Koşul
Bir işlemin yalnız belirli koşulda yapılması seçim yapısıdır.
eğer sıcaklık < 0 ise
"donma riski" yaz
değilse
"donma riski yok" yazC ve C++ karşılığı:
if (temperature < 0) {
/* işlem */
} else {
/* diğer işlem */
}Döngü
Belirli işlemlerin tekrarlanması döngüyle ifade edilir.
Koşul başta denetleniyorsa:
while koşul:
işlemBelirli sayıda tekrar için:
for başlangıç; koşul; güncelleme:
işlemKoşulun döngü sonunda denetlendiği yapı:
do:
işlem
while koşulC ve C++ bu üç temel modeli de destekler.
Fonksiyon
Fonksiyon belirli bir görevi kapsülleyen, adlandırılmış program parçasıdır.
Örnek:
int max2(int a, int b)
{
return a > b ? a : b;
}Fonksiyon kullanımı:
- tekrar eden kodu azaltır,
- programı parçalara ayırır,
- test edilebilirliği artırır,
- arabirim ile gerçekleştirim ayrımını destekler.
Akış şemaları
Akış şeması algoritmayı grafiksel olarak gösterir.
Temel semboller:
- başlangıç ve bitiş,
- işlem,
- karar,
- giriş ve çıkış,
- akış oku.
Küçük algoritmalarda öğretici olabilir. Büyük yazılımlarda sözde kod, durum diyagramları, UML ve doğrudan kaynak kod daha kullanışlı hale gelir.
Nassi-Shneiderman diyagramları yapısal programlamanın:
- sıra,
- seçim,
- tekrar
yapılarını kutular içinde gösterir. Tarihsel ve öğretici değeri vardır, ancak modern yazılım geliştirmede zorunlu bir araç değildir.
Programlama dilleri
Programlama dilleri farklı soyutlama düzeyleri sunar.
C:
- düşük düzey donanım ayrıntılarına erişebilir,
- doğrudan bellek adresleriyle çalışabilir,
- küçük çalışma zamanı maliyetine sahiptir,
- sistem programlamada güçlüdür.
C++:
- C benzeri düşük düzey olanakları,
- sınıflar,
- şablonlar,
- RAII,
- jenerik programlama,
- standart kütüphane,
- değer semantiği,
- modern tür sistemi
ile genişletir.
C++ tarihsel olarak C'den gelişmiş olsa da günümüzde "C'nin üzerine birkaç nesne yönelimli özellik eklenmiş dil" olarak düşünülmemelidir. Kendi dil kuralları, tür sistemi ve standart kütüphanesi olan bağımsız bir programlama dilidir.
Ünite 2: C Programlarının Yapısı
En küçük C programı
Standart bir C programı:
int main(void)
{
return 0;
}ile başlayabilir.
main, barındırılan standart C ortamında programın giriş noktasıdır.
Yaygın iki biçim:
int main(void)ve:
int main(int argc, char *argv[])şeklindedir.
void main() standart C programının taşınabilir giriş biçimi değildir.
Derleme süreci
Basitleştirilmiş süreç:
Kaynak kod
↓
Önişleme
↓
Derleme
↓
Assembly
↓
Nesne kodu
↓
Bağlama
↓
Yürütülebilir programGerçek araç zincirinde bazı aşamalar birleştirilebilir.
Örneğin:
cc program.c -o programkomutu önişleme, derleme, assembly ve bağlama adımlarını tek komutla yürütebilir.
Başlık dosyaları
Standart kütüphane bildirimleri başlık dosyalarıyla görünür hale getirilir.
Örnek:
#include <stdio.h><stdio.h>:
printf,fopen,fclose,fread,fwrite
gibi standart giriş çıkış işlevlerinin bildirimlerini içerir.
Standart başlıklar için:
#include <stdio.h>biçimi tercih edilir.
Projeye ait başlıklar için:
#include "my_header.h"kullanımı yaygındır.
Açıklamalar
C'de çok satırlı açıklama:
/* açıklama */biçimindedir.
C99'dan itibaren tek satırlı açıklama da standarttır:
// açıklamaAçıklama kodun ne yaptığını tekrar etmek yerine, gerektiğinde neden o şekilde yazıldığını açıklamalıdır.
Temel veri türleri
C'nin temel türlerinden bazıları:
char
short
int
long
long long
float
double
long double
_Bool / boolC23 ile bool, true ve false doğrudan dil düzeyindeki adlar arasında yer alır.
Tamsayı türlerinin kesin bit genişliği her platformda aynı olmak zorunda değildir.
Örneğin:
sizeof(int)değeri çoğu güncel sistemde 4 bayttır, ancak C standardı int türünün daima 32 bit olmasını şart koşmaz.
Kesin genişliğin önemli olduğu durumlarda:
#include <stdint.h>
int32_t
uint32_t
int64_t
uint64_tgibi türler, gerçekleştirim tarafından sağlanıyorsa kullanılabilir.
sizeof
Bir türün veya nesnenin bayt cinsinden boyutu:
sizeof(int)
sizeof variableile alınır.
sizeof(char) tanım gereği 1'dir. Ancak bir C baytının bit sayısı:
CHAR_BITile belirlenir ve standart yalnız bunun en az 8 olmasını garanti eder.
Değişken bildirimi
int count;
double average;
char grade;Başlangıç değeri:
int count = 0;
double pi = 3.141592653589793;Yerel otomatik değişken başlangıç değeri verilmeden okunmamalıdır.
Sabitler
Literal:
42
3.14
'A'
"metin"gibi kaynak kodda doğrudan yazılan değerlerdir.
Salt okunur nesne:
const int max_count = 100;ile tanımlanabilir.
C'de const ile tanımlanan nesne değiştirilemez olarak kullanılır, ancak her bağlamda derleme zamanı sabiti anlamına gelmez.
Makro sabit:
#define MAX_COUNT 100şeklinde de tanımlanabilir.
Tür güvenliği ve hata ayıklama açısından uygun olduğunda const, enum veya C23'teki sabit ifade olanakları makro yerine tercih edilebilir.
Yerel değişken
Blok içinde tanımlanan değişkenin kapsamı ilgili blokla sınırlıdır:
void f(void)
{
int x = 10;
}x bu blok dışından adıyla erişilemez.
Küresel değişken
Dosya kapsamında tanımlanan nesne:
int counter;dosya kapsamına sahiptir.
Küresel durum program parçaları arasında gizli bağımlılık oluşturabileceğinden gerektiğinde kullanılmalıdır.
static
Blok içindeki:
static int count;nesnesinin saklama süresi programın tamamıdır, fakat adı ilgili blokla sınırlıdır.
Dosya kapsamındaki:
static int value;iç bağlantıya sahiptir ve başka çeviri birimlerinden aynı adla erişilemez.
static sözcüğünün etkisi bulunduğu bağlama göre değerlendirilmelidir.
extern
Başka bir yerde tanımlanmış dış bağlantılı nesneyi bildirmek için:
extern int global_counter;kullanılabilir.
Büyük programlarda ortak değişkenleri başlık dosyalarında extern ile bildirmek, tek bir .c dosyasında tanımlamak tipik yaklaşımdır.
register
Tarihsel C'de:
register int i;derleyiciye değişkenin hızlı erişim için yazmaçta tutulabileceğine ilişkin bir ipucu verirdi.
Modern derleyiciler yazmaç atamasını optimizasyon sırasında kendileri yaptığı için bu kullanım pratik önemini büyük ölçüde kaybetmiştir. Güncel C'de kaynak uyumluluğu açısından görülebilir ancak performans yönetimi için güvenilir bir araç değildir.
Ünite 3: C'de İşleçler ve Program Denetimi
Aritmetik işleçler
+ toplama
- çıkarma
* çarpma
/ bölme
% kalanTamsayı bölmesi kesirli kısmı atar:
7 / 2sonucu:
3olur.
Kayan noktalı sonuç için operandlardan en az biri kayan nokta olmalıdır:
7.0 / 2.0sonucu:
3.5olur.
Artırma ve azaltma
++i;
--i;
i++;
i--;Ön ve son biçimlerin ifade içindeki değer üretme zamanı farklıdır.
Basit sayaç güncellemelerinde:
++i;ve:
i++;aynı son değeri oluşturur.
Bir ifadede aynı nesneyi birden fazla kez, sıralaması güvence altına alınmadan değiştiren karmaşık kullanımlardan kaçınılmalıdır.
Bileşik atama
x += 5;
x -= 2;
x *= 3;
x /= 4;genellikle:
x = x + 5;gibi ifadelerin kısa biçimidir.
Karşılaştırma işleçleri
== eşit
!= eşit değil
< küçük
> büyük
<= küçük veya eşit
>= büyük veya eşitAtama ile karşılaştırma farklıdır:
x = 5;atamadır.
x == 5karşılaştırmadır.
Mantıksal işleçler
&& VE
|| VEYA
! DEĞİLC'de && ve || kısa devre değerlendirmesi yapar.
Örneğin:
if (p != NULL && *p > 0) {
...
}ifadesinde p == NULL ise *p değerlendirilmez.
Bit işleçleri
& bit VE
| bit VEYA
^ bit XOR
~ bit tümleme
<< sola kaydırma
>> sağa kaydırmaMantıksal && ile bit düzeyindeki & aynı değildir.
Bit işleçleri:
- bayraklar,
- protokoller,
- register erişimi,
- bit maskeleri,
- sıkıştırılmış veri
üzerinde önemlidir.
if
if (score >= 50) {
passed = 1;
}else ile:
if (score >= 50) {
passed = 1;
} else {
passed = 0;
}İç içe if
if (x > 0) {
if (x % 2 == 0) {
...
}
}Derin iç içe koşullar okunabilirliği azaltabilir. Gerektiğinde koşullar sadeleştirilmeli veya işlem fonksiyonlara ayrılmalıdır.
Koşul işleci
condition ? value_if_true : value_if_falseÖrnek:
int max = a > b ? a : b;Küçük değer seçimlerinde kullanışlıdır. Büyük iş bloklarını ?: içine taşımak okunabilirliği azaltır.
switch
switch (command) {
case 1:
start();
break;
case 2:
stop();
break;
default:
handle_unknown();
break;
}break kullanılmazsa yürütme sonraki case bölümüne düşebilir. Bilinçli fall-through açık biçimde belirtilmelidir.
for
for (int i = 0; i < 10; ++i) {
printf("%d\n", i);
}Üç bölüm:
başlangıç
koşul
güncellemeişlevini görür.
while
while (condition) {
...
}Koşul ilk yinelemeden önce sınanır.
do-while
do {
...
} while (condition);Gövde en az bir kez yürütülür.
break
En yakın döngü veya switch yapısından çıkar:
if (found) {
break;
}continue
Mevcut yinelemenin kalanını atlar ve döngünün sonraki adımına geçer.
goto
C dilinde goto vardır:
goto cleanup;Genel kontrol akışında gereksiz kullanımı okunabilirliği bozar. Bununla birlikte C'de birden fazla kaynak edinilmişse tek bir hata temizleme yoluna gitmek için kontrollü kullanım bazen pratiktir:
FILE *f = fopen(path, "rb");
if (f == NULL) {
return -1;
}
void *buffer = malloc(size);
if (buffer == NULL) {
goto close_file;
}
/* işlem */
free(buffer);
fclose(f);
return 0;
close_file:
fclose(f);
return -1;Bu tür kullanım rastgele dallanma değil, yapılandırılmış hata temizleme tekniğidir.
Ünite 4: Diziler ve Karakter Dizileri
Tek boyutlu diziler
int values[5];beş int elemanı için ardışık saklama alanı oluşturur.
İndisler:
0, 1, 2, 3, 4aralığındadır.
C sınır denetimi yapmaz. Dizinin dışına erişmek tanımsız davranıştır.
Başlangıç değeri
int values[5] = {10, 20, 30, 40, 50};Boyut çıkarılabilir:
int values[] = {10, 20, 30};Kısmi başlangıç:
int values[5] = {1, 2};kalan elemanları sıfırlar.
Eleman sayısı
Aynı kapsamda gerçek dizi için:
size_t count = sizeof values / sizeof values[0];kullanılabilir.
Ancak dizi fonksiyon parametresi olarak geldiğinde çoğunlukla işaretçiye dönüşür. Bu yöntem fonksiyon parametresi üzerinde dizi uzunluğunu vermez.
Çok boyutlu diziler
int matrix[3][4];üç satır ve dört sütundan oluşan gerçek bir dizi yapısıdır.
C'de satır-major yerleşim kullanılır. Son indis bellekte ardışık değişir.
Eleman:
matrix[i][j]ile alınır.
Matris toplama
Aynı boyutlu matrislerde:
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < cols; ++j) {
c[i][j] = a[i][j] + b[i][j];
}
}Matris çarpımı
A matrisi m x n, B matrisi n x p ise sonuç m x p olur:
C[i][j] = sum A[i][k] B[k][j]Klasik algoritma:
for (size_t i = 0; i < m; ++i) {
for (size_t j = 0; j < p; ++j) {
c[i][j] = 0;
for (size_t k = 0; k < n; ++k) {
c[i][j] += a[i][k] * b[k][j];
}
}
}C karakter dizileri
C'de ayrı bir yerleşik string türü yoktur. Klasik metin temsili '\0' ile sonlanan char dizisidir.
char name[] = "Ali";bellekte:
'A' 'l' 'i' '\0'olarak tutulur.
Burada NULL ile '\0' aynı kavram değildir.
'\0': değeri sıfır olan karakterdir.NULL: null pointer constant için kullanılan makrodur.
Güvenli metin okuma
Tarihsel C kaynaklarında sık görülen:
gets(buffer);işlevi güvenli değildir ve C11 ile standarttan çıkarılmıştır. Alınacak verinin uzunluğunu sınırlayamadığı için kullanılmamalıdır.
Yerine:
fgets(buffer, sizeof buffer, stdin);kullanılabilir.
Karakter dizisi uzunluğu
#include <string.h>
size_t n = strlen(text);strlen, sonlandırıcı '\0' karakterini saymaz.
Çalışma süresi metin uzunluğu kadar ilerleme gerektirdiği için:
O(n)düzeyindedir.
Kopyalama
Standart:
strcpy(destination, source);işlevi hedef tamponun yeterli büyüklükte olduğunu varsayar. Kapasite denetlenmeden kullanımı taşmaya yol açabilir.
Boyutu açık olarak yönetmek çoğu durumda daha güvenlidir.
Örneğin:
if (strlen(source) + 1 <= destination_size) {
memcpy(destination, source, strlen(source) + 1);
}Güvenli C kodunda tampon kapasitesi veriyle birlikte düşünülmelidir.
Birleştirme
strcat(destination, source);hedefte yeterli boş alan olmasını gerektirir.
Metin birleştirme kodunda kapasite hesabı yapılmadan strcat kullanmak güvenli değildir.
Karşılaştırma
strcmp(a, b)sonucu:
- negatif:
a < b, - sıfır: eşit,
- pozitif:
a > b
anlamındadır.
Metinler:
a == bile içerik bakımından karşılaştırılmaz. Bu ifade işaretçi veya dizi bağlamına göre adres davranışıyla ilgilidir.
Ünite 5: İşaretçiler ve Bellek
İşaretçi kavramı
İşaretçi, başka bir nesnenin veya fonksiyonun adresini temsil edebilen türdür.
int value = 10;
int *p = &value;Burada:
value:int,&value:int *,p:int *,*p:int
türündedir.
Adres alma
&valuenesnenin adresini verir.
Dolaylı erişim
*pişaret edilen nesneye erişir.
*p = 20;sonrasında:
value == 20olur.
Null işaretçi
Bir işaretçinin geçerli bir nesneyi göstermediğini belirtmek için:
int *p = NULL;kullanılabilir.
Null işaretçiyi dereference etmek:
*ptanımsız davranıştır.
İşaretçi aritmetiği
Dizi elemanını gösteren işaretçide:
p + 1adres değerini bir bayt değil, işaret edilen türün bir elemanı kadar ilerletir.
Örnek:
int a[4] = {10, 20, 30, 40};
int *p = a;
printf("%d\n", *(p + 2));çıktı:
30olur.
İşaretçi aritmetiği yalnız aynı dizi nesnesi ve bir-past-the-end sınırları içinde tanımlı davranışa sahiptir.
Dizi ve işaretçi ilişkisi
Birçok ifadede:
arrayilk elemanın işaretçisine dönüşür:
&array[0]Ancak dizi ve işaretçi aynı tür değildir.
Örneğin:
sizeof arraydizinin tamamının boyutunu verebilirken:
sizeof pointeryalnız işaretçinin boyutunu verir.
İşaretçi ile dizi dolaşma
for (int *p = a; p != a + count; ++p) {
printf("%d\n", *p);
}indisli sürümle aynı temel belleği dolaşır:
for (size_t i = 0; i < count; ++i) {
printf("%d\n", a[i]);
}İşaretçi dizisi
const char *names[] = {
"Ali",
"Ayse",
"Mehmet"
};Burada names karakter dizilerinin kendisini değil, karakter dizilerine işaret eden işaretçileri tutar.
İşaretçiye işaret eden işaretçi
int value = 5;
int *p = &value;
int **pp = &p;**pp ile value değerine ulaşılabilir.
Bu yapı:
- dinamik iki boyutlu yapılar,
- çıktı parametreleri,
argv,- işaretçi değişkeninin değiştirilmesi
gibi durumlarda görülebilir.
Dinamik bellek ayırma
C'de:
#include <stdlib.h>
int *p = malloc(count * sizeof *p);ile dinamik alan ayrılabilir.
Başarı denetlenmelidir:
if (p == NULL) {
/* hata */
}Kullanım bitince:
free(p);
p = NULL;yapılabilir.
calloc
int *p = calloc(count, sizeof *p);ayrılan bütün baytları sıfır bit değerleriyle başlatır.
Bu durum her tür için semantik olarak "sıfır değerli nesne dizisi" anlamına otomatik olarak indirgenmemelidir. Tamsayı türlerinde pratik ve standart uyumlu kullanım yaygındır.
realloc
int *temp = realloc(p, new_count * sizeof *p);başarısız olduğunda eski p hâlâ geçerli olabileceği için sonucu doğrudan p üzerine yazmak risklidir.
Güvenli desen:
int *temp = realloc(p, new_count * sizeof *p);
if (temp != NULL) {
p = temp;
}Bellek hataları
C'de önemli hata sınıfları:
- null dereference,
- buffer overflow,
- out-of-bounds erişim,
- use-after-free,
- double free,
- dangling pointer,
- memory leak,
- uninitialized read.
Bu hataların çoğu derleme sırasında yakalanmayabilir.
Derleyici uyarıları ve çalışma zamanı araçları önemlidir:
-Wall
-Wextra
-Wpedantic
-fsanitize=address
-fsanitize=undefinedgibi seçenekler geliştirme sırasında yararlı olabilir.
Ünite 6: C Fonksiyonları
Fonksiyon bildirimi
Prototip:
int sum(int a, int b);Tanım:
int sum(int a, int b)
{
return a + b;
}Çağrı:
int result = sum(3, 4);void fonksiyon
Değer döndürmeyen fonksiyon:
void print_message(void)
{
puts("Merhaba");
}void parametre listesi C'de fonksiyonun parametre almadığını açıkça belirtir.
Parametre aktarımı
C'de parametreler değer ile aktarılır.
void f(int x)
{
x = 10;
}çağıranın değişkenini değiştirmez.
Kaynaklarda bazen "referans ile çağırma" denilen C tekniği gerçekte işaretçi değerinin fonksiyona geçirilmesidir.
void set_value(int *p)
{
*p = 10;
}çağrı:
int x = 0;
set_value(&x);Burada yine p parametresinin kendisi değer ile aktarılmıştır. Ancak bu değer x nesnesinin adresidir.
C'de C++ anlamındaki reference türü yoktur.
Çıktı parametresi
Bir fonksiyon birden çok sonuç üretecekse:
int divide(int a, int b, int *quotient, int *remainder)
{
if (b == 0) {
return 0;
}
*quotient = a / b;
*remainder = a % b;
return 1;
}kullanılabilir.
Diziyi fonksiyona aktarma
double average(const int values[], size_t count);parametre bağlamında:
const int values[]işlevsel olarak işaretçi parametresidir.
Bu nedenle eleman sayısı ayrıca taşınmalıdır.
const parametre
Fonksiyon işaret edilen veriyi değiştirmeyecekse:
void print_array(const int *values, size_t count);bildirimi niyeti açıklar ve yanlış değişikliği engeller.
Küresel değişkenlerle veri paylaşımı
Küresel değişken fonksiyonlar arasında veri paylaşabilir, ancak:
- bağımlılıkları gizler,
- testleri zorlaştırır,
- eşzamanlılık sorunları oluşturabilir.
Fonksiyon parametreleri ve açık veri yapıları çoğu zaman daha iyi tasarım sağlar.
static yerel değişken
int next_id(void)
{
static int id = 0;
return ++id;
}id yalnız fonksiyon kapsamında görünür fakat program boyunca varlığını sürdürür.
Bu tür gizli durum thread-safety ve yeniden giriş açısından ayrıca değerlendirilmelidir.
Özyineleme
Fonksiyon kendisini çağırabilir.
Faktöriyel:
unsigned long long factorial(unsigned n)
{
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}Her özyinelemeli algoritmada:
- taban durum,
- taban duruma ilerleme
bulunmalıdır.
Özyineleme çağrı yığını kullanır. Çok derin özyineleme stack overflow oluşturabilir.
Ünite 7: C Yapıları, Birlikler ve Bit Alanları
struct
struct, farklı türde alanları tek veri yapısında birleştirir.
struct Student {
int id;
char name[64];
double grade;
};Nesne:
struct Student s;Alan erişimi:
s.id = 10;typedef
typedef struct {
int x;
int y;
} Point;sonrasında:
Point p;kullanılabilir.
C23 ile etiket ve typedef ihtiyaçları bazı yeni dil olanaklarıyla sadeleşmiş olsa da geleneksel biçim çok yaygındır.
Başlangıç değeri
struct Point {
int x;
int y;
};
struct Point p = {10, 20};Belirlenmiş başlatıcı:
struct Point p = {
.x = 10,
.y = 20
};özellikle çok alanlı yapılarda okunabilirliği artırır.
Yapı işaretçisi
struct Student *p = &s;alan:
p->idile alınır.
Bu:
(*p).idifadesinin kısa biçimidir.
Yapının fonksiyona aktarılması
Değer ile:
void print_point(struct Point p);veya işaretçi ile:
void print_point(const struct Point *p);aktarılabilir.
Büyük yapılarda gereksiz kopyayı önlemek ve salt okunur erişimi belirtmek için const işaretçi uygun olabilir.
Dizi içinde struct
struct Student students[100];yapısında her eleman bir Student kaydıdır.
union
Birlikte bütün üyeler aynı depolama alanını paylaşır:
union Value {
int i;
double d;
};Aynı anda alanın yalnız uygun aktif yorumunun kullanılması gerekir.
union:
- protokol çözümleme,
- donanım temsili,
- tagged union
gibi yapılarda kullanılabilir, ancak tür yorumlama kuralları dikkatle ele alınmalıdır.
Etiketli birlik
Birliğin hangi üyesinin geçerli olduğunu ayrıca tutmak daha güvenlidir:
enum ValueType {
VALUE_INT,
VALUE_DOUBLE
};
struct Value {
enum ValueType type;
union {
int i;
double d;
} data;
};Bit alanları
struct Flags {
unsigned ready : 1;
unsigned mode : 3;
};belirli bit genişlikli alanlar tanımlar.
Ancak:
- alan yerleşimi,
- sıralama,
- padding
gibi ayrıntılar gerçekleştirim tanımlı olabilir.
Ağ protokolü veya kalıcı dosya biçimi gibi tam bit düzeninin zorunlu olduğu yapılarda açık maskeler ve kaydırmalar daha taşınabilir olabilir.
Ünite 8: C Dosya İşlemleri ve Standart Kütüphane
Dosya açma
FILE *f = fopen("data.txt", "r");Başarı denetlenmelidir:
if (f == NULL) {
perror("fopen");
return 1;
}Açma kipleri
Yaygın kipler:
r oku
w yaz, varsa içeriği sıfırla
a sona ekle
r+ oku/yaz
w+ oku/yaz, içeriği sıfırla
a+ oku ve sona ekleİkili kip için:
rb
wb
abgibi biçimler kullanılır.
Özellikle Windows gibi metin ve ikili kip ayrımı bulunan sistemlerde doğru kip önemlidir.
Dosya kapatma
if (fclose(f) != 0) {
/* hata */
}Yazma hatalarının bir bölümü tampon boşaltılırken ortaya çıkabileceği için fclose sonucu önemli olabilir.
Karakter okuma ve yazma
int ch = fgetc(f);fgetc dönüş türü inttir; çünkü bütün unsigned char değerlerinin yanında EOF değerini de temsil etmesi gerekir.
Yazma:
fputc('A', f);Satır okuma
char line[256];
while (fgets(line, sizeof line, f) != NULL) {
...
}tampon sınırı belirli olduğundan gets kullanımından daha güvenlidir.
Biçimlendirilmiş G/Ç
Yazma:
fprintf(f, "%d %.2f\n", id, value);Okuma:
int n = fscanf(f, "%d %lf", &id, &value);scanf ailesinde dönüş değeri okunabilen alan sayısını gösterir ve mutlaka denetlenmelidir.
İkili yazma
size_t written = fwrite(buffer, element_size, count, f);İkili okuma:
size_t read_count = fread(buffer, element_size, count, f);Sonuçlar beklenen eleman sayısıyla karşılaştırılmalıdır.
Struct'ı doğrudan dosyaya yazmak
fwrite(&record, sizeof record, 1, f);aynı program içinde geçici veri için çalışabilir, ancak taşınabilir kalıcı dosya biçimi değildir.
Nedenleri:
- padding,
- endian,
- tür genişliği,
- ABI,
- derleyici,
- sürüm değişiklikleri.
Kalıcı veya ağ üzerinden taşınan veri için açık serileştirme biçimi kullanılmalıdır.
Dosya konumu
fseek(f, offset, SEEK_SET);
long pos = ftell(f);
rewind(f);rastgele erişim için kullanılabilir.
Büyük dosyalarda platformun büyük dosya API'leri ve off_t benzeri türleri gerekebilir.
Dosya silme ve yeniden adlandırma
remove("file.txt");
rename("old.txt", "new.txt");standart C kütüphanesinde bulunur.
Dosya sistemi izinleri ve yarış koşulları işletim sistemine bağlıdır.
Karakter sınıflandırma
#include <ctype.h>
isdigit(c)
isalpha(c)
isspace(c)
toupper(c)
tolower(c)Bu işlevlere EOF veya unsigned char olarak temsil edilebilen değerler verilmelidir. Negatif char değerini doğrudan geçirmek tanımsız davranış oluşturabilir.
Güvenli kalıp:
toupper((unsigned char)c)Matematik kütüphanesi
#include <math.h>
sqrt
pow
sin
cos
tan
log
log10
exp
fabsişlevleri yaygındır.
Tarih ve zaman
#include <time.h>başlığı:
time,difftime,mktime,strftime
gibi olanaklar sağlar.
Yerel saat, zaman dilimi ve takvim işlemleri platform ayrıntıları içerdiğinden salt saniye değerinden daha karmaşıktır.
Ünite 9: C Önişlemcisi
Önişleme
# ile başlayan yönergeler derlemenin sözdizim çözümünden önce önişlemci tarafından işlenir.
Başlıca kullanım:
- dosya dahil etme,
- makro,
- koşullu derleme,
- derleme zamanı yapılandırması.
Nesne benzeri makro
#define BUFFER_SIZE 4096Makro metinsel yer değiştirmedir. Türü yoktur.
Bir sabit nesne veya enum daha uygun olabiliyorsa makro kullanmak zorunlu değildir.
Fonksiyon benzeri makro
#define SQUARE(x) ((x) * (x))Parantez kullanımı öncelik sorunlarını azaltır.
Buna rağmen:
SQUARE(i++)tehlikelidir, çünkü i++ birden fazla değerlendirilir.
C'de uygun olduğunda inline fonksiyon daha güvenlidir:
static inline int square_int(int x)
{
return x * x;
}# ve ##
Stringification:
#define TO_STRING(x) #xToken birleştirme:
#define JOIN(a, b) a##bİleri makro metaprogramlamada kullanılır ancak gereksiz karmaşıklık oluşturabileceğinden ölçülü kullanılmalıdır.
#undef
#undef NAMEdaha önce tanımlanmış makroyu kaldırır.
Koşullu derleme
#ifdef DEBUG
...
#endifve:
#if FEATURE_LEVEL >= 2
...
#elif FEATURE_LEVEL == 1
...
#else
...
#endifkullanılabilir.
Header guard
#ifndef MY_HEADER_H
#define MY_HEADER_H
/* bildirimler */
#endifaynı başlığın bir çeviri biriminde birden fazla kez etkili olmasını önler.
#pragma once birçok derleyicide desteklenir, ancak ISO C standardının zorunlu bir yönergesi değildir.