C++ Funkcje

Z DMGO
Przejdź do nawigacji Przejdź do wyszukiwania

Co to jest funkcja

Możliwość posługiwania się podprogramami – jedna z najciekawszych cech języków programowania. Pewne instrukcje są wykonywane w danym programie wielokrotnie dla tych samych lub innych zmiennych. Dlatego właśnie dobrze jest je zgrupować pod jedną nazwą.

Tworzymy więc funkcję, czyli podprogram wykonujący pewne zadanie na potrzeby programu głównego. Jest to fragment programu, któremu nadano nazwę i który możemy wykonać przez podanie jego nazwy oraz ewentualnych argumentów (o ile takowe istnieją).

Budowa funkcji

Każda funkcja musi mieć:

  • Nazwę
  • Typ zwracanej przez nią wartości
  • Dowolną liczbę argumentów

Funkcja może pobierać argumenty z programu głównego i użyć ich do wykonania określonych zadań. Jeżeli funkcja zwraca jakąś wartość, należy określić typ zwracanej wartości. Sposób wywołania funkcji zależy od jej typu. Zanim jednak ją wywołamy, musimy wiedzieć, czy pobiera ona argumenty z programu głównego i czy zwraca jakąś wartość. Każda funkcja, która zwraca wartość musi być wywołana przy jednoczesnym przypisaniu danej wartości pewnej zmiennej globalnej.

Istnieje też funkcja, która nie zwraca wartości i nie ma argumentów.

void wypisz()  {
//argumenty funkcji
}

Nazwą funkcji jest wypisz, a funkcja nie przyjmuje żadnych argumentów, ponieważ wewnątrz nawiasów jest pusto. Między klamrami zaś należ umieścić kod, który ma zostać wykonany, gdy zostanie wywołana funkcja. Kod ten nazywa się ciałem funkcji.

Słowo kluczowe void umieszczone przed nazwą funkcji informuje nas, że funkcja nie zwraca żadnych argumentów, które można byłoby przekazać innej funkcji czy instrukcji. Funkcja wypisz może np. wyświetlić komunikat „WITAJ!”. Oprócz wyświetlania informacji żadne dane nie będą dalej przekazywane.

Wartością zwrotną funkcji jest wartość wyliczona, która jest przekazywana do dalszych działań. Jej typ wypisujemy przed definicją naszej funkcji i jest ona zwracana za pomocą słowa kluczowego return.

Przykład:

typ_zwracanej_wartosci nazwa_funkcji(typ_arg_1 nazwa_arg_1 /*,…*/ typ arg_n nazwa_arg_n) {
return zwracana_wartosc;
}

Argumenty określają, jakiego typu zmienne należy przekazać do funkcji jej wywołaniu. Są to dane, na podstawie których zostaną wykonane instrukcje wewnątrz funkcji.

Przykład funkcji z argumentami

void sprawdz(int a)
{
    if (a % 2 == 0) {
        cout << "Liczba << a <<" jest
    }
    else {
        cout << "Liczba << a <<" jest
    }
}

Została zadeklarowana funkcja sprawdź, która ma jeden argument typu całkowitego oraz nie zwraca żadnej wartości, czyli zwraca typ void Funkcja Sprawdź sprawdza, czy podana liczba jest parzysta, czy nie i wyświetla odpowiedni komunikat.

Funkcja o nazwie wyrażenie mająca trzy argumenty typu float i zwracająca wartości typu float.

float wyrazenie(float x, float y, float z)
{
    float wyr = x + 2 * y sqrt(z);
    return wyr;
}

Należy pamiętać, że funkcja, która zwraca wartość musi zwracać ją zawsze. Jeżeli funkcja dojdzie do końca bloku funkcji i nie napotka słowa kluczowego return, zachowanie wyjścia z funkcji jest niezdefiniowane. Oznacza to, że aplikacja może zakończyć pracę w wyniku wystąpienia błędu krytycznego aplikacji.

Wywołanie funkcji

Aby wywołać funkcję w programie, należy po prostu napisać jej nazwę w odpowiednim miejscu, a w nawiasach okrągłych jej argumenty jeśli funkcja je posiada.

Wywołana funkcja musi zawsze być zadeklarowana przed miejscem, gdzie została wywołana. Można przed wywołaniem funkcji napisać tylko jej deklarację, a definicję czyli jej ciało w dowolnym miejscu programu.

Przykład: Program, który wykonuje dodawanie dwóch liczb rzeczywistych. Działanie to będzie wywoływać funkcja suma.

#include <iostream>
using namespace std;

float suma(float a, float b)
{
	return a + b;
}
int main()
{
	float x, y;
	cout << "Podaj x ";
	cin >> x;
	cout << "Podaj y ";
	cin >> y;
	cout << x << " + " << y << " = " << suma(x, y);
	return 0;
}

Definicja funkcji jest umieszczona przed funkcją główną main . Ma ona dwa argumenty typu rzeczywistego i zwraca wartość typu rzeczywistego. Jeśli funkcja wykonuje tylko jedno działanie, to może je zapisać od razu w instrukcji return.

Funkcję suma wywołujemy przez instrukcję suma(x, y);

Zmienne x oraz y są zmiennymi globalnymi i są to zmienne typu rzeczywistego, tak jak zmienne a oraz b, będące zmiennymi lokalnymi.

Służą one do zapisywania wyrażenia, które funkcja ma wykonać. Funkcja pracuje na zmiennych a i b, będących kopiami zmiennych x i y, przekazanych do funkcji jako argumenty.

Jest to przykład przekazywania argumentów przez ich wartość. W momencie, gdy zmienne są przekazywane jako argumenty, funkcja tworzy ich kopię w pamięci. Oznacza to, że wszelkie zmiany w wartości zmiennych nie będą widoczne w miejscu, gdzie zostały przekazane do funkcji. Nazwy tych zmiennych mogą, ale nie muszą być takie same.

Kopiowanie danych przekazywanych do funkcji bardzo często nie jest pożądanym efektem. Dane mogą bowiem zajmować dużo miejsca w pamięci i wówczas będziemy niepotrzebnie tracić moc obliczeniową komputera na tworzenie ich kopii.

Aby zapobiec kopiowaniu danych, wystarczy użyć symbolu & w odpowiednim miejscu, czyli zastosować przekazywanie argumentów przez referencję.

Symbol & umieszcza się za typem zmiennej i przed jej nazwą, np.:

int &zmienna;

Jeśli chcemy przekazywać argumenty przez referencję, stosujemy następujący zapis:

typ_funkcji nazwa_funkcji (typ_argumetu &nazwa_arg /*kolejne argumenty*/)

Dane przekazywane przez referencję nie będą kopiowane. Będziemy pracowali na oryginalnych danych, co w praktyce oznacza, że modyfikacja wartości zmiennej zmodyfikuje nam tę zmienną, która została przekazana do funkcji przez argument.

Przykład przekazywania argumentu przez referencję

#include <iostream>
#include <cstdlib>
using namespace std;

void zmienna(int &a, int &b)
{
	int x;
	x = a; a = b; b = x;
}

int main()
{
	int x = 5;
	int y = 6;
cout << "Przed zmienna: " << endl;
cout << " x = " << x << " y = " << y << endl;
zmienna(x,y);
cout << "Po zmianie: " << endl;
cout << " x = " << x << " y = " << y << endl;
return 0;
}

Jeśli definicję funkcji umieścimy po funkcji głównej main , to nie będzie ona widoczna dla kompilatora, chyba że przed funkcją main umieścimy prototyp naszej funkcji.

Prototyp (deklaracja) funkcji to model, po którym funkcja będzie rozpoznawana w programie. W przeciwieństwie do definicji prototyp jest zakończony średnikiem. Definicja funkcji zaś musi mieć nagłówek, szkielet (zawierający wszystkie instrukcje wykonywane w obrębie danej funkcji) oraz instrukcję powrotu (część kończącą każdą funkcję).

Nagłówek definicji ma budowę identyczną z prototypem, ale nie jest zakończony średnikiem. W przypadku kiedy funkcja ma oddzielną deklarację (prototyp) i definicję, nie jest konieczne nazywanie argumentów funkcji podczas deklaracji. Wystarczą jedynie typy tych argumentów. Często używa się tej właściwości, żeby nie przyćmić deklaracji funkcji niepotrzebnymi nazwami argumentów.

Rozdzielenie deklaracji funkcji od jej definicji

#include <iostream>
#include <math.h>
using namespace std;

// funkcja o nazwie delta typu float, pracująca na argumentach t.float
float delta(float a, float b, float c)
{
	return b * b - 4 * a * c;
}
// funkcja o nazwie dwa_rozw typu void, pracująca na argumentach t.float
void dwa_rozw(float a, float b, float d)
{
	float x1 = (-b-sqrt(d))/(2 * a);
	float x2 = (-b+sqrt(d))/(2 * a);
	cout << "x1= " << x1 << endl << "x2= " << x2;
}
float jedno_rozw(float a, float b)
{
	return -b / a;
}

int main()
{
	float a, b, c;
	cout << "Rozwiazanie rownania ax * x + bx + c = 0" << endl;
	cout << "Podaj a: ";
	cin >> a;
	cout << "Podaj b: ";
	cin >> b;
	cout << "Podaj c: ";
	cin >> c;

	if (a==0)
	{
		cout << "Rownanie jest liniowe i posiada jedno rozwiazanie: " << jedno_rozw(b,c);
	}
	else
	{
		float d = delta(a, b, c);
		if (d < 0)
		{
			cout << "Rownanie nie posiada rozwiazania";
		}
		else
		{
			if(d==0)
			{
				cout << "Rownanie posiada jedno rozwiazanie: " << jedno_rozw(2 * a, b);
			}
			else
			{
				cout << "Rownanie posiada dwa rozwiazania:" << endl;
				dwa_rozw(a, b, c);
			}
		}
	}
	return 0;
}

Przeładowanie funkcji

Przeładowanie nazwy funkcji polega na tym, że w danym programie występuje więcej niż jedna funkcja o takiej samej nazwie. To, która z nich zostanie zastosowana w kodzie, zależy od argumentów, z którymi zostanie ona wywołana.

Rozważmy funkcję do obliczenia pola powierzchni wybranych figur. Inaczej będzie wyglądała funkcja obliczająca pole koła, a inaczej trójkąta. Pisanie kilku funkcji o różnych nazwach dla poszczególnych figur jest pracochłonne.

Zamiast nazywać funkcje: pole_kola , pole_trojkata itd., wygodniej jest napisać pole Język C++ dopuszcza taką możliwość. Jeżeli w jednym zakresie istnieje kilka funkcji o tych samych nazwach, to należ je jakoś odróżnić. Wystarczy zadbać o to, aby typy argumentów były różne w każdej z funkcji lub by była inna liczba argumentów. Można też zamienić kolejność błędu wtedy nie będzie.

Przykład przeładowania funkcji

float pole(float promien);
float pole(float dlugosc , float szerokosc);

Funkcje wyglądają podobnie. Mają jednakowe nazwy, a mimo to kompilator nie wyświetla żadnego błędu.

Na tym właśnie polega przeładowanie nazwy funkcji. Takich funkcji może być więcej. Wystarczy zmienić typ argumentu, ich liczbę lub kolejność.

Podczas przeładowania nazwy funkcji ważna jest jedynie odmienność argumentów. Typ zwracany przez funkcję nie jest brany pod uwagę. Zatem niepoprawny będzie format przeładowania funkcji:

float pole(int promien);
int pole(int promien);

W trakcie kompilacji takie przeładowanie będzie traktowane jako błąd!

Przykład przeładowania funkcji

#include <iostream>
#include <math.h>
using namespace std;

float pole(float promien)
{
	return M_PI * promien * promien;
}

float pole(float a, float b)
{
	return a * b;
}
int main()
{
	float pr, x, y;
	cout << "Podaj dlugosc promienia: ";
	cin >> pr;
	cout << "Pole kola wynosi: " << pole(pr) << endl;
	cout << "Pole prostokata wynosi: " << pole(3, 4.5);
	return 0;
}

Argumenty domniemane

Jeśli podczas wywoływania funkcji podamy złą liczbę argumentów, to kompilator wyświetli błąd.

W C++ można umieszczać argumenty domniemane.

Argument domniemany to argument, który może zostać podany w wywołaniu funkcji lub nie.

Argumenty te określa się w deklaracji funkcji, a nie w jej definicji. Jeśli jednak funkcja jest w programie napisana powyżej jakiegokolwiek jej wywołania, to oddzielna deklaracja tej funkcji nie jest potrzebna.

Sama definicja jest wtedy także jej pierwszą deklaracją występującą w tym programie.

Rozpatrzmy funkcję służącą do wyświetlenia informacji o temperaturze. Zazwyczaj temperatura jest podawana w stopniach Celsjusza. Czasem jednak chcemy mieć inną skalę. Rodzaj takiej jednostki można określić w wywołaniu funkcji za pomocą liczby całkowitej.

Wartość 0 to np. stopnie Celsjusza, 1 – kelwiny, 2 – stopni Farenheita.

Wygodnie oczywiście byłoby mieć funkcję, która podaje domyślną skalę Celsjusza, a jedynie na życzenie użytkownika w innych wariantach.

Czy jest możliwe wykonanie takiej operacji?

Jest to możliwe przy zastosowaniu argumentów domniemanych.

Jeżeli nie podamy nic, zostanie wykorzystana skala Celsjusza, w przeciwnym wypadku w Kelwinach czy stopniach Farenheita.

Przykład 1

#include <iostream>
using namespace std;

float temperatura(float stopnie, int skala = 0) {
	if (skala == 0) return stopnie;
	if (skala == 1) return stopnie + 273;
	if (skala == 2) return stopnie * 1.8+32; }
int main() {
	float temp;
	cout << "Dzis jest " << temperatura(26) <<" C" << endl;
	cout << "Dzis jest " << temperatura(26, 1) <<" K" << endl;
	cout << "Dzis jest " << temperatura(26, 2) <<" F" << endl;
	return 0; }

Taką funkcję można wywoływać dwojako. Pierwszy sposób to normalne wywołanie z określeniem konkretnej wartości argumentu skala:

int t = temperatura(26, 0);

Drugi sposób jest nieco krótszy, ponieważ pomijamy wartość drugiego argumentu:

int t = temperatura(26);

W obu powyższych zapisach zostanie po prostu wypisana temperatura 26*C. Jeśli chcemy wyświetlić tę temperaturę w kelwinach, należy zastosować zapis:

int t = temperatura(26, 1);

Wszelkie argumenty domniemane należy umieszczać na końcu! Nie może być sytuacji, w której pierwszy argument będzie domniemanym, a pozostałe nie.

Przykład 2:

Rozważmy następujący zapis:

void wypisz (int arg1=0, int arg2, int arg3 = 1);

Jeżeli podczas wywołania funkcji zostaną podane dwa argumenty, to mogą mieć miejsce dwie sytuacje:

  • Funkcja zostanie wywołana z pierwszym argumentem domniemanym równym 0, pozostałe 1,2,3 to argumenty, które należy podać w wywołaniu.
  • Argumenty pierwszy i drugi określamy w wywołaniu, a trzeci jako że jest domniemany będzie równy 1.

Taka funkcja nie może zostać wywołana. Otrzymamy komunikat o błędzie.

Zadanie sprawdzające

Napisz program zawierający funkcję sprawdzającą, czy z trzech liczb podanych przez użytkownika możemy zbudować trójkąt prostokątny. Wykorzystaj funkcję typu void, a argument typu float. Wykorzystaj do tego celu program główny main. Należy tylko zaimplementować funkcję, która wyświetli następujący rezultat Output.

Program główny main oraz rezultat działania programu.