Arvutiteaduse instituut
  1. Kursused
  2. 2024/25 kevad
  3. Programmeerimine keeles C++ (LTAT.03.025)
EN
Logi sisse

Programmeerimine keeles C++ 2024/25 kevad

  • Pealeht
  • 1. Muutujad ja andmetüübid
  • 2. Keele põhikonstruktsioonid I
  • 3. Keele põhikonstruktsioonid II
  • 4. Klass, struktuur, mallid
  • 5. Dünaamiline mäluhaldus I
  • 6. Dünaamiline mäluhaldus II
  • 7. Kontrolltöö 1

Seitsmendal nädalal toimub 1. kontrolltöö

1. kontrolltöö näidis on Moodles

  • 8. Dünaamiline mäluhaldus III
8 Dünaamiline mäluhaldus III
8.1 Kodutöö
8.2 Harjutused
8.3 Videolingid
  • 9. STL andmestruktuurid I
  • 10. STL andmestruktuurid II
  • 11. OOP I Klassid
  • 12. OOP II Pärilus ja polümorfism
  • 13. Erindite töötlemine
  • 14. Täiendavad teemad
  • 15. Kontrolltöö 2

Viieteistkümnendal nädalal toimub 2. kontrolltöö

2. kontrolltöö näidis on Moodles

  • 16. Projekti esitlus
  • Mõned viited - vajalikud kaaslased
  • Vanad materjalid
  • Juhendid
  • Viited

Kodused ülesanded

Funktsioonide nimede ja tüüpide valideerimise kood on juhendi lõpus. Esitada tuleb kuus faili: arvud.h, arvud.cpp, pilt.h, pilt.cpp, ilm.h, ilm.cpp.

1. Funktsioonid

NB! Loodud funktsioonid lisa vastavalt failidesse arvud.h ja arvud.cpp.

1a. Kirjuta funktsioon unique_ptr<int> genereeri_arv(), mis tagastab juhusliku täisarvu lõigul [1, 1000].

1b. Kirjuta mallifunktsioon std::string proovi_arvu(std::weak_ptr<T> ptr), mis kontrollib, kas parameetrina antud nõrk viit on kehtiv. Kehtiva viida korral tagastab funktsioon on olemas, ma luban ja mittekehtiva viida korral ei ole enam :/. Näiteks, testprogrammi

auto arv1{genereeri_arv()};
auto arv2{genereeri_arv()};
auto arv3{genereeri_arv()};
cout << format("arv1: {}; arv2: {}; arv3: {}", *arv1, *arv2, *arv3);
auto ptr1{std::make_shared<int>(32)};
std::weak_ptr<int> weak1{ptr1};
cout << format("\nptr1: {}", proovi_arvu(weak1));
std::weak_ptr<float> weak2{make_shared<float>(25.5)};
weak2.reset();
cout << format("\nptr2: {}", proovi_arvu(weak2));

võimalik väljund on

arv1: 525; arv2: 162; arv3: 494
ptr1: on olemas, ma luban
ptr2: ei ole enam :/

2. Pildid

NB! Loodud funktsioonid lisa vastavalt failidesse pilt.h ja pilt.cpp.

Üks lihtsamaid pildiformaate on PPM (Portable Pixel Map). PPM fail koosneb päisest, kus on neli välja:

  • id - PPM faili puhul on see P6
  • laius - pildi laiuse pikslite arv
  • kõrgus - pildi kõrguse pikslite arv
  • värv - maksimaalne värvi väärtus [0, 255] (tegelikult võib olla [0, 65535] )

Peale päist on RGB väärtused binaarkujul (iga piksli jaoks on kolm baiti värvide rgb jaoks).

2a. Koosta struktuur RGB värvide väärtuste hoidmiseks.

  • Struktuur sisaldab liikmeid r, g ja b jaoks (sobib tüüp unsigned char).
  • Struktuuri suurus peab olema kolm baiti.
    • Struktuuri suurust saab kontrollida sizeof(RGB) abil. Juhul kui suurus on midagi muud (näiteks 4), siis saab struktuuri definitsioonis nime järele kirjutada attributepacked, mis võiks asja parandada.

Huvitavat: On ka failiformaat ID-ga "P3", mille RGB väärtused on ASCII koodis.

2b. Koosta struktuur PiltRGB, milles on

  • isendiväljad pildi päise parameetrite jaoks ja unikaalne viit nimega m_andmedRGB massiivi jaoks
  • konstruktor (parameetriks faili nimi), mis loeb PPM faili päise isendimuutujatessse ja faili ülejäänud sisu massiivi m_andmed.
    • faili avamiseks sobib std::ifstream fail{faili_nimi, std::ios::binary}, sest tegemist on binaarfailiga. Faili saab sisse lugeda käsuga read, mille üheks parameetriks on reinterpret_cast<char *>(toorviit värvipikslite algusele). Võib eeldada, et fail on alati olemas.
  • funktsioon void salvesta(faili nimi), mis salvestab struktuuri etteantud nimega faili PPM formaadis.
  • funktsioon RGB& piksel(int x, int y) - tagastab viite RGB struktuurile, mis vastab etteantud koordinaatidega pikslile.
  • funktsioon void inverteeri(), mis pöörab pildi värvid ümber, nt kui maksimaalne värvi väärtus on 255, siis (0, 5, 255) -> (255, 250, 0).
  • funktsioon void lisa_kolmandik(), mis lisab kolmandikjooned nii horisontaalselt kui ka vertikaalselt. Näiteks, pilt
 

muutub peale funktsiooni void lisa_kolmandik() rakendamist järgmiseks:

 

Ümardamise erinevuste vältimiseks arvutada kolmandik ujukomaarvudes ja lõpus muuta see täisarvuks, nt int horisontaalne_nihe = ((double)1 / 3) * laius. Kolmandikjoon lisada vastavast äärest, nt parempoolne vertikaaljoon lisada paremast äärest kolmandiku kaugusele ja vasakpoolne vertikaaljoon vasakust äärest kolmandiku kaugusele. Analoogiliselt toimida horisontaaljoontega.

(näidisfailid)

3. Ilmajaam ja sensorid

NB! Loodud funktsioonid lisa vastavalt failidesse ilm.h ja ilm.cpp.

Loo klassid, mille abil saab simuleerida võrguta ilmajaamade ja nende sensorite tööd. Ilmajaamadel on sensorid ja ilmajaamad saavad sensoreid omavahel jagada. Sensoril on nõrgad seosed (viidad) nendega seotud ilmajaamadele ja neid on võimalik võrgust lahti ühendada.

Loo kaks klassi Ilmajaam ja Sensor, mille kõik isendimuutujad on avalikud.

Klass Ilmajaam algab päisega class Ilmajaam : public std::enable_shared_from_this<Ilmajaam> { See on vajalik, et jooksvast objektist (this) saaks luua weak_ptr (funktsiooniga weak_from_this()).

Klassis Ilmajaam on järgmised isendiväljad:

  • m_nimi - sõne ilmajaama nime jaoks
  • m_sensorid - vektor, mille elementideks on jagatud viidad Sensor objektidest

Klassis on konstruktor, mille parameetriks on sõne (ilmajaama nimi). Konstruktor loob uue objekti ja väärtustab isendivälja m_nimi etteantud nimega.

Klassis Sensor on järgmised isendiväljad:

  • m_nimi - sõne sensori nime jaoks
  • m_ilmajaamad - vektor, mille elementideks on nõrgad viidad Ilmajaam objektidest

Klassis on konstruktor, mille parameetriks on sõne (sensori nimi) ja nõrk viit ilmajaamale. Konstruktor loob uue objekti ja väärtustab isendivälja m_nimi ning lisab parameetrina antud ilmajaama vektorisse. Sensorit ei saa luua ilma, et mõni Ilmajaam oleks sellega seotud.

Klassis Ilmajaam on järgmised funktsioonid:

  • weak_ptr<Sensor> lisaSensor(string nimi) , mille parameetriks on sõne (sensori nimi) ja mis tagastab nõrga viida äsjaloodud sensorile. Funktsioon loob jagatud viida uuest sensorist ja lisab selle ilmajaama vektorisse.

NB! Uue sensori loomiseks on vaja nõrka viita jooksvast ilmajaamast (this).

Siin on see koodiosa, millega saab ilmajaamas luua jagatud viita uuele sensorile

std::make_shared<Sensor>(nimi, std::enable_shared_from_this<Ilmajaam>::weak_from_this())  

Funktsioon lisaSensor on funktsioon, millega sensoreid luua!

  • lisaSensorid, mille parameetriks on jagatud viit Ilmajaam objektile ja mis ei tagasta midagi. Funktsioon lisab parameetriks olevalt ilmajaamalt sensorid jooksva (aktiivse) ilmajaama sensorite vektorisse ja vastupidi. Kui lisatav sensor on juba olemas (kontrollida nime järgi), siis sensorit ei lisata. Sensori lisamisel lisatakse ilmajaama nõrk viit ka sensorile endale.
  • int mõõda(string) - funktsioon, mis tagastab etteantud nimega sensori mõõtmise väärtuse (rohkem allpool). Kui sensorit pole, siis tagastatakse int tüübi minimaalne väärtus.
  • void eemalda(Sensor* s), mis eemaldab sensori ilmajaama nimekirjast (kui sensor on seal).

Klassis Sensor on järgmised funktsioonid:

  • void lisaJaam(weak_ptr<Ilmajaam> jaam) - lisab ilmajaama sensori vektorisse.
  • void lahti() - lülitab sensori "välja", st eemaldab sensori tema ilmajaamadest ja tühjendab sensori ilmajaamade vektori (kustutab kõik viidad ilmajaamadele).
  • int mõõda() - tagastab temperatuuri näidu. Selle alamülesande raames piisab, kui tagastusväärtus on 0.

Automaatkontroll ei nõua << ülelaadimist, aga selle olemasolul kasutatakse seda, et printida muutujate infot, kui mõni test põrub.

4. Ilmajaamade ja sensorite simulatsioon

NB! Loodud funktsioonid lisa vastavalt failidesse ilm.h ja ilm.cpp.

Loo lisafunktsioonid, mille abil simuleerida eelmises ülesandes tehtud klasside tööd.

4a. Sensori mõõda funktsiooni täiendamine

Kirjuta funktsioon int random_nr(), mida saab kasutada ülesandes mõõtmistulemuste simuleerimiseks. Funktsioon

  • kasutab generaatoriks mt19937 (static)
  • seemneks on 2025
  • genereerib täisarvu lõigult [16, 26] kasutades C++ random teegi vahendit uniform_int_distribution (samuti static).

Esimesed 5 numbrit võiks olla: 17, 21, 25, 19, 26. Kui funktsioon on loodud, lisada selle kasutamine funktsioonile Sensor::mõõda.

4b. Simulatsiooni loomine

Loo funktsioon ilm_main, mille parameetriks on stringstream objekt ja mis tagastab viimase näidu (täisarvu). Funktsioonis on lokaalsed vektorid ilmajaamade (shared_ptr<Ilmajaam>) ja sensorite (weak_ptr<Sensor>) hoidmiseks ning int tüüpi muutuja viimase näidu jaoks. Edaspidi nimetame neid vastavalt ilmajaamade ja sensorite vektoriteks.

Kasutaja sisend simuleeritakse stringstream objektiga, kuhu salvestatakse järjest täisarvud käskudega ja erinevad argumendid, mida vajatakse käskude täitmisel.

Käskude jaoks defineeri enum Käsud klass. Käsud on järgmised: UUS_JAAM, UUS_SENSOR, MÕÕDA_SENSOR, JAAMADE_ÜHENDAMINE, EEMALDA_SENSOR, EEMALDA_JAAM

Funktsioon täidab järjest stringstream objektis olevaid käske (mugav on kasutada switch lauset). Käske töötle järgmiselt:

UUS_JAAM

Peale käsukoodi on ilmajaama nimi. Loo uus ilmajaama objekt ja lisa see ilmajaamade vektorisse.

UUS_SENSOR

Peale käsukoodi on argumentideks varemloodud ilmajaama indeks ilmajaamade vektoris ja sensori nimi. Loo uus sensori objekt ilmajaamale (kasuta lisaSensor) ja lisa sensor sensorite vektorisse.

MÕÕDA_SENSOR

Peale käsukoodi on argumentideks varemloodud ilmajaama indeks ilmajaamade vektoris ja sensori nimi. Rakenda sensorile funktsiooni mõõda.

JAAMADE_ÜHENDAMINE

Peale käsukoodi on argumentideks esimese ja teise ilmajaama indeksid ilmajaamade vektoris. Ühenda kahe ilmajaama sensorid omavahel kasutades funktsiooni lisaSensorid.

EEMALDA_SENSOR

Peale käsukoodi on argumendiks sensori indeks sensorite vektoris. Kustuta sensor temaga ühendatud ilmajaamadest, tühjenda sensori ilmajaamade vektor ning kustuta sensor sensorite vektorist.

EEMALDA_JAAM

Peale käsukoodi on argumendiks kustutatava ilmajaama indeks ilmajaamade vektoris. Kustuta ilmajaam ilmajaamade vektorist.

Automaatkontrollis ja ise testimiseks saab kasutada järgmisi funktsioone:


void test_ilm_1() {
  std::stringstream ss;
  int code = static_cast<int>(Käsud::UUS_JAAM);
  ss << code << ' ' << "Puhkekodu" << ' ';
  ss << code << ' ' << "Kontor" << ' ';
  code = static_cast<int>(Käsud::UUS_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Sahver" << ' ';
  ss << code << ' ' << 1 << ' ' << "Koosolekuruum" << ' ';
  code = static_cast<int>(Käsud::EEMALDA_SENSOR);
  ss << code << ' ' << 0 << ' ';
  code = static_cast<int>(Käsud::MÕÕDA_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Sahver" << ' ';
  int tulemus = ilm_main(ss);
  //std::println("test_ilm_1: {}", tulemus);
  std::cout << "test_ilm_1: " << tulemus << '\n';
}

void test_ilm_2() {
  std::stringstream ss;
  int code = static_cast<int>(Käsud::UUS_JAAM);
  ss << code << ' ' << "Puhkekodu" << ' ';
  ss << code << ' ' << "Kontor" << ' ';
  code = static_cast<int>(Käsud::UUS_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Sahver" << ' ';
  ss << code << ' ' << 1 << ' ' << "Koosolekuruum" << ' ';
  code = static_cast<int>(Käsud::JAAMADE_ÜHENDAMINE);
  ss << code << ' ' << 0 << ' ' << 1 << ' ';
  code = static_cast<int>(Käsud::MÕÕDA_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Koosolekuruum" << ' ';
  int tulemus = ilm_main(ss);
  //std::println("test_ilm_2: {}", tulemus);
  std::cout << "test_ilm_2: " << tulemus << '\n';
}

void test_ilm_3() {
  std::stringstream ss;
  int code = static_cast<int>(Käsud::UUS_JAAM);
  ss << code << ' ' << "Kasvuhoone_1" << ' ';
  code = static_cast<int>(Käsud::UUS_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Tomatid" << ' ';
  ss << code << ' ' << 0 << ' ' << "Kurgid" << ' ';
  code = static_cast<int>(Käsud::MÕÕDA_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Porgandid" << ' ';
  int tulemus = ilm_main(ss);
  //std::println("test_ilm_3: {}", tulemus);
  std::cout << "test_ilm_3: " << tulemus << '\n';
}

void test_ilm_4() {
  std::stringstream ss;
  int code = static_cast<int>(Käsud::UUS_JAAM);
  ss << code << ' ' << "Kasvuhoone_1" << ' ';
  ss << code << ' ' << "Kasvuhoone_2" << ' ';
  code = static_cast<int>(Käsud::UUS_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Tomatid" << ' ';
  ss << code << ' ' << 1 << ' ' << "Kurgid" << ' ';
  code = static_cast<int>(Käsud::JAAMADE_ÜHENDAMINE);
  ss << code << ' ' << 0 << ' ' << 1 << ' ';
  code = static_cast<int>(Käsud::EEMALDA_JAAM);
  ss << code << ' ' << 0 << ' ';
  code = static_cast<int>(Käsud::MÕÕDA_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Tomatid" << ' ';
  ss << code << ' ' << 0 << ' ' << "Kurgid" << ' ';
  int tulemus = ilm_main(ss);
  //std::println("test_ilm_4: {}", tulemus);
  std::cout << "test_ilm_4: " << tulemus << '\n';
}

void test_ilm_5() {
  std::stringstream ss;
  int code = static_cast<int>(Käsud::UUS_JAAM);
  ss << code << ' ' << "Puhkekodu" << ' ';
  ss << code << ' ' << "Kontor" << ' ';
  code = static_cast<int>(Käsud::UUS_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Sahver" << ' ';
  ss << code << ' ' << 1 << ' ' << "Koosolekuruum" << ' ';
  code = static_cast<int>(Käsud::MÕÕDA_SENSOR);
  ss << code << ' ' << 0 << ' ' << "Sahver" << ' ';
  int tulemus = ilm_main(ss);
  // std::println("test_ilm_5: {}", tulemus);
  std::cout << "test_ilm_5: " << tulemus << '\n';
}

Kui funktsioonid üksteise järel välja kutsuda, võiks programmi väljund olla järgnev:


test_ilm_1: -2147483648

test_ilm_2: 17

test_ilm_3: -2147483648

test_ilm_4: 25

test_ilm_5: 19

Funktsioonide nimede/tüüpide kontroll



template <typename IntT, typename FloatT>
concept arvudKorrektne =
    requires(std::weak_ptr<IntT> i, std::weak_ptr<FloatT> f) {
      { genereeri_arv() } -> std::same_as<std::unique_ptr<IntT>>;
      { proovi_arvu(i) } -> std::same_as<std::string>;
      { proovi_arvu(f) } -> std::same_as<std::string>;
    };

static_assert(arvudKorrektne<int, float>, "Arvude funktsioonid katki");

template <typename PiltT>
concept piltKorrektne = requires(PiltT pilt) {
  { PiltT{"fail"} };
  { pilt.m_andmed } -> std::same_as<std::unique_ptr<struct RGB[]> &>;
  { pilt.piksel(1, 2) } -> std::same_as<struct RGB &>;
  { pilt.salvesta("fail") };
};

static_assert(piltKorrektne<PiltRGB>, "Pildi struktuur katki");

template <typename PiltT>
concept piltModKorrektne = requires(PiltT pilt) {
  { PiltT{"fail"} };
  { pilt.piksel(1, 2) } -> std::same_as<struct RGB &>;
  { pilt.lisa_kolmandik() };
  { pilt.inverteeri() };
};

static_assert(piltModKorrektne<PiltRGB>, "Pildi funktsioonid katki");

template <typename IlmajaamT, typename SensorT>
concept ilmKorrektne =
    requires(IlmajaamT ilmajaam, SensorT sensor,
             std::shared_ptr<IlmajaamT> ilmajaamSharedPtr,
             std::weak_ptr<IlmajaamT> ilmajaamWeakPtr, std::string nimi) {
      { SensorT{nimi, ilmajaamWeakPtr} };
      { sensor.mõõda() };
      { sensor.lahti() };
      { sensor.lisaJaam(ilmajaamWeakPtr) };
      { IlmajaamT{nimi} };
      {
        ilmajaam.m_sensorid
      } -> std::same_as<std::vector<std::shared_ptr<SensorT>> &>;
      { ilmajaam.mõõda(nimi) } -> std::same_as<int>;
      { ilmajaam.lisaSensorid(ilmajaamSharedPtr) };
      { ilmajaam.lisaSensor(nimi) } -> std::same_as<std::weak_ptr<SensorT>>;
      { ilmajaam.eemalda(&sensor) };
    };

static_assert(ilmKorrektne<Ilmajaam, Sensor>, "Ilmajaam või sensor katki");

template <typename IlmajaamT, typename SensorT>
concept ilmMainKorrektne = requires(IlmajaamT ilmajaam, SensorT sensor,
                                    std::weak_ptr<IlmajaamT> ilmajaamPtr,
                                    std::string nimi, std::stringstream ss) {
  { Käsud::UUS_JAAM };
  { Käsud::UUS_SENSOR };
  { Käsud::EEMALDA_SENSOR };
  { Käsud::EEMALDA_JAAM };
  { Käsud::MÕÕDA_SENSOR };
  { Käsud::JAAMADE_ÜHENDAMINE };
  { ilm_main(ss) };
};

static_assert(ilmMainKorrektne<Ilmajaam, Sensor>, "enum või ilm_main katki");

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused