Języki Programowania – Zajęcia 2

Instrukcje warunkowe, pętle, funkcje i tablice

Cel zajęć

Na poprzednich zajęciach poznaliście podstawy pracy z C++: tworzenie plików źródłowych, kompilację programu przy użyciu g++ oraz podstawowe typy danych i operacje na zmiennych.

Dzisiaj poznamy podstawowe konstrukcje programistyczne, które znacie już z Pythona: instrukcje warunkowe, pętle, funkcje oraz tablice. Zobaczymy, jak zapisuje się je w C++ i wykorzystamy je w jednym większym zadaniu.

1. Instrukcja warunkowa if

Instrukcja if pozwala wykonać określony fragment programu tylko wtedy, gdy podany warunek jest spełniony.

Podstawowa składnia wygląda następująco:

if (warunek) {
    // instrukcje wykonywane, gdy warunek jest prawdziwy
}
else {
    // instrukcje wykonywane, gdy warunek jest fałszywy
}

Przykład:

int x = 10;

if (x > 5) {
    cout << "x jest wieksze od 5" << endl;
}
else {
    cout << "x nie jest wieksze od 5" << endl;
}

Możemy również sprawdzać kilka warunków za pomocą else if:

if (x > 10) {
    cout << "x jest wieksze od 10" << endl;
}
else if (x > 5) {
    cout << "x jest wieksze od 5, ale nie od 10" << endl;
}
else {
    cout << "x jest mniejsze lub rowne 5" << endl;
}

Podstawowe operatory porównania:

Operator Znaczenie
==równe
!=różne
>większe
<mniejsze
>=większe lub równe
<=mniejsze lub równe
Uwaga: w C++ pojedynczy znak = oznacza przypisanie, natomiast == oznacza porównanie.

2. Pętla for

Pętla for pozwala wielokrotnie wykonać określony fragment programu.

Podstawowa składnia:

for (inicjalizacja; warunek; zmiana) {
    // instrukcje
}

Przykład:

for (int i = 0; i < 10; i++) {
    cout << i << endl;
}

Program wykona instrukcje dla i = 0, 1, 2, ..., 9. Jest to odpowiednik znanej z Pythona konstrukcji:

for i in range(10):
    print(i)

Warto zwrócić uwagę na trzy części nagłówka pętli:

3. Pętla while

Pętla while wykonuje instrukcje tak długo, jak długo spełniony jest podany warunek.

Składnia:

while (warunek) {
    // instrukcje
}

Przykład:

int i = 0;

while (i < 10) {
    cout << i << endl;
    i++;
}

Operator ++ zwiększa wartość zmiennej o 1:

i++;

jest równoważne:

i = i + 1;

4. Tablice

Tablica pozwala przechowywać wiele wartości tego samego typu. Tablicę o określonym rozmiarze tworzymy na przykład tak:

double measurements[10];

Możemy od razu podać wartości:

double measurements[10] = {
    10.2, 9.8, 10.5, 10.1, 9.9,
    10.3, 11.7, 10.0, 9.6, 10.4
};

Podobnie jak w Pythonie, numerowanie elementów zaczyna się od zera:

measurements[0]   // pierwszy element
measurements[1]   // drugi element
measurements[9]   // dziesiaty element

Elementy tablicy możemy przeglądać za pomocą pętli:

for (int i = 0; i < 10; i++) {
    cout << measurements[i] << endl;
}
Na tych zajęciach używamy klasycznych tablic C++. Nie używamy jeszcze std::vector. Do tematu wektorów wrócimy na kolejnych zajęciach.

5. Funkcje

Funkcja pozwala wydzielić fragment programu wykonujący określone zadanie. Funkcję możemy następnie wielokrotnie wywoływać z różnych miejsc programu.

Ogólna składnia funkcji:

typ_zwracany nazwa_funkcji(parametry) {
    // instrukcje
    return wartosc;
}

Na przykład:

double square(double x) {
    return x * x;
}

Funkcję możemy następnie wywołać:

double result = square(3.0);

cout << result << endl;

Funkcja może również niczego nie zwracać. W takim przypadku używamy typu void:

void printHello() {
    cout << "Hello!" << endl;
}

5.1. Funkcja przyjmująca tablicę

Do funkcji możemy przekazać również tablicę. Przykładowo funkcja która zwiększa wszystkie elementy tablicy o 1 może wyglądać następująco:


void addCalibrationOffset(double measurements[], int length) {
    for (int i = 0; i < length; ++i) {
        measurements[i] += 1;
    }
}

Funkcję możemy wywołać dla naszej tablicy:

addCalibrationOffset(measurements, 10);
Ważne: przy przekazywaniu klasycznej tablicy do funkcji podajemy również jej rozmiar. Sama tablica nie przekazuje funkcji informacji o liczbie przechowywanych elementów.

6. Ćwiczenie – krótkie przykłady

Ćwiczenie 1: instrukcja warunkowa

Napiszcie program, który pobiera od użytkownika liczbę całkowitą i sprawdza, czy liczba jest dodatnia, ujemna czy równa zero.

Program powinien wykorzystać if, else if i else.

Ćwiczenie 2: pętla

Napiszcie program, który wypisuje liczby od 1 do 10 oraz ich kwadraty. Wykorzystajcie pętlę for.

Przykładowy fragment wyniku:

1  1
2  4
3  9
...
10  100

Ćwiczenie 3: funkcja

Napiszcie funkcję:

double suma(double x, double y)

która zwraca sumę dwóch liczb. Wywołajcie ją dla kilku wartości i wypiszcie wyniki.

7. Zadanie – analiza wyników pomiarów

Zadanie 1

Napiszcie program w C++, który analizuje wyniki 10 pomiarów pewnej wielkości fizycznej.

Wykorzystajcie następującą tablicę:

double measurements[10] = {
    10.2, 9.8, 10.5, 10.1, 9.9,
    10.3, 11.7, 10.0, 9.6, 10.4
};

Program powinien:

  1. wypisać wszystkie wyniki pomiarów na ekran,
  2. obliczyć i wypisać wartość średnią,
  3. znaleźć i wypisać wartość minimalną,
  4. znaleźć i wypisać wartość maksymalną,
  5. policzyć, ile pomiarów jest mniejszych od średniej,
  6. policzyć, ile pomiarów jest większych od średniej,
  7. wypisać wszystkie pomiary, które różnią się od średniej o więcej niż 10%.

Obliczenia powinny zostać wykonane z wykorzystaniem funkcji. Program powinien zawierać co najmniej następujące funkcje:

double average(double data[], int size);

double minimum(double data[], int size);

double maximum(double data[], int size);

int countAbove(double data[], int size, double value);

Możecie oczywiście utworzyć również dodatkowe funkcje, jeśli ułatwi Wam to rozwiązanie zadania.

Nie używajcie jeszcze std::vector. Nie korzystajcie również z gotowych funkcji STL do obliczania minimum, maksimum lub średniej. Celem zadania jest przećwiczenie tablic, pętli, instrukcji warunkowych i funkcji.

8. Przykładowy wynik programu

Pomiary:
10.2 9.8 10.5 10.1 9.9 10.3 11.7 10.0 9.6 10.4

Srednia: 10.25
Minimum: 9.6
Maksimum: 11.7

Pomiary mniejsze od sredniej: 6
Pomiary wieksze od sredniej: 4

Pomiary rozniace sie od sredniej o wiecej niz 10%: 1 // ten pomiar to 11.7

9. Schemat pracy

1. Utwórz / otwórz katalog projektu
              |
              v
2. Utwórz plik .cpp
              |
              v
3. Napisz / zmodyfikuj kod (niewielkie fragmenty kodu, najlepiej po 1 funkcji na raz!)
              |
              v
4. Skompiluj:
   g++ program.cpp -o program
              |
              v
5. Jeżeli kompilacja się udała:
   uruchom program
              |
              v
6. Sprawdź działanie
              |
              v
7. Wprowadź poprawki i wróć do punktu 4
Na kolejnych zajęciach będziemy rozwijać poznane konstrukcje i wprowadzać kolejne elementy języka C++. Szczególnie ważne będzie łączenie ich w większe, samodzielne programy.

10. Kompilacja i uruchomienie

Program zapiszcie w pliku main.cpp.

Otwórzcie terminal w VS Code: Terminal → New Terminal.

Jeżeli terminal znajduje się w katalogu zawierającym main.cpp, wykonajcie:

g++ main.cpp -o main

Jeżeli kompilacja zakończyła się poprawnie, uruchomcie program:

./main

Jeżeli korzystacie z Windowsa i program został utworzony jako main.exe, może być konieczne użycie:

.\main.exe
Przypomnienie:
g++ main.cpp -o main
kompiluje program, natomiast
./main
uruchamia program wynikowy.

11. Co zrobić, gdy kompilator zgłosi błąd?

Podczas pisania programu bardzo często pojawią się błędy składniowe lub logiczne. Jest to normalna część programowania.

Przykładowo, usuńcie średnik z jednej z instrukcji:

double x = 10
cout << x << endl;

Spróbujcie skompilować program i zobaczcie komunikat kompilatora. Następnie poprawcie kod i ponownie wykonajcie:

g++ main.cpp -o main
Pamiętajcie: po każdej zmianie kodu źródłowego trzeba ponownie skompilować program, aby zmiana znalazła się w programie wykonywalnym.

12. Czym jest std::vector?

Klasyczne tablice w C++ (np. double measurements[10]) mają stały rozmiar, ustalany w kodzie. Przekazując je do funkcji, zwykle podajemy również ich rozmiar, ponieważ sama tablica nie udostępnia go funkcji jako osobnej informacji.

std::vector to dynamiczna tablica dostępna w bibliotece standardowej C++. Aby z niej korzystać, należy dołączyć nagłówek:

#include <vector>
Najważniejsze właściwości std::vector:

Tworzenie wektora i dostęp do elementów

Wektor można utworzyć na kilka sposobów:

std::vector<double> a;
std::vector<double> b = {1.2, 3.4, 5.6};
std::vector<double> c(5, 0.0);

Do elementów odwołujemy się za pomocą indeksów:

std::cout << b[0] << std::endl;  // 1.2
std::cout << b[2] << std::endl;  // 5.6

b[1] = 4.0;  // zmiana drugiego elementu
Uwaga: dla wektora zawierającego N elementów poprawne indeksy to 0, 1, ..., N-1. Odwołanie do indeksu spoza tego zakresu prowadzi do błędu logicznego i może powodować niezdefiniowane zachowanie.

Rozmiar i dodawanie elementów

Metoda .size() zwraca liczbę elementów. Metoda .push_back() dodaje nowy element na końcu:

std::vector<double> values = {2.0, 4.0, 6.0};
std::cout << values.size() << std::endl;  // 3

values.push_back(8.0);
std::cout << values.size() << std::endl;  // 4

Po dodaniu elementu rozmiar wektora zwiększa się automatycznie. Nie trzeba wcześniej rezerwować konkretnej liczby elementów ani ręcznie aktualizować zmiennej z rozmiarem.

Przeglądanie elementów w pętli

Jeśli potrzebujemy indeksu elementu, możemy użyć klasycznej pętli for:

for (int i = 0; i < values.size(); i++) {
    std::cout << values[i] << std::endl;
}

Jeśli chcemy jedynie odczytać kolejne wartości, możemy użyć pętli zakresowej:

for (double value : values) {
    std::cout << value << std::endl;
}

Pętla zakresowa przechodzi po wszystkich elementach wektora. Zmienna value przyjmuje kolejno wartość każdego elementu.

Przekazywanie wektora do funkcji

Wektor można przekazać do funkcji przez stałą referencję. Dzięki temu nie tworzymy kopii całego wektora, a funkcja nie może modyfikować jego elementów:

double average(const std::vector<double>& data) {
    if (data.empty()) {
        return 0.0;
    }

    double sum = 0.0;
    for (double value : data) {
        sum += value;
    }

    return sum / data.size();
}

Funkcję można wywołać dla wektorów o różnej liczbie elementów. W powyższym przykładzie zwrócenie 0.0 dla pustego wektora jest przyjętą konwencją na potrzeby ćwiczenia; w większych programach sposób obsługi pustych danych należy świadomie zaprojektować.

Zadanie 2 – analiza pomiarów z wykorzystaniem std::vector

Napisz program, który wykonuje analizę wyników podobną do tej z Zadania 1, ale zamiast klasycznej tablicy wykorzystuje std::vector<double>.

Krok 1. Przygotowanie danych

Utwórz wektor zawierający następujące wartości pierwotnie przechowywane w tablicy measurements.

Wypisz liczbę pomiarów (długość vectora), a następnie wypisz wszystkie wartości w pętli.

Krok 2. Dodawanie pomiarów

Dodaj do wektora dwa nowe pomiary. Sprawdź, czy rozmiar wektora zmienił się zgodnie z oczekiwaniami, i wypisz zaktualizowaną listę pomiarów.

Krok 3. Funkcje analizujące dane

Przygotuj funkcje obliczające średnią, minimum i maksimum. Zastosuj następujące deklaracje:

double average(const std::vector<double>& data);
double minimum(const std::vector<double>& data);
double maximum(const std::vector<double>& data);

Funkcje powinny działać dla wektorów o różnej liczbie elementów. Zastanów się, jak obsłużyć pusty wektor.

Krok 4. Analiza wyników

Korzystając z napisanych funkcji i pętli, oblicz:

  1. liczbę pomiarów mniejszych od średniej;
  2. liczbę pomiarów większych od średniej;
  3. wszystkie pomiary różniące się od średniej o więcej niż 10%.

Krok 5. Sprawdzenie rozwiązania

Uruchom program dla danych początkowych, a następnie dla danych uzupełnionych o dwa dodatkowe pomiary. Sprawdź, czy wszystkie obliczenia uwzględniają nowe elementy.

Wymagania: wykorzystaj .size(), .push_back() oraz przekazywanie wektora do funkcji. Nie zakładaj na sztywno, że wektor zawsze zawiera dziesięć elementów. Możesz samodzielnie zaimplementować algorytmy albo skorzystać z funkcji biblioteki standardowej, jeśli potrafisz wyjaśnić ich działanie.

15. Najważniejsze rzeczy z dzisiejszych zajęć

16. Punktacja – 0–5 pkt

Punkty Kryterium
0.5 pkt Ćwiczenia: Wykonanie trzech ćwiczeń wprowadzających.
0.5 pkt Zadanie 1.1: Poprawne utworzenie i wykorzystanie tablicy oraz wypisanie jej elementów.
1.5 pkt Zadanie 1.2-4: obliczyć i wypisać wartość średnią, minimalną, maksymlaną (0.5 pkt każde).
1 pkt Zadanie 1.5-6: policzyć, ile pomiarów jest mniejszych/większych od średniej (0.5 pkt każde).
0.5 pkt Zadanie 1.7: wypisać wszystkie pomiary, które różnią się od średniej o więcej niż 10%.
1.0 pkt Zadanie 2: Powtórzenie zadania przy użyciu vector.