C dilinde değişken tanımlamayı ve temel veri türlerini gördük. Değişkenlere atanan ifadeler her zaman başka bir değişken olmayabilir. Derleyiciler için nesne belirtmeyen, yani sağ-taraf değeri olan ifadeler de değişkenlere atanabilir. Bu ifadeler bildiğimiz tamsayılar, ondalıklı sayılar veya karakterler olabilir. Aslında şu ana kadar birçoğunun koda nasıl yazılacağını gördük. C dilinde bunlara sabit (constant) adı verilir. Bu bölümde C dilinde sabit olan ifadeleri görecek ve sizeof operatörünü inceleyeceğiz.
Öncelikle sabitler ile ilgili şu küçük açıklamayı yapmamız gerek: C dilindeki sabitler ile C++ dilindeki sabitler aynı şeyi ifade etmezler. C++ dilinde burada göreceğimiz sabitlerin çoğu değişmez (literal) olarak adlandırılır. Ancak C’de değişmez ve sabitler farklı anlamlara gelir. C’de genellikle kaynak koda yazılan ve doğrudan makine koduna dahil edilen nesne belirtmeyen değişmez ifadelere sabit adı verilirken, nesne belirten (yani sol taraf değeri olan) değişmez ifadelere ise değişmez adı verilmektedir. C’de C99 standartlarına kadar bulunan tek değişmez String değişmezi (String literal) iken C99 standardı ile birleşik değişmezler (compound literals) dile eklenmiştir.
Bu yazıda değişmezleri ele almayacak, sadece sabitlerden bahsedeceğiz. Öncelikle C’de C23 standartlarına kadar olan tüm sabit kategorilerini listeleyelim:
- Tam Sayı Sabitleri
- Onluk Tabanda Tam Sayı Sabitleri
- Sekizlik Tabanda Tam Sayı Sabitleri
- Onaltılık Tabanda Tam Sayı Sabitleri
- İkilik Tabanda Tam Sayı Sabitleri (C23)
- Kayan Noktalı Sayı Sabitleri
- Onluk Tabanda Kayan Noktalı Sayı Sabitleri
- Onaltılık Tabanda Kayan Noktalı Sayı Sabitleri (C99)
- Karakter Sabitleri
- Önceden Tanımlı Sabitler (C23)
truefalsenullptr
- Numaralandırma Sabitleri
Sabitler yazılış biçimlerine göre bir tür bilgisine sahip olurlar. Özellikle karakter sabitlerinde ve kayan noktalı sayı sabitlerinde çok farklı yazılış biçimleri, ön ekler ve son ekler bulunmaktadır. Ancak elbette burada hepsini anlatmayacağım. Numaralandırma sabitleri ise tamamen bu yazının kapsamı dışındadır. Temel olarak C’de yer alan ve sık kullanılan sabitleri, onların yazılış biçimlerini ve tür bilgilerini ele almaya çalışacağım. Bunun yanında sabitlere sembolik isimler vererek onları kodda daha rahat kullanmamıza yarayan #define önişlemci komutundan da bahsedeceğim.
Ayrıca bir önceki yazıda türlerin boyutlarından ve bellek yerleşimlerinden bahsetmiştim. Her türün bir boyut bilgisinin olduğunu ve bu boyut bilgisinin sistemden siteme değişiklik gösterdiğini anlatmıştım. Ancak türlerin boyut bilgisini programın içerisinde nasıl öğrenebileceğimiz konusundan bahsetmemiştim. İşte bu yazıda ayrıca türlerin boyut bilgisini öğrenmemize yarayan sizeof operatörünü de inceleyeceğiz.
C’de Sabitlerin Kullanımı
C’de sabitleri genellikle değişkenlere ilk değer verirken veya ifadelerde çeşitli hesaplamalar yaparken kullanırız. Bu sabit türlerinden biri olan tam sayı sabitleri belki de karşımıza en çok çıkan sabitlerdir. Bunlarla ilgili birkaç örnek gösterelim:
int a = 56;
int b = 44;
int c = a + b + 50; // c = 100
int d = c / 3; // d = 50
Evet yukarıda gördüğümüz 56, 44, 50 ve 3 gibi sayıların hepsi birer tam sayı sabitidir. Yine daha önce bahsettiğim gibi bu sabitleri değişkenlere ilk değer verirken veya ifadelerin içerisinde sıklıkla kullanmaktayız. Tabii kayan noktalı sayı sabitleri ve karakter sabitleri de yine sıklıkla karşımıza çıkmaktadır:
double a = 5.78;
double b = 0.901;
char c = 'a'; // c = 97
char d = '9'; // d = 57
Yine C23 standartları ile dile eklenecek olan true, false, nullptr gibi anahtar kelimeler de birer sabit sayılmaktadır. Bunlara da önceden tanımlı sabitler denilmektedir. Çünkü bu sabitler kullanıcı tarafından herhangi bir biçimde yazılabilen sabitler gibi değil, belli bir kelime şeklinde yazılan sabitlerdir. Derleyici de bu kelimeleri gördüğünde onları belli türe sahip bir sabit olarak algılar. Örnekler:
// C23
bool a = true;
bool b = false;
int *c = nullptr;
"nullptr" sabitinin ayrıntılarından burada değil, göstericiler (pointers) konusunu konuşurken bahsedeceğim. Elbette tüm bu sabitlerin birer türleri vardır. Normal değişkenler ve ifadeler gibi onlar da bir türden başka bir türe dönüştürlebilir veya yükseltilebilirler. Bu dönüşümlerden de burada değil, tür dönüşümlerini anlatırken bahsedeceğim. Ancak her bir sabitin tür bilgisinin ne olduğunu bu yazıda anlatacağım. Yine sabitlerin tür bilgilerini değiştirmeye yarayan ön ek ve son eklerden bahsedeceğim. Şimdi sırasıyla sabitleri inceleyelim.
Tam Sayı Sabitleri
İfadelerde doğrudan tam sayı türünden kullanılabilen sabitlere tam sayı sabitleri (integer constants) adı verilmektedir. Tam sayı sabitleri, yazıldığı tabana göre 4 kategoriye ayrılabilir:
- Onluk Tabanda Tam Sayı Sabitleri
- Sekizlik Tabanda Tam Sayı Sabitleri
- Onaltılık Tabanda Tam Sayı Sabitleri
- İkilik Tabanda Tam Sayı Sabitleri (C23)
Sıfırdan farklı bir onluk tabanda bir rakam (1, 2, 3, 4, 5, 6, 7, 8, 9) ile başlayıp devamında sıfır veya daha fazla sayıda onluk tabanda rakam (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) ile devam ederek yazılan sabitlere onluk tabanda tam sayı sabitleri (decimal integer constants) adı verilmektedir. En bilindik ve en sık kullanılan tam sayı sabitleridir. Örnekler:
1
10
123
7890
123123214
Sıfır ile başlayıp devamında sıfır veya daha fazla sayıda sekizlik tabanda bir rakam (0, 1, 2, 3, 4, 5, 6, 7) ile devam ederek yazılan sabitlere sekizlik tabanda tam sayı sabitleri (octal integer constants) adı verilmektedir. Örnekler:
int a = 07; // a = 7
int b = 01231; // b = 665
int c = 06057; // c = 3119
Burada harici olarak nasıl taban dönüşümlerinin yapıldığını anlatmayacağım. Eğer "0" rakamından sonra 7’den büyük bir rakam girerseniz, derleme zamanında şu gibi bir hata alırsınız:
int a = 08; // error: invalid digit "8" in octal constant
"0x" veya "0X" ile başlayıp devamında bir veya daha fazla sayıda onaltılık tabanda bir rakam (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, a, b, c, d, e, f) ile devam ederek yazılan sabitlere onaltılık tabanda tam sayı sabitleri (hexadecimal integer constants) adı verilmektedir. Örnekler:
int a = 0xFF; // a = 255
int b = 0XeF; // b = 239
int c = 0x89e; // c = 2206
Buradaki harflerin büyük ya da küçük olarak yazılmasının bir önemi yoktur. C23 standartları ile birlikte ikilik tabanda yazım da dile eklenecektir. Bu tür sabitlere de ikilik tabanda tam sayı sabitleri (binary integer constants) adı verilecektir. Bu tür sabitlerde ise başa "0b" veya "0B" ön eki getirilerek daha sonrasında sadece "0" veya "1" rakamlarından biri yerleştirilebilecektir. Örnekler:
// C23
int a = 0b10101; // a = 21
int b = 0B010100; // b = 20
int c = 0b1111111; // c = 127
Peki bütün bu tam sayı sabitlerinin türü nedir? Bu konu aslında doğrudan "şudur" diyebileceğimiz kadar basit değildir. Öncelikle yazılan sabitin hangi temel veri türü aralığına girdiğini anlamak gerekir. Ayrıca onluk tabanda yazım ile diğer tabanda yazımlar arasında farklılıklar bulunmaktadır. Bunu bir örnek üzerinde açıklamaya çalışalım. Öncelikle int türünün 32-bit, long türünün 64-bit ve long long türünün de 64-bit olduğu bir sistem (LP64) düşünelim:
324324 // int
8223372036854775807 // long
Bu sistemde int türünün maksimum alabileceği değer "2.147.483.647" olduğundan, bu değeri geçen sabitler long türünden kabul edilecektir. Yani sabitin alabileceği değer veri türünün kabul edilen aralığına göre belirlenir. Sekizlik, onaltılık ve ikilik tabanda yazımların bu durumdan bir farkı vardır. Onlar unsigned olan türlerden de olabilirler. Sekizlik tabandan bir örnek gösterelim:
0123 // int
030000000007 // unsigned int
070000000007 // long int
077777777777777777777777 // long unsigned int
Bu örnek sistemde long türü ile long long türü aynı uzunlukta olduğundan, herhangi bir son ek koymadan long long veya unsigned long long türünden sabit elde etmek mümkün olmayacaktır. Son ek demişken bu türleri çeşitli son ekler ile kısıtlayabiliriz. Örneğin "l" veya "L" son eki ile yazdığımız sabitin en az long türünden olmasını; "u" veya "U" son eki ile de sabitin en az unsigned int türünden işaretsiz bir tam sayı sabiti olmasını sağlayabilirsiniz:
324324 // int
324324U // unsigned int
324324l // long
Bunları küçük veya büyük harf olarak yazmanız arasında hiçbir fark yoktur. Yine bu iki son eki birleştirip unsigned long kısıtlaması da yapabilirsiniz:
324324UL // unsigned long
"UL", "LU", "uL", "lu" gibi yazımların tamamı birbirine denktir. Eğer kısıtlamayı long long türünden yapmak isterseniz, C99 ile dile eklenen "ll" veya "LL" son eklerinden birini kullanabilirsiniz:
12ll // long long
324324LL // long long
"U" ve "LL" son eklerini aynı anda kullanarak unsigned long long kısıtlaması yapabilirsiniz:
12ull // unsigned long long
324324LLU // unsigned long long
Şimdi de C23 standartlarında, alınan son eklerle ve yazılan tabana göre sabitin küçükten büyüğe doğru hangi türleri alabileceğini gösteren tabloyu verelim (burada "WB" ve "UWB" son ekleri C23 standardı ile _BitInt ve unsigned _BitInt türlerine dönüştürme yapma amacıyla eklenmiştir, şimdilik onlara aldırış etmeyin):
| Son Ek | Onluk Tabanda Sabitler | İkilik, Sekizlik, Onaltılık Tabanda Sabitler |
|---|---|---|
| Yok | int long int long long int |
int unsigned int long int unsigned long int long long int unsigned long long int |
| u veya U | unsigned int unsigned long int unsigned long long int |
unsigned int unsigned long int unsigned long long int |
| l veya L | long int long long int |
long int unsigned long int long long int unsigned long long int |
| u veya U ile l veya L | unsigned long int unsigned long long int |
unsigned long int unsigned long long int |
| ll veya LL (C99) | long long int | long long int unsigned long long int |
| u veya U ile ll veya LL (C99) | unsigned long long int | unsigned long long int |
| wb veya WB (C23) | _BitInt(N) | _BitInt(N) |
| u veya U ile wb veya WB (C23) | unsigned _BitInt(N) | unsigned _BitInt(N) |
Son olarak bu konuyla ilgili bir şeye daha değineceğim. C++ diline C++14 ile eklenen tek tırnak (‘) ile uzun tam sayı sabitlerini ayırma özelliği, C23 standartları ile C diline de eklenecektir. Bu özellik ile uzun tam sayı sabitlerinin arasına tek tırnak (‘) yerleştirerek, kodu okuyan kişiye kolaylık sağlayabilirsiniz. Derleyiciler sabitlerin arasına yerleştirilen tek tırnaklara aldırış etmeyecektir. Örneğin aşağıdaki değişkenlerin hepsinin değeri birbirine eşittir:
unsigned long long l1 = 12346744073709550592ull; // C99
unsigned long long l2 = 12'346'744'073'709'550'592llu; // C23
unsigned long long l3 = 1234'6744'0737'0955'0592uLL; // C23
unsigned long long l4 = 123467'440737'0'95505'92LLU; // C23
Kayan Noktalı Sayı Sabitleri
Değişkenlere değer verirken veya ifadelerin içerisinde doğrudan kullanılabilen kayan noktalı sayılara kayan noktalı sayı sabitleri (floating constants) adı verilmektedir. Kayan noktalı sayı sabitlerini C dilinde tam (whole-number) ve kesirli (fractional) kısımlarının arasına "." koyarak yazabilirsiniz (önceki yazılarda da görmüştünüz). Ancak tam kısmı "0" olan bir kayan noktalı sayı sabitini sadece "." ile başlatmanız mümkündür:
123.56
10.5
0.7
.7 // 0.7
Aynı şekilde kesirli kısmı "0" olan bir kayan noktalı sayı sabitinin de sonuna sadece "." koyabilirsiniz:
8.0
1. // 8.0
Bu şekilde derleyici onları birer kayan noktalı sayı sabiti olarak değerlendirecektir. Kayan noktalı sayı sabitleri yazılış biçimine göre iki kategoriye ayrılabilir:
- Onluk Tabanda Kayan Noktalı Sayı Sabitleri
- Onaltılık Tabanda Kayan Noktalı Sayı Sabitleri
Az önceki gibi tam ve kesirli kısmını onluk tabanda yazarsanız, bu tip kayan noktalı sayı sabitlerine onluk tabanda kayan noktalı sayı sabitleri (decimal floating-point constant) adı verilmektedir. Eğer tam ve kesirli kısımları onaltılık tabanda yazarsanız, bu tip kayan noktalı sayı sabitlerine onaltılık tabanda kayan noktalı sayı sabitleri (hexadecimal floating-point constant) adı verilmektedir.
Az önce gördüğümüz sabitler onluk tabandaki kayan noktalı sayı sabitlerine örnektir. Onaltılık tabandaki kayan noktalı sayı sabitleri ise C99 standartları ile dile eklenmiştir. Burada ayrıntısına girmeyecek, sadece kafanızda bir fikir oluşturması açısından örnek verip geçeceğim:
0x1.2p3
Çok büyük veya çok küçük gerçek sayıları kısa ondalık üs formunda yazmaya bilimsel gösterim (scientific notation) adı verilmektedir. Bilimsel gösterim hem yazma hem de işlem yapma açısından matematikte bize kolaylık sağlar. C’de de kayan noktalı sayı sabitlerinde bu gösterime benzer kısa yazımlar yapmak mümkündür. Bunun için ondalık tabanda yazım için "e" veya "E" son eki kullanılmaktadır. Örnekler üzerinden gidelim:
3.2e5 // 320000.0
3.2e-4 // 0.000320
0.00002E4 // 0.2
5E-2 // 0.05
5.2E0 // 5.2
Burada "e" ve "E" kullanımı arasında herhangi bir kullanım farklılığı yoktur. Bir kayan noktalı sayının sonuna "e" veya "E" getirdiğinizde, bu durum sayının 10 üzeri "x" ile (10x) çarpılacağını belirtir. Buradaki "x" sayısı ise "e" harfinden sonra gelen sayıdır. Bu sayı negatif veya pozitif bir tam sayı olabilir (veya sıfır da olabilir). İşte yukarıdaki örneklerdeki sonuçlar da bu mantığa göre bulunabilmektedir. Onlatılık tabandaki kayan noktalı sayılarda ise "p" veya "P" son eki kullanılır. Ancak bunun ayrıntısına burada girilmeyecektir.
Çok kısa bir hatırlatma yapmaktada fayda var: "e" veya "E" harfinden sonra gelen sayının başına "+" yazmamız da mümkündür. Bu bazı durumlarda okunabilirliği artırabilmektedir. Elbette sıfırın başına da "+" veya "-" yerleştirebilirsiniz. Söz dizimi açısından bir sorun olmasa da bunun pek bir anlamı yoktur. Örnek:
3.2e+5 // 320000.0
0.00002E+4 // 0.2
5.2E+0 // 5.2
5.2E-0 // 5.2
Şimdi yazım biçimleri gördükten sonra tür konusuna gelebiliriz. Şu ana kadar gördüğümüz yazımların tümü C dilinde double olarak kabul edilmektedir. Bu tür bilgisini elbette tam sayılarda olduğu gibi çeşitli son ekler ile değiştirmeniz mümkündür. Bu son ekleri C23 standartları için şöyle sıralayabiliriz:
| Son Ek | Tür |
|---|---|
| Yok | double |
| f veya F | float |
| l veya L | long double |
| df veya DF (C23) | _Decimal32 |
| dd veya DD (C23) | _Decimal64 |
| dl veya DL (C23) | _Decimal128 |
_Decimal32, _Decimal64 ve _Decimal128 türlerine dönüştürme son ekleri C23 standartları ile dile eklenecektir. Bunlardan burada bahsetmeyeceğim. Diğerleri için çeşitli örnekler verip konumuzu bitirelim:
double a = 1.23e4;
double b = .32;
float c = 112.34f;
float d = 1.2E-4F;
long double e = 1.232132E2l;
long double f = 12.09L;
Karakter Sabitleri
C’de tek tırnak arasına yazılan sabitlere karakter sabitleri (character constants) adı verildiğinden zaten bahsetmiştim. Daha önce "Basic Execution Character Set" isimli karakter kümesinden bahsetmiştim. İşte tırnak içerisine normalde bu karakter kümesinin bir alt kümesi olan "Basic Character Set" isimli karakter kümesi yazılabilmektedir. Bu karakter kümesinde yer alan karakterlere buradan erişebilirsiniz. Bu karakter kümesi C kaynak kodunda kullanılabilecek karakterleri içermektedir.
Ancak bu karakter kümesi içerisinde yer alan her karakter de tek tırnak içerisinde doğrudan kullanılmaz. Tek tırnak işareti (‘), ters eğik çizgi işareti (\) ve yeni satır karakteri gibi karakterler buna dahildir. Eğer bu 3 karakter dışında kalan "Basic Character Set" karakterlerinden biri tek tırnak içerisinde kullanılırsa, derleyici bu sabiti int türünden algılayıp karakterin ASCII’deki tam sayı karşılığını sabitin kullanıldığı yere yapıştıracaktır:
char a = 'A'; // ASCII: 65
char b = '-'; // ASCII: 45
char c = '2'; // ASCII: 50
Peki biz tek tırnak ve ters eğik çizgi işaretini, karakter sabiti içerisinde nasıl kullanabiliriz? Bunun için daha önce gördüğümüz kaçış dizgelerinde (escape sequences) kullanılan ters eğik çizgi işaretini şu şekilde kullanabilirsiniz:
char test1 = '\''; // Tek tırnak işareti (ASCII: 39)
char test2 = '\\'; // Ters eğik çizgi işareti (ASCII: 92)
Yine soru işareti (?) ve çift tırnak (") karakterlerini de aynı şekilde ters eğik çizgi işareti ile birlikte yazabilirsiniz. Ancak bunları ters eğik çizgi işareti kullanmadan da yazabilirsiniz:
char question1 = '?'; // ASCII: 63
char question2 = '\?'; // ASCII: 63
char quote1 = '"'; // ASCII: 34
char quote2 = '\"'; // ASCII: 34
Kaçış dizgelerinde kullanılan "\a", "\b", "\f", "\n", "\r", "\t", "\v" karakterleri (yani ekrana herhangi bir grafiksel karakter basmayan veya boşluk bırakan karakterler) de ters eğik çizgi ile tek tırnak içerisinde karakter sabiti olarak kullanılabilirler:
char a = '\a'; // ASCII: 7
char b = '\b'; // ASCII: 8
char f = '\f'; // ASCII: 12
char n = '\n'; // ASCII: 10
char r = '\r'; // ASCII: 13
char t = '\t'; // ASCII: 9
char v = '\v'; // ASCII: 11
ASCII karakterlerini sekizlik veya onaltılık formatlarda karakter sabiti içerisinde tutabilirsiniz. Sekizlik yazım için "\" karakterinden sonra, karakterin sekizlik tabandaki karşılığı yazılır. Onaltılık yazım için ise "\x" karakterlerinden sonra, karakterin onaltılık tabandaki karşılığı yazılır. Örneğin "M" karakterinin ASCII tablosundaki sekizlik karşılığı "115", onaltılık karşılığı ise "4D" olduğundan, bu karakteri karakter sabiti olarak şu şekillerde yazabiliriz:
char normal = 'M'; // ASCII: 77
char octal = '\115'; // ASCII: 77
char hex = '\x4D'; // ASCII: 77
C99 standartları ile birlikte, karakterin Unicode karşılığını karakter sabitine yazma olayı dile eklenmiştir. Bunu yine "\u" veya "\U" karakterleriyle yazmanız mümkündür. Ancak bu konuyu bu yazıda ele almayacağım.
Peki tek tırnaklar içerisine birden fazla karakter veya ASCII karşılığı olmayan geçersiz bir karakter yazılamaz mı? Aslında şimdiye kadar gördüğümüz ve ASCII karşılığı olan karakter sabiti yazımlarına tek byte tam sayı karakter sabiti (single-byte integer character constant) adı verilmektedir. Tek byte ile ifade edilemeyen ve tek tırnak içerisine birden fazla karakter yazarak oluşturulan sabitlere ise çoklu karakter sabiti (multicharacter constant) adı verilmektedir. Bu karakter sabitlerinin hangi tam sayıya karşılık geleceği implementasyona bağlı (implementation-defined) bir durumdur. Örnekler:
char m1 = 'ab'; // 98
char m2 = 'cv'; // 118
char m3 = 'my1'; // 49
Gördüğünüz gibi yukarıdaki örneği denediğimiz sistemde, karakter sabiti içerisine yazılan son karakterlerin ASCII karşılığını değer olarak üretmiştir. Ancak her sistemde sonucun bu şekilde çıkacağı kesin değildir. Aslında en doğrusu bu tip bir kullanımdan kaçınmak olacaktır.
yukarıdaki iki kullanımın da türünün int olduğunu söylemiştik. Tek byte tam sayı karakter sabitlerinde "Basic Character Set" içerisinde yer alan ve char türüne de sığan karakterlerin, char türü ile aynı boyutta olacağı garanti altına alınmışken, çoklu karakter kullanımı gibi kullanımların değeri implementasyona göre değişir. Bunun yanında karakter sabitinin türünü değiştiren çeşitli ön ekler de bulunmaktadır.
Karakter sabitinin türünü değiştiren ön eklerin hepsinin ayrıntısına elbette burada girmeyeceğim. Sadece bir tablo üzerinde tür, isimlendirme ve kullanım örneğini verip konuyu tamamlayacağım:
| Ön Ek (Söz Dizimi) | İsim | Tür | Kullanım Örneği |
|---|---|---|---|
| u8′<tek_karakter>’ (C23) | UTF-8 Karakter Sabiti (UTF-8 Character Constant) | char8_t | u8’a’ |
| u'<tek_karakter>’ (C11) | UTF-16 Karakter Sabiti (UTF-16 Character Constant) | char16_t | u’貓’ |
| U'<tek_karakter>’ (C11) | UTF-32 Karakter Sabiti (UTF-32 Character Constant) | char32_t | U’貓’ |
| L'<tek_karakter>’ | Geniş Karakter Sabiti (Wide Character Constant) | wchar_t | L’β’ |
| L'<coklu_karakter>’ | Geniş Çoklu Karakter Sabiti (Wide Multicharacter Constant) | wchar_t | L’AB’ |
Önceden Tanımlı Sabitler (C23)
C’de bazı anahtar kelimeler, belli bir türe ve değere sahip bir sabiti belirtirler. Bu anahtar keliemelere önceden tanımlı sabitler (predefined constants) adı verilmektedir. Bu sabit kategorisinin ismi ilk defa C23 standartları ile kullanılmaya başlanacaktır. Daha önce gördüğümüz true ve false anahtar kelimeleri ile yine kısaca bahsettiğim nullptr anahtar kelimesi bu sabit kategorisinin şimdilik tamamını oluşturmaktadır. Örnek:
// C23
bool test1 = false;
bool test2 = true;
int *p1 = nullptr;
C23 standartlarından önce boş göstericileri belirtmek için NULL isimli sabit kullanılmaktaydı (hala daha kullanılabilir, kaldırılmadı). Ancak bu sabitin derleyici implementasyonuna göre değişen (ve genellikle tam sayı olan) değeri, dikkatli kullanılmadığında gösterici işlemlerinde bazı sorunlara neden olabilmekteydi. Bu nedenle C++11 standardı ile C++ diline eklenmiş olan nullptr anahtar kelimesi, C23 standartları ile de C diline eklenmek istenmiştir. Bu anahtar kelimeyle ilgili ayrıntıları göstericiler konusunu anlatırken konuşacağız.
#define Önişlemci Bildirimi
Her ne kadar günümüzde kullanımı üzerine tartışmalar olsa da C dilinin belki de en önemli özelliklerinden biri, derlemeden önce gerçekleşen ön işleme (preprocessing) isimli bir aşamaya sahip olmasıdır. Ön işleme aslında sadece kaynak kodda bazı metinsel değişiklikler yapmadan ibarettir. Ön işleme aşamasında önişlemci (preprocessor) program; kaynak koda başka bir kod dosyasının içeriğini yapıştırabilir, kaynak kodda belli bir sembolik sabitin kullanıldığı yerde onu başka bir yazı ile değiştirebilir veya kaynak kodun bir kısmını derlemeye dahil etme, bir kısmını ise derlemeden çıkarma işlemlerini gerçekleştirebilir.
Verdiğim örneklerden de anlayacağınız gibi, bunların hepsi metin işleme (text processing) ile alakalı işlemlerdir. C’de önişlemciye ne yapmasını söyleyebilmek için bazı önişlemci bildirimleri (preprocessing directives), önişlemci operatörleri (preprocessing operators) ve önceden tanımlanmış makro isimleri (predefined macro names) kullanılmaktadır. Elbette biz burada sadece önişlemci bildirimleri ile ilgilenecek ve diğerlerini ilgili yazılarda ele alacağız.
C’de önişlemci bildirimlerinin hepsi "#" işareti ile başlamaktadır. Bu işaretten sonra ise bildirimin ne yapacağını belirten kelime gelir. Örneğin daha önce pek çok kez #include önişlemci bildirimini görmüştük. Bu bildirim, ondan sonra ismi yazılan dosyanın içeriğini alıp o anda bulunduğu yere yapıştırmaktaydı. İşte C standartlarında da çok çeşitli önişlemci bildirim kategorileri yer almaktadır. C23 standartlarına göre bu kategorileri ve içerdikleri önişlemci bildirimlerini listeleyecek olursak:
- Koşulsal Dahil Etme Bildirmleri (Conditional Inclusion Directives)
#if#ifdef#ifndef#else#elif#elifdef(C23)#elifndef(C23)#endif
- Kaynak Dosya Dahil Etme Bildirimleri (Source File Inclusion Directives)
#include
- İkilik Dosya Dahil Etme Bildirimleri (Binary Resource Inclusion Directives)
#embed(C23)
- Makro Yer Değiştirme Bildirimleri (Macro Replacement Directives)
#define#undef
- Satır Kontrol Bildirimleri (Line Control Directives)
#line
- Tanılayıcı Bildirimler (Diagnostic Directives)
#error#warning(C23)
- Pragma Bildirimleri (Pragma Directives)
#pragma
- Boş Bildirim (Null Directive)
#
Bu bildirimlerin kullanım şeklinde göre de pek çok davranış değişikliği olmaktadır. Yeri geldikçe bütün bu bildirimleri ve operatörleri anlatacağım. Ancak şimdilik sadece #define önişlemci bildirimine odaklanmak istiyorum. Bu bildirimin çok farklı kullanımları bulunsa da burada sadece bir tanesini ele alacağım. #define önişlemci bildiriminin en bilindik kullanımı şöyledir:
#define <Tanımlayıcı> <Yer Değiştirme Listesi>
Bu bildirim ile o anki kaynak kodda "<Tanımlayıcı>" yazan yerleri "<Yer Değiştirme Listesi>" kısmına yazdıklarınız ile değiştirebilirsiniz. Örnek:
#define PI 3.14
int main()
{
double r = 5;
double area = PI * r * r;
return 0;
}
Yukarıdaki örnekte #define ile önce PI isimli bir tanımlayıcı bildirilmiştir. Bu tanımlayıcıya makro (macro) adı da verilmektedir. PI isimli makronun yer değiştirilecek değeri ise 3.14 olarak belirlenmiştir. Bu yüzden kodda PI yazan her yere ön işleme aşamasında 3.14 değeri yazılacaktır. Ön işleme aşaması bittikten sonra kod şuna dönüşecektir:
int main()
{
double r = 5;
double area = 3.14 * r * r;
return 0;
}
#define bildiirmi yapmanın en büyük avantajı, kodda sık kullanılan sabitlere bir isim verip o şekilde kullanmaktır. Şu örneğe bakalım:
double area = 4.67354376535734656346487362758687 * 78;
double b = area + 4.67354376535734656346487362758687;
double c = area - 4.67354376535734656346487362758687;
double d = b – c + 4.67354376535734656346487362758687;
Burada 4.67354376535734656346487362758687 sabiti çok sık kullanılmaktadır. Bu şekilde anormal büyüklükteki sabitlerin farklı yerlerde kullanımı yazım hatalarından dolayı bulunması zor bazı mantık hatalarına neden olabilmektedirler. Bunun için bu sabiti bir makro şeklinde isimlendirip şu şekilde kolayca kullanabiliriz:
#define NUMBER 4.67354376535734656346487362758687
double area = NUMBER * 78;
double b = area + NUMBER;
double c = area – NUMBER;
double d = b – c + NUMBER;
Makro isimleri geleneksel olarak büyük harfle yazılmaktadır, ancak siz bu şekilde yazmak zorunda değilsiniz. Elbette önişlemci bildirimlerinin temel amacı kaynak kodda metin işleme olduğundan, herhangi bir söz dizimine bağlılıkları bulunmamaktadır. Yani bu da #define ile çok ilginç şeyler yapılabilir demek oluyor. Hemen bir örnek vereyim:
#define INT int
#define NUM1 number1
#define NUM2 number2
#define RETURN return 0;
INT main()
{
INT NUM1 = 5;
INT NUM2 = 6;
INT result = NUM1 + NUM2;
RETURN
}
Evet bu garip görünen kod gerçekten de çalışmaktadır. #define ile anahtar kelimelerin, ifadelerin, deyimlerin, sabitlerin, kısacası her şeyin ismini değiştirmeniz mümkündür. Yukarıdaki kod ön işleme aşamasından sonra şuna benzer bir çıktı verecektir:
int main()
{
int number1 = 5;
int number2 = 6;
int result = number1 + number2;
return 0;
}
Ancak #define bildiriminin (ve elbette diğer önişlemci bildirimlerinin) bu kadar esnek olması, aynı zamanda onların en büyük tehlikesidir. Kaynak koddaki her yazıyı değişikliğe uğratabileceklerinden ve derleme aşamasından önce işlem yaptıklarından, oldukça dikkatli kullanılmaları gerekir. Örneğin şu kodu çalıştırdığınızda gelen hatalar karşısında bir anda kafanız karışabilir:
#define number1 3
int main()
{
int number1 = 5;
int number2 = 6;
int result = number1 + number2;
return 0;
}
Bu kodun ön işleme aşamasından çıkmış hali hiç de düzgün bir çalışan kod olmayacaktır:
int main()
{
int 3 = 5;
int number2 = 6;
int result = 3 + number2;
return 0;
}
sizeof Operatörü
Daha önceki yazılarda C’deki çeşitli operatörlerden bahsetmiştim. C’de yer alan operatörler genellikle çeşitli semboller ile gösterilse de anahtar kelime formunda olan operatörler de vardı. İşte sizeof operatörü de bunlardan biridir. sizeof operatörü ile bir nesnenin veya türün bellekte kapladığı alanı öğrenmek mümkündür. Örneğin int türünün 4 byte olduğu bir sistemde, şu satır ekrana 4 sayısını basacaktır:
printf("int size = %d", sizeof(int)); // ÇIKTI: int size = 4
C’de türlerin boyutları (yani bellekte kapladığı alanlar) sistemden sisteme değişebildiği için bu operatörün kullanımının değeri büyüktür. Bu operatör ile hangi türün bellekte ne kadar yer kaplayacağını öğrenmenin yanı sıra, belli bir ifadenin de bellekte kaplayacağı boyutu öğrenebilirsiniz. Örnek:
float x = 6.5; // 4 byte
float y = 6.7; // 4 byte
printf("Size of Result = %d", sizeof(x * y)); // ÇIKTI: Size of Result = 4
Yukarıdaki örnekte float türünün o sistemde 4 byte olduğu varsayılmış ve float türündeki x ile y değişkenlerinin çarpımlarının boyutu sınanmıştır. Bu çarpım sonucu yine float türünden olacağı için, sonuç 4 byte olacaktır. Sonucun her zaman byte cinsinden olduğuna dikkat edin. Peki bu operatörün kendisi tam olarak hangi türde bir veri geri döndürmektedir?
C’de boyut bilgisi tutmak için tanımlanmış işaretsiz bir sayı türü olan size_t türü, C’deki birçok başlık dosyasında tanımlanmaktadır. Bu türün denk geldiği tür ise implementasyona bağlıdır (implementation-defined). sizeof ve _Alignof gibi pek çok operatör de bu türden değer geri dönmektedir. C’de bu şekilde özelleştirilmiş pek çok tür bulunsa da bunların ayrıntısına daha sonra değineceğiz.
C23 standartlarına göre char, unsigned char ve signed char veri türlerine uygulanan sizeof operatörü her zaman 1 değerini döndürür:
sizeof(char) // 1 byte
sizeof(unsigned char) // 1 byte
sizeof(signed char) // 1 byte
Aslında sizeof operatörünün iki kullanım biçimi bulunmaktadır: Parantezli ve parantezsiz. Yukarıdaki kullanımların hepsi parantezli kullanıma örnektir. Parantezsiz kullanımda ise sizeof operatörnün yanına sadece bir ifade yazılabilir. Bu operatörü parantez olmadan türler le kullanmak, derleme zamanında hataya neden olur:
sizeof int // HATA!
sizeof float // HATA!
sizeof unsigned char // HATA!
Onun yerine şu gibi kullanımlar uygundur:
sizeof x
sizeof y
sizeof 12
Ancak birleşik ifadeleri kullanmanız sırasında yine hata ile karşılaşırsınız. Bu nedenle birleşik ifadeleri parantez içine almanız gerekir. Bu da parantezli olanı kullanıyormuşsunuz hissi uyandırır. Yukarıdaki son örnekte de gördüğünüz gibi sabitlere de sizeof operatörünü uygulayıp boyutlarını öğrenebilirsiniz. Zaten bu konuyu bu yazıya koyma sebeplerimden biri de bu sayılır (aşağıdaki sonuçlar benim denediğim sisteme göredir):
sizeof 'a' // 4 byte
sizeof 123 // 4 byte
sizeof 12UL // 8 byte
sizeof (42.8) // 8 byte
sizeof 42.8f // 4 byte
Böylelikle çok rahat bir şekilde sisteminizdeki türlerin boyutunu öğrenebilir, kodda kullandığınız sabitlerin ve ifadelerin boyutlarını da test edebilirsiniz. İleride C’nin daha fazla özelliğini öğrendikçe sizeof ile ilgili yeni bilgileri söyleyeceğim. Şimdilik bu kadarının yeterli olduğunu düşünüyorum ve konuyu son bir örnek ile bitirmek istiyorum (yine aşağıdaki sonuçlar benim sistemime göredir, sizde değişebilir):
#include <stdio.h>
int main(void)
{
char x;
short a;
int b;
unsigned int c;
long d;
float e;
double f;
long double g;
printf("sizeof(x) = %d\n", sizeof(x));
printf("sizeof(char) = %d\n", sizeof(char));
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(short) = %d\n", sizeof(short));
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(int) = %d\n", sizeof(int));
printf("sizeof(c) = %d\n", sizeof(c));
printf("sizeof(unsigned int) = %d\n", sizeof(unsigned int));
printf("sizeof(d) = %d\n", sizeof(d));
printf("sizeof(long) = %d\n", sizeof(long));
printf("sizeof(e) = %d\n", sizeof(e));
printf("sizeof(float) = %d\n", sizeof(float));
printf("sizeof(d) = %d\n", sizeof(d));
printf("sizeof(g) = %d\n", sizeof(g));
printf("sizeof 78.8 = %d\n", sizeof 78.8);
}
sizeof(char) = 1
sizeof a = 2
sizeof(short) = 2
sizeof(b) = 4
sizeof(int) = 4
sizeof(c) = 4
sizeof(unsigned int) = 4
sizeof d = 8
sizeof(long) = 8
sizeof(e) = 4
sizeof(float) = 4
sizeof(d) = 8
sizeof(g) = 16
sizeof 78.8 = 8
Bu arada son bir not; büyük ihtimalle "%d" ile ekrana basım yaptığınız için derleyiciniz birçok uyarı (warning) mesajı verecektir. sizeof operatörünün döndürdüğü değer işaretsiz bir değer olduğundan, ekrana işaretli formatta basmaya çalışmak bu hataya neden olacaktır (ve tabii ki bir de int türü dışında bir tür olmasından kaynaklanabilir). Bunu şimdilik göz ardı edebilirsiniz. Bir sonraki yazıda printf ve formatlı çıktılar ile ilgili daha fazla ayrıntıya değineceğim.
Bu yazıda C’de kodun içine doğrudan yazabileceğimiz sabitler ve onların türleri hakkında bilgi vermeye çalıştım. Ayrıca #define önişlemci bildiriminin bir kullanımını anlatmaya çalıştım. Son olarak da sizeof operatöründen bahsettim. #define ve sizeof ile ilgili ileriki yazılarda da yeri geldiğinde konuşacağım. Çünkü bu bildirim ve operatörün özellikleri sadece burada anlatılanlarla sınırlı değildir ve oldukça geniş kullanım alanlarına sahiptirler.


