C++ Defteri

cs204 dersi boyunca notlar.

Konular

Temeller

Preprocessor compiler linker

linker obj file'ları ve kütüphaneleri alıp hepsini birleştiriyor. compiler tamamen cpp'da. cpp'ı bilgisayarın anlayacağı şekle çeviriyor.

Preproccessor

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]

Veri Tipi Boyutları

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

Pointerlar

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

Pointer Tanımlama

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;

Dereferencing (*)

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

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 Arithmetic

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

Heap ve Stack

Stack

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

Basic Data Structures

Linked Data Structures

Arrays

Vectors

Linked List

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;
    }
}

Yeni bir node ekleme

    node *p, *q;
    p = new node(5,"Ali",nullptr);

    node *q;
    q = new node(6,"Musa",nullptr);

    p->next = q;

Linked listte dolaşma

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

Listenin sonun ekleme

void Add2End(node * tail, int id){
    node *nn = new node(id,nullptr);
    tail->next = nn;
}

Static vs Dynamic Memory Allocation

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

Automatic Variables

Programmer delete to release space when it is no longer needed.

Silinene kadar her zaman oradalar

Stack

Kullanıldığı Yerler

Özellikler

Operations

    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.

Queue

|-----||-----||-----||-----||-----||-----|
|Front||     ||     ||     ||     ||Rear |
|-----||-----||-----||-----||-----||-----|

Operations

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
|-----||-----||-----||-----|

Implementation

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();
    };
};

Binary Trees

    A
   / \
  B   C
 / \
D   E
   / \
  F   G
struct node{
    int val;
    node* left = nullptr;
    node* right = nullptr;
}

Implementation

inserting

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

    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;
}

Useful Recursions

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

Classlar

Copy Constructor'ler ne zaman çağrılıyor

Constructor, 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){

}

Operator Overloading

Return_Type  classname::operator Operator_Symbol (parameters)

// Ornekler
const myclass & myclass ::operator = (const myclass & rhs)

Bazı Önemli Overloading'ler

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

Iterators

Bulunması gereken önemli alt parçalar.

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;
};

Inheritance

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;
}

Move Semantics

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

Smart Pointers

Keywords

-> * normal pointerlarda olduğu gibi kullanılabilir.

Implemantations

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ı

Shared Pointer Reference Counter

Some Functions

Inheritance

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;
        }
};
class Fizik: public Ders, public Bilim 
{
    //...
}

Polymorphsim

Ders* kk;
Matematik * ss;
kk = ss;

Exception Handling

eğer throw'un içi boş verilirse bir önceki throw'da ne atıldıysa o atılır.

Bitwise İşlemleri

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;
}

Threads & Concurrency

Kodu compile edebilmek için -pthread ile vermek gerekiyor gcc için.

Thread nedir? Bilgisayarda çalışan bir process dir.

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?

c++ multithread

#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 nedir?

detach join edilemez hale getiriyor

başka özellikler

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

Threadleri beraber nasıl çalıştıracağız

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;

Sync Sıkıntıları (Producer - Consumer Problemi)

tek bir producer ve consumer olursa herhangi bir sıkıntı yok ancak birden çok olursa sıkıntı oluyor örnek

Mutex (Mutual Exclusion)

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.

promise & future

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

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;
}

dining philosopher problem