Kurs podstawy programowania UMCS, zdjęcie laptopa.

PP – Laboratorium 13

Wejściówka

Zaprojektuj strukturę Point, która posiada dwa pola typu zmiennoprzecinkowego x, y. Następnie napisz funkcję w języku C++, która przyjmuje trzy argumenty: punkt typu Point oraz dwie liczby rzeczywiste, będące współrzędnymi kierunkową i przecięć funkcji liniowej. Funkcja powinna zwrócić true, jeśli punkt należy do prostej, bądź false w przeciwnym wypadku.

Rozwiązanie:
struct Point {
    float x, y;
};

bool f(const Point &p, float a, float b) {
    return !(a * p.x + b - p.y);
}

Zadanie 1 – metody i konstruktory

Zaprojektuj klasę Animal, która posiada dwa pola: zmienną typu wyliczeniowego AnimalType type oraz tablicę będącą maksymalnie 10 znakową nazwą pupila – name. Dodatkowo klasa powinna zawierać metodę say_something(int n). Metoda say_something w zależności od typu zwierzęcia powinna wyświetlić odpowiedni komunikat np. dla kota "miał" oraz powtórzyć go n razy, gdzie n zostało przekazane w argumencie metody. Napisz program w języku C++, który przetestuje działanie naszej klasy.

Rozwiazanie:
#include <iostream>
#include <cstring>

enum AnimalType {PIES, KOT};

class Animal {
private:
    char name[11];
    AnimalType type;
public:
    void say_something(int n);
    const char *get_name();
    Animal() : name("unknown"), type(KOT) {}
    Animal(const char *name, AnimalType type);
};

void Animal::say_something(int n) {
    for(int i = 0; i < n; ++i)
        std::cout << (type == KOT ? "mial\n" : "hal\n");
}

const char *Animal::get_name() {
    return this->name;
}

Animal::Animal(const char *name, AnimalType type) {
    memcpy(this->name, name, strlen(name));
    this->type = type;
}

int main() {
    Animal *kotek = new Animal("Zbigniew", KOT);
    Animal *piesek = new Animal("Zbyszek", PIES);

    std::cout << kotek->get_name() << std::endl;
    kotek->say_something(10);

    std::cout << piesek->get_name() << std::endl;
    piesek->say_something(5);

    return 0;
}
Omówienie:

Aby rozwiązać to zadanie moglibyśmy wykorzystać wskaźniki na funkcję, jednakże zdecydowanie lepszym rozwiązaniem jest wykorzystanie możliwości posiadania funkcji w klasie: metod, konstruktorów itd.

Cała znajomość programowania, która poznaliście do tej pory sprowadzała się do tzw. programowanie strukturalnego. Wszystkie implementowane funkcje miały zasięg globalny, innymi słowy były widoczne z każdego miejsca w naszym programie. Wymagało to mi. unikatowych nazw funkcji. Ponadto bardzo często implementowaliśmy funkcje pomocnicze, które powinny być widoczne tylko dla innych funkcji lub obiektów a użytkownik nie powinien mieć do nich dostępu, niestety tak nie było.

Metody to funkcje składowe zadeklarowane w klasach i strukturach. Metody tak, jak atrybuty klas i struktur, mogą mieć jedne z trzech praw dostępu: private, protected, public. Metody publiczne mogą być wywołane z poza klasy, zaś metody prywatne mogą być wywołane tylko i wyłącznie wewnątrz klasy. Tworzenie metod prywatnych jest bardzo wygodne, ponieważ umożliwia nam dzielenie skomplikowanej, zazwyczaj publicznej metody na mniejsze elementy, które przy późniejszej analizie są bardziej czytelne i łatwiejsze do modyfikacji.

Definicję metod można przenieść poza definicję klasy, dzięki czemu oddzielamy wizualny interfejs klasy od jej implementacji. Warto zaznaczyć, że metody definiowane wewnątrz klasy automatycznie stają się funkcjami typu inline. Aby temu zaradzić, ten sam kod co poprzednio można zapisać następująco:

class Animal {
private:
    char name[11];
    AnimalType type;
public:
    void say_something(int n);
    const char *get_name();
    Animal() : name("unknown"), type(KOT) {}
    Animal(const char *name, AnimalType type);
};

void Animal::say_something(int n) {
    for(int i = 0; i < n; ++i)
        std::cout << (type == KOT ? "mial\n" : "hal\n");
}

const char *Animal::get_name() {
    return this->name;
}

Animal::Animal(const char *name, AnimalType type) {
    memcpy(this->name, name, strlen(name));
    this->type = type;
}

W kontekście metod klas (i struktur), należy wspomnieć o wskaźniku this. Wskaźnik this występuje w metodach niestatycznych klas. Ten wskaźnik wskazuje na obiekt, dla którego została wywołana metoda, co umożliwia jawne odwołanie się zarówno do atrybutów, jak i innych metod klasy (instancji klasy).

Aby skorzystać z metod, które są umieszczone wewnątrz klasy, musimy utworzyć sobie najpierw zmienną, a następnie za pomocą utworzonej zmiennej wywołujemy metodę klasy.

Bardzo często, a właściwie prawie zawsze, tworząc obiekt klasy należy zainicjować go początkowymi wartościami. W języku C++, w tym celu używamy konstruktorów.


Konstruktor jest specyficzną metodą, która jest wywoływana zawsze gdy tworzony jest obiekt. Jeśli programista nie utworzy konstruktora dla klasy, kompilator automatycznie utworzy konstruktor, który nic nie będzie robił. Konstruktor nie pojawi się nigdzie w kodzie, jednak będzie on istniał w skompilowanej wersji programu i będzie wywoływany za każdym razem, gdy będzie tworzony obiekt klasy. Jeśli chcemy zmienić domyślne własności konstruktora jaki jest tworzony przez kompilator C++ wystarczy, że utworzymy własny konstruktor dla klasy.

W przeciwieństwie do innych metod konstruktor nie zwraca żadnego typu danych. Ponadto drugą istotną własnością konstruktora jest jego nazwa, która jest taka sama jak nazwa klasy. Do konstruktora można przekazywać parametry, tak samo jak do funkcji. Dodatkowo język C++ umożliwia również tworzenie kilku konstruktorów dla jednej klasy (muszą się one jednak różnić parametrami wejściowymi tak jak w przypadku funkcji).

Gdy tworzymy klasę, wszystkie zmienne jakie są zadeklarowane wewnątrz niej są zainicjowane przypadkowymi wartościami, które w konstruktorze następnie ustawiamy na takie, jakie uważamy za stosowne np.:

Animal::Animal() {
    memcpy(this->name, "unknown", 7);
    this->type=KOT;
}

Takie rozwiązanie jest oczywiście poprawne, niemniej jednak czasami zachodzi potrzeba zainicjowania zmiennej w trakcie tworzenia klasy, a nie po jej utworzeniu. Precyzując, inicjalizowanie składowych nowego obiektu odbywa się zanim obiekt zacznie istnieć. Takie rozwiązanie nazywamy listą inicjalizacyjną konstruktora. Pozornie ciężko jest znaleźć różnice pomiędzy konstruktorem a listą inicjalizacyjną. Wynika to z faktu, że mechanizmy te są ze sobą bardzo ściśle związane, a lista inicjalizacyjna jest rozszerzeniem możliwości konstruktora. Tym samym nie można zdefiniować listy inicjalizacyjnej nie definiując konstruktora w danej klasie. Uzupełniając, lista inicjalizuje składowe podczas ich tworzenia za pomocą odpowiednich konstruktorów, natomiast konstruktor najpierw tworzy składowe za pomocą domyślnych konstruktorów, a następnie przypisuje im odpowiednie wartości za pomocą operatora przypisania. Aby użyć listy inicjalizacyjnej, należy użyć następującego zapisu:

Animal::Animal(): name("unknown"), type(KOT) {}

Powyższy zapis ma kilka bardzo istotnych zalet:
– jest szybszy – brzmi to trochę absurdalnie, ale różnice są znaczne gdy przyjdzie do wykonywania pomiarów czasowych (dla powyższego przykładu nie odczujemy żadnej różnicy, jednakże w przypadku bardziej rozbudowanych, złożonych składowych już tak),
– jest czytelniejszy – programista nie musi analizować zawartości konstruktora, by wiedzieć jaką domyślną wartością zostanie zainicjowana klasa,
– umożliwia inicjowanie zmiennych zdefiniowanych jako stałe,
– umożliwia inicjowanie zmiennych zdefiniowanych jako referencje (i tylko ta metoda umożliwia zainicjować zmienną zadeklarowaną np. tak: int& zmienna;),
– jest metodą stosowaną przy dziedziczeniu klas.
Warto więc od początku wpajać sobie nawyk, który został tu zaprezentowany, ponieważ w przyszłości może to oszczędzić dużo problemów.

Zadanie 2 – destruktory

Uzupełnij poprzedni program o odpowiedni destruktor oraz konstruktor kopiujący.

Rozwiązanie:
#include <iostream>
#include <cstring>

enum AnimalType {PIES, KOT};

class Animal {
private:
    char name[11];
    AnimalType type;
public:
    void say_something(int n);
    const char *get_name();
    Animal() : Animal("unknown", KOT) {}
    Animal(const char *name, AnimalType type);
    Animal(const Animal &animal);
    ~Animal();
};

void Animal::say_something(int n) {
    for(int i = 0; i < n; ++i)
        std::cout << (type == KOT ? "mial\n" : "hal\n");
}

const char *Animal::get_name() {
    return this->name;
}

Animal::Animal(const char *name, AnimalType type) {
    memcpy(this->name, name, strlen(name));
    this->type = type;
}

Animal::Animal(const Animal &animal) {
    memcpy(this->name, animal.name, sizeof(animal.name));
    this->type = animal.type;
}

Animal::~Animal() {
    std::cout << "Destruktor " << name << std::endl;
}

int main() {
    Animal *kotek = new Animal("Zbigniew", KOT);
    Animal *piesek = new Animal("Zbyszek", PIES);
    Animal kotek_cpy = Animal(*kotek);

    std::cout << kotek->get_name() << std::endl;
    kotek->say_something(10);

    std::cout << piesek->get_name() << std::endl;
    piesek->say_something(5);

    delete kotek;
    delete piesek;

    std::cout << kotek_cpy.get_name() << std::endl;

    return 0;
}
Omówienie:

Często jest tak, że podczas „życia” obiektu klasy rezerwujemy pamięć, którą chcielibyśmy zwalniać zawsze przed usunięciem obiektu. Pierwszym wariantem jest pamiętanie o wywołaniu funkcji, która będzie za to odpowiedzialna. Takie podejście jest jednak ryzykowne, ponieważ bardzo łatwo zapomnieć o wywoływaniu funkcji, która będzie zwalniała ewentualną zarezerwowaną dynamicznie pamięć.

Lepszym rozwiązaniem tego problemu jest wykorzystanie destruktorów. Destruktor jest specjalną metodą, która jest wywoływana zawsze tuż przed zniszczeniem (usunięciem) instancji klasy z pamięci. Destruktor, tak samo jak konstruktor nie posiada zwracanego typu. Dodatkowo destruktor zawsze musi być bezparametrowy oraz jest możliwość zdefiniowania tylko i wyłącznie jednego destruktora dla danej klasy. Kolejną charakterystyczną własnością destruktora jest jego nazwa, tak samo jak w przypadku konstruktora, nazwa destruktora jest taka sama jak nazwa klasy, z tym że poprzedza ją znak ~.


Konstruktor kopiujący to konstruktor spełniający specyficzne zadanie. Mianowicie jest on wywołany (jawnie lub niejawnie), gdy występuje inicjalizacja obiektu za pomocą innej instancji tej klasy. Jeżeli nie zaimplementujemy konstruktora kopiującego, kompilator zrobi to automatycznie. Konstruktor taki będzie po prostu tworzył drugą instancję wszystkich pól obiektu.

Kompilator wywołuje konstruktor niejawnie, jeżeli zachodzi potrzeba stworzenia drugiej instancji obiektu np. podczas przekazywania obiektu do funkcji przez wartość. Z drugiej strony możemy go wywołać jawnie w następujący sposób:

Animal kotek("Zbigniew", KOT); //To nie jest konstruktor kopiujący
Animal kotek2(kotek); //To jest konstruktor kopiujący
Animal kotek3 = kotek2; //To również wywołanie konstruktora kopiującego a nie przypisanie.
kotek3 = kotek; //To nie jest wywołanie konstruktora kopiującego a przypisanie.

Jeżeli dokonujemy w instrukcjach inicjujących alokacji pamięci, to nie możemy zdać się na konstruktor kopiujący tworzony niejawnie. Gdy skorzystamy z domyślnego konstruktora kopiującego, to w tak stworzonym obiekcie pole, będące wskaźnikiem, będzie wskazywać na ten sam fragment pamięci, co w obiekcie wzorcowym. Jeżeli nie jest to zamierzony efekt (a zwykle nie jest) trzeba samodzielnie zaimplementować konstruktor kopiujący.

Należy zwrócić uwagę na dwie ostatnie linie powyższego listingu. Konstruktor kopiujący zostanie użyty tylko podczas inicjalizacji nowego obiektu, zaś operator przypisania zostanie użyty wtedy, gdy wartość jednego obiektu ma zostać przypisana innemu.

Aby samemu zaimplementować konstruktor kopiujący należy zadeklarować konstruktor o jednym parametrze, będącym referencją, najlepiej stałą const na obiekt tej samej klasy:

Animal::Animal(const Animal &animal) {
    memcpy(this->name, animal.name, sizeof(animal.name));
    this->type = animal.type;
}

Ostatnim omawianym pojęciem jest delegacja konstruktów (od C++ 11). W przypadku wielu wariantów konstruktorów często zdarza się, że muszą one powielać różne testy poprawności argumentów lub jakieś szczególne operacje konieczne do inicjalizacji obiektu. Niekiedy taki wspólny kod wyciąga się do osobnych prywatnych lub chronionych metod.

Jednakże w C++11 dodano możliwość użycia na liście inicjalizacyjnej innych konstruktorów klasy:

Animal::Animal(AnimalType type) : Animal::Animal("unknown", type) {}

Zadanie 3

Zaliczenie z pewnego przedmiotu wygląda tak, że studenci piszą dwa kolokwia, oba za 50 punktów. Ocena dostateczna przyznawana jest w przedziale (50-60] punktów, i rośnie o połowę oceny co 10 punktów aż do 100. Student, który nie otrzyma zaliczenia, może poprawić jedno z kolokwiów, ale wtedy może otrzymać co najwyżej ocenę dostateczną. Napisz klasę, w której znajduje się: imię i nazwisko studenta będące napisem, wynik pierwszego kolokwium, wynik drugiego kolokwium i wynik poprawy. Zakładamy, że student, który nie otrzyma zaliczenia, zawsze będzie poprawiał kolokwium, które gorzej napisał. Napisz funkcję w języku C++, która przyjmie tablicę takich struktur oraz jej rozmiar, a zwróci średnią ocen (nie punktów) zdobytych z tego przedmiotu.

Rozwiązanie:
#include <iostream>
#include <cstring>

class Student {
public:
    char id[100];
    int result_1, result_2, result_3;
    Student() {}
    Student(const char *name, int result_1, int result_2, int result_3) {
        strcpy(id, name);
        this->result_1 = result_1;
        this->result_2 = result_2;
        this->result_3 = result_3;
    }
    Student(const char *name, int result_1, int result_2) : Student(name, result_1, result_2, 0) {}
};

float function(Student arr[], int n) {
    float avg = 0;
    for(int i = 0; i < n; ++i) {
        int sum = arr[i].result_1 + arr[i].result_2;
        if(sum <= 50) {
            if(arr[i].result_1 < arr[i].result_2)
                sum = arr[i].result_2 + arr[i].result_3;
            else
                sum = arr[i].result_1 + arr[i].result_3;
            avg += sum <= 50 ? 2 : 3;
        } else switch ((sum - 1) / 10) {
            case 9: avg += 5; break;
            case 8: avg += 4.5; break;
            case 7: avg += 4; break;
            case 6: avg += 3.5; break;
            case 5: avg += 3; break;
            default: avg += 2; break;
        }
    }
    return avg / n;
}

int main() {
    Student arr[3];
    arr[0] = Student("Jan Nowak", 40, 4, 30);
    arr[1] = Student("Adam Kowalski", 26, 30);
    arr[2] = Student("Adam Kowalski", 50, 50);
    std::cout << function(arr, 3) << std::endl;
    return 0;
}

Zadanie 4

Napisz klasę prostokąt posiadającą dwie zmienne prywatne i jedną metodę, która zwróci pole prostokąta.

Rozwiązanie:
#include <iostream>

class  Rectangle {
private:
    int x, y;
public:
    int area() {
        return x * y;
    }
    void set_x(int x) { this->x = x; }
    void set_y(int y) { this->y = y; }
    int get_x() { return x; }
    int get_y() { return y; }

    Rectangle(): Rectangle(0, 0) {}
    Rectangle(int x, int y) {
        this->x = x;
        this->y = y;
    }
};


int main() {
    int x, y;
    std::cin >> x >> y;
    //Rectangle rect = Rectangle(x, y);
    Rectangle rect;
    rect.set_x(x);
    rect.set_y(y);
    std::cout << rect.area() << std::endl;
    std::cout << rect.get_x() << " " << rect.get_y() << std::endl;
    return 0;
}
Omówienie:

Enkapsulacja inaczej zwana hermetyzacją (kapsułkowaniem) jest to jedno z głównych i podstawowych założeń programowania obiektowego. Polega na ukrywaniu metod i atrybutów dla klas i funkcji zewnętrznych. Dostęp do nich możliwy jest tylko z wewnątrz klasy, do której należą, lub z klas dziedziczących, czy też klas i funkcji zaprzyjaźnionych. Warto dodać, że gdy wszystkie pola w klasie znajdują się w sekcji prywatnej lub chronionej, to taką hermetyzację nazywa się hermetyzacją pełną.

W związku z powyższym, aby dostać się do prywatnych atrybutów klasy musimy zdefiniować specjalne metody. Te metody powinny umożliwiać odczytywanie lub ustawianie wartości pól klasy, a ogólnie nazywamy je akcesorami. Akcesory dzielimy na te, które ustawiają wartości pól tzw. settery (fraza set w nazwie metody) i te, które umożliwiają pobranie wartości pól tzw. gettery (fraza get w nazwie metody).

Zadanie 5

Utwórz klasę o nazwie Box, która posiada trzy zmienne prywatne typu zmiennoprzecinkowego podwójnej precyzji: width, height, depth. Wewnątrz klasy zdefiniuj konstruktor oraz klasę Ball wewnątrz tej klasy. Klasa Ball zawiera jedną prywatną zmienną typu zmiennoprzecinkowego podwójnej precyzji – r, która jest promieniem piłki. Dodatkowo klasa powinna posiadać konstruktor oraz metodę, która sprawdza czy dana piłka mieści się w pudełku. Zaprojektuj w języku C++ odpowiednie klasy i ich metody oraz funkcje umożliwiające wykorzystanie tych klas.

Rozwiązanie:
#include <iostream>

class Box {
private:
    double width, height, depth;
    class Ball {
    private:
        double r;
    public:
        Ball(double r) : r(r) {}
        bool fit(Box *box) {
            return r <= box->height / 2.0 &&
                    r <= box->width / 2.0 &&
                    r <= box->depth / 2.0;
        }
    };
    Ball *ball;
public:
    Box(double width, double height, double depth, double r) : width(width), height(height), depth(depth) { 
        ball = new Ball(r);
    }
    bool ball_fiting(Ball *b);
    Ball *get_ball() { return ball; }
    ~Box();
};

Box::~Box() {
    delete ball;
}

bool Box::ball_fiting(Ball *b) {
    return b->fit(this);
}


int main() {
    Box *box = new Box(5.0, 6.0, 7.2, 2.5);
    std::cout << box->ball_fiting(box->get_ball()) << std::endl;
    delete box;
    return 0;
}

Zadanie 6

Zaprojektuj klasę w języku C++, która umożliwi wykonanie operacji dodawania liczb zespolonych. Napisz program, który przetestuje działanie tak zaprojektowanej klasy. Program powinien poprosić o rzeczywistą i urojoną część dwóch liczb zespolonych oraz wyświetlić rzeczywistą i urojoną część ich sumy.

Rozwiązanie:
#include <iostream>

class Complex{
public:
    double real, imag;
    Complex(double real, double imag) {
        this->real = real;
        this->imag = imag;
    }
};

Complex add(const Complex &n1, const Complex &n2) {
    return Complex(n1.real + n2.real, n1.imag + n2.imag);
}

int main() {
    double real, imag;

    std::cin >> real >> imag;
    Complex number_1(real, imag);
    std::cin >> real >> imag;
    Complex number_2(real, imag);

    Complex result = add(number_1, number_2);
    std::cout << result.real << " " << result.imag << std::endl;

    return 0;
}

Zadanie 7

Utwórz klasę w języku C++ o nazwie Rectangle. Klasa powinna posiadać dwa pola prywatne typu zmiennoprzecinkowego: width i height oraz konstruktor. W przypadku, gdy użytkownik nie poda długości boków podczas tworzenia obiektu tej klasy, należy przypisać im domyślne wartości (odpowiednio: 5.f oraz 3.f). Dodatkowo klasa powinna posiadać metodę duplicate, która zwraca nowy obiekt tej klasy będący dwa razy większy oraz metodę area, która zwraca pole tego prostokąta. Napisz program w języku C++, który przetestuje działanie tak zaprojektowanej klasy.

Rozwiązanie:
#include <iostream>

class Rectangle {
    float width, height;
public:
    Rectangle() : width(5.f), height(3.f) {}
    Rectangle(float x, float y) : width(x), height(y) {}
    Rectangle duplicate();
    float area();
};

Rectangle Rectangle::duplicate() {
    return Rectangle(width * 2.f, height * 2.f);
}

float Rectangle::area() {
    return width * height;
}

int main() {
    Rectangle * test = new Rectangle();
    Rectangle d_test = test->duplicate();
    std::cout << test->area() << std::endl << d_test.area() << std::endl;
    return 0;
}

Zadanie 8

Zdefiniuj strukturę Point oraz klasę Rectangle. Struktura Point powinna zawierać dwa pola typu zmiennoprzecinkowego reprezentujące punkt w dwuwymiarowym układzie współrzędnych. Klasa Rectangle powinna posiadać pole reprezentujące dynamicznie zaalokowaną tablicę wierzchołków (wskaźnik na wskaźniki typu Point) oraz funkcje, która zwróci pole tego prostokąta. Projektując program w języku C++ zapewnij, że wszystkie stworzone obiekty zostaną usunięte. Konstruktor klasy Rectangle, powinien przyjmować cztery liczby zmiennoprzecinkowe x, y, w, h, które są kolejno pozycją lewego górnego rogu prostokąta oraz jego szerokością i wysokością.

Rozwiązanie:
#include <iostream>
#include <cmath>

struct Point {
    float x, y;
    Point (float x, float y) : x(x), y(y) {}
};

class Rectangle {
    Point **vertices;

public:
    Rectangle(float x, float y, float width, float height);
    ~Rectangle();
    float area();
};

Rectangle::Rectangle(float x, float y, float width, float height) {
    vertices = new Point*[4];
    vertices[0] = new Point(x, y);
    vertices[1] = new Point(x + width, y);
    vertices[2] = new Point(x, y + height);
    vertices[3] = new Point(x + width, y + height);
}

Rectangle::~Rectangle() {
    for(int i = 0; i < 4; ++i)
        delete vertices[i];
    delete[] vertices;
}

float Rectangle::area() {
    float a = sqrt(pow(vertices[0]->x - vertices[1]->x, 2) +
            pow(vertices[0]->y - vertices[1]->y,2));
    float b = sqrt(pow(vertices[0]->x - vertices[2]->x, 2) +
            pow(vertices[0]->y - vertices[2]->y,2));
    return a * b;
}

int main() {
    Rectangle *rect = new Rectangle(1, 2, 5, 4);
    std::cout << rect->area() << std::endl;
    delete rect;
    return 0;
}

Zadanie 9

Rozbuduj poprzedni program o metody, które umożliwią tworzenie głębokiej kopii obiektu klasy Rectangle. Dodatkowo należy przeciążyć operator przypisania dla klasy Rectangle tak, aby wykonał głęboką kopię wszystkich pól składowych.

Rozwiązanie:
#include <iostream>
#include <cmath>

struct Point {
    float x, y;
    Point (float x, float y) : x(x), y(y) {}
};

class Rectangle {
    Point **vertices;

public:
    Rectangle();
    Rectangle(float x, float y, float width, float height);
    Rectangle(const Rectangle &rect);
    ~Rectangle();
    float area();

    Rectangle& operator =(const Rectangle &rect);
};

Rectangle::Rectangle() {
    vertices = nullptr;
}

Rectangle::Rectangle(float x, float y, float width, float height) {
    vertices = new Point*[4];
    vertices[0] = new Point(x, y);
    vertices[1] = new Point(x + width, y);
    vertices[2] = new Point(x, y + height);
    vertices[3] = new Point(x + width, y + height);
}

Rectangle::Rectangle(const Rectangle &rect) {
    this->vertices = new Point*[4];
    for(int i = 0; i < 4; ++i)
        this->vertices[i] = new Point(rect.vertices[i]->x, rect.vertices[i]->y);
}

Rectangle::~Rectangle() {
    for(int i = 0; i < 4; ++i)
        delete vertices[i];
    delete[] vertices;
}

float Rectangle::area() {
    float a = sqrt(pow(vertices[0]->x - vertices[1]->x, 2) +
            pow(vertices[0]->y - vertices[1]->y,2));
    float b = sqrt(pow(vertices[0]->x - vertices[2]->x, 2) +
            pow(vertices[0]->y - vertices[2]->y,2));
    return a * b;
}

Rectangle& Rectangle::operator =(const Rectangle &rect) {
    if(this->vertices) {
        for(int i = 0; i < 4; ++i)
                delete vertices[i];
        delete[] vertices;
    }
    
    this->vertices = new Point*[4];
    for(int i = 0; i < 4; ++i)
        this->vertices[i] = new Point(rect.vertices[i]->x, rect.vertices[i]->y);
    return *this;
}

int main() {
    Rectangle *rect = new Rectangle(1, 2, 5, 4);
    Rectangle *rect_cpy = new Rectangle(*rect);
    Rectangle rect_cpy_2;
    rect_cpy_2 = *rect;
    std::cout << rect->area() << " " << rect_cpy->area() << " " << rect_cpy_2.area() << std::endl;
    delete rect;
    std::cout << rect_cpy->area() << std::endl;
    std::cout << rect_cpy_2.area() << std::endl;
    delete rect_cpy;
    return 0;
}
Omówienie:

Przeciążanie (przeładowanie) operatorów ma za zadanie przypisać operatorom nowe funkcje, określone przez nas zachowanie. Należy dodać, że generalnie większość operatorów nie jest generowana automatycznie dla tworzonych przez nas klas i struktur. Sytuacja wygląda nieco inaczej w przypadku operatora przypisania =, który generowany jest automatycznie i wykonuje płytką kopię wszystkich pól składowych. Jeżeli chcemy zmienić domyślne zachowanie operatora lub określić jak operator powinien się zachowywać dla naszej klasy należy go przeciążyć. Przeładowania operatorów w większości przypadków można dokonać jako metodę składową lub jako funkcję globalną (wyjątkiem są operatory =, [], -> te muszą być zdefiniowane jako metody).

Wspomniany wyżej operator przypisania może być przeciążony w następujący sposób:
Typ& Typ::operator=(const Typ& t);
Bardziej rozbudowany przykład mamy poniżej:

Rectangle& Rectangle::operator =(const Rectangle &rect) {
    if(this->vertices) {
        for(int i = 0; i < 4; ++i)
                delete vertices[i];
        delete[] vertices;
    }
    
    this->vertices = new Point*[4];
    for(int i = 0; i < 4; ++i)
        this->vertices[i] = new Point(rect.vertices[i]->x, rect.vertices[i]->y);
    return *this;
}

Powyższy kod w liniach 2-6 weryfikuje, czy do wskaźników nie jest przypisany adres dynamicznie zaalokowanego obszaru pamięci. W sytuacji, gdy tak jest, to najpierw tą pamięć zwalnia, a następnie w liniach 7-9 dynamicznie allokuje pamięć i inicjalizuje zmienne wartościami obiektu przekazanego w argumencie.


Warto dodać, że nie wszystkie operatory występujące w języku C++ można przeładować. Przykładami takich operatorów są: odniesienie do składowej klasy (operator .), odniesienia do składnika będącego wskaźnikiem (operator .*), operatora zakresu (operator ::), czy operator zwracającego wartość zależnie od spełnienia warunku (operator ?:).

Dodatkowo nie możemy modyfikować priorytetów i argumentowości operatorów. Kontynuując nie możemy przeciążyć operatorów dla typów wbudowanych tj. int, char, float itd. Tematyka przeciążania operatorów jest bardzo rozbudowanym działem, a omówienie przeładowania wszystkich operatorów wykracza za zakres tego kursu. Jednakże osoby zainteresowane zachęcam do zapoznania się z dokumentacją przeciążenia operatorów – dokumentacja.

Zadanie 10*

Zaimplementuj klasę w języku C++, która będzie symulowała zachowanie kolejki liczb całkowitych (FIFO). Kolejka powinna być implementacją tablicową, czyli pojedynczy węzeł reprezentowany jest przez element tablicy. Napisz program w języku C++, który przetestuje działanie tej klasy.

Rozwiązanie:
#include <iostream>
#include <cstdlib>

#define SIZE 10

class MyQueue {
    int *arr, capacity, front, rear, count;

public:
    MyQueue(int size = SIZE);
    ~MyQueue();

    void pop();
    void push(int x);
    int first();
    int size();
    bool is_empty();
    bool is_full();
};

MyQueue::MyQueue(int size) {
    arr = new int[size];
    capacity = size;
    front = 0;
    rear = -1;
    count = 0;
}

MyQueue::~MyQueue() {
    delete[] arr;
}

void MyQueue::pop() {
    if(is_empty()) {
        exit(EXIT_FAILURE); //Zakończenie programu błędem
    }
    front = (front + 1) % capacity;
    count--;
}

void MyQueue::push(int item) {
    if (is_full()) {
        exit(EXIT_FAILURE); //Zakończenie programu błędem
    }
    rear = (rear + 1) % capacity;
    arr[rear] = item;
    count++;
}

int MyQueue::first() {
    if (is_empty()) {
        exit(EXIT_FAILURE); //Zakończenie programu błędem
    }
    return arr[front];
}

int MyQueue::size() {
    return count;
}

bool MyQueue::is_empty() {
    return (size() == 0);
}

bool MyQueue::is_full() {
    return (size() == capacity);
}

int main() {
    MyQueue q(5);

    q.push(1);
    q.push(2);
    q.push(3);

    std::cout << "Pierwszy element: " << q.first() << std::endl;
    
    q.pop();
    q.push(4);
    
    std::cout << "Rozmiar kolejki: " << q.size() << std::endl;
    q.pop();
    q.pop();
    q.pop();
    
    if (q.is_empty()) 
        std::cout << "Kolejka jest pusta.\n";
    else 
        std::cout << "Kolejka nie jest pusta.\n";

    return 0;
}

Zadanie 11*

Znamy takie kalendarze jak: gregoriański, juliański, starogrecki, rzymski, egipski, aztecki, czy bardzo ciekawy kalendarz Majów. Każdy z nich jest charakterystyczny, dzieli czas na pewne cykle, a niektóre kalendarze zakładają np. lata przestępne, bądź inne istotne parametry. Oczywiście nie są to jedyne znane nam kalendarze, są również kalendarze bardzo osobliwe związane z pewnymi światami opisanymi w literaturze, filmie, czy grach. Jednym z takich popularnych kalendarzy jest kalendarz elfów, pojawiający się w książkach Sapkowskiego. (Tak! Tego gościa od Wiedźmina. 🙂 )

Naszym zadaniem jest napisanie programu, który w sposób automatyczny będzie liczył dni tygodnia, dla kalendarza określonego pewnymi zmiennymi parametrami. Te zmienne parametry dotyczą liczby miesięcy, liczby dni w poszczególnych miesiącach, liczbie dni tygodnia, numeru miesiąca, w którym doliczany jest dodatkowy dzień dla roku przestępnego. Zakładamy, że w naszym kalendarzu może pojawić się rok przestępny, a metoda sprawdzenia, czy dany rok takim jest wygląda następująco:

F1: 0
F2: 2
Frok: (a * Frok - 1 + Frok - 2 + b) mod m


gdzie a, b i m to parametry wybrane do określenia naszego kalendarza. Jeśli wynikiem funkcji będzie wartość nieparzysta to rok jest przestępny.

Zakładamy, że pierwszego dnia pierwszego miesiąca pierwszego roku mamy pierwszy dzień tygodnia. Dodatkowo obliczenia rozpoczynamy od roku 1.

Napisz program w języku C++, który na standardowym wejściu przyjmie w pierwszym wierszu sześć liczb naturalnych n_months, n_week_days, leap_month, a, b, m, oznaczająco kolejno: liczba miesięcy, liczba dni tygodnia, miesiąc przestępny oraz parametry metody obliczającej, czy dany rok jest przestępnym. W kolejnym wierszu program powinien wczytać od użytkownika ciąg n_months liczb dodatnich, z których każda oznacza liczbę dni w kolejnych miesiącach. Na koniec program, powinien wczytać 3 liczby dodatnie d, m, r, oznaczające datę w postać: dzień miesiąc rok.

W wyniku działania program powinien wyświetlić na standardowym wyjściu liczbę naturalną, oznaczającą numer dnia tygodnia dla odczytanej daty.

Uwagi: n_months, n_week_days, leap_month, a, b, m, d, m, r oraz wartości określające ilość dni w miesiącach to liczby naturalne. W zadaniu nie można używać struktur i klas.

Przykład:
input:
3 5 2 20 15 5
10 12 7
9 2 1
output:
4

Rozwiązanie:
#include<iostream>

void calculate_years_leap_days(int *arr, int n, int a, int b, int m) {
    int tmp, n_leap_days = 0, f_0 = 0, f_1 = 2;
    for(int i = 2; i <= n; ++i) {
        tmp = (a * f_1 + f_0 + b) % m;
        f_0 = f_1;
        f_1 = tmp;
        if(f_1 % 2) 
            ++n_leap_days;
        arr[i] = n_leap_days;
    }
}

unsigned int calculate_day_of_the_week(int *arr, int *days_months, int day, int month, int year, int leap_month, int n_week_days, int n_months) {
    int tmp = year - 1, n_leap_days = 0;
    if(year > 1) 
        n_leap_days = (month > leap_month && arr[year] > arr[tmp]) ? arr[tmp] + 1 : arr[tmp];
    return (days_months[n_months] * tmp + days_months[month - 1] + day + n_leap_days - 1) % n_week_days + 1;
}

int main(){
    int day, month, year, n_months, n_week_days, leap_month, a, b, m;
    std::cin >> n_months >> n_week_days >> leap_month >> a >> b >> m;
    
    int *days_months = new int[n_months + 1]();
    for(int i = 1; i <= n_months; ++i) {
        std::cin >> days_months[i];
        days_months[i] += days_months[i - 1];
    }
    
    std::cin >> day >> month >> year;
    int *years_n_leap_days = new int[year + 1]();
    calculate_years_leap_days(years_n_leap_days, year, a, b, m);
    
    std::cout << calculate_day_of_the_week(years_n_leap_days, days_months, day, month, year, leap_month, n_week_days, n_months) << std::endl;
    
    delete[] days_months; 
    delete[] years_n_leap_days;
    return 0;
}

Zadanie 12

Napisz program w języku C++, który zdefiniuje typ złożony Point reprezentujący punkt w dwuwymiarowej przestrzeni euklidesowej. Punkt powinien być opisany za pomocą dwóch współrzędnych. Zadeklaruj wszystkie pola klasy jako prywatne i dodaj odpowiednie akcesory. Zdefiniuj funkcję globalną do pobierania od użytkownika współrzędnych i wstawiania ich do obiektu klasy Point. Dla tej klasy, w programie głównym utwórz: (1) obiekt automatyczny tej klasy, (2) obiekt dynamiczny, wskazywany wskaźnikiem, (3) tablicę automatyczną obiektów automatycznych, (4) tablicę automatyczną wskaźników na obiekty, zainicjuj te wskaźniki w dowolny sposób, (5) dynamiczną tablicę obiektów automatycznych, (6) dynamiczną tablicę wskaźników, zainicjuj te wskaźniki. Na końcu programu zwolnij pamięć dla wszystkich obiektów tworzonych dynamicznie. W każdym przypadku skorzystaj z publicznych metod tej klasy.

Rozwiązanie:
#include <iostream>

class Point {
private:
    double x;
    double y;

public:
    Point() : x(0.0), y(0.0) {}
    void set_x(double x) { this->x = x; }
    void set_y(double y) { this->y = y; }
    const double get_x() { return x; }
    const double get_y() { return y; }
};

void get_coordinates_from_std_in(Point& point) {
    double x, y;
    std::cin >> x;
    std::cin >> y;
    point.set_x(x);
    point.set_y(y);
}

int main() {
    // (1)
    Point point;
    get_coordinates_from_std_in(point);
    std::cout << point.get_x() << ", " << point.get_y() << std::endl;

    // (2)
    Point* dynamic_point = new Point();
    get_coordinates_from_std_in(*dynamic_point);
    std::cout << dynamic_point->get_x() << ", " << dynamic_point->get_y() << std::endl;
    delete dynamic_point;

    // (3)
    const int n = 3;
    Point points_arr[n];
    for (int i = 0; i < n; ++i) {
        get_coordinates_from_std_in(points_arr[i]);
        std::cout  << i << " : " << points_arr[i].get_x() << ", " << points_arr[i].get_y() << std::endl;
    }

    // (4)
    Point* points_pointers_arr[n];
    for (int i = 0; i < n; ++i) {
        points_pointers_arr[i] = new Point();
        get_coordinates_from_std_in(*points_pointers_arr[i]);
        std::cout  << i << " : " << points_pointers_arr[i]->get_x() << ", " << points_pointers_arr[i]->get_y() << std::endl;
    }
    for (int i = 0; i < n; ++i) delete points_pointers_arr[i];

    // (5)
    Point* points_daynamic_arr = new Point[n];
    for (int i = 0; i < n; ++i) {
        get_coordinates_from_std_in(points_daynamic_arr[i]);
        std::cout  << i << " : " << points_daynamic_arr[i].get_x() << ", " << points_daynamic_arr[i].get_y() << std::endl;
    }
    delete[] points_daynamic_arr;

    // (6)
    Point** points_pointers_dynamic_arr = new Point*[n];
    for (int i = 0; i < n; ++i) {
        points_pointers_dynamic_arr[i] = new Point();
        get_coordinates_from_std_in(*points_pointers_dynamic_arr[i]);
        std::cout  << i << " : " << points_pointers_dynamic_arr[i]->get_x() << ", " << points_pointers_dynamic_arr[i]->get_y() << std::endl;
    }
    for (int i = 0; i < n; ++i) delete points_pointers_dynamic_arr[i];
    delete[] points_pointers_dynamic_arr;

    return 0;
}

Zadanie 13

W języku C++ definiuj klasę Matrix3x3 reprezentującą macierz o rozmiarze 3x3 liczb zmiennoprzecinkowych. Zdefiniuj metodę print() wypisującą na ekranie elementy tej macierzy. Zdefiniuj metodę add() dodawania liczby do takiej macierzy; funkcja ma zwracać nowo obliczoną macierz. Napisz program w języku C++, który przetestuje działanie metod tej klasy.

Rozwiązanie:
#include <iostream>
#include <cstring>

class Matrix3x3{
    float values[9];
public:
    void print();
    Matrix3x3*add(const float &);
    Matrix3x3(float []);
};

void Matrix3x3::print() {
    for(unsigned int i = 0; i < 3; ++i) {
        for(unsigned int j = 0; j < 3; ++j)
            std::cout << values[i * 3 + j] << " ";
        std::cout << std::endl;
    }
}

Matrix3x3* Matrix3x3::add(const float &scalar) {
    for(unsigned int i = 0; i < 9; ++i) values[i] += scalar;
    return this;
}

Matrix3x3::Matrix3x3(float values[]) {
    std::memcpy(this->values, values, sizeof(float) * 9);
}

int main() {
    float values[9] = {1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f};
    Matrix3x3 mat(values);
    mat.print();
    std::cout << std::endl;
    
    Matrix3x3 *result = mat.add(3.f);
    result->print();
    return 0;
}

Zadanie 14

W języku C++ napisz klasę Identifier, która udostępnia tylko jedną publiczną bezargumentową metodę id. Metoda id powinna zwracać, za każdym razem inną, nieujemną liczbę całkowitą dla danego obiektu.

Rozwiązanie:
//Version 1.0
#include <iostream>

class Identifier{
    unsigned int _id;
public:
    Identifier() : _id(0) {}
    unsigned int id() { return _id++; }
};


int main() {
    Identifier *identifier = new Identifier();
    for(unsigned int i = 0; i < 10; ++i)
        std::cout << identifier->id() << std::endl;
    delete identifier;
    return 0;
}
//Version 2.0
#include <iostream>

class Identifier{
    unsigned int _id = 0;
public:
    unsigned int id() { return _id++; }
};


int main() {
    Identifier identifier;
    for(unsigned int i = 0; i < 10; ++i)
        std::cout << identifier.id() << std::endl;
    return 0;
}

Zadanie 15

W języku C++ zdefiniuj klasę Vector, która będzie realizowała podstawową funkcjonalność dynamicznej tablicy liczb całkowitych typu int. W klasie zdefiniuj metody:
init() – inicjującą elementy składowe obiektu,
int& at(int i) – zwracającą referencję na i-ty element tablicy,
void append(int v) – dodającą element na koniec tablicy,
void resize(int size) – zmieniającą rozmiar tablicy na nowy, zachowując elementy już dodane do tablicy,
int size() – zwracającą rozmiar tablicy,
void clear() – czyszczącą całą zawartość obiektu.
Napisz program w języku C++, w którym przetestujesz implementację tej klasy.

Rozwiązanie:
#include <iostream>

class Vector {
private:
    int* elements;
    int vector_size;
    int capacity;

public:
    Vector();
    ~Vector();
    int& at(int);
    const int size();
    void init();
    void append(int);
    void resize(int);
    void clear();

};

Vector::Vector() {
    init();
}

void Vector::init() {
    elements = nullptr;
    vector_size = 0;
    capacity = 0;
}

int& Vector::at(int i) {
    if (i >= 0 && i < vector_size) return elements[i];
    std::cout << "Index out of range\n";
    return elements[vector_size - 1];
}

void Vector::append(int v) {
    if (vector_size >= capacity) resize(capacity * 2 + 1);
    elements[vector_size++] = v;
}

void Vector::resize(int size) {
    int* elements = new int[size];
    for (int i = 0; i < vector_size; ++i) elements[i] = this->elements[i];
    delete[] this->elements;
    this->elements = elements;
    capacity = size;
}

const int Vector::size() {
    return vector_size;
}

void Vector::clear() {
    delete[] elements;
    init();
}

Vector::~Vector() {
    delete[] elements;
}

int main() {
    Vector vector;
    for (int i = 0; i < 5; i++) vector.append(i);

    std::cout << vector.size() << std::endl;
    for (int i = 0; i < vector.size(); i++) std::cout << i << ": " << vector.at(i) << std::endl;

    vector.resize(10);
    std::cout << vector.size() << std::endl;
    for (int i = 5; i < 11; i++)  vector.append(i);


    std::cout << vector.size() << std::endl;
    for (int i = 0; i < vector.size(); i++) std::cout << i << ": " << vector.at(i) << std::endl;

    vector.clear();
    std::cout << vector.size() << std::endl;

    return 0;
}

Zadanie 16

W języku C++ napisz klasę Mutex, której obiekty w każdym momencie są w jednym z dwóch stanów wolny lub zajęty. Bezpośrednio po utworzeniu obiekt typu Mutex powinien być w stanie wolny. Klasa powinna udostępniać następujące publiczne metody:
lock(), której wywołanie zmienia stan mutexa z wolny na zajęty (w przypadku, gdy mutex jest już w stanie zajęty, metoda nie powinna zmieniać jego stanu),
release(), której wywołanie zmienia stan mutexa z zajęty na wolny (w przypadku, gdy jest już w stanie wolny, metoda nie powinna zmieniać jego stanu),
state() zwracającą wartość true gdy mutex jest w stanie wolny i false w przeciwnym wypadku.
Zadbaj o odpowiednią hermetyzację klasy i o prawidłową inicjalizację obiektów. W programie głównym stwórz dynamiczną tablicę obiektów Mutex, automatyczną tablicę dynamicznych obiektów Mutex, dynamiczną tablicę dynamicznych obiektów Mutex oraz dwuwymiarową tablicę dynamiczną obiektów Mutex. Zadbaj o poprawne zwalnianie pamięci.

Rozwiązanie:
#include <iostream>

class Mutex {
private:
    bool is_locked;

public:
    Mutex() : is_locked(false) {}
    void lock() { if (!is_locked) is_locked = true; }
    void release() { if (is_locked) is_locked = false; }
    const bool state() { return !is_locked; }
};

int main() {
    unsigned int n = 3, m = 2;
    Mutex* mutex_dynamic_arr = new Mutex[n];
    for (unsigned int i = 0; i < n; ++i) {
        std::cout << i << ": " << mutex_dynamic_arr[i].state() << std::endl;
        mutex_dynamic_arr[i].lock();
        std::cout << i << ": " << mutex_dynamic_arr[i].state() << std::endl;
        mutex_dynamic_arr[i].release();
        std::cout << i << ": " << mutex_dynamic_arr[i].state() << std::endl;
    }
    delete[] mutex_dynamic_arr;

    std::cout << std::endl;

    Mutex* mutex_pointers_arr[n];
    for (unsigned int i = 0; i < n; ++i) {
        mutex_pointers_arr[i] = new Mutex();
        std::cout << i << ": " << mutex_pointers_arr[i]->state() << std::endl;
        mutex_pointers_arr[i]->lock();
        std::cout << i << ": " << mutex_pointers_arr[i]->state() << std::endl;
        mutex_pointers_arr[i]->release();
        std::cout << i << ": " << mutex_pointers_arr[i]->state() << std::endl;
    }
    for (unsigned int i = 0; i < n; ++i) delete mutex_pointers_arr[i];

    std::cout << std::endl;

    Mutex** mutex_pointers_dynamic_arr = new Mutex*[n];
    for (unsigned int i = 0; i < n; i++) {
        mutex_pointers_dynamic_arr[i] = new Mutex();
        std::cout << i << ": " << mutex_pointers_dynamic_arr[i]->state() << std::endl;
        mutex_pointers_dynamic_arr[i]->lock();
        std::cout << i << ": " << mutex_pointers_dynamic_arr[i]->state() << std::endl;
        mutex_pointers_dynamic_arr[i]->release();
        std::cout << i << ": " << mutex_pointers_dynamic_arr[i]->state() << std::endl;
    }
    for (unsigned int i = 0; i < n; i++) delete mutex_pointers_dynamic_arr[i];
    delete[] mutex_pointers_dynamic_arr;

    std::cout << std::endl;

    Mutex** mutex_dynamic_2d_arr = new Mutex*[n];
    for (unsigned int i = 0; i < n; i++) {
        mutex_dynamic_2d_arr[i] = new Mutex[m];
        for (int j = 0; j < m; j++) {
            std::cout << i << ": " << mutex_dynamic_2d_arr[i][j].state() << std::endl;
            mutex_dynamic_2d_arr[i][j].lock();
            std::cout << i << ": " << mutex_dynamic_2d_arr[i][j].state() << std::endl;
            mutex_dynamic_2d_arr[i][j].release();
            std::cout << i << ": " << mutex_dynamic_2d_arr[i][j].state() << std::endl;
        }
    }
    for (unsigned int i = 0; i < n; i++) delete[] mutex_dynamic_2d_arr[i];
    delete[] mutex_dynamic_2d_arr;;

    return 0;
}

Zadanie 17

W języku C++ zaprojektuj i zaimplementuj klasę String do obsługi napisów. Klasa ta powinna mieć metody:
– konstruktor domyślny, konstruktor inicjujący i konstruktor kopiujący,
– destruktor,
length() – zwracającą długość napisu
cstring() – zwracającą wskaźnik na tablicę znakową w tylu c
append(...) – dodającą do aktualnego napisu inny napis
clear() – czyszczącą napis
empty() – zwracającą informację czy napis jest pusty
print() – drukującą napis na ekranie
– operator przypisania = – zadbaj o głęboką kopię obiektu
Przetestuj tworzoną klasę w programie głównym na obiektach automatycznych i dynamicznych.

Rozwiązanie:
#include <iostream>
#include <cstring>

class String {
private:
    char* str;
public:
    String() : str(nullptr) {}
    String(const char*);
    String(String&);
    ~String();
    int length();
    const char* cstring();
    void append(String&);
    void clear();
    const bool empty();
    void print();
    String& operator=(String&);
};

String::String(const char* s) {
    int length = std::strlen(s);
    str = new char[length + 1];
    std::strcpy(str, s);
}

String::String(String& other) {
    int length = other.length();
    str = new char[length + 1];
    std::strcpy(str, other.cstring());
}

String::~String() {
    delete[] str;
}

int String::length() {
    return std::strlen(str);
}

const char* String::cstring() {
    return str;
}

void String::append(String& other) {
    int old_length = length();
    int append_length = other.length();
    char* str = new char[old_length + append_length + 1];
    std::strcpy(str, this->str);
    std::strcpy(str + old_length, other.cstring());
    delete[] this->str;
    this->str = str;
}

void String::clear() {
    delete[] str;
    str = nullptr;
}

const bool String::empty() {
    return str == nullptr || str[0] == '\0';
}

void String::print() {
    std::cout << str;
}

String& String::operator=(String& other) {
    if (this != &other) {
        delete[] str;
        int length = other.length();
        str = new char[length + 1];
        std::strcpy(str, other.cstring());
    }
    return *this;
}

int main() {
    String str_0("Hello");
    String str_1(" World");
    String str_2(str_0);

    str_0.print();
    std::cout << std::endl;
    str_1.print();
    std::cout << std::endl;
    str_2.print();
    std::cout << std::endl;

    str_0.append(str_1);
    str_0.print();
    std::cout << std::endl;

    str_2.clear();
    std::cout << (str_2.empty() ? "Yes" : "No") << std::endl;

    String* ptr_str_0 = new String("Dynamic");
    String* ptr_str_1 = new String(" String");

    ptr_str_0->print();
    std::cout << std::endl;
    ptr_str_1->print();
    std::cout << std::endl;

    delete ptr_str_0;
    delete ptr_str_1;

    return 0;
}

Zadanie 18

W języku C++ zaprojektuj i zaimplementuj klasę Array realizującą funkcjonalność dynamicznej tablicy (typu int). W klasie zdefiniuj metody:
– konstruktory – domyślny, inicjujący, kopiujący,
int& at(int i) – zwracającą referencję na i-ty element tablicy,
void append(int v) – dodającą element na koniec tablicy,
void insert(int i, int v) – wstawiającą element v za i-tym elementem,
void resize(int size) – zmieniającą rozmiar tablicy na nowy, zachowując elementy już dodane do tablicy,
int size() – zwracającą rozmiar tablicy,
void clear() – czyszczącą całą zawartość obiektu.
Napisz program w języku C++, w którym przetestujesz implementację tej klasy.

Rozwiązanie:
#include <iostream>

class Array {
private:
    int* elements;
    int array_size;

public:
    Array() : elements(nullptr), array_size(0) {}
    
    Array(int size) : array_size(size) {
        elements = new int[array_size];
        for (int i = 0; i < array_size; ++i) elements[i] = 0;
    }

    Array(const Array& other) : array_size(other.array_size) {
        elements = new int[array_size];
        for (int i = 0; i < array_size; ++i) elements[i] = other.elements[i];
    }

    int& at(int i) {
        if (i >= 0 && i < array_size) return elements[i];
        std::cout << "Index out of range\n";
        return elements[array_size - 1];
    }

    void append(int v) {
        resize(array_size + 1);
        elements[array_size - 1] = v;
    }

    void insert(int i, int v) {
        if (i >= 0 && i <= array_size) {
            resize(array_size + 1);
            for (int j = array_size - 1; j > i; --j) elements[j] = elements[j - 1];
            elements[i] = v;
        } else {
            std::cout << "Index out of range\n";
        }
    }

    void resize(int size) {
        if (size < 0) {
            std::cout << "Invalid size\n";
            return;
        }
        int* elements = new int[size];
        int copy_size = (array_size < size) ? array_size : size;
        for (int i = 0; i < copy_size; ++i) elements[i] = this->elements[i];
        delete[] this->elements;
        this->elements = elements;
        array_size = size;
    }

    int size() const {
        return array_size;
    }

    void clear() {
        delete[] elements;
        elements = nullptr;
        array_size = 0;
    }

    ~Array() {
        delete[] elements;
    }
};

int main() {
    Array arr;
    std::cout << arr.size() << std::endl;

    Array arr_constructor_test(5);
    std::cout << arr_constructor_test.size() << std::endl;

    Array cpy_arr(arr_constructor_test);
    std::cout << cpy_arr.size() << std::endl;

    for (int i = 1; i <= 5; ++i) arr_constructor_test.append(i);
    std::cout << arr_constructor_test.size() << std::endl;

    arr_constructor_test.insert(2, 10);
    std::cout << arr_constructor_test.size() << std::endl;
    
    arr_constructor_test.resize(8);
    std::cout << arr_constructor_test.size() << std::endl;
    
    for (int i = 0; i < arr_constructor_test.size(); ++i)
        std::cout << i << ": " << arr_constructor_test.at(i) << std::endl;

    arr_constructor_test.clear();
    std::cout << arr_constructor_test.size() << std::endl;
    
    return 0;
}

Zadanie 19

W języku C++ zaimplementuj klasę List realizującą funkcjonalność jednokierunkowej listy wskaźnikowej. W klasie zdefiniuj metody:
– konstruktory – domyślny, (konstruktor kopiujący uczyń prywatnym),
int size() – zwracającą rozmiar tablicy,
int& at(int i) – zwracającą referencję na i-ty element tablicy,
int* find(int v) – zwracającą wskaźnik na znaleziony element v lub nullptr jeżeli takiego elementu nie ma na liście,
void append(int v) – dodającą element na koniec listy,
void insert(int i, int v) – wstawiającą element v za i-tym elementem,
void remove(int i) – usuwającą i-ty element z listy,
void clear() – czyszczącą całą zawartość obiektu.
Napisz program w języku C++, w którym przetestujesz implementację tej klasy.

Rozwiązanie:
#include <iostream>

class List {
private:
    class Node {
    public:
        int value;
        Node* next;
        Node(int v) : value(v), next(nullptr) {}
    };

    Node* head;
    int list_size;
    
    List(const List& other);

public:
    List() : head(nullptr), list_size(0) {}
    ~List();
    int size();
    int& at(int);
    int* find(int);
    void append(int);
    void insert(int, int);
    void remove(int);
    void clear();
};

List::List(const List& other) : head(nullptr), list_size(0) {
    Node* current = other.head;
    while (current != nullptr) {
        append(current->value);
        current = current->next;
    }
}

List::~List() {
    clear();
}

int List::size() {
    return list_size;
}

int& List::at(int i) {
    Node* current = head;
    int count = 0;
    while (current != nullptr) {
        if (count++ == i) return current->value;
        current = current->next;
    }
    std::cout << "Index out of range\n";
    return head->value;
}

int* List::find(int v) {
    Node* current = head;
    while (current != nullptr) {
        if (current->value == v) return &(current->value);
        current = current->next;
    }
    return nullptr;
}

void List::append(int v) {
    Node* node = new Node(v);
    if (head == nullptr) {
        head = node;
    } else {
        Node* current = head;
        while (current->next != nullptr) current = current->next;
        current->next = node;
    }
    ++list_size;
}

void List::insert(int i, int v) {
    if (i < 0 || i > list_size) {
        std::cout << "Index out of range\n";
        return;
    }

    Node* node = new Node(v);
    if (i == 0) {
        node->next = head;
        head = node;
    } else {
        Node* current = head;
        int count = 0;
        while (current != nullptr && count++ < i - 1) current = current->next;
        node->next = current->next;
        current->next = node;
    }
    ++list_size;
}

void List::remove(int i) {
    if (i < 0 || i >= list_size) {
        std::cout << "Index out of range\n";
        return;
    }

    if (i == 0) {
        Node* to_remove = head;
        head = head->next;
        delete to_remove;
    } else {
        Node* current = head;
        int count = 0;
        while (current != nullptr && count++ < i - 1) current = current->next;
        Node* to_remove = current->next;
        current->next = to_remove->next;
        delete to_remove;
    }
    --list_size;
}

void List::clear() {
    Node* current = head;
    while (current != nullptr) {
        Node* to_remove = current;
        current = current->next;
        delete to_remove;
    }
    head = nullptr;
    list_size = 0;
}

int main() {
    List list;
    for (int i = 0; i < 5; ++i) list.append(i);

    std::cout << list.size() << std::endl;
    for (int i = 0; i < list.size(); ++i) std::cout << i << ": " << list.at(i) << std::endl;

    list.insert(2, 10);
    std::cout << list.size() << std::endl;

    list.remove(3);
    std::cout << list.size() << std::endl;

    int* found = list.find(3);
    std::cout << (found ? std::to_string(*found) : "Not found") << std::endl;
    
    list.clear();
    std::cout << list.size() << std::endl;

    return 0;
}

Zadanie 20

Zaprojektuj klasę Point posiadającą dwa pola prywatne typu float oraz akcesory i mutatory do tych pól

Rozwiązanie:
class Point {
private:
    float x, y;

public:
    void set_x(double x) { this->x = x; }
    void set_y(double y) { this->y = y; }
    const double get_x() { return x; }
    const double get_y() { return y; }
};

Zadanie 21

Do klasy zaprojektowanej w poprzednim zadaniu dopisz metodę move, która przyjmie dwa argumenty typu float: x i y, która przesunie punkt o zadany wektor.

Rozwiązanie:
class Point {
private:
    float x, y;

public:
    void move(float x, float y) {
	this->x += x;
	this->y += y;
    }
    void set_x(float x) { this->x = x; }
    void set_y(float y) { this->y = y; }
    const double get_x() { return x; }
    const double get_y() { return y; }
};

Przygotuj się na kolejne laboratorium!

W celu przygotowania się na kolejne zajęcia, spróbuj wykonać poniższe zadania samodzielnie.

Zadanie 1

Napisz program w języku C++, który wyświetli zawartość obiektu std::string znak po znaku posługując się operatorem subskryptowym [], pętlą for i funkcją std::string::size.

Zadanie 2

Napisz funkcję w języku C++, która przyjmie tablicę obiektów std::string i jej rozmiar. Funkcja powinna zwrócić napis, w którym znajdą się skonkatenowane, oddzielone pojedynczą spacją napisy z tablicy. Użyj operatora +=. Napisz program w języku C++, który przetestuje działanie tej funckji.

Zadanie 3

Napisz funkcję w języku C++, która przyjmie jako parametry dwa obiekty klasy std::string, które nazwane zostaną przeszukiwany i poszukiwany. Funkcja powinna zwrócić indeks, w którym napis poszukiwany znajduje się w przeszukiwanym lub -1 jeżeli nie zostanie on znaleziony. Użyj metody std::string::find.

Zadanie 4

Napisz program w języku C++, który wyświetli zawartość obiektu std::string posługując się iteratorami std::string::begin, std::string::end oraz pętlą for.

Zadanie 5

Napisz funkcję w języku C++, która przyjmie obiekt std::vector oraz dwie wartości całkowite a i b. Funkcja powinna zwrócić obiekt std::vector zawierający liczby z wektora wejściowego znajdujące się w obustronnie otwartym zakresie (a, b). Napisz program w języku C++, który przetestuje działanie tej funkcji.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *