Programowanie obiektowe
Zakład Pomiarowej i Medycznej Aparatury Elektronicznej
Wydział Podstawowych Problemów Techniki
Wrocław 2003
1
Wstęp
Czym będziemy się zajmować?
1. Mechanizmy występujące w C++.
• Dynamiczne tworzenie obiektów, konstruktory i destruktory.
• Wzorce klas i ich zastosowania.
• Dziedziczenie wielokrotne.
• Funkcje i klasy zaprzyjaźnione.
• Operacje wejścia – wyjścia
• Wyjątki
2. Elementy Javy.
2
3. Model klient – serwer.
Oprócz tego:
• Dygresje o programowaniu obiektowym w Turbo Pascalu.
• Trochę o programach Delphi i C++ Builder.
3
Literatura polecana
[1] B. Stroustrup, Język C++. WNT, Warszawa 1994 (i wyd. nast.).
[2] J. Grębosz, Symfonia C++. Oficyna Kallimach, Kraków 1999.
[3] J. Grębosz, Pasja C++. Oficyna Kallimach, Kraków 1999.
[4] S. B. Lippman, J. Lajoie, Podstawy języka C++, WNT, Warszawa
2001.
[5] S. B. Lippman, Model obiektu w C++. WNT, Warszawa 1999.
[6] A. Struzińska-Walczak, K. Walczak, Nauka programowania w języku
C++. Wydawnictwo W&W, Warszawa 1998.
4
[7] J. Liberty, C++ Księga eksperta. Helion, Gliwice 1999.
[8] K. Jamsa, Wygraj z C++. Mikom, Warszawa 1996.
[9] J. Kisilewicz, Język C++. Programowanie obiektowe. Oficyna
Wydawnicza P. Wr. Wrocław 2002.
[10] B. Boone, Java dla programistów w C i C++. WNT, Warszawa
1998.
[11] S. Holzner, JAVA 2, szybkie wprowadzenie. Help, 2002.
[12] K. Walczak, Java. Wydawnictwo W&W, Warszawa 2002.
5
2
Klasy w C++ i Pascalu
2.1
Obiekty w C++ i Pascalu - początki
W C++ – struktury, w Pascalu – rekordy, grupują dane różnych typów.
Przykład. Liczby zespolone w Pascalu – dodawanie i mnożenie.
type
complex=record
x:double;
y:double;
end;
var
z,z1,z2:complex;
6
procedure plus(z1,z2:complex;var z:complex);
begin
z.x:=z1.x+z2.x;
z.y:=z1.y+z2.y;
end;
procedure times(z1,z2:complex;var z:complex);
begin
z.x:=z1.x*z2.x-z1.y*z2.y;
z.y:=z1.x*z2.y+z2.x*z1.y;
end;
7
Przykład. Liczby zespolone w C++ – dodawanie i mnożenie.
#include "complex.h"
struct complex z1,z2,z;
struct complex plus(struct complex z1,struct complex z2)
{
struct complex z;
z.x=z1.x+z2.x;
z.y=z1.y+z2.y;
return(z);
}
8
struct complex times(struct complex z1,struct complex z2)
{
struct complex z;
z.x:=z1.x*z2.x-z1.y*z2.y;
z.y:=z1.x*z2.y+z2.x*z1.y;
return(z);
}
9
Plik nagłówkowy complex.h
struct complex
{
double x;
double y;
};
struct complex plus(struct complex z1,struct complex z2);
struct complex times(struct complex z1,struct complex z2);
10
W tych programach dane i operacje na nich są luźno związane. Trochę
lepiej wygląda to, gdy operacje są już na danych określonych typów.
type {Turbo Pascal}
complex=object
x:double;
y:double;
end;
complex_operation=object(complex)
procedure plus(z1,z2:complex;var z:complex);
procedure times(z1,z2:complex;var z:complex);
end;
11
procedure
complex_operation.plus(z1,z2:complex;var z:complex);
begin
z.x:=z1.x+z2.x;
z.y:=z1.y+z2.y;
end;
procedure
complex_operation.times(z1,z2:complex;var z:complex);
begin
z.x:=z1.x*z2.x-z1.y*z2.y;
z.y:=z1.x*z2.y+z2.x*z1.y;
end;
12
Odpowiedni fragment programu może być taki:
var
z,z1,z2,
o:complex_operation;
begin
write(’x1=’);read(z1.x);
write(’y1=’);read(z1.y);
write(’x2=’);read(z2.x);
write(’y2=’);read(z2.y);
o.plus(z1,z2,z);writeln(z.x:0:4,’+i’,z.y:0:4);
o.times(z1,z2,z);writeln(z.x:0:4,’+i’,z.y:0:4);
end.
13
Można obiekty określać „na raty”.
type
complex=object
x:double;
y:double;
end;
plus_operation=object(complex)
procedure plus(z1,z2:complex;var z:complex);
end;
complex_operation=object(plus_operation)
procedure times(z1,z2:complex;var z:complex);
end;
14
procedure
plus_operation.plus(z1,z2:complex;var z:complex);
begin
z.x:=z1.x+z2.x;
z.y:=z1.y+z2.y;
end;
procedure
complex_operation.times(z1,z2:complex;var z:complex);
begin
z.x:=z1.x*z2.x-z1.y*z2.y;
z.y:=z1.x*z2.y+z2.x*z1.y;
end;
15
Inne pytanie. Dlaczego musimy pisać plus(z1,z2,z) zamiast po prostu
z=z1+z2, tak jak piszemy z = z
1
+ z
2
? Odpowiedź później. W C++ na
szczęście nie musimy, ale w Pascalu i Javie tak.
Przykład. Podobnie w C++, ale teraz będą klasy. Zakładamy (dla
uproszczenia), że już wiadomo, co to jest complex.
class complex_operation
{
struct complex plus(struct complex z1,struct complex z2);
struct complex times(struct complex z1,struct complex z2);
}
16
Funkcje plus oraz times definiujemy osobno.
complex_operation::
struct complex plus(struct complex z1,struct complex z2)
{
struct complex z;
z.x:=z1.x*z2.x-z1.y*z2.y;
z.y:=z1.x*z2.y+z2.x*z1.y;
return(z);
}
17
Można je też definiować wewnątrz klasy.
class complex_operation
{
complex_operation(); // konstruktor
~complex_operation(); // destruktor
struct complex plus(struct complex z1,struct complex z2)
{
struct complex z;
z.x:=z1.x*z2.x-z1.y*z2.y;
z.y:=z1.x*z2.y+z2.x*z1.y;
return(z);
}
18
struct complex times(struct complex z1,struct complex z2);
}
2.2
Enkapsulacja
Etykiety:
private:
public:
protected:
Mogą być umieszczane w dowolnej kolejności.
19
Schemat klasy:
class nazwa_klasy
{
// składniki, domyślnie private
}
Składnikami klasy mogą być zmienne i funkcje czyli metody. W klasie
może być zawarta tylko deklaracja metody albo również jej definicja.
Definicja umieszczona na zewnątrz klasy poprzedzona jest nazwą klasy i
operatorem zakresu.
20
Przykład.
#include <iostream.h>
class moja_klasa
{
// domyślnie private
int n;
public:
void ustal_n(int k){n=k;}
int podaj_n(){return n;}
void zmien_n(int k){n+=k;}
double f(int k);
21
};
double moja_klasa::f(int k){return n*n/(3.14*k);}
int main()
{
moja_klasa a,b;
a.ustal_n(3);
b.ustal_n(4);
cout << a.podaj_n() << endl;
cout << b.podaj_n() << endl;
cout << a.f(2);
22
return 0;
}
Do składników klasy (publicznych) mamy dostęp:
• obiekt.składnik
• wskaźnik->składnik
23
Składowe statyczne:
static int n
Teraz n jest wspólne dla wszystkich obiektów. Do składników
statycznych klasy mamy dostęp:
• klasa::składnik
• obiekt.składnik
• wskaźnik->składnik
24
2.3
Konstrukcja, destrukcja i przyjaźń
Klasa:
class list:public element{
int n;
item *p;
public:
list(int size); // (1) konstruktor
~list; // (2) destruktor
void put(float x); // (3)
float first();
float last()
float get(int n);
25
void delete(int n);
insert(int n,int position);
friend int length(list &); // (4)
}
26
Klasa element definiuje element item listy postaci
[wartość x, wskaźnik do następnego x] oraz definiuje funkcję
NewElement tworzącą element i zwracającą wskaźnik do niego.
(1) Konstruktor – funkcja o tej samej nazwie co klasa, bez określenia
typu! Jest wykonywana automatycznie przy deklaracji obiektu. W
tym przykładzie tworzy listę pustą, tzn. wskaźnik na nic nie
pokazuje i ustala n = 0.
(2) Destruktor – funkcja wykonywana przy likwidacji obiektu.
(3) Metoda put(float x) tworzy nowy element i dopisuje go na
koniec listy. Dalsze metody podają pierwszy, ostatni, n-ty element
listy, usuwają n-ty element, wstawiają element na wiejsce n-te.
27
(4) Metoda zaprzyjaźniona – ma dostęp do prywatnych składników
klasy.
28
3
Dziedziczenie
3.1
Podstawowe zasady
Dziedziczenie:
class przodek
{
private:
int n;
protected:
int bla_bla(int);
public:
void bla_bla_bla(int);
29
przodek(int);
};
class dziedzic : public przodek;
{
public:
bla_bla_bla_bla(int);
dziedzic(int);
};
Klasa dziedzic zachowuje się tak jak
30
class przodek_i_dziedzic
{
private:
int n;
public:
int bla_bla(int);
void bla_bla_bla(int);
void bla_bla_bla_bla(int);
};
3.2
Więcej o konstruktorach
Nie wszystko się dziedziczy.
31
Nie dziedziczy się konstruktorów, destruktorów i operatora przypisania
(operator=).
Klasa pochodna uruchamia najpierw konstruktor klasy podstawowej z
odpowiednimi parametrami.
dziedzic::dziedzic(int nn):przodek(nn)
{
};
Używane często funkcje new oraz delete w stosunku do tablic:
int *t;
new t[10];
32
// ?????
delete [] t;
albo
int *t;
int n=10;
new t[n];
// ?????
delete [] t;
Tablice wielowymiarowe (tutaj dwuwymiarowe):
int *t;
new t[10][10];
33
// ?????
delete [] t;
ale nie (niestety)
int *t;
int n=10;
new t[n][n];
// ?????
delete [] t;
ale
int *t;
int n=10;
34
new t[n][10];
// ?????
delete [] t;
albo
int *t;
int n=10;
new t[n*n];
// ?????
delete [] t;
wtedy musimy je używać w postaci t[n*i+j].
35
3.3
Przykład
#include <iostream.h>
int fact(int);
class cpermutation;
class permutation;
//////////////////////////////////////////////
int fact(int n) // silnia, czyli n!
36
{
int m=1;
int k;
for(k=1;k<=n;k++) m*=k;
return m;
}
//////////////////////////////////////////////
37
class cpermutation
{
protected:
int n; // dlugosc permutacji
int m; // liczba wszystkich permutacji: n! - to na pozniej
int na; // aktualna liczba permutacji
int *perm; // tablica permutacji, permutacja=wektor
public:
cpermutation(int);
~cpermutation(){delete [] perm;};
void perm_out(void);
};
38
cpermutation::cpermutation(int nn)
{
int i,j;
n=nn;
m=fact(nn);
na=nn;
perm=new int[m*n];
for(i=0;i<na;i++){for(j=0;j<n;j++){perm[n*i+j]=(i+j)%n; //
}};
};
39
void cpermutation::perm_out(void)
{
int i,j;
for(i=0;i<na;i++)
{
for(j=0;j<n;j++)
{
cout << perm[n*i+j] << " ";
};
cout << endl;
};
};
40
class permutation:public cpermutation
{
public:
permutation(int);
};
permutation::permutation(int nn):cpermutation(nn)
{
};
41
int main()
{
int i,j;
cpermutation p1(3);
permutation p2(3);
p1.perm_out();
cout << "Uwaga! Bzdet!" << endl;
p2.perm_out(); // tu bzdury, bo konstruktor nie konstruuje
return 0;
};
42
0 1 2
1 2 0
2 0 1
Uwaga! Bzdet!
0 1 2
1 2 0
2 0 1
0 0 0
0 0 0
0 0 0
43
3.4
Kolejność wywoływania konstruktorów
Konstruktory uruchamiane są wg hierarchii, tzn. najpierw klasa
podstawowa, potem jej pochodna itd.
Na liście inicjalizacyjnej można pominąć wywołanie konstruktora
bezpośredniej klasy podstawowej tylko wtedy gdy:
• klasa podstawowa nie ma żadnego konstruktora,
• ma konstruktory, a wśród nich jest domniemany.
44
#include <iostream.h>
#include <stdlib.h> // m.in. funkcje atoi, atof
class nn
{
protected:
int n;
public:
nn(int m) : n(m)
{
cout << n << endl;
}
};
45
int main(int argc, char *argv[])
{
if(argc!=2){return 1;};
int k;
k=atoi(argv[1]);
nn x(k);
return 0;
}
46
#include <iostream.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc!=2){return 1;};
int k;
k=atoi(argv[1]);
int n;
n(k);
// NIE!
return 0;
}
47
Przykład.
Hierarchia klas
nn
1
nn
2
nn
3
→ nn
4
[nn
1
]
→ nn
5
[nn
4
]
przodek
daleki potomek
kl
1
[kl
2
] oznacza, że klasa kl
1
zawiera klasę kl
2
.
Deklarujemy obiekty klas nn
4
oraz nn
5
.
#include <iostream.h>
#include <stdlib.h>
48
class nn1
{
protected:
int n1;
public:
nn1(int m):n1(m)
{cout << "nn1 " << n1 << endl;}
~nn1()
{cout << "dd1 " << n1 << endl;}
};
49
class nn2
{
int n2;
public:
nn2(int m):n2(m)
{cout << "nn2 " << n2 << endl;}
~nn2()
{cout << "dd1 " << n2 << endl;}
};
50
class nn3
{
public:
nn3()
{cout << "nn3 "
<< endl;}
~nn3()
{cout << "dd3 "
<< endl;}
};
51
class nn4:public nn3
{
protected:
nn1 n4;
public:
nn4(int m) :n4(m),nn3()
{cout << "nn4 "
<< m << endl;}
~nn4()
{cout << "dd4 "
<< endl;}
};
52
class nn5:nn4
{
protected:
nn2 m5;
public:
nn5(int m):m5(m),nn4(m)
{cout << "nn5 " << endl;}
~nn5()
{cout << "dd5 " << endl;}
};
53
int main(int argc, char *argv[]) // program multi
{
if(argc!=3){return 1;};
int i;
int j;
i=atoi(argv[1]);
j=atoi(argv[2]);
nn4 x(i);
nn5 y(j);
return 0;
}
54
Program multi 1 2
Wywoływanie konstruktorów
nn3
nn1 1
nn4 1
nn3
nn1 2
nn4 2
nn2 2
nn5
55
Wywoływanie destruktorów
dd5
dd1 2
dd4
dd1 2
dd3
dd4
dd1 1
dd3
56
3.5
Dziedziczenie wielokrotne
Klasa może wywodzić się więcej niż od jednej klasy.
mama
tata
→ dziecko
Nie wystarczy tylko
class mama;
Definicja klasy umieszczonej na liście pochodzenia musi być znana
kompilatorowi – nie wystarczy deklaracja zapowiadająca.
57
class mama{
// ...
};
class tata{
// ...
};
class dziecko: public mama, public tata
{
// ...
};
58
#include <iostream.h>
class mama
{
protected:
int wm;
public:
mama(int m) : wm(m)
{
cout << "Wiek mamy " << wm << endl;
}
};
59
class tata
{
protected:
int wt;
public:
tata(int m) : wt(m)
{
cout << "Wiek taty " << wt << endl;
}
};
60
class dziecko : public mama, public tata
{
public:
dziecko() : mama(20), tata(21)
{cout << "Rodzice Zosi w wieku:\n";}
void wiek_rodzicow(){
cout << "Mama " << wm<<" lat,\tTata "
<< wt << " lat.\n";
}
};
61
main()
{
dziecko Zosia;
Zosia.wiek_rodzicow();
}
Rezultat działania programu
Wiek mamy 20
Wiek taty 21
Rodzice Zosi w wieku:
Mama 20 lat,Tata 21 lat.
62
#include <iostream.h>
class mama
{
protected:
int wiek;
public:
mama(int m) : wiek(m)
{
cout << "Wiek mamy " << wiek << endl;
}
};
63
class tata
{
protected:
int wiek;
public:
tata(int m) : wiek(m)
{
cout << "Wiek taty " << wiek << endl;
}
};
64
class dziecko : public mama, public tata
{
public:
dziecko() : mama(20), tata(21)
{cout << "Rodzice Zosi w wieku:\n"; }
void wiek_rodzicow()
{
cout << "Mama " << wiek<<" lat,\tTata "
<< wiek << " lat.\n";
}
};
65
main()
{
dziecko Zosia;
Zosia.wiek_rodzicow();
}
Nie wiadomo czyj jest wiek.
66
Borland C++ 5.5 for Win32 Copyright (c) 1993, 2000 Borland
mtd1.cpp:
Error E2014 mtd1.cpp 35: Member is ambiguous:
’mama::wiek’ and ’tata::wiek’
in function dziecko::wiek_rodzicow()
Error E2014 mtd1.cpp 36: Member is ambiguous:
’mama::wiek’ and ’tata::wiek’
in function dziecko::wiek_rodzicow()
*** 2 errors in Compile ***
67
Uniknięcie wieloznaczności:
void wiek_rodzicow()
{
cout << "Mama " << mama::wiek<<" lat,\tTata "
<< tata::wiek << " lat.\n";
}
68
4
C++ a Java
4.1
Podstawy
Java – język czysto obiektowy,
• nie ma zmiennych i funkcji globalnych,
• są tylko klasy,
• nie ma wskaźników – nie ma obawy o przekroczenie zakresu tablic,
• system sam zwalnia pamięć – nie ma destruktorów,
• wielowątkowość,
• obsługa wyjątków,
69
• kompilacja do kodu pośredniego,
• aplety (ang. applets) – programiki w przeglądarkach WWW,
• wykonanie niezależne od sprzętu – JVM.
Tekst źródłowy z rozszerzeniem java.
Tekst skompilowany z rozszerzeniem class.
70
C
C++
Java
Dostęp do
pamięci
Wskaźniki,
adresy
Wskaźniki,
adresy,
obiekty
Obiekty
Grupowanie
danych
Struktury
Struktury,
klasy
Klasy
Sposób
definiowania
Funkcje
Funkcje,
metody
Metody
71
4.2
Proste programy w C++ i Javie
Przykłady z książki [
Program w C++ zapisany w pliku HelloWorld.cpp.
#include <iostream.h>
main()
{
cout << "Hello World" << endl!;
}
Po skompilowaniu i wykonaniu da efekt:
Hello World!
Program w Javie zapisany w pliku HelloWorld.java.
72
class HelloWorld{
static public void main(String args[])
{
System.out.println("Hello World!");
}
}
Po skompilowaniu
javac HelloWorld.java
i wywołaniu
java HelloWorld
da efekt (ten sam co program w C++):
Hello World!
73
Program w C++
#include <iostream.h>
class Astronaut
{
int earthWeight;
public:
Astronaut(int wt)
{
earthWeight=wt;
}
74
double moonWeight()
{
return earthWeight*0.166;
}
};
main()
{
int weight;
cout << "Ile wazysz na ziemi?" << endl;
cin >> weight;
Astronaut student(weight);
75
cout << "na ksiezycu wazysz "
<< student.moonWeight() << endl;
}
Po skompilowaniu i wykonaniu otrzymujemy (po podaniu własnej wagi
72):
Ile wazysz na Ziemi?
72
Na ksiezycu wazysz 11.952
76
Program zapisany w pliku PlanetaryScale0.java
class Astronaut
{
Double earthWeight;
Astronaut(double weight)
{
earthWeight=new Double(weight);
}
public double moonWeight()
{
77
return earthWeight.doubleValue() * .166;
}
}
class PlanetaryScale
{
Astronaut armstrong;
public static void main(String args[])
{
char c;
double earthWeight;
78
StringBuffer strng=new StringBuffer();
double moonWeight;
Astronaut armstrong;
System.out.println("Ile wazysz na Ziemi?");
try
{
while((c=(char) System.in.read())!=’\n’)
strng.append(c);
earthWeight=
Double.valueOf(strng.toString()).doubleValue();
79
} catch(java.io.IOException e)
{
earthWeight=0.0;
}
armstrong=new Astronaut(earthWeight);
System.out.println("Na ksiezycu wazysz "+
armstrong.moonWeight());
}
}
80
Po skompilowaniu
javac PlanetaryScale0.java
i wywołaniu java PlanetaryScale
otrzymujemy (po podaniu własnej wagi 72):
Ile wazysz na Ziemi?
72
Na ksiezycu wazysz 11.952
81
4.3
Typy danych i operatory
Dwa typy danych – proste i referencyjne. Nie ma wskaźników, typy
złożone przekazywane są przez referencje.
Wszystkie typy proste są ze znakiem.
Trzy typy referencyjne: klasa, interfejs i tablica.
82
Typy proste.
Typ
Rozmiar
byte
1 bajt (liczba całkowita ze znakiem)
boolean 1 bajt (wartości true i false)
char
2 bajty Unicode
short
2 bajty
int
4 bajty
long
8 bajtów
float
4 bajty
double
8 bajtów
83
Typy referencyjne.
String str=new String();
System.out.println("str ma "+str.length()+" znaków.");
W C++ operator new zwracał wskaźnik do obiektu. W Javie operator
ten zawiera referencję do obiektu – nie ma operatora delete, Java sama
zlikwiduje obiekt, do którego nie referencji.
Nie ma operatorów przeciążonych, jedynie + służy również do
konkatenacji (sklejania) łańcuchów.
Dodatkowe (w porównaniu z C++) operatory: >>> przesuwa bity
zmiennej całkowitej w prawo, po lewej stronie wstawiane są zera.
Przesunięcie z przypisaniem >>>=.
84
Przykład.
n = n >>> 1
n >>>= 1
W obu przypadkach n jest przesunięte o jeden bit w prawo.
85
4.4
Klasy
Nie ma średnika po klamrze kończącej klasę.
Kwalifikatory dostępu przed każdą składową.
class myClass
{
public myClass(){/* ... */}
public int n;
protected int f1(){return 1;}
private boolean f2(){return true;}
private double x;
// ... itd.
}
86
Funkcje składowe są zawsze definiowane w deklaracjach klas, nie oznacza
to że są inline.
Parametry typów prostych są przekazywane przez wartość – funkcja
operuje na kopii parametru. Typy obiektowe są przekazywane przez
referencję.
Nie łańcuchów takich jak w C, jest klasa String. Obiekt typu String
jest niezmienny, nie można modyfikować jego zawartości. Utowrzenie
obiektu:
String str="To jest łańcuch";
Długość tego łańcucha jest podawana przez funkcję składową length().
Tablice są klasami – nie można określić rozmiaru tablicy przy deklaracji.
Trzeba użyć new. Nawiasy [] po lewej lub prawej stronie.
87
Przykłady.
int array1[];
int [] array2;
array1=new int[10];
for(int i=0;i<array1.length;i++)array1[i]=i;
Nie można wpisywać danych poza zakresem tablicy. Linia
array1[12]=1;
spowoduje komunikat diagnostyczny:
java.lang.ArrayIndexOutOfBoundsException
88
Każda klasa po czymś dziedziczy. Wszystkie klasy dziedziczą po klasie
Object. Jeśli jawnie nie określi się, po jakiej klasie się dziedziczy, to
dziedziczy się po Object. Zamiast dwukropka jak w C++ używa się
słowa kluczowego extends.
Nie ma dziedziczenia wielokrotnego, można jednak używać interfejsów,
będących odpowiednikami klas abstrakcyjnych – nie mogą one zawierać
definicji składowych.
Interfejsy się implementuje, a nie dziedziczy.
89
Przykład.
public class aaa // domyślnie dziedziczy po Object
{
public void f(String text){/* ... */}
// ...
}
public class bbb extends aaa
{
public void g(){/* ... */}
// ...
}
90
public class ccc extends bbb
{
ccc(){/* ... */}
// ...
}
91
Przykład.
interface aaa
{
public void f(String text);
// ...
}
interface bbb
{
public void g();
// ...
}
92
public class ccc implements aaa,bbb
{
ccc(){/* ... */}
public void f(String text){/* ... */}
public void g(){/* ... */}
// ...
}
Klasy abstrakcyjne nie mogą służyć do deklaracji obiektów, mogą mieć
jednak definicje, jeżeli nie mają, to są poprzedzone słowem abstract.
93
abstract class aaa
{
public void bbb(){/* ... */}
abstract void ccc();
public final int ddd=500; // stała
}
class xxx extends aaa
{
public int eee=50;
public void ccc(){/* ... */}
}
94
Można oczywście przedefiniować składowe
class xxx extends aaa
{
public void bbb(){/* coś innego niż poprzednio */}
public int eee=50;
public void ccc(){/* ... */}
}
95
5
Przeciążanie funkcji i operatorów
5.1
Przeciążanie nazw funkcji
Przeciążanie = przeładowanie = overloading:
więcej niż jedna funkcja o tej samej nazwie w tym samy zakresie
ważności. O tym, którą funkcję wybierze kompilator, decydują typy
argumentów.
Przykład. Potęga liczby dodatniej całkowita lub rzeczywista. Potęga
całkowita: x
n
= x
m+k
, gdzie m = n/2, k = n%2.
#include <iostream.h>
#include <math.h>
96
float power(float x,unsigned int n);
float power(float x,unsigned float y);
float power(float x,int n)
{
switch(n)
{
case 0: return 0;
case 1: return x;
case 2: return x*x;
default:
{
97
float t;
unsigned int i,j;
i=n>>1;
j=n&&1;
return power(x,j)*(t=power(x,i))*t;
}
}
}
98
float power(float x,float y)
{
return exp(y*log(x));
}
main()
{
unsigned int n=5;
float y=5.01;
cout << power(2,n) << endl;
cout << power(2,y) << endl;
}
99
5.2
Przeciążanie operatorów
#include <iostream.h>
class complex;
class complex
{
float re,im;
public:
complex(float r, float i){re=r;im=i;};
complex(float r){re=r;im=0;};
complex(){re=0;im=0;};
100
friend complex operator+(complex, complex);
friend complex complex_out(complex a)
{
cout << a.re << "+i" << a.im << endl;
};
};
complex operator+(complex a, complex b)
{
return complex(a.re+b.re,a.im+b.im);
};
101
int main()
{
complex a(2,2);
complex b(1);
complex c;
c=a+b;
complex_out(c);
return 0;
}
Uwaga. Nie można definiować nowych operatorów. Nie wszystkie
operatory można przeciążać.
102
6
Operacje wejścia/wyjścia
6.1
Strumień
Biblioteki obsługujące operacje wejścia/wyjścia:
iostream.h – jakiekolwiek korzystanie,
fstream.h – operacje we/wy na plikach zewnętrznych,
strstream.h – operacje we/wy na tablicach.
Uwaga. W Borland C++ wystarczy włączyć fstream, gdyż ten sam
włącza iostream
103
Przeciążamy operatory << i >>. Przykłady na klasie complex.
Realizacja przez globalne funkcje operatorowe realizujące przeciążenia <<
oraz >> dla klasy complex.
#include <iostream.h>
class complex
{
public:
float re,im;
complex(float r, float i){re=r;im=i;};
complex(float r){re=r;im=0;};
complex(){re=0;im=0;};
104
friend complex operator+(complex a, complex b)
{
return complex(a.re+b.re,a.im+b.im);
};
};
ostream& operator<<(ostream& strumien_wyj, complex z)
{
strumien_wyj << z.re << "+i" << z.im;
return strumien_wyj ;
}
105
istream& operator>>(istream& strumien_wej, complex &z)
{
strumien_wej >> z.re >> z.im;
return strumien_wej ;
}
main()
{
complex a(2,2);
complex b;
cout << "Podaj b" << endl;
cin >> b;
106
cout << a+b << " jest to liczba zespolona" << endl;
}
107
Realizacja przez funkcje zaprzyjaźnione realizujące przeciążenia << oraz
>> dla klasy complex.
#include <iostream.h>
class complex
{
float re,im;
public:
complex(float r, float i){re=r;im=i;};
complex(float r){re=r;im=0;};
complex(){re=0;im=0;};
108
friend
complex operator+(complex a, complex b)
{
return complex(a.re+b.re,a.im+b.im);
};
friend
ostream& operator<<(ostream& strumien_wyj, complex z)
{
strumien_wyj << z.re << "+i" << z.im;
return strumien_wyj ;
}
109
friend
istream& operator>>(istream& strumien_wej, complex &z)
{
strumien_wej >> z.re >> z.im;
return strumien_wej ;
}
};
110
main()
{
complex a(2,2);
complex b;
cout << "Podaj b" << endl;
cin >> b;
cout << a+b << " jest to liczba zespolona" << endl;
}
111
Wersja „symetryczna”
#include <iostream.h>
class complex
{
float re,im;
public:
complex(float r, float i){re=r;im=i;};
complex(float r){re=r;im=0;};
complex(){re=0;im=0;};
friend
complex operator+(complex a, complex b)
112
{
return complex(a.re+b.re,a.im+b.im);
};
friend
ostream& operator<<(ostream& strumien_wyj, complex z)
{
strumien_wyj << z.re << "+i" << z.im;
return strumien_wyj ;
}
friend
istream& operator>>(istream& strumien_wej, complex &z)
113
{
char a,b;
strumien_wej >> z.re >> a >> b
>> z.im;
return strumien_wej ;
}
};
114
main()
{
complex a(2,2);
complex b;
cout << "Podaj b" << endl;
cin >> b;
cout << a+b << " jest to liczba zespolona" << endl;
}
115
6.2
Sterowanie formatem
Klasa ios, podstawowa dla istream i ostream zawiera publiczny typ
wyliczeniowy
public:
enum{
skipws=0x0001,
left=0x0002,
right=0x0004,
internal=0x0008,
dec=0x0010,
oct=0x0020,
hex=0x0040,
116
showbase=0x0080,
showpoint=0x0100,
uppercase=0x0200,
showpos=0x0400,
scientific=0x0800,
fixed=0x1000,
unitbuf=0x2000,
stdio=0x4000
};
Użycie: ios::left itd.
117
Funkcje zmieniające flagi i reguły formatowania:
• setf ustawia flagi,
• unsetf kasuje flagi,
np. cout.setf(ios::hex), cin.unsetf(ios::hex).
Przykład skopiowany z książki Symfonia C++.
#include <iostream.h>
main()
{
float x = 1175 ;
118
long stare_flagi ;
cout << x << endl;
cout <<"Zapamietanie flag formatowania\n" ;
stare_flagi = cout.flags();
cout.setf(ios::showpoint) ;
cout << x << endl ;
cout.setf(ios::scientific, ios::floatfield);
cout << x << endl ;
119
cout.setf(ios::uppercase);
cout << x << endl ;
cout.unsetf(ios::showpoint) ;
cout << x << endl ;
cout <<"Powrót do starego sposobu formatowania\n" ;
cout.flags(stare_flagi);
cout << x << endl ;
}
120
Wykonanie programu daje w efekcie
1175
Zapamietanie flag formatowania
1175.00
1.175000e+03
1.175000E+03
1.175000E+03
Powrot do starego sposobu formatowania
1175
121
Inne funkcje klasy ios to
• int width(int)
• int width()
• int precision(int)
• int precision()
• int fill(int)
• int fill()
• char fill(char)
• char fill()
122
6.3
Manipulatory
Wartości wstawiane do strumienia, aby wywołać określony efekt: hex,
dec, oct, flush, endl, ends, ws.
np.
int i=30;
cout << i << " " << hex << i;
da w efekcie 30 1e. Manipulatory z parametrami, to
• setw(int) – ustawia szerokość, tak jak funkcja width,
• setfill(int) – podobnie,
• setprecision(int),
123
• setiosflags(long) – odpowiada setf,
• resetiosflags(long) – odpowiada unsetf.
124
6.4
Pliki
Klasy zapewniające możliwość zapisu do pliku lub odczytu:
• ofstream – zapis do pliku
• ifstream – odczyt z pliku
• fstream – zapis i odczyt
Wystarczy włączyć fstream.
125
Kolejność postępowania.
1. Zdefiniowanie obiektu klasy ofstream, ifstream lub fstream.
2. Określenie konkretnego pliku, który kontaktuje się ze strumieniem.
3. Przeprowadzanie operacji we/wy.
4. Zlikwidowanie strumienia.
Otwieranie strumienia – open.
void open(char* name, int mode=xxx, int
prot=filebuf::openprot);
Trzeci argument określa, kto może z tego pliku korzystać – jest zależne
od systemu (np. UNIX).
Tryby otwarcia określone jako typ enum w klasie ios.
126
• in – do czytania
• out – do pisania
• ate – ustawienie się na końcu pliku
• app – do dopisywania
• trunc – otwarcie z ew. skasowaniem jeśli istnieje
• nocreate – otwarcie jeśli istnieje
• noreplace – otwarcie jeśli istnieje
• binary – tryb binarny (domyślny tekstowy), nie zawsze istnieje
Tryby mogą być OR-owane operatorem binarnym
|.
127
#include <fstream.h>
#include <string.h>
#include <stdlib.h>
inline double rnd()
{
return double(rand())/RAND_MAX; // Borland C++
}
int main(int argc, char *argv[])
{
if(argc!=2){return 1;};
128
int n;
n=atoi(argv[1]);
randomize();
//Borland C++
// srandom(1111); //GCC (Linux, Solaris) 1111 - przykładowe
ofstream frnd; //definicja obiektu
frnd.open(argv[2]
,ios::noreplace
);
//otwarcie
for(int i=0;i<n;i++)
frnd << rnd() << endl;
//operacje
frnd.close();
//zamknięcie.
129
return 0;
}
Konwersje w trybie tekstowym: przy zapisie do pliku znak ’\n’ jest
zamieniany na parę ’\r’ i ’\n’. Przy odczycie – odwrotnie. W
trybie binarnym nie ma żadnych konwersji.
Nie zawsze – jest w DOS-ie, nie ma w UNIX-ie.
130
Jak drukować (na pececie):
#include <fstream.h>
#define PRINTER ofstream("prn", ios::out)
int main()
{
for(int i=0;i<10;i++)
PRINTER << i << endl;
// operacje
return 0;
}
131
7
Wzorce
7.1
Wzorce klas
Wzorzec albo szablon, czyli template. Typ T jest tu „uniwersalnym”
typem dla różnych klas, otrzymywanych przez podstawienie za T
ustalonego typu.
#include <iostream.h>
template<class T> class stack
{
T* v;
T* p;
int sz;
132
public:
stack(int s){v=p=new T[sz=s];}
~stack(){delete[] v;}
void push(T a){*p++=a;}
T pop(){return *--p;}
int size() const {return p-v;}
};
133
main()
{
stack<char> sc(100);
stack<int> si(100);
sc.push(’a’);
si.push(100);
cout << sc.pop() << endl;
cout << si.pop() << endl;
}
7.2
Wzorce funkcji
Moża też definiować wzorce funkcji globalnych.
134
#include <iostream.h>
template<class T> void swap(T v[], int i, int j)
{
T tmp;
tmp=v[i];
v[i]=v[j];
v[j]=tmp;
}
template<class T>
void q_sort(T t[],
int left, int right)
135
{
int i,last;
if(left>=right) return;
swap(t,left,(left+right)/2);
last=left;
for(i=left+1;i<=right;i++)
{
if(t[i]<t[left])
{
swap(t,++last,i);
}
}
136
swap(t,left,last);
q_sort(t,left,last-1);
q_sort(t,last+1,right);
}
int main()
{
int a[]={3,1,2};
double b[]={3.0,1.0,2.0};
q_sort(a,0,2);
q_sort(b,0,2);
cout << a[0] << a[1] << a[2] << endl;
137
cout << b[0] << b[1] << b[2] << endl;
return 0;
}
Daje w wyniku:
123
123
Jest to tylko najprostszy przykład!
138
8
Aplety Javy – początki
8.1
Strona WWW apletu
Na podstawie [
Wszytkie pokazane programy są drobnymi modyfikacjami programów z
[
].
Program (aplet) hello.java włączany do strony WWW w języku html.
Jeśli przeglądarka nie obsługuje Javy, to aplet jest ignorowany.
Trudno obecnie taką przeglądarkę spotkać.
139
import java.awt.Graphics;
/** Pierwszy aplet w Javie */
public class hello extends java.applet.Applet
{
public void paint(Graphics g)
{
g.drawString("Nie żyje Java", 60,30);
}
}
Kompilacja javac hello.java, otrzymujemy hello.class.
140
Następnie budujemy stronę WWW.
<html>
<!- Strona WWW napisana dla Sun Applet Viewer >
<head>
<title>hello</title>
</head>
<body>
<hr>
<applet
141
code=hello.class
width=200
height=200>
</applet>
<hr>
</body>
</html>
Uruchamiamy przy pomocy programu AppletViewer:
appletviewer hello.htm
142
Uwaga 1. Nie ma metody main(), nie można więc uruchomić
programu poleceniem java.
Uwaga 2. Poleceniem javadoc tworzymy dokumentację w postaci
plików .html.
143
8.2
Pola tekstowe
import java.awt.*;
public class text extends java.applet.Applet
{
TextField text1;
public void init()
{
text1 = new TextField(20);
add(text1);
text1.setText("Niech żyje Java");
}
}
144
Wybrane metody klasy TextField
getText
Pobiera tekst
setText
Wprowadza tekst
processActionEvent(ActionEvent) Przetwarza
zdarzenia
akcji, wysyłając je do
ActionListener
Funkcja init uruchamia się automatycznie, gdy startuje aplet. Funkcja
init nie zwraca wartości.
145
8.3
Przyciski
Jeden przycisk.
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class clicker extends Applet implements ActionListener {
TextField text1;
Button button1;
public void init(){
text1 = new TextField(20);
146
add(text1);
button1 = new Button("Kliknij tutaj!");
add(button1);
button1.addActionListener(this);
}
public void actionPerformed(ActionEvent event){
String msg = new String ("Niech żyje Java");
if(event.getSource() == button1){
text1.setText(msg);
}
}
}
147
Wybrane metody klasy Button
Button()
Tworzy przycisk bez
etykiety
Button(String)
Tworzy przycisk z po-
daną etykietą
addActionListener(ActionListener)
Wprowadza słuchacza
akcji
removeActionListener(ActionListener) Usuwa słuchacza akcji
processActionEvent(ActionEvent)
Przetwarza zdarzenia
akcji, wysyłając je do
ActionListener
148
Wybrane metody klasy String
String()
Tworzy nowy łańcuch
tekstowy
String(String)
Tworzy nowy łańcuch
tekstowy z zawartości
podanego łańcucha
length()
Zwraca
długość
łańcucha
substring(int,int) Zwraca podłańcuch
valueOf(n)
łańcuch
określający
liczbę n
149
Dwa przyciski.
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class clickers extends Applet implements ActionListener {
TextField text1;
Button button1, button2;
public void init(){
text1 = new TextField(20);
150
add(text1);
button1 = new Button("Niech żyje");
add(button1);
button1.addActionListener(this);
button2 = new Button("Java");
add(button2);
button2.addActionListener(this);
}
public void actionPerformed(ActionEvent e){
if(e.getSource() == button1){
text1.setText("Niech żyje");
151
}
if(e.getSource() == button2){
text1.setText("Java");
}
}
}
152
Zobaczmy co się stanie, gdy w pliku .htm zmienimy wiersze
width=200
height=200
na
width=400
height=400
153
8.4
Obszary tekstowe
Obszary tekstowe działają podobnie jak pola tekstowe, ale mają
możliwość pracy z wieloma wierszami tekstu.
Umożliwia utworzenie obszru tekstowego o danej liczbie wiersz i kolumn,
z paskami przewijania oraz możliwościami edycji tekstu.
154
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
public class txtarea extends Applet implements ActionListener {
TextArea textarea1;
Button button1;
public void init(){
textarea1 = new TextArea("", 5, 20);
add(textarea1);
155
button1 = new Button("Kliknij tutaj!");
add(button1);
button1.addActionListener(this);
}
public void actionPerformed (ActionEvent e){
String msg = "Niech żyje Java";
if(e.getSource() == button1){
textarea1.insert(msg, 0);
}
}
}
156
Wybrane metody klasy TextArea
TextArea()
Tworzy nowy obszar
tekstowy
TextArea(String) Tworzy nowy obszar
tekstowy z podanym
tekstem
TextArea()
Tworzy nowy obszar
tekstowy z podanym
tekstem oraz podaną
liczbą wierszy i kolumn
append(String)
Dołącza
tekst
na
końcu obszaru
157
9
Konstruktory kopiujące i funkcje wirtualne
9.1
Konstruktory kopiujące
Konstruktor kopiujący w danej klasie Klasa jest postaci
Klasa::Klasa(Klasa &)
Konstruktor taki służy do skonstruowania obiektu, który jest kopią
innego, już istniejącego.
Wywołanie jawne:
Klasa obiekt_wzor; // wcześniej zdefiniowany
// wzór klasy Klasa
Klasa obiekt_nowy=Klasa(obiekt_wzor); // def. nowego
158
#include <iostream.h>
#include <string.h>
class f_lin {
float a, b;
// wspolczynniki rownania
char nazwa[80] ; // nazwa (opis) rownania
public :
// konstruktor
f_lin(float wsp_a, float wsp_b, char* txt);
159
//konstruktor kopiujacy
f_lin(f_lin &);
float v_lin(float x)
{
return a*x+b;
}
char* opis() {return nazwa;}
} ;
160
f_lin::f_lin(float wsp_a,float wsp_b,char * txt ):
a(wsp_a), b(wsp_b)
{
strcpy(nazwa, txt) ;
}
f_lin::f_lin(f_lin & f1)
{
a = f1.a ;
b = f1.b ;
// zamiast :
strcpy(nazwa, wzorzec.nazwa) ;
161
strcpy(nazwa,
"KONSTRUKTOR KOPIUJACY");
}
void fun_1(f_lin rec) ;
f_lin fun_2(void) ;
/*******************************************************/
main()
{
f_lin g(2.0, 1.0, "Funkcja g");
// Rozne warianty tego samego
162
f_lin
h(g);
//f_lin
h = f_lin(g) ;
//f_lin
h = g ;
cout << "Podaj x: " ;
float x ;
cin
>> x ;
cout << "\nWedlug funkcji liniowej g, \nopisanej jako "
<< g.opis()
<< "\nargumentowi x=" << x
163
<< " odpowiada wartosc "
<< g.v_lin(x) << endl ;
cout << "\nWedlug funkcji liniowej h, \nopisanej jako "
<< h.opis()
<< "\nargumentowi x= " << x
<< " odpowiada wartosc "
<< h.v_lin(x) << endl ;
cout <<"\nDo fun_1 wysylam funkcje liniowa "
<< g.opis() << endl ;
164
fun_1(g) ;
cout << "\nTeraz wywolam fun_2, a jej"
" rezultat\n"
"podstawie do innej funkcji liniowej \n" ;
cout << "Obiekt chwilowy zwrocony jako "
"rezultat funkcji \nma opis "
<< ( fun_2() ).opis()
<< endl ;
}
/*******************************************************/
165
void fun_1(f_lin rec)
{
cout << "Natomiast w fun_1 "
"odebralem te funkcje liniowa\n"
"opisana jako "
<< rec.opis()
<< endl ;
}
/*******************************************************/
f_lin fun_2(void)
{
f_lin w(0, 0, "Funkcja w") ;
166
cout << "W funkcji fun_2 definiuje funkcje liniowa"
" i ma \nona opis "
<< w.opis() << " i wartosc "
<< w.v_lin(1) << " w punkcie x=1"
<< endl ;
return w;
}
167
Program ten daje następujący efekt:
Podaj x: 0.3
Wedlug funkcji liniowej g,
opisanej jako Funkcja g
argumentowi x=0.3 odpowiada wartosc 1.6
Wedlug funkcji liniowej h,
opisanej jako KONSTRUKTOR KOPIUJACY
argumentowi x= 0.3 odpowiada wartosc 1.6
168
Do fun_1 wysylam funkcje liniowa Funkcja g
Natomiast w fun_1 odebralem te funkcje liniowa
opisana jako KONSTRUKTOR KOPIUJACY
Teraz wywolam fun_2, a jej rezultat
podstawie do innej funkcji liniowej
W funkcji fun_2 definiuje funkcje liniowa i ma
ona opis Funkcja w i wartosc 0 w punkcie x=1
Obiekt chwilowy zwrocony jako rezultat funkcji
ma opis KONSTRUKTOR KOPIUJACY
169
Dokładna kopia powstaje przy inicjalizacji nowego obiektu za pomocą
automatycznie generowanego konstruktora kopiującego. Przykład
skopiowany z książki Symfonia C++.
#include <iostream.h>
#include <string.h>
class wizytowka {
public :
char *nazw ;
char *imie ;
// konstruktor
wizytowka(char * na, char * im) ;
170
// destruktor
wizytowka::~wizytowka() ;
void personalia() {
cout << imie << " " << nazw << endl ;
}
//-----------
void zmiana_nazwiska(char *nowe)
{
strcpy(nazw, nowe);
}
} ;
171
// definicja konstruktora
wizytowka::wizytowka(char *im, char *na)
{
nazw = new char [80] ;
strcpy(nazw, na) ;
imie = new char [80] ;
strcpy(imie, im) ;
}
172
// definicja destruktora
wizytowka::~wizytowka()
{
delete nazw ;
delete imie ;
}
main()
{
wizytowka fizyk( "Albert", "Einstein") ;
wizytowka kolega = fizyk ;
173
cout << "Po utworzeniu blizniaczego obiektu oba "
"zawieraja nazwiska\n" ;
fizyk.personalia();
kolega.personalia();
// moj kolega nazywa sie naprawde Albert Metz
kolega.zmiana_nazwiska("Metz");
cout << "\nPo zmianie nazwiska kolegi brzmi ono : ";
174
kolega.personalia();
cout << "Tymczasem niemodyfikowany fizyk"
" nazywa sie : " ;
fizyk.personalia();
}
175
Program ten daje następujący efekt:
Po utworzeniu blizniaczego obiektu oba zawieraja nazwiska
Albert Einstein
Albert Einstein
Po zmianie nazwiska kolegi brzmi ono : Albert Metz
Tymczasem niemodyfikowany fizyk nazywa sie : Albert Metz
176
Jak na to poradzić? Piszemy własny konstruktor kopiujący.
wizytowka::wizytowka(wizytowka & wzor)
{
nazw = new char [80] ;
strcpy(nazw, wzor.nazw) ;
imie = new char [80] ;
strcpy(imie, wzor.imie) ;
}
177
Tak zmodyfikowany program da następujący wynik:
Po utworzeniu blizniaczego obiektu oba zawieraja nazwiska
Albert Einstein
Albert Einstein
Po zmianie nazwiska kolegi brzmi ono : Albert Metz
Tymczasem niemodyfikowany fizyk nazywa sie : Albert Einstein
178
9.2
Funkcje wirtualne
Funkcja wirtualna poprzedzona jest słowem virtual. Nie może być
typu static.
/*--------------------------------------------------
cv.h
----------------------------------------------------*/
#include <iostream.h>
#include <math.h>
class fun0 {
protected:
float x;
179
public:
void virtual v()
{
cout << x <<endl;
}
void arg(float xx) {
x=xx;
}
} ;
class fun1 : public fun0 {
public:
180
void v()
{
cout << exp(x) <<endl;
}
}
;
class fun2 : public fun0 {
public:
void v()
{
cout <<
log(x) <<endl;
}
181
}
;
class fun3 : public fun0 {
public:
void v()
{
cout << sqrt(x) <<endl;
}
}
;
#include "cv.h"
void f(fun0 & fun_e) ;
/*******************************************************/
182
main()
{
fun0
f0;
fun1
f1;
fun2
f2;
fun3
f3;
cout << "Podaj x: " ;
float x ;
cin
>> x ;
183
f0.arg(x);
f1.arg(x);
f2.arg(x);
f3.arg(x);
cout << "Zwykle wywolania funkcji skladowych\n"
"na rzecz obiektow\n" ;
f0.v() ;
f1.v() ;
f2.v() ;
f3.v() ;
184
cout << "Wywolanie funkcji na rzecz obiektu \n"
"pokazywanego wskaznikiem funkcji fun0\n" ;
fun0 *ptrfun ;
// ustawienie wskaznika
ptrfun = & f0;
ptrfun-> v() ;
185
cout << "Rewelacja okazuje sie przy "
"pokazaniu wskaznikiem\ndo funkcji"
" na obiekty klas pochodnych\n"
" od klasy fun ! \n" ;
ptrfun = &f1;
ptrfun-> v() ;
ptrfun = &f2 ;
ptrfun-> v() ;
ptrfun = &f3 ;
186
ptrfun-> v() ;
cout << "Podobne zachowanie jest takze \n"
"w stosunku do referencji \n" ;
f(f0);
f(f1);
f(f2);
f(f3);
}
/*******************************************************/
187
void f(fun0 & fun_e)
{
fun_e.v();
}
188
Wynik działania programu:
Podaj x: 4
Zwykle wywolania funkcji skladowych
na rzecz obiektow
4
54.5982
1.38629
2
Wywolanie funkcji na rzecz obiektu
pokazywanego wskaznikiem funkcji fun0
4
Rewelacja okazuje sie przy pokazaniu wskaznikiem
189
do funkcji na obiekty klas pochodnych
od klasy fun !
54.5982
1.38629
2
Podobne zachowanie jest takze
w stosunku do referencji
4
54.5982
1.38629
2
190
Po pominięciu słowa virtual, czyli
class fun0 {
protected:
float x;
public:
void v()
{
cout << x <<endl;
}
void arg(float xx) {
x=xx;}
} ;
191
otrzymujemy
Podaj x: 4
Zwykle wywolania funkcji skladowych
na rzecz obiektow
4
54.5982
1.38629
2
Wywolanie funkcji na rzecz obiektu
pokazywanego wskaznikiem funkcji fun0
4
192
Rewelacja okazuje sie przy pokazaniu wskaznikiem
do funkcji na obiekty klas pochodnych
od klasy fun !
4
4
4
Podobne zachowanie jest takze
w stosunku do referencji
4
4
4
4
193
Wirtualny – możliwy, mogący zaistnieć. Funkcja oznaczona jako
wirtualna, może (nie musi) być zrealizowana w klasach pochodnych
jeszcze raz inaczej. Bez słowa virtual będzie jak w ostatnim
przykładzie.
Konstruktory nie mogą być virtualne, ale destruktory powinny.
Jeśli klasa deklaruje jedną ze swoich funkcji jako virtual wówczas
destruktor deklarujemy też jako virtual.
194
class fun0 {
protected:
float x;
public:
void virtual v()
{
cout << x <<endl;
}
void arg(float xx) {
x=xx;
}
virtual ~fun0();
195
} ;
class fun1 : public fun0 {
public:
void v()
{
cout << exp(x) <<endl;
}
~fun1(); // też jest virtualny!
}
;
196
Polimorfizm – wielopostaciowość: fragment kodu, w którym wywołuje się
funkcję wirtualną.
Mając tylko wersję binarną (skompilowaną) funkcji f oraz plik
nagłówkowy cv.h można napisać nową klasę
class fun_spec : public fun0 {
public:
void v()
{
cout << sqrt(exp(x)) <<endl;
}
}
;
i wywołać ją w programie
197
#include "cv.h"
main()
{
fun_spec fs;
fs.arg(4);
f(fs);
}
W rezultacie otrzyma się wynik
√
e
4
.
Funkcja wirtualna nie może być klasy static.
198
Klasa abstrakcyjna to klasa, która nie reprezentuje żadnego konkretnego
obiektu. Na przykład funkcja (tak w ogóle) może być klasą podstawową
dla konkretnych funkcji.
#include <iostream.h>
class fun
{
protected:
double x;
double y;
public:
void dane(double xx,double yy){x=xx;y=yy;}
199
double virtual funkcja()=0; // czysto abstrakcyjna
};
class wykladnicza : public fun
{
public:
double funkcja(){return exp(x)+exp(y);}
};
class logarytmiczna : public fun
{
public:
200
double funkcja(){return log(x)+log(y);}
};
main()
{
wykladnicza w;
logarytmiczna l;
fun* wskfun;
wskfun=&w;
wskfun->dane(0,0);
201
cout << wskfun->funkcja() << endl;
wskfun=&l;
wskfun->dane(1,1);
cout << wskfun->funkcja() << endl;
return 0;
}
w rezultacie otrzymuje się
2
0
202