cs204 dersi boyunca notlar.
linker obj file'ları ve kütüphaneleri alıp hepsini birleştiriyor. compiler tamamen cpp'da. cpp'ı bilgisayarın anlayacağı şekle çeviriyor.
Preproccessor - translation unit - include da fark var "" => proje dosyası kontrol edilir - <> = standart kütüphaneye bakılır - #define ___ ____ => identifier, token-string - tüm tanımlamalar compilation dan önce değiştirilir. - const double PI = 3.1416 vs #define PI 3.14 - const olan daha iyi çünkü debug sürecinde ne olduğunu anlayabiliriz diğerinde otomatik üzerine yazılıyor.
Conditional Preprocessing
#ifdef __ => identifier =====> if defined
*statements*
#endif
#ifndef ======> if not defined
#undef => define'ı kaldırmaya yarıyor
# => bunlarla beraber
Macros
#define MULTIPLY(a,b) a*b
#define SQUARE(x) x*x
kullanırken dikkatli olunması gerekiliyor.
#define SQUARE(x) x*x
int x = SQUARE(3+5);
=OUTPUT=
3+5*3+5
g++ İle Preprocess İşlemleri
g++ -E test.cpp
dış programdaki kodlar buraya dahil edilir.
g++ -S test.cpp
assemble edilerek bir assembly dosyası oluşturulur.
g++ -C test.cpp
compile edilmiş kodu oluşuturıur
gcc main.c -o main
Use option -o, as shown below, to specify the output file name for the executable.
gcc -Dname[=value]
Type | Typical Bit Width | Typical Range |
---|---|---|
char | 1byte | -127 to 127 or 0 to 255 |
unsigned char | 1byte | 0 to 255 |
signed char | 1byte | -127 to 127 |
int | 4bytes | -2147483648 to 2147483647 |
unsigned int | 4bytes | 0 to 4294967295 |
signed int | 4bytes | -2147483648 to 2147483647 |
short int | 2bytes | -32768 to 32767 |
unsigned short int | 2bytes | 0 to 65,535 |
signed short int | 2bytes | -32768 to 32767 |
long int | 8bytes | -2,147,483,648 to 2,147,483,647 |
signed long int | 8bytes | same as long int |
unsigned long int | 8bytes | 0 to 4,294,967,295 |
long long int | 8bytes | -(2^63) to (2^63)-1 |
float | 4bytes | |
double | 8bytes | |
long double | 12bytes | |
wchar_t | 2 or 4 bytes | 1 wide character |
Pointers => A pointer is variable to stora an "address in computer memory" It points to a memory location
*
=> follow the address go to object with address
&
=> get the address go to addresss with object
int *ptr;
char *pchar;
ptr = &counter; // Store value in it;
cout << *ptr; // Dereference try to reach the value the pointer points to
cout << (*ptr).Day();
cout << ptr->Day(); // Diğeri ile aynı işlemi yapıyor.
char *p;
char * = NULL;
**Null Pointers **
char * = NULL;
NULL is replaced by nullptr in C++09;
Pointerlardaki değere erişip değiştirme yapmak istersen deferencing kullanabiliriz. Bunun için
int *asd;
std::cout << *asd << endl;
Static memory allocation ==> int array[100] If memory allocated at runtime on the fly we need dynamic memory allocation
*new type*
double *p;
// a pointer for double
p = new double;
// Memory allocated for double value
veya p2 = new int[4]
=> it gives dynamic memory
cin >> no_students;
Student* students = new Student[no_student];
Student s1,s2;
students[0] = s1;
students[1] = s2;
int* primenumveber = new int [100];
primenumbers[0] = 2;
primenumbers[1] = 3;
int *pa = new int[100];
//pa[0] and *pa aynı şey
// pa[1] == *(pa+1) ===> true
//+1 4byte ilerle demek
Pointer aritmatiğinide önemli olan kullandığımız veri tipinin size'dır.
int myArray[100];
int* p1 = &(myArray[5]);
int* p2 = &(myArray[10]);
cout << p1 << " " << p2 << endl;
int const p => CONSTANT pointer to an int const int p => pointer to a CONSTANT int
Stack - Automatic variable'lar burada saklanıyor Heap - Programda en büyük segmente sahip olan yer - new'ler her zaman heapten geliyor
int s = 0;
cout << &s << endl; // Bu stackten geliyor 0x00010
int k = 2;
cout << &k << endl; // Bu stackten geliyor 0x00008 adres daha küçük
int *p = new int;
cout << &p << endl; // Pointer'ın adresi stackten geliyor
cout << p << endl; // Pointer'ın sakladığı adres HEAP
Arrays - Çok fazla veya az memory ayrılmış olabilir. yer ayırma problemi var - Kullanım kolaylığı var. - Doğrudan erişim imkanı, indeksler ile ulaşabiliyoruz. - Sıralanmış bir diziye bir öğe eklemek, tüm öğelerin kaydırılmasını gerektirebilir.
Vectors - Yeniden boyutlandırılabilir ancak verimsiz. - tüm elemanların kaydırılması gerektirir.
struct node{
string word;
int num;
node *next;
};
node *p = new node;
struct point{
int x;
int y;
// Default constructor
point::point(){
x=0;
y=0;
}
// Constructor
point::point(int x_cord, y_cord) {
x = x_cord;
y = y_cord;
}
}
node *p, *q;
p = new node(5,"Ali",nullptr);
node *q;
q = new node(6,"Musa",nullptr);
p->next = q;
Bunun için bir walk değişkeni oluşturulup liste üzerinde gezilir.
node* walk = head;
while(pt){
cout << ptr -> info << endl;
ptr = ptr->next;
}
//nullptr olana kadar dönene kadar gidiyor
void Add2End(node * tail, int id){
node *nn = new node(id,nullptr);
tail->next = nn;
}
int main(){
int arr[5] = {1,2,3,4,5}; // Bu statik bir değişken
}
Problemler 1. Program sırasında boyutu arttıramıyoruz 2. Eğer az verilirse memory boşa gider 3. Eğer fazladan veri giderse program patlar
Dynamic Memory Allocation - Run-time'da memory allocation yapılabiliyor - Allocate edilen memory'e sadece pointerlar ile ulaşabilir. - Bundan dolayı pointerları kaybetmemek gerekiyor - Heap'ten allocation yapılıyor.
Automatic Variables - C++ allocates memory oh the stack - Scope'u kadar yaşarlar ondan sonra silinir
Programmer delete to release space when it is no longer needed.
Silinene kadar her zaman oradalar
- local variables = { } süslü parentezler kapanınca ömrü biter
- global variables = programın tüm hepsi için geçerlidir
- static variables = global gibi program bitince silinir.
- Pointers delete delete pointerVariable
node *top
pointer'ı olması gerekir void push(int data)
{
if (isEmpty())
{
top = new node(data, nullptr);
}
else
{
top = new node(data, top);
}
};
Stackler için örnek bir soru postfix ler olabilir.
|-----||-----||-----||-----||-----||-----|
|Front|| || || || ||Rear |
|-----||-----||-----||-----||-----||-----|
Static Queue
Enqueue(3);
Front & Rear
|-----||-----||-----||-----||-----||-----|
| 3 || || || || || |
|-----||-----||-----||-----||-----||-----|
Enqueue(5);
Front Rear
|-----||-----||-----||-----||-----||-----|
| 3 || 5 || || || || |
|-----||-----||-----||-----||-----||-----|
Enqueue(8);
Front Rear
|-----||-----||-----||-----||-----||-----|
| 3 || 5 || 8 || || || |
|-----||-----||-----||-----||-----||-----|
Dynamic Queue
Enqueue(3);
|-----||-----||-----||-----|
|Front|| || ||Rear | ---> nullptr
|-----||-----||-----||-----|
class Queue
{
private:
Stack content;
public:
Queue(){};
void dequeue()
{
Stack buffer;
while (!content.isEmpty())
{
int q = content.pop();
buffer.push(q);
}
buffer.pop();
content = buffer;
}
void enqueue(int data)
{
content.push(data);
}
bool isEmpty()
{
return content.isEmpty();
};
};
A
/ \
B C
/ \
D E
/ \
F G
struct node{
int val;
node* left = nullptr;
node* right = nullptr;
}
inserting - burada recursive kullanmak daha rahat oluyor.
bool addHelper(node *&curr, int data)
{
if (curr == nullptr)
{
node *newNode = new node(data);
curr = newNode;
return true;
}
else if (curr->val < data)
{
addHelper(curr->right, data);
}
else if (curr->val > data)
{
addHelper(curr->left, data);
}
else
{
return false;
}
}
Traversing Trees - Inorder - Preorder - Postorder
A
/ \
B C
/ \
D E
/ \
F G
Output: D B F E G A C
if (nodePtr)
{
displayInOrder(nodePtr->left);
cout << nodePtr->value << endl;
displayInOrder(nodePtr->right);
}
A
/ \
B C
/ \
D E
/ \
F G
Output: A B D E F G C
if (nodePtr)
{
cout << nodePtr->value << endl;
displayPreOrder(nodePtr->left);
displayPreOrder(nodePtr->right);
}
A
/ \
B C
/ \
D E
/ \
F G
Output: D F G E B C A
if (nodePtr)
{
displayPostOrder(nodePtr->left);
displayPostOrder(nodePtr->right);
cout << nodePtr->value << endl;
}
int noNodesHelper(node* _node){
if(_node == nullptr){
return 0;
}
else{
return noNodesHelper(_node->left) + noNodesHelper(_node->right) + 1;
}
}
Binary tree'ler hakkında daha detaylı sorular ve bilgiler için buradan faydalınabilir. http://cslibrary.stanford.edu/110/BinaryTrees.html
A a(b)
=> a'nın içine b kopyalanıyorA a() = b;
=> bunu overload ederek değiştirebilirizConstructor, cpy constructor ve destructor'lar tanımlanmazsa otomatik olarak compiler tarafından oluşturulur. Copy constructorlar otomatik oluşursa shallow copy oluşur.
Class oluştururken initialization list kullanılabilir. Initialization list kullanmanın avantajları object oluşmadan önce bunları set edebilmesidir. Örneğin bir const değeri bu şekilde set edebiliriz.
Point::Point() : x(0), y(0)
{
}
copy constructor
node(const node& cpy){
}
Return_Type classname::operator Operator_Symbol (parameters)
// Ornekler
const myclass & myclass ::operator = (const myclass & rhs)
ostream &operator<<(ostream &os, const Name &names)
{
os << names.name << " " << names.surname;
return os;
}
ClockTime operator+ (const ClockTime & lhs, const ClockTime & rhs)
ClockTime result(lhs); //uses the default (compiler generated) copy constructor
result += rhs; //uses the previously defined operator+=
return result;
}
this
içinde bulunan objenin adresini döndürür. Friend Functios - Bir objenin non-public değerlerine erişmek için tanımlarız. - Fonksiyon veya class'ıfriend
olarak tanımlamak mümkündür.
Bulunması gereken önemli alt parçalar. - Inıt() - HasMore() - Next() - Current()
IteratorW(const LinkedList& list ) : ll(list), current(nullptr){};
void Init(){
// Inıt kısmında artık . ile çağırıyoruz çünkü bu bir referans
current = ll.head;
};
bool HasMore() const{
return (current != nullptr);
};
void Next(){
current = current->next;
};
// Eğer değer değiştirme istersek referans olarak vermeliyiz
int & Current() const{
return current->value;
};
class A{
virtual int k;
}
class B : public A{
virtual int k;
}
cout << sizeof(B);
#include<iostream>
using namespace std;
class Base
{
public:
void foo() { cout << "Base::foo() called"; }
};
class Derived: public Base
{
public:
void foo() { cout << "Derived::foo() called"; }
};
int main()
{
Derived d;
Base* w;
w = &d;
d.foo(); // Derived::foo() called
w->foo(); // Base::foo() called
return 0;
}
Burada sürekli kopyalamayı enegellemek için kullanıyoruz. İki kere kopyalamak yerine bunu taşıyoruz.
int &rx = 12; // Bunu yapamıyoruz
const int &rx = 12 // Bu mümkün ancak const oluyor
int &&rx = 12; // Bu da mümkün ve rx değiştirebiliyoruz
int &&rx = foo();
Classlarda kullanılmasında move constructor kullanıyoruz.
class Array{
Array(Array&& src); // Move consturctor
Array& operator=(const Array& src);
Array& operator=(Array&& src);
}
void Array::operator=(Array&& src){
swap(this->mem, src.mem);
swap(this->sz, src.size)
}
std::swap
-> * normal pointerlarda olduğu gibi kullanılabilir.
unique_ptr<int> ptr = make_unique<int>();
shared_ptr<int> ptr_1 = make_shared<int>(15);
shared_ptr<int> ptr_2 ;
ptr_2 = ptr_1; // Artık ikisininde adresi aynı
Some Functions - use_count() ===> smrt_pointer.use_count() => kaç tane olduğu yazıyor. - swap() ===> swap(ptr_1,ptr_2); => iki pointerın değerlerini değiştirir.
Inheritance elimizdeki bir classdan yararlanarak başka classlar üretmektir.
class Ders{
protected:
int value;
public:
Ders(){};
~Ders(){};
string name;
virtual void ask() = 0; // Pure virtual function
};
class Matematik : public Ders{
public:
Matematik(){};
void ask(){
cout << "Matematik sorusu" << endl;
}
int getValue(){
return value;
}
};
virtual
function olarak yaparız.class Fizik: public Ders, public Bilim
{
//...
}
Ders
class'ından derive edilmişDers* kk;
Matematik * ss;
kk = ss;
eğer throw'un içi boş verilirse bir önceki throw'da ne atıldıysa o atılır.
a |= 1 << bit;
a ^= b;
b ^= a;
a ^= b;
a << 1; // x2
b >> 1; // /2
(n ^ (n >> 31)) - (n >> 31);
int k = [1,2,3,3,2];
int sayi = 0;
for(l=0; l <k.size(); k++)
sayi ^= k[l]
void swap(int *x, int *y)
{
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
}
Kodu compile edebilmek için -pthread ile vermek gerekiyor gcc için.
Thread nedir? Bilgisayarda çalışan bir process dir. - is a path of execution - her process en az bir threadi vardır. buna da main thread denir
Kısa süre aralıklarla threadler işlemci tarafından çalıştırılır. Aslında sadece tek bir core var.
Do we need parallelism for concurrency? - eski zamanlarda ghz leri yüksek bilgisayarlar varmış tek core işlemciler var. ancak bunların kullanımı sıkıntılı - günümüzde multicore ancak ghz ler daha düşük
c++ multithread
- c++ 11'e kadar standart bir thread yoktu.
- pthread ile standart bir hale geldi
- std::thread
ile
- main fonksiyonu main threadi temsil eder
#include <iostream>
#include <thread>
using namespace std;
void hello() {
cout << "Hello thread\n";
}
int main() {
thread aThread(&hello); // non blocking element programı bloklamıyor hemen aşağıya iniyor
aThread.join(); // blocking element main thread bu kodun bitmesini gerekiyor ve aşağıya inmiyor
cout << "ye main\n";
return 0;
}
Threadlerde ana iki işlem vardır bunlar join
ve detatch
dir
join => program thread'in bitmesini bekliyor diğer threadler çalışıyor arkada
join ettikten sonra kendisini destruct edebiliyor
detach => artık joinable olmuyor işlemi bitiyor main thread için
aThrad.joinable()
diye kullanabiliyoruz.
kodu join etmeden kullanırsak hata alırız.
chrono::seconds dura(2);
this_thread::sleep_for(dura); // Thread uyuyor
detach join edilemez hale getiriyor
get_id ==> thread'lerin unique id'leri var. id geri döner sleep_until ==> thread'i uyutmak için kullanırız. sleep_for ==> n uyutmak yield => reschedule yapmaya yarıyor
struct Counter {
int value;
Counter : value(0){}
void increment(){
++value;
}
}
Bunun sonucunun ne çıkacağını bilemeyiz 1 veya 2 çıkabilir. Atomik bir işlem değil. Senron edebilmek için bazı data strutcurları kullanabilir veya atomik hale getirebiliriz.
Multithread uygulamaları düzenlemeye scheduling denir. Eğer senkronize işlemlerini doğru yapılmazsa deadlock oluşabilir.
kodları sync edebilmek için std::atomic
keyword'unü kullanabiliriz. ! counter örneği düşünürsek referans olarak vermemiz gerekiyor bunun için ref(counter)
diyererek referanslayabiliriz.
atomic<int> value;
tek bir producer ve consumer olursa herhangi bir sıkıntı yok ancak birden çok olursa sıkıntı oluyor örnek * Thread 1: data üretip queue koy * Thread 2: queue kontrol et eğer varsa datayı sil * Thread 3: queue kontrol et eğer varsa datayı sil buradaki sıkıntı ortak queue kullanıyor. eğer tek bir data varsa aynı anda ikisinin queue empty olmadığını görür ikisi de dequeue etmeye çalışırsa programda probelem oluşur.
mutex mut;
void increment(){
mut.lock();
increment ++;
mut.unlock();
}
Kodun içerisinde exception varsa ne zaman lock veya unlock olduğunu bilemiyoruz.
lock_guard<std::mutex> l(mut)
kullanarak güvenli bir şekilde lock ve unlock yapabiliriz
mutex'i lock ettikten sonra tekrar lock edemeyiz. unlock etmek gerekiyor. mutex aynı anda lock edilebilir bu da deadlock oluşturabiliyoru. bunu engellemek için recursive mutex kullanırız.
struct Complex {
recursive_mutex mutex;
double i;
Complex() : i(1) {}
void mul(double x){
lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void div(double x){
lock_guard<std::recursive_mutex> lock(mutex);
i /= x;
}
void both(double x, double y){
lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
mutex'i belirli bir süre kilitlemek içi timex_mutex kullanabiliriz. veya recursive_timed_mutex de kullanabiliriz.
producer consumer kodlarında ortak beklemek için while kullanabiliriz.
std::lock sayesinde aynı anda birden çok mutex'i aynı anda lock edebiliriz. multiple mutex varsa çok önemli. içeri sıralaması önemlidir.
Threadler arasında iletişim kurabilmek mümkündür. Bunu da future kullanarak yapabiliriz. shared state future::get kullanarak yapabiliriz. Promise future objesinden geliyor. bir örnek
void foo(promise<string> & prms) {
string str = "Hello from the thread";
prms.set_value(str);
}
int main() {
promise<string> prms;
future<string> ftr = prms.get_future();
thread thr(&foo, ref(prms));
cout << ftr.get() << endl;
thr.join();
return 1;
}
burada join olmadan önce bile set_value olmadan bile ftr.get yazdıramıyor. blocking element görevini görüyor.
prms.set_exception(current_exception()); // => diyerek exception handling kullanabiliriz.
async ile bu işlemler çok daha rahat oluyor.
string foo() {
string str = "Hello from the thread";
return str;
}
int main() {
auto ftr = async(&foo); // async future object döndürüyor
cout << "Hello from the main" << endl;
auto str = ftr.get();
cout << str << endl;
return 1;
}