Alokacja pamięci dla tablicy dwuwymiarowej struktura tablicy float a[3][4]; /* rezerwacja 3*4 elementów typu float */ a - adres początku dwuwymiarowej tablicy a[0] - adres początku pierwszej tablicy składowej a[1] - adres początku drugiej tablicy składowej a[2] - adres początku trzeciej tablicy składowej stała stała zmienna zmienna zmienna adres adres typ float typ float typ float a a[0] a[0][0] a[0][1] a[0][2] a[1] a[1][0] a[1][1] a[1][2] a[2] a[2][0] a[2][1] a[2][2] aby poprawnie obsłużyć tablicę musimy znać adresy: początku całej tablicy oraz początków wektorów składowych dla normalnej (statycznej) tablicy jest to określone przez nazwy tablic składowych a[ ][N] (np. w definicji funkcji zależnej od tablicy: void ff(float a[][n]);) dla tablicy określonej dynamicznie te adresy musimy podać dodatkowo zwykle stosowany sposób organizacji: o wskaźnik do tablicy jako całości wskazuje tablicę z adresami początków wszystkich tablic składowych o tablica wskaźnikowi wskaźnik do tablicy adresów wskaźnik do wskaźnika do typu składowego tablicy sposób dynamicznej alokacji tablicy dwuwymiarowej (np. typu float o potrzebna znajomość adresów początków wierszy zapisane w tablicy adresów pokazuje na nią wskaźnik do adresów (wskaźników) do float: float **a;
o potrzebna alokacja miejsca do przechowania adresów początków wierszy danych tablicy różne metody alokacji o metoda pierwsza: robimy dynamiczną alokację tablicy o rozmiarze w (ilości wierszy tablicy docelowej); adres początku tej tablicy zapisujemy w a alokujemy oddzielnie każdy wiersz tablicy; adres początku i-tego wiersza wpisujemy jako a[i] o metoda druga: alokujemy jednorazowo pamięć dla całej tablicy (adres początku obszaru zapisujemy w pomocniczym wskaźniku b) alokujemy pamięć dla adresów początku wierszy (wskazuje ją a), wpisujemy adresy początków wierszy jako pozycje tablicy którą wskazuje a o gdy chcemy zlikwidować tablicę a musimy dokonać odpowiednich zwolnień pamięci we właściwym porządku Przykład: dynamiczna alokacja tablicy float o rozmiarach N x K, przykład użycia, zwolnienie pamięci Metoda 1: #include <iostream> int main() cout << "Podaj ilosc wierszy i kolumn tablicy: "; int N, K; cin >> N >> K; float **a; // wskaźnik do tablicy jako całości a= new float* [N]; // alokacja wektora adresów wierszy for (int k=0; k<n; k++) a[k]=new float[k]; //alokacja tablic - wierszy a // użycie tablicy for(int i=0; i<n; i++) for(int j=0; j<k; j++) a[i][j]=0.0; // zwykła obsługa tablicy
int mniej; mniej=(n<k?n:k); for(int k=0; k<mniej; k++) *(*(a+k)+k)=1.0; // wersja wskaźnikowa for(int k=0; k<n; k++) for(int j=0; j<k; j++) cout << *(*(a+k)+j) << " "; cout << endl; // zwolnienie pamięci for (int k=0; k<n; k++) delete [ ] a[k]; delete [ ] a; system("pause"); Metoda 2: #include <iostream> #include <iomanip> int main() cout << "Podaj rozmiary (N x K) : "; int N,K; cin >> N >> K; int ** a; int *b; b=new int[n*k]; a = new int* [N]; for(int i=0; i<n; i++) a[i]=&b[i*k]; for(int i=0; i<n; i++) for(int j=0; j<k; j++) // *(*(a+i)+j)=(100*(i+1)+j+1); a[i][j]=100*(i+1)+j+1;
for(int i=0; i<n; i++) for(int j=0; j<k; j++) cout << setw(5) << a[i][j] << " "; cout << "\n"; //zwolnienie pamieci delete [] a[0]; delete [] a; system("pause"); return EXIT_SUCCESS; Typy wskaźników rzutowanie wskaźnik niesie wiedzę o typie na który wskazuje może wskazywać tylko na obiekty określonego typu int *wi1, *wi2, o1; double *wd; wi1=&o1; //O.K. wi2=wi1; //O.K. wd=wi1; //niedozwolone!!! możemy wymusić podobne podstawienia poprzez rzutowanie: wd=(double *) w1; wd=reinterpret_cast<double *> (w2) za sensowność tego rzutowania odpowiada programista typowe zastosowanie zamiana adresu na liczbę całkowitą i odwrotnie int adres_num=0x22ff6c; wi2=reinterpret_cast<int*>(adres_num); sygnal=*wi2; //ustawienie //odczyt o2=reinterpret_cast<int>(wi2); //adres liczba
wskaźniki void nie zawierają wiedzy o typie wskazywanym (tylko inormacje o położeniu) każdy wskaźnik dowolnego (nie-stałego) typu można przypisać wskaźnikowi typu void odwrotnie konieczne rzutowanie Stałe wskaźniki, wskaźniki do stałych wskaźnik stały ustawiony raz nie może ulec zmianie: int a, b; int * const wa=&a; wa=b; //błąd!!! wskaźnik do stałej uznaje pokazywany obiekt za stały: const a=10, b=20; const int *w; w=&o1; //O.K. w=&o2; //O.K. *w++; //blad!!! #include <cstdlib> #include <iostream> int main(int argc, char *argv[]) //******************************************************* // konwersje const_cast const int a=100; int *wa; wa=const_cast<int*>(&a); cout << "Wartosc stalej - przez wskaznik: *wa=" << *wa << endl; cout << "Wartosc stalej - przez nazwe: a=" << a << endl; *wa=*wa+1; cout << "\npo zmianie: (zwiekszenie wartosci o 1) \n\n";
cout << "Adresy: przez nazwe - " <<&a << " przez wskaznik " << wa << endl; cout << "Wartosci: przez nazwe " <<a << " przez wskaznik " << *wa << endl; cout << "Roznica adresow: " << &a - wa << "\n\n"; system("pause"); Wartosc stalej - przez wskaznik: *wa=100 Wartosc stalej - przez nazwe: a=100 Po zmianie: (zwiekszenie wartosci o 1) Adresy: przez nazwe - 0x22ff74 przez wskaznik 0x22ff74 Wartosci: przez nazwe 100 przez wskaznik 101 Aby kontynuowa, naci nij dowolny klawisz... Roznica adresow: 0
Wskaźniki do funkcji wskaźnik może pokazywać na różne obiekty w tym na funkcje wskaźnik ma zawierać adres może to być adres tego miejsca w pamięci, gdzie zaczyna się kod instrukcji danej funkcji wskaźnik musi zawierać informację typu co jaki jest charakter obiektu jaki wskazuje nazwa funkcji jest inaczej jej adresem w pamięci int ff(int); /* ff jest funkcją zależną od zmiennej typu int zwracającej wartość typu int */ int (*fp)(int); /* fp jest wskaźnikiem do funkcji zależnej od zmiennej int zwracającej wartość typu int */ fp = ff; /* wskaźnik fp wskazuje na funkcję ff */ k=ff(j); k=(*fp)(j); /* równoważne wywołanie funkcji ff */ po co nam wskaźniki do funkcji? o procedury zależne od funkcji (np. całkowanie, rozwiązywanie równań, sortowanie) o kształt tych procedur nie zależy od funkcji o wskaźniki do funkcji pozwalają na napisanie tych procedur w sposób ogólny (niezależny od konkretnej funkcji) Przykład 1: Program w którym użytkownik wskazuje w czasie wykonania, której funkcji użyć
#include <iostream> // przykład użycia wskaźników do funkcji int pierwsza(); int druga(); int main() int i; int (*w_f)(); cout << Podaj numer funkcji na która ma pokazywac wskaznik:\n ; cout << dopuszczalne wartosci : 1 lub 2: ; cin >> I; switch (i) case 1 : w_f=pierwsza; break; case 2 : w_f=druga; break; default: w_f=null; break; cout << Wedle rozkazu! \n ); if (w_f) for (i=0; i<3; i++) (*w_f)(); int pierwsza() cout << funkcja pierwsza!\n ; return 5; int druga() cout << funkcja druga!\n ; return 8;
ważniejsze momenty programu: o definicja wskaźnika do funkcji tak, by ilość i typ argumentów oraz typ zwracanego wyniku były takie same, jak w funkcjach, które ma wskazywać o ustawienie wskaźnika przyrównanie wskaźnika do nazwy funkcji (bez listy argumentów); nazwa funkcji reprezentuje jej adres o wykonanie funkcji określonej przez wskaźnik w_f użycie operatora odwołania pośredniego (*w_f)() o w tak napisanym programie wskaźnik w_f może pokazywać na dowolną funkcję niezależną od żadnych argumentów i zwracającej wartości typu int (o dowolnej nazwie). Przykład 2: rozwiązywanie równania f(x)=0 metodą bisekcji: start: podanie dwóch wartości xm, xp: f(xm)<0, f(xp)>0 dla xs=(xm+xp)/2 wyliczamy f(xs) o gdy f(xs)<0 za xm wstawiamy xs o gdy f(xs)>0 za xp wstawiamy xs powtarzamy, dopóki xm xp > eps (zakładana dokładność) dla ustalonej funkcji (np. f(x)=sin(x)*sin(x)-0.5) #include <iostream> #include <cmath> double ss(double x) return sin(x)*sin(x)-0.5; double bisec(double xm, double xp, double eps) double xh; double delta; delta = fabs(xm-xp); while (delta>eps) xh=(xm+xp)/2; if(ss(xh)<0.0) xm=xh; else xp=xh; delta=fabs(xm-xp); return (xm+xp)/2;
void start(double *xm, double *xp) int ok=1; do cout << "Podaj: xm xp "; cin << *xm, *xp; if (ss(*xp)<0.0 ss(*xm)>0.0) cout << "Zle wartosci poczatkowe:\n"; cout << xm= << *xm << ym= << ss(*xm) << endl; cout << xp= << *xp << ym= << ss(*xp) << endl; cout << "Sprobuj jeszcze raz\n\n"; else ok=0; while (ok); main() double xx,xm,xp; start(&xm,&xp); xx=bisec(xm,xp,1.0e-10); cout << "Rozwiazanie: << x << daje wartość funkcji << ss(xx) << endl; momenty zależne od funkcji: o ustalenie danych startowych funkcja start o funkcja bisec wersja ogólna użycie wskaźników #include <stdio.h> #include <math.h> double bisec(double (*f)(double), double xm, double xp, double eps) /* rozwiązanie równania g(x) = 0 dla dowolnej funkcji g(x) reprezentowanej przez wskaźnik do funkcji f */ double xh, delta; delta = fabs(xm-xp);
while (delta>eps) xh=(xm+xp)/2; if((*f)(xh)<0.0) xm=xh; else xp=xh; delta=fabs(xm-xp); return (xm+xp)/2; void start(double (*f)(double), double *xm, double *xp) /* zadanie warunków początkowych */ int ok=1; do printf("podaj: xm xp "); scanf("%lf %lf",xm,xp); if ((*f)(*xm)*(*f)(*xp)>0.0 (*f)(*xm)>0.0) printf("zle wartosci poczatkowe:\n"); printf("xm=%f ym=%f\n",*xm,(*f)(*xm)); printf("xp=%f yp=%f\n",*xp,(*f)(*xp)); printf("sprobuj jeszcze raz\n\n"); else ok=0; while (ok); double ss(double x) return sin(x)*sin(x)-0.5; main() double xx,xm,xp; double (*f)(double); start(ss,&xm,&xp); xx=bisec(ss,xm,xp,1.0e-10); f=ss; printf("rozwiazanie: %15.10lf %15.5e\n",xx,(*f)(xx)); system("pause");
funkcja jako argument innej funkcji: wywołanie (parametr aktualny) nazwa funkcji; odebranie (parametr formalny) wskaźnik do funkcji wykonanie funkcji określonej przez wskaźnik f użycie operatora odwołania pośredniego (*f)(x) tak napisany program możemy zastosować dla dowolnej funkcji zależnej od jednej zmiennej typu double zwracającej wartości typu double (o dowolnej nazwie). Złożone deklaracje deklaracja wskaźnika do funkcji przykład deklaracji złożonej zasady interpretacji deklaracji złożonych: o zaczynamy od nazwy obiektu o staramy się iść w prawo (tam mogą być operatory o najwyższym priorytecie ( ) [ ] ) o jeżeli to nie jest możliwe (np. gdy są nawiasy) idziemy w lewo o powtarzamy ten proces aż do osiągnięcia lewego skrajnego brzegu deklaracji Przykłady: int f[10]; int *f[10]; int f( ); int *f( ); int (*f)(); int *(*f)(); int (* (f[10] ) ) ( ); tablica 10 elementów typu int tablica 10 elemntów typu wskaźnik do int funkcja zwracająca wartości typu int funkcja zwracająca wskaźnik do int wskaźnik do funkcji zwracającej wartości typu int wskaźnik do funkcji zwracającej wskaźnik do zmiennej typu int f jest tablicą 10 elementów będących wskaźnikami do funkcji zwracających wartości typu int
Argumenty funkcji main w C/C++ możliwe przekazanie z wiersza polecenia wywołującego program dodatkowych parametrów (argumentów) do uruchamianego programu odbywa się to za pomocą argumentów funkcji main: main(int argc, char *argv[]) argc liczba całkowita >=1 określająca liczbę argumentów w linii komend argv wskaźnik do tablicy zawierającej argumenty argv[0] nazwa programu argv[1] pierwszy argument dodatkowy... argv[argc] NULL Przykład: echo, mnożenie #include <iostream> int main(int argc, char *argv[]) for(int i=1; i< argc; i++) cout << argv[i] << ; cout << endl; return 0; #include <iostream> #include <cstdlib> int main(int argc, char *argv[]) int s1,s2; s1=atoi(argv[1]); s2=atoi(argv[2]); cout << s1 << x << s2 << = << s1*s2 << endl;