C ve C++ Programlama Dilleri

Not: Bu yazı C ve C++ programlama dilleri arasında ki farklılıklar, yeni standart ile gelen bazı değişiklikler vb konuları soru cevap şeklinde, maddeler olarak topladığım bir makaledir.


  • C++11 standartları ile beraber dil büyüklüğü neredeyse 2 katına çıktığını söyleyebiliriz. Bunun devamında C++14 standartları(minör) ve C++17 standartları(major) gelmiştir. C++20 standartları için çalışmalar devam etmektedir.

  • C++11 standartları ile dilin daha fazla genişlemesi konusunda birçok iyi ve kötü yorumlar yapıldı. Ama şu konu atlanıyor, Bjarne Stroustrup’un da yaptığı açıklamalarda C++ dilinin zaten küçük bir dil olma gibi bir amacı yoktur. Bu bakımdan C++11 standartlarıyla birlikte gelen yeniliklerle dil tamamen yenilendi. Hatta kendi tabirleri ile Modern C++ deniliyor.

  • C++ dilinin en övündüğü alanlardan başında Data Abstraction(Veri Soyutlama) gelmektedir.

  • C++ dili size uygulama yazarken birçok programlama tekniği ile geliştirme yapmanıza izin verir. (Nesne Yönelimli, Prosedürel, Çoklu Paradigmalı vs.)

  • C ve C++ dillerinde derleyici büyüklüğü zaten tartışılmaması gereken bir konudur. C dilinin amacı dili ve derleyiciyi olabildiğince küçük tutmaktır.

  • Peki bunu nasıl yapıyorlar? C derleyicisinin kontrolleri C++ derleyicileri kadar katı ve fazla değildir. Bu demek oluyorki, c programcıları kod yazarken hata yapma olasılıkları daha yüksektir. Derleyici kısmında ki kontrolleri azaltarak ve bazı mekanizmaları çıkartarak derleyiciyi küçük tutuyorlar.


  • C++ dili içerisinde ki C daha güvenlidir. Çünkü derleme zamanı kontrolleri daha fazladır.

  • C dilinde kullandığımız pointer kavrami C++ da yerlerini referanslara bırakıyor. (Yanlış anlaşılmasın pointer kavramı hala kullanılmaktadır. Referanslar sadece seviyesi yükseltilmiş notasyonel bir hiledir.)

  • C++ dilinde diziler çok nadir kullanılırlar. Bunun yerine vektor yada array sarmalayıcıları kullanılır. Örneğin C dilinde char str[100] yazı tutar ama C++ dilinde bunu kullanmayacağız. Char yerine string sınıfını kullanacağız. Bu cümleden şöyle bir anlam çıkarmayın! C++ dilinde char str[] kullanamıyoruz. Tabiki de kullanabiliyoruz fakat buna gerek yok.

C dilinden C++ diline geçerken performans kaybediyor muyuz?

  • Kesinliklikle PERFORMANS kaybetmiyoruz. Dilin geliştirilirken dikkat ettiği kısımlardan biri de PERFORMANS kaybetmemektir.

C ile C++ arasında ki bazı farklılıkları kod parçalarıyla inceleyelim.

func(int x){}

// implicit C, yani tür bilgisi yazmaya gerek yok.
// Derleyici bunu işaretli int olarak anlar.
// C++ dilinde bu kural IPTAL edilmiştir.

Geri dönüş değeri ile ilgili örnek

int func(){}
// C dilinde geçerlidir.
// C derleyicisi geri dönüş değeri üreteceğini söylemiştir.
// Return olmamasi geçerli.
// C++ dilinde geçersiz.

int func(int x){}
// C de geçerlidir.
// C++ dilinde geçersizdir.

int func(int){}
// C de isimlendirme mecburdur
// C++ dilinde isimlendirme mecbur değildir. Geçerlidir.

main fonksiyonunun geri dönüş değeri ve tipi

  • C dilinde neden main fonksiyonun geri dönüş değeri int ? Bunun sebebi exit fonksiyonuna yapılan çağrıdır. Non-zero başarısızlık, zeri ise başarı olarak kabul edilir. (Stdlib exit_clear, exit_success)

  • void main olarak tanımlasak ne olur? C dilinde main fonksiyonunun geri dönüş değerini void olarak kullanırsak programın başarıyla yada başarısızlık ile bittiği görülmeyecektir.

  • Diğer bir konu C dilinde main Recursive olarak çağırılabilir mi? C dilinde main Recursive olarak çağırılabilir. C++ dilinde ise bu yasaklanmıştır. C++ dilinde main fonksiyonuna return deyimi yazmaya gerek yoktur ama geliştirici olarak return yazın. Geri dönüş türü ise kesinlikle int olmalıdır.

// KODU INCELEYELIM. C ve C++ derleyecileri bu koda nasıl bir davranış sergilerler.

int main()
{
func();
}
/* C++ kodu şöyle yorumlar
func benim için bir identifier(isim).
Ben bu ismi bir arayayım.(NAME LOOKUP)
Ismi bulamadığını söyler ve sentaks hatası verir.
Yani buradan çıkaracağımız C++ derleyicisi bir isim arayıp onu bulamaması direk sentaks hatası.
C dilinde ise bu GEÇERLIDIR.
*/

/* C kodu şöyle yorumlar

Bir fonksiyon bildirimi ile karşılasmasa bile önce func ismini arar.
Baktı ve bulamıyor. Sonra func fonksiyon çağrı operatörünün OPERANDI olmuş mu? diye bakar.
Evet olmuş. O zaman ben bu ismi arayıp bulamasam bile func benim için fonksiyon ismidir diyor.
Geri dönüş değeri int olan bir fonksiyon. (Yani implicit bir bildirim)
Bu aslında tehlikeli bir durum. C++ dilinde implicit function declaration yok.
C++ dilinde fonksiyon bildirimleri zorunludur.
*/
void func();
void foo(void);
// C++ dili için hiçbir anlam farkı yoktur.
// C dili için durum böyle değil. C dilinde foo fonksiyonunun parametre değişkeni yok demektir.
// func isimli fonksiyon ise bilgi vermiyorum demek. Yani değişkeni olmadığını değil bilgi vermediğini anlarız.
// PEKI BILGI VERMEMENIN ONEMI VAR MI?
// Tabiki de yok bu sadece GERIYE UYUMLULUGU sağlamak için yapılmıştır.

Aşağıda ki kodu inceleyelim.

int func()
{
printf("");
int x;
// executable statement
// …
}
  • Kodun devamı var gibi düşünün. Şimdi C89 standartlarına göre kural şöyle yerel bildirimler blokların başında yapılmalıdır. Yani executable statement(yürütülebilir deyim)den sonra bildirim yapamayız. Bu kesinlikle kötü bir kural. Zaten bu kural konulduğuna da pişman olunulmuş sonradan.

  • Önce şu deyim mevzusunu hatılayalım. C dilinde deyimler 2’ye ayrılıyordu. DECLARATION STATEMENT ve EXECUTABLE STATEMENT.

  • Şimdi OOP’de bu kural böyle olsaydı ileride çok büyük sıkıntılar çıkarabilirdi. Örnek olarak kodun bakımı.

  • Bir ismin scope nu ne kadar daraltırsak o kodun bakımını yapmak o kadar kolay olacaktır.

  • Konuya geri dönecek olursak artık yerel bildirimlerin blok başında yapılmasına gerek kalmadı. C++ dilinde artık gerekli yerlerde değişken tanımlaması yapılabilir. Böyle de yapılması gereklidir.

  • Burada diğer önemli bir konu ömür konusudur. Yerel değişkenler C de otomatik ömürlü de olabilir, statik ömürlüde olabilir. Statik ömürlü demek programın başından sonuna kadar bellek alanını korumasıdır.

  • Neler statik ömürlü hatırlayalım… Global değişkenler | Static keywordü ile tanımlananlar | Fonksiyonlar | Stringler.

  • Otomatik ömürlü nesnelere ilk değer vermezsek, garbage value ile hayata başlar. Çöp değerler hayata başlamanın zararı var mı? Eğer onu çöp değerle kullanırsak zararı var. Bu aslında bildirimin blok başında yapılmasının bir zorlaması. C++ da şuna alışmalıyız, eğer çok önemli bir nedeni yoksa tüm nesnelere ilk değer vererek hayata başlatın. Gene bu bildirim ile ilgili fazlaca kullandığımız bir yer daha var. for döngüleri, döngü değişkeni. Örneğin for(int k=0;….) C99 da bu eklenmiştir ve böyle kullanabiliyoruz. C++ da tabiki de bunu böyle kullanabiliyoruz. Java, C# gibi diller bu olayı C++ dan biraz daha önce görüp eklemişler. Şimdi bu kural olmasaydı örneğin C89 da kullanıyor gibi düşünelim, bize zararı nedir? Öncelikle scope unu gereksiz yere genişletiyor, aynı zamanda program sonuna kadar bu değişken visible durumda oluyor. Ama yeni kurala göre for parantezi içinde tanıtılan değişkenin ömrü döngünün bloğunun sonuna kadardır.


auto keywordü ile ilgili olarak C ve C++’da anlam değişimi var.

auto int x; // Bu kod C dilinde otomatik ömürlü demek

int x; // C++ dilinde bu da otomatik ömürlü, yani auto keywordunu kullanmaya gerek kalmıyor

auto x = 10; // işte burada bir anlam değişikliği var.
// auto burada değişkenin veri türünü belirliyor, yani burada int
  • Sonuç olarak auto artık C++11 standartlarında type detection. Yani tür bilgisi çıkarımını derleyici yapıyor.

C++11 decltype

decltype(exp) x; //x in türü exp neyse o

C89 standartlarında bool türü yok, C++ dilinde var

  • 0 = False ve 1 = True anlamına gelmektedir.

  • bool hemen hemen her sistem de 1 byte (char altı olacak)

  • C++ dilinde true ve false artık birer keyword

bool x = false; // yada true
  • C dilinde karşılaştırma türü int, C++ dilinde bool

  • bool ile diğer türler arası otomatik tür dönüşümü vardır.

bool x = 13; // non-zero bütün değerler True
  • Yani artık bool gereken yerler de direk olarak true false kullanmalıyız.

Karakter Sabiti Konusu

  • ‘A’ C dilinde int, C++ dilinde char
#include <iostream>

int main(){

std::cout << typeid('A').name() << std::endl; // c

}
#include <iostream>
/*
bool türü bir tam sayı türüdür. int altı türdür.
C dilinde integral promotion kuralı, tam sayıya yükseltme kuralı int ten küçük türler işleme sokulduğunda int e dönüştürür.
*/
int main(){

char c1 = 1, c2 = 3;
std::cout << typeid(c1+c2).name(); // int

}

Veri Kaybı

double d = 34.7;
int ival = d; // d değeri 34
  • C ve C++ da legal bir davranış ama veri kaybı vardır. Bazı dillerde direk veri kaybına sebep olan davranışlar sentaks dışı yani direk hata.

  • C++11 de uniform initializer getirildi. Yani bunun anlamı veri kaybına yol açabilecek dönüşümleri geçersiz kılmak

#include <iostream>
int main() {
// insert code here…

double d = 34.6;
int ival{d}; //Type double cannot be narrowed to int in initializer list

return 0;
}

C ve C++ Dilinde Dönüşümler

  • C ve C++ dillerinde dönüşümler iki kategoriye ayrılıyor. IMPLICIT TYPE CONVERSION ve EXPLICIT TYPE CONVERSION

  • implicit dönüşüm otomatik dönüştürme. Şöyle anlatayım ival + dval olsun. Derleyici bunun için geçici bir nesne oluşturuyor. Bu işlemi double türünde yapıyor. Yani dönüştürme otomatik oluyor.

  • explicit ise operatör yardımıyla yapılan dönüşümlerdir. i1 ve i2 int türden değişkenler olsun. i1/i2 olarak bölme yapmamalıyız. int/int sonuç int çıkar. 10/3 = 3 olur. C dilinde tür dönüştürme operatürü (). Normalde olması gereken (double)i1/i2 … Ama artık C++ ta bunuda yapmamamız gerekiyor. Artık bunun için yeni 4 tane type cast operatörü geldi. Bu operatörler
static_cast
const_cast
reinterpret_cast
dynamic_cast

  • NULL C dilinde sembolik bir sabittir. C++ dilinde null var ama kesinlikle kullanmamalıyız. C++11 standartlarıyla yeni bir keyword eklendi. nullptr
ptr = 0;
// C ve C++ geçerli (ptr bir pointer olarak düşünelim)
// Otomatik olarak null dönüşümü yapıcak, bir pointera null atamak istediğimizde 0 yazabiliriz fakat C++11 de böyle yapmamalıyız.

  • C++11 de nullptr var. nullptr bir adres sabiti. Yani atama şu şekilde olmalı;
ptr = nullptr;

  • C++ dilinde mecbur kalmadıkça önişlemci komutlarını kullanmayacağız. Çünkü çok tehlikeli durumlar yaratabiliyor define komutu. Bunu ileri ki yazılarımda değinirim.

  • null sembolik sabitini bazi C derleyicileri (void*)0 olarak define etmiş.
int *ptr = (void*)0; // yanlış olur
// C++ dilinde void* türden T* türüne otomatik tür dönüşümü yok.

C++ dilinde ilk değer verme sentaksları;

int ival = 1;
int ival(1);
int ival = int();
int ival{exp};


char str[4] = “KAYA”;
// C dilinde legal, C++ da error..
// C++ dilinde dizinin boyutu kadar karaktere sahip bir stringle ilk değer veremeyiz.
// initializer-string for char array is too long


char *p = “Murat”;
*p = “S”; // Böyle birşey yapamayız, runtime hatası alırız. Stringler constant ve readonly dir

const char *p = “Murat”; // semantik açıdan bu daha doğru

// Yani stringleri asla char *p şeklinde göstermicez.

-> C++ dilinde pointerlar hala var ama C dilindeki kadar yoğun kullanmayacağız. C++ da seviyesi daha yüksek olan referansları kullanacağız.

-> C++ dilinde pointer ile primitive türler arası otomatik tur dönüşümü yok. C dilinde var.

int *p;
int x = 20;
p = x; // C dilinde yanlış ama legal, int türünden int * türüne otomatik tür dönüşümü gerçekleştirir

// C++ dilinde bu tamamen hata, tam sayı değerleri pointer değerlere atayamayız

// Farklı türden adres atamakta sentaks hatası

const int x; // C dilinde geçerli, C++ x değişkenine ilk değer vermek zorunlu
  • Sonuç olarak C++ dilinde pointer değişkenlere adres olmayan bir verinin atanması veya farklı türden bir adresin atanması sentaks hatası.