Wykład 4. Funkcje własne

Transkrypt

Wykład 4. Funkcje własne
Wykład 4. Funkcje własne
4.1. Programowanie strukturalne
Programowanie strukturalne jest jedną z metod (paradygmatów) programowania. Metoda ta polega ona na dekompozycji
dużego projektu programistycznego na mniejsze zadania cząstkowe.
Zadania cząstkowe wykonywane są przez funkcje, które przekazują sobie dane za pośrednictwem argumentów.
Funkcja jest to podprogram o określonym działaniu, stanowiący element biblioteki języka C++, lub napisany przez programistę jako tzw. funkcja własna.
4.2. Zalety programowania strukturalnego
Podział programu na funkcje, realizujące wydzielone zadania składowe, ułatwia opracowanie projektu.
Gotowy program można łatwo ulepszać i rozwijać, modyfikując funkcje własne.
Można utworzyć biblioteki własnych funkcji z danej
dziedziny zastosowań, ułatwia pisanie innych programów z danej dziedziny.
Ta sama funkcja może być bez zmian wykorzystana
w różnych programach.
Jeżeli jakieś zadanie cząstkowe jest wielokrotnie wykonywane w różnych miejscach programu, to powierzenie
tego zadania funkcji własnej skutkuje znacznym skróceniem tekstu programu.
2
4.3. Wywoływanie funkcji w main
W celu uruchomienia w ciele main funkcji (własnej lub bibliotecznej), trzeba wykonać instrukcję wywołania tej funkcji.
Wywołanie funkcji w main:
int main()
{
instrukcja;
instrukcja;
instrukcja_wywołania;
instrukcja_wywołania; → //rozpoczęcie wykonania
{
instrukcja;
instrukcja;
---instrukcja;
}
← //powrót do main
instrukcja;
instrukcja;
===
return 0;
}
W chwili, gdy kolejną instrukcją main jest instrukcja wywołania podprogramu, następuje przerwanie działania main. Następnie rozpoczyna się wykonanie podprogramu. Po jego
zakończeniu następuje automatyczny powrót do kolejnej instrukcji w main – następnej po instrukcji wywołania funkcji.
3
4.3. Definicja funkcji własnej
Ogólna postać definicji funkcji
typ_funkcji nazwa_funkcji (typ nazwa_argumentu,
typ nazwa_argumentu, . . . )
{
instrukcja;
instrukcja;
// - - - instrukcja;
}
Typ funkcji jest to typ wartości, jaką przyjmuje instrukcja
wywołania tej funkcji po zakończeniu jej działania.
Na przykład funkcja biblioteczna sqrt jest typu double.
double y=sqrt(x);
wartość sqrt(x) po prawej stronie operatora = zostaje zapamiętana w zmiennej y, której typ jest zgodny z typem funkcji.
Istnieją funkcje o typie void.
Instrukcja wywołania funkcji typu void nie przyjmuje żadnej
wartości.
gFunkcji typu void nie wolno wywoływać w wyrażeniach, ani
po prawej stronie instrukcji przypisania. Jej wywołanie ma
postać samodzielnej instrukcji, np:
putch(’A’); //napisanie litery A
Nazwa funkcji nadawana jest dowolnie przez programistę.
Argumenty funkcji służą do pobrania przez funkcję danych
do obliczeń (argumenty wejściowe) lub przekazania wyników
obliczeń (argumenty wyjściowe).
4
4.4. Miejsce definicji funkcji własnej w programie
Przykład 1
Funkcja typu void z argumentami wejściowymi
Zadaniem funkcji jest dodanie argumentów i wyprowadzenie
wyniku na ekran.
#include <studio.h>
#include <conio.h>
void dodaj(int,int);
//prototyp funkcji
int main()
}
int a,b;
printf("\nPodaj dwie dodawane liczby: ");
scanf("%d%d",&a,&b);
dodaj(a,
// ↓
getch(); // ↓
return 0;// ↓
}
// ↓
// ↓
// ↓
// ↓
void dodaj(int x,int
{
int s;
s=x+y;
printf(”%d”,s);
}
//powrót do main →
b); //wywołanie → dodaj
↓
↓
// autom. przypisania:
↓
// x=a, y=b
↓
↓
↓
↓
y) //definicja funkcji
Definicje funkcji własnych piszemy za main.
Prototypy (deklaracje) funkcji własnych piszemy przed main.
5
Prototypy są to nagłówki funkcji, w których można pominąć
nazwy argumentów, ale trzeba napisać ich typy. W odróżnieniu
od nagłówka definicji, prototyp jest zakończony średnikiem.
Dzięki obecności prototypu kompilator może rozpoznać nazwę
funkcji oraz sprawdzić, czy w instrukcjach wywołania użyto
odpowiedniej (zgodnej z definicją) liczby, typów i kolejności
argumentów.
Prototypy funkcji bibliotecznych znajdują się w zbiorach nagłówkowych, włączanych do programu za pomocą dyrektyw
#include.
4.5. Przekazywanie danych wejściowych z main do funkcji
Instrukcja wywołania funkcji dodaj(a,b) powoduje:
(a) przekopiowanie argumentów wywołania do argumentów
funkcji
(b) uruchomienie sekwencji instrukcji zawartych w ciele
funkcji
Po zakończeniu działania funkcji następuje przejście do kolejnej instrukcji main .
dodaj(a,
void dodaj(int x,
{
int s;
s=x+y;
printf(”%d”,s);
} //powrót do main
b);
int y) //def. funkcji
b
a
5
118
y = b;
x = a;
x
y
5
118
6
Liczba argumentów w instrukcji wywołania funkcji musi być
taka sama, jak w definicji funkcji; także ich kolejność i typy
muszą być zgodne.
Przekazanie danych odbywa się jednokierunkowo. Ewentualne
zmiany wartości zmiennych x, y w czasie działania funkcji
dodaj nie wpływają na stan zmiennych a, b w funkcji main.
4.6. Zmienne wskaźnikowe
Zmienne wskaźnikowe (zwane w skrócie wskaźnikami) służą
do przechowania adresów innych zmiennych określonego typu.
Zmienną wskaźnikową deklarujemy, pisząc symbol ’*’ przed
nazwą zmiennej. Na przykład:
float *wf; // zmienna wf zawiera adres zmiennej typu float
double *wd; // zmienna wd zawiera adres zmiennej typu double
int *wi;
// zmienna wi zawiera adres zmiennej typu int;
Chcąc wpisać do zmiennej wskaźnikowej konkretny adres
innej zmiennej, stosujemy operator adresu &, jak poniżej:
int x=9;
int *wx;
wx=&x;
To samo można uzyskać, stosując krótszy zapis, jak poniżej:
int x=9, *wx=&x;
Zmienna wx wskazuje na zmienną x (jest jej wskaźnikiem),
Zmienna x jest zmienną wskazywaną przez wx.
W C++ istnieje stała o nazwie NULL, która oznacza adres
pusty.
Wskaźnik o zawartości NULL nie wskazuje na żadną komórkę
pamięci. Ilustrację tych pojęć stanowi rysunek:
int x=9, *wx=&x;
7
wx
x
FFF4 –––––→
&x==
FFF4
9
wx=NULL;
wx
NULL
Utworzenie wskaźnika na zmienną x pozwala odwołać się do
zmiennej x za pośrednictwem tego wskaźnika.
W tym celu zmienną wskaźnikową poprzedzamy gwiazdką:
double x,*wx=&x; //utworzenie wskaźnika na zmienną x
*wx=3.56; //odwołanie się przez wskaźnik do zmiennej x
4.7. Mechanizm przekazywania danych z funkcji do main
za pomocą argumentu wskaźnikowego
Przekazywanie wyniku przez wskaźnik opiera się na bardzo
prostym pomyśle. Funkcja ma dodatkowy argument – wskaźnikowy. Po wywołaniu funkcji, do tego argumentu wskaźnikowego zostaje przekopiowany adres tej zmiennej funkcji
main, do której chcemy przekazać wynik. Odwołując się
w ciele funkcji do tego adresu, przekazujemy wynik do main.
Pokazuje to przykład 2.
8
Przykład 2
Zadaniem funkcji jest dodanie wartości dwóch liczb całkowitych.
Funkcja jest typu void i posiada dwa argumenty wejściowe
i jeden argument wskaźnikowy.
Argumenty wejściowe służą do przekazania z main do funkcji
wartości dodawanych liczb.
Argument wskaźnikowy służy do przekazania z main do funkcji adresu zmiennej, do której należy przesłać wynik.
#include <studio.h>
#include <conio.h>
void dodaj(int,int,*int);
//prototyp funkcji
int main()
}
int a,b,suma;
printf("\nPodaj dwie dodawane liczby: ");
scanf("%d%d",&a,&b);
dodaj(a,
b,
&suma); //wywołanie
printf(”%d”,suma);↓
↓
↓
getch();
// ↓
↓
↓
↓
↓ // x=a,y=b
return 0;
// ↓
}
// ↓
↓
↓ // w=&suma
// ↓
↓
↓
// ↓
↓
↓
// ↓
↓
↓
void dodaj(int x,int y,
int* w) //definicja
{
int s;
s=x+y;
*w=s; //przekazanie wyniku pod adres w==&suma
}
//skok do instrukcji printf w main →
9
Przykład 3. Program z funkcją własną pierw_2, obliczającą
metodą Newtona z dokładnością EPS pierwiastek kwadratowy
z liczby x. Funkcja jest typu void, ma dwa argumenty wejściowe do przekazania wartości x, EPS. Funkcja przekazuje wynik
obliczeń za pośrednictwem argumentu wskaźnikowego.
#include <stdio.h>
#include <conio.h>
#include <math.h>
void pierw_2(double,double,double *); //prototyp
int main()
{
const double EPS=1e-6;
double wynik,x;
printf("\nPodaj x: ");
scanf("%lf",&x);
pierw_2(x,EPS,&wynik); //wywołanie funkcji
printf("\nWynik: %.6lf",wynik);
getch();
return 0;
}
void pierw_2(double x, double eps, double *w)
{
double p;
if (x<eps*eps) p=0;
else
{
if(x>1.0) p=x/2.0; else p=x*2.0;
double p0=p+2*eps;
while(fabs(p0-p)>=eps)
{
p0=p;
p=(x+p*p)/(2*p);
}
}
*w=p; //przekazanie p pod adres w==&wynik
}
10
4.8. Przekazywanie danych z funkcji do main przez
argumenty referencyjne
Relacja referencji
Stosując operator referencji &, jak w zapisie poniżej:
int x, &y=x;
gdzie & jest operatorem referencji,
powodujemy, że obie zadeklarowane w ten sposób zmienne:
x oraz y, pozostają w związku referencyjnym.
Oznacza to, że obie zmienne są zlokalizowane w tym samym
obszarze pamięci. Wszelkie zmiany wartości jednej z tych
zmiennych będą dotyczyły także drugiej zmiennej.
Można powiedzieć, że jedna zmienna ma dwie różne nazwy:
x oraz y.
Jeżeli utworzymy referencję pomiędzy argumentem wyjściowym funkcji, a zmienną użytą w instrukcji wywołania, to
wszelkie działania na argumencie wyjściowym będą jednocześnie działaniami na zmiennej w instrukcji wywołania. Dzięki
temu można wyniki działania funkcji bezpośrednio przekazywać do main.
dodaj(a,b,suma);//instrukcja wywołania
void dodaj(int x,int y,int &w) //nagłówek funkcji
W wyniku wywołania występuja automatyczne operacje:
int x=a; int y=b; int &w=suma;
Zatem zmienne w, suma są w związku referencyjnym – jest to
ta sama zmienna o dwóch różnych nazwach!
11
Przykład 4. Funkcja dodaj, która przekazuje sumę swoich
argumentów wejściowych przez argument referencyjny
#include <studio.h>
#include <conio.h>
void dodaj(int,int,&int);
//prototyp funkcji
int main()
}
int a,b,suma;
printf("\nPodaj dwie dodawane liczby: ");
scanf("%d%d",&a,&b);
dodaj(a,
b,
suma);//wywołanie
printf(”%d”,suma);↓
↓
¦
getch();
// ↓
↓
¦
return 0;
// ↓
↓
¦//x=a,y=b,
}
// ↓
↓
¦//&w=suma
↓
¦
// ↓
// ↓
↓
¦
// ↓
↓
¦
void dodaj(int x,int y,
int &w) //definicja
{
int s;
s=x+y;
w=s; //przekazanie wyniku s do &w=suma
}
//skok do instrukcji printf w main →
12
Przykład 5. Program z funkcją własną pierw_2, obliczającą
metodą Newtona z dokładnością EPS pierwiastek kwadratowy
z liczby x. Funkcja przekazuje wynik obliczeń przez argument
referencyjny.
#include <stdio.h>
#include <conio.h>
#include <math.h>
void pierw_2(double,double, double &); //prototyp
int main()
{
const double EPS=1e-6;
double wynik,x;
printf("\nPodaj x: ");
scanf("%lf",&x);
pierw_2(x,EPS, wynik); //wywołanie funkcji
printf("\nWynik: %.6lf",wynik);
getch();
return 0;
}
void pierw_2(double x,double eps, double &w)
{
double p;
if (x<eps*eps) p=0;
else
{
if(x>1.0) p=x/2.0; else p=x*2.0;
double p0=p+2*eps;
while(fabs(p0-p)>=eps)
{
p0=p;
p=(x+p*p)/(2*p);
}
}
w=p; //przekazanie wartości p do &w=wynik
}
13
4.9. Przekazywanie danych za pomocą instrukcji return
Wykonanie zawartej w bloku funkcji instrukcji return powoduje, że instrukcja wywołania funkcji jako całość przyjmuje taką
wartość, jak wyrażenie po słowie return.
Wstawiając po słowie return wynik obliczeń, przekazujemy go
więc jako wartość, którą przyjmuje instrukcja wywołania:
Funkcja biblioteczna sqrt zwraca wynik typu double przez
return. Przy wywołaniu:
x=sqrt(2);
wyrażenie sqrt(2) przyjmie wartość 1.414... i ta wartość zostaje
zapamiętana w zmiennej x.
Można wywołać tego rodzaju funkcję jako argument printf:
printf(”%.3lf”,sqrt(2));
co spowoduje wyprowadzenie wartości 1.414 na ekran.
Wywołując funkcję sqrt w taki sposób:
sqrt(2);
również spowodujemy jej uruchomienie. Nie da to jednak żadnej korzyści, bo ta wartość nie zostanie nigdzie zapamiętana,
ani wyprowadzona na ekran.
Funkcja zawierająca return nie może być typu void – funkcja
ma taki typ, jak wyrażenie po return.
14
Przykład 6. Funkcja dodaj, która przekazuje sumę swoich
argumentów wejściowych przez return
#include <studio.h>
#include <conio.h>
int dodaj(int,int);
//prototyp funkcji
int main()
}
int a,b,suma;
printf("\nPodaj dwie dodawane liczby: ");
scanf("%d%d",&a,&b);
suma=dodaj(a,b);//wywołanie
printf(”%d”,suma);
getch();
return 0;
}
int dodaj(int x,int y) //definicja funkcji
{
int s;
s=x+y;
return s; //przekazanie wyniku pod &w=suma
}
//skok do instrukcji printf w main →
Przykład 7. Program z funkcją własną pierw_2, obliczającą
metodą Newtona z dokładnością EPS pierwiastek kwadratowy
z liczby x. Funkcja przekazuje wynik obliczeń przez return.
#include <stdio.h>
#include <conio.h>
#include <math.h>
double pierw_2(double,double); //prototyp
15
int main()
{
double p,x;
const double EPS=1e-6;
printf("\nPodaj argument pierwiastka: ");
scanf("%lf",&x);
p=pierw_2(x,EPS);
//wywołanie funkcji
printf("\nWynik: %.6lf",p);
getch();
return 0;
}
//definicja funkcji
double pierw_2(double x,double eps)
{
double p;
if (x<eps*eps) p=0;
else
{
if(x>1.0) p=x/2.0; else p=x*2.0;
double p0=p+2*eps;
while(fabs(p0-p)>=eps)
{
p0=p;
p=(x+p*p)/(2*p);
}
}
return p; //przekazanie wyniku
}
Na zakończenie tego punktu, zapamiętajmy, że:
Funkcja, która nie jest typu void, musi zawierać przynajmniej jedną instrukcję return.
Funkcja typu void nie może zawierać instrukcji return.
Wyrażenie po return musi mieć typ zgodny z typem
funkcji.
Przez return można zwracać tylko jedną wartość. Jeżeli
funkcja ma zwrócić więcej danych, to trzeba ją zaopatrzyć w odpowiednie argumenty wyjściowe – wskaźnikowe lub referencyjne.
16
4.10. Funkcja wywołująca samą siebie – rekurencja
Funkcja może wywoływać w swoim bloku samą siebie. Daje to
możliwość prostego programowania obliczeń rekurencyjnych,
które stanowią istotę wielu algorytmów. Zaletą procedur rekurencyjnych jest duża prostota zapisu, a ich wadą – duża zajętość obszaru pamięci przeznaczonego na przechowanie
zmiennych automatycznych.
Przykład 8. Funkcje znajdujące n-ty wyraz szeregu Fibonacci:
1, 1, 2, 3, 5, 8, 13, 21 … Dwa pierwsze wyrazy szeregu są
jedynkami, a każdy następny wyraz jest sumą dwóch wyrazów
poprzedzających. Oznaczając n–ty wyraz szeregu jako wn,
można to zapisać następująco:
Jeśli n<3, to wn=1, w przeciwnym przypadku wn = wn-1 + wn-2.
Zapis ten prowadzi bezpośrednio do pokazanej realizacji rekurencyjnej funkcji własnej fibon.
Przykład 8. Obliczenie n-tego wyrazu szeregu Fibonacci
#include <conio.h>
#include <stdio.h>
double fibon(unsigned); //prototyp
int main()
{
printf("Podaj numer wyrazu: ");
unsigned n;
scanf("%d",&n);
printf("Wyraz nr %u: %le",n, fibon(j));
getch();
return 0;
}
17
//definicje funkcji fibon
//wersja rekurencyjna
double
fibon(unsigned n)
{
double w1,w2;
if (n<3) return 1;
w1=fibon(n-1);
w2=fibon(n-2);
return w1+w2;
}
//wersja iteracyjna
double
fibon(unsigned n)
{
double
post=1,ost=1,wyraz;
if(n<3) return 1.0;
for(int j=2;j<=n;j++)
{
wyraz=post+ost;
post=ost;
ost=wyraz;
}
return wyraz;
}

Podobne dokumenty