Podręcznik
Strona: | SEZAM - System Edukacyjnych Zasobów Akademickich i Multimedialnych |
Kurs: | 4. Złożone struktury danych |
Książka: | Podręcznik |
Wydrukowane przez użytkownika: | Gość |
Data: | sobota, 23 listopada 2024, 12:56 |
1. Tablice jednowymiarowe
W tym rozdziale zostaną omówione typy tablicowe. Rozpoczniemy od przedstawienia tablic jednowymiarowych. Tablice jednowymiarowe w języku C++ to struktury danych, które umożliwiają przechowywanie wielu elementów tego samego typu pod jednym wspólnym identyfikatorem. Każdy element w tablicy jest indeksowany, co pozwala na dostęp do konkretnego elementu na podstawie jego położenia.Wielkość pamięci potrzebnej na zapamiętanie tablicy jest określana przez kompilator jako proste przemnożenie ilości elementów w tablicy przez rozmiar pojedynczego elementu. Oczywiście tablica może zawierać elementy dowolnego typu prostego.
typ_elementów nazwa_zmiennej [rozmiar_tablicy];
rozmiar_tablicy musi być dodatnią liczbą, której wartość da się ustalić na etapie kompilacji, a więc musi być wyrażeniem typu całkowitego zawierającym stałe zdefiniowane wcześniej, operatory arytmetyczne i nawiasy. Najczęściej używa się tu liczb całkowitych lub stałych całkowitych.
int tablica[5]; // Deklaracja tablicy na 5 elementów typu int
int tablica[5] = {1,2,3,4,5}
cont int n = 5;
int A[n] = {0};
int n;
cin >> n;
double A[n];
Tablice o zmiennym rozmiarze (ang. VLAs — Variable Length Arrays) są obsługiwane w niektórych innych językach, takich jak C (od standardu C99), ale nie są częścią standardu C++. Niektóre kompilatory (np. GCC) mogą je wspierać jako rozszerzenie specyficzne dla kompilatora, ale nie jest to zgodne ze standardem. Używanie tego rodzaju rozszerzeń może prowadzić do kodu, który nie jest przenośny między różnymi kompilatorami i środowiskami.
0
, natomiast kończą się na wartości n-1
(gdzie n
to rozmiar tablicy). A więc w przypadku deklaracji int a[5]; ostatnim elementem tablicy jest element o indeksie 4, czyli a[4]. Zobaczcie to na rysunku:W przypadku przekroczenia zakresu indeksu, czyli odwołania się do nieistniejącego elementu tablicy, wystąpi błąd w trakcie wykonywania programu. Przekroczenie zakresu indeksu tablicy jest często spotykanym, a przy tym wyjątkowo trudnym do zlokalizowania błędem w programach pisanych w C++.
Poniżej przedstawiono przykład odwołania do pierwszego oraz trzeciego elementu tablicy.
int pierwszyElement = tablica[0]; // Dostęp do pierwszego elementu.
int trzeciElement = tablica[2]; // Dostęp do trzeciego elementu.
tablica[0] = 10; // Zmieniamy wartość pierwszego elementu na 10.
tablica[1] = 20; // Zmieniamy wartość drugiego elementu na 20.
const int n = 5;
int A[n]={0};
// wczytywanie danych do tablicy
for (int i = 0; i < n; i++)
cin >> A[i];
// drukowanie zawartości tablicy
for (int i = 0; i < n; i++)
cout << A[i] << endl;
1.1. Napisy
Do znaków napisu odwołujemy się jak do elementów tablicy jednowymiarowej, poprzez indeksy, zaczynając od 0. Przy tym rozmiar napisu, czyli liczbę jego znaków można wyznaczyć za pomocą funkcji size(). Funkcja ta nie ma parametrów (wnętrze nawiasów jest puste) i zapisujemy ją w tzw. notacji kropkowej.
Jeśli napis jest zmienną typu string, to jego długość (liczba znaków) jest równa n = napis.size(), pierwszy znak to napis[0], zaś ostatni znak to napis[n-1] lub inaczej: napis[napis.size()-1].
string napis = "Napis"; // początkowa wartość napisu
cout << napis[0]; // drukuje pierwszy znak napisu, czyli literę N
cout << napis [imie.size()-1]; // drukuje ostatni znak napisu, czyli literę s
cout << napis + ".cpp " // drukuje napis Napis.cpp
2. Tablice wielowymiarowe
W języku C++ możliwa jest także implementacja tablic wielowymiarowych. Tablice wielowymiarowe statyczne w języku C++ to struktury danych pozwalające przechowywać elementy tego samego typu w wielu wymiarach. Możliwe jest tworzenie tablic o w zasadzie dowolnym zagnieżdżeniu.Ogólna postać definicji tablic wielowymiarowych jest następująca:
typ_elementów nazwa_zmiennej [rozmiar 1] [rozmiar 2]...[rozmiar n];
gdzie rozmiar 1, rozmiar 2, ... , rozmiar n - są odpowiednimi zakresami indeksów dla poszczególnych wymiarów. Czyli, jak wspomnieliśmy - nic nowego.
int tab[4][5][8];
const int w=3, k=4;
int tab[w][k]={2,3,8,6,4,2,9,0,7,1,12,5};
const int w=3, k=4;
int tab[w][k]={0};
Poniżej przedstawiono nagranie demonstrujące deklarację tablicy dwuwymiarowej, wypełnianie jej przykładowymi danymi oraz drukowanie zawartości tablicy na ekranie.
Możemy wyróżnić elementy znajdujące się na głównej przekątnej.
oraz elementy znajdujące się na drugiej przekątnej.
Poniżej przedstawiono przykładową aplikację, która umożliwia wydrukowanie na ekranie wartości z głównej przekątnej tablicy oraz z drugiej przekątnej.
Wartość możemy wydrukować przy pomocy pojedynczej pętli for. Nie ma konieczności wykorzystywania dwóch pętli. Jeśli mamy tablicę tab[n][n] i chcemy wydrukować elementy jej głównej przekątnej, robimy to tak:
for (int i = 0; i < n; i++)
cout << a[i][i] << "\t";
for (int i = 0; i < n; i++)
cout << a[i][n-1-i] << "\t";
- elementy leżące nad główną przekątną,
- elementy leżące pod główną przekątną,
- elementy leżące pod drugą przekątną,
- elementy leżące nad drugą przekątną,
- elementy leżące nad obiema przekątnymi,
- elementy leżące pod obiema przekątnymi,
- elementy leżące na prawo od obu przekątnych,
- elementy leżące na lewo od obu przekątnych,
- elementy leżące na obwodzie tablicy oraz poza obwodem.
To te najbardziej charakterystyczne. Zapewne jest dużo więcej możliwości. W kolejnym przykładzie zademonstrujemy w jaki sposób "przechodzić" po tych charakterystycznych elementach. Przykład demonstruje w jaki sposób wydrukować elementy znajdujące się nad główną przekątną.
2.1. Generowanie liczb losowych
We wszystkich pokazywanych do tej pory przykładach tablice były wypełniane danymi podawanymi przez użytkownika. Zwłaszcza w przypadku tablic wielowymiarowych o dużych wymiarach jest to proces czasochłonny. Tablice możemy wypełniać także za pomocą losowania do nich wartości. W tym celu należy wykorzystać funkcję rand()
z biblioteki cstdlib
. Funkcja generuje liczbę losową całkowitą o rozkładzie jednostajnym z przedziału <0, RAND_MAX>
, gdzie RAND_MAX
typu int jest stałą (równą 32767). Domyślnie funkcja rand()
korzysta z predefiniowanego ziarna losowości, więc każdorazowe uruchomienie programu bez zmiany tego ziarna spowoduje wygenerowanie takiej samej sekwencji "losowych" liczb. Aby to zmienić, stosuje się funkcję srand(unsigned int seed)
, która pozwala ustawić ziarno na nową wartość. Typowo stosuje się funkcję srand(time(0))
, by ziarno było oparte na aktualnym czasie systemowym, co zapewnia większą losowość przy każdym uruchomieniu programu. Korzystając z funkcji rand()
możliwe jest wylosowanie liczby z dowolnego przedziału za pomocą prostych operacji matematycznych.
Za pomocą wyprowadzonego wzoru możemy losować liczbę rzeczywistą (double) z przedziału <a;b>. Korzystając z właściwości działania modulo możemy w analogiczny sposób wyprowadzić wzór na losowanie liczb całkowitych.
Wzór umożliwia wylosowanie liczby całkowitej (int) z przedziału <a;b>.
2.2. Formatowanie wydruków
Sterowanie sposobem prezentacji liczb zmiennoprzecinkowych jest możliwe poprzez ustalenie ich formatu i precyzji. Dostępne są trzy formaty:
- Format ogólny pozwala na wyświetlanie liczb możliwie najdokładniej uwzględniając dostępne miejsce. Precyzja w tym przypadku oznacza maksymalną liczbę cyfr.
- Format naukowy prezentuje wartość w postaci wykładniczej z jedną cyfrą przed kropką dziesiętną i wykładnikiem. Precyzja określa maksymalną liczbę cyfr po kropce.
- Format stały prezentuje wartość w sposób naturalny, taki do jakiego jesteście przyzwyczajeni. Zawsze występuje część całkowita, po której występuje część ułamkowa. Precyzja określa liczbę cyfr po kropce.
Przykład wyjaśnia, jak ustawić precyzję, natomiast aby zmienić tryb wypisywania na naukowy, podajemy:
cout << scientific;
Natomiast tryb stałoprzecinkowy ustawiamy tak:
cout << fixed;
Bardzo ważne jest też ustawienie równej szerokości pól przeznaczonych na drukowanie liczb. Szerokość pola wydruku ustawia się następująco (uwaga: dla każdej drukowanej pozycji osobno):
cout << setw(7);
A oto najwygodniejszy sposób, który pozwala w jednej instrukcji ustawić format, precyzję i szerokość wydruku, np.
cout << fixed << setprecision(3) << setw(6) << x << y <<endl;
iomani
p i używać tych standardowych manipulatorów w sposób podany poniżej.a) przed drukowaniem czegokolwiek ustawić stały format wydruku za pomocą manipulatora fixed
(tzn. w postaci naturalnej, z kropką dziesiętną) oraz precyzję wydruku, czyli liczbę miejsc po kropce, używając manipulatora setprecision
, w sposób następujący:
cout << fixed << setprecision(d);
wartość d może być oczywiście konkretną liczbą.
b) w instrukcji drukowania przed wyprowadzeniem jakiejś wartości podać, jaką szerokość w ma ona zająć, używając manipulatora setw
, np.:
cout << setw(4) << x << setw(7) << y << endl;
albo:
cout << setw(12) << x[i][j];
Oczywiście precyzję można ustawiać tak jak szerokość wydruku - w pętli, do każdej drukowanej wartości inną.
3. Pliki tekstowe
Wczytywanie danych z pliku odbywa się w sposób analogiczny jak w przypadku strumieniowej obsługi strumienia wejścia oraz wyjścia konsoli. W przypadku obsługi konsoli wykorzystywaliśmy bibliotekę iostream, natomiast w przypadku obsługi plików tekstowych wykorzystywana jest biblioteka fstream.
W tej bibliotece znajdziemy dwie główne klasy do obsługi plików:
std::ifstream
– służy do strumieniowego odczytu z plików.std::ofstream
– służy do strumieniowego zapisu do plików.
Do otwarcia pliku używamy obiektu jednej z klas strumieniowych i metody open()
, podając jako argument nazwę pliku oraz (opcjonalnie) tryb otwarcia.
ifstream plk_we; // zmienna plk_we określa plik do odczytu
plk_we.open("dane.txt");
Domyślnie katalog roboczy to:
- Katalog, w którym znajduje się plik wykonywalny, jeśli uruchamiasz program bezpośrednio.
- Katalog, z którego uruchamiasz program z terminala lub konsoli.
#include <iostream>
#include <filesystem> // C++17
int main() {
std::cout << std::filesystem::current_path() << std::endl;
return 0;
}
std::filesystem::current_path()
(dostępna od C++17) zwróci aktualny katalog roboczy, co pozwoli zobaczyć, skąd program próbuje otworzyć plik.good()
lub nowszej i bardziej uniwersalnej is_open()
.
int main() {
…
dane.open (... );
if ( !dane.is_open() ) { // jeśli nie ma podanego pliku
cout << " Blad otwarcia pliku" << endl ;
return 1; // błędne zakończenie pracy programu
}
// dalej działania na plikach
// i cala reszta programu
return 0; // poprawne zakończenie pracy programu
}
is_open()
służy do sprawdzenia, czy plik został poprawnie otwarty, zwracając true, jeśli plik jest otwarty, a false, jeśli otwarcie się nie powiodło. Nie monitoruje ona stanu operacji odczytu czy zapisu. Z kolei metoda good()
ocenia ogólny stan strumienia, sprawdzając, czy wszystkie operacje przebiegały bez błędów, w tym odczyt i zapis, oraz czy strumień nadal działa poprawnie. Metoda good()
wykrywa błędy takie jak koniec pliku lub problemy z odczytem, a is_open()
dotyczy wyłącznie poprawności otwarcia pliku.
Nazwa pliku otwieranego do odczytu lub zapisu może być wczytywana, czyli może byc zmienną, ale w tym szczególnym przypadku zmienna ta nie może być typu string
. W przypadku straszych wersji C++ (np. C++98 lub C++03), funkcje takie jak open()
w klasie std::ifstream
czy std::ofstream
wymagały podania ścieżki pliku w postaci wskaźnika do tablicy znaków typu const char*
. W tych starszych wersjach C++, obiekty typu std::string
nie były automatycznie konwertowane na tablice znaków, dlatego konwersja przez c_str()
była potrzebna. Przykład w starszych wersjach C++:std::string nazpl = "dane.txt";
std::ifstream dane;
dane.open(nazpl.c_str()); // Konwersja std::string na const char* jest konieczna
open()
przyjmowały również argumenty typu std::string
, dzięki czemu wywołanie c_str()
nie jest już konieczne. Można bezpośrednio przekazywać obiekt typu std::string
do metody open()
.std::string nazpl = "dane.txt";
std::ifstream dane;
dane.open(nazpl); // Nie trzeba używać c_str() w C++11 i nowszych
std::ios::in
– otwarcie pliku do odczytu,std::ios::out
– otwarcie pliku do zapisu (usuwa poprzednią zawartość pliku),std::ios::app
– otwarcie pliku w trybie dopisywania (dodawanie danych na końcu pliku),std::ios::binary
– otwarcie pliku w trybie binarnym,std::ios::ate
– otwarcie pliku i ustawienie wskaźnika na koniec.
dane.open("nazwa_pliku.bin", std::ios::binary | std::ios::app);
close()
.dane.close();
zmienna_plikowa >> zmienna_1 >> zmienna_2 >> ... >> zmienna_n;
Często istnieje konieczność sprawdzenia czy wczytany został znak końca pliku. W tym celu możemy wykorzystać metodę
eof()
, która sprawdza, czy osiągnięto koniec pliku (ang. end of file). Metodę możemy wykorzystać w połączeniu z pętlą while w celu wczytywania danych z pliku do momentu, gdy zostaną wczytane wszystkie dane.while (!plk.eof())
// dopóki nie napotkano końca pliku, wykonuj
// zmienna, do której wczytujemy linię
string linia;
...
// Nasza zmienna plikowa nazywa się plk
getline(plk, linia);
<fstream>
. Aby zapisać dane, należy utworzyć obiekt std::ofstream
i otworzyć plik, podając jego nazwę. Jeśli plik nie istnieje, zostanie utworzony, a jeśli istnieje, jego zawartość zostanie nadpisana (chyba że użyjemy trybu dopisywania std::ios::app
).std::ofstream plk;
plk.open("plik.txt");
<<
, podobnie jak przy wypisywaniu na konsolę.
plk << "Wartosc zmiennej: " << zmienna << endl;
close()
, aby upewnić się, że wszystkie dane zostały poprawnie zapisane i zasoby zostały zwolnione.4. Struktury
W języku C++ struktury (ang. structures) są typem złożonym, który pozwala na grupowanie różnych typów danych w jedną jednostkę.
struct NazwaStruktury {
typDanych1 pole1;
typDanych2 pole2;
// inne pola
};
#include <iostream>
struct Osoba {
std::string imie;
int wiek;
float waga;
};
int main() {
// Inicjalizacja struktury
Osoba osoba1;
osoba1.imie = "Jan";
osoba1.wiek = 30;
osoba1.waga = 75.5;
// Dostęp do pól struktury
std::cout << "Imię: " << osoba1.imie << std::endl;
std::cout << "Wiek: " << osoba1.wiek << std::endl;
std::cout << "Waga: " << osoba1.waga << std::endl;
return 0;
}
Osoba
to struktura zawierająca trzy pola: imie
(typ std::string
), wiek
(typ int
) i waga
(typ float
). W funkcji main
tworzony jest obiekt osoba1
, a następnie przypisywane są wartości do pól tej struktury.Każde pole danej struktury musi mieć w jego obrębie unikalną nazwę - co oznacza, że pomiędzy nawiasami klamrowymi po słowie struct nie może znaleźć się dwa razy ta sama nazwa. Struktury należy wczytywać i drukować odwołując się do kolejnych pól. Nie wolno tych operacji wykonywać na całych rekordach. Rekordy można na siebie kopiować w całości, poprzez instrukcję przypisania (nie trzeba kopiować pola za polem).
W strukturach można umieszczać inne struktury, co pozwala na tworzenie bardziej złożonych typów danych.
struct Data {
int dzien;
int miesiac;
int rok;
};
struct Student {
std::string imie;
std::string nazwisko;
Data dataUrodzenia; // zagnieżdżona struktura
};<br>
5. Tablice struktur
Tablice struktur w C++ to mechanizm pozwalający na przechowywanie wielu obiektów struktury w jednym uporządkowanym zestawie, czyli w tablicy. Umożliwia to łatwe zarządzanie większą ilością danych tego samego typu strukturalnego. Tablice struktur mogą być jednowymiarowe (proste) lub wielowymiarowe. Poniżej przedstawiono przykład definicji tablicy struktur.
#include <iostream>
#include <string>
struct Osoba {
std::string imie;
int wiek;
float waga;
};
int main() {
// Deklaracja tablicy struktur o nazwie "osoby", która zawiera 3 obiekty typu Osoba
Osoba osoby[3] = {
{"Jan", 30, 70.5},
{"Anna", 25, 55.0},
{"Tomek", 20, 65.5}
};
// Wyświetlanie danych z tablicy struktur
for (int i = 0; i < 3; i++) {
std::cout << "Imię: " << osoby[i].imie << ", Wiek: " << osoby[i].wiek << ", Waga: " << osoby[i].waga << std::endl;
}
return 0;
}
Osoba osoby[2] = {
{"Maria", 22, 60.0},
{"Piotr", 27, 80.0}
};
osoby[0].imie = "Maria";
osoby[0].wiek = 22;
osoby[0].waga = 60.0;
Przykłady:
osoby[0].imie
— dostęp do polaimie
pierwszego elementu tablicy.osoby[1].wiek
— dostęp do polawiek
drugiego elementu tablicy.
6. Funkcje
Funkcje w C++ to podstawowe bloki kodu, które wykonują określone zadania. Umożliwiają podział programu na mniejsze części. Dzięki temu możemy ponownie wykonywać ten sam kod bez potrzeby ponownej implementacji tych samych linii kodu.
- nagłówka
- ciała funkcji, czyli jej treści.
typ_zwracany nazwa_funkcji(typ_parametr param1, typ_parametr param2, ...) {
// ciało funkcji - instrukcje do wykonania
return zwracana_wartosc; // jeśli funkcja ma typ inny niż void
}
typ_zwracany
- Typ wartości zwracanej przez funkcję. Może to być typ prosty, taki jakint
,float
,double
,char
, lub złożony, np. obiekt klasy. Jeśli funkcja nic nie zwraca, używamyvoid
.nazwa_funkcji
- Nazwa funkcji. Powinna być jednoznaczna i opisywać działanie funkcji.typ_parametr
iparam1, param2, ...
- Lista argumentów przekazywanych do funkcji. Każdy argument musi mieć określony typ i nazwę. Funkcja może mieć dowolną liczbę parametrów (w tym zero).return
- Instrukcjareturn
służy do zwracania wartości przez funkcję. Jest wymagana, jeśli funkcja zwraca coś innego niżvoid
.
- Funkcje zwracające wartość.
- Funkcje
void
(niezwracające wartości). - Funkcje z parametrami.
- Funkcje przeciążone (ang. overloading).
- Funkcje rekurencyjne.
- Funkcje inline (funkcje wbudowane).
- Funkcje z referencjami jako parametry.
6.1. Funkcje zwracające wartość
Funkcje zwracające wartość w C++ to funkcje, które po zakończeniu swojego działania zwracają wynik, który można wykorzystać w innych częściach programu. Wartość zwracana może być dowolnego typu, od prostych typów, takich jak int
, double
czy char
, po bardziej złożone, jak wskaźniki, referencje, obiekty klas, struktury.
Oto prosty przykład funkcji, która zwraca sumę dwóch liczb całkowitych:
int dodaj(int a, int b) {
return a + b;
}
int main() {
int wynik = dodaj(5, 10); // Funkcja dodaj zwraca wartość, którą przypisujemy do zmiennej wynik
std::cout << "Wynik: " << wynik << std::endl; // Wynik: 15
return 0;
}
struct Punkt {
int x, y;
};
Punkt stworzPunkt(int a, int b) {
Punkt p;
p.x = a;
p.y = b;
return p;
}
int main() {
Punkt p = stworzPunkt(5, 10);
std::cout << "Punkt: (" << p.x << ", " << p.y << ")" << std::endl;
return 0;
}
6.2. Funkcje void (niezwracające wartości).
Funkcje void
w C++ to funkcje, które nie zwracają żadnej wartości. Funkcje te mogą modyfikować stan programu poprzez efekty uboczne, takie jak modyfikowanie globalnych zmiennych, zmiennych przekazywanych przez referencję lub wskaźniki, czy też operacje wejścia/wyjścia.
Poniżej przedstawiono przykład funkcji void
, która wyświetla tekst na ekranie:
#include <iostream>
void wypiszPowitanie() {
std::cout << "Witaj, użytkowniku!" << std::endl;
}
int main() {
wypiszPowitanie(); // Wywołanie funkcji void
return 0;
}
W funkcjach
void
można używać instrukcji return
, ale nie może ona zwracać wartości. Służy tylko do wcześniejszego zakończenia działania funkcji. Jest to przydatne np. w sytuacjach, gdy w trakcie wykonywania funkcji wystąpi błąd i chcemy natychmiast przerwać dalsze operacje.6.3. Funkcje z parametrami.
Funkcje z parametrami w C++ pozwalają na przekazywanie danych do funkcji w momencie jej wywołania, co umożliwia bardziej uniwersalne i elastyczne podejście do rozwiązywania problemów. Parametry te są zmiennymi, które przyjmują wartości przekazywane podczas wywołania funkcji i mogą być używane wewnątrz ciała funkcji.
Poniżej znajduje się przykład funkcji, która oblicza sumę dwóch liczb przekazanych jako parametry:
#include <iostream>
int dodaj(int a, int b) {
return a + b;
}
int main() {
int wynik = dodaj(5, 7); // Wywołanie funkcji z parametrami 5 i 7
std::cout << "Wynik: " << wynik << std::endl; // Wyświetli: Wynik: 12
return 0;
}
// Poprawne
void funkcja(int a, int b = 5); // b ma wartość domyślną
// Błędne
void funkcja(int a = 0, int b); // Kompilator zgłosi błąd<br>
6.4. Funkcje z referencjami jako parametry.
Przekazywanie parametrów przez referencję w C++ to technika, która pozwala na efektywne przekazywanie zmiennych do funkcji bez konieczności ich kopiowania. Dzięki temu modyfikacje dokonane na parametrach w funkcji mają bezpośredni wpływ na oryginalne zmienne, które zostały przekazane jako argumenty. Aby zademonstrować przekazywanie parametrów przez referencję możemy wrócić do przykładu zademonstrowanego w poprzednim podrozdziale (Funkcje z parametrami), który miał na celu pokazać, że parametry są domyślnie przekazywane przez wartości.
#include <iostream>
using namespace std;
void zwieksz(int a){
a++;
}
int main() {
int x = 1;
zwieksz(x);
cout<<"Wartosc zmiennej x="<<x<<endl;
return 0;
}
Deklaracja parametrów przekazywanych przez zmienną jest podobna do deklaracji parametrów przekazywanych przez wartość. Aby poinformować kompilator, że chodzi o referencję do zmiennej, a nie o nową zmienną - umieszcza się przed nazwą zmiennej znak ampersand &:
typ_zwracany nazwa (typ_p1 &p1, typ_p2 &p2, ..., typ_pn &pn);
gdzie p1, p2 ... są nazwami parametrów odpowiednich typów.
int& parametr
oraz
int ¶metr
są sobie zupełnie równoważne (przynajmniej w przypadku deklaracji listy parametrów funkcji).
6.5. Funkcje przeciążone (ang. overloading).
Przeciążanie funkcji (ang. function overloading) w C++ to technika programistyczna, która pozwala na definiowanie wielu funkcji o tej samej nazwie, ale różnych zestawach parametrów. Dzięki temu można zrealizować różne operacje na danych, używając tej samej nazwy funkcji, co zwiększa czytelność kodu oraz pozwala na lepszą organizację.
Poniżej znajduje się przykład przeciążania funkcji w C++:
#include <iostream>
// Funkcja do dodawania dwóch liczb całkowitych
int dodaj(int a, int b) {
return a + b;
}
// Funkcja do dodawania dwóch liczb zmiennoprzecinkowych
double dodaj(double a, double b) {
return a + b;
}
// Funkcja do dodawania trzech liczb całkowitych
int dodaj(int a, int b, int c) {
return a + b + c;
}
int main() {
int x = 5, y = 10, z = 15;
double a = 5.5, b = 10.5;
std::cout << "Dodawanie liczb całkowitych: " << dodaj(x, y) << std::endl; // Wywołuje dodaj(int, int)
std::cout << "Dodawanie liczb zmiennoprzecinkowych: " << dodaj(a, b) << std::endl; // Wywołuje dodaj(double, double)
std::cout << "Dodawanie trzech liczb całkowitych: " << dodaj(x, y, z) << std::endl; // Wywołuje dodaj(int, int, int)
return 0;
}
6.6. Funkcje rekurencyjne.
Rekurencja w C++ to technika programowania, która polega na definiowaniu funkcji, która wywołuje samą siebie w celu rozwiązania problemu. Rekurencja jest często stosowana do rozwiązywania problemów, które można podzielić na mniejsze podproblemy o tym samym charakterze. W C++ można implementować rekurencję zarówno dla funkcji prostych, jak i bardziej złożonych algorytmów, takich jak sortowanie czy przeszukiwanie.
Jednym z klasycznych przykładów rekurencji jest obliczanie silni liczby całkowitej.
#include <iostream>
// Funkcja rekurencyjna do obliczania silni
int silnia(int n) {
if (n <= 1) { // Warunek zakończenia
return 1;
}
return n * silnia(n - 1); // Wywołanie rekurencyjne
}
int main() {
int n;
std::cout << "Podaj liczbę do obliczenia silni: ";
std::cin >> n;
std::cout << "Silnia " << n << " wynosi: " << silnia( n ) << std::endl;
return 0;
}
6.7. Funkcje inline (funkcje wbudowane).
Funkcje inline (funkcje wbudowane) w C++ to funkcje, które są definiowane z użyciem słowa kluczowego inline
. Głównym celem korzystania z funkcji inline jest zwiększenie wydajności programu przez eliminację narzutu związanego z wywołaniami funkcji. Zamiast standardowego wywołania funkcji, kompilator wstawia kod funkcji w miejscu, w którym jest wywoływana. To pozwala na szybsze wykonywanie kodu, zwłaszcza w przypadku funkcji, które są wywoływane wielokrotnie w programie.
Oto prosty przykład ilustrujący użycie funkcji inline w C++:
#include <iostream>
// Definicja funkcji inline
inline int dodaj(int a, int b) {
return a + b;
}
int main() {
int x = 5;
int y = 10;
// Wywołanie funkcji inline
int wynik = dodaj(x, y);
std::cout << "Wynik dodawania: " << wynik << std::endl;
return 0;
}