1 Wstęp do informatyki- wykład 10 Funkcje (przekazywanie parametrów przez wartość i zmienną) Treści prezentowane w wykładzie zostały oparte o: S. Prata, Język C++. Szkoła programowania. Wydanie VI, Helion, 2012 www.cplusplus.com Jerzy Grębosz, Opus magnum C++11, Helion, 2017 B. Stroustrup, Język C++. Kompendium wiedzy. Wydanie IV, Helion, 2014 S. B. Lippman, J. Lajoie, Podstawy języka C++, WNT, Warszawa 2003.
Funkcje wywołanie funkcji funkcje typu void Wywołanie funkcji typu void: Funkcje typu void wywołujemy w osobnej linii używając jej nazwy i podając parametry, a całość kończąc średnikiem. #include<iostream> using namespace std; void powitanie(int n); //deklaracja funkcji int main() powitanie(5); //instrukcja wywołania funkcji //na konsoli:czesc! Czesc! Czesc! Czesc! Czesc! void powitanie(int n)//definicja funkcji for(int i = 0; i < n; i++) cout << "Czesc! "; cout << endl; 2
Definiowanie funkcji funkcje zwracające wartość #include<iostream> using namespace std; double cube(double x); //prototyp- deklaracja funkcji int main() double q = cube(1.2); //wywołanie funkcji double side; cout << " podaj bok :"; cin >> side; cout << "Kostka o boku "<< side<< " ma pojemnosc " << cube(side) <<" cm3" <<endl;//2 wywołanie double cube(double x) //definicja funkcji return x*x*x; 3
Parametry funkcji i przekazywanie przez wartość Zajmijmy się teraz sposobem przesyłania argumentów do funkcji. Najpierw jednak sprawa nazewnictwa. //definicja double cube(double x) return x*x*x; //wartość typu double nazwy które widzimy w pierwszej linijce definicji funkcji są to tzw. argumenty formalne funkcji. Czasem zwane parametrami formalnymi. 4
Parametry funkcji i przekazywanie przez wartość int main() double q = cube(3);//wywołanie funkcji w main cout << q << endl; To natomiast, co pojawia się w nawiasie w momencie wywoływania tej funkcji czyli w naszym przypadku 3 to tak zwane argumenty (parametry) aktualne. Czyli takie argumenty, z którymi aktualnie funkcja ma wykonać pracę. Często mówi się prościej: argumenty wywołania funkcji bo z tymi argumentami funkcję wywołujemy. W standardzie C++ używa się słowa argument dla określenia parametrów aktualnych, a słowa parametr dla param. formalnych. 5
Parametry funkcji i przekazywanie przez wartość double cube(double x); //prototyp C++ normalnie przekazuje parametry do funkcji przez wartość, oznacza to, że jeśli do funkcji przekazywana jest liczba, to tworzona jest nowa zmienna. Argumenty przesłane do funkcji są zatem tylko kopiami. Jakiekolwiek działanie na nich nie dotyczy oryginału. double side = 5; double vol = cube(side); Kiedy funkcja cube jest wywoływana tworzona jest nowa zmienna x typu double inicjalizowana wartością 5, cube() działa na kopii side, a nie na oryginale. 6
Parametry funkcji i przekazywanie przez wartość - przykład void zwieksz(int formalny) formalny += 1000; // zwiekszenie liczby o 1000 cout << "W funkcji modyfikuje arg formalny\n\t" << " i teraz arg formalny = "<< formalny << endl; Jak widać, w tej funkcji zwiększa się wartość parametru formalnego funkcji. Funkcję tę wywołujemy na przykład w takim fragmencie programu: int main() int aktu = 2; cout << "Przed wywolaniem, aktu = " << aktu << endl; zwieksz(aktu); cout << "Po wywolaniu, aktu = " << aktu << endl; 7
Parametry funkcji i przekazywanie przez wartość Jeżeli wykonamy taki fragment programu, to otrzymamy: Przed wywolaniem, aktu = 2 W funkcji modyfikuje arg formalny i teraz arg formalny = 1002 Po wywolaniu, aktu = 2 Do funkcji przesyłamy tylko wartość liczbową zmiennej aktu (czyli argumentu aktualnego). Wartość ta służy do inicjalizacji parametru formalnego, czyli zmiennej lokalnej tworzonej przez funkcję na stosie. Jest to więc jakby zrobienie kopii w obrębie funkcji. Funkcja pracuje na tej kopii. Czyli w naszym przykładzie dodanie 1000 nie nastąpiło do komórki pamięci, gdzie tkwi aktu, ale do tej zmiennej lokalnej na stosie, gdzie mieści się kopia (o nazwie formalny). Po opuszczeniu funkcji ten fragment stosu jest niszczony, znika więc też kopia. 8
Parametry funkcji i przekazywanie przez wartość Ponieważ przy przekazywaniu parametrów przez wartość do funkcji przesyłamy tylko wartość liczbową argumentu aktualnego, to przy wywołaniu funkcji możemy w miejsce argumentów podstawiać: zainicjalizowane wcześniej zmienne albo stałe, lub wyrażenia, np.: double q = cube(1.2); //wywołanie funkcji double side = 4; cout << cube(side); 9
Funkcje definiowanie funkcji przed main() Każda nazwa przed jej użyciem musi zostać zadeklarowana. Dotyczy to też nazw funkcji. Funkcje muszą więc być deklarowane. Jednakże każda definicja (funkcji) jest także przy okazji jej deklaracją. Jeżeli więc w pliku definicja funkcji jest wcześniej (po prostu wyżej) niż linijka z jakimkolwiek wywołaniem tej funkcji, to nie trzeba osobnej deklaracji tej funkcji. Jeśli natomiast funkcja nie jest osobno deklarowana, a wywołanie następuje w linijce powyżej definicji tej funkcji, wówczas kompilator zaprotestuje komunikatem o błędzie. 10
11 Parametry funkcji i przekazywanie przez wartość - przykład //f-cja oblicza ilość cyfr danej liczby całkowitej include<iostream> using namespace std; int ilecyfr(int n) n = abs(n); int ile = 0; //licznik cyfr liczby //do while bo każda liczba ma przynajmniej 1 cyfrę do ile++; //zwiekszamy licznik n /= 10; //pozbywamy się policzonej cyfry while(n! = 0); return ile;
Parametry funkcji i przekazywanie przez wartość - przykład int main() cout<< "liczba 834 ma "<< ilecyfr(834) << " cyfr "<< endl; int liczba; cout << "podaj liczbe calkowita "; cin >> liczba; cout << "liczba "<< liczba <<" ma " << ilecyfr(liczba) << " cyfr "<< endl; 12
Parametry funkcji przekazywanie przez wartość przykład2 //funkcja zwraca najmniejsza cyfrę wśród cyfr //liczby będącej parametrem funkcji, np. 537 int mincyfra(int n) n = abs(n); int minc = n%10; //ostatnia cyfra do minc n /= 10; //skracamy ostatnia cyfra while(n > 0) int c = n%10; if(c < minc) minc = c; n = n/10; // n/=10; return minc; 13
Parametry funkcji przekazywanie przez wartość przykład2 //funkcja oblicza przybliżoną wartość ln 2 za //pomocą n początkowych wyrazów szeregu //ln2 = 1-1/2 + 1/3 1/4 +...+(-1) (n-1) 1/n //n parametr funkcji, dla n<=0 f-cja zwraca 0 include <iostream> using namespace std; double ln2(int n) double s = 0.0; for(int i = 1, zn = 1; i <= n; i++, zn = -zn) s += zn*1.0/i;// / rzeczywiste stad 1.0 return s; int main() cout << ln2(10); 14
Przesyłanie argumentów przez referencję W C++ argumenty możemy przesyłać do funkcji nie tylko przez wartość ale i przez referencję, czyli przez przezwisko. #include <iostream> using namespace std; void zer(int wart, int &ref);//deklaracja int main() int a = 44, b = 77; cout << "Przed wywolaniem funkcji: a = " << a << ", b = " << b << endl; zer(a, b); //wywołanie cout << "Po powrocie z funkcji: a = " << a << ", b = " << b << endl; 15
Przesyłanie argumentów przez referencję void zer(int wart, int &ref) cout << "\tw funkcji przed zerowaniem \n" wart = 0; ref = 0; << "\twart = " << wart << ", ref = " << ref << endl; cout << "\tw funkcji po zerowaniu \n" << "\twart = " << wart << ", ref = " << ref << endl; 16
Przesyłanie argumentów przez referencję void zer(int wart, int &ref);//deklaracja W rezultacie działania tego programu na ekranie pojawi się: Przed wywolaniem funkcji: a = 44, b = 77 W funkcji przed zerowaniem wart = 44, ref = 77 W funkcji po zerowaniu wart = 0, ref = 0 Po powrocie z funkcji: a = 44, b = 0 Funkcja zer, przyjmuje dwa argumenty: pierwszy z nich jest przesyłany przez wartość, drugi natomiast przez referencję &. Widać, że argument, który funkcja przyjmowała przez wartość, nie został zmodyfikowany. Natomiast zmienna, którą funkcja odebrała przez referencję została zmodyfikowana. 17
Przesyłanie argumentów przez referencję void zer(int wart, int &ref);//deklaracja zer(a, b); //wywołanie dla a = 44, b = 77; W tym przypadku do funkcji zamiast liczby 77 (wartość zmiennej b) został wysłany adres zmiennej b w pamięci komputera. Ten adres funkcja sobie odebrała i (na stosie) stworzyła sobie referencję, czyli komórce pamięci o przysłanym adresie nadała pseudonim (przezwisko, alias) ref. Referencja jest inną nazwą danej zmiennej. Zatem ta sama komórka, na którą w main mówiło się b, stała się teraz w funkcji zer znana pod przezwiskiem ref. Są to dwie różne nazwy, ale określają ten sam obiekt. Zatem gdy do obiektu o przezwisku ref wpisano zero to znaczy, że odbyło się to faktycznie na obiekcie b. 18
Przesyłanie argumentów przez referencję Po zakończeniu działania funkcji likwiduje się śmieci: kopię zmiennej a. adres obiektu b, który to obiekt wewnątrz funkcji przezywaliśmy ref. Ten adres został zlikwidowany. ( My tracimy adres, ale np. funkcja main ma ten adres u siebie zanotowany). Wniosek: przesłanie argumentów funkcji przez referencję pozwala tej funkcji na modyfikowanie zmiennych znajdujących się poza tą funkcją. Ten sposób przesyłania stosuje się m.in. do dużych obiektów, gdyż przesłanie ich przez wartość (wymagające zrobienia kopii) powodowałoby spowolnienie wywoływania takiej funkcji. W przypadku gdy taka funkcja jest wywoływana bardzo wiele razy, może to być ważnym czynnikiem. Jeszcze innym sposobem przesłania argumentu może być przekazywanie przez wskaźnik. 19
20 Przesyłanie argumentów przez referencję - przykład /*f-cja obliczająca średnią geometryczną dwóch liczb rzeczywistych sqrt(x*y), jeśli któryś argument <0 nie da się policzyć średniej => f-cja logiczna, zwraca true, gdy da się obliczyć średnią, false w przeciwnym przypadku, średnia jako dodatkowy argument */ bool sredniag(double x, double y, double &srg) if (x >= 0 && y >= 0) srg = sqrt(x*y); return true; else return false;
Przesyłanie argumentów przez referencję - przykład #include <iostream> #include <cmath> using namespace std; bool sredniag(double x, double y, double &srg); int main() double a = 4.9, b = 5.8; double sg; if(sredniag(x, y, sg)) cout<<"srednia geometryczna "<< sg << endl; else cout<<"nie da sie obliczyc sredniej"<<endl; if(sredniag(4, 4, sg)) cout<<"srednia geometryczna "<<sg<<endl; else cout<<"nie da sie obliczyc sredniej"<<endl; bool sredniag(double x, double y, double &srg)... 21
Przesyłanie argumentów przez referencję przykład 2 Napisz funkcję sumailosccyfr, która dla zadanej parametrem liczby całkowitej jako wartość zwróci sumę cyfr tej liczby i jako parametr zwróci ilość jej cyfr. int sumailosccyfr(int n, int &ilc) if (n < 0) n = -n; //wartość bezwzględna int sc = 0; //zmienna na sumę cyfr ilc = 0; //zerujemy parametr ilość cyfr //pętla do-while, bo każda liczba nawet 0 // ma przynajmniej jedna cyfra do ilc++; //zwiększamy liczbę cyfr sc += n%10; //do sumy dodajemy ostania cyfra n/=10; //pozbywamy się policzonej już cyfry while(n > 0); return sc; 22
Przesyłanie argumentów przez referencję przykład 2 #include <iostream> using namespace std; int sumailosccyfr(int n, int &ilc); int main() int n; cout << "Podaj liczba "; cin >> n; int ilec; int sc = sumailosccyfr(n, ilec); cout<<"suma cyfr liczby "<< n <<" wynosi "<< sc <<", a liczba cyfr = "<< ilec << endl; 23
Przesyłanie argumentów przez referencję przykład 2 /*Jeśli chcemy funkcję wywołać w cout to musimy pamiętać, że wyrażenia w cout są opracowywane od prawej strony. Zatem poniższe wywołanie będzie się odnosić do nieobliczonej jeszcze wartości ilec: cout << "Suma cyfr liczby "<<125<<" wynosi " << sumailosccyfr(125, ilec) << ", a liczba cyfr = "<< ilec << endl; */ //wywołanie musi być zatem przestawione: cout<< "Liczba cyfr liczby " << 125 <<" wynosi " << ilec << ", a suma cyfr = " << sumailosccyfr(125, ilec)<<endl; //main int sumailosccyfr(int n, int &ilc)//definicja 24
Referencje Referencja wartość, która zawiera informacje o położeniu innej wartości w pamięci lub nośniku danych. W C++, referencje są implementowane jako oferująca mniej możliwości, ale bezpieczniejsza odmiana wskaźników. Podobnie jak i one, referencje wskazują tutaj na pewien obszar pamięci z tą różnicą, że nie mogą być modyfikowane. Do referencji można przypisać adres tylko raz, a jej dalsze używanie niczym się nie różni od używania zwykłej zmiennej. Operacje jakie wykona się na zmiennej referencyjnej, zostaną odzwierciedlone na zmiennej zwykłej, z której pobrano adres. Referencje deklaruje się jak zmienne z podaniem znaku &: TypDanych & referencja Taki zapis byłby możliwy w liście argumentów funkcji, jednak w ciele funkcji referencja musi być od razu zainicjalizowana. 25
Referencje Zapisujemy do niej adres innej zmiennej : TypDanych & referencja = innazmienna; Od tej pory można używać obu tych zmiennych zamiennie. Poniższe przypisania dadzą więc ten sam efekt: innazmienna = 9; referencja = 9; Zobaczmy działanie referencji na konkretnym przykładzie: int i = 0; int &ref_i = i; cout << i; // wypisuje 0 ref_i = 1; cout << i; // wypisuje 1 cout << ref_i; // wypisuje 1 26
l-wartości (lvalue) i r-wartości (rvalue) W instrukcji przypisania: zmienna = 50; // do zmiennej wpisujemy liczbę 50 to po obu stronach znaku = mogą stać tylko wyrażenia określonego typu. Nie można na przykład napisać tak: 50 = zmienna;//blad! Nie można wstawić czegoś do 50 Po lewej stronie znaku przypisania, musi być coś, do czego w ogóle da się cokolwiek wpisywać. Można coś wpisać do zmiennej, ale nie da się nic wpisać do liczby 50. Ogólnie mówiąc: wyrażenia, które wolno postawić po lewej stronie znaku przypisania, nazywamy l-wartościami. Wyrażenie, które nie może stać po lewej stronie (zatem tylko po prawej), nazywamy r-wartością (rvalue). 27
int a = 6, b = 40; l-wartości (lvalue) i r-wartości (rvalue) a + b = 10; // Błąd: wyrażenie (a+b) nie jest //l-wartością (jest tylko rwartoscią) a = (a + b); // Poprawnie a = (6+a) *3; // r-wartościami są wyrażenia (6+a), // 3 oraz całość (6+a)*3; L-wartość to wyrażenie oznaczające zmienną(obiekt) ( może to być sam obiekt, referencja lub wskaźnik na obiekt). Ogólniej mówiąc: L-wartość to wyrażenie określające jakieś miejsce w pamięci, którego adres można poznać. R-wartość to wartość chwilowa, obiekt tymczasowy (lub napisana dosłownie liczba), która zostaje obliczona, użyta i odtąd staje się niepotrzebna. 28
Referencje do l-wartości i r-wartości Referencje o których mówiliśmy do tej pory to przezwiska, (kolejne nazwy, aliasy) dla istniejących obiektów posiadających nazwę czyli lwartości. int obiekt; int & ref_obiektu = obiekt; //ref. do l-wartości Przed standardem C++11 dostępny był tylko ten jeden rodzaj referencji. Od C++ 11 można tworzyć tzw. referencję do r-wartości. Jest to nazwa dla obiektu tymczasowego, który sam nie ma nazwy. Aby stworzyć taką referencję używamy nie jednego ale dwóch znaków &: int && ref_rwartosci = 3+12*22; // referencja do r-wartości 29
Stałe referencje Od powyższej reguły istnieje wyjątek. Od dawna możemy tworzyć stałą referencję do typu, do której przypisujemy wartość tymczasową: const int& e = 5; // OK! Taka sytuacja została dopuszczona, aby można było w wywołaniu takiej funkcji: void funkcja(const int&); podać wartość bezpośrednio - bez tworzenia wcześniej specjalnej zmiennej i stałej referencji do niej: funkcja(5); // OK! 30
Referencje jako argumenty funkcji - przykład #include <iostream> using namespace std; void fun_przez_wartosc( int wart ) ++wart; // inkrementacja lokalnej kopii cout << "arg. przez wartosc = "<< wart <<" \n"; int main() int obj = 40; // obj jest lwartością fun_przez_wartosc(obj);//arg. przez wartosc = 41 cout << " main: obj = " << obj //main: obj = 40 fun_przez_wartosc(80+1); //arg. przez wartosc = 82 /*do funkcji odbierającej argument przez wartość można wysłać zarówno obiekt, jak i liczbę. W funkcji działamy na kopii.*/ 31
Referencje jako argumenty funkcji - przykład #include <iostream> using namespace std; void fun_ref_lwartosci(int & ref_lwartosc) cout << "fun_ref_lwartosci(" << ref_lwartosc << ")\n"; ++ref_lwartosc; // inkrementacja oryginału int main() int obj = 40; // obj jest lwartością fun_ref_lwartosci(obj);//fun_ref_lwartosci(40) cout << "w main obj = " << obj << endl; //w main obj = 41 //f-ja odbiera argument jako referencję do obiektu //- lwartości, ref_lwartosc jest aliasem obj 32
Referencje jako argumenty funkcji - przykład #include <iostream> using namespace std; void fun_ref_lwartosci(int & ref_lwart) cout << "fun_ref_lwartosci(" << ref_lwart << ")\n"; ++ref_lwart; // inkrementacja oryginału int main() int obj = 40; // obj jest lwartością fun_ref_lwartosci(obj);//fun_ref_lwartosci(40) cout << "w main obj = " << obj << endl; //w main obj = 41 /*f-ja odbiera argument jako ref. do lwartości, to ref_lwart - alias obj,a obj musi być lwartoscią */ 33
Referencje jako argumenty funkcji - przykład #include <iostream> using namespace std; void fun_ref_constlwart(const int & ref_stala_lwart) cout << "fun_ref_constlwart(" << ref_stala_lwart << ")\n"; //inkrementacja zabroniona, bo ref. jest do const int main() int obj = 40; // obj jest lwartością fun_ref_constlwart(obj); fun_ref_constlwart(80+3); //ok bo const /*funkcja ma wprawdzie dostęp do oryginalnego obiektu, ale bez możliwości modyfikowania go*/ 34
Referencje jako argumenty funkcji - przykład #include <iostream> using namespace std; void fun_ref_rwart(int && ref_wart_tymczas) cout << "fun_ref_rwart "; // inkrementacja rwartości ++ref_wart_tymczas; cout << ref_wart_tymczas; int main() fun_ref_rwart(80+4); //fun_ref_rwart 85 /*f-cja odbiera argument przez referencję do rwartości, to można jej wysłać jako argument tylko rwartości(czyli liczby, wyrażenia itd.)*/ 35