====== 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 //<>//. 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