Przekazywanie argumentów – wskaźniki Przykład:
Transkrypt
Przekazywanie argumentów – wskaźniki Przykład:
Przekazywanie argumentów – wskaźniki • klasyczne wywołanie – wyliczenie i zwrotne • • przekazanie tylko jednej wielkości moŜliwość uŜycia zmiennych globalnych – niebezpieczeństwa z tym związane wyjście – wywołanie funkcji z adresami zmiennych Przykład: // adresy (wskaźniki) jako argumenty funkcji #include <iostream> using namespace std; const double PI=3.1415926; void kula(double r, double *pow, double *obj); main() { double r,s,v,*p; p=&v; r=1.0; kula(r, &s, p); cout << "Pole powierzchni = " << s << endl; cout << "Objetosc = " << *p << endl; return 0; } void kula(double r, double *pow, double *obj) { *pow=4*PI*r*r; *obj=4*PI*r*r*r/3; } _____________________________________ Pole powierzchni = 12.5664 Objetosc = 4.18879 • bardzo częste stosowane • gdy przekazujemy tabelę, to przekazujemy adres jej początku Jak to działa? int a, *wa; cout << "adres a : " << &a << " wartosc a : " << a << endl; cout << "adres wa : " << &wa << " wartosc wa : " << wa <<endl;wa=&a; *wa=1; adres a : 0x22ff74 adres wa : 0x22ff70 wartosc a : 2 wartosc wa : 0x7 1. Definicja zmiennych: int a, *wa; a 0x74 wa 0x70 2. Podstawienie: wa=&a; a wa 0x74 0x74 0x70 3. Podstawienie: *wa=1; a wa 1 0x74 0x74 0x70 Poprzez odwołanie pośrednie moŜemy za pomocą wskaźnika zmieniać wartość innych zmiennych (tych, na które pokazuje wskaźnik). To jest kluczowe przy wykorzystaniu wskaźników jako argumentów funkcji. W naszym przykładzie: 1. Definicja zmiennych: double r, s, v, *p; r 0x70 s 0x68 v 0x60 p 0x5c 2. Podstawienie: p=&v; r 0x70 s 0x68 v 0x60 p 0x60 0x5c 3. Podstawienie: r=1.0; r 1.000000 0x70 s 0x68 v 0x60 p 0x60 0x5c 4.Wywołanie funkcji: r 0x70 r 1.000000 0x30 s 0x68 pow 0x68 0x38 v 0x60 obj 0x60 0x3c p 1.000000 kula(r,&s,p); kula(r,*pow,*obj); 0x60 0x5c 4.Wyliczenia w funkcji: *pow=4*PI*rr*rr; *obj=4*PI*rr*rr*rr/3; r 1.000000 0x70 r 1.000000 0x30 s 12.566 0x68 pow 0x68 0x38 v 4.1888 0x60 obj 0x60 0x3c p 0x60 0x5c 5. Zakończenie funkcji kula – zmienne r, pow, obj są kasowane, ale zawartość komórek pamięci na króre wskazywały pow, obj (czyli komórek s oraz v – zmiennych lokalnych main) pozostaje!!! 6.Wydruk zawartości komórek s, v – drukuje wyliczone w funkcji kula wartości • kluczowy punkt – mając adres jakiegoś obiektu (wskaźnik) moŜemy nim manipulować mimo tego, Ŝe punkt manipulacji jest (formalnie rzecz biorąc) – poza zakresem waŜności tego obiektu • ta metoda pozwala obliczyć i wycofać z funkcji obliczone wartości dowolnej ilości zmiennych • poprawny sposób komunikacji pomiędzy funkcjami w C/C++ - patrząc tylko na miejsce wywołania funkcji moŜemy z góry powiedzieć, które argumenty mogą być zmieniane przez operacje wewnątrz funkcji tak, by zmiany te były aktualne po zakończeniu działania funkcji – to umoŜliwia/ułatwia analizę programu. Zmienne referencyjne • w C++ moŜna tworzyć zmienne referencyjne do istniejących juŜ zmiennych • zmienne takie to aliasy (przezwiska) innych zmiennych – zarówno zmienna jak i alias odpowiadają temu samemu miejscu w pamięci int a=5; int &b=a; int &c; b=10; a=20; // definicja i inicjalizacja a // b jest referencją do a, wartosc b=5 // nielegalna definicja, referencja musi być // zainicjowana // a=10, b=10 // a=20, b=20 • w C++ referencje uŜywane najczęściej w funkcjach, które zmieniają swoje argumenty (alternatywa dla wskaźników) #include <iostream> using namespace std; void podwoj(int a, int &b) { a *= 2; b *= 2; } int main() { int a=1, b=2; podwoj(a,b); cout << "a=" << a << " b=" << b << endl; } a=1 b=4 • argument przesłany przez wartość – bez zmian; przesłany przez referencję – zmodyfikowany • powód – przez referencję przesyłamy adres argumentu (a nie jego wartość) • o tym, Ŝe jest to wywołanie argumentu przez referencję decyduje kształt definicji funkcji; samo wywołanie wygląda identycznie jak dla przypadku przekazania argumentu przez wartość • utrudnienie analizy kodu; niebezpieczeństwo nieumyślnej zmiany argumentu • główne zastosowanie – przesłanie do funkcji danych duŜych rozmiarów (struktur, klas) Przykład – test #include <cstdlib> #include <iostream> using namespace std; double gg(double a, double *c) { *c = *c + a; a=15; *c = *c - a; return a-3; } int main() { double a,b,c; a=2; c=3; b=gg(a,&c); cout << a << " " << b << " " << c << "\n"; system("PAUSE"); return 0; } • co wypisze powyŜszy program? • jak zmieni się odpowiedź, gdy pierwszy argument gg będzie referencją? Domyślne argumenty funkcji • w większości języków – funkcje muszą być wywołane z taką ilością argumentów, dla jakiej zostały zdefiniowane • C++ dostarcza mechanizmu zmiany tej zasady – poprzez domyślne argumenty funkcji • argument domyślny – taki, który został zainicjowany w deklaracji funkcji • o tym, Ŝe argument jest domyślny informujemy kompilator tylko raz (w deklaracji funkcji) Przykład: void ff(int a, int b=2, int c=4); main() { ff(1); ff(1,1); ff(1,1,1); } //deklaracja funkcji // ff(1,2,4) // ff(1,1,4) // ff(1,1,1) void ff(int a, int b, int c) { … } //definicja funkcji Uwaga: • nie da się opuścić argumentu b a podać c: zapis ff(1,,3) – błędny!!! ; w C++ nie moŜe wystąpić sytuacja, w której dwa przecinki są obok siebie • w trakcie kompilacji kompilator moŜe (w danym zakresie waŜności) napotkać definicję argumentu domniemanego tylko jednokrotnie void f1(int a, int b=2); void f1(int, int=2); // błąd, powtórzenie deklaracji void f2(int, int=3); void f2(int=2, int); // poprawne, wartości // domniemane: 3,2 void f3(int a=3, int b); void f3(int a, int b=2); // czy to jest // poprawne? • Typowe zastosowania: gdy mamy sytuację, w której wartość jednego argumentu często się powtarza. Przykład: funkcja drukująca temperaturę w jednej z trzech skal (Celsjusza, Kelvina i Fahrenheita); najczęściej w skali Celsjusza void temperatura(float stopnie, int skala=0) { cout << ”Temperatura wynosi: ” cout.setf(ios::fixed); cout.precision(1); // ustala notację liczb rzeczywistych // ustala dokładność switch (skala) { case 0: cout << 1.0*stopnie << endl; break; case 1: cout <<stopnie+273.2<<endl; break; case 2: cout << (9*stopnie/5+32.0)<<endl; break; } //Celsjusz //Kelvin //Fahrenheit } Wywołanie dla skali Celsjusza: temperatura(27.3); //temp(27.3, 0) Funkcje inline • dla krótkich funkcji (o krótkim ciele) efektywniej jest wpisać ciało funkcji w miejsce jej wywołania zamiast ją wywoływać (powód – koszt wywołania funkcji, zwłaszcza dla wielokrotnych wywołań) Przykład: int zao(float liczba) { return (liczba + 0.5); } lepiej zamiast: k=zao(m) + zao(n*13.3); wpisać: k=(int)(m+0.5) + (int)((n*13.3)+0.5); • jak to zrobić, by się duŜo nie napracować? o klasyczne C – poprzez makrodefinicje: #define zao(a) ((int)((a)+0.5)) o w C++ dodatkowa moŜliwość – deklaracja typu funkcji inline inline int zao(float liczba) { return (liczba + 0.5); } o inline (słowo kluczowe C++) – wskazanie dla kompilatora, by dokonywać rozwinięcia (wstawienia) ciała funkcji w miejsce wywołania o istnieje moŜliwość, Ŝe kompilator zignoruje tą deklarację i skompiluje funkcję normalnie (na pewno tak będzie, gdy kompilacja do pracy z debuggerem) o aby kompilator mógł wstawić ciało funkcji musi mieć całą definicję funkcji (a nie tylko deklarację) przed pierwszym uŜyciem funkcji o funkcje typu inline umieszczamy na samej górze tekstu programu (często w pliku nagłówkowym) o mechanizm inline – pomyślany tylko dla małych funkcji (i tylko dla takich powinien być stosowany)