C++ Notlarım
Kendime göre not aldığım için referans almak yerine deneyerek anlamaya çalışmanızı, yanlış yahut daha iyileştirmek için için geri bildirimlerinizi beklerim.
C++ programlama dilinde “preprocessor” ve “pragma” kavramları, programın derleme sürecini yönlendirmek veya derleme öncesinde bazı özellikleri belirlemek için kullanılan önemli araçlardır.
Preprocessor (Önişlemci)
C++ dilinde ön işlemci, programınızın kaynak kodu derlenmeden önce bazı metin değişiklikleri yapmanıza olanak tanır. Bu değişiklikler genellikle `#` sembolü ile başlayan önişlemci direktifleri (preprocessor directive) kullanılarak yapılır. Önişlemci, genellikle aşağıdaki işlevleri gerçekleştirir:
— #include
: Başka dosyaların içeriğini mevcut dosyaya dahil eder. Örneğin, başlık dosyalarını (`*.h`) dahil etmek için kullanılır.
Açılı parantezler
< >
sadece sistem dizinlerinde başlık dosyasını aramayı sağlar. Derleyici, önceden tanımlanmış dizinlerde bu başlık dosyalarını arar.Çift tırnak işaretleri
" "
kullanıcı tanımlı başlık dosyaları için kullanılır. Derleyici önce yerel dizinde arama yapmasını, sonra sistem dizinlerinde aramasını söyler.
— #define
: Sabitler, makrolar ve fonksiyon benzeri metin yerine koymaları tanımlamak için kullanılır.
— #ifdef
, #ifndef
, #endif
: Belirli koşulları kontrol etmek için kullanılır. Örneğin, bir kod parçasını belirli bir koşula göre derlemek için kullanılabilir.
#include <iostream>
#define PI 3.14159
#ifdef DEBUG
#define LOG(x) std::cout << x << std::endl
#else
#define LOG(x)
#endif
int main() {
LOG("Debugging message");
return 0;
}
#pragma
: Derleyiciye özel talimatlar verir. Bu, derleyiciye özgü özellikleri ayarlamak için kullanışlıdır.#pragma
, özellikle derleyiciye yönelik özel direktifleri ifade etmek için kullanılır.
- Derleyicinin bazı özelliklerini veya davranışlarını kontrol etmek için kullanılır. Örneğin, bazı derleyiciler pragma kullanarak derleme optimizasyonları veya hata ayıklama ayarlarını kontrol edebilir.
- Pragma direktifleri, derleyiciye özel olabilir ve derleyiciye, belirli bir kod bölümünün nasıl işlenmesi gerektiğini söylemek için kullanılır.
Örneğin, GCC (GNU Compiler Collection) derleyicisine özgü bir pragma kullanımı:
#pragma GCC optimize("O2")
/**
* Bu, GG derleyicisine, 2. seviye optimizasyon yapması talimatıdır.
* Belirli fonksiyonlar veya kod bölümleri için özel optimizasyon sağlar.
* Genel derleyici ayarlarını değiştirmeden yerel optimizasyonlar yapabilirsiniz.
* Genel derleme iyileştirme seviyesi vermek için -O2 bayrağı kullanılır.
*
* Optimizasyon Seviyeleri:
* O0: Optimizasyon yok
* O1: Temel optimizasyonlar
* O2: Orta seviye optimizasyonlar (çoğu durumda önerilen)
* * Derleme süresini çok artırmadan iyi bir performans sağlar.
* * Kod boyutunu makul seviyede tutar.
* * Hata ayıklamayı zorlaştırmadan optimizasyon yapar.
* O3: Agresif optimizasyonlar
* Aşırı optimizasyon bazen beklenmeyen davranışlara yol açabilir.
* Hata ayıklama zorlaşabilir, özellikle yüksek optimizasyon seviyelerinde.
* Farklı derleyicilerde farklı sonuçlar alabilirsiniz.
* Os: Boyut optimizasyonu
*/
#pragma GCC optimize("O2")
int main() {
// Optimizasyon uygulanacak kod buraya
return 0;
}
Her derleyici, pragma kullanımı için farklı sözdizimlerine sahip olabilir ve derleyici belgelerinde bu pragma kullanımları ayrıntılı olarak açıklanır. Bu nedenle, pragma kullanırken derleyici belgelerine başvurmanız önemlidir. Modern C++’ta, önişlemci direktiflerinin bazı kullanımları yerine dil özellikleri tercih edilir:
#define
yerineconst
veyaconstexpr
- Makro fonksiyonlar yerine inline fonksiyonlar
- Bazı durumlarda template’ler makroların yerini alabilir
“decay”, “arry to pointer”, “function to pointer” Nedir?
C++’da fonksiyonları çağırdığımızda, argümanlarımızın tür dönüşümlerinden geçmesi gerekebilir. Örneğin, func(const char& c){..}
fonksiyonunun const char&
argümanına değerin kopyalandığı ve int x=100; func(x);
değişkeniyle işlevi çağırırsak “100" değeri fonksiyon içinde “d” karakteri olarak dönüştürülecektir.
Bu dönüşüme “bozunma” anlamına gelen “decay” ingiliçce kelimesi karşılık geliyor.
array to pointer
Aşağıdaki koda baktığımızda void print_size(char arr[128])
fonksiyonunun derleyici tarafından void print_size(char *arr);
olarak anlaşıldığını çıktının 8 byte uzunluğunda (adres bilgisinin boyutu 8byte olur) dönünce anlıyoruz. Yani burada da bir bozunma (decay) var ve char
dizisi bir işaretçiye dönüşmüş olur. Yani array to pointer vakasını görürüz.
#include <iostream>using std::cout;
using std::endl;void print_size(char arr[128]) {
cout << "arr char dizisinin boyutu: " << sizeof(arr);
}int main() {
char arr[128] = { 'a', 'b', 'c' };
print_size(arr);
}
auto anahtarıyla tanımlanan değişkene bir dizi atandığında da array to pointer decay dönüşümünü görürüz. a
Bir dizidir ve x1
değişkenine atandığında dizinin işaretçiye dönüştürüldüğünü görürüz.
Ancak a
’nın adresini atadığımızda &a
, 3 boyutlu bir diziyi işaret ettiği için bu kez x2
değişkenine 3 boyutlu bir diziyi gösteren pointer olarak ataması yapılır.
Şimdi gelelim;
function to pointer
Aşağıdaki call
işlevi bir parametre alıyor callback
adında.
void call(void callback(int), int x) {
callback(x);
}
İşte callback işlevi derleyicinin gözünde function to pointer olacak şekilde şu hale geliyor:
void call(void (*callback)(int), int x) {
callback(x);
}
auto
Anahtarıyla da function to pointer kavramını görebiliriz.
void func(int a) { }int main() {// 3 tanımlama da aynıdır.
auto fp1 = func; auto fp2 = &func; void (*fp)(int) = func;
}
Yukarıdaki gibi auto ile tanımlı değişkenlere atanan func
işlevinin tanımları aynı hedefi işaret edeceklerdir.
Kaynak: *
decltype()
“c++ programlama dilinde decltype, bir ifadenin türünü sorgulamak için kullanılan bir anahtar sözcüktür.”
Ondalık Ayaraç (C++ 14 ile gelen ‘ )
Aşağıda tek tırnak işaretlerinin sadece kullanıcılar için olduğunu, bunları herhangi bir konumda kullanmanın derleyiciyi etkilemediğini, sadece kullanıcıya okurken fayda sağlayacağınız yerlede kullanabileceğinizi bilmeniz yeterlidir.
long x = 10'000'000
double y = 12'800long long decn = 1'000'000'000ll;
long long hexn = 0xFFFF'FFFFll;
long long octn = 00'23'00ll;
long long ondalik = 163;
long long onaltilik = 0xA3;
long long ikilik = 0b1010'0011ll;
Onaltılık İkilik Onluk Sayı Sisteminde
2, 10, 16 sayı sisteminde C++ kodunun görüntülenmesi
replit.com
#include <iostream>int main() {
long onluk = 163;
std::cout << "Onluk:" << onluk << " \n";
long onaltilik = 0xA3;
std::cout << "0x... Onaltılık:" << onaltilik << " \n";
// tek tırnak ile okunurluluğu arttıracak yerden ayırıyoruz
long ikilik = 0b1010'0011;
std::cout << "0b... İkilik:" << ikilik << " \n";
}
Include Guards
Rectangle.hpp dosyasını hem Windows.hpp hem Shapes.hpp dosyası kullanıyorsa Rectangle.hpp dosyasının bir kere yüklenip diğerleri tarafından kullanılmasını sağlamak için #ifndef … #endif
içinde #define
kullanarak tanımlamamızı yaparız.
Rectangle sınıfını derleyicinin bir yerde görmesini sağlamak için yaptığımız bu include guard işlemi ODR (One Definition Rule) kuralına uygunluğu sağlar.
Vector
Dinamik olarak yeni elemanların eklenebildiği (List gibi) ancak tüm elemanlara indeks ile erişilebildiğimiz (degisken[3]) ve yeni eklenen elemanın da tüm elemanların hemen ardına yani bitişik olacak şekilde taşındığı, jenerik olarak elemanların tipinin verilebildiği (template) tiptir.
En yaygın metotları:
- push_back
- size
- resize
- empty
- clear
1 elemanlı ve değeri 10 olan dizi yaratır
vector<int> degiskenAdi{10} 10 elemanlı her birinin değeri 0 olan dizi yaratır
vector<int> degiskenAdi(10)
Değişken Tanımlama & Bellek (Heap & Stack)
Visual Studio içinde memory sekmesinde veya immediate penceresinde değişken adreslerini ve değerlerini görüntülemek için bakınız.
Belleği temel olarak heap ve stack diye bölümleyelim. Tabi bir de “static code” bölümü de var.
Basitçe nasıl çalışıyor bakalım:
int i=4;
satırı (instruction) “stack text” bölümünden alınır ve çalıştırıldığında stack üstünde i
değişkeni tanımlanır ve içine 4 yazılır.
int y=2;
Aynı şekilde çalıştırılır ve doğruda başlatıldığı için y değişkeni ve değeri de stack üstünde yaratılır.
int dizi[5];
veya X degiskenx{};
tanımları hep stack üstünde yaratılan değişken örnekleridir.
3. Satırda class1 cls1
değişkeni stack üstünde yaratılır ancak değeri olan new class1()
nesnesi heap üstünde oluşturularak ilişkilendirilir.
int* sayi = new int;
veya int* diziIsaretci = new int[5];
veya X* x = new X;
ise değeri heap üstünde yaratılıp işaretçi değişkeni stack üstünde yaratılıp değerin adresinin atandığı adreslerdir *.
Method1
'den çıkılırken stack üstündeki tüm değişken tanımları silinir ancak heap üstündeki tanımlamalar kalır. C#, Java gibi sanal makina üstündeki bu uygulamalarda çöp toplayıcı (garbage collector) devreye girer ve kimsenin sahiplenmediği bu cls1 nesnesini otomatik temizler. C/C++ dillerinde ise bizim kaldırmamız beklenir.
Stack üstündeki değişkenler KAPSAM (scope) bittiği anda otomatik olarak kaldırılırlar. Yani bir fonksiyondan dönen değer eğer STACK üstünde tanımlıysa, bu fonksiyonu çağıran komuta dönen değer hiç bir şey olacaktır.
Bu yüzden bir fonksiyon HEAP üstünde bir değer oluşturarak onun adresini dönmelidir.
Neden işaretçi kullanıyoruz — 1 ?
Uygulamamızdan doğrudan erişebildiğimiz yer sadece stack bölgesidir. Heap bölgesine veya klavye, monitör, dosya gibi yerlere ancak işaretçi (pointer) üstünden erişebildiğimizi aklımıza sokalım.
Aşağıdaki ekran çıktısında stack bölgesine her fonksiyon çağrımızla birlikte (uygulamayı çalıştırdığımızda main işleviyle başlayarak) stack bölgesine fonksiyonları sırasıyla yükleyip, kaldırdığımızı görürüz.
int a;
gibi hemen başlattığımız değişkenlerin stack bölgesinde, new anahtarıyla oluşturduğumuz nesnelerin (new int;
) heap bölgesinde yaratıldığını,
- heap bölgesindeki her değişkene ancak işaretçi (
pointer
) ile erişebildiğimizi, - işaretçinin gösterdiği heap bölgesindeki değeri C dilinde
free
, C++ dilindedelete
ile özgürleştirdiğimizi - işimiz bitince işaretçiye
nullptr
değeri atayarak kod kapsamının (süslü parantezlerin) dışına çıktığımızda işaretçi değişkenin otomatik olarak kaldırılarak, bellek sızıntısını önleyeceğimizi bilelim - Asla bir dizinin sadece bir kısmını silmeye çalışmayın. Tek seçeneğiniz tüm diziyi silmek. Bir dizinin bir kısmını silmeye çalışırsanız, yığın yöneticisini (heap manager) bozarsınız* (
int A[100]; … delete [] A;
).
Neden işaretçi kullanıyoruz — 2?
“Pass by value & Pass by Reference”
Bir metoda parametre geçirmek istediğimizde “değer türler” maliyetsiz kopyalanabildiği için doğrudan parametre olarak tanımlı değişkene değeri kopyalanarak aktarılır. Aşağıdaki ekran görüntüsünde p1 isimli değişken şu şekilde tanımlı olsaydı: void manipulate(double p1)
bu fonksiyona çağrı manipulate(dValue)
olacaktı ve dValue
içindeki değer p1
parametresine değer geçişi (pass by value) olarak yazılacaktı.
Ancak değer tiplerinin kopyalanması maliyetsiz olsa dahi, referans türlerin kopyalanması sisteme fazladan yük getireceği için referans geçişinin yapılması yani değeri değil, değerin adresinin (referansının) geçişi daha verimli bir yöntem olacaktır. Bu nedenle manipulate(double* pValue)
olarak tanımlanırken fonksiyon çağrısı manipulate(&dValue)
olarak yapılır.
Nesne Oluşturma
- C++’da,
new
anahtar sözcüğünü kullanarak veya kullanmadan sınıf’tan nesne oluşturabiliriz. new
Anahtar kelimesi kullanılmıyorsa, yığın bölümünde saklanacaktır. Kod kapsamı sona erdiğinde bu nesne yok edilir.- Ancak, nesne için dinamik olarak yer ayırmak istediğimizde, o sınıfın işaretçisini oluşturabilir ve
new
operatörünü kullanarak nesneyi somutlaştırabiliriz (int* p = new int;
).
- Stack üstünde yaratılan bir değişkene atanan dizi yerine boyutu daha büyük bir dizi oluşturarak tekrar atama yapamayız.
- İşaretçiye atanmış bir dizinin boyutunu yeni boyutla oluşturulmuş bir diziyi atayarak tekrar oluşturabiliriz. 3 Elemanlı diziyi bu kez 13 elemanlı yeni dizi oluşturarak tekrar işaretçimize atayabiliyoruz.
Bir de aşağıdaki örnek ve ekran çıktılarıyla pekiştirelim:
int main() {
int stackInt = 12;
int* heapInt = new int();
*heapInt = 12;
}
stackInt
adında STACK üstünde bir değişken yaratıp içine 12 yazdık.
heapInt
adından HEAP üstünde (new
kullandığımız zaman heap üstünde yaratılır) bir değer yaratıp bunu mecburen int tipinde bir işaretçiye atadık.
Mecburen bir işaretçiye atadık çünkü heap üstündeki bir değere erişebilmek için bir işaretçi kullanmamız gerekiyor. Doğrudan heap’e erişemeyiz!
Integer tipinde değer okuyabilen heapInt
adındaki işaretçimize sizeof heapInt
dediğimizde 8 byte gelmesi, bunun bir işaretçi olduğunu bir kez daha gösteriyor. Buna göre basit bir tamsayıyı heap üstünde yaratıp bir de işaretçiyle erişmeye kalktığımızda 8 byte işaretçi + 4 byte değer için toplam 12 byte kaplamış olacağız. Bu durumda en verimli yöntem stack üstünde int değişkeni tanımlamak olacaktır.
Değişken Tanımlama & Başlatma
- Programın bir eylem gerçekleştirmesini istediğimizde statement kullanılır (; ile biten komutlardır).
- Programın bir değer hesaplamasını istediğimizde expressions kullanılır (hesaplama yapan ifade:
2+5*x-divide(4,2)
). - Bir expression eğer
;
ile bitiyorsa ve expression statement denir (Örn.y = 2+5*x-divide(4,2);
) - type identifier { expression };
int a;
Başlatılmamış a değişkeninin adresi (&a): 0x012FFE40, değeri 3435973836 gibi herşey olabilir. Kontrolsüz bir değer aldığı için değişkenin başlatılması iyi kodlama yöntemi olarak kabul edilir.
Bellekte 0x012FFE40 adresinde değeri alelade bir veri olabilir.- Başlatma (Initialization): Başlatma, nesneye tanım noktasında bir başlangıç değeri sağlandığı anlamına gelir.
Başlatma = Nesneye tanım noktasında (at the point of definition) bilinen bir değer verilir.
Atama = Nesneye tanım noktasının ötesinde (beyond the point of definition) bir değerin verilmesidir.
Başlatılmamış = Nesneye henüz bilinen bir değer verilmemesidir. T obj{};
// value initializationT obj{v};
// direct initializationT obj = v;
// copy initialization- Uniform Initialization nelere yarar?
Başlatmaya zorlar
Değer ve referans türlerin hepsinde uygulanabilir
Kullanıcı tanımlı tiplerde kesinlikle kullanılmalı
Tür daraltmalarında hata üretir (float f{}; int i{f};
hata verir) int a;
// uninitialized (değer tipler başlatılmayabilir)int a( 12 );
// direct initializationint a{};
// value initialization (varsayılan değer “0” atanır)int a{ 12 };
// direct initialization (tercih edilen)char c[5];
// uninitializedchar c[5]{};
// otomatik varsayılan değerlerle başlatırchar c[5]{"abc"}
// direct initializationchar c[5]={'a','b','c'}
// aggregate/copy initializationchar c[5]={"abc"}
// aggregate/copy initializationint a = 12;
// copy initializationstd::string s1;
// initialization (referans tipler varsayılan değer alır)std::string s1("değer");
// direct initializationint *p1 = new int;
// uninitializedint *p1 = new int{};
// uniform initializationchar *p1 = new char[5]{"Hello"};
// uniform initialization- Değer veya Sıfır Başlatma (VALUE/ZERO INITIALIZATION)
int a {};
Bir değişken boş parantezlerle başlatıldığında , değer başlatma gerçekleşir.
Çoğu durumda, değer başlatma , değişkeni sıfıra (veya belirli bir tür için daha uygunsa boş) başlatır.
Sıfırlamanın gerçekleştiği bu gibi durumlarda buna sıfır başlatma denir .
a Değişkeni 0 değeriyle başlatılmış olacak. Boş süslü parantezler ile yapılan başlatmadır.int a {};
← Geçici olarak başlatıp daha sonra değeri değişecekseint a {0};
← 0 Değeri bilinçli kullanılacaksa (her ne kadar vermesek bile 0 olacaksa da) bilinçli olarak 0 değeri verilmeli - Doğrudan Başlatma (DIRECT INITALIZATION)
int a(12);
Basit veri türleri için (int gibi), kopyalama ve doğrudan başlatma temelde aynıdır. Daha karmaşık türler için, doğrudan başlatma, kopya başlatmadan daha verimli olma eğilimindedir. - Kopya Atama (COPY ASSIGNMENT)
int a; a = 12;
Değişkeni tanımla ve sonra atama yapma işidir. - Doğrudan Liste Başlatma (Tek Biçimli veya Ayraç Başlatma da denir) (DIRECT LIST/UNIFORM/BRACE INITIALIZATION)
int a {12};
Doğrudan ve kopya listesi başlatma işlevi hemen hemen aynıdır, ancak genellikle doğrudan biçim tercih edilir. - …!… Mümkün olduğunda parantez kullanarak başlatmayı tercih edin.
- …!… Başlatılmamış bir değişkende saklanan değeri kullanmak tanımsız davranışa (UB- Undefined Behaviour) neden olur.
- …!… Oluşturulduktan sonra değişkenlerinizi başlatın.
POINTER Kavramı
Ham işaretçi (raw owning pointer), ömrü akıllı işaretçi (smart pointer) gibi bir kapsülleme nesnesi tarafından kontrol edilmeyen bir işaretçidir. Bir program bellekteki öbek (heap) üzerinde bir nesne tahsis ettiğinde, o nesnenin adresini bir işaretçi şeklinde alır. Bu tür işaretçilere sahip olma işaretçileri (raw owning pointer) denir.
dereferencing = İşrateçinin gösterdiği adresteki değeri almak
// 100 numaralı adreste 2 byte uzunluğunda 7 değeri tutulsun
int x = 7;
cout << &x; // diyelimki 100 diye adresi döner// 200 numaralı adreste int tipinde değer okuyacak p işaretçisi
int *p = &x
Pointer Örneği
Özetle stack & heap üstünde nesne oluşturma komutunu new
anahtar sözcüğü etkiler. Bir işaretçiniz varsa, siz new
ile nesne yaratana kadar hiçbir şey oluşturulmaz. Stack üstünde bir nesne yaratmak için ise sınıfın yapıcı metodunu çağırarak (new
olmaksızın) nesneyi başlatırız.
Soldaki ekran görüntüsünde:
- Stack üstünde bir x nesnesi yaratıldı ve 16. satırdan çıkıldığında x değişkeninin otomatik olarak desctructor metodu çağrıldı ve bellekten temizlendi.
STACK üstünde daha az alanın olduğu, ama temizlik açısından kodun kapsamı sona erince herşeyin otomatik temizlenir ancak HEAP için yönetimi raw owning pointer kullandığımız yani smart pointer kullanmadığımız sürece biz yönetmek zorundayız. Dinamik bellek rezervasyonu istiyorsanız ve nesneyi elle silmek istemiyorsanız std::unique_ptr
, std::make_unique
kullanabilirsiniz (aşağıda anlatılacak).
Sağdaki ekran görüntüsünde:
- x İşaretçisi kod kapsamından çıkmasın rağmen yok edici metodu çağrılmaz. Hem X tipinde HEAP üstündeki değeri hem x işaretçi değişkeni yaşamaya devam eder.
delete x
ile HEAP üstündeki x değişkeninin işaret ettiği değerini sileriz, ardından x değişkenine nullptr
değerini atayarak boşa çıkartırız. Böylece x değişkeninin yok edici metodunun çalıştırıldığını görebiliriz.
Kod kapsamından çıkıldığında değişkenin otomatik olarak temizlenmesini sağlamak için stack üstünde yaratıyor veya delete işaretçi_değişken
komutuyla heap üstündeki değeri siliyoruz.
int main(){
X x{}; // x kapsam dışına çıkılınca otomatik silinecek
X *p = x; // x nesnesini işaretçi değişken p'ye atıyoruz
delete p; // p işaretçisi üstünden x değişkeni silinir
p = nullptr;
}
Stack üstündeki nesnenin metotlarına erişirken “.
” kullanırken işaretçinin gösterdiği nesnenin metotlarına “->
” ile erişiriz. Aşağıdaki ekran görüntüsünde satır 19 ve 22. satırlar.
Ref: *
#include <iostream>
using namespace std;int main() {// Dizi adresleyen işaretçi tanımlanırken başlatılıyor
// new ile başlatılan değer HEAP üstünde yaratılır
int *p = new int[20];// işaretçi daha büyük bir diziye atanmadan önce
// belleğin çöplüğe dönmemesi için,
// delete ile ilk işaret ettiği heap üstündeki
// 20 elemanlı diziyi siliyoruz
delete []p;// yeni 40 elemanlı diziyi new ile HEAP üstünde yaratıyoruz
// bu değeri p işaretçimize atıyoruz
p = new int[40]; return Q;
}1213 return Q;
Çalışan bir programa süreç (process) denir. İşletim sistemi (OS), belirli bir işlemin sahip olduğu belleğin kaydını tutar.
- İşletim sistemi, bir işlemin sahip olmadığı herhangi bir şeye bakmasına veya belleğe kaydetmesine izin vermez. Yani bellekteki her hareketi işletim sistemi gözetir ve yetkisiz erişimlere müsade etmez!
- Yığın yöneticisi (Heap Manager) bir sürecin parçasıdır. Gerektiğinde işletim sisteminden bellek ister ve işletim sistemine belleği geri verebilir. Tipik olarak, oldukça büyük bellek parçalarını ister ya da geri verir.
- Yığın yöneticisi, yığındaki bellekle ilgili kendi tablosunu tutar. Bu belleğin bir kısmı, yığın yöneticisi tarafından programa tahsis edilmiştir ve program bu bellekle istediğini yapmakta özgürdür. Yığının diğer bölümleri yığın yöneticisine ait olarak işaretlenir. Hâlâ sürece aittir, ancak program tarafından kullanılmamalıdır. İşletim sistemi, yığın yöneticisinin yığındaki bellek sahipliği kavramını bilmiyor. İşletim sisteminin bildiği kadarıyla, hepsi sürece aittir.
- Bir işlemin belleğinin bir kısmı çalışma zamanı yığını (run time stack) için kullanılır. İşletim sistemi, çalışma zamanı yığınının sınırlarını takip eder ve bir programın bir çerçevenin parçası olarak ayrılmamış belleğin kullanmasına izin vermez. Böylece, çalışma zamanı yığınındaki bellek, çerçevelere (frames) karşılık gelen küçük parçalar halinde işletim sisteminden ayrılır (allocate) ve geri verilir (deallocate)*.
- Çalışma zamanı yığınını bozarsanız, hata ayıklayıcı programda neler olduğunu söyleyemez.
İşletim sistemine göre, bir işlem belleği yanlış bir şekilde kullandığında veya kendisine ait olmayan belleği kullandığında bir bellek hatası (memory fault) oluşur. Bir Linux sisteminde, bir bellek hatasına segmentasyon hatası (segmentation fault) denir. Bir bellek hatası oluştuğunda, işletim sistemi işlemi hemen sonlandırır.
Askıda Kalan İşaretçiler (Dangling Pointers)
Askıda kalmış bir işaretçi (dangling pointer), programınızın kullanmaması gereken bir bellek işaretçisidir. Asılı bir işaretçinin sahip olabileceği birkaç durum vardır:
- işletim sisteminin sizin işleminize ait olmadığını bildiği, belleğin bir bölümündeki bir adrestir. Böyle sarkan bir işaretçinin kullanılması, bir bellek hatasına yol açar.
- sildiğiniz öbekteki belleği (heap üstünde yaratılmış değişkeni) gösteren işaretçidir. Hataya neden olmaz ama sonuçta belleği kötü kullanmış olursunuz.
- sildiğiniz işaretçiye değer atamak
- Başlatılmamış bir işaretçi değişkeni, yalnızca nereye işaret ettiğini bilmediğiniz için genellikle sarkan bir işaretçi olarak adlandırılır.
int* p;
*p = 0;
- Dizinin sınırları dışında olan bir dizi indeksi kullanmak, aynı zamanda sarkan bir işaretçinin kullanımı olarak kabul edilir.
int a[10];
a[10] = 1;a dizisinin izin verilen dizinleri 0 ile 9 arasındadır ve a[10] dizinin dışındadır. (Dizi sınırları hataları için otomatik bir kontrol yoktur. Bu kontrolleri sizin yapmanız beklenir.)
- Bazı belleklerin salt okunur olarak işaretlendiğini düşünün, programınız salt okunur bellekte bir şeyi değiştirmeye çalışırsa, bir bellek hatası alır. Örneğin, bir dize sabiti salt okunur bellekte depolanır. Dize sabitlerinin (string) diziler olarak ele alınabileceğini göreceğiz.
char* str = "a string constant";
str[0] = 'b';
// "bir dize sabitinin" saklandığı belleği değiştirmeye çalıştığı için bir bellek hatasına yol açar. new kullanarak bellek ayırmanın yerine bir dize sabiti kullanmayın.
- Boş göstericilerden kaynaklanan bellek hataları: Bellek adresi 0 hiçbir zaman işleminize ait değildir. 0 adresindeki belleği kullanmaya yönelik herhangi bir girişim, bir bellek hatasına yol açacaktır.
- Yetersiz bellek nedeniyle bellek hataları: Programınız, çalışma zamanı yığınında veya yığında bellek yetersiz kalırsa, büyük olasılıkla bir bellek hatası alır.
Asılı bir işaretçi kullanmak çok garip hatalara neden olabilir. İmkansız gibi görünen bir şey olursa, sarkan bir işaretçi kullandığınızdan şüphelenmelisiniz. Bir bellek hatası alırsanız, hatanın tam olarak nerede oluştuğunu bulmak için bir hata ayıklayıcı kullanın. Programı hata ayıklayıcıda çalıştırmanız yeterlidir. Bellek hatası oluştuğunda program duracaktır. O noktadaki işaretçilerin ve dizi indekslerinin değerlerine bakın. Bir p göstergesinin geçerli olup olmadığından emin değilseniz, *p
değerini göstermeye çalışın. Bir Linux sisteminde, bir boş gösterici 0x0
veya (null
) olarak gösterilir; yığına bir işaretçi, 0x6a003c
gibi nispeten küçük bir sayıdır; ve çalışma zamanı yığınına bir işaretçi, 0xffffffffffff2104
gibi çok büyük bir sayıdır.
auto
C++ derleyici auto ile kendisinin gelen değerin tipini tespit ederek bir değişken yaratır.
for(char c : metin)...yerinefor(auto c : metin)...
Vector içindeki her elemanı for döngüsünde değişkene kopyalamak ilkel tipler için ucuz bir yöntem.
vector<int> v{'a','l','i'}
for(auto c : v)...
Ancak vektörümüz string veya daha karmaşık elemanlar taşıyorsa bu pahalı hale gelir. Örneğin string elemanları olan vektör için değeri bir değişken yaratıp içine kopyalamak yerine adresini verebiliriz:
vector<string> v{”a”,”l”,”i”}
for(auto & c : v)... <- Adresini c'ye ata
Eğer değeri değiştirmeyeceksek sabit referans şeklinde read-only olmasını sağlayabiliriz:
vector<string> v{”a”,”l”,”i”}
for(auto const& c : v)... <- Adresini sadece okumak için c'ye ata
Automatic Return Type Deduction
Derleyici, işlevin dönüş tipini fonksiyonun içine bakarak bizim yerimize analiz eder ve işlevin başına tekrar dönüş tipi yazmamıza gerek kalmaz (sadece auto
yazarız):
#include <iostream>
using namespace std;auto islev(int parametre){
return "Merhaba Dünya\n";
}int main() {
std::cout << "Hello World!\n";
cout << islev(12);
}
Pair
Tuple
Tuple, bir dizi öğeyi tutabilen bir nesnedir. Öğeler farklı veri türlerinde olabilir. Tuple öğeleri, erişilecekleri sırayla bağımsız değişkenler olarak başlatılır.
C# içinde Tuple tipinde değişken tanımlama:
C++ içinde tuple değişken tanımlama:
tuple<tip1, tip2...> değişken (örnek1, örnek2...)
std::make_tuple(p1,p2,p3...)
std::get<tuple_öğesinin_tipi> (tuple_değişken)
std::get<tuple_öğresinin_1_endeksli_sırası>(tuple_değişken)
Oluşturmak:1) declare and initialize
tuple<int,char> foo (10,'x');2) automatic type deduction
auto bar = std::make_tuple ("test", 3.1, 14, 'y');
3) declaration and initialization are seperate
tuple <char, int, float, string> degisken;
degisken = make_tuple('a', 10, 15.5, "benim adım cemil");
Casting — Tip Dönüşümü
Diğer dillerdeki gibi dönüştürmek istediğiniz tipi parantez içinde yazarak dönüşümü sağlayabilirsiniz.
Sınıf ve Nesne Oluşturmak
Yapıcı ve Yıkıcı Metotlar
Eğer bir sınıfta varsayılan yapıcı ve yıkıcı metotlar tanımlı değilse derleyici otomatik olarak bu metotları ekler tıpkı C# ve Java’da olduğu gibi.
Varsayılan yapıcı metot ve yıkıcı metot (yıkıcı metot tektir ve bu yüzden varsayılanı yoktur).
Copy Constructor geliştirici tarafından tanımlı değilse yine derleyici tarafından yapıcı ve yıkıcı metotlardaki gibi derleyici tarafından oluşturulur. Aşağıdaki ekran görüntüsünde yapıcı metot bir kez çağırılır s1
için ancak s2
derleyici tarafından yaratılan copy constructor kullanılarak oluşturulduğundan çıktıda göremiyoruz. main
Fonksiyonundan çıkarken her iki değişkenin de destructor
fonksiyonu tetiklenir.
Copy Constructor bilinçli olarak oluşturulduğunda bu fonksiyona girildiğini çıktılarda görüyoruz.
Eğer copy constructor ile çoklanmasın istersek ve Sinif s2 = s1;
yazarsak:
Copy assignment operators *
Çağrılabilir Nesne (Callable Object)
Çağrılabilir bir nesne, object()
veya object(args)
sözdizimi ile bir işlev gibi çağrılabilen bir nesneler için kullanılır; diğer bir deyişle, bir işlev işaretçisi (function pointer) veya operator()
işlevini aşırı yükleyen sınıf türünde bir nesne. operator()
öğesinin sınıfınızdaki aşırı yüklenmesi onu çağrılabilir hale getirir.
Biraz yukarıdan bakarak anlamaya çalışalım. C++ dilinde fonksiyon tanımları:
- Hepimizin malumu “sıradan fonksiyonlar”
- Sınıfların üyesi olan fonksiyonlar (nesneye ait — dinamik)
- Sınıfa ait fonksiyonlar (static)
- functor
- Function pointer
- Lambda expression C++ 11 added standard (*, )
- Std:: Function C++ 11 Addition Standard
Sıradan Fonksiyonlar
#include <iostream>// herhangi bir şeye ait olmayıp sıradan bir fonksiyon
int topla(int a, int b) { return a + b; }int main() {
int a = 10;
int b = 20;
int toplam = topla(a, b);
std::cout << a << "+" << b << ": " << toplam << std::endl;
}
Sınıfın Statik ve Dinamik Üyesi Fonksiyonlar
#include <iostream>class HesapMakinasi {
public:
int base = 20; // this İşaretçisiyle nesnenin "base" üyesine erişiyoruz
int topla(const int a, const int b) const {
return this->base + a + b;
}; // Statik metot sınıfa aittir bu yüzden içinde this çalışmaz
static int statik_topla(const int a, const int b) {
return a + b;
};
};int main(void) {
HesapMakinasi facit;
int a = 10;
int b = 20;// Sinif'in üyesi olan fonksiyon
facit.topla(a, b);// sınıfa ait fonksiyonu çağıralım
obj.statik_topla(a, b);
HesapMakinasi::statik_topla(a, b);
}
functor
Sınıftan üretilen nesnenin bir fonksiyon gibi çağırılmasını sağlamak için operator()
fonksiyonunu aşırı yükleriz.
class FonksiyonGibiCalisanSinif {
public:
int operator()(const int a, const int b) const { return a + b; };
};int main() {
// nesne yarat ve fonksiyon gibi çağır
FonksiyonGibiCalisanSinif toplaNesnesi;
toplaNesnesi(5, 10);
}
Fonksiyon İşaretçisi
Bir değişkene fonksiyon atayabilmek için fonksiyonu işaretçi olarak tanımlayıp değer olarak verebiliriz.
#include <iostream>
#include <functional>
// Aşağıdaki tanıma uygun fonksiyon tipi oluşturacağız daha aşağıda
int max(int x, int y) { return x >= y ? x : y; }
int min(int x, int y) { return x <= y ? x : y; }
int add(int x, int y) { return x + y; }
int multiply(int x, int y) { return x * y; }// C# bir fonksiyonu tip olarak tanımlayıp değişkene değer olarak
// atayabilmemizi sağlasın diye delegate özelliğini verir elimize
//
// delegate int fonksiyonDelegate(int p1, int p2);
//
// işte bunu C++'ta
// - ya dışarıda tip tanımlayarak
// - ya fonksiyon argumanının tipinde belirterek tanımlıyoruz:
int (*hesapla1)(int, int); // dışarıda bir tip olara
// - ya da C# delegate gibi tip tanımlayarak
std::function<int(int, int)> hesapla2;// Şimdi bir fonksiyon içinde parametre olarak kullanalım
// fonksiyona argumanında fonksiyon tipini tanımlıyoruz
int calc(int x, int y, int(*hesapla)(int, int)) {
return hesapla(x, y);
}int calc(int x, int y, hesapla1 fonkArg) {
return fonkArg(x, y);
}int calc(int x, int y, hesapla2 fonkArg) {
return fonkArg(x, y);
}
int main(void)
{
int x = 2;
int y = 5; std::cout << "max: " << calc(x, y, max) << std::endl;
// max: 5
std::cout << "min: " << calc(x, y, min) << std::endl;
// min: 2 std::cout << "toplam: " << calc(x, y, add) << std::endl;
// toplam: 7 std::cout << "çarp: " << calc(x, y, multiply) << std::endl; // çarp: 10// aynı işi lambda fonksiyonla yapalım
auto sum = [](int x, int y)->int{ return x + y; };
std::cout << "toplam: " << calc(x, y, sum) << std::endl;
// toplam: 7
}
lambda Fonksiyon *
auto func1 = [] (int x, int y) -> int { return x + y; };
auto func2 = [] (int x, int y) { return x > y; };
auto func3 = [] { global_ip = 0; };
- [] bulunduğu kapsamdan (scope) hiçbir değişken yakalanmaz
- [&] bulunduğu kapsamdaki tüm değişkenleri yakalar ve lamda fonksiyon içine referans olarak geçirir
- [=] bulunduğu kapsamdaki tüm değişkenleri yakalar ve lamda fonksiyon içine değer olarak kopyalar
- [x,& y] x değişkenin değer olarak, y değişkenini referans olarak geçirir
- [&, x] x değer olarak geçerken diğer değişkenler referans olarak geçirilir
- [=, & y] y referans olarak geçerken diğer değişkenler değer olarak geçirilir
- [this] sınıf içindeki this işaretçisini geçirir. & veya =kullanıldıysa varsayılan olarak this de geçirilir
auto topla = [] (int x, int y) {return x + y;}; // lambda fonksiyonu bir fonksiyon işaretçisine atıyoruz
void (* func_ptr) (int, int) = topla; // ve bu fonksiyonu çalıştırıyoruz
func_ptr (10, 20);
bind İle Bir Fonksiyonu Başka Bir Nesneye Bağlamak
Bind() fonksiyonu, içine verilen objeye göre yeni bir fonksiyon kopyası yaratır. Oluşan bu kopya fonksiyonu daha sonradan argüman listesi ile beraber gönderilen objeye kullanabiliriz.
Bunu Javascript’te şöyle yaparız:
baglayacagimizFonksiyonunAdi.bind(hedefNesne, ...argumanDegerleri)
C++’de ise şöyle:
fonkum = std::bind(&Sinif::fonksiyon, baglanacakNesne,
std::placeholders::_1, std::placeholders::_2);fonkum(x, y);
fonkum(5, 10); using namespace std;fonkum1 = bind(&HesapMakinesi::topla, obj, placeholders::_1, 10);
fonkum1(5); // Sonuç 15 olur çünkü y için 10 değeri sabitlenmiş
Biraz daha güzeli:
#include <iostream>
#include <functional>void yazdir(int x, int y, int z) {
std::cout << x << " " << y << " " << z << std::endl;
}
std::function<void(int, int, int)> fonk;int main() {
//------------------------------------------------------------------
fonk1 = std::bind(&yazdir, 1, 2, 3);
//x=1, y=2, z=3 olacağı için parametre ne girsek işe yaramayacak
fonk1(4, 5, 6);
//çıktı: 123//------------------------------------------------------------------using namespace std;fonk2 = bind(&yazdir, placeholders::_2, placeholders::_1, 3);
// x=y, y=x, z=3
fonk2(1, 2, 7);
// çıktı: 213//------------------------------------------------------------------
int n = 10;
fonk3 = bind(&print_func, placeholders::_1, placeholders::_1, n);
// x=x, y=x, z=n fonk3(5, 6, 7);
//çıktı: 5510
}
Ref: developpaper.com
Sıralama (Sorting)
#include <algorithm>std::sort
Sınıflar
Bir değişken tanımlandığında başlatmazsak eğer işaret ettiği yerde çöp bilgiler olacaktır. Bunun önüne geçmek için değikenin değerini başlatmamız gerekiyor.
int degisken{}; // degiskenin int için varsayılan değeri 0 olur.
double degisken{}; // degisken için varsayılan değeri 0.0 olur.
std:string degisken{}; // degisken için varsayılan boş değerdir.
Peki ya özel tipte bir değişekni başlattığımızda ilk değeri ne olacak?
// Bana varsayılan bir dikdörtgen oluştur
Rectangle rect{}// Eni 10, boyu 20 olan bir dikdörtgen oluştur
Rectangle rect{10, 20}
Bir sınıfın default initializer ile başlatıldığında tetiklenen yeri yapıcı metodu olacağından sınıfın parametresiz ve parametreli yapıcı metotları aşağıdaki gibi olacaktır:
class Rectangle {private:
double width_{};
double height_{}; public:
// varsayılan yapıcı metot.
Rectangle() = default; // parametreli yapıcı metot width_ ve height_ veri üyesi
Rectangle(double w, double h) : width_{w}, height_{h} {} /* alan hesabı yapan metodun içinde sadece bir çarpım işlemi var
* sınıf üyeleri olan width_ ve height_ değişmediği için bu
* bu metodu const olarak işaretliyoruz.
*
double Area() const {
return width_ * height_;
} // Getter işlevler içeriği aynen döneceği için const olmalı
int Width() const { return width_; }
int Height() const { return height_; }};
Fonksiyonlar — İşlevler
Bir işlevin bölümleri:
int fonksiyonAdi(string, double)
PrototipifonksiyonAdi(string, double)
İmzası- `{…}` fonksiyonun uygulanma kısmı
int fonksiyonAdi(string param1, double param2)
{ // fonksiyonun
.. // uygulaması
.. // (implementation)
}
Aşırı Yükleme (function overloading)
Aynı isimde farklı parametre sayı veya tiplerinde birden fazla işlevin olması halidir.
void hede(int a){...}
void hede(double a){...}
int hede(int a){...} // Geçersiz! Dönüş tipi dikkate alınmaz!
void hede(int a, int b){...}
void hede(int a){...} // Geçersiz! Parametre sayısı&tipi aynı!
Pass by Value ve Pass by Reference *
Prefer pass by value for objects that are cheap to copy, and pass by const reference for objects that are expensive to copy. If you’re not sure whether an object is cheap or expensive to copy, favor pass by const reference.
Önce pass by value nedir bakalım. Değişkenin DEĞERİNİN geçirildiği duruma “pass by value” diyoruz. 0 geliyor 1 artıyor ve main
fonksiyonuna döndüğünde hala 0. Çünkü değerini geçirdiğimiz için yeni bir int
türünde nesne yaratılarak byVal
fonksiyonuna geçiriliyor.
Şimdi pass by reference nedir bakalım. main
Fonksiyonundaki a
değişkeninin adresini geçiriyoruz ve x
değişkeniyle a
değişkeni aynı adrese baktığı için x
değeri 2 arttırılınca a
’nın da hedef adresi aynı olduğu için 2 değerini main
fonksiyonunda görüyoruz.
class Kisi{
public:
Kisi(); private:
std::string adi;
std::string soyadi;
};class Doktor:public Kisi{
public:
Doktor(); private:
std::string diplomaNo;
std::string uzmanlik;
};bool receteYazabilirmi(Doktor dr);
Reçete yazabilecek doktoru sormak için:
Doktor dr;
bool yazarMi = receteYazabilirmi(dr);
dr
Nesnesini yaratmak için önce Doktor
yapıcı metodu, diplomaNo
ve uzmanlik
alanlarının yapıcı metotları, Kisi
sınıfının yapıcı metodu ve adi
, soyadi
string alanlarının yaratılması ile toplam 6 yapıcı metot çağırılmış olacak. Doğal olarak fonksiyondan çıkılırken tüm bu nesnelerin (Kisi, 2 string üyesi, Doktor ve onun da 2 string üyesi) yıkıcı metodu çalıştırılacak.
Tüm bu yapıcı, yıkıcı fonksiyon çağrılarından kurtulmak için pass by reference
bool receteYazabilirmi(const Doktor& dr);
Değişkeni fonksiyonun parametresine değer olarak geçirdiğimizde yeni bir nesne yaratarak değerini geçirmiş oluruz. Yani kopya oluşturmuş oluruz. int
, float
, double
, gibi kopyalanması ucuz olacak tipler için bu yöntemi kullanabiliriz ancak kopyalanması pahalı olabilecek nesnelerin değer olarak geçişini yapmak doğal olarak verimsiz bir yöntem. O halde referans olarak nesneyi fonksiyona geçirirsek kopya oluşturmayız. Ancak fonksiyona geçirilen bu bellek referansının değiştirilemez olması için const
ile sabitlemezsek, fonksiyon içinde değişikliğe uğrayabilirler.
inline Fonksiyon
Tanımladığın bir fonksiyonun önüne inline ifadesi getirdiğinde, derleme sırasında, derleyiciye; fonksiyonu çağırdığın yerde, o fonksiyonun içeriğinin bir kopyasını yerleştirmesini tavsiye etmiş oluyorsun. inline
olup olmama konusundaki son kararı derleyici verdiği için “tavsiye etmiş” oluyoruz, bu değişimin olacağının garantisi yok!
Derleyici önüne inline
belirteci eklediğin bir fonksiyonu, inline
olarak kullanılmayabilir; ya da inline
eklemesen de inline
olarak kullanılabilir.
En fazla 10 satır uzunlukta bir fonksiyonun varsa inline olarak işaretleyebiliriz çünkü bu fonkisyonu 15 yerde çağırıyorsak 10 satır+15 çağrıdan oluşan satır “inline
” ile bize 150 satır olarak kodumuzun büyümesiyle sonuçlanacak.
Bir işlev çok küçükse ve zaman açısından kritikse, onu satır içi olarak tanımlayabiliriz.
https://www.youtube.com/watch?v=SFgBr6Jd7ok
Derleyiciyi karmaşık bir fonksiyonu satır içi yapmaya zorlayamazsınız. Ancak bir fonksiyon basitçe yeterli olduğunda, derleyici bu işlevin kendisini satır içi yapabilir.
Header
dosyasında sınıf tanımlamaları ve inline
işlevleri kodlarken, cpp
dosyasında inline
olmayan işlevlerin tanımlamalarını yapabiliriz.
Multithread
std::thread
Bir thread düzgün yapılandırılmış ise yani çalıştıracağı fonksiyon veya fonksiyon gibi çağırılabilir nesne (functor
) yapıcı metoda geçirilmişse buna düzgün yapılandırılmış thread nesnesi (properly constructed thread object) diyoruz. Bu nesnenin ya join
veya detach
metodunu çağırırız.
Eğer join
veya detach
metodu çağırılmamış ise std::terminate
çağırılacağı için programınız güvensiz (unsafe) durumuna düşer.
join()
tetiklendiğinde çağıran thread durur, thread tamamlanıncaya kadar engellenir.
detach()
tetiklendiğinde, çağıran thread durmaz, çalışmaya devam eder.
joinable()
fonksiyonu 1 dönerse thread nesnesi doğru yapılandırılmış demektir ve join
veya detach
metotlarından birini çağırabiliriz. Bu metotlardan birini çağırdıktan sonra thread nesnesi artık joinable olmayacaktır ve eğer tekrar bu metotlardan birini çağırırsanız hata alırsınız.
join metodunun çağırıldığı yere synchronize point
adı verilir. Eğer thread nesnesinin join
metodu çağırılırsa çağırılan thread işlemeye başlar ve çağıran thread durur, thread nesnesinin işini bitirmesini bekler.
Eğer detach metodu çağırılırsa, çağıran thread ve çağırılan thread birbirinden bağımsız olarak işlemeye devam ederler:
https://replit.com/@cemt/thread-joinable-1
RAII (Resource Acquisition Is Initialization)
Yapıcı metotlar kaynakları alır, yıkıcı metot ise serbest bırakır.
std::algorithm
- sort,
- find,
- find_if,
- random_shuffle
- count,
- count_if,
- for_each
- copy
- copy_if
std::async
std::async iki farklı parametre alıyor ve bu parametrelere göre ana thread ile aynı veya ayrı bir thread içinde fonksiyonu çağırıyor.
- deferred
- async
Derleyici Bayrakları
Access özniteliği, uyguladıkları işlevler veya çağıranları tarafından geçersiz veya güvenli olmayan erişimlerin ve ayrıca hiçbir zaman okunmayan nesnelere salt yazma erişimlerinin algılanmasını sağlar.
Bu tür erişimler,
-Wstringop-overflow
,-Wuninitialized
,-Wunused
ve diğerleri gibi uyarılarla teşhis edilebilir.
C++ Projesinde Harici Kütüphane Kullanmak
Kodumuz basitçe :
#include <iostream>int main(){
std::cout << "Merhaba Dunya";
return 0;
}
Şimdi derleyip çalıştıralım:
Harici bir kütüphaneden (CinarFramework
) bir header dosyası içerip, içindeki bir sınıftan e
adında nesne yaratalım:
Harici bir kütüphane Cinar
isim uzayı altında Exception.h
dosyası içinde tanımlı sınıflardan IvalidMultipartContentException
adında bir sınıf mevcut.
Şimdi bu sınıftan bir nesne yaratıp tekrar derleyelim ve kütüphanede yer alan “Exception.h
başlık dosyasını bulamadım” hatasını görelim:
Bu kütüphaneyi sisteme yüklemeli ve içerdiği sınıf ve fonksiyonları bize sunan başlık dosyalarına erişmeliyiz.
Başlık dosyalarını Typescript dilindeki d.ts dosyaları gibi düşünebilirsiniz. Bir javascript kodunu derleyip ikili kod üretemeyiz ancak boşluk ve yeni satırları atarak sıkıştırıp (minification), değişken ve fonksiyon adlarını kısaltarak (uglification) hızlıca internet üstünden transferini sağlayabiliriz. Bir nevi ikili kod gibi düşünün bu çıktıyı. Bu çirkin javascript kodunu Typescript ile geliştirme yaparken kullanabilmemiz için bir tanımlama (definition) dosyasına ihtiyacımız olur. Fonksiyonlar, parametre ve dönüş tipleri, global değişkenler vs. her şeyi *.d.ts
dosyasından çekerek geliştirme yaparız. İşte bu tanımlama dosyaları tıpkı C++’da olduğu gibi tek başına bir anlamı olmayan ama kodu yazarken bize kütüphanenin yapısı hakkında bilgi veren verinin verisi (meta data) anlamında çalışan kodun tanımı olarak lazım olurlar.
Kütüphaneyi yüklemek için apt install cinarframework-dbg
komutunu kullanıyoruz ki; bu kütüphane erişebileceğiniz bir kütüphane olmayıp sadece örnek olarak kullanılıyor. Kurulu kütüphanenin bizi ilgilendiren içeriklerine göz atarak kavrayışımızı arttıralım:
# g++ main.cpp
main.cpp:2:29: fatal error: Cinar/Exception.h: No such file or directory
compilation terminated.
Şair burada diyor ki; #include "Cinar/Exception.h"
kodunu işletecek Exception.h
dosyasını bulamadım.
# g++ main.cpp -I/opt/cinar/include
In file included from main.cpp:2:0:
/opt/cinar/include/Cinar/Exception.h:177:28: warning: override controls (override/final) only available with -std=c++11 or -std=gnu++11
std::string GetTitle() const override;
^
...
^
/tmp/ccSnxu3E.o: In function `main':
main.cpp:(.text+0x20): undefined reference to `Cinar::InvalidMultipartContentException::InvalidMultipartContentException()'
/tmp/ccSnxu3E.o: In function `Cinar::InvalidMultipartContentException::~InvalidMultipartContentException()':
main.cpp:(.text._ZN5Cinar32InvalidMultipartContentExceptionD2Ev[_ZN5Cinar32InvalidMultipartContentExceptionD5Ev]+0xd): undefined reference to `vtable for Cinar::InvalidMultipartContentException'
collect2: error: ld returned 1 exit status
Bazı korunmuş kelimeler vardır ki, C++ 11 ile çözümlenir bu yüzden -std=c++11
parametresi eklenmeli.
# g++ main.cpp -I/opt/cinar/include -std=c++11
/tmp/cc2YPlvl.o: In function `main':
main.cpp:(.text+0x20): undefined reference to `Cinar::InvalidMultipartContentException::InvalidMultipartContentException()'
/tmp/cc2YPlvl.o: In function `Cinar::InvalidMultipartContentException::~InvalidMultipartContentException()':
main.cpp:(.text._ZN5Cinar32InvalidMultipartContentExceptionD2Ev[_ZN5Cinar32InvalidMultipartContentExceptionD5Ev]+0xd): undefined reference to `vtable for Cinar::InvalidMultipartContentException'
collect2: error: ld returned 1 exit status
Exception.h
dosyasında “InvalidMultipartContentException” sınıfını ve varsayılan yapıcı metodunu #include “Cinar/Exception.h”
dosyasında görüyoruz.
Bu sınıftan e
adında bir nesne yaratıyoruz. InvalidMultipartContentException e;
ile varsayılan yapılandırıcıya çağrı yapıyoruz.
undefined reference to `vtable for Cinar::InvalidMultipartContentException'
Çıktısı bize
undefined reference to `Cinar::InvalidMultipartContentException::InvalidMultipartContentException()
fonksiyonunun tanımsız “undefined” olarak gösteriyor.
#include "Cinar/Exception.h"
başlık dosyasını çift tırnak karakterleri arasında ("Cinar/Exception.h"
) yazdığımız için -I
anahtarıyla derleyici bu dosyanın yerini de söylememiz gerekiyor:
g++ main.cpp -I/opt/cinar/include -std=c++11
Komutu çalıştığında, header dosyasını vererek fonksiyonun imzasını eriştirmiş ancak fonksiyonun gövdesini içeren kütüphane dosyasını (*.so) vermemiş oluyoruz. Bu yüzden undefined
hatası veriyor!
Not: Daha aşağıda derleyicinin çift tırnak içindeki dosyaları varsayılan olarak nerelerde arayacağını göreceğiz
Şimdi kütüphaneyi içerecek parametreyle (-lCinarFramework
) komutu tekrar çalıştıralım. Kütüphanenin adını -l
bayrağıyla; -lCinarFramework
olarak verdik:
Bu kez CinarFramework
kütüphanesi benim bildiğim yerlerde yok, “cannot find
” diyor.
Bu yüzden kütüphanenin olduğu dizini -L
anahtarıyla veriyoruz. Paketin bu dosyayı nereye kaydettiğini bulalım:
g++ main.cpp
-I/opt/cinar/include # Header dosyalarının yerini (*.h dosyaları)
-lCinarFramework # Kütüphanenin adını (*.so dosyası)
-L/opt/cinar/lib # Kütüphanenin hangi dizinde olduğunu
-std=c++11
Dikkat edeceğimiz 2 şey var:
- Kütüphane dosyamızın adıyla paket adının farklı ancak
lib
ile başlayan dosya adının (libCinarFramework.so
) linking ve run-time da kullanılması. ../cinar/include
ve../cinar/lib
dizinlerinde bulunan.h/.hpp
ve.so
dosyalarının kullanımı
Bu sınıf cinarframework-dbg
isimli debian paketiyle geliyor. Kütüphane dosyamız libCinarFramework.so
adında ancak paket adı cinarframework-dbg
. Aralarında bir bağ kurmaya gerek yok. İçinde binlerce kütüphane dosyası (xxx.so) olabilirdi. Aynı paket ayrıca kod geliştirmede içindeki sınıf ve fonksiyonlara erişmek için başlık (header/*.h/*.hpp) dosyalarını da barındırıyor.
Nitekim #include "Cinar/Exception.h"
satırıyla bu başlık dosyasını kodumuza dahil ediyoruz ki; derleme veya intellisense kısmında sorun yaşamayalım.
lib/libCinarFramework.so
dosyasını ise kodların bağlanması kısmında derleyici kullanmak için isteyecek
Çalışan komut satırı şu oldu:
# g++ main.cpp
-L/opt/cinar/lib/
-lCinarFramework
-I/opt/cinar/include/
-std=c++11
Kütüphaneleri Bağlarken (Linking Libraries)
Bir örnek de kütüphaneleri bağlama kısmıyla ilgili.
#include <iostream>
#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>using namespace std;int main(){
using namespace g3;
auto worker = LogWorker::createLogWorker();
auto defaultHandler = worker->addDefaultLogger("etiketi", "./log.txt"); // logger başlatıldı
g3::initializeLogging(worker.get()); LOG(INFO) << "streaming API is as easy as ABC or " << 123;
g3::internal::shutDownLogging(); cout << "merhaba dunya" << endl;
return 0;
}
#include <iostream>
#include <g3log/g3log.hpp>
#include <g3log/logworker.hpp>using namespace std;int main(){
using namespace g3;
auto worker = LogWorker::createLogWorker();
auto defaultHandler = worker->addDefaultLogger("etiketi", "./log.txt"); // logger başlatıldı
g3::initializeLogging(worker.get()); LOG(INFO) << "streaming API is as easy as ABC or " << 123;
g3::internal::shutDownLogging(); cout << "merhaba dunya" << endl;
return 0;
}
Paketten çıkan kütüphaneden libg3logger.so
dosyasını derlemeye dahil etmek için lib
ve .so
düştükten sonra kalanı -l
ile parametre olarak veriyoruz (-lg3logger
):
Peki g++ bu kütüphanenin yerini nasıl buldu?
Kütüphaneleri Nasıl Bulur
Bir programın yahut kütüphanenin programın bağımlı olduğu paylaşılan kütüphaneleri listelemek için ldd
komutunu kullanabiliriz:
ldd ./myprogram
C++ programlarını derlerken kütüphane dosyalarını (.so
-shared library- ve .a
-statik library-) g++
ile kullanımı:
Önce kütüphane dosyaları (.a
ve .so
) anlayalım:
Özetle:
.a
dosyaları tamamen derleme zamanında kullanılır..so
dosyaları hem derleme zamanında (referans için) hem de çalışma zamanında (yükleme için) kullanılır.
Bu yaklaşım, dinamik kütüphanelerin bellek kullanımını optimize etmesine ve birden fazla program tarafından paylaşılabilmesine olanak tanır.
1. .a
(Statik Kütüphane) Dosyaları:
- Derleme zamanında kullanılır. Derleme sırasında tüm kodu programa dahil edilir.
- Programın içine doğrudan dahil edilir (gömülür).
- Çalıştırılabilir dosyanın bir parçası haline gelir. Sonuçta daha büyük ama bağımsız bir çalıştırılabilir dosya oluşur.
- Program her çalıştırıldığında yeniden yüklenmesine gerek yoktur.
2. .so
(Paylaşılan/Dinamik Kütüphane) Dosyaları:
- Hem derleme zamanında hem de çalışma zamanında rol oynar.
- Derleme sırasında:
1. Kütüphane referansları çözümlenir.
2. Sembol tabloları oluşturulur. - Çalışma zamanında:
1. Kütüphane dosyası bellekte yüklenir.
2. Semboller çözümlenir ve bağlanır.
Örnek süreç:
1. Derleme zamanı:
g++ -c program.cpp # Nesne dosyası oluşturur
g++ program.o -lmylib -o program # Çalıştırılabilir dosya oluşturur
Bu aşamada, .so
dosyası için sadece referanslar oluşturulur, içerik kopyalanmaz.
2. Çalışma zamanı:
./program
Program başlatıldığında, dinamik bağlayıcı (ld.so) gerekli .so dosyalarını yükler.
Şimdi kütüphane dosyalarını nerelerde bulacağına bakalım:
1. Komut Flag’leri:
-L<dizin>
: Kütüphane dosyalarının aranacağı ek dizinleri belirtir.-l<kütüphane_adı>
: Bağlanacak kütüphaneyi belirtir (lib
öneki ve.so/.a
soneki olmadan).
Örnek:
g++ program.cpp -L/usr/local/lib -lmylib
2. Ortam Değişkenleri:
LD_LIBRARY_PATH
: Çalışma zamanında dinamik kütüphanelerin aranacağı dizinleri belirtir.LIBRARY_PATH
: Derleme zamanında statik ve paylaşılan kütüphanelerin aranacağı dizinleri belirtir.
Örnek:
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH
export LIBRARY_PATH=/path/to/lib:$LIBRARY_PATH
3. Varsayılan Dizinler:
Tipik olarak aşağıdaki dizinlerde kütüphaneler aranır:
/lib
/usr/lib
/usr/local/lib
- Derleme Örneği:
g++ program.cpp -L/path/to/lib -lmylib -o program
4. Kütüphane Önceliği:
g++ önce dinamik (.so), sonra statik (.a) kütüphaneleri arar. Bunu değiştirmek için:
- Sadece statik:
-static
- Önce statik, sonra dinamik:
-Wl,-Bstatic -lmylib -Wl,-Bdynamic
5. Kütüphane Bilgisi Alma:
nm -D /path/to/libmylib.so objdump -T /path/to/libmylib.so
6. Özel Kütüphane Konumları:
- Kütüphaneleri
/etc/ld.so.conf
dosyasına veya/etc/ld.so.conf.d/
dizinine ekleyebilirsiniz. Ardından ldconfig çalıştırılmalıdır.
ldconfig
: Dinamik bağlayıcının önbelleğini günceller.
sudo ldconfig
7. Rpath Kullanımı:
Çalıştırılabilir dosyaya kütüphane yolunu gömme:
g++ program.cpp -Wl,-rpath,/path/to/lib -L/path/to/lib -lmylib -o program
Yukarıdaki tanımlara göre cevap; kütüphaneyi varsayılan dizinlerde bulacaktır:
/usr/lib
/usr/local/lib
/lib
Bizim g3log kütüphanemiz de /usr/lib
dizininde saklı. Eğer buradaki dosyayı değilde başka bir dizindeki dosyayı kullanmasını isteseydik derleme komutumuzda -L
anahtarıyla bunu belirtmemiz gerekirdi. Aşağıdaki ifadeyle ya paylaşılan libg3logger.so
dosyasını ya da statik derlenmiş libg3logger.a
dosyasını arayacaktı:
-L/path/to/custom/library -lg3logger
--verbose
anahtarıyla derlediğimizde LIBRARY_PATH
anahtarının içinde /usr/lib/
dizinini görebiliriz. Derleyici (g++) LIBRARY_PATH
ortam değişkeninin değeri olan dizinleri otomatik olarak araştırarak -l
anahtarıyla linklenen kütüphaneleri bulamaya çalışır.
Ben bu komut satırında olanları biraz daha yoğun görebilmek için --verbose
anahtarını kullanacak ve çıktıyı anlamak için irdeleyeceğim :
İlk satırların gcc’nin kendi tanımları olarak geldiğini öğrendim. Bunun için g++ -v
komutuyla karşıma geleni ve derlemedeki --verbose
anahtarıyla karşıma geleni yan yana koyayım:
# g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v
--with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.12'
--with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs
--enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++
--prefix=/usr
--program-suffix=-5
--enable-shared
--enable-linker-build-id
--libexecdir=/usr/lib
--without-included-gettext
--enable-threads=posix
--libdir=/usr/lib
--enable-nls
--with-sysroot=/
--enable-clocale=gnu
--enable-libstdcxx-debug
--enable-libstdcxx-time=yes
--with-default-libstdcxx-abi=new
--enable-gnu-unique-object
--disable-vtable-verify
--enable-libmpx
--enable-plugin
--with-system-zlib
--disable-browser-plugin
--enable-java-awt=gtk
--enable-gtk-cairo
--with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre
--enable-java-home
--with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64
--with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64
--with-arch-directory=amd64
--with-ecj-jar=/usr/share/java/eclipse-ecj.jar
--enable-objc-gc
--enable-multiarch
--disable-werror
--with-arch-32=i686
--with-abi=m64
--with-multilib-list=m32,m64,mx32
--enable-multilib
--with-tune=generic
--enable-checking=release
--build=x86_64-linux-gnu
--host=x86_64-linux-gnu
--target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)
İkinci kısımı için tafsilat verelim:
COLLECT_GCC_OPTIONS='-L/opt/cinar/lib/'
'-I'
'/opt/cinar/include/'
'-std=c++11'
'-v'
'-shared-libgcc'
'-mtune=generic'
'-march=x86-64' /usr/lib/gcc/x86_64-linux-gnu/5/cc1plus
-quiet -v
-I /opt/cinar/include/
-imultiarch x86_64-linux-gnu
-D_GNU_SOURCE main.cpp
-quiet
-dumpbase main.cpp
-mtune=generic
-march=x86-64
-auxbase main
-std=c++11
-version
-fstack-protector-strong
-Wformat
-Wformat-security
-o /tmp/ccwVj0cE.s
C++ 11 versiyonuyla derlemek istediğimizi -std=c++11
olarak belirttiğimiz için GNU aşağıdaki derleyiciyi seçiyor:
GNU C++11 (Ubuntu 5.4.0-6ubuntu1~16.04.12) version 5.4.0 20160609 (x86_64-linux-gnu)
compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100
--param ggc-min-heapsize=131072
Demek ki bir yerlerde iki kez bu ../c++/5
dizinini geçmişiz!
ignoring duplicate directory "/usr/include/x86_64-linux-gnu/c++/5"
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/5/../../../../x86_64-linux-gnu/include"
Burası şokomelli kısmı. Bir dosyayı include ile içeri alırken iki yöntem var #include "..."
ve #include <...>
bizim için
- harici bir kütüphane ise açılı parantezlerin
<
>
, - dosya olarak kodumuza yakın bir yerde ise
"
"
arasında
kütüphanenin yerini belirtiyoruz.
Harici kütüphaneleri sistem dizinlerinde varsayılan olarak ararken bu dizinlerden önce aranacak dizin[leri]
-L[dizin_yolu]
ile veriyoruz.Header dosyaları (başlık dosyaları) için varsayılan dizinlerin üstünde bakmasını istediğimiz dizinleri
-I[dizin_yolu]
parametresiyle veriyoruz. Varsayılan dizinlerg++ -v
ile aşağıdaki gibi listelenecektir
- #include <…> search starts here:
/usr/include/c++/9
/usr/include/x86_64-linux-gnu/c++/9
/usr/include/c++/9/backward
/usr/lib/gcc/x86_64-linux-gnu/9/include
/usr/local/include
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.-
g++
Çalışırken ortam değişkenlerindenCPATH
,C_INCLUDE_PATH
veyaCPLUS_INCLUDE_PATH
değişkeninin değeri olan dizin[ler] başlık dosyaları için bakılacak dizinler arasına alınır.
$ export CPLUS_INCLUDE_PATH=/Source/AMF_COMMON/src/:/tmp
$ make
>>> Compiling Li/X1MessageProcessor.cpp
Using built-in specs.
COLLECT_GCC=g++
Target: x86_64-linux-gnu
...
.....
.......
#include "..." search starts here:
#include <...> search starts here:
/opt/cinar/include
/opt/cinar/include/asn
/opt/cinar/include/crypto
/usr/include/mongocxx/v_noabi
/usr/include/bsoncxx/v_noabi
../../../src
/opt/cinar/include/15.201912
/opt/cinar/include/15.201906-Interworking
/usr/include/prometheus
/Source/AMF_COMMON/src/ <----- CPLUS_INCLUDE_PATH değerlerinden
/tmp <----- CPLUS_INCLUDE_PATH değerlerinden
/usr/include/c++/5
#include "..." search starts here:
#include <...> search starts here:
/opt/cinar/include/
/usr/include/c++/5
/usr/include/x86_64-linux-gnu/c++/5
/usr/include/c++/5/backward
/usr/lib/gcc/x86_64-linux-gnu/5/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/5/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.GNU C++11 (Ubuntu 5.4.0-6ubuntu1~16.04.12) version 5.4.0 20160609 (x86_64-linux-gnu)
compiled by GNU C version 5.4.0 20160609, GMP version 6.1.0, MPFR version 3.1.4, MPC version 1.0.3
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 85af4995304287cdd19cfa43cf5d6cf1
COLLECT_GCC_OPTIONS='-L/opt/cinar/lib/'
'-I' '/opt/cinar/include/'
'-std=c++11'
'-v'
'-shared-libgcc'
'-mtune=generic'
'-march=x86-64'
as -v -I /opt/cinar/include/ --64 -o /tmp/cctrrKfo.o /tmp/ccwVj0cE.s
GNU assembler version 2.26.1 (x86_64-linux-gnu) using BFD version (GNU Binutils for Ubuntu) 2.26.1COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/5/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/5/../../../:/lib/:/usr/lib/COLLECT_GCC_OPTIONS='-L/opt/cinar/lib/'
'-I' '/opt/cinar/include/'
'-std=c++11'
'-v'
'-shared-libgcc'
'-mtune=generic'
'-march=x86-64'
/usr/lib/gcc/x86_64-linux-gnu/5/collect2
-plugin /usr/lib/gcc/x86_64-linux-gnu/5/liblto_plugin.so
-plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
-plugin-opt=-fresolution=/tmp/ccenBuj8.res
-plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lgcc
-plugin-opt=-pass-through=-lc
-plugin-opt=-pass-through=-lgcc_s
-plugin-opt=-pass-through=-lgcc
--sysroot=/
--build-id
--eh-frame-hdr
-m elf_x86_64
--hash-style=gnu
--as-needed
-dynamic-linker /lib64/ld-linux-x86-64.so.2
-z relro /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
-L/opt/cinar/lib/ <--- **** kütüphane dizini buraya gelmiş ***
-L/usr/lib/gcc/x86_64-linux-gnu/5
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../../../lib
-L/lib/x86_64-linux-gnu
-L/lib/../lib
-L/usr/lib/x86_64-linux-gnu
-L/usr/lib/../lib
-L/usr/lib/gcc/x86_64-linux-gnu/5/../../.. /tmp/cctrrKfo.o
-lCinarFramework <--- **** Bizim kütüphane buraya gelmiş ***
-lstdc++
-lm
-lgcc_s
-lgcc
-lc
-lgcc_s
-lgcc /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
root@f16a16cc0bbd:/kod# g++ main.cpp -I/opt/cinar/include -lCinarFramework -std=c++11
/usr/bin/ld: cannot find -lCinarFramework
collect2: error: ld returned 1 exit status
root@f16a16cc0bbd:/kod#
root@f16a16cc0bbd:/kod# g++ main.cpp -I/opt/cinar/include -L/opt/cinar/lib -lCinarFramework -std=c++11
root@f16a16cc0bbd:/kod#
root@f16a16cc0bbd:/kod# echo $?
0
root@f16a16cc0bbd:/kod# ./main
bash: ./main: No such file or directory
root@f16a16cc0bbd:/kod# ./a.out
merhaba dunya
Makefile
Varsayılan olarak make komutunu çalıştırdığımızda all
isimli etiket tetiklenir. Derleme için gerekli araçların yüklenmesi için prereqs
, yapılandırma için build
etiketlerini sırayla all
etiketi içinde tetikliyoruz. Böylece derlemede gerekli olacak g3log
kütüphanesini içeren paket prereqs
aşamasında kurulmuş olacak.
sstreaö — stringstream İle C#’taki StringBuilder
Ücretsiz C++ eğitiminde tesadüf ettim sstream kütüphanesine. Besbelli StringBuilder gibi çalışıyor ve verimli bir şekilde string işlemleri yapmamızı sağlıyor.
—
boolapha — nonboolalpha
/*
boolalpha format bayrağı ayarlandığında, bool değerleri metinsel temsillerine göre kullanılır doğru (true) veya yanlış (false).
nonboolalpha format bayrağı ayarlandığında, bool değerleri tam sayı olarak 1 (true) veya 0 (false) görüntülenir.
*/
#include <iostream> // std::cout, std::boolalpha, std::noboolalphausing namespace std;class Deneme {
bool hede;
public:
void yazdir(){ cout << "hede: " << this->hede << endl; }
};int main () {
Deneme d{};
bool b = true;
cout << "b: " << b << '\n'; // b: 1
d.yazdir(); // hede: 0
//-----------------------------
cout << boolalpha << '\n';
(Deneme{}).yazdir(); // hede: false
cout << "b: " << b << '\n'; // b: true
//-----------------------------
cout << noboolalpha << '\n';
cout << "b: " << b << '\n'; // b: 1
d.yazdir(); // hede: 0
return 0;
}
Ders Notlarım
C++, fonksiyonlarda isimsiz parametre destekler ancak C desteklemez.
Pointer veya numerik türlerden boolean türüne dönüşüm impilicty olur.
Boolean türden pointer türüne dönüşüm olmaz:
const Nesneleri C’de başlatılmayabilir AMA C++ da BAŞLATILMALIDIR
external ve internal linkage
C++ global const nesneler iç bağlantıdadır (internal linkage)