• Nie Znaleziono Wyników

Wstęp do programowania obiektowego

N/A
N/A
Protected

Academic year: 2021

Share "Wstęp do programowania obiektowego"

Copied!
48
0
0

Pełen tekst

(1)

Wstęp do programowania obiektowego

Przekazywanie parametrów do funkcji w C++

Metody i funkcje operatorowe

Strumienie: standardowe, plikowe, napisowe

1

(2)

PRZEKAZYWANIE

PARAMETRÓW DO

FUNKCJI W C++

(3)

W C++ dostępne są dwa tryby

przekazywania parametrów do funkcji:

Przez wartość

Przez referencję

3

(4)

PRZEKAZYWANIE PARAMETRÓW DO

FUNKCJI W C++ PRZEZ

WARTOŚĆ

(5)

Przykład: przekazywanie przez wartość

#include <stdio.h>

int kwadrat (int x){

return x*x;

}

int main(){

int i = 3;

printf(„Kwadrat %d to %d”, i, kwadrat(i));

return 0;

}

5

(6)

Parametry formalne i aktualne

#include <stdio.h>

int kwadrat (int x) {

return x*x; }

int main() { int i = 3;

printf(„Kwadrat %d to %d”, i, kwadrat(i));

return 0;

Definicja funkcji Parametr formalny

Wywołanie funkcji

(7)

Przekazywanie parametrów do funkcji przez wartość

#include <stdio.h>

int kwadrat (int x) {

return x*x; } int main() { int i = 3;

printf(„Kwadrat %d to %d”, i, kwadrat(i));

return 0;

}

7 Definicja funkcji

Parametr formalny

Parametr aktualny Wywołanie funkcji

1. Tworzona jest nowa zmienna lokalna (parametr formalny) o nazwie jak w nagłówku funkcji (tutaj x).

2. Wyliczana jest wartość parametru aktualnego; może to być zmienna, stała lub wyrażenie (tutaj jest to zmienna o wartości 3).

3. Wartość parametru aktualnego jest kopiowana do parametru formalnego.

4. Parametr formalny jest używany jak zwykła zmienna lokalna.

5. Po zakończeniu funkcji nie ma kopiowania wartości do parametru aktualnego (nawet jeżeli jest on „zmienną”). Operacje na parametrach formalnych nie mają wpływu na parametry aktualne.

(8)

Parametr aktualny jako wyrażenie

#include <stdio.h>

int kwadrat (int x) {

return x*x; } int main() {

int i = 3;

int j = 5;

printf(„Kwadrat sumy %d i %d to %d”, i, j, kwadrat(i+j));

return 0;

(9)

Kilka parametrów

#include <stdio.h>

float funkcja (int a, float b) {

return 5*a + b; } int main()

{

int i = 2;

float j = 3.14;

printf(„Wartość funkcji to %f”, kwadrat(i,j));

return 0;

}

9

(10)

Przekazywanie wskaźnika jako parametr

Aby

nie kopiować niepotrzebnie dużych ilości danych (np.

przekazując przez wartość strukturę lub obiekt o dużej objętości) lub też

umożliwić funkcji modyfikację zmiennej

przekazuje się przez wartość wskaźnik.

Przekazanie (skopiowanie) adresu (jednej liczby całkowitej) to prosta operacja i umożliwia pełny dostęp do wskazywanego obiektu.

Sam wskaźnik nie jest modyfikowany (kopiowany jest adres), ale może być modyfikowany

(11)

Przekazywanie wskaźnika: przykład

#include <stdio.h>

void zwiększOJeden(int* a) //parametr formalny to wskaźnik {

(*a)++; //nawias dla pewności

}

int main() {

int i = 2;

zwiększOJeden(&i); //parametr aktualny to adres zmiennej i printf(”Wartość i to %d”, i); // wypisze „3”

return 0;

}

Do parametru formalnego a (wskaźnika) jest zapisywana wartość adresu zmiennej i, więc operacje na obiekcie wskazywanym przez a odbywają się tak naprawdę na zmiennej i.

C/C++ nie sprawdza na jakiej pamięci operujemy (czy jest ona np.

zarezerwowana na inne zmienne), stąd możliwe są różne błędy związane z dostępem przez wskaźnik do „cudzej” pamięci. 11

(12)

PRZEKAZYWANIE PARAMETRÓW DO

FUNKCJI W C++ PRZEZ

REFERENCJĘ

(13)

Cechy referencji w C++

Referencja – w C++ jest to wskaźnik o

ograniczonych możliwościach (nie można używać

„arytmetyki wskaźników”, uproszczony dostęp do wskazywanego elementu – bez „*”)

Referencję deklarujemy i inicjujemy następująco:

<typ danych> & <nazwa> = <istniejąca_zmienna<;

np.

int &ref_r = r;

Referencja musi być zainicjalizowana w momencie utworzenia.

Referencji po przypisaniu nie można przestawić na inną zmienną.

13

(14)

Przykład użycia referencji

int i=0;

int &ref_i=i;

cout << i; // wypisuje 0 ref_i = 1;

cout << i; // wypisuje 1 cout << ref_i; // wypisuje 1

(15)

To samo za pomocą wskaźnika

int i=0;

int *wsk_i; // deklaracja wsk.

wsk_i = &i; // inicjacja wsk.

cout << i; // wypisuje 0

*wsk_i = 1;

cout << i; // wypisuje 1 cout << *wsk_i; // wypisuje 1

15

(16)

W nagłówku funkcji używamy &

(ampersandu) pomiędzy nazwą typu oraz nazwą parametru formalnego

Parametr aktualny musi być zmienną (nie może być wyrażeniem ani stałą)

Parametr formalny jest aliasem (alternatywną nazwą) zmiennej będącej parametrem

aktualnym.

Jeżeli chcemy uniemożliwić zmiany wartości zmiennej to używamy słowa kluczowego

const przed parametrem formalnym.

(17)

Przekazywanie parametrów przez referencję

#include <stdio.h>

void zwiększOJeden(int& a) //parametr formalny to referencja {

a++;

}

int main() {

int i = 2;

zwiększOJeden(i); //parametr aktualny to zmienna i

printf(”Wartość i to %d”, i); // wypisze „3”

return 0;

}

Parametr formalny a to referencja do zmiennej i, więc operacje na obiekcie wskazywanym przez a odbywają się tak naprawdę na zmiennej i.

Funkcję wywołujemy jak dla zwykłej zmiennej

Wewnątrz funkcji operujemy referencją jak zwykłą zmienną (bez gwiazdki) 17

(18)

METODY

OPERATOROWE

(19)

Operatory – znaki symbolizujące

wykonanie powszechnie znanych (lub domyślnych) operacji

Operandy – argumenty na których te operacje się wykonuje

19

(20)

Operatory w C++ (1/3)

Artymetyczne:

a=b a+b a-b a*b a/b a%b a++ ++a a-- --a

Porównania:

a==b a!=b a>b a<b a>=b a<=b

(21)

Operatory w C++ (2/3)

Logiczne:

!a a&&b a||b Bitowe:

~a a&b a|b a^b a<<b a>>b Działanie z podstawieniem:

a+=b a-=b a*=b a/=b a%=b a&=b a|=b a^=b a<<=b a>>=b

21

(22)

Operatory w C++ (3/3)

Zakresu i wskaźnikowe:

a[b] *a &a a->b a.b a->*b a.*b

Inne:

, a?b:c a::b sizeof() typeid() new new[] delete delete[]

<wywołanie funkcji>

<rzutowania typu>

(23)

Funkcje operatorowe

Funkcje operatorowe pozwalają na zdefiniowanie działania tych operatorów w odniesieniu do argumentów

obiektowych.

Służy do tego funkcja operatorowa, której nazwa ma postać:

operator<symbol operatora>

Przykładowe nazwy: operator+

operator<<

23

(24)

Ograniczenia:

nie można zmienić:

* liczby argumentów * priorytetu operatora

* sposobu wiązania (lewostronnie, prawostronnie)

nie można przedefiniowywać niektórych operatorów:

(25)

Przykład 1: operator dodawania liczb zespolonych

Definiujemy metodę:

Zespolona operator+ (const Zespolona & z) { return Zespolona(re+z.re, im+z.im);

}

Przykład wywołania (z użyciem symbolu operatora):

Zespolona z, z1(1,0), z2(0,1);

z = z1 + z2;

Ale można też wywoływać pełną nazwą („standardowo”):

z = z1.operator+(z2);

25

(26)

Metoda operatorowa dwuargumentowa

Składnia:

<typ wyniku> operator <symbol

operatora>(<argument typu obiektowego>)

Funkcja operatorowa dwuargumentowa należąca do klasy

(metoda operatorowa) jest wywoływana na rzecz obiektu, który jest po lewej od operatora (dane tego obiektu są dostępne przez this, albo bezpośrednio przez nazwę).

Drugi argument jest przekazywany bezpośrednio (przez referencję lub wskaźnik).

(27)

Przykład 2: operator mnożenia liczb zespolonych

Zespolona operator* (const Zespolona& z) { return Zespolona(re*z.re-im*z.im,

re*z.im+im*z.re);

}

Wywołanie operatorem:

z = z1 * z2;

Wywołanie w postaci pełnej:

z = z1.operator*(z2);

27

(28)

Metoda operatorowa jednoargumentowa

Składnia:

<typ wyniku> operator <symbol operatora> (void)

Funkcja operatorowa jednoargumentowa należąca do klasy (metoda

operatorowa) jest zwykle wywoływana na rzecz obiektu po prawej od operatora (dane tego obiektu są dostępne przez this, albo

bezpośrednio przez nazwę).

Przykład: definicja operatora minus (zmiana znaku liczby) Zespolona operator- (void)

{ return Zespolona(-re, -im); } Użycie:

Zespolona z, z2(0,1);

z=-z2;

Dla porównania: operator odejmowania dwóch liczb zespolonych:

Zespolona operator- (const Zespolona& z)

(29)

Inne języki obiektowe

W wielu językach obiektowych nie ma możliwości

przedefiniowywania operatorów, ze względu m.in. na możliwe niejasności jakie to powoduje (operator

jakiej klasy się wywoła, jakie są priorytety

rozpatrywania, z której strony się łączą itp.). Zamiast metod operatorowych używa się „zwykłych” metod.

Definicja operacji dodawania liczb zespolonych bez użycia operatorów:

Zespolona dodaj(const Zespolona& z) { return Zespolona(re+z.re, im+z.im); } Wywołanie dla takiej definicji:

z = z1.dodaj(z2);

29

(30)

FUNKCJE

OPERATOROWE

(31)

Jak zdefiniować operator, który pozwala na zapis:

int x=5;

Zespolona z, z1(5,3);

z = x * z1;

gdzie x jest zmienną typu float, tzn. lewym argumentem operatora jest zmienna typu standardowego?

Wiemy, że operatory dwuargumentowe „wywołują się” dla swojego lewego argumentu, ale nie mamy możliwości zmiany działania wbudowanego typu float.

31

(32)

Funkcja operatorowa

Rozwiązaniem jest zdefiniowanie funkcji

operatorowej (poza klasą, ale w obszarze widoczności wywołania). Lista argumentów będzie tym razem pełna (nie mamy tym razem „za darmo” obiektu dla którego wołamy funkcję czyli this).

Składnia nagłówka funkcji operatorowej jednoargumentowej:

<typ wyniku> operator <symbol operatora> (<typ argumentu>)

Składnia nagłówka funkcji operatorowej dwuargumentowej:

(33)

Czyli definiujemy np. poza main() i klasą Zespolona następującą funkcję:

Zespolona operator* (float a, const Zespolona& z)

{ return Zespolona(a*z.re, a*z.im); }

Jeżeli w takiej funkcji używane są pola prywatne klasy Zespolona, funkcję należy zaprzyjaźnić z klasą.

Wtedy możemy używać wspomnianego kodu:

int x=5;

Zespolona z, z1(5,3);

z = x * z1;

33

(34)

Podobna sytuacja występuje przy

definiowaniu operatorów wprowadzania lub wyprowadzania do strumienia, np.:

cout << z;

(35)

STRUMIENIE

35

(36)

Strumienie

Strumień - ciąg bajtów o nieokreślonej długości

W języku C++ wyróżniamy trzy typy strumieni:

strumienie wejściowe (wczytują dane),

strumienie wyjściowe (wypisują dane),

strumienie uniwersalne, umożliwiające zarówno wczytywanie, jak i wypisywanie danych.

Strumieniami posługujemy się zwykle do operacji wejścia/wyjścia (ekran, klawiatura, pliki), np.

pobieraliśmy dane z klawiatury lub pliku,

wypisywaliśmy dane na ekran, zapisywanie danych do pliku.

Można też strumieniami przetwarzać dane, np.

(37)

Do wczytywania danych ze strumienia wejścia służy operator >>, a wysyłania danych do strumienia wyjścia służy

operator <<

Tak naprawdę wszystkie dane, które

strumień wypisuje na ekran muszą zostać sformatowane, ale w wielu przypadkach (dla zmiennych typów standardowych) dzieje się to automatycznie.

37

(38)

Strumienie vs funkcje

Dla przykładu, aby wypisać zmienną x typu float, w C++ dołączamy bibliotekę iostream.h i piszemy:

std::cout << x;

Jeżeli użyjemy przestrzeni nazw std (using namespace std;) to możemy jeszcze krócej:

cout << x;

W języku C musieliśmy dołączyć bibliotekę stdio.h i wypisywać za pomocą funkcji (precyzując

formatowanie):

printf("%f", x);

(39)

Możemy też łączyć strumienie w

"kaskady":

cout <<"x ma wartość ";

cout <<x;

cout <<".\n";

cout << "x ma wartość " << x << ".\n";

39

(40)

Strumienie predefiniowane

Strumienie predefiniowane to strumienie już stworzone i gotowe do korzystania.

Strumienie te dziedziczą po klasie

ostream dla strumieni wyjścia i istream dla wejścia (są to obiekty!).

Dołączenie pliku nagłówkowego iostream sprawia, że mamy od początku otwarte 3 predefiniowane strumienie:

std::cin - standardowe wejście

std::cout - standardowe wyjście

(41)

#include <iostream>

#include <string>

using namespace std;

int main(){

string x;

cout << "Podaj swoje imię:";

cin >> x;

cout << x << endl;

return 0;

}

Operator >> "wyciąga" pojedyncze słowo oddzielone białymi znakami oraz zapisuje je do zmiennej x (tutaj obiekt klasy string).

41

(42)

Możemy stworzyć bufor na tekst (c-string czyli tablica charów) oraz wypełnić go

znakami, Metoda getline() klasy cin

pobiera wskaźnik na stworzony wcześniej bufor oraz jego rozmiar.

char tekst[100];

cout << "Podaj imię i nazwisko:";

cin.getline(tekst, 100);

(43)

STRUMIENIE NAPISOWE

43

(44)

Strumienie napisów

Wyróżniamy jeszcze jeden rodzaj

strumieni - stringstream. Dzięki niemu jesteśmy w stanie operować na napisach tak, jak na zwykłym strumieniu.

Wyobraźmy sobie sytuację, gdy musimy

zamienić liczbę całkowitą na napis. Język C umożliwiał nam dokonywanie takich

operacji za pomocą funkcji sprintf() bądź funkcji itoa().

(45)

Przykład stosowania strumieni napisowych w C++

#include <iostream>

#include <sstream> // plik nagłówkowy do „stringstream”

using namespace std;

int main() {

long x;

string napis;

stringstream ss;

cout << "Podaj dowolna liczbę całkowitą: ";

cin >> x;

ss << x; // Do strumienia 'wysyłamy' podaną liczbę napis = ss.str(); // Zamieniamy strumień na napis

cout << "Długość napisu wynosi " << napis.length() << " znaków."<< endl;

return 0;

}

45

(46)

STRUMIENIE PLIKOWE

(47)

Strumienie plikowe

Za pomocą strumieni możemy czytać i zapisywać do plików.

Przykładowy program czyta po jednym znaku ze standardowego wejścia i zapisuje go do pliku tekst.txt, dopóki użytkownik nie wciśnie <Enter>

#include <iostream>

#include <fstream> //plik nagłówkowy fstream do strumieni plikowych int main(){

char a; // mini-bufor

std::ofstream f ("tekst.txt"); // tworzymy strumień wyjściowy f,

podłączamy go do pliku tekst.txt (param. konstruktora) std::cout << "Podaj tekst do zapisu:";

do {

a = std::cin.get(); // wczytujemy pojedynczy znak ze standardowego wejścia, f << a; // zapisujemy znak z mini-bufora do pliku

} while (a != '\n'); // dopóki użytkownik nie wciśnie <Enter>

return 0;

}

47

(48)

Takie podejście (pliki traktowane jako

strumienie) jest powszechnie stosowane w innych językach obiektowych (m.in. Java, C#, Python).

Cytaty

Powiązane dokumenty

Można jednakże zauważyć, że chociaż w rzeźbie (Sculpture) imitacje kwiatów i listowia sprawiają przyjemność jako ornamenty archi- tektoniczne, to jako część

 Przeszukiwany jest stos wywołań funkcji w poszukiwaniu takiej, która zawiera obsługę wyjątku danego typu (czyli odpowiednią instrukcję catch).. ◦ Jeżeli

• Parametrami szablonów mogą być również szablony klas, jako tak zwane szablony parametrów szablonów.. Stack&lt;int, std::vector&gt;

 OutputIterator set_union (InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result);.  OutputIterator

Model dziedziny (uzupełniony): klasy, atrybuty klas oraz

 Jednostka programu, która zadeklarowała instancję klasy (obiekt), ma dostęp do publicznych bytów tej klasy, ale tylko poprzez tę instancję.  Każda instancja klasy ma

 Symbole pojawiające się wyłącznie po prawej stronie to symbole terminalne.  Generalnie symbole terminalne to symbole z alfabetu definiowanego języka,

Z dobroci serca nie posłużę się dla zilustrowania tego mechanizmu rozwojem istoty ludzkiej, lecz zaproponuję przykład róży, która w pełnym rozkwicie osiąga stan