Jeszcze o funkcjach
Temat ważny,
ponieważ nie istnieje program w
C/C++ bez funkcji (choćby
main())
Jeszcze o parametrach
przypomnienie sprzed tygodnia
W klasycznym C jedynym mechanizmem
wywołującym skutki nielokalne jest przekazanie
adresu (przez operator adresowy &)
Adres posłuży do zmiany komórek wskazanych
przez niego za pomocą L wyrażenia *adres
W C zastosowano odwołanie do
„niskopoziomowego rozumienia” zmiennej, tzn.
faktu, iż każda deklarowana zmienna posiada
miejsce w pamięci (adres) i zajmuje tę pamięć w
sposób charakterystyczny dla swego typu
A co dodatkowo w C++?
Możliwe jest połączenie parametrów aktualnych z formalnymi
przez nazwę (referencję) a nie tylko przez wartość
Deklarowana zmienna nie tylko ma jakiś adres (wyznaczany
operatorem &), ale sama jej nazwa jest nośnikiem tego adresu
Na potrzeby połączeń parametrów, wystarczy aby w opisie
parametrów formalnych pojawił się operator & przed nazwą
parametru formalnego aby w wywołaniu można było użyć
samej nazwy argumentu a w treści funkcji samej nazwy
parametru
Oczywistą konsekwencją jest ograniczenie takiego argumentu
do nazwy zmiennej (tylko zmienna ma adres określony po
kompilacji) o identycznym typie co parametr. Wykluczone jest
użycie argumentu będącego wyrażeniem (wyrażenie ma
wartość, lecz nie ma adresu – powstaje na stosie)
Wprowadzenie tego mechanizmu połączeń unowocześnia
istotnie C++ względem C (na wzór innych języków
programowania strukturalnego)
Złożone parametry funkcji
Tablice w parametrach
Jednowymiarowe
Wielowymiarowe
Struktury
Funkcje
Tablice jednowymiarowe w
parametrach funkcji
Najczęściej spotykane jest przekazanie adresu.
Jest to podejście klasyczne (działa w C i C++)
Jest elastyczne i uniwersalne (nie wymaga
„zanurzenia” nagłówka funkcji w definicji tablicy)
Argumentem jest nazwa tablicy –NAZWA TABLICY
JEST ADRESEM JEJ PIERWSZEGO ELEMENTU
W ciele funkcji używa się modyfikacji tego adresu
np.x++ lub odliczania od niego np. *(x+i)
Przykład ze statystyką –
wyznaczane są statystyki
randomizowanej tablicy
//nagłówki i definicje
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <conio.h>
const maxn = 100;
struct par_stat{
double srednia;
double odch;
double swob;
}; //definicja struktury – tzw. rekordu
void statystyka(double*,int,par_stat&);// par& to już
referencja C++
Statystyka cd
// funkcja główna
main()
{
double dane[maxn]; //tablica lokalna
int i,n;
par_stat parametry;
clrscr();
randomize();
for (i=0;i<maxn;i++) dane[i]=(double)random(100);
statystyka(dane,maxn,parametry);
printf("srednia= %5.2f odchylenie= %5.2f NDF=%3.0f",
parametry.srednia,parametry.odch,parametry.swob);
getchar();
}
Statystyka cd
//funkcja wyznaczająca statystyki
void statystyka(double *x,int ile, par_stat& w){
double suma=ile, sumax=0.0L, sumaxx=0.0L;
int j;
for (j=0;j<maxn;j++)
{sumax+=*x;
sumaxx+=*x**x;
x++;
}
w.srednia=sumax/suma; // kropka – operator
„wyłuskania”
w.odch=sqrt((sumaxx-sumax*sumax/suma)/(suma-1.0));
w.swob=suma-1.0;
}
Tablice wielowymiarowe w
parametrach
Rzadko spotykane w C
Wymagają zanurzenia nagłówka w definicji
tablicy, lub samodzielnego operowania
adresami (dokonywania linearyzacji –
ponieważ niezależnie od liczby wymiarów,
tablica zajmuje kolejne komórki pamięci)
W C++ przekazanie przez referencję, często z
tzw. tablicą otwartą, wówczas w ciele funkcji
spotykana konstrukcja dostępu, to np.
*(*(x+i)+k)
gdzie x – adres początku,
i – numer wiersza,
k - kolumny
Przykład trywialny – tworzenie
macierzy E
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <conio.h>
const maxn = 10;
typedef double mac[maxn][maxn];
void tworz(double [ ][maxn],int);//prototyp z tablicą otwartą
main(){
mac dane; int i,j,n;
clrscr(); tworz(dane,maxn);
for (i=0;i<maxn;i++)
{printf("\n");
for (j=0;j<maxn;j++) printf(" %5.1f",dane[i][j]); }
getchar();}
void tworz(double x[][maxn],int ile){
for (int j=0;j<ile;j++)
for (int k=0;k<ile;k++) *(*(x+j)+k)=j==k?1:0;
}
Przykład z szukaniem
pierwiastka
Metoda Newtona
pozwala na rozwiązanie równania
f(x)=0
Załóżmy, że rozwiązanie y znajduje się w przedziale [a,b],
istnieją ciągłe f’ i f’’, f’’ nie zmienia znaku w [a,b]
Przybliżeniem rozwiązania jest x
i
, h
i
jest błędem
przybliżenia
Mamy zatem
f(x
i
+h
i
)=0
Rozwijając w szereg Taylora (w pierwszym przybliżeniu)
f(x
i
+h
i
)=f(x
i
)+h
i
f’(x
i
)
Pamiętając, że h
i
zostało obliczone w przybliżeniu, mamy
x
i+1
=x
i
-f(x
i
)/f’(x
i
)
W praktyce, przerywamy iteracje, gdy |x
i+1
-x
i
|<eps
Interpretacja geometryczna –
kolejne przybliżenia w
przecięciu stycznych
Zbudujmy program szukający
rozwiązania f(x)=sin(x)-1/2x=0
FUNKCJE
Wartości f dla dowolnych parametrów – f
Wartości f’ dla dowolnych parametrów – fp
Wyznaczenie rozwiązania – newt
I OCZYWIŚCIE main()
Dane to:
pierwsze przybliżenie
dokładność
maksymalna liczba iteracji (po jej
przekroczeniu uznajemy, że metoda nie jest
zbieżna)
Istnieje kilka rozwiązań
to, które znajdziemy, zależy od
startu
Czas na program
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
double f(double),fp(double); //prototypy (i kilka na raz! Lista prototypów)
int newt(double*, double, int); // newt=0 zbieżny, 1 rozbieżny
main()
{
double x0,eps;
int n;
printf(” podaj przybl., dokładność i maks. l. iteracji \n”);
scanf(”%lf %lf %d”, &x0,&eps,&n); //uwaga na &
if(!newt(&x0,n,eps))
printf(”Rozwiązanie w %f \n”,x0);
else
printf(”Proces rozbieżny \n”);
getchar();
} // to koniec głównej funkcji
A teraz czas na zdefiniowanie
zapowiedzianych prototypami
funkcji
// funkcja newt
int newt(double *x, int n, double eps)
{
double x1; // potrzeba dwóch x dla oceny
int i=0; // licznik iteracji
do
{
x1=-f(*x)/fp(*x); //poprawka
*x+=x1; //poprawiam rozwiązanie
if(fabs(x1)<eps) return 0; //osiągnięta dokładność
} while(i++<n); //przekroczono l.iteracji
return 1; //gdy nie osiągnięto
zadowalającej dokł.
} // koniec newt, 0 jest rozwiązanie, 1 brak
I jeszcze funkcje z równania
double f(double x)
{
return sin(x)-0.5*x;
}
double fp(double x)
{
return cos(x)-0.5;
}
Uwagi o programie
Funkcja newt, niestety sztywno rozwiązuje
wyłącznie równanie f(x)=0, pod warunkiem,
że funkcja opisana jest w f a jej pochodna w
fp
A przecież metoda Newtona jest ogólna (a
funkcja Newt NIE)
Gdyby funkcji newt można przekazać przez
parametry na czym ma działać (jakimi
funkcjami się posługiwać do wyznaczania
wartości funkcji i jej pochodnej)byłaby
uniwersalna
Zmieniony program
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
double f(double),fp(double); //prototypy
int newt2(double*, double, int, double (*)(double), double (*)(double)); //NOWOŚĆ
C++
// newt2=0 zbieżny, 1 rozbieżny
main()
{
double x0,eps;
int n;
printf(” podaj przybl. dokł. i maks. l. iteracji \n”);
scanf(”%lf %lf %d”, &x0,&eps,&n); //uwaga na &
if(!newt(&x0,n,eps,f,fp))
printf(”Rozwiązanie w %f \n”,x0);
else
printf(”Proces rozbieżny \n”);
getchar();
} // to koniec głównej funkcji
Poprawiona funkcja newt
int newt2(double *x, int n, double eps,
double (*f1)(double), double (*f2)(double))
{
double x1;
int i=0;
do
{
x1=-(*f1)(*x)/(*f2)(*x);
*x+=x1;
if (fabs(x1)<eps) return 0;
} while(i++<n);
return 1;
}
//funkcje pozostają bez zmian
//gdy użyjemy innych funkcji, wystarczy podać to w wywołaniu
Co warto wiedzieć o takich
parametrach?
Funkcje nie są zmiennymi
Identyfikator funkcji jest typu „wskaźnik do funkcji”
(i ma wartość adresu funkcji)
Można wykonać operacje na wskaźnikach do funkcji
Można zapisywać wskaźniki do funkcji w tablicach
Wskaźniki do funkcji można przekazywać jako
argumenty innych funkcji. Parametr jest wówczas
wskaźnikiem do funkcji z lokalną nazwą
Informacja o typie wartości zwracanej przez funkcję
musi być uwzględniona w deklaracji
UWAGA
Między tablicami i funkcjami istnieje
analogia:
nazwa tablicy jest adresem jest początku
nazwa funkcji jest jej wskaźnikiem
Do funkcji nie można przekazać tablicy tylko
jej adres
Do funkcji nie można przekazać funkcji tylko
wskaźnik do niej
Bardziej złożone typy
(na struktury przyjdzie czas)
int f[]; //tablica o elementach int
int *f[]; //tablica wskaźników do int
int f(); //funkcja zwracająca wartość int
int *f(); //funkcja zwr. wskaźnik do int
int (*f)(); //wskaźnik do funkcji
zwracającej int
int *(*f)(); // wskaźnik do funkcji zwr. wsk.
do int
int (*(f[]))(); //tablica wskaźników do funkcji
zwr. int
Pliki
W stylu C
W stdio dostarczony jest zestaw funkcji
realizujących operacje we/wy, które pozwalają
zapisać i odczytać dane ze zbiorów lub
urządzeń.
ANSI C stosuje tzw. strumienie do zaznaczania
abstrakcyjnego portu, poprzez który dane mogą
przepływać jedno lub dwukierunkowo.
Strumień ANSI C jest uważany za konstrukcję
niskiego poziomu, na którą jest nakładany
bardziej złożony system plikowy.
Strumienie są reprezentowane przez zmienne
typu FILE. Konstrukcja FILE jest biblioteczną.
Funkcje strumieniowe traktują zbiór danych jako
ciąg pojedynczych znaków, które mogą być
później grupowane w formatowanych
operacjach we/wy.
Jak skorzystać z pliku (w C)?
Kilka kroków
(jak w TP, Moduli i
innych)
Zadeklarowanie wskaźnika do FILE
„Otwarcie” pliku – identyfikacja
zbioru/portu, określenie dopuszczalnych
operacji, skojarzenie wskaźnika FILE* z
danymi
fopen
Operacje transmisji z/do pliku
różne funkcje z biblioteki stdio
Zamknięcie pliku
fclose
Funkcja
Opis
Wartość
zwracana
FILE *fopen
(const char
*namefile,
const char
*mode)
int fclose
(FILE *stream)
Funkcja otwiera plik i wiąże z tym plikiem
strumień.
namefile - nazwa pliku
mode - tryb ustawienia pliku
r - do czytania. Zbiór musi istnieć
w - otwiera pusty zbiór do zapisu, jeśli
plik istnieje kasuje jego zawartość
a - do zapisu na końcu zbioru. Jeśli plik
nie istnieje tworzy nowy
r+ - otwiera istniejący plik do zapisu i
odczytu
w+ - otwiera pusty plik do zapisu i
odczytu. Istniejący plik kasuje
a+ - otwiera do odczytu i zapisu na końcu
pliku. Jeżeli plik nie istnieje to go
tworzy.
b - otwiera plik w trybie binarnym
a i a+ - zapisuje zawsze na końcu mimo
użycia funkcji pozycjonującej fseek()
w i w+ - zawsze kasuje istniejący plik
zamyka strumień określony parametrem
wywołania funkcji.
wartości funkcji: 0-poprawne zamknięcie,
EOF - błąd operacji
Funkcja
otwiera plik i
wiąże z tym
plikiem
strumień.
Przykład
#include<conio.h>
#include<stdio.h>
int main() {
char *nazwa_pliku = "tekst.txt"; //w domyślnej lokalizacji
FILE *plik;
clrscr();
if (plik = fopen (nazwa_pliku, "rt")){
printf ("poprawne otwarcie pliku tekstowego do
czytania\n");
fclose(plik);
}
else
printf ("błąd otwarcia pliku do czytania\n");
getch();
}
Nazwy predefiniowane
Nazwa
Zastosowanie
stdin strumień wejściowy (konsola)
stdoutstrumień wyjściowy (konsola)
stderr strumień komunikatów błędów (konsola)
stdaux
strumień pomocniczy (konsola)
stdprn
strumień drukarki
Zdefiniowane stałe
EOF – znacznik końca pliku (-1)
NULL – wskaźnik zerowy
BUFSIZ – bieżący rozmiar bufora
Podstawowe funkcje obsługi
transmisji:
znaki i łańcuchy
Operowanie znakami: fgetc(…); fputc(…);
int fgetc(FILE *stream) czyta znak ze
strumienia stream jako unsigned char i
przekształca do int . Zwraca znak czytany lub
EOF, jeśli powstał błąd lub napotkano EOF.
Operowanie łańcuchami:fgets(…); fputs(…);
char *fgets(char *string, int n, FILE
*stream) odczytuje ze strumienia stream ciąg
znaków aż do:- pierwszego znaku '\n'
(włącznie)- do końca strumienia- lub, gdy
przeczytano n-1 znakówi wstawia je do stringu
dołączając na końcu '\0' Zwraca string lub,
NULL jeśli powstał błąd lub napotkano EOF.
#include<conio.h>
#include<stdio.h>
int main() {
char *nazwa_pliku = "tekst.txt"; char znak, str[20],
buf[100]; FILE *plik;
clrscr();
if (plik = fopen (nazwa_pliku, "w+t"))
printf ("poprawne otwarcie pliku do zapisu i odczytu");
else { printf ("błąd otwarcia pliku"); return 1; }
printf ("\npodaj tekst: ");
gets (str);
fputs (str, plik);
rewind (plik);
printf ("\ntekst odczytany z pliku znak po znaku : ");
while ((znak=fgetc(plik))!=EOF) printf ("%c", znak);
getch();
}
Podstawowe funkcje obsługi
transmisji:
formatowana transmisja
Funkcja
Opis
Wartość zwracana
int
fprintf
(FILE
*stream, const
* format,...);
Funkcja formatuje i
wpisuje dane do
strumienia określonego
parametrem stream.
Zwraca liczbę
zapisanych znaków lub
-1 gdy wystąpił błąd.
int fscanf
(FILE
*stream, const
* format,...);
Funkcja czyta dane ze
strumienia określonego
parametrem stream, od
bieżącej pozycji, i
umieszcza w miejscu
pamięci wskazanym w
parametrze
nieustalonym.
Zwraca liczbę
przepisanych danych
wejściowych, gdy
wystąpił błąd:0
EOF po napotkaniu
końca zbioru
Pliki binarne
Funkcja
Opis
Wartość zwracana
size_t fread
(void *buf, size_t
size, size_t n, FILE
*stream)
Odczytuje ze
strumienia stream, co
najwyżej n obiektów o
rozmiarze size bajtów i
zapamiętuje je w bloku
pamięci wskazywanym
przez parametr buf.
Zwraca liczbę
przeczytanych
obiektów ( nie
bajtów), w
przypadku błędu lub
końca pliku to
zwraca liczbę
mniejszą od n.
size_t fwrite (const
void *buf, size_t
size, size_t size,
FILE *stream)
Wysyła do strumienia n
obiektów o rozmiarze
size pobranych z
obszaru pamięci
wskazywanego
parametrem buf.
Zwraca liczbę
wysłanych obiektów,
w przypadku błędu
{zwraca liczbę
mniejszą od n.
Sterowanie wskaźnikiem
pozycji
int
fseek
(FILE
*stream,
long offset,
int pos)
Funkcja przesuwa
wskaźnik pozycji w pliku
do miejsca oddalonego o
offset (typu long) bajtów
od pozycji pos:
SEEK_SET- początek
pliku
SEEK_CUR - bieżąca
pozycja
SEEK_END - koniec
zbioru
Zwraca 0 jeśli OK,
lub != 0 jeśli błąd.
Uwaga!!!
Dla plików
tekstowych fseek
ma ograniczone
użycie, bo zamiana
CR i LF na jeden
znak może
spowodować, że
fseek() da
nieoczekiwane
rezultaty.
void rewind(fp); Przesuwa wskaźnik pozycji w
pliku na jego początek.
Sterowanie wskaźnikiem
pozycji
long
ftell(FILE
*stream);
Podaje bieżącą
pozycję wskaźnika
w pliku, liczoną
jako przesunięcie
względem początku
zbioru.
Zwraca -1L
jeśli błąd, w
przeciwnym
wypadku
wartość
niezerową
sterowanie
int
fgetpos(fp
, pos)
Zapamiętuje w pos
bieżącą pozycję
wskaźnika
strumienia.
Zwraca, 0 gdy
poprawnie
zakończona lub
!= 0 gdy błąd.
int
fsetpos(fp
, pos)
Ustawia wskaźnik
pozycji w pliku wg
parametru pos.
Zwraca 0 gdy
poprawnie
zakończona lub
!= 0 gdy błąd.
Biblioteka strumieni w C++
Biblioteka została stworzona jako hierarchia
klas zadeklarowanych w wielu plikach
nagłówkowych
iostream.h – podstawowy
io.h – deklaracje na najniższym poziomie
istream.h, ostream.h – podstawowe klasy
we/wy
fstream.h - zawiera ifstream.h i ofstream.h –
podstawowe operacje klas strumieni
Ogólne funkcje we/wy
strumienia
(metody klas strumieniowych)
funkcja void open – umożliwia otwarcie pliku
strumienia do zapisu/odczytu/dopisywania
oraz określenie, czy operacje we/wy
prowadzone są na tekście czy na danych
binarnych
funkcja void close – beparametryczna,
zamyka strumień
sprawdzanie poprawności operacji, funkcje
int good (zero – błąd operacji)
int fail (zero – poprawna operacja)
Przeciążony operator ! Stosowany do zmiennych
strumienia dla określenia błędu
Sekwencyjne strumienie we/wy
dla plików tekstowych
Funkcje i operatory związane z plikami
tekstowymi są proste
Operator << umieszczający dane w
strumieniu (łańcuchy i znaki)
Operator >> pobierający dane ze
strumienia
Funkcja getline – wczytywanie łańcuchów
ze strumienia (z podaną maksymalną
liczbą znaków i ogranicznikiem)
Przykład: przepisanie pliku
wej.txt do wyj.txt z zamianą
znaków ‘a’ na ‘A’
#include <iostream.h>
#include <fstream.h>
#include <conio.h>
int main()
{ fstream plikWe, plikWy;
char linia[129]; clrscr();
plikWe.open(”wej.txt”,ios::in);
if(!plikWe) {cout<<”Brak pliku”;return 1;}
plikWy.open(”wyj.txt”,ios::out);
while (plikWe.getline(linia,128)){
for (int i=0;linia[i]!=‘\0’;i++) if (linia[i]==‘a’)linia[i]=‘A’;
plikWy<<linia<<‘\n’; cout<<linia<<‘\n’;
}
plikWe.close(); plikWy.close();
getch(); return 0;
}
Strumienie dla plików
binarnych
Funkcja write wysyła bajty we wskazanej
liczbie z bufora do strumienia wyjściowego
(przypomina Pascalowy BlockWrite)
Funkcja read pobiera bajty we wskazanej
liczbie do bufora ze strumienia wejściowego
(przypomina Pascalowy BlockRead)
Funkcja seekg pozwala ustalić przeniesienie
wskaźnika strumienia do dowolnego,
dozwolonego położenia (także względem:
początku, bieżącej pozycji lub końca)