Wstęp do programowania obiektowego
Przekazywanie parametrów do funkcji w C++
Metody i funkcje operatorowe
Strumienie: standardowe, plikowe, napisowe
1
PRZEKAZYWANIE
PARAMETRÓW DO
FUNKCJI W C++
W C++ dostępne są dwa tryby
przekazywania parametrów do funkcji:
Przez wartość
Przez referencję
3
PRZEKAZYWANIE PARAMETRÓW DO
FUNKCJI W C++ PRZEZ
WARTOŚĆ
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
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
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.
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;
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
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
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
PRZEKAZYWANIE PARAMETRÓW DO
FUNKCJI W C++ PRZEZ
REFERENCJĘ
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
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
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
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.
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
METODY
OPERATOROWE
Operatory – znaki symbolizujące
wykonanie powszechnie znanych (lub domyślnych) operacji
Operandy – argumenty na których te operacje się wykonuje
19
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
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
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>
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
Ograniczenia:
nie można zmienić:
* liczby argumentów * priorytetu operatora
* sposobu wiązania (lewostronnie, prawostronnie)
nie można przedefiniowywać niektórych operatorów:
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
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).
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
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)
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
FUNKCJE
OPERATOROWE
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
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:
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
Podobna sytuacja występuje przy
definiowaniu operatorów wprowadzania lub wyprowadzania do strumienia, np.:
cout << z;
STRUMIENIE
35
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.
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
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);
Możemy też łączyć strumienie w
"kaskady":
cout <<"x ma wartość ";
cout <<x;
cout <<".\n";
cout << "x ma wartość " << x << ".\n";
39
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
#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
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);
STRUMIENIE NAPISOWE
43
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().
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
STRUMIENIE PLIKOWE
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
Takie podejście (pliki traktowane jako
strumienie) jest powszechnie stosowane w innych językach obiektowych (m.in. Java, C#, Python).