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…)
  • ł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 Key promoter i wydrukować sobie Clion Reference Card. Również należy zainstalować sobie wersję CLiona na domowym sprzęcie należy skorzystać z darmowej 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 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 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. 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++

NazwaOpis
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 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 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

  1. 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.
  2. 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;
      }
  3. Domyślnym typem zwracanym (jeśli nie określono) jest int.
  4. 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
  5. Zapomnienie o zwróceniu wartości z funkcji, która zadeklarowana została jako zwracająca wartość, jest błędem składniowym.
  6. 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:

  1. Deklarując jedynie rozmiar tablicy, bez podawania jej elementów:
    int tab[100];
  2. Deklarując rozmiar i podając elementy tablicy:
    int tab[5] = {1,2,3,4,5};
  3. Deklarując rozmiar, ale nie podając wartości wszystkich elementów:
    int tab[10] = {1,2,3,4,5};
  4. 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 <iostream> // 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

  1. Skompiluj i uruchom przykład z sekcji Prosty program w C++
  2. [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ą
  3. [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 <string>
    • 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
  4. [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 <string>
    • uruchomienie testów: ALT+SHIFT+F10 → lab1_palindrome_tests
  5. [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
    • Moduł: doublebasepalindromes
    • Pliki z implementacją: DoubleBasePalindromes.h/cpp
    • Sygnatura metody:
      uint64_t DoubleBasePalindromes(int max_vaule_exculsive);
    • Pliki nagłówkowe:
      #include <cstdint>
    • uruchomienie testów: ALT+SHIFT+F10 → lab1_double_base_palindrome_tests
pl/dydaktyka/jimp2/2017/labs/wprowadzenie.txt · ostatnio zmienione: 2017/07/17 08:08 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0