====== Wprowadzenie do programowania w C++ ======
===== IDE =====
Na zajęciach kod programów będzie pisany przy użyciu CLion IDE. IDE oprócz dostarczania podstawowego kolorowania składni i możliwości uruchamiania kodu,
dostarcza szereg przydatnych narzędzi, które znacznie potrafią przyspieszyć pisanie kodu. Są to między innymi:
* podpowiadanie składni (CTRL+SPACE)
* zautomatyzowanie nawigacji po projekcie (CTRL+B, CTRL+SHIFT+BACKSPACE, CTRL+ALT+HOME, CTRL+N, itd...)
* [[https://www.jetbrains.com/help/clion/2016.3/adding-deleting-and-moving-code-elements.html|łatwiejsze zarządzanie liniami kodu]] (TAB, SHIFT+TAB, CTRL+/, ALT+D, CTRL+Z, CTRL+Y, CTRL+D, CTRL+DEL, CTRL+BACKSPACE itd...)
* automatyczna generacja części kodu (ALT+INSERT, CTRL+SHIFT+D, ...)
* narzędzie do automatycznego formatowania kodu (CTRL+ALT+L)
* integracja z debuggerem
* integracja z systemem kontroli wersji git,
* integracja z narzędziem do automatycznego budowania kodu CMake
* integracja z frameworkiem do testów jednostkowych Google Test
* narzędzia do refaktoryzacji kodu, czyli poprawiania czytelności i jakości kodu bez modyfikacji jego zachowania
* i wiele innych
W celu zmaksymalizowania produktywności należy zainstalować plugin [[https://plugins.jetbrains.com/clion/plugin/4455-key-promoter|Key promoter]] i wydrukować sobie [[https://resources.jetbrains.com/assets/products/clion/CLion_ReferenceCard.pdf|Clion Reference Card]].
Również należy zainstalować sobie wersję CLiona na domowym sprzęcie należy skorzystać z darmowej [[https://www.jetbrains.com/student/|licencji studenckiej]].
===== CMake =====
Na zajęciach będziemy korzystać z systemu budującego CMake. W celu ułatwienia pracy
należy zapoznać się z elementami znajdującymi się w dokumencie [[https://www.jetbrains.com/help/clion/2016.3/quick-cmake-tutorial.html|CMake]],
w szczególności,
* jak ma nazywać się plik ze specyfikacją projektu w CMake :?:
* jak zdefiniować zmienną przechowującą nazwy plików cpp (z kodem źródłowym)
* jak dodać nowy cel (target) w postaci pliku wykonywalnego :?:
* jak dodać nowy cel (target) w postaci biblioteki :?:
* jak zdefiniować żeby stworzona wyżej biblioteka eksportowała pliki h (nagłówkowe) :?:
* jak w hierarchii projektu dołączyć podprojekt (podkatalog) z innym plikiem CMakeLists.txt :?:
* jak dołączyć bibliotekę do celu w postaci innego pliku wykonywalnego lub biblioteki :?:
Zagadnienia te będą obowiązywać na kolokwium :!:
===== Git i automatyczne sprawdzanie zadań =====
Na zajęciach będziemy wykorzystywali system kontroli wersji git.
Pod [[https://gitlab.com/agh-jimp2/exercises|linkiem]] znajduje się adres repozytorium, gdzie zdefiniowane są testy do zadań
do laboratoriów. Każde laboratorium ma swój osobny katalog np. lab1, lab2, itd... w każdym laboratorium znajdują się podfoldery
dla każdego zadania, przykładowy factorial do pierwszego zadania z silnią już znajduje się repozytorium. Należy sklonować repozytorium
i zaimportować projekt do CLiona.
Należy zapoznać się z plikami CMakeLists.txt w folderze lab1 i lab1/factorial. Polecenie **add_subdirectory_if_exists(reversestring)** nie jest
wbudowane w CMake, ale jest zdefiniowane w tym projekcie jako wariant **add_subdirectory**, ale nie powodujący błędu jeśli podkatalog
nie istnieje. Żeby rozwiązywać kolejne zadania należy nawiązać do konwencji nazwniczej użytej w projekcie tzn. kolejne foldery muszą się
nazywać tak samo jak to jest przedstawione w kolejnych deklaracjach add_subdirectory_if_exists. W przeciwnym wypadku podprojekt nie
będzie widoczny :!:
W celu sklonowania repozytorium do lokalnego folderu wystarczy wpisać w linii komed polecenie (jako opcjonalny można podać ostatni argument
z nazwą folderu do którego ma zostać skolnowany projekt
git clone https://gitlab.com/agh-jimp2/exercises.git
Należy sobie odświeżyć wiedzę na temat gita, w szczególności:
* git diff,
* git commit,
* git status,
* git add,
* git push,
* git pull,
* git log,
* git checkout
Dostępny projekt jest wyposażony w automatyczne testy jednostkowe weryfikujące poprawność napisanego kodu. Same testy zostaną omówione pod koniec laboratoriów,
jednak teraz wystarczy wiedzieć, że są to mini programiki, których celem jest uruchomienie małego fragmentu kodu i sprawdzenie, czy zachowuje się on zgodnie z naszymi oczekiwaniami
i zaraportowanie użytkownikowi sytuacji gdy jest przeciwnie.
Projekt został tak pomyślany, że każde zadania ma swoje osobne testy. Ogólna struktura kodu wygląda tak, że zdefiniowana jest biblioteka z kodem rozwiązania, która jest linkowana zarówno
do programu zdefiniowanego przez Ciebie, jak i mini programiku z testami. Stąd po naciśnięciu CTRL+F9 (Build) zostanie zbudowany projekt. Następnie można nacisnąć kombinację CTRL+ALT+F10
żeby zobaczyć listę dostępnych celów (targets) zdefiniowanych w całym projekcie. Wśród nich powinny znajdować się następujące cele:
* factorial
* libfactorial
* lab1_factorial_tests
* lab1_all_tests
* i wiele innych
Cele zdefiniowane ze słowem tests powinny znajdować się w niższej sekcji i posiadać ikonę z czerwonym elementem. Są to zadania, które zostały rozpoznane przez IDE jako testy.
Wybranie zadania factorial uruchomi program z funkcją main zdefiniowaną w **lab1/factorial/main.cpp**. Natomiast uruchomienie zadania lab1_factorial_tests uruchomi testy jednostkowe
dotyczące zadania z silnią. Zadanie lab1_all_tests uruchamia wszystkie testy zdefiniowane dla laboratorium numer 1. W tym momencie jednak to zadanie nie powinno się dać skompilować
ponieważ brakuje kodu realizującego kolejne zadania. Uruchomienie libfactorial nie jest możliwe, dlatego, że jest to bilblioteka z kodem i nie ma punktu wejścia z funkcją main tak jak cały program.
Uruchomienie zadania lab1_factorial_tests powinno zakończyć się jednak częściowym powodzeniem, tzn. część testów powinno przejść, a część nie.
{{ unit-test.png?600 |}}
Uzyskujemy informację, że z 16 testów wykonanych 12 zawiodło. Testy zostały zorganizowane w dwie grupy factorial_test i FactorialDataDriveTests, każdej z tych grup można się przyglądnąć osobno.
Wynik testu informuje nas, że na przykład uruchomienie factorial(5) powinno dać nam w rezultacie 120, ale otrzymany wynik to 0 :!:
W innym wypadku napisane jest, że oczekiwano p.second równego 1, ale uzyskany wynik factorial(p.first) był również równy 0 :!:
Jeśli przeniesiemy się do definicji funkcji factorial (CTRL+SHIFT+ALT+N i wpisać początek nazwy factorial), a następnie naciśniemy (CTRL+B) by przeskoczyć do definicji funkcji wszystko będzie
jasne defnicja funkcji factorial jest następująca:
int factorial(int value) {
return 0;
}
Nic dziwnego, że zawsze zwraca 0.
===== Typy =====
Lista typów wbudowanych w standard C%%++%%
^Nazwa^Opis^
|char |Znak, albo mała liczba całkowita |
|short int \\ (short) |Mała liczba całkowita |
|int |Liczba całkowita |
|long int \\ (long)|Długa liczba całkowita |
|bool |Wartość **true** albo **false** |
|float | Liczba zmiennoprzecinkowa |
|double |Liczba zmiennoprzecinkowa podwójnej precyzji |
|long double |Duża liczba zmiennoprzecinkowa podwójnej precyzji |
|void | Pusty typ danych |
Typy //char, short, int, long int// posiadają warianty **signed** i **unsigned**.
Standard nie gwarantuje rozmiaru typu i rozmiary podstawowych typów zmieniają
się w zależności dla jakiej architekturze jest kompilowany kod.
Jeśli stawiamy pewne wymagania, np. chcemy przynajmniej 8 bajtową liczbę typu integer lepiej
skorzystać z typów zdefniowane w [[http://en.cppreference.com/w/cpp/header/cstdint|cstdint]].
Oprócz typów prymitywnych w C%%++%% można zdefiniować znacznie bogatszy system typów niż w C
w oparciu o klasy. Temat ten będzie zgłębiony na dalszych laboratoriach jednak w ćwiczeniach przyda się
znajomość klasy [[http://en.cppreference.com/w/cpp/string/basic_string|std::string]], przyglądnąć się jak
w dokumentacji przedstawione są metody znajdujące się w klasie string (własciwie to basic_string), np.
* c_str()
* length()/size()
* substr()
* find_first_of()
* replace()
===== Instrukcje sterujące =====
W C%%++%% dostępne są następujące instrukcje sterujace:
* instrukcja warunkowa **if ... else ... **
* instrukcja warunkowa **switch ... case**
* pętla **while**
* pętla **do ... while**
* pętla **for**
* **break** - przerywa aktualną pętlę
* **continue** - kontynuuje pętlę przechodząc do następnej iteracji
* **return**
===== Funkcje =====
- Główną funkcją każdego programu jest funkcja //main()//.
* musi ona zwracać typ //int//
* może przyjmować dwa parametry: //int argc, char* argv[]//, oznaczające odpowiednio liczbę parametrów przekazanych do programu i listę tych parametrów. Zerowym parametrem jest zawsze nazwa programu.
- Pisanie funkcji można rozdzielić na dwie fazy:
* opcjonalna deklarację (tworzenie tzw. prototypu funkcji): double potega(double);
* definicję:
double potega(double liczba){
return liczba*liczba;
}
- Domyślnym typem zwracanym (jeśli nie określono) jest //int//.
- Brak określenia typu zwracanego przez funkcję w jej definicji jest błędem składniowym jeśli prototyp określa typ zwracany inny niż int
- Zapomnienie o zwróceniu wartości z funkcji, która zadeklarowana została jako zwracająca wartość, jest błędem składniowym.
- Funkcje rozróżniane są po nazwie i liście parametrów, a nie po zwracanym typie. Dlatego nie można zdefiniować dwóch funkcji o takich samych nazwach i liście parametrów a innych zwracanych typach:// NIEPOPRAWNIE
void foo(int a, int b){...}
double foo(int c, int d){...}
===== Tablice =====
==== Tablice jednowymiarowe ====
Tablice jednowymiarowe w C%%++%% można deklarować na cztery sposoby:
- Deklarując jedynie rozmiar tablicy, bez podawania jej elementów: int tab[100];
- Deklarując rozmiar i podając elementy tablicy:int tab[5] = {1,2,3,4,5};
- Deklarując rozmiar, ale nie podając wartości wszystkich elementów: int tab[10] = {1,2,3,4,5};
- Podając listę elementów a nie podając rozmiaru tablicy:int tab[] = {1,2,3,4,5};
====Tablice wielowymiarowe====
Tablice wielowymiarowe deklaruje się analogicznie do jednowymiarowych, jednakże podczas deklaracji tablicy wielowymiarowej konieczne jest podanie rozmiarów wymiarów, poza pierwszym:
//Poprawnie
int tab[][4] = {{1,2,3,4},{5,6,7,8}};
//NIEPOPRAWNIE
int tab[][] = {{1,2,3,4},{5,6,7,8}};
Definiując funkcję, której parametrem jest tablica wielowymiarowa, również konieczne jest podanie wszystkich rozmiarów wymiarów, poza pierwszym:
void foo(int tab[][10]){
...
}
===== Prosty program =====
==== Przykład ====
Program ma za zadanie wczytanie imienia i wyświetlenie powitania na ekranie.
// W pliku o nazwie program.cpp
#include // Dołączamy bibliotekę odpowiedzialna za
// operacje wejścia i wyjścia
using namespace std;
int main(int argv, char* arg[]){
char imie[20];
cout << "Podaj swoje imie: ";
cin >> imie;
cout << "Witaj " << imie << endl;
return 0;
}
==== Jak kompilujemy ====
W konsoli g++ program.cpp -o program
==== Omówienie ====
Warto zauważyć:
* Nie jest wymagane podanie rozszerzenia biblioteki //iostream//.
* Biblioteka //iostream// jest ekwiwalentem znanej z C biblioteki //stdio//. C%%++%% wprowadza //strumienie// za pomocą których odbywają się wszystkie operacje wejścia/wyjścia.
* Przekierowanie danych do/ze strumienia możliwe jest dzięki operatorom //</ oraz //>>//. Obiektem strumienia odpowiedzialnym za wyświetlanie na ekranie jest //cout// natomiast //cin// jest to strumień wejściowy.
* **using namespace std** określa, że korzystamy z przestrzeni nazw **std** z biblioteki //iostream//.
====== Ćwiczenia ======
- Skompiluj i uruchom przykład z sekcji [[#prosty_program_w_c|Prosty program w C++]]
- [1 plus] Napisz program obliczający silnię. Silnia powinna być obliczana przez funkcję int factorial(int);
Zdefiniowaną w katalogu factorial. Wykonaj dwie wersje funkcji:
* rekurencyjną
* iteracyjną
- [2 plusy] Wyświetlanie napisu wspak. Napisz funkcję która jako parametr będzie przyjmowała tablicę znaków i wyświetlała ją wspak. Funkcja powinna być rekurencyjna! Podpowiedź: przerwij rekurencję po napotkaniu zerowego znaku końcowego //\0//.
* Moduł: **reversestring**
* Pliki z implementacją: **ReverseString.h/cpp**
* Sygnatura metody: std::string reverse(std::string str)
* Pliki nagłówkowe: #include
* Wskazówki: const char *characters = str.c_str(); //uzyskanie z obiektu string wskaźnika na poszczególne znaki
size_t size = str.size(); //uzyskanie z obiektu string ilości znaków
//utworzenie nowego obiektu string na podstawie innego char*, char[], itp..
return std::string(reversed_characters)
* uruchomienie testów: ALT+SHIFT+F10 -> lab1_reverse_string_tests
- **[2 punkty]** Napisz funkcję palindrom, sprawdzającą czy podany jako parametr napis jest palindromem. Funkcja powinna zwracać **//true//** gdy napis jest palindromem, a **//false//** gdy nie jest. \\ Napisz proste menu posiadające dwie opcje: //Wyjście// i //Sprawdź palindrom//. Po wybraniu //Sprawdź palindrom// program powinien poprosić o wpisanie słowa a następnie sprawdzić i wyświetlić na ekranie czy podane słowo jest palindromem. Po wybraniu //Wyjście// program powinien kończyć działanie.
* Moduł: **palindrome**
* Pliki z implementacją: **Palindrome.h/cpp**
* Sygnatura metody: bool is_palindrome(std::string str);
* Pliki nagłówkowe: #include
* uruchomienie testów: ALT+SHIFT+F10 -> lab1_palindrome_tests
- [2 plusy] Napisz funkcję która będzie przyjmować tablicę dwuwymiarową o rozmiarach 10 na 10 i będzie uzupełniać ją iloczynami wierszy i kolumn, tworząc //tabliczkę mnożenia//. Napisz osobną funkcję do wyświetlania tej tablicy.
* Moduł: **multiplicationtable**
* Pliki z implementacją: **MultiplicationTable.h/cpp**
* Sygnatura metody: void MultiplicationTable(int tab[][10]);
* uruchomienie testów: ALT+SHIFT+F10 -> lab1_multiplication_table_tests
- **[3 punkty]** [[http://projecteuler.net/index.php?section=problems&id=36|Palindromy liczbowe]]
* Moduł: **doublebasepalindromes**
* Pliki z implementacją: **DoubleBasePalindromes.h/cpp**
* Sygnatura metody: uint64_t DoubleBasePalindromes(int max_vaule_exculsive);
* Pliki nagłówkowe: #include
* uruchomienie testów: ALT+SHIFT+F10 -> lab1_double_base_palindrome_tests