7 CPA 5 Object programming essentials

Zmienne w klasie są zwane polami albo właściwościami
komponenty klasy (zmienne i metody) zadeklarowane bez Access Specifier (private public)
będą zawsze private


Przykładowe zaimplementowanie stosu bez klasy:
#include <iostream>

using namespace std;

int stack[100];
int SP = 0;

void push(int value) {
 stack[SP++] = value;
}

int pop(void) {
 return stack[--SP];
}

int main(void) {
 push(3);
 push(2);
 push(1);
 cout << pop() << endl;
 cout << pop() << endl;
 cout << pop() << endl;
 return 0;
}

.



przykład z zastosowaniem klasy:
class Stack {
    private:
 int stackstore[100];
 int SP;
    public:
        Stack(void) { SP = 0; }
 void push(int value);
 int pop(void) {
      return stackstore[--SP];
     }
};

void Stack::push(int value) {
 stackstore[SP++] = value;
}


.

DZIEDZICZENIE

class Nowa:Stara{ };

możemy dodać nowe zmienne

możemy nadpisać stare metody (funkcje)

przykład:


class AddingStack : Stack {
    private:
int sum;
    public:
void push(int value);
int pop(void);
};

i nadpisana metoda:

void AddingStack::push(int value) {
sum += value;
Stack::push(value);                        // jak widać wywołujemy starą metodę push
}


UWAGA żeby stworzyć konstruktor do klasy pochodnej, trzeba wspomnieć konstruktor
superklasy (tu Stack), będzie on wywołany przed konstruktorem klasy pochodnej


AddingStack::AddingStack(void) : Stack() {     //zapis z :: gdy ciało poza klasą, w ciele prototyp;
sum = 0;
}


metody klas mają pełen dostęp do zmiennych różnież prywatnych, za ich pomocą powinno się ustawiać wartość prywatnych zmiennych

przykład:

class Class {
public:
    void setVal(int value) {
            Class::value = value;
    }
private:
    int value;
};


zastąpienie notacji Nazwa::Metoda   this ->

class Class {
public:

     void setVal(int value) {
           this -> value = value;
    }
 
private:
     int value;
};

Jeśli chcemy przeciążyć Metodę setVal

void setVal(int value) {
this -> value = value;
}
void setVal(void) {
value = -2;
}


UWAGA Konstruktor nigdy nie zwraca nawet nie trzeba pisać VOID i nie może mieć returna
przykład:
class Class {
public:
Class(void) {              //konstruktor
this -> value = -1;
}
     
void setVal(int value) { this -> value = value; }

private:
        int value;
};


Stworzenie obiektu

Class objekt1;           // wywołuje powstanie konstruktora.

Konstruktor może być przeciążony.

przykład:
class Class {
public:
Class(void) { this -> value = -1; }
Class(int val) { this -> value = val; }


Jeśli masz TYLKO konstruktor z parametrem (nie void w nawiasie) nie możesz stworzyć
klasy Class objekt;  tylko musisz  Class objekt(1);



Konstruktor kopiujący

class Class {
public:
Class(int val) { this -> value = val; }

Class(Class const &source) {                    //konstruktor kopiujący
value = source.value + 100;                           //uruchamiany przy kop. obiektow
}                                                                     //np objekt2=objekt1;

int value;
};


Destruktor

przykład:

class Class {
public:
Class(int val) {
value = new int[val];      /tablica o wielkosci val

}

~Class(void) {
delete [] value;

}

int *value;
};


AUTO - każda zmienna jest auto w c++, ale możemy ją zamienić na STATIC
istnieje wtedy przez cały czas, jeśli jest w funkcji to pozostaje po czasie istnienia funkcji


Statyczne komponenty Klasy:

można dać zmienną statyczną w klasie, będzie ona istniała nawet bez obiektu, ale co ważne
w klasie można ją jedynie zadeklarować, jej pierwsza inicjacja musi być poza klasą i PRZED MAIN
(w obszarze zmiennych GLOBALNYCH)


class Class {
public:
static int Static;
};

int Class::Static = 0;       // inicjacja statycznej zmiennej poza klasą w obszarze GLOBALNYM

int main(void) {
Class instance1;   // stworzenie obiektu który już ma tą wartość



zmienna statyczna może oczywiście być prywatna.

Statyczne również mogą być metody (funkcje) w klasie.

przykład

...
static void HowMany(void) {
cout << Counter << " instances" << endl;
}
...

wtedy możemy taką metodę wywoływać:

HowMany();             // w srodku klasy
a.HowMany();          //dla istniejącego obiektu
Class:HowMany();   //wywołanie bez obiektu za pomoca klasy


Metoda statyczna nie może wywoływać zwykłej, bo ta może jeszcze nie istnieć, może statyczna.



Wskaźnik do obiektów i tworzenie za pomocą NEW

Clasa *ptr;        //wskaźnik o typie klasy
ptr=new Clasa();

kasowanie:
delete ptr;


jeśli chcemy wywołać metodę albo odwołać się do zmiennej w obiekcie do któego mamy dostęp
przez wskaźnik to:

(*ptr).value=0;
albo
ptr->value=0

do metody (funkcji też można się tak odwołac )
ptr->funkcja();



zmienne, obiekty tworzone w zwykły sposób odkładane są na stosie, tworzone za pomocą NEW na stercie (heap), nad stertą mamy pełną kontrolę


Konstruktory w obszarze public (i mogą być przeciążane )

Przykład tablicy tablic stworzonych NEW:

#include <iostream>
using namespace std;
class Array {
 int *values;
 int  size;
public:
 Array(int siz) { 
  size = siz; values = new int[size];
  cout << "Array of " << size << " ints constructed." << endl; 
 }
 ~Array(void) { 
  delete [] values; 
  cout << "Array of " << size << " ints destructed." << endl; 
 
 }
 int Get(int ix) { return values[ix]; }
 void Put(int ix, int val) { values[ix] = val; }
}; 
int main(void) {
 Array *arr[2] = { new Array(2), new Array(2) };
 
 for(int i = 0; i < 2; i++)
  for(int j = 0; j < 2; j++)
   arr[i]->Put(j, j + 10 + i);
 for(int i = 0; i < 2; i++) {
  for(int j = 0; j < 2; j++)
   cout << "#" << i + 1 << ":" << arr[i]->Get(j) << "; ";
  cout << endl;
 }
 delete arr[0]; delete arr[1];
 return 0;
}


Jeśli mamy 2 klasy i druga w swoim polu tworzy obiekty pierwszej, to nie ma problemu jeśli pierwsza nie potrzebuje parametrów:

class Element {
int value;
public:
Element(void) { cout<<"stworzylem";}          // konstruktor pierwszego (void)
};

class Collection {
Element el1, el2;                        //w 2 klasie tworzymy obiekty 1 klasy
public:
Collection(void) { cout << "Collection constructed!" << endl; }      //konstruktor 2 klasy


Ale jeśli 1 klasa potrzebuje parametrów (konstruktor nie z void)

class Element {
int value;
public:
Element(int val) { value=val;}          // konstruktor pierwszego (void)
};

to konstruktor 2 klasy musi wyglądać TAK:

class Collection {
Element el1, el2;
public:
Collection(void) : el2(2), el1(1) { cout << "Collection constructed!" << endl; }



 w konstruktorze nie można przypisać zmiennej cons żadnej wartości, ponieważ podczas wywołania konstruktora obiekt już istnieje. Poniższy przykład nie skompiluje się:
Obowiązek użycia listy inicjalizacyjnej :
1)Przypisanie wartości do zmiennej CONST
2)Utworzenie w obiekcie innego obiektu KTÓRY POTRZEBUJE ARGUMENTÓW

Zamierzony efekt można osiągnąć używając listy inicjalizacyjnej. Składowe wiek oraz wzrost musimy zainicjalizować na liście, jednak wartość składowej imie można przypisać w konstruktorze:
Jeżeli jest taka możliwość, powinno się jednak wszystkie możliwe składowe inicjalizować za pomocą listy inicjalizacyjnej, ponieważ jest to rozwiązanie szybsze i wydajniejsze od użycia konstruktorów.


ZADANIA:

Zad1:
#include <iostream>
    #include <string>
    using namespace std;
 
    class A {
    public: string a(string b) {                   //funkcja a zwracajaca strinf a przyjmujaca string b
        return b.substr(0,2);
    }};
 
    class B {
    public: string a(string b) {
        return b.substr(2,2);
    }};
 
    int main() {
        A a;
        B b;
        cout << a.a(b.a("ABCD"));                      // najpierw 2 wycina CD a potem pierwsza 0 do 2
        return 0;                                                    // czyli zostaje CD
    }


Wynik CD

Zad2:

    #include <iostream>
    using namespace std;
 
    class A {
    public:
        float v;
        float set(float v) {
            v += 1.0;                         //tutaj v odnosi sie do argumentu bo argument v
            this -> v = v;                   //a do v klasy(obiektu) musimy odnosic sie przez this
            return v;
        }
        float get(float d) {             //tutaj v odnosi się do v klasy i obiektu, bo argument inny niz v
            v += 1.0;
            return v;
        }
    };
 
    int main() {
        A a;
        cout << a.get(a.set(a.set(0.5)));
        return 0;
    }

Wynik 3.5


Zad3:

    #include <iostream>
    using namespace std;
 
    class A {
    public:
        A() { v = 2.5; }                      //konstruktor
        float v;
        float set(float v) {
            A::v += 1.0;
            return v;
        }
        float get(float v) {
            v += A::v;
            return v;
        }
    };
 
    int main() {
        A a;
        cout << a.get(a.set(1.5));
        return 0;
    }


Wynik 5

Zad4:
    #include <iostream>
    using namespace std;
 
    class A {
    public:
        float v;
        A() : v(1.0) {}                                //lista inicjalizacyjna
        A(A &a) : v(2.0) {}
        A(float f) : v(3.0) {}
        float get() {
            return A::v;
        }
    };
 
    int main() {
        A a, b(a.get()), c(b);
        cout << a.v + b.v + c.v;
        return 0;
    }

Wynik 6

6 CPA 4 Accessing different kind of data



int ** ptrarr;            -> wskaznik na wskaznik do int, przykład alokacja (10 wierszy):

ptrarr=new int  *[10];
for (r=0;r<10;r++)                                ->w przeciwienstwie do zwykłej tablicy
   ptrarr[r]=new int[kolumn];               -> jak widać każdy rząd morze mieć inną liczbę kolumn


dostęp:
ptrarr[r][c]=0;



Konwersja:

automatyczna(niejawna)   lub  jawna (rzutowanie)

L - Long

Konwersja typu C:
(int)k

Konwersja typu funkcyjnego:   (działa także dla obiektów)
int(k)

Jeśli zmienne zmienno przecinkowe (float  7 znaków i double 15 znaków są bardzo duże lub małe
to również następuje strata dokładności)

Jeśli dodajemy zmienne różnej długości bitowej, najmniejsza zostanie zpromowana do największej



Stringi
#include<string>              -> nie jest wbudowany więc trzeba dyrektywę

string nazwa="costam";      -> użyć ""   bo ' ' sa dla literałów
albo
string nazwa("costam");
wiec
string nazwa2(nazwa); rownoznaczne z string nazwa2=nazwa;

można używać  +   żeby dodawać

cout << TheGood + " & " + TheBad << endl;
TheQuestion += "or not to be";
ale
String = "A" + "B";     NIE ZADZIAŁA, czyli zmienna + "cos tam"

wpisanie spacji z klawiatury:
string LineOfTypes;
        cin >> LineOfTypes;

spowoduje że wczyta tylko pierwsze słowo
żeby wczytać cały wiersz GETLINE

getline(cin,LineOfTypes);


MOZNA porównywać stringi za pomocą ==
albo za pomocą: if(secret.compare(password) == 0)


FUNKCJE String:

Substring - bierze wycinek i tworzy nowy
str2 = str1.substr(1,2)    1- start   2 -długość

size i lenght
S.size();

find - znajduje string w innym stringu jak nie znajdzie zwraca string::npos
int where_it_begins = S.find(another_string, start_here);

Rozmiary:
cout << "length = " << s.length() << endl;
        cout << "capacity = " << s.capacity() << endl;
        cout << "max size = " << s.max_size() << endl;


Dodanie stringów (jak + )
string str1 = "content";
str2 = "appendix";
str1.append(str2);    -> ale bardziej uniwersalny bo moze str1.append(str2,0,3);

Insert może wsadzic w srodek:
string quote = "to be ";
quote.append(quote);
quote.insert(6, "or not ");     -> wsadzi or not od 6 znaku


Assign zastępuje string
string sky;
sky.assign(80,'*');

Replace zastępuje fragment innym
string ToDo = "I'll think about that in one hour";
string Schedule = "today yesterday tomorrow";

ToDo.replace(22, 12, Schedule, 16, 8);

Erase - zamazuje
WhereAreWe.erase(38, 8)


Swap - zamienia
Drink.swap(Needs);




Namespaces:

jeśli pominiesz
using namespace std;

to musisz:
std::cout <<...


możesz tworzyć swoje przestrzenie nazw w nich nazywać zmienne tak samo a potem do niech się odwoływać z użyciem ::  albo otwierać i zamykać osobno przestrzen nazw przed wywołaniem
czyli najpierw określic namespace a potem otworzyć using namespaces

namespace Hogwarts {
int Troll = 1;
}
namespace Mordor {
int Troll = 2 ;
}
int main(void) {
using namespace Hogwarts;
cout << Troll << " " ;
}
using namespace Mordor;
cout << Troll << endl;
}
return 0;

}



ZADANIA

Zad1:

    int main() {
        short s = 1;
        int i = 2;
        long l = 3;
        float f = 4.4;
        double d = 6.6;
     
        cout << s/float(i) + int(f)/i + long(d)/s;
        return 0;
    }

Wynik 8.5  bo 
short s = 1;
int i = 2;
cout << s/float(i); 
da 0,5  (wynik we float)

operacja z int w całkowitych, long rzutowany na double zrobi operacje na long


Zad2:
    int main() {
        string s = "a";
     
        cout << s  << "b" + "c";

Błąd bo nie można "b" + "c"











5 CPA 3 Extending expressive power: pointers, functions and memory

Wskaźniki - przetrzymując informację o lokalizacji, adresie innych danych.

inicjacja:
int *p;

jedyną wartością jaką można samemu przypisać do p jest 0;
p=0;    albo p=NULL,   nie można literału np.234

do p możemy przypisać adres zmiennej który uzyskujemy &
p=&i;

żeby zobaczyć wartość jaka kryje się pod adresem przechowywanym przez p
*p

cout<<*p;

co ważne:
*p=4; 
nie zmieni wartości p, ale zmiennej wskazywanej przez p

sizeof - ile bajtow pamieci zajmuje argument

i=sizeof(char);

char c;
int i=sizeof c;

char tab[10];
i=sizeof tab;    -> rozmiar całej tablicy

int *ptr;
int tab[3];   tak naprawdę tab jest wskaznikiem na pierwszy element tablicy

ptr=tab;    albo    ptr=&tab[0];

dodając do wskaźnika typu int  1 tak naprawdę podnosimy adres o 4, więc możemy chodzić po tablicy:

ptr+1=&tab[1];


Funkcje:
deklaracja funkcji to prototyp
ciało funkcji to definicja

void jak nic nie zwraca, przyjmuje

jeśli definicja poniżej main to przed mainem musi być deklaracja,prototyp zakończony;
prototyp nie musi miec nazw argumentów(parametrów) np:

int funkcja(int);

side efect - efekt uboczny funkcji -- kiedy zawiera zmienna globalna;

GreetManyTimes(1 + sizeofego);    - wyświetli 1+sizeofego   (wyliczy)



funkcje które coś zwracają czyli nie:   void funkcja (int aa);   ale int funkcja(int aa);
mogą być wywoływane
value=funckja(2);
albo funkcja(2);     -> i wtedy tracą to co zwracają


przekazywanie parametru funkcji przez wartość - tworzy kopie i nie zmienia oryginalnej zmiennej

przekazywanie przez referencje wygląda tak


void funkcja(int &aa);
tutaj normalnie
}

przekazywana przez referencję może być tylko ZMIENNA ( przez wartośc może być wynik funkcji która coś zwraca)


można w stylu C przekazać wskaźnik
void ByPtr(int *ptr)
ale tu operujemy
*ptr zeby operować na wartościach

przykła:

int  variable = 1;
int *pointer  = &variable;

ByPtr(pointer);



Parametr domyślny:
void funkcja(int repeats=1)

co ważne jeśli masz 2 parametry, to nie możesz podać tylko drugiego


INLINE - jeśli funkcje wyweoływane pod rząd wiele razy to można dodać ten parametr
prologi i epilog funkcji wywołany tylko raz, oszczedza miejsce w pamięci


inline int function(int parameter)
{}


Przeciążanie funkcji - ilość argumentów, typ argumentów
zwracany parametr NIE JEST BRANY POD UWAGE!


W przypadku:
void PlayWithNumber(int x) { … }
void PlayWithNumber(float x) { … }
:
PlayWithNumber(1.0);

nie wybierze float bo 1.0 jest w c++ typu double a w dół nie będzie degradował, jeśli chcemy zeby zadziałało dać 1 wtedy wybierze int albo dać jako parametr (1.0f)


Operator   ?:

oblicza1   ?  jeśliniezero:jeślizero
np:

return a>b ? a:b

wersja z 3 parametrami:

return a > b ? (a > c ? a : c) : (b > c ? b : c)

Sortowanie bąbelkowe:
#include <iostream>
 
using namespace std;

int main(void) {
        int  numbers[5];
        int  aux;
        bool swapped;
        // ask the user to enter 5 values 
        for(int i = 0; i < 5; i++) {
                cout << endl << "Enter value #" << i + 1 << ": ";
                cin >> numbers[i];
        }
        // sort them 
        do {
                swapped = false;
                for(int i = 0; i < 4; i++) {
                        if(numbers[i] > numbers[i + 1]) {
                            swapped = true;
                            aux = numbers[i];
                            numbers[i] = numbers[i + 1];
                            numbers[i + 1] = aux;
                         }
                }
        } while(swapped);
        // print results 
        cout << endl << "Sorted array: " << endl;
        for(int i = 0; i < 5; i++)
                cout << numbers[i] << " ";
        cout << endl;
        return 0;
}

.

amorphous pointer

VOID *ptr     ->  nie można dereferencji czyli wartości określić (*ptr=1) ale można przekonwertować w inny wskaźnik określonego typu

Alokacja pamięci:
new - musi byc okreslony typ do rezerwacji pamięci, jeśli tablica, wielkosc, zwraca wskaźnik i nie wypełnia trescią  (zawiera śmieci)

float *array=new float[20];
int count=new int;

Uwalnianie pamięci:
delete [] array;
delete count;

po skasowaniu wszystkie wskazniki ktore wskazywaly na ten obszar spowoduja bład programu

tworzenie tablic za pomoca new podczas trwania programu to tworzenie dynamiczne

Przykłady funkcji


int *f3(int &a) {         -> pobiera zwykły int, przekazuje referencja a zwraca adres do tej zmiennej
        return &a;            -> dlatego mamy return &a  (zwroc adres)
    }                               -> a przed naz. funkcji int* bo zwraca adres (czyli wskaznik)


   int *f2(int *a) {       -> pobierz wskaźnik (int *a), co wazne jak zwraca tez wskaznik
        return a;               -> przez return to wystarczy samo return a; (zmienna wskaznikowa
    }                               ->zwraca adres bez dodatkowych


int f1(int *a) {            -> przyjmij wskaznik ale zwroc wartość (dlatego int f1 bez *
        return *a;            -> i dlatego return *a czyli zwroc wartosc ze wskaznika
    }


Przypomnienie do zmiennych
int  - 4 bajty   (całkowity)
long - 4 bajty (calkowity  określany L)
long long - 8 bajtów (calkowity  )
float - 4 bajty (zmiennoprzecinkowy 7 znakow okreslany  f )
double - 8 bajtów   (zmienno przecinkowy 15 znaków  określany  f)
long double - 12 bajtów



ZADANIA:

Zad1:

#include <iostream>
    using namespace std;
 
    int main() {
        float f[2];
        float *p1 = f, *p2 = p1 + 1;
        cout << (p2 - p1) / sizeof(float);
        return 0;
    }
 
wynik 0


bo p2-p1  da 1  (odjete wskazniki (adresy) ale roznica pomimo ze skacza co 4 wyjdzie 1 bo float)
sozeof(float) = 4
czyli  1/4   a to dla danych int  wyjdzie  0



Zad2:

   #include <iostream>
    using namespace std;


    int fun(int a, int b) {                   -> moga byc inty a moze przyjac chary,
        return b-a;                               ->zamieni wtedy na liczby asci i je odejmie
    }                                                  -> i zwroci w tym przypadku inta, roznica z asci


    int main() {
        cout << fun('a','c') ;                 -> jeśli było by przeciązenie, to co najmniej jedna
        return 0;                                   -> musiala by byc char zeby rozpoznal
    }                                                    ->chyba ze by nie bylo zadnej innej przeciazonej z dwoma int

wynik 2 bo taka różnica kodów ASCI znakow   c    a


Zad3:

#include <iostream>
    using namespace std;

    int fun(long a) {
        return a / a;
    }

    int fun(int a) {
        return 2 * a;
    }

    int fun(double a) {
        return 3* a;
    }

    int main() {
        cout << fun(1000000000L) << fun (1L) << fun(1.f);
        return 0;
    }


wynik 111      , wywołanie 1L  wywoła funkcje przeciazona longiem nie int.
                          wywołanie fun(1.f) wywoła funkcję z double