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)