Programowanie w j
ę
zyku C++
Krzysztof Karbowski
Politechnika Krakowska
Kraków 2003
Krzysztof Karbowski: Programowanie w języku C++
2
Wydanie: 24.10.2009
Krzysztof Karbowski: Programowanie w języku C++
3
Ś
rodowisko Visual Studio 2005.
File – New – Project... :
Rys.1.
Rys.2
Krzysztof Karbowski: Programowanie w języku C++
4
Project – Add New Item …
Rys.3.
Kompilacja programu: „Build – Build Solution” (F7).
Uruchomienie programu: "Debug – Debug Without Debugging" (Ctrl+F5)
Krzysztof Karbowski: Programowanie w j
Kompilator Dev-C++
File – New Project...
Programowanie w języku C++
C++
www.bloodshed.net
5
Krzysztof Karbowski: Programowanie w j
Programowanie w języku C++
6
Krzysztof Karbowski: Programowanie w języku C++
7
1. Pierwszy program.
wariant 1:
#include <iostream>
using namespace std;
int main()
{
cout << "Pierwszy program \n" ;
return 0;
}
wariant 2:
#include <stdio.h>
int main()
{
printf("Pierwszy program \n") ;
return 0;
}
UWAGA:
C++ jest językiem o wolnym formacie zapisu. Poza nielicznymi
wyjątkami instrukcje można zapisywać w dowolnym miejscu. Koniec
instrukcji jest oznaczany znakiem „ ; ”.
wariant 3:
#include <stdio.h>
main(){ printf("Pierwszy program \n"); return 0;}
Kompilacja i konsolidacja programu:
1.
kompilator
2.
linker
2. Drugi program
/* --------------------------------------------------
Program na przeliczanie wysokosci podanej w stopach
na wysokosc w metrach.
Opearacje wejscia/wyjscia.
---------------------------------------------------- */
#include <iostream>
using namespace std;
int main()
{
int stopy;
// definicja zmiennej
float metry;
// definicja zmiennej
float przelicznik=0.3;
// def. i inicjalizacja zmiennej
cout << "Podaj wysokosc w stopach: " ;
cin >> stopy ;
// przyjecie danej z klawiatury
metry = stopy * przelicznik ;
cout << endl ;
// wypisanie wynikow
Krzysztof Karbowski: Programowanie w języku C++
8
cout << stopy << " stop to " << metry
<< " metrow \n" ;
return 0;
}
3. Instrukcje steruj
ą
ce.
3.1. Prawda – fałsz
wartość zero
-
odpowiada stanowi: fałsz (FALSE)
wartość inna niż zero -
odpowiada stanowi: prawda (TRUE)
3.2. Instrukcja warunkowa if
Forma 1:
if (wyrażenie) instrukcja1 ;
Forma 2:
if (wyrażenie) instrukcja1 ;
else
instrukcja2 ;
Przykład:
int i ;
cout << “Podaj liczbe: “ ;
cin >> i ;
if (i – 4) cout << "liczba rozna od 4" ;
else cout << "liczba rowna 4" ;
Wybór wielowariantowy:
if (wyrażenie1)
instrukcja1 ;
else if (warunek2)
instrukcja2 ;
else if (warunek3)
instrukcja3 ;
3.3. Blok instrukcji
{
instrukcja1 ;
instrukcja2 ;
instrukcjan ;
}
Przykład:
int i ;
cout << “Podaj liczbe: “ ;
cin >> i ;
if (i < 5)
{
cout << "liczba mniejsza od 5" ;
cout << endl ;
i = 5 ;
} // tu NIE MA srednika
else
{
cout << "Liczba nie jest mniejsza od 5" ;
cout << endl ;
}
Krzysztof Karbowski: Programowanie w języku C++
9
3.4. Instrukcja while
while (wyrażenie) instrukcja ;
Działanie:
Wykonuj instrukcję tak długo jak długo wartość wyrażenia jest niezerowa. Warunek
sprawdzany jest przed wykonaniem instrukcji.
Przykład:
#include <iostream>
using namespace std;
int main()
{
int ile ;
cout << "podaj ilosc znakow: " ;
cin >> ile ;
// narysuj znaki x
while (ile)
{
cout << "x" ;
ile = ile - 1 ;
}
cout << endl ;
return 0;
}
3.5. P
ę
tla do ... while ...
do instrukcja1 while (wyrażenie) ;
Działanie:
Wykonuj instrukcję tak długo jak długo wartość wyrażenia jest niezerowa. Warunek
sprawdzany jest po wykonaniu instrukcji.
Przykład:
#include <iostream>
using namespace std;
int main()
{
char litera;
do
{
cout << "Podaj litere: " ;
cin >> litera ;
cout << "\nNapisales: " << litera << endl ;
} while (litera != ‘k’);
cout << "koniec petli\n" ;
return 0;
}
3.6. P
ę
tla for
for (instrukcja_inicjalizująca ; wyrażenie_warunkowe ; instrukcja_kroku) instrukcja ;
Przykład:
Krzysztof Karbowski: Programowanie w języku C++
10
for (i = 0 ; i < 10 ; i=i+1)
{
cout << "*" ;
}
Działanie:
1.
Wykonywana jest instrukcja inicjalizująca pętli.
2.
Obliczane jest wyrażenie warunkowe. Jeśli jest równe 0 to praca pętli jest przerywana.
3.
Jeżeli wyrażenie warunkowe jest różne od zera, to wykonywane są instrukcje będące
treścią pętli.
4.
Po wykonaniu treści pętli wykonywana jest instrukcja kroku, po czym powtarzana jest
akcja 2.
Przypadki szczególne:
•
Instrukcja inicjalizująca nie musi być jedną instrukcją. Może być ich kilka, wtedy
oddzielone są przecinkami. Podobnie jest w przypadku instrukcji kroku
•
Elementy instrukcja_inicjalizująca , wyrażenie_warunkowe oraz instrukcja_kroku nie
muszą wystąpić. Dowolny z nich można opuścić, zachowując średnik oddzielający go
od sąsiada. Opuszczenie wyrażenia warunkowego traktowane jest tak, jakby stało tam
wyrażenie zawsze prawdziwe.
Przykład
:
#include <iostream>
using namespace std;
int main()
{
int i, ile ;
cout << "Podaj liczbe: " ;
cin >> ile ;
for (i=1; i<=ile; i=i+1)
{
cout << i << ". -*-" ;
cout << endl ;
}
cout << "koniec petli\n" ;
return 0;
}
3.7. Instrukcja switch
switch (wyrażenie)
{
case wart1:
intrukcja1 ;
case wart2:
intrukcja2 ;
default:
instrukcja3 ;
}
Przykład
:
#include <iostream>
using namespace std;
// rozpoznawanie cyfr
int main()
{
Krzysztof Karbowski: Programowanie w języku C++
11
int liczba;
cout << "Podaj liczbe: ";
cin >> liczba;
switch (liczba)
{
case 1:
cout << "jeden" << endl;
break;
case 2:
cout << "dwa" << endl;
// tu nie ma break
case 3:
cout << "trzy" << endl;
break;
default:
cout << "nie znam" << endl;
break;
}
return 0;
}
3.8. Instrukcja break
Instrukcja break przerywa działanie pętli:
•
for
•
while
•
do ... while
Przykład:
int i=7;
while (1)
{
i=i-1;
if (i<5) break;
}
3.9. Instrukcja goto
goto etykieta ;
Skok do miejsca w programie opatrzonego etykietą.
Przykład:
cout << "tekst 1" ; //zostanie wyswietlony
cout << "tekst 2" ; //zostanie wyswietlony
goto etykieta1;
cout << "tekst 3" ; //nie zostanie wyswietlony
etykieta1:
cout << "tekst 4" ; //zostanie wyswietlony
3.10. Instrukcja continue
Instrukcja continue działa wewnątrz pętli:
•
for
•
while
•
do ... while
Krzysztof Karbowski: Programowanie w języku C++
12
i powoduje zaniechanie wykonywania instrukcji wewnątrz pętli ale nie przerywa działania
pętli.
Przykład:
int i;
for (i=0; i<10; i=i+1)
{
cout << i;
if (i>5) continue;
cout << endl;
}
cout << endl;
Inaczej można by zapisać:
int i;
for (i=0; i<10; i=i+1)
{
cout << i;
if (i>5) goto koniec;
cout << endl;
koniec:
}
cout << endl;
3.11. Klamry w instrukcjach steruj
ą
cych
Przykład 1:
while (i < 4) {
i = i + 1 ;
}
Przykład 2 (Visual Studio 6.0):
while (i < 4)
{
i = i + 1 ;
}
Przykład 3:
while (i < 4)
{
i = i + 1 ;
}
4. Typy
4.1. Deklaracje typów
Deklaracja – informuje kompilator, że dana nazwa reprezentuje obiekt jakiegoś typu, ale nie
rezerwuje dla niego miejsca w pamięci.
Definicja - dodatkowo rezerwuje miejsce w pamięci.
Przykład:
Krzysztof Karbowski: Programowanie w języku C++
13
int a ; //deklaracja i definicja obiektu typu "int" o nazwie "a"
extern int b; //deklaracja obiektu typu "int" o nazwie "b";
//definicja wyst
ą
pi w innym miejscu
4.2. Systematyka typów j
ę
zyka C++
•
typy fundamentalne
•
typy pochodne
•
typy wbudowane
•
typy zdefiniowane przez użytkownika
4.3. Typy fundamentalne
4.3.1. Typy reprezentuj
ą
ce liczby całkowite
•
short int
inaczej:
short
•
int
•
long int
inaczej:
long
4.3.2. Typ reprezentuj
ą
cy znaki alfanumeryczne
•
char
4.3.3. Modyfikatory powy
ż
szych typów:
•
signed
liczba ze znakiem
•
unsigned
liczba bez znaku (liczba dodatnia)
Przez domniemanie przyjmuje się, iż brak modyfikatora oznacza typ z modyfikatorem signed.
4.3.4. Typy reprezentuj
ą
ce liczby zmiennoprzecinkowe
•
float
•
double
•
long double
Przykłady:
int a ;
short b ;
short int c ;
long double d ;
unsigned int c ;
4.4. Definiowanie obiektów “w biegu”
#include <iostream>
using namespace std;
int main()
{
int a ;
a=1;
int b; // deklaracja/definicja obiektu
Krzysztof Karbowski: Programowanie w języku C++
14
b=a+1
cout << b;
return 0;
}
4.5. Stałe dosłowne
4.5.1. Stałe b
ę
d
ą
ce liczbami całkowitymi:
np.:
17
-33
0
1000
W układzie ósemkowym (zapis rozpoczyna się od cyfry 0):
np.:
010
014
061
W układzie szesnastkowym (zapis rozpoczyna się od 0x):
np.:
0x10
0xa1
0xff
Stałe całkowite traktowane są jak typ int, chyba że reprezentują liczby, które nie zmieściły by
się w typie int – wtedy stała jest typu long.
Wymuszenie typu stałej:
200L
-
stała typu long
277u
-
stała z modyfikatorem unsigned
50uL
-
stała unsigned long
4.5.2. Stałe reprezentuj
ą
ce liczby zmiennoprzecinkowe
12.3
-10.1
10.4e-3
4.5.3. Stałe znakowe
‘a’
‘7’
Znaki specjalne:
‘\n’
-
nowa linia
‘\t’
-
tabulator
‘\\’
-
backslash
‘\’’
-
apostrof
‘\"‘
-
cudzysłów
‘\0’
-
znak o kodzie 0 (NULL)
‘\?’
-
znak zapytania
4.5.4. Stałe tekstowe (ła
ń
cuchy tekstowe, stringi)
"To jest ła
ń
cuch tekstowy"
4.6. Typy pochodne
Operatory umożliwiające tworzenie obiektów typów pochodnych:
[]
– tablica obiektów danego typu
*
– wskaźnik do obiektu danego typu
()
– funkcja zwracająca wartość danego typu
&
– referencja obiektu danego typu
Przykłady:
int t[10] ;
// tablica 10 elementów typu int
float *p ;
// wska
ź
nik do obiektu typu float
char func() ;
//funkcja zwracaj
ą
ca obiekt typu char
Krzysztof Karbowski: Programowanie w języku C++
15
5.6.1. Typ void
void *p ;
- oznacza, że p jest wskaźnikiem do obiektu nieznanego typu
void funkcja();
- funkcja nie zwraca wartości
4.7. Zakres wa
ż
no
ś
ci nazwy obiektu, a czas
ż
ycia obiektu
Czas życia obiektu – okres od momentu zdefiniowania obiektu (przydzielenia miejsca w
pamięci) do momentu, gdy przestaje on istnieć (zwolnienia miejsca w pamięci).
Zakres ważności nazwy obiektu – część programu, w której nazwa znana jest
kompilatorowi.
4.8. Zasłanianie nazw
Przykład
:
#include <iostream>
using namespace std;
int k=33; // zmienna globalna
int main()
{
cout << "main (przed blokiem): k=" << k << endl;
{ //poczatek bloku: zakres lokalny
int k=1;
cout << "blok: k=" << k << endl;
cout << "dostep do zmiennej globalnej k=" <<
::k << endl;
} //koniec bloku
cout << "main (za blokiem): k=" << k << endl;
return 0;
}
4.9. Modyfikator const
Służy do tworzenia obiektów stałych, których wartości nie da się zmienić.
const float pi=3.1415927;
Wartości tak zainicjalizowanego obiektu nie można zmienić.
Inicjalizacja – nadanie wartości obiektowi w momencie jego utworzenia.
Przypisanie – wstawienie do obiektu wartości w jakimkolwiek późniejszym momencie.
Obiekty const można inicjalizować, ale nie można do nich nic przypisać.
4.10. Instrukcja typedef
Pozwala na nadanie dodatkowej nazwy już istniejącemu typowi.
typedef int cena ;
typedef char * napis ;
cena x;
//co odpowiada: int x;
napis komunikat;
//co odpowiada: char * komunikat;
4.11. Typy wyliczeniowe enum
enum nazwa_typu {lista_wyliczeniowa} ;
Przykład:
enum liczby { zero, jeden, dwa, trzy };
enum liczby2
{
Krzysztof Karbowski: Programowanie w języku C++
16
jedenascie=11,
trzynascie=13,
dwadziescia=20
};
liczby a;
//definicja zmiennej
liczby2 b;
//definicja zmiennej
a=zero;
b=dwadziescia;
5. Operatory
5.1. Operatory arytmetyczne
+
dodawanie
-
odejmowanie
*
mnożenie
/
dzielenie
Przykład:
a = b + c ;
a = b – c ;
a = b * c ;
a = b / c ;
a = (c + d + 3.1) / f ;
5.1.1. Operator modulo
Oblicza resztę z dzielenia
a = b % c ;
5.1.2. Jednoargumentowe operatory + i –
Przykład:
+12.7 -x -(a*b)
5.1.3. Operatory inkrementacji i dekrementacji
i = i + 1 ;
można zastąpić
i++ ;
k = k – 1;
można zastąpić
k--;
Formy operatorów:
•
przedrostkowa (prefix):
++a
lub
--b .
Najpierw zmieniana jest wartość zmiennej, a
następnie zmieniona wartośc staje się wartością wyrażenia.
•
końcówkowa (postfix):
a++
lub
b-- .
Wartość zmiennej staje się wartością wyrażenia, a
następnie zmieniana jest wartość zmiennej.
Przykład:
int a=0;
int b=0;
cout << ++a << endl; // pojawi si
ę
1
cout << b++ << endl; // pojawi si
ę
0
cout << a << endl; // pojawi si
ę
1
cout << b << endl; // pojawi si
ę
1
5.1.4. Operator przypisania =
m = 27.9
Krzysztof Karbowski: Programowanie w języku C++
17
Do obiektu stającego po lewej stronie operatora = wstawiona zostaje wartość wyrażenia
stojącego po prawej.
5.2. Operatory logiczne
5.2.1. Operatory relacji
<
mniejszy niż ...
<=
mniejszy lub równy
>
większy niż ...
>=
większy lub równy
!=
różny
= =
równy
Przykład
:
#include <iostream>
using namespace std;
int main()
{
int a=10;
int b=5;
cout << "a=" << a << " b=" << b << endl;
if (a=b)
// tu powinno byc a==b
cout << "a=b" << endl;
else
cout << "a!=b" << endl;
return 0;
}
Powyższy program wyświetli informacje, że a=b, gdyż w wyrażeniu warunkowym instrukcji
if znajduje się instrukcja przypisania a nie operator relacji. Instrukcja przypisania jest
wyrażeniem, a więc ma wartość (wartość wyrażenia po prawej stronie znaku =). W tym
przypadku jest to wartość 5, która jest różna od zera, a więc zostanie zinterpretowana jako
"prawda".
5.2.2. Operatory sumy i iloczynu logicznego
||
suma logiczna – operacja logiczna "lub" (alternatywa)
&&
iloczyn logiczny – operacja logiczna "i" (koniunkcja)
Przykład:
int k=2;
if ((k==10) || (k==2))
cout << "k równe 2 lub 10 \n" ;
if ((k>=0) && (k<=10))
cout << "k nalezy do przedzialu [0,10] \n" ;
5.2.3. Operator negacji logicznej
! wyrażenie
Przykład:
int i = 0 ;
if (!i)
cout << "tekst\n" ;
Krzysztof Karbowski: Programowanie w języku C++
18
5.3. Operatory bitowe
zmienna
<<
ile_miejsc
przesunięcie w lewo
zmienna
>>
ile_miejsc
przesunięcie w prawo
zmienna1
&
zmienna2
bitowy iloczyn logiczny (AND)
zmienna1
|
zmienna2
bitowa suma logiczna (OR)
zmienna1
^
zmienna2
bitowa różnica symetryczna (XOR)
~
zmienna
bitowa negacja
5.4. Pozostałe operatory przypisania
operator
zamiennik
i = i + 2 i += 2
i = i – 2 i -= 2
i = i * 2 i *= 2
i = i / 2 i /= 2
i = i % 2 i %= 2
i = i >> 2 i >>= 2
i = i << 2 i <<= 2
i = i & 2 i &= 2
i = i | 2 i |= 2
i = i ^ 2 i ^= 2
5.5. Wyra
ż
enie warunkowe
(warunek) ? wartość1 : wartość2
Jeżeli warunek ma wartość niezerową to wartością wyrażenia stanie się wartość1 – w
przeciwnym wypadku wartość2.
Przykład:
int a;
a=5;
c = (a<10) ? 0 : 100 ;
// zmiennej c zostanie przypisane 0
5.6. Operator sizeof
Zwraca rozmiar obiektu lub typu.
sizeof(nazwa_typu)
lub
sizeof(nazwa_obiektu)
Zwrócona liczba jest typu
size_t
(inaczej:
unsigned int
).
Gdy argumentem operatora jest nazwa tablicy statycznej zwraca rozmiar tablicy w bajtach.
Nie jest w stanie określić rozmiaru tablicy alokowanej dynamicznie oraz tablicy będącej
parametrem formalnym funkcji.
5.7. Operator rzutowania
(nazwa_typu)obiekt
lub
nazwa_typu(obiekt)
Umożliwia przekształcenie typu obiektu.
Przykład:
int a;
float b=10.1;
a=(int)b;
cout << a << endl;
//zostanie wyswietlona wartosc 10
Krzysztof Karbowski: Programowanie w języku C++
19
5.8. Operator przecinek
Umożliwia grupowanie wyrażeń.
(2+4, a*4, 3<6, 77+2)
Wyrażenia składowe obliczane są od lewej do prawej. Wartością całego wyrażenia staje się
wartość wyrażenia znajdującego się najdalej z prawej strony.
5.9. Programy przykładowe
Przykład
:
Temat: Program dodający dwie liczby rzeczywiste.
#include <iostream>
using namespace std;
int main()
{
float a, b, wynik;
cout << "Podaj pierwsza liczbe: ";
cin >> a;
cout << "Podaj druga liczbe: ";
cin >> b;
wynik=a+b;
cout << "Suma: " << wynik << endl;
return 0;
}
Przykład
:
Temat: Drukowanie alfabetu.
#include <iostream>
int main()
{
char znak;
for (znak=‘a’; znak<=‘z’; znak++)
cout << znak ;
cout << endl ;
return 0;
}
Przykład
:
Temat: Obliczanie n! .
#include <iostream>
using namespace std;
int main()
{
unsigned long silnia, liczba, licz;
cout << "Podaj liczbe: ";
cin >> liczba;
silnia=1;
licz=2;
if (liczba)
while (licz<=liczba) //for ( ; licz<=liczba; )
{
silnia *= licz;
Krzysztof Karbowski: Programowanie w języku C++
20
licz ++;
}
cout << liczba << "! =" << silnia << endl;
return 0;
}
Przykład
:
Temat: Wyjście z pętli gdy wprowadzona liczba jest równa zero.
#include <iostream>
using namespace std;
int main()
{
int liczba;
do
{
cout << "Podaj liczbe: ";
cin >> liczba;
if (!liczba) cout << "zero - koniec petli\n";
else cout << "To nie jest zero\n";
} while (liczba);
return 0;
}
Przykład
:
Temat: Program pobiera dwie liczby a następnie kod operacji: 0 – koniec; 1 – suma; 2 –
różnica; 3 – iloczyn; 4 – iloraz. Wyświetla wynik obliczeń.
#include <iostream>
int main()
{
float a, b;
int kod;
do
{
cout << "Wprowadz a= ";
cin >> a;
cout << "Wprowadz b= ";
cin >> b;
cout << "0:koniec; 1:(+); 2:(-); 3:(*); 4:(/)." <<
"Podaj kod: ";
cin >> kod;
switch (kod)
{
case 0:
cout << "Koniec pracy\n";
break;
case 1:
cout << a << "+" << b << "=" << a+b << endl;
break;
case 2:
cout << a << "-" << b << "=" << a-b << endl;
break;
Krzysztof Karbowski: Programowanie w języku C++
21
case 3:
cout << a << "*" << b << "=" << a*b << endl;
break;
case 4:
cout << a << "/" << b << "=" << a/b << endl;
break;
default:
cout << "Nieznany kod operacji\n";
break;
}
} while (kod); //while (kod!=0);
return 0;
}
6. Funkcje
Przykład
:
#include <iostream>
using namespace std;
char literki(int ile);
//deklaracja funkcji
//----------------------------------------------
int main()
{
int m;
char znak;
cout << "Podaj ilosc literek: ";
cin >> m;
znak=literki(m);
cout << "Drukowano litere " << znak << endl;
return 0;
}
//----------------------------------------------
// Definicja funkcji
char literki(int ile)
{
int i;
for (i=0; i<ile; i++)
cout << "f";
cout << endl;
return ‘f’;
}
6.1. Zwracanie rezultatu przez funkcj
ę
Przykład
: Temat: Program obliczający potęgi liczb całkowitych z podanego przedziału.
#include <iostream>
using namespace std;
long potega(long liczba, int stopien);
void koniec();
//------------------------
int main()
{
long pocz, kon, i;
cout << "Podaj poczatek przedzialu: ";
cin >> pocz;
Krzysztof Karbowski: Programowanie w języku C++
22
cout << "Podaj koniec przedzialu: ";
cin >> kon;
for (i=pocz; i<=kon; i++)
{
cout << i << "^2=" << potega(i,2) << "\t";
cout << i << "^3=" << potega(i,3) << endl;
}
koniec();
return 0;
}
//--------------------------
long potega(long liczba, int stopien)
{
long wynik=liczba;
int i;
for (i=1; i<stopien; i++)
wynik *= liczba;
return wynik;
}//----------------
void koniec()
{
cout << "\nKONIEC PRACY\n";
}
6.2. Przesyłanie argumentów
Rodzaje przesyłania argumentów do funkcji:
•
przez wartość,
•
przez referencję,
•
przez wskaźnik.
Przykład
:
Temat: przesyłanie argumentów przez wartość i przez referencję.
#include <iostream>
using namespace std;
//---------------------------
void zeruj(int a, int &b)
{
cout << "-------------\n";
cout << "Funkcja\n";
cout << "a=" << a << "\tb=" << b << endl;
a=0; b=0;
cout << "Po wyzerowaniu:\n";
cout << "a=" << a << "\tb=" << b << endl;
cout << "-------------\n";
return;
}
//---------------------------
int main()
{
int A=7, B=77;
cout << "Main\n";
cout << "A=" << A << "\tB=" << B << endl;
zeruj(A,B);
cout << "Main\n";
Krzysztof Karbowski: Programowanie w języku C++
23
cout << "Po wywolaniu funkcji:\n";
cout << "A=" << A << "\tB=" << B << endl;
return 0;
}
6.3. Argumenty domniemane
Przykład:
Deklaracje funkcji:
void funkcja1(int arg1, int arg2=0);
void funkcja2(float arg1, int arg2=1, int arg3=5);
Wywołanie funkcji:
funkcja1(10);
funkcja1(10,0);
funkcja1(-10,5);
funkcja2(-2.1,-5);
funkcja2(-2.1,-5,5);
funkcja2(-1, ,3);
tak jest
Ź
LE
Określenie argumentów domniemanych musi wystąpić w deklaracji
funkcji lub w definicji, która jest jednocześnie deklaracją.
6.4. Funkcje inline
Przykład:
inline int dodaj(int a, int b)
{
return a+b;
}
Kompilator utworzy kod, w którym w miejscu wywołania funkcji zostanie wstawiony kod
definiujący funkcję, a nie skok do miejsca definicji funkcji.
Definicja funkcji inline musi znajdować się przed jej wywołaniem, gdyż
w momencie wywołania funkcja musi być znana kompilatorowi – nie
wystarczy deklaracja funkcji.
6.5. Zakresy wa
ż
no
ś
ci nazw deklarowanych wewn
ą
trz funkcji
•
Zakres ważności nazw deklarowanych w obrębie funkcji ogranicza się tylko do bloku tej
funkcji. Nie można spoza funkcji za pomocą danej nazwy próbować dotrzeć do zmiennej
będącej w obrębie funkcji
•
Nie można wykonać instrukcji goto spoza funkcji do etykiety zdefiniowanej wewnątrz
funkcji.
6.6. Wybór zakresu wa
ż
no
ś
ci nazwy i czasu
ż
ycia obiektu
6.6.1. Obiekty globalne
Obiekty zadeklarowane na zewnątrz wszystkich funkcji.
Zakres ważności: plik programu.
Czas życia: czas działania programu.
Wartość początkowa: 0
6.6.2. Obiekty automatyczne
Obiekty zadeklarowane wewnątrz bloku programu (np. wewnątrz funkcji, bloku
instrukcji).
Zakres ważności: blok programu.
Krzysztof Karbowski: Programowanie w języku C++
24
Czas życia: od momentu wystąpienia definicji do końca bloku.
Wartość początkowa: przypadkowa.
6.6.2. Obiekty lokalne statyczne
Obiekty zadeklarowane wewnątrz bloku programu (np. wewnątrz funkcji, bloku
instrukcji) i poprzedzone słowem
static
.
Przykład:
static int a;
static float b=2.1;
Zakres ważności: blok programu.
Czas życia: czas działania programu.
Wartość początkowa: 0.
Krzysztof Karbowski: Programowanie w j
6.7. Obiekty w programie składaj
Przykład
:
Plik main.cpp:
#include <iostream
using namespace std;
#include "funkcja1.h"
#include "funkcja2.h"
int a;
//definicja zmien
int main()
{
int x, y;
cout << "Podaj zmienna globalna a=";
cin >> a;
cout << "Podaj x=";
cin >> x;
cout << "Podaj y=";
cin >> y;
Wypisz_a();
cout << "x+y=" << dodaj(x,y) << endl;
cout << "x-y=" << odejmij(x,y) << endl;
return 0;
}
Plik funkcja1.cpp:
#include <iostream
using namespace std;
extern int a;
//deklaracja zmiennej globalnej
//--------------------------
int dodaj(int a,
{
return a+b;
}
//-------------------------
Programowanie w języku C++
6.7. Obiekty w programie składaj
ą
cym si
ę
z kilku plików
iostream>
std;
#include "funkcja1.h"
#include "funkcja2.h"
//definicja zmiennej globalnej
cout << "Podaj zmienna globalna a=";
cout << "Podaj x=";
cout << "Podaj y=";
cout << "x+y=" << dodaj(x,y) << endl;
y=" << odejmij(x,y) << endl;
iostream>
std;
//deklaracja zmiennej globalnej
--------------------------
a, int b)
-------------------------
25
z kilku plików
Krzysztof Karbowski: Programowanie w języku C++
26
void Wypisz_a()
{
cout << "\nZmienna globalna a=" << a << endl;
}
Plik funkcja2.cpp:
int odejmij(int a, int b)
{
return a-b;
}
Plik funkcja1.h:
int dodaj(int a, int b);
void Wypisz_a();
Plik funkcja2.h:
int odejmij(int a, int b);
6.7.1. Nazwy statyczne globalne
Deklaracje globalne poprzedzone słowem
static
.
Nazwa ta nie będzie znana w innych plikach.
7. Preprocesor
7.1. Dyrektywa #define
#define wyraz ciąg znaków zastępujących go
Kompilator zamieni wszelkie wystąpienia wyrazu ciągiem znaków zastępujących go.
Przykład 1:
#define DWA 2
i=DWA + 2;
Przykład 2:
#define DO_START
do {
#define DO_STOP
}while (!stop);
DO_START
a=a+1;
cin >> stop;
DO_STOP
7.2. Makrodefinicje
#define wyraz(parametry) ciąg znaków zastępujących go
Kompilator zamieni wszelkie wystąpienia wyrazu ciągiem znaków zastępujących go
uwzględniając parametry. Stąd w makrodefinicjach najczęściej występuje nadmiar nawiasów,
aby ustrzec się przed potencjalnymi błędami.
Przykład:
#define KWADRAT(a)
((a) * (a))
wynik=KWADRAT(liczba);
zostanie zamienione na:
wynik=((liczba) * (liczba)) ;
Krzysztof Karbowski: Programowanie w języku C++
27
7.3. Dyrektywy kompilacji warunkowej
#if warunek
linie kompilowane warunkowo
#endif
lub
#if warunek
linie kompilowane warunkowo
#else
linie kompilowane warunkowo
#endif
lub
#ifdef nazwa
linie kompilowane warunkowo
#else
linie kompilowane warunkowo
#endif
lub
#ifndef nazwa
linie kompilowane warunkowo
#else
linie kompilowane warunkowo
#endif
Przykład 1:
#define WARIANT 1
#if (WARIANT==1)
// linie programu
// .............
#else
// linie programu
// .............
#endif //WARIANT==1
7.4. Wstawianie innych plików
#include <nazwa_pliku_1>
#include "nazwa_pliku_2"
Przykład:
Zabezpieczenie przed wielokrotnym dołączeniem pliku:
#ifndef PLIK1_H
#define PLIK1_H
// zwykla tresc pliku
// ..................
#endif //PLIK1_H
7.5. Dyrektywa #undef
Przykład
#define ABC
// zaczyna obowiazywac nazwa ABC
// ...................
// linie programu
Krzysztof Karbowski: Programowanie w języku C++
28
// .....................
#undef ABC
// przestaje obowiazywac nazwa ABC
// .....................
8. Tablice
Definicja tablicy:
typ nazwa[rozmiar];
Rozmiar tablicy musi być stałą znaną w trakcie kompilacji.
Przykłady:
char zdanie[80];
// tablica o nazwie zdanie zawierajaca
// 80 elementów typu char
float numer[9]; // tablica 9 elementow typu float
int *wskaz[20]; // tablica 20 element bedacych wskaznikami
// obiektow typu int
Tablice można tworzyć z:
•
typów fundamentalnych (za wyjątkiem void),
•
typów wyliczeniowych (enum),
•
wskaźników,
•
innych tablic,
•
z obiektów typu zdefiniowanego przez użytkownika,
•
ze wskaźników do pokazywania na składniki klasy.
8.1. Elementy tablicy
Tablica:
int t[4];
składa się z następujących elementów:
t[0] t[1] t[2] t[3]
Numeracja elementów tablicy zaczyna się od zera.
Dla zdefiniowanej powyżej tablicy możliwe są m.in. następujące instrukcje przypisania:
t[0]=1;
t[1]=23;
t[2]=-5;
t[3]=10;
Poniższe przypisanie RÓWNIEś ZOSTANIE WYKONANE:
t[4]=1;
Kompilator C++ nie sprawdza zakresu indeksów tablicy
Sposób obliczenia rozmiaru tablicy (nie dotyczy tablicy dynamicznej i parametru formalnego
funkcji):
size_t rozmiar = sizeof(tab)/sizeof(tab[0]);
Przykład
:
#include <iostream>
using namespace std;
int main()
{
int t[4];
Krzysztof Karbowski: Programowanie w języku C++
29
for (int i=0; i<4; i++)
t[i]=2*i;
for (i=0; i<4; i++)
cout << "t[" << i << "]=" << t[i] << endl;
return 0;
}
8.2. Inicjalizacja tablic
Przykłady:
int tab1[4] = { 17, 5, 4, 200 };
int tab2[4] = { 3,-2 };
// pozostałe elementy zostan
ą
// zainicjalizowane wartoscia 0
int tab3[] = {-3, 1, 6, 8 };
// zostanie zdefiniowana
// tablica "int tab3[4]"
8.3. Przekazywanie tablic do funkcji
Nazwa tablicy jest równocześnie adresem jej zerowego elementu.
Tablice przesyła się podając funkcji tylko adres początku tablicy.
Przykład
:
#include <iostream>
using namespace std;
void dodaj2(int tablica[], int ile);
//----------------------------
int main()
{
const int rozmiar=4;
int tab[rozmiar];
// mo
ż
na wykorzystac zmienna rozmiar
// gdyz jest to zmienna z modyfikatorem
// const
int i;
for (i=0; i<rozmiar; i++)
tab[i]=i;
dodaj2(tab,rozmiar);
for (i=0; i<rozmiar; i++)
cout << "tab[" << i << "]=" << tab[i] << endl;
return 0;
}
//-------------------------------
void dodaj2(int tablica[], int ile)
{
int i;
for (i=0; i<ile; i++)
tablica[i] += 2;
}
Przykład
:
#include <iostream>
using namespace std;
#define ROZMIAR 4
void dodaj2(int tablica[ROZMIAR]);
//----------------------------
int main()
{
int tab[ROZMIAR];
Krzysztof Karbowski: Programowanie w języku C++
30
int i;
for (i=0; i<ROZMIAR; i++)
tab[i]=i;
dodaj2(tab);
for (i=0; i<ROZMIAR; i++)
cout << "tab[" << i << "]=" << tab[i] << endl;
return 0;
}
//-------------------------------
void dodaj2(int tablica[ROZMIAR])
{ // ROZMIAR nie jest konieczny, gdyz funkcja nie korzysta z
tej
// informacji
int i;
for (i=0; i<ROZMIAR; i++)
tablica[i] += 2;
}
W funkcji do określenia rozmiaru tablicy nie można wykorzystać operatora
sizeof
, gdyż na
podstawie parametru formalnego nie da się określić rozmiaru elementu.
8.4. Tablice znakowe
Łańcuch znaków (string) – ciąg znaków alfanumerycznych zakończony znakiem o kodzie 0
(\0 – NULL).
Przykłady tablic znakowych:
char zdanie[80];
// tablica do przechowywania 80 elementów
// b
ę
d
ą
cych znakami.
char sTab[10] = { "lancuch" };
sTab:
l a n c u c h NULL 0 0
Inne sposoby inicjalizacji:
char sTab[10] = { ‘k’, ‘o’, ‘t’ };
k o t 0 0 0 0 0 0 0
Ponieważ NULL ma kod 0, więc łańcuch zostanie poprawnie zakończony.
char sTab[] = { ‘k’, ‘o’, ‘t’ };
k o t
Kompilator określi rozmiar tablicy na podstawie liczby elementów na liście
inicjalizacyjnej. Nie ma tam znaku NULL, a więc nie jest to tablica przechowująca łańcuch
znakowy.
char sTab[] = { "kot" };
k o t NULL
Na liście inicjalizacyjnej znajduje się łańcuch znakowy, więc kompilator
automatycznie doda znak NULL.
W powyższy sposób można jedynie inicjalizować tablice. Nie można wykonywać operacji
przypisania.
Krzysztof Karbowski: Programowanie w języku C++
31
Przykład
:
Temat: Funkcja kopiująca łańcuchy znakowe.
#include <iostream>
using namespace std;
void kopiuj(char cel[], char zrodlo[]);
void wypisz(char tab[]);
//----------------------------
int main()
{
char sTab1[] = { "kot"};
char sTab2[10];
cout << "sTab1=";
wypisz(sTab1);
kopiuj(sTab2,sTab1);
cout << "sTab2=";
wypisz(sTab2);
return 0;
}
//------------------------------
void kopiuj(char cel[], char zrodlo[])
{
int i;
for (i=0; ; i++)
{
cel[i]=zrodlo[i];
if (zrodlo[i]==NULL) break;
}
}
//------------------------------
void wypisz(char tab[])
{
int i;
for (i=0; ; i++)
if (tab[i]==NULL) break;
else cout << tab[i];
cout << endl;
}
Przykład
:
Temat: Program wykorzystujący funkcję biblioteczną kopiująca łańcuchy znakowe.
#include <iostream>
#include <string>
using namespace std;
void wypisz(char tab[]);
//----------------------------
int main()
{
char sTab1[] = { "kot"};
char sTab2[10];
cout << "sTab1=";
wypisz(sTab1);
strcpy(sTab2,sTab1);
cout << "sTab2=";
wypisz(sTab2);
return 0;
Krzysztof Karbowski: Programowanie w języku C++
32
}
//----------------------------
void wypisz(char tab[i]) // taka jak w poprzednim programie
8.5. Tablice wielowymiarowe
Są to tablice, których elementami są inne tablice.
int Tab[4][2]; // tablica czterech elementów, z których
// ka
ż
dy jest dwuelementow
ą
tablic
ą
// obiektów typu int
Uwaga: zapis
Tab[i,j]
zostanie zinterpretowany jako
Tab[j]
(występuje operator
"przecinek").
8.5.1. Inicjalizacja tablic wielowymiarowych:
int Tab1[3][2] = { 11, 12, 21, 22, 31, 32 };
int Tab2[3][2] = { {11, 12}, {21, 22}, {31, 32} };
Element Tab1[1,1] jest przesunięty w stosunku do początku tablicy o
(1*2)+1
elementów,
czyli
numer_wiersza*ilo
ść
_kolumn+numer_kolumny
– aby obliczyć jego położenie
musi być znana liczba kolumn tablicy (drugi wymiar) – nie jest konieczna znajomość ilości
wierszy (pierwszy wymiar).
0
1
0
11
12
1
21
22
2
31
32
8.5.2. Przesyłanie tablic wielowymiarowych do funkcji
Aby funkcja mogła obliczyć, gdzie w pamięci znajduje się określony element tablicy – musi
znać liczbę kolumn tablicy.
void funkcja1(float tab[][4]);// funkcja musi zna
ć
liczb
ę
// kolumn
void funkcja2(float tab[][3][5]); // aby obliczy
ć
poło
ż
enie
// elementu
// void funkcja3( float tab[][][]) tak jest
ź
le
9. Wska
ź
niki
Wskaźnik to obiekt do przechowywania informacji o adresie i typie
innego obiektu.
9.1. Definiowanie wska
ź
ników
int * w ;
// w jest wska
ź
nikiem do obiektów typu int
char * pLancuch;
// pLancuch jest wskaznikiem do obiektow typu char
float * pWsk;
// pWsk jest wskaznikiem do obiektow typu float
Zastosowanie wskaźników:
•
ulepszenie pracy z tablicami,
•
funkcje mogące zmieniać wartość przysyłanych do nich argumentów,
Krzysztof Karbowski: Programowanie w języku C++
33
•
dostęp do specjalnych komórek pamięci,
•
rezerwacja obszarów pamięci.
9.2. Praca ze wska
ź
nikiem
Przykład
:
#include <iostream>
using namespace std;
//----------------------------
int main()
{
int nLiczba=5;
int nLiczba2=10;
int *pInt;
int *pInt2;
int *pInt3;
float Liczba3=1.2;
float *pFloat;
void *pWsk;
cout << "nLiczba=" << nLiczba << endl; // pojawi sie 5
pInt = &nLiczba; // ustawienie wskaznika
cout << "pInt=" << pInt << endl; // pojawi sie adres
cout << "(*pInt)=" << *pInt << endl; // pojawi sie 5
cout << "Przypisanie *pInt=6\n" ;
*pInt = 6 ;
cout << "(*pInt)=" << *pInt << endl; // pojawi sie 6
cout << "nLiczba=" << nLiczba << endl; // pojawi sie 6
cout << "zmiana obiektu wskazywanego\n" ;
pInt = &nLiczba2;
cout << "nLiczba2=" << nLiczba2 << endl; // pojawi sie 10
cout << "(*pInt)=" << *pInt << endl; // pojawi sie 10
// --------------------------------
pFloat = &Liczba3;
cout << "(*pFloat)=" << *pFloat << endl; // pojawi sie 1.2
// --------------------------------
// pFloat=pInt; // <<< - zostanie zasygnalizowany blad
pWsk = pInt;
cout << "pWsk=" << pWsk << endl; // pojawi sie adres
// cout << *pWsk; // <<< - blad
pInt2 = (int*)pWsk; // musi byc rzutowanie
cout << "(*pInt2)=" << *pInt2 << endl; // pojawi sie 10
pFloat = (float*)pInt; // musi byc rzutowanie
cout << "(*pFloat)=" << *pFloat << endl; // pojawi sie
// przypadkowa liczba mimo prawidlowego adresu
pInt3 = (int*)pFloat; // musi byc rzutowanie
cout << "(*pInt3)=" << *pInt3 << endl; // pojawi sie 10
return 0;
}
Wskaźnik do obiektu każdego niestałego typu można przypisać
wskaźnikowi typu void.
Krzysztof Karbowski: Programowanie w języku C++
34
9.3. Zastosowanie wska
ź
ników wobec tablic
Przykład:
int *wsk1, *wsk2;
int tab1[3]={11, 12, 13 };
int tab2[3]={21, 22, 23 };
wsk1 = &tab1[0];
wsk2 = tab2; // nazwa tablicy jest adresem jej pierwszego
// elementu
wsk1 = &tab1[2]; // wskaznik pokazuje na ostatni element
// tablicy
wsk2 = wsk2 + 1; // wskaznik pokazuje na drugi element
// tablicy
Dodanie do wskaźnika liczby całkowitej powoduje, że pokazuje on
o tyle dalszy element tablicy niezależnie od tego jakiego typu są to
elementy (niezależnie od ilości pamięci przeznaczonej na każdy
element).
Przykład
:
#include <iostream>
using namespace std;
int main()
{
int *wsk1, *wsk2;
int tab[10];
int i;
wsk1=tab;
for (i=0; i<10; i++)
*(wsk1+i)=i*10;
cout << "Elementy tablicy:\n";
for (i=0; i<10; i++)
cout << i << ": " << *(wsk1+i) << endl;
cout << endl;
wsk2 = &tab[3];
cout << "3: " << *wsk2 << endl;
wsk2 += 2;
cout << "(3+2): " << *wsk2 << endl;
return 0;
}
9.4. Działania na wska
ź
nikach
(wskaźnik + liczba_całkowita) – przesunięcie wskaźnika o podaną liczbę elementów w przód,
(wskaźnik – liczba_całkowita) – przesunięcie wskaźnika o podaną liczbę elementów w tył,
(wskaźnik – wskaźnik) – gdy wskaźniki pokazują na elementy tej samej tablicy to wynikiem
będzie liczba dzielących je elementów (liczba dodatnia lub ujemna).
9.5. Porównywanie wska
ź
ników
operator
==
równość wskaźników oznacza, że pokazują na ten sam obiekt,
operator
!=
przeciwieństwo operatora
==
,
Operatory mające zastosowanie do wskaźników pokazujących na elementy tej samej tablicy:
Krzysztof Karbowski: Programowanie w języku C++
35
> < >= <=
9.6. Zastosowanie wska
ź
ników w argumentach funkcji
Przykład
:
#include <iostream>
using namespace std;
void zmien(int *wsk);
//---------------------
int main()
{
int liczba=1;
cout << "Przed wywolaniem funkcji: " << liczba << endl;
zmien(&liczba);
cout << "Po wywolaniu funkcji: " << liczba << endl;
return 0;
}
//---------------------
void zmien(int *wsk)
{
*wsk=100;
}
9.6.1. Odbieranie tablicy jako wska
ź
nika
Przykład
:
#include <iostream>
using namespace std;
void fkc_tab(int tab[], int rozmiar);
void fkc_wsk1(int *wsk, int rozmiar);
void fkc_wsk2(int *wsk, int rozmiar);
//---------------------
int main()
{
int tablica[4] = { 1, 2, 3, 4 };
fkc_tab(tablica,4);
fkc_wsk1(tablica,4);
fkc_wsk2(tablica,4);
cout << endl;
return 0;
}
//---------------------
void fkc_tab(int tab[], int rozmiar)
{
cout << "\nPrzeslano jako tablice\n" ;
for (int i=0; i<rozmiar; i++)
cout << tab[i] << "\t";
}
//---------------------
void fkc_wsk1(int *wsk, int rozmiar)
{
cout << "\nPrzeslano jako wskaznik - wariant 1\n" ;
for (int i=0; i<rozmiar; i++)
cout << *(wsk+i) << "\t";
}
//---------------------
void fkc_wsk2(int *wsk, int rozmiar)
Krzysztof Karbowski: Programowanie w języku C++
36
{
cout << "\nPrzeslano jako wskaznik - wariant 2\n" ;
for (int i=0; i<rozmiar; i++)
cout << wsk[i] << "\t";
}
9.6.2. Argument formalny b
ę
d
ą
cy wska
ź
nikiem do obiektu const
Przykład:
void funkcja(const int *wsk, int rozmiar);
Wskaźnik const pokazuje na obiekty, ale nie pozwala na ich modyfikacje.
Zastosowanie:
1.
Zagwarantowanie, że obiekt wysłany do funkcji poprzez wskaźnik nie zostanie
zmieniony,
2.
Umożliwienie wysłania do funkcji poprzez wskaźnik obiektu stałego (można na niego
pokazać tylko wskaźnikiem do stałej).
9.7. Zastosowanie wska
ź
ników przy dost
ę
pie do konkretnych
komórek pami
ę
ci
Przykład:
wsk = 0x0065fde8;
Wskaźnikowi należy przypisać adres komórki pamięci (sposób adresowania zależny jest od
architektury komputera, systemu operacyjnego i kompilatora).
9.8. Rezerwacja obszarów pami
ę
ci
9.8.1. Operatory new i delete
Przykład:
int *wsk;
wsk = new int;
// ...........
delete wsk;
Operator
new
utworzy nowy obiekt typu
int
, który nie będzie miał nazwy, a jego adres
zostanie przekazany do wskaźnika
wsk
. Operator
delete
zlikwiduje obiekt pokazywany
przez wskaźnik
wsk
.
Dynamiczne tworzenie tablic:
float *wsk;
int rozmiar;
cout << “Podaj rozmiar tablicy: “; cin >> rozmiar;
wsk = new float[rozmiar];
//....
delete [] wsk;
Wstępna inicjalizacja obiektu:
int *wsk;
wsk = new int(12);
Alokacja obiektu w konkretnym miejscu pamięci:
wsk = 0x0065fde8;
wsk2 = wsk new int;
Cechy obiektów utworzonych operatorem
new
:
Krzysztof Karbowski: Programowanie w języku C++
37
•
obiekty te nie mają nazwy – dostęp do nich odbywa się poprzez wskaźnik,
•
początkowe wartości obiektów są przypadkowe, chyba że zostały wstępnie
zainicjalizowane,
•
czas życia: od chwili utworzenia operatorem
new
do momentu usunięcia operatorem
delete
,
•
zakres ważności: jeśli w danym momencie jest przynajmniej jeden wskaźnik pokazujący
na obiekt, to jest do niego dostęp.
Przykład
:
#include <iostream>
using namespace std;
int main()
{
int *wsk1, *wsk2;
int i, rozmiar;
cout << "Podaj rozmiar tablicy: ";
cin >> rozmiar;
wsk1 = new int[rozmiar];
if (wsk1==NULL)
cout << "Brak pamieci\n";
else
{
for (i=0; i<rozmiar; i++)
wsk1[i]=i*10;
cout << "Tablica:\n";
for (i=0; i<rozmiar; i++)
cout << i << ": " << wsk1[i] << endl;
wsk2 = new int;
*wsk2=10;
cout << "*wsk2 = " << *wsk2 << endl;
wsk2=wsk1; // utrata dostepu do obiektu
delete [] wsk1;
wsk1=NULL;
//delete wsk2; // niebezpieczenstwo zalamania programu
}
return 0;
}
Wskaźniki definiowane z modyfikatorem
static
inicjalizowane są
wartością
NULL
. Pozostałe wskaźniki mają wartości przypadkowe, a
więc wskazują na przypadkowe obszary pamięci. Próba zapisu takiego
miejsca może spowodować załamanie programu.
9.9. Sposoby inicjalizowania wska
ź
ników
wsk = & obiekt;
wsk = inny_wskaznik;
wsk = tablica;
wsk = funkcja;
Krzysztof Karbowski: Programowanie w języku C++
38
wsk = new int;
wsk = new float[10];
wsk = 0x82a5f2; // adres
wsk = "lancuch tekstowy";
9.10. Tablice wska
ź
ników
Przykład – pięcioelementowa tablica wskaźników do obiektów typu
float
:
float *tabwsk[5]; // inaczej: float *(tabwsk[5]);
Przykład – tablice wskaźników do stringów:
char *tab1[3];
char *tab2[3] = { "jeden", "dwa", "trzy" };
W tablicy
tab2
znajdują się wskaźniki do miejsc w pamięci, w których zlokalizowane są
poszczególne łańcuchy tekstowe.
Przykład
:
#include <iostream>
using namespace std;
char* dopisz_spacje(const char *wsk1, char *wsk2);
char* dopisz_spacje2(const char *wsk1, char **wsk2);
void kody(const char *wsk, int rozmiar);
//--------------------
int main()
{
char tab1[] = { "tablica1" };
char tab2[20] = { 't','a','b','l','i','c','a','2' };
char tab3[] = { 't','a','b','l','i','c','a','3','\0' };
char *wsk1 = "wskaznik1";
char *wsk2 = { "wskaznik2" };
char *wsk=NULL;
cout << "tab1: " << tab1 << endl;
wsk = new char[80];
cout << "t a b 1 : " << dopisz_spacje(tab1,wsk) << endl;
delete [] wsk;
wsk=NULL;
cout << "t a b 1 (wersja 2): " << dopisz_spacje2(tab1,&wsk)
<< endl;
delete [] wsk;
cout << "tab2: ";
kody(tab2,20);
cout << "tab3: " << tab3 << endl;
cout << "wsk1: " << wsk1 << endl;
cout << "wsk2: " << wsk2 << endl;
return 0;
}
//--------------------
char* dopisz_spacje(const char *wsk1, char *wsk2)
{
Krzysztof Karbowski: Programowanie w języku C++
39
int ile_znakow, i;
for (ile_znakow=0; *(wsk1+ile_znakow)!=NULL; ile_znakow++)
/* */;
for (i=0; i<ile_znakow; i++)
{
wsk2[2*i] = wsk1[i];
wsk2[2*i+1] = ' ';
}
wsk2[2*ile_znakow]=NULL;
return wsk2;
}
//------------------------
char* dopisz_spacje2(const char *wsk1, char **wsk2)
{
int ile_znakow, i;
for (ile_znakow=0; *(wsk1+ile_znakow)!=NULL; ile_znakow++)
/* */;
*wsk2 = new char[2*ile_znakow+1];
for (i=0; i<ile_znakow; i++)
{
(*wsk2)[2*i] = wsk1[i];
(*wsk2)[2*i+1] = ' ';
}
(*wsk2)[2*ile_znakow]=NULL;
return *wsk2;
}
//----------------------
void kody(const char *wsk, int rozmiar)
{
int i;
for (i=0; i<rozmiar; i++)
cout << int(*(wsk+i)) << ',';
cout << endl;
}
9.11. Wska
ź
niki do funkcji
Definicja:
int (*wsk_do_funkcji)(int, char);
oznacza, że
wsk_do_funkcji
jest wskaźnikiem do funkcji wywoływanej z parametrami
typu
int
i
char
oraz zwracającej wartość typu
int
.
Nazwa funkcji jest jednocześnie adresem jej początku.
Przykład
:
#include <iostream>
using namespace std;
int dodaj_dwa(int a);
int dodaj_trzy(int a);
int wywolaj(int (*wsk)(int), int liczba);
Krzysztof Karbowski: Programowanie w języku C++
40
//----------------------
int main()
{
int a=10;
int (*wsk_fkc)(int parametr);
int (*tab_wsk_fkc[2])(int); // tablica wskaznikow do funkcji
cout << "dodaj_dwa: " << dodaj_dwa(a) << endl;
cout << "dodaj_trzy: " << dodaj_trzy(a) << endl;
wsk_fkc=dodaj_dwa;
cout << "wsk. do dodaj_dwa: " << (*wsk_fkc)(a) << endl;
wsk_fkc=dodaj_trzy;
cout << "wsk. do dodaj_trzy: " << (*wsk_fkc)(a) << endl;
cout << "f(dodaj_trzy): " << wywolaj(dodaj_trzy,a) << endl;
tab_wsk_fkc[0]=dodaj_dwa;
tab_wsk_fkc[1]=dodaj_trzy;
cout << "tablica ze wsk. do dodaj_trzy: "
<< (*tab_wsk_fkc[1])(a) << endl;
return 0;
}
//------------------------
int dodaj_dwa(int a)
{ return a+2; }
//------------------------
int dodaj_trzy(int a)
{ return a+3; }
//------------------------
int wywolaj(int (*wsk)(int), int liczba)
{ return (*wsk)(liczba); }
___________________________________________________________________________
10. Przeładowanie nazw funkcji
Przeładowanie nazwy funkcji polega na tym, że w danym zakresie ważności jest więcej niż
jedna funkcja o tej samej nazwie. Poszczególne funkcje różnią się typami argumentami.
Przykład
:
#include <iostream>
using namespace std;
void drukuj(int);
void drukuj(float);
void drukuj(char);
void drukuj(int, float);
void drukuj(float, int);
//-----------------
int main()
{
int a = 10;
float b = 12.6;
char c = ‘a’;
drukuj(a);
drukuj(b);
drukuj(c);
Krzysztof Karbowski: Programowanie w języku C++
41
drukuj(a,b);
drukuj(b,a);
return 0;
}
//-----------------
void drukuj(int a)
{ cout << a << endl; }
//-----------------
void drukuj(float a)
{ cout << a << endl; }
//-----------------
void drukuj(char a)
{ cout << a << endl; }
//------------------
void drukuj(int a, float b)
{ cout << a << " " << b << endl; }
//------------------
void drukuj(float b, int a)
{ cout << a << " " << b << endl; }
Przykład – poniższych funkcji nie można przeładować:
void funkcja(int tab[]);
void funkcja(int *wsk);
10.1. Wska
ź
nik do funkcji przeładowanej
Przykład:
int funkcja(int);
int funkcja(float);
//------------
int (*wsk_do_fkc)(int);
wsk_do_fkc = funkcja; // do wskaznika zostanie przypisany adres
// funkcji "funkcja(int)" – wynika to z definicji wskaznika
11. Klasy
Definicja klasy:
class nazwa_klasy
{
// cialo klasy
// ..............
} ;
// <<- srednik
Definicja obiektu danej klasy (typu zdefiniowanego przez użytkownika):
nazwa_klasy zmienna;
Definicja wskaźnika i referencji do obiektu danej klasy:
nazwa_klasy * wsk ;
nazwa_klasy & refer = zmienna;
Krzysztof Karbowski: Programowanie w języku C++
42
11.1. Składniki klasy
Dostęp do składników klasy:
•
obiekt.składnik
•
wskaźnik
->
składnik
•
referencja.składnik
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//---------------------
class COsoba
{
public:
char m_sImie[80];
char m_sNazwisko[80];
int m_nWaga;
int m_nWzrost;
};
//---------------------
int main()
{
COsoba Kowalski;
COsoba &rOsoba = Kowalski; // referencja (musi byc
//
zainicjalizowana)
COsoba *pOsoba; // wskaznik
strcpy(Kowalski.m_sImie, "Jan");
strcpy(Kowalski.m_sNazwisko, "Kowalski");
Kowalski.m_nWaga = 65;
Kowalski.m_nWzrost = 178;
pOsoba = & Kowalski;
cout << Kowalski.m_sImie << " " << Kowalski.m_sNazwisko
<< endl;
cout << "waga: " << pOsoba->m_nWaga << endl;
cout << "wzrost: " << rOsoba.m_nWzrost << endl;
return 0;
}
11.2. Funkcje składowe klasy
Definicja funkcji składowej klasy jako funkcji typu
inline
:
class nazwa_klasy
{
// ............
typ nazwa_funkcji(lista_parametrow)
{
// cialo funkcji
}
};
Deklaracja i definicja funkcji składowej klasy:
class nazwa_klasy
{
Krzysztof Karbowski: Programowanie w języku C++
43
// ............
typ nazwa_funkcji(lista_parametrow);
};
typ nazwa_klasy :: nazwa_funkcji(lista_parametrow)
{
// cialo funkcji
}
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//---------------------
class COsoba
{
public:
char m_sImie[80];
char m_sNazwisko[80];
int m_nWaga;
int m_nWzrost;
void WstawDane(char *imie, char *nazwisko,
int waga, int wzrost);
void WydrukujDane();
};
void COsoba::WstawDane(char *imie, char *nazwisko,
int waga, int wzrost)
{
strcpy(m_sImie, imie);
strcpy(m_sNazwisko, nazwisko);
m_nWaga = waga;
m_nWzrost = wzrost;
}
void COsoba::WydrukujDane()
{
cout << m_sImie << " " << m_sNazwisko << endl;
cout << "waga: " << m_nWaga << endl;
cout << "wzrost: " << m_nWzrost << endl;
}
//---------------------
int main()
{
COsoba Kowalski;
COsoba *pOsoba;
Kowalski.WstawDane("Jan","Kowalski",65,178);
pOsoba = & Kowalski;
pOsoba->WydrukujDane();
return 0;
}
11.3. Rodzaje dost
ę
pu do składników klasy
Rodzaje dostępu do składników (zmiennych i funkcji) klasy:
•
private
– składniki dostępne są wyłącznie dla składników klasy,
•
public
– składniki są dostępne bez ograniczeń.
Krzysztof Karbowski: Programowanie w języku C++
44
Przykład:
class nazwa_klasy
{
// skladniki prywatne
private:
// deklaracje/definicje składników
// ----------------------
// składniki publiczne
public:
// deklaracje/definicje składników
};
Dopóki w definicji klasy nie wystąpi żadna etykieta to składniki mają dostęp
private
.
11.4. Obiekty b
ę
d
ą
ce składnikami klasy
Przykład klasy zawierającej obiekt innej klasy:
class zarowka
{
public:
int moc;
double srednica;
void zaswiec();
void zgas();
};
class lampa
{
public:
int wysokosc;
zarowka punkt_swietlny1;
zarowka punkt_swietlny2;
void zaswiec();
void zgas();
};
11.5. Przesyłanie do funkcji argumentów b
ę
d
ą
cych obiektami
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//---------------------
class COsoba
{
public:
char m_sImie[80];
char m_sNazwisko[80];
int m_nWaga;
int m_nWzrost;
void WstawDane(char *imie, char *nazwisko,
int waga, int wzrost);
};
void COsoba::WstawDane(char *imie, char *nazwisko,
int waga, int wzrost)
{
strcpy(m_sImie, imie);
strcpy(m_sNazwisko, nazwisko);
Krzysztof Karbowski: Programowanie w języku C++
45
m_nWaga = waga;
m_nWzrost = wzrost;
}
//---------------------
void wydrukuj_dane(COsoba osoba);
void wydrukuj_nazwisko(COsoba & osoba);
//----------------------
int main()
{
COsoba Kowalski;
Kowalski.WstawDane("Jan","Kowalski",65,178);
wydrukuj_dane(Kowalski);
wydrukuj_nazwisko(Kowalski);
return 0;
}
//---------------------
void wydrukuj_dane(COsoba osoba)
// przekazanie obiektu przez wartosc
{
cout << osoba.m_sImie << " " << osoba.m_sNazwisko << endl;
cout << "waga: " << osoba.m_nWaga << endl;
cout << "wzrost: " << osoba.m_nWzrost << endl;
}
//----------------------
void wydrukuj_nazwisko(COsoba & osoba)
// przekazanie obiektu przez referencje
{
cout << "Nazwisko: " << osoba.m_sNazwisko << endl;
}
11.6. Składnik statyczny
class nazwa_klasy
{
// cialo klasy
static typ1 nazwa1;
// deklaracja składnika statycznego
static typ2 nazwa2;
// ...........
};
typ1 nazwa_klasy::nazwa1; // definicja składnika statycznego
typ2 nazwa_klasy::nazwa2 = wartosc; // definicja
// i inicjalizacja składnika stat.
Cechy składnika statycznego:
•
miejsce w pamięci dla składnika statycznego jest przydzielane tylko raz,
niezależnie od ilości obiektów danej klasy – składnik statyczny jest wspólny dla
wszystkich obiektów klasy,
•
składnik statyczny istnieje nawet wtedy gdy nie ma żadnego obiektu danej klasy,
•
dostęp do składnika statycznego:
nazwa_klasy::nazwa_skladnika
•
dostęp do składnika statycznego gdy istnieją obiekty danej klasy:
obiekt.nazwa_skladnika
wskaznik->nazwa_skladnika
Krzysztof Karbowski: Programowanie w języku C++
46
11.7. Statyczna funkcja składowa
class nazwa_klasy
{
// ciało klasy
static typ nazwa_funkcji(parametry); // deklaracja funkcji
// ...
};
Cechy statycznej funkcji składowej:
•
funkcję można wywołać gdy nie istnieje żaden obiekt danej klasy,
•
wewnątrz funkcji nie ma wskaźnika
this
,
•
funkcja nie ma możliwości odwoływania się do niestatycznych składników klasy,
•
sposoby wywołania funkcji: takie jak dostęp do składnika statycznego.
Przykład
:
#include <iostream>
using namespace std;
class klasa
{
public:
int numer;
static long licznik;
static void zerujlicznik(){ licznik=0; }
};
long klasa::licznik = 0;
//---------
int main()
{
cout << "licznik=" << klasa::licznik << endl;
klasa obiekt1;
obiekt1.licznik++;
cout << "licznik=" << klasa::licznik << endl;
klasa obiekt2;
obiekt2.licznik++;
cout << "licznik=" << klasa::licznik << endl;
klasa::zerujlicznik();
cout << "licznik=" << klasa::licznik << endl;
return 0;
}
11.8. Funkcje składowe typu const
class nazwa_klasy
{
// ciało klasy
typ nazwa_funkcji(parametry) const;
// ............
};
typ nazwa_klasy::nazwa_funkcji(parametry) const
{
// ciało funkcji
}
Są to funkcje deklarujące, że nie będą zmieniały obiektu, na rzecz którego pracują.
Służą do pracy z obiektami zdefiniowanymi jako
const
.
Krzysztof Karbowski: Programowanie w języku C++
47
12. Funkcje zaprzyja
ź
nione
Są to funkcje, które mają dostęp do składników prywatnych danej klasy.
class nazwa_klasy
{
friend typ nazwa_funkcji(parametry);
// cialo klasy
};
typ nazwa_funkcji(parametry)
{
// cialo funkcji
}
Funkcje te nie posiadają wskaźnika
this
do składników klasy, z którą są zaprzyjaźnione, a
więc muszą posługiwać się operatorami
.
(kropka) lub
->
.
Przykład
:
#include <iostream>
using namespace std;
class klasa
{
friend int przyjaciel(klasa ob);
private:
int liczba;
public:
void ustaw(int wartosc) { liczba=wartosc; }
void wyswietl() { cout << "liczba=" << liczba << endl; }
};
//---------------
int przyjaciel(klasa ob)
{
return ob.liczba;
}
//---------------
int main()
{
klasa objekt;
objekt.ustaw(10);
objekt.wyswietl();
cout << "Przyjaciel: liczba=" << przyjaciel(objekt) << endl;
return 0;
}
13. Struktury, unie i pola bitowe
13.1. Struktura
Struktura to klasa, w której wszystkie składniki są publiczne. Składnikami struktur nie mogą
być funkcje.
struct nazwa_struktury
{
// lista składnikow
};
Krzysztof Karbowski: Programowanie w języku C++
48
13.2. Unia
Unia to struktura, w której poszczególne składniki zajmują to samo miejsce w pamięci.
Przykład:
union pojemnik
{
char c;
int i;
float f;
};
13.3. Pole bitowe
Pole bitowe to typ składnika klasy polegający na tym, że informacja jest przechowywana na
określonej liczbie bitów.
Przykład:
class klasa
{
public:
unsigned int bit1 : 1;
unsigned int bit4 : 4;
};
14. Konstruktory i destruktory
14.1. Konstruktor
Konstruktor to funkcja składowa klasy postaci
nazwa_klasy(parametry);
która jest automatycznie wywoływana podczas definiowania obiektu danej klasy.
Cechy konstruktora:
•
konstruktor może być przeładowywany,
•
konstruktor nie ma wyspecyfikowanego typu wartości zwracanej,
•
konstruktor nie zwraca żadnej wartości (nawet
void
),
•
konstruktor może być wywoływany dla tworzenia obiektów z modyfikatorem
const
, ale sam nie może być funkcją typu
const
,
•
konstruktor nie może być typu
static
,
•
nie można posłużyć się adresem konstruktora.
14.2. Kiedy wywoływany jest konstruktor
14.2.1. Konstruowanie obiektów lokalnych
Obiekty lokalne automatyczne – konstruktor uruchamiany jest w momencie, gdy program
napotyka definicję obiektu.
Obiekty lokalne statyczne – konstruktor zostanie uruchomiony w momencie uruchomienia
programu.
14.2.2. Konstruowanie obiektów globalnych
Konstruktor zostanie uruchomiony w momencie uruchomienia programu.
Krzysztof Karbowski: Programowanie w języku C++
49
14.2.3. Konstrukcja obiektów tworzonych operatorem new
Konstruktor zostanie uruchomiony po wywołaniu operatora
new
. Konstruktor nie przydziela
miejsca w pamięci dla tworzonego obiektu – jedynie inicjalizuje wartości składników.
14.2.3. Inne przypadki uruchamiania konstruktora
Konstruktor wywoływany jest podczas tworzenia obiektów chwilowych swojej klasy.
Konstruktor jest wywoływany jeśli jest tworzony obiekt jakiejś klasy, który zawiera obiekt
klasy tego konstruktora.
14.3. Destruktor
Destruktor to funkcja składowa klasy postaci
~nazwa_klasy();
która jest automatycznie wywoływana podczas likwidowania obiektu danej klasy.
Cechy destruktora:
•
destruktor nie może zwracać żadnej wartości (nawet
void
),
•
destruktor jest wywoływany bez jakichkolwiek argumentów,
•
destruktor nie może być przeładowany,
•
nie można pobrać adresu destruktora,
•
destruktor nie może być funkcją typu
const
, ale może pracować na rzecz
obiektów typu
const
.
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//-------------------
class napis
{
private:
char tekst[80];
char przypis[80];
int x;
int y;
public:
napis();
napis(char *t, char *p, int wspx=0, int wspy=0);
~napis();
void wyswietl();
};
//-------
napis::napis()
{
strcpy(tekst,"BRAK");
strcpy(przypis,"BRAK");
x=0; y=0;
wyswietl();
}
//--------
napis::napis(char *t, char *p, int wspx, int wspy)
{
strcpy(tekst,t);
strcpy(przypis,p);
Krzysztof Karbowski: Programowanie w języku C++
50
x=wspx;
y=wspy;
wyswietl();
}
//---------
napis::~napis()
{
cout << "destruktor: " << tekst << endl;
}
//---------
void napis::wyswietl()
{
cout << "\n------------------\n";
cout << "tekst: " << tekst << endl;
cout << "przypis: " << przypis << endl;
cout << "x=" << x << " y=" << y << endl;
cout << "\n------------------\n";
}
//------------------------------------
int main()
{
napis Tekst1("Tekst1","Przypis1",10,10);
napis Tekst2;
napis Tekst3("Tekst3","Przypis3");
napis Tekst4 = napis("Tekst4","Przypis4",1,2);
napis *wsk;
wsk = new napis("Wskaznik","Przypis",5,3);
delete wsk;
return 0;
}
14.4. Konstruktor domniemany
Konstruktor domniemany ta taki konstruktor, który można wywołać bez żadnego argumentu.
Przykład:
class klasa1
{
//...........
public:
klasa1(int a);
klasa1();
// konstruktor domniemany
klasa1(float b);
//.........
};
class klasa2
{
//...........
public:
klasa2(int a);
klasa2(float b);
klasa2(int a=1; double b=3.1); // konstruktor domniemany
//............
};
Jeśli klasa nie ma żadnego konstruktora, to kompilator sam wygeneruje dla tej klasy
konstruktor domniemany.
Krzysztof Karbowski: Programowanie w języku C++
51
14.5. Lista inicjalizacyjna konstruktora
Definicja konstruktora z listą inicjalizacyjną:
nazwa_klasy::nazwa_klasy(parametry) : lista_inicjalizacyjna
{
// ciało konstruktora
}
Etapy wykonania konstruktora:
1.
inicjalizacja składników z listy inicjalizacyjnej konstruktora,
2.
przypisania i inne akcje zdefiniowane w ciele konstruktora
Cechy listy inicjalizacyjnej:
•
składnik bez modyfikatora
const
można inicjalizować przez listę inicjalizacyjną lub
przez przypisanie w ciele konstruktora,
•
składnik typu
const
można inicjalizować tylko za pomocą listy inicjalizacyjnej,
•
lista inicjalizacyjna nie może inicjalizować składnika
static
.
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//-----------------
class klasa
{
public:
int a;
int b;
const int c;
char tekst[20];
klasa(int aa, int bb, int cc, char *t);
void wyswietl();
};
klasa::klasa(int aa, int bb, int cc, char *t)
: a(aa), b(bb), c(cc)
{
strcpy(tekst,t);
wyswietl();
}
void klasa::wyswietl()
{
cout << "---------------\n";
cout << tekst << endl;
cout << "a=" << a << " b=" << b << " c=" << c << endl;
cout << "---------------\n";
}
//---------------------
int main()
{
klasa dane(1,2,3,"Dane");
return 0;
}
Krzysztof Karbowski: Programowanie w języku C++
52
14.6. Konstrukcja obiektu, którego składnikiem jest obiekt innej
klasy
Przykład
:
#include <iostream>
#include <string>
using namespace std;
#define MAXDLSTRINGU 50
//---------------------------------
class CWyswietlacz
{
private:
int m_nMin;
int m_nMax;
int m_nWskazanie;
char m_sOpis[MAXDLSTRINGU];
public:
CWyswietlacz(const char* sOpis, int nMin, int nMax,
int nWskazanie=0);
~CWyswietlacz();
void Ustaw(int nWartosc);
void Wyswietl();
};
//------------
CWyswietlacz::CWyswietlacz(const char* sOpis, int nMin,
int nMax, int nWskazanie)
: m_nMin(nMin), m_nMax(nMax),
m_nWskazanie(nWskazanie)
{
strcpy(m_sOpis,sOpis);
cout << "Konstruktor wyswietlacza " << m_sOpis << endl;
}
//------------
CWyswietlacz::~CWyswietlacz()
{
cout << "Destruktor wyswietlacza " << m_sOpis << endl;
}
//------------
void CWyswietlacz::Ustaw(int nWartosc)
{
m_nWskazanie=nWartosc;
}
//------------
void CWyswietlacz::Wyswietl()
{
cout << m_sOpis << ": " << m_nWskazanie << endl;
}
//---------------------------------
class CPrzyrzad
{
private:
CWyswietlacz m_objWoltomierz;
CWyswietlacz m_objAmperomierz;
char m_sNazwa[MAXDLSTRINGU];
Krzysztof Karbowski: Programowanie w języku C++
53
public:
CPrzyrzad(const char* sNazwa, const char* sWoltOpis,
int nWoltMin, int nWoltMax, int nWoltWskaz,
const char* sAmpOpis, int nAmpMin, int AmpMax,
int nAmpWskaz);
~CPrzyrzad();
void UstawWolt(int nWartosc);
void UstawAmp(int nWartosc);
void WyswietlWolt();
void WyswietlAmp();
};
//-----------
CPrzyrzad::CPrzyrzad(const char* sNazwa, const char* sWoltOpis,
int nWoltMin, int nWoltMax, int nWoltWskaz,
const char* sAmpOpis, int nAmpMin, int nAmpMax,
int nAmpWskaz)
: m_objWoltomierz(sWoltOpis,nWoltMin,nWoltMax,
nWoltWskaz),
m_objAmperomierz(sAmpOpis,nAmpMin,nAmpMax,nAmpWskaz)
{
strcpy(m_sNazwa,sNazwa);
cout << "Konstruktor przyrzadu " << m_sNazwa << endl;
}
//-----------
CPrzyrzad::~CPrzyrzad()
{
cout << "Destruktor przyrzadu " << m_sNazwa << endl;
}
//-----------
void CPrzyrzad::UstawWolt(int nWartosc)
{
m_objWoltomierz.Ustaw(nWartosc);
}
//-----------
void CPrzyrzad::UstawAmp(int nWartosc)
{
m_objAmperomierz.Ustaw(nWartosc);
}
//-----------
void CPrzyrzad::WyswietlWolt()
{
cout << m_sNazwa << "-> ";
m_objWoltomierz.Wyswietl();
}
//-----------
void CPrzyrzad::WyswietlAmp()
{
cout << m_sNazwa << "-> ";
m_objAmperomierz.Wyswietl();
}
//-----------------------------------------------
//-----------------------------------------------
int main()
{
CPrzyrzad objMiernik("Miernik",
"Woltomierz miernika",0,10,5,
Krzysztof Karbowski: Programowanie w języku C++
54
"Amperomierz miernika",0,10,9);
objMiernik.WyswietlWolt();
objMiernik.WyswietlAmp();
CPrzyrzad objPanel("Panel",
"Woltomierz panelu",0,10,1,
"Amperomierz panelu",0,10,2);
objPanel.UstawWolt(10);
objPanel.UstawAmp(10);
objPanel.WyswietlWolt();
objPanel.WyswietlAmp();
return 0;
}
Kolejność wywołania konstruktorów:
1.
Konstruktory obiektów składowych,
2.
Konstruktor klasy zawierającej obiekty.
Kolejność wywoływania destruktorów:
1.
Destruktor klasy zawierającej obiekty.
2.
Destruktory obiektów składowych,
Obiekt klasy będącej składnikiem innej klasy może być inicjalizowany
jedynie za pomocą listy inicjalizacyjnej konstruktora.
Uwagi:
•
jeśli klasa obiektu składowego nie ma konstruktora, wtedy nie umiescza się go na liście
inicjalizacyjnej,
•
jeśli klasa obiektu składowego ma konstruktor domniemany (i ten konstruktor chcemy
użyć), to wywołanie tego konstruktora na liście inicjalizacyjnej można pominąć,
•
jeśli klasa obiektu składowego ma konstruktory inne niż domniemany, to pominięcie
wywołania konstruktora na liście inicjalizacyjnej spowoduje błąd kompilacji.
14.7. Konstruktor kopiuj
ą
cy (inicjalizator kopiuj
ą
cy)
Jest to konstruktor następującego typu:
klasa::klasa(klasa & obiekt);
Konstruktor kopiujący służy do skonstruowania obiektu, który jest kopią innego, już
istniejącego obiektu danej klasy. Konstruktor ten pracuje gdy następuje inicjalizacja a nie
przypisanie obiektu.
Konstruktor kopiujący można wywołać z jednym argumentem będącym referencją
obiektu danej klasy. Konstruktor ten może mieć więcej parametrów, ale muszą to być
parametry domniemane.
Przykład jawnego wywołania konstruktora kopiujacego:
K obiekt_wzor;
// definicja obiektu wzorcowego
K obiekt_nowy = K(obiekt_wzor); // definicja nowego obiektu
albo
K obiekt_nowy = obiekt_wzor;
albo
K obiekt_nowy(obiekt_wzor);
Analogicznie dla typów wbudowanych można by zapisać:
int wzor = 1;
Krzysztof Karbowski: Programowanie w języku C++
55
int kopia1 = int(wzor);
int kopia2 = wzor;
int kopia3(wzor);
Niejawne wywołanie konstruktora kopiującego:
1.
podczas przesyłania obiektu do funkcji, gdy przesłanie odbywa się przez wartość,
2.
podczas zwracania obiektu jako rezultatu funkcji, jeśli funkcja zwraca rezultat przez
wartość (ta sytuacja może być zależna od implementacji kompilatora – Visual C++ 6.0
nie utworzy obiektu tymczasowego).
Przykład
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CKlasa
{
public:
int m_nLiczba;
char m_sString[30];
CKlasa(int nLiczba, const char* sString);
CKlasa(const CKlasa &objWzor);
~CKlasa();
};
CKlasa::CKlasa(int nLiczba, const char* sString)
{
m_nLiczba=nLiczba;
strcpy(m_sString,sString);
cout << "Konstruktor " << m_sString << endl;
}
CKlasa::CKlasa(const CKlasa &objWzor)
{
m_nLiczba=objWzor.m_nLiczba;
strcpy(m_sString,objWzor.m_sString);
cout << "Konstruktor kopiujacy " << m_sString << endl;
}
CKlasa::~CKlasa()
{
cout << "Destruktor " << m_sString << endl;
}
//--------------------------
void NapiszString(CKlasa objWartosc)
{
cout << "NapiszString: " << objWartosc.m_sString << endl;
}
//--------------------------
CKlasa UtworzObiekt(int nLiczba, const char* sString)
{
CKlasa objLokalny(nLiczba,sString);
return objLokalny;
}
//--------------------------
int main()
{
CKlasa objWzorzec(1,"Wzorzec");
Krzysztof Karbowski: Programowanie w języku C++
56
cout << "--- Obiekt na bazie wzorca---\n";
CKlasa objKopia=objWzorzec;
// albo
// CKlasa objKopia=CKlasa(objWzorzec);
// albo
//CKlasa objKopia(objWzorzec);
cout << "---Wywolaj <<NapiszString>>---\n";
NapiszString(objWzorzec);
cout << "---Wywolaj <<UtworzObiekt>>---\n";
CKlasa objZFunkcji=UtworzObiekt(1,"Z funkcji");
cout << "----- Koniec programu ---- \n";
return 0;
}
Jeśli w klasie nie ma zdefiniowanego konstruktora kopiującego to gdy
zachodzi potrzeba wywołania takiego konstruktora, to kompilator
wygeneruje
konstruktor
kopiujący
działający
według
zasady
kopiowania "składnik po składniku".
Jeśli w programie wystarczy kopiowanie "składnik po składniku" to nie ma potrzeby
programowania
konstruktora
kopiującego
–
wystarczy
konstruktor
generowany
automatycznie.
14.7.1. Sytuacja, gdy niezb
ę
dny jest konstruktor kopiuj
ą
cy
Przykład
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CKlasa
{
public:
char *m_sString;
CKlasa(const char* sString);
CKlasa(const CKlasa &objWzor);
void Zmien(const char* sString);
~CKlasa();
};
CKlasa::CKlasa(const char* sString)
{
m_sString = new char[80];
strcpy(m_sString,sString);
}
CKlasa::CKlasa(const CKlasa &objWzor)
{
m_sString = new char[80];
strcpy(m_sString,objWzor.m_sString);
}
CKlasa::~CKlasa()
{
delete [] m_sString;
}
void CKlasa::Zmien(const char* sString)
{
strcpy(m_sString,sString);
Krzysztof Karbowski: Programowanie w języku C++
57
}
//--------------------------
int main()
{
CKlasa objWzor("obiekt 1");
CKlasa objKopia=objWzor;
cout << "Wzor: " << objWzor.m_sString << endl;
cout << "Kopia: " << objKopia.m_sString << endl;
// zmiana tekstu
objKopia.Zmien("obiekt 2");
cout << "Po zmianie:\n";
cout << "Wzor: " << objWzor.m_sString << endl;
cout << "Kopia: " << objKopia.m_sString << endl;
return 0;
}
Na ekranie pojawi się następujący tekst:
Wzor: obiekt 1
Kopia: obiekt 1
Po zmianie:
Wzor: obiekt 1
Kopia: obiekt 2
Gdyby nie było konstruktora kopiującego pojawiłby się tekst:
Wzor: obiekt 1
Kopia: obiekt 1
Po zmianie:
Wzor: obiekt 2
Kopia: obiekt 2
15. Tablice obiektów
Agregat (skupisko danych) – to tablica obiektów danej klasy lub obiekt danej klasy, który:
1.
nie ma składników
private
i
protected
,
2.
nie ma konstruktorów,
3.
nie ma klas podstawowych,
4.
nie ma funkcji wirtualnych.
15.1 Tablice obiektów b
ę
d
ą
cych agregatami
15.1.1 Tworzenie tablic
Przykład:
class CKlasa
{
public:
int m_nA;
float m_B;
};
// .........
CKlasa aTablica[5];
for (i=0; i<5; i++)
{
aTablica[i].m_nA=1;
aTablica[i].m_B=1.3;
};
Krzysztof Karbowski: Programowanie w języku C++
58
15.1.2 Inicjalizacja tablic
Przykład:
CKlasa aTab[5]=
{ 1, 1.1,
// element aTab[0]
2, 2.2,
// element aTab[1]
};
// pozostałe elementy zostan
ą
wypełnione
// zerami
15.2. Tablice obiektów nie b
ę
d
ą
cych agregatami
15.2.1. Tworzenie tablic
Jeśli z obiektów danej klasy należy utworzyć tablicę to:
•
klasa ta nie może mieć żadnych konstruktorów,
•
albo klasa ta musi mieć konstruktor domniemany,
•
albo definiowana tablica musi być zainicjalizowana.
15.2.2. Inicjalizacja tablic
Podczas inicjalizacji tablic należy posłużyć się konstruktorem.
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CKlasa
{
public:
int m_nLiczba;
char m_sString[30];
CKlasa(int nLiczba, const char* sString);
~CKlasa();
};
CKlasa::CKlasa(int nLiczba, const char* sString)
{
m_nLiczba=nLiczba;
strcpy(m_sString,sString);
cout << "Konstruktor " << m_sString << endl;
}
CKlasa::~CKlasa()
{
cout << "Destruktor " << m_sString << endl;
}
//--------------------------
int main()
{
CKlasa aTab[3] = {
CKlasa(0,"Element0"),
CKlasa(1,"Element1"),
CKlasa(2,"Element2")
};// musza zostac zainicjalizowane WSZYSTKIE elementy
return 0;
}
Krzysztof Karbowski: Programowanie w języku C++
59
15.3. Tablica obiektów definiowana operatorem new
Przykład:
class CKlasa
{
public:
int m_nA;
float m_B;
};
// .........
CKlasa *pWsk;
pWsk = new CKlasa[5];
for (i=0; i<5; i++)
{
pWsk[i].m_nA=i;
pWsk[i].m_B=0.1*i;
}
delete [] pWsk;
pWsk = NULL;
Jeśli z obiektów danej klasy należy utworzyć tablicę operatorem
new
to:
•
tablicy tej nie można zainicjalizować,
•
klasa ta nie może mieć żadnych konstruktorów,
•
albo klasa ta musi mieć konstruktor domniemany,
16. Konwersje
Konwersja – przekształcenie obiektu danego typu (klasy) na inny typ.
Konwersje obiektu typu A na typ B mogą być zdefiniowane przez:
1.
konstruktor klasy B przyjmujący jako jedyny argument obiekt typu A,
2.
specjalną funkcję konwertującą (operator konwersji) zdefiniowaną w klasie A.
Po zdefiniowaniu jednej z w/w funkcji konwersje będą zachodziły automatycznie (niejawnie).
16.1. Konstruktor jako konwerter
Konstruktor przyjmujący jeden argument określa konwersję od typu
tego argumentu do typu klasy, do której sam należy.
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CLiczba
{
public:
double m_Liczba;
char m_sOpis[30];
CLiczba(double Liczba, const char* sOpis)
{ m_Liczba=Liczba; strcpy(m_sOpis,sOpis); }
void Napisz();
Krzysztof Karbowski: Programowanie w języku C++
60
};
void CLiczba::Napisz()
{
cout << "(" << m_Liczba << ":\"" << m_sOpis << "\")";
}
//--------------------------
class CZespol
{
public:
double m_Real;
double m_Imag;
CZespol(double Real, double Imag)
: m_Real(Real), m_Imag(Imag) { }
CZespol(double Real); // konwersja z double
// zamiast powyzszego konwertera mozna zastosowac
// konstruktor: CZespol(double Real, double Imag=0);
CZespol(CLiczba objLiczba); // konwersja z CLiczba
void Napisz();
};
CZespol::CZespol(double Real)
{
m_Real=Real;
m_Imag=0;
cout << "<<Konwersja z double>>\n";
}
CZespol::CZespol(CLiczba objLiczba)
{
m_Real=objLiczba.m_Liczba;
m_Imag=0;
cout << "<<Konwersja z CLiczba>>\n";
}
void CZespol::Napisz()
{
cout << "(" << m_Real << "+" << m_Imag << "i" << ")";
}
//--------------------------
CZespol dodaj(CZespol obj1, CZespol obj2)
{
CZespol objWynik(0,0);
objWynik.m_Real = obj1.m_Real + obj2.m_Real;
objWynik.m_Imag = obj1.m_Imag + obj2.m_Imag;
return objWynik;
}
//---------------------------
int main()
{
CZespol objA(1,1), objB(2,2);
CLiczba objL(3,"trzy");
double wart=5;
CZespol objWynik(0,0);
objWynik=dodaj(objA,objB);
objA.Napisz(); cout << "+"; objB.Napisz();
cout << "="; objWynik.Napisz(); cout << endl;
objWynik=dodaj(objA,wart);
Krzysztof Karbowski: Programowanie w języku C++
61
objA.Napisz(); cout << "+"<< wart;
cout << "="; objWynik.Napisz(); cout << endl;
objWynik=dodaj(objA,objL);
objA.Napisz(); cout << "+"; objL.Napisz();
cout << "="; objWynik.Napisz(); cout << endl;
return 0;
}
16.2. Funkcja konwertuj
ą
ca (operator konwersji)
Funkcja konwertująca (operator konwersji) obiektu klasy K na typ T to funkcja składowa
klasy K postaci:
K::operator T()
Cechy:
•
musi być funkcją składową klasy, z której dokonuje konwersji,
•
nie ma określonego typu zwracanego rezultatu,
•
ma pustą listę argumentów,
•
jest dziedziczona,
•
może być funkcją wirtualną.
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CZespol
{
public:
double m_Real;
double m_Imag;
CZespol(double Real=0, double Imag=0)
: m_Real(Real), m_Imag(Imag) { }
operator double();
void Napisz();
};
CZespol::operator double()
{
cout << "<<Konwersja na double>>\n";
return m_Real;
}
void CZespol::Napisz()
{
cout << "(" << m_Real << "+" << m_Imag << "i" << ")";
}
//---------------------------
int main()
{
CZespol objA(1,1);
double wynik;
double skladnik;
objA.Napisz(); cout << endl;
skladnik=objA;
wynik = 2 + skladnik;
Krzysztof Karbowski: Programowanie w języku C++
62
cout << "2 + " << skladnik << " = " << wynik << endl;
return 0;
}
16.3. Sytuacje, w których zachodzi konwersja
Niejawne konwersje zachodzą, gdy:
1.
podczas wywołania funkcji zachodzi niezgodność argumentów aktualnych z
argumentami formalnymi i jest jednoznaczna możliwość usunięcia niedopasowania
poprzez konwersję,
2.
gdy funkcja zwraca rezultat innego typu niż zadeklarowany i można jednoznacznie
przeprowadzić konwersję,
3.
w obecności operatorów (np.:
a+b
,
c-d
),
4.
podczas realizacji wyrażeń inicjalizujących,
5.
podczas realizacji wyrażeń:
if(obj)
...,
switch(obj)
...,
while(obj)
...
, for(...; obj ;...)
kompilator będzie starał się wykonać konwersję
(int)obj
.
Jawne wywołanie konwersji:
CKlasa obj;
obj = (CKlasa)obj2;
// forma rzutowania
obj = CKlasa(obj2);
// forma wywołania funkcji
17. Przeładowanie operatorów
Funkcja operatorowa:
typ_zwracany
operator
nazwa_operatora ( argumenty )
{
// ciało funkcji
}
Funkcja operatorowa może być funkcją globalną lub niestatyczną funkcją składową klasy, dla
której pracuje.
Operatory predefiniowane (kompilator sam wygeneruje funkcję operatorową klasy):
•
=
przypisanie
•
&
(jednoargumentowy)
pobranie adresu obiektu danej klasy,
•
new, delete
utworzenie i likwidacja obiektu danej klasy
17.1. Operatory dwuargumentowe
17.1.1. Operator dwuargumentowy jako funkcja globalna
Przykład
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CZespol
{
public:
Krzysztof Karbowski: Programowanie w języku C++
63
double m_Real;
double m_Imag;
CZespol(double Real=0, double Imag=0)
: m_Real(Real), m_Imag(Imag) { }
void Napisz();
};
void CZespol::Napisz()
{
cout << "(" << m_Real << "+" << m_Imag << "i" << ")";
}
//---------------------------
CZespol operator+(CZespol arg1, CZespol arg2)
{
CZespol wynik;
wynik.m_Real = arg1.m_Real + arg2.m_Real ;
wynik.m_Imag = arg1.m_Imag + arg2.m_Imag ;
return wynik;
}
//---------------------------
int main()
{
CZespol objA(1,1);
CZespol objB(2,2);
CZespol objW;
objW=objA+objB;
// inaczej:
// objW = operator+(objA,objB);
objW.Napisz();
cout << endl ;
return 0;
}
17.1.2. Operator dwuargumentowy jako funkcja składowa klasy
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CZespol
{
public:
double m_Real;
double m_Imag;
CZespol(double Real=0, double Imag=0)
: m_Real(Real), m_Imag(Imag) { }
CZespol operator+(CZespol arg2);
void Napisz();
};
void CZespol::Napisz()
{
cout << "(" << m_Real << "+" << m_Imag << "i" << ")";
}
//---------------------------
CZespol CZespol::operator+(CZespol arg2)
Krzysztof Karbowski: Programowanie w języku C++
64
{
CZespol wynik;
wynik.m_Real = m_Real + arg2.m_Real ;
// inaczej:
// wynik.m_Real = this->m_Real + arg2.m_Real ;
wynik.m_Imag = m_Imag + arg2.m_Imag ;
return wynik;
}
//---------------------------
int main()
{
CZespol objA(1,1);
CZespol objB(2,2);
CZespol objW;
objW=objA+objB;
// inaczej:
// objW=objA.operator+(objB);
objW.Napisz();
cout << endl ;
return 0;
}
Funkcja operatorowa, która jest funkcją składową klasy – wymaga, aby
obiekt stojący po lewej stronie znaku operatora był obiektem jej klasy.
Operator, który jest zwykłą funkcją globalną – nie ma tego
ograniczenia.
Funckję operatorową, która pracuje na argumentach klas A i B można zrealizować jako:
1.
funkcję składową klasy A,
2.
funkcję globalną,
3.
funkcję składową klasy B.
17.2. Operatory jednoargumentowe przedrostkowe (prefiksowe)
17.2.1. Przedrostkowy operator jednoargumentowy jako funkcja globalna
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CZespol
{
friend CZespol operator-(CZespol arg);
private:
double m_Real;
double m_Imag;
public:
CZespol(double Real=0, double Imag=0)
: m_Real(Real), m_Imag(Imag) { }
void Napisz();
};
void CZespol::Napisz()
{
Krzysztof Karbowski: Programowanie w języku C++
65
cout << "(" << m_Real << "+" << m_Imag << "i" << ")";
}
//---------------------------
CZespol operator-(CZespol arg)
{
CZespol wynik;
wynik.m_Real = - arg.m_Real ;
wynik.m_Imag = - arg.m_Imag ;
return wynik;
}
//---------------------------
int main()
{
CZespol objA(1,1);
CZespol objW;
objW= - objA ;
// inaczej:
// objW=operator-(objA);
objW.Napisz();
cout << endl ;
return 0;
}
17.2.2. Przedrostkowy operator jednoargumentowy jako funkcja
składowa klasy
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class CZespol
{
public:
double m_Real;
double m_Imag;
CZespol(double Real=0, double Imag=0)
: m_Real(Real), m_Imag(Imag) { }
CZespol operator-();
void Napisz();
};
void CZespol::Napisz()
{
cout << "(" << m_Real << "+" << m_Imag << "i" << ")";
}
CZespol CZespol::operator-()
{
CZespol wynik;
wynik.m_Real = - m_Real ;
// inaczej:
// wynik.m_Real = - (this->m_Real) ;
wynik.m_Imag = - m_Imag ;
return wynik;
}
//---------------------------
Krzysztof Karbowski: Programowanie w języku C++
66
int main()
{
CZespol objA(1,1);
CZespol objW;
objW= - objA ;
// inaczej:
// objW=objA.operator-();
objW.Napisz();
cout << endl ;
return 0;
}
17.3. Operatory, które musz
ą
by
ć
niestatycznymi funkcjami
składowymi
Operatory, które muszą być niestatycznymi funkcjami składowymi:
=
[]
()
->
17.3.1. Operator przypisania =
Operator przypisania to funkcja postaci:
klasa & klasa::operator=(klasa & obiekt)
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//--------------------------
class COsoba
{
private:
char* m_sImie;
char* m_sNazw;
public:
// konstruktor
COsoba(const char* sImie, const char* sNazw);
COsoba(const COsoba& objWzor);
// konstr.kopiujacy
~ COsoba();
// destruktor
// operator przypisania
COsoba& operator=(const COsoba& objWzor);
void Napisz();
};
//===========
COsoba::COsoba(const char* sImie,const char* sNazw)
// konstruktor
{
m_sImie = new char[strlen(sImie)+1];
strcpy(m_sImie,sImie);
m_sNazw = new char[strlen(sNazw)+1];
strcpy(m_sNazw,sNazw);
cout << "Konstruktor "; Napisz();
}
//============
COsoba::COsoba(const COsoba& objWzor)
// konstr.kopiujacy
{
Krzysztof Karbowski: Programowanie w języku C++
67
m_sImie = new char[strlen(objWzor.m_sImie)+1];
strcpy(m_sImie,objWzor.m_sImie);
m_sNazw = new char[strlen(objWzor.m_sNazw)+1];
strcpy(m_sNazw,objWzor.m_sNazw);
cout << "Konstruktor kopiujacy "; Napisz();
}
//============
COsoba::~COsoba()
//destruktor
{
cout << "destruktor "; Napisz();
delete [] m_sImie;
delete [] m_sNazw;
}
//=============
COsoba& COsoba::operator=(const COsoba& objWzor)
//operator przypisania
{
// destruktor
delete m_sImie;
delete m_sNazw;
// konstruktor kopiujacy
m_sImie = new char[strlen(objWzor.m_sImie)+1];
strcpy(m_sImie,objWzor.m_sImie);
m_sNazw = new char[strlen(objWzor.m_sNazw)+1];
strcpy(m_sNazw,objWzor.m_sNazw);
cout << "Operator przypisania "; Napisz();
return *this;
}
//=============
void COsoba::Napisz()
{
cout << m_sImie << " " << m_sNazw << endl;
}
//-------------------------------------
int main()
{
COsoba objJan("Jan","Kowalski");
COsoba objJan2=objJan;
COsoba objPusty("pusty","pusty");
objPusty=objJan;
//inaczej: objPusty.operator=(objJan);
objJan.Napisz();
objJan2.Napisz();
objPusty.Napisz();
return 0;
}
Sytuacje, w których operator przypisania nie jest generowany automatycznie:
1.
jeżeli klasa ma składnik const – składnik taki można tylko inicjalizować,
2.
jeżeli klasa ma składnik będący referencją – referencję można tylko inicjalizować.
Krzysztof Karbowski: Programowanie w języku C++
68
17.4. Operator wysyłania/pobierania danych do/z strumienia (<<, >>)
Przykład
:
#include <iostream>
using namespace std;
//--------------------------
class CZespol
{
friend ostream& operator<<(ostream& ekran,
CZespol& objL);
friend istream& operator>>(istream& klawiatura,
CZespol& objL);
private:
double m_Real;
double m_Imag;
public:
CZespol(double Real=0, double Imag=0)
: m_Real(Real), m_Imag(Imag) { }
};
//-----------------------------
ostream& operator<<(ostream& ekran, CZespol& objL)
{
ekran << "(" << objL.m_Real << "+"
<< objL.m_Imag << "i" << ")";
return ekran;
}
//-------------------------------
istream& operator>>(istream& klawiatura, CZespol& objL)
{
cout << "\tCzesc rzeczywista: ";
klawiatura >> objL.m_Real;
cout << "\tCzesc urojona: ";
klawiatura >> objL.m_Imag;
return klawiatura;
}
//---------------------------
int main()
{
CZespol objA;
cout << "Podaj liczbe zespolona:\n";
cin >> objA;
cout << "Liczba zespolona: " << objA << endl ;
// poniewaz operator << jest lewostronnie ł
ą
czny
// mozna inaczej zapisac:
// ( ( (cout << "Liczba zespolona: ") << objA ) << endl );
// lub inaczej:
// (operator<<((cout << "Liczba zespolona: "),objA)) <<
endl;
return 0;
}
18. Dziedziczenie
Dziedziczenie polega na definiowaniu klasy, która jest odmianą innej klasy:
class CKlasaPodstawowa
Krzysztof Karbowski: Programowanie w języku C++
69
{
//..............
};
class CKlasaPochodna : CKlasaPodstawowa
{
//...............
};
Przykład
:
#include <iostream>
#include <string>
using namespace std;
//------------------------------------------------------------
class COsoba
{
public:
char m_sImie[80];
char m_sNazwisko[80];
int m_nWaga;
int m_nWzrost;
COsoba(char* sImie, char* sNazwisko, int nWaga,
int nWzrost);
~COsoba();
void WypiszDane();
};
//-----------------------------------------------------------
COsoba::COsoba(char* sImie, char* sNazwisko, int nWaga,
int nWzrost)
: m_nWaga(nWaga), m_nWzrost(nWzrost)
{
strcpy(m_sImie,sImie);
strcpy(m_sNazwisko,sNazwisko);
cout << "Konstruktor obiektu :" << m_sNazwisko
<< " klasy COsoba\n";
}
//-----------------------------------------------------------
COsoba::~COsoba()
{
cout << "Destruktor obiektu :" << m_sNazwisko
<< " klasy COsoba\n";
}
//-----------------------------------------------------------
void COsoba::WypiszDane()
{
cout << m_sImie << " " << m_sNazwisko << "; " << m_nWaga <<
" kg; " << m_nWzrost << " cm\n";
}
//------------------------------------------------------------
//------------------------------------------------------------
class COsobaZAdresem : public COsoba
{
public:
char m_sUlica[80];
char m_sMiasto[80];
COsobaZAdresem(char* sImie, char* sNazwisko, int nWaga,
Lista pochodzenia
Krzysztof Karbowski: Programowanie w języku C++
70
int nWzrost, char* sUlica, char* sMiasto);
~COsobaZAdresem();
void WypiszDane();
};
//------------------------------------------------------------
COsobaZAdresem::COsobaZAdresem(char* sImie, char* sNazwisko,
int nWaga, int nWzrost,
char* sUlica, char* sMiasto)
: COsoba(sImie,sNazwisko,nWaga,nWzrost)
{
strcpy(m_sUlica,sUlica);
strcpy(m_sMiasto,sMiasto);
cout << "Konstruktor obiektu :" << m_sNazwisko
<< " klasy COsobaZAdresem\n";
}
//------------------------------------------------------------
COsobaZAdresem::~COsobaZAdresem()
{
cout << "Destruktor obiektu :" << m_sNazwisko
<< " klasy COsobaZAdresem\n";
}
//------------------------------------------------------------
void COsobaZAdresem::WypiszDane()
{
cout << m_sImie << " " << m_sNazwisko << "; " << m_nWaga <<
" kg; " << m_nWzrost << " cm; ul." << m_sUlica <<
"; " << m_sMiasto << endl;
}
//-------------------------------------------------------------
//-------------------------------------------------------------
int main()
{
COsoba objKowalski("Jan","Kowalski",65,178);
objKowalski.WypiszDane();
COsobaZAdresem
objNowak("Adam","Nowak",81,180,"Dluga","Krakow");
objNowak.WypiszDane();
return 0;
}
Dziedziczenie powoduje zagnieżdżenie zakresów obiektów:
class CKlasaPodstawowa
{
public:
int x;
int y;
};
class CKlasaPochodna : public CKlasaPodstawowa
{
public:
int x;
int y;
Krzysztof Karbowski: Programowanie w języku C++
71
};
W powyższym przykładzie składniki
x
i
y
klasy
CKlasaPochodna
zasłonią składniki
x
i
y
klasy
CKlasaPodstawowa
.
Zakresy ważności w klasie
COsobaZAdresem
:
COsobaZAdresem
{
char m_sImie[80];
char m_sNazwisko[80];
int m_nWaga;
int m_nWzrost;
void WypiszDane();
{
char m_sUlica[80];
char m_sMiasto[80];
void WypiszDane();
}
}
Funkcja
WypiszDane
w klasie
COsobaZAdresem
zasłoni funkcję
WypiszDane
z klasy
COsoba
. Funkcje te nie są funkcjami przeładowanymi, gdyż mają różne zakresy ważności.
W klasie pochodej można zdefiniować:
•
dodatkowe dane składowe,
•
dodatkowe funkcje składowe,
•
składniki, które już istnieją w klasie podstawowej.
W zdefiniowanym obiekcie klasy pochodnej będzie zawarty fragment
będący jakby obiektem klasy podstawowej, ale dziedziczenie nie polega
na tworzeniu obiektów pochodnych tylko klas pochodnych.
Dostęp do zasłoniętych składników:
COsobaZAdresem Kowalski;
//wywołanie funkcji z klasy pochodnej COsobaZAdresem
Kowalski.WypiszDane();
//wywołanie funkcji z klasy podstawowej COsoba
Kowalski.COsoba::WypiszDane();
18.1. Dost
ę
p do składników klasy podstawowej
18.1.1. Prywatne składniki klasy podstawowej
Prywatne składniki klasy podstawowej są dziedziczone, ale w zakresie klasy pochodnej oraz
z poza klasy nie ma do nich dostępu.
18.1.2. Nieprywatne składniki klasy podstawowej
Składniki
public
– są dziedziczone i dostępne w zakresie klasy pochodnej oraz z poza klasy.
Odziedziczone
po COsoba
Krzysztof Karbowski: Programowanie w języku C++
72
Składniki
protected
– są dziedziczone i dostępne w zakresie klasy pochodnej ale nie są
dostępne z poza klasy.
18.1.3. Dost
ę
p do składników klasy podstawowej okre
ś
lony przez klas
ę
pochodn
ą
Rodzaje dziedziczenia:
class CklasaPodstawowa : public CKlasaPochodna
class CklasaPodstawowa : protected CKlasaPochodna
class CklasaPodstawowa : private CKlasaPochodna
Jeśli na liście dziedziczenia nie określi się jego typu to przyjęte zostanie dziedziczenie typu
private
.
Dziedziczenie typu
public
:
klasa
podstawowa
klasa
pochodna
sk
ła
d
n
ik
i
private
private
sk
ła
d
n
ik
i
protected
protected
public
public
Dziedziczenie typu
protected
:
klasa
podstawowa
klasa
pochodna
sk
ła
d
n
ik
i
private
private
sk
ła
d
n
ik
i
protected
protected
public
protected
Dziedziczenie typu
private
:
klasa
podstawowa
klasa
pochodna
sk
ła
d
n
ik
i
private
private
sk
ła
d
n
ik
i
protected
private
public
private
18.2. Wyj
ą
tki w dziedziczeniu
Nie dziedziczy się:
•
konstruktorów
•
operatora przypisania
•
destruktora
Krzysztof Karbowski: Programowanie w języku C++
73
18.3. Kolejno
ść
wywoływania konstruktorów
Przykład
#include <iostream>
using namespace std;
class CZarowka
{
protected:
int m_nMoc;
public:
CZarowka(int nMoc);
~CZarowka();
void Zaswiec();
};
//-----------------------------------------------
CZarowka::CZarowka(int nMoc)
: m_nMoc(nMoc)
{
cout << "Konstruktor CZarowka; moc="
<< m_nMoc << endl;
}
//-----------------------------------------------
CZarowka::~CZarowka()
{
cout << "Destruktor CZarowka; moc="
<< m_nMoc << endl;
}
//-----------------------------------------------
void CZarowka::Zaswiec()
{
cout << "Swieci zarowka o mocy "
<< m_nMoc << endl;
}
//-----------------------------------------------
//-----------------------------------------------
class CStarter
{
public:
int m_nTyp;
CStarter(int nTyp);
~CStarter();
};
//-----------------------------------------------
CStarter::CStarter(int nTyp)
: m_nTyp(nTyp)
{
cout << "Konstruktor CStarter; Typ "
<< m_nTyp << endl;
}
//-----------------------------------------------
CStarter::~CStarter()
{
cout << "Destruktor CStarter; Typ "
<< m_nTyp << endl;
}
Krzysztof Karbowski: Programowanie w języku C++
74
//-----------------------------------------------
//-----------------------------------------------
class CSwietlowka : public CZarowka
{
protected:
int m_nMocEkw; //ekwiwalent mocy
CStarter m_objStarter;
public:
CSwietlowka(int nMoc, int nMocEkw,
int nTypStartera);
~CSwietlowka();
void Zaswiec();
};
//-----------------------------------------------
CSwietlowka::CSwietlowka(int nMoc, int nMocEkw,
int nTypStartera)
: CZarowka(nMoc),m_objStarter(nTypStartera),
m_nMocEkw(nMocEkw)
{
cout << "Konstruktor CSwietlowka; moc="
<< m_nMoc << endl;
}
//-----------------------------------------------
CSwietlowka::~CSwietlowka()
{
cout << "Destruktor CSwietlowka; moc="
<< m_nMoc << endl;
}
//-----------------------------------------------
void CSwietlowka::Zaswiec()
{
cout << "Swieci swietlowka o mocy "
<< m_nMoc << endl;
}
//-----------------------------------------------
//-----------------------------------------------
//-----------------------------------------------
int main()
{
CSwietlowka objLampa(100,15,1);
objLampa.Zaswiec();
return 0;
}
Kolejność wywoływania konstruktorów:
1.
konstruktory klas podstawowych (starszych w hierarchii),
2.
konstruktory obiektów będących składnikami klasy pochodnej,
3.
konstruktor obiektu klasy pochodnej.
18.4. Przypisanie i inicjalizacja obiektów w warunkach dziedziczenia
Klasa pochodna nie dziedziczy operatora przypisania oraz konstruktorów (w szczególności
konstruktora kopiującego).
Krzysztof Karbowski: Programowanie w języku C++
75
Gdy klasa pochodna nie definiuje swojego operatora przypisania to kompilator wygeneruje
operator przypisania pracujący metodą „składnik po składniku”. Oznacza to, że dla
typów wbudowanych zrobi ich kopie a dla typów zdefiniowanych przez użytkownika
(klas) wykorzysta operatory przypisania tych klas (o ile istnieją). Także dla klasy
podstawowej wykorzysta jej operator przypisania (jeśli go posiada) w części będącej
dziedzictwem.
Gdy klasa pochodna nie definiuje swojego konstruktora kopiującego to kompilator
wygeneruje taki konstruktor pracujący metodą „składnik po składniki) (analogicznie
jak dla operatora przypisania).
18.4.1. Definiowanie konstruktora kopiuj
ą
cego i operatora przypisania
Przykład
#include <iostream>
using namespace std;
//-----------------------------------------------
class CPrzodek
{
public:
int m_A;
CPrzodek(int A);
CPrzodek(const CPrzodek& Wzor);
~CPrzodek();
CPrzodek& operator=(const CPrzodek& Wzor);
};
//-----------------------------------------------
CPrzodek::CPrzodek(int A)
: m_A(A)
{
cout << "Konstruktor CPrzodek: "
<< m_A << endl;
}
//-----------------------------------------------
CPrzodek::CPrzodek(const CPrzodek& Wzor)
{
m_A=Wzor.m_A;
cout << "Konstruktor kopiujacy CPrzodek: "
<< m_A << endl;
}
//-----------------------------------------------
CPrzodek::~CPrzodek()
{
cout << "Destruktor CPrzodek: " << m_A << endl;
}
//-----------------------------------------------
CPrzodek& CPrzodek::operator=(const CPrzodek& Wzor)
{
m_A=Wzor.m_A;
cout << "Operator przypisania CPrzodek: "
<< m_A << endl;
return *this;
}
Krzysztof Karbowski: Programowanie w języku C++
76
//-----------------------------------------------
//-----------------------------------------------
class CPotomek : public CPrzodek
{
public:
int m_B;
CPotomek(int A, int B);
CPotomek(const CPotomek& Wzor);
~CPotomek();
CPotomek& operator=(const CPotomek& Wzor);
};
//-----------------------------------------------
CPotomek::CPotomek(int A, int B)
: CPrzodek(A), m_B(B)
{
cout << "Konstruktor CPotomek: "
<< m_B << endl;
}
//-----------------------------------------------
CPotomek::CPotomek(const CPotomek& Wzor)
: CPrzodek(Wzor)
{
m_B=Wzor.m_B;
cout << "Konstruktor kopiujacy CPotomek: "
<< m_B << endl;
}
//-----------------------------------------------
CPotomek::~CPotomek()
{
cout << "Destruktor CPotomek: "
<< m_B << endl;
}
//-----------------------------------------------
CPotomek& CPotomek::operator=(const CPotomek& Wzor)
{
// wywolanie operatora przypisania klasy
// podstawowej;
// jako parametr aktualny operatora mozna podac
// "Wzor" gdyz referencja do obiektu klasy
// pochodnej moze wystapic zamiast referencji
// do obiektu klasy podstawowej
(*this).CPrzodek::operator=(Wzor);
m_B=Wzor.m_B;
cout << "Operator przypisania CPotomek: "
<< m_B << endl;
return *this;
}
//-----------------------------------------------
//-----------------------------------------------
//-----------------------------------------------
int main()
{
CPotomek obj1(1,1);
CPotomek obj2=obj1;
CPotomek obj3(3,3);
Krzysztof Karbowski: Programowanie w języku C++
77
obj3=obj1;
return 0;
}
18.5. Konwersje standardowe przy dziedziczeniu
Wskaźnik do obiektu klasy pochodnej może być niejawnie przekształcony na wskaźnik
dostępnej jednoznacznie klasy podstawowej.
Referencja obiektu klasy pochodnej może być niejawnie przekształcona na referencję
jednoznacznie dostępnej klasy podstawowej.
Przykład
#include <iostream>
using namespace std;
//-----------------------------------------------
class CSamochod
{
public:
int m_Paliwo;
};
//-----------------------------------------------
class CFiat : public CSamochod
{
public:
int m_LiczbaMiejsc;
};
//-----------------------------------------------
class CCosDoJezdzenia : private CSamochod
{
public:
int m_LiczbaMiejsc;
};
//-----------------------------------------------
//-----------------------------------------------
void Zatankuj(CSamochod& Klient)
{
Klient.m_Paliwo =100;
}
//-----------------------------------------------
//-----------------------------------------------
int main()
{
CFiat objFIAT;
CCosDoJezdzenia objSAM;
// niejawna konwersja refernecji CFiat
// na CSamochod
Zatankuj(objFIAT);
// Zatankuj(objSAM); BLAD gdyz CCosDoJezdzenia
// dziedziczy CSamochod jako
// private
Krzysztof Karbowski: Programowanie w języku C++
78
// jawna konwersja refernecji CCosDoJezdzenia
// na CSamochod
Zatankuj((CSamochod&)objSAM);
return 0;
}
Sytuacje, w których zachodzą konwersje standardowe:
•
podczas przysłania argumentów do funkcji (jako referencji lub wskaźnika).
Funkcję przyjmującą argument będący referencją obiektu klasy podstawowej można
wywołać z argumentem będącym referencją obiektu klasy pochodnej. Analogicznie
jest ze wskaźnikami.
•
przy zwracaniu przez funkcję rezultatu będącego referencją lub wskaźnikiem.
Funkcja zdefiniowana jako zwracająca referencję (lub wskaźnik) obiektu klasy
podstawowej może zwracać referencję (lub wskaźnik) obiektu klasy pochodnej.
•
przy przeładowanych operatorach.
Jeśli funkcja operatorowa spodziewa się obiektu (lub jego referencji) klasy
podstawowej, to można ją wywołać z obiektem (lub referencją)klasy pochodnej.
•
w wyrażeniach inicjalizujących.
Gdy ma nastąpić inicjalizacja obiektu klasy podstawowej to jako obiekt wzorcowy
można wykorzystać obiekt klasy potomnej (gdyż konstruktor kopiujący przyjmuje
referencję obiektu).
Konwersje standardowe zachodzą tylko podczas pracy z referencjami
lub wskaźnikami obiektów, a nie z samymi obiektami.
Obiekty klas pochodnych mogą być traktowane jako obiekty swych klas
podstawowych tylko podczas pracy na ich wskaźnikach lub
referencjach.
19. Funkcje wirtualne
Definiowanie funkcji wirtualnej:
class nazwa_klasy
{
// ciało klasy
virtual typ nazwa_funkcji(/* parametry */)
{
// ciało funkcji
}
};
albo
class nazwa_klasy
{
// ciało klasy
virtual typ nazwa_funkcji(/* parametry */);
};
typ nazwa_klasy::nazwa_funkcji(/* parametry */)
{
// ciało funkcji
}
Krzysztof Karbowski: Programowanie w języku C++
79
Przykład
#include <iostream>
using namespace std;
//-----------------------------------------------
class CInstrument
{
public:
virtual void Zagraj()
{ cout << "Dzwiek\n"; }
};
//-----------------------------------------------
class CSkrzypce : public CInstrument
{
public:
virtual void Zagraj()
{ cout << "Dzwiek skrzypiec\n"; }
};
//-----------------------------------------------
class CFortepian : public CInstrument
{
public:
virtual void Zagraj()
{ cout << "Dzwiek fortepianu\n"; }
};
//-----------------------------------------------
void Muzyk(CInstrument& objInstr)
{
objInstr.Zagraj();
}
//-----------------------------------------------
//-----------------------------------------------
int main()
{
CInstrument objInstrument;
CSkrzypce objSkrzypce;
CFortepian objFortepian;
CInstrument* pInstrument;
cout << "Wywolanie poprzez obiekt\n";
objInstrument.Zagraj();
objSkrzypce.Zagraj();
objFortepian.Zagraj();
cout << "\nWywolanie poprzez wskaznik\n";
pInstrument = &objInstrument;
pInstrument->Zagraj();
pInstrument = &objSkrzypce;
pInstrument->Zagraj();
pInstrument = &objFortepian;
pInstrument->Zagraj();
cout << "\nWywolanie poprzez referencje\n";
Muzyk(objInstrument);
Muzyk(objSkrzypce);
Muzyk(objFortepian);
return 0;
Krzysztof Karbowski: Programowanie w języku C++
80
}
Rodzaje powiązań wywołań funkcji z adresami określającymi, gdzie są te funkcje:
wczesne wiązanie – wiązanie na etapie kompilacji,
późne wiązanie – wiązanie na etapie wykonania programu.
Cechy funkcji wirtualnych:
•
różny zakres ważności,
•
te same nazwy,
•
te same parametry,
•
ten sam typ.
Sytuacje, w których dla funkcji wirtualnych zachodzi wczesne wiązanie:
•
wywołanie na rzecz obiektu:
obiekt.funkcja();
•
jawne użycie kwalifikatora zakresu:
wska
ź
nik->klasa::funkcja();
•
wywołanie z konstruktora/destruktora klasy podstawowej.
Klasa abstrakcyjna – klasa, która nie reprezentuje żadnego obiektu.
Funkcja czysto wirtualna – funkcja zadeklarowana w klasie abstrakcyjnej jako:
virtual typ nazwa(/* parametry *)=0;
Jeśli klasa deklaruje jedną ze swoich funkcji jako
virtual
wówczas jej destruktor
powinien zostać zadeklarowany jako
virtual
– (jest to wyjątek od reguły, że funkcje
wirtualne muszą mieć tę samą nazwę).
Przykład
A:
Plik „Pracownik.h”
#ifndef _PRACOWNIK_H_
#define _PRACOWNIK_H_
//-----------------------------------------
class CPracownik
{
public:
char m_sImie[80];
char m_sNazwisko[80];
CPracownik(char* sImie, char* sNazwisko);
virtual void Napisz()=0;
};
//-----------------------------------------
void Wizytowka(CPracownik* Dane);
//-----------------------------------------
#endif // _PRACOWNIK_H_
Krzysztof Karbowski: Programowanie w języku C++
81
Plik “Pracownik.cpp”
#include <iostream>
#include <string>
using namespace std;
#include "Pracownik.h"
//-----------------------------------------
CPracownik::CPracownik(char* sImie, char* sNazwisko)
{
strcpy(m_sImie,sImie);
strcpy(m_sNazwisko,sNazwisko);
}
//------------------------------------------
void Wizytowka(CPracownik* Dane)
{
cout << "******************************\n";
cout << "Politechnika Krakowska\n";
cout << "Katedra Inzynierii Procesow Produkcyjnych\n";
Dane->Napisz();
cout << Dane->m_sImie << " " << Dane->m_sNazwisko << endl;
cout << "******************************\n";
}
Plik „main.cpp”
#include <iostream>
using namespace std;
#include "Pracownik.h"
//-------------------------------------------
class CAsystent : public CPracownik
{
public:
CAsystent(char* sImie, char* sNazwisko)
: CPracownik(sImie,sNazwisko) {}
virtual void Napisz();
};
//---------
void CAsystent::Napisz()
{
cout << "Mgr inz.\n";
}
//-------------------------------------------
class CAdiunkt : public CPracownik
{
public:
CAdiunkt(char* sImie, char* sNazwisko)
: CPracownik(sImie,sNazwisko) {}
virtual void Napisz();
};
//---------
void CAdiunkt::Napisz()
{
cout << "Dr inz.\n";
}
//-------------------------------------------
class CProfesor : public CPracownik
Krzysztof Karbowski: Programowanie w języku C++
82
{
public:
CProfesor(char* sImie, char* sNazwisko)
: CPracownik(sImie,sNazwisko) {}
virtual void Napisz();
};
//---------
void CProfesor::Napisz()
{
cout << "Prof. dr hab. inz.\n";
}
//-------------------------------------------
int main()
{
CAsystent Asystent("Jan","Nowak");
CAdiunkt Adiunkt("Adam","Kowalski");
CProfesor Profesor("Jozef","Adamski");
cout << endl;
Wizytowka(&Asystent);
cout << endl;
Wizytowka(&Adiunkt);
cout << endl;
Wizytowka(&Profesor);
return 0;
}