Wstęp do programowania obiektowego
Wykład 5
Słowo kluczowe const
Przeładowywanie operatorów strumieniowych Klasa string
Agregacja i kompozycja
SŁOWO KLUCZOWE
CONST
Słowo kluczowe const
Słowem kluczowym const w C++
deklaruje się stałe.
Obiekty zadeklarowane jako const mogą być inicjalizowane, natomiast nie mogą być przypisywane poza ich inicjalizacją, np.
const int b = 7;
b = 12; //błąd kompilacji
Argumenty funkcji zadeklarowane jako const
nie mogą być zmieniane - przy przekazywaniu
przez referencję (przy przekazywaniu przez
Metody z przyrostkiem const
void nazwa(…) const – taka metoda nie może nic zmieniać w obiekcie, może tylko czytać
class Punkt { float x,y;
float przesun(float dx, float dy) const {
x += dx; //błąd kompilacji y += dy; //błąd kompilacji }
}
Wskaźniki i słowo const
const int * wsk = &a;
wsk jest wskaźnikiem do obiektu stałego typu int, czyli można wskaźnikiem wsk pokazywać na różne zmienne, ale nie można ich modyfikować za pomocą wsk.
int * const wsk = &a;
wsk jest stałym wskaźnikiem do zmiennej int, czyli można modyfikować obiekt na który wskazuje, ale nie można
pokazywać tym wskaźnikiem na inny obiekt niż ten, na który został ustawiony w czasie deklaracji.
const int * const wsk = &a;
wsk jest stałym wskaźnikiem do stałej int, ani nie można
modyfikować obiektu pokazywanego przez wskaźnik, ani
nie można pokazać tym wskaźnikiem na inny obiekt.
Wskaźniki i słowo const - przykłady
int a, b;
const int * wsk = &a;
wsk = &b;
*wsk = 6; //error: assignment of read-only location *wsk ---
int a, b;
int * const wsk = &a;
wsk = &b; //error: assignment of read-only variable wsk
*wsk = 6;
---
int a, b;
const int * const wsk = &a;
wsk = &b; //error: assignment of read-only variable wsk
PRZEŁADOWYWANIE OPERATORÓW
STRUMIENIOWYCH << >>
Przeładowywanie operatora strumieniowego
<<
Definicja funkcji operatorowej:
ostream& operator<<(ostream& os, const Zespolona& z) { os << „(” << z.re << „,” << z.im << „)”;
return os; }
Przykładowe użycie:
Zespolona z(5.5, 3.1);
cout << z;
Taką funkcję operatorową definiujemy poza klasą, ale musimy ją z klasą zaprzyjaźnić, jeżeli korzysta ona z prywatnych składowych klasy:
friend ostream& operator<<(ostream&, const Zespolona
Przeładowywanie operatora strumieniowego >>
Definicja:
istream& operator>>(istream& is, Zespolona& z) {
is >> z.re >> z.im;
return is;
}
Przykładowe użycie:
Zespolona z(5.5, 3.1);
cout << z;
Zaprzyjaźniamy funkcję z klasą w miarę potrzeby.
Liczby możemy wprowadzić w jednej linijce oddzielone
spacjami, albo po każdej przyciskając <Enter>.
KLASA STRING
Klasa string
Stanowi wygodną obiektową alternatywę dla tzw. c-stringa, czyli tablicy znaków typu char, zakończonej zerem
(reprezentacja napisów w czystym strukturalnym C).
Klasa string umożliwia jednolity,
niezależny od systemu i bezpieczny
sposób na składowanie i manipulowanie napisami.
Zdefiniowana jest m.in. w pliku
nagłówkowym string.h, iostream.h, itp.
Przykłady nadania wartości
string n1("tekst1 "); //konstruktorem
string n2 = "tekst2 "; //podstawieniem stałej napisowej string n3 = n1; //podstawieniem innego stringa cout << n1 << n2 << n3;
Operatory
Klasa string ma zdefiniowane operatory:
= przypisanie
== równość
!= nierówność
< > <= >= porządek słownikowy + += łączenie napisów
[] (indeksowanie) dostęp do poszczególnych
znaków
Wybrane metody klasy string
empty() Zwraca true jeżeli napis jest pusty.
size(), length() Zwraca ilość znaków w napisie.
at(int) Zwraca znak o podanym
położeniu, podobnie jak operator [ ], clear() Usuwa wszystkie znaki z napisu.
erase(...) Usuwa podciąg.
find(...) Znajduje podciąg w napisie
swap(string, string) Zamienia dwa stringi wartościami substr(...) Zwraca podciąg napisu
append(...) Dodaje zadany napis na końcu
innego
Poruszanie się po stringu iteratorem
Iterator – metoda (niezawodnego) poruszania się po zbiorze danych tego samego typu (tutaj znaki typu char)
using namespace std;
#include <iostream>
#include <string>
int main () {
string str ("Napis testowy");
for (string::iterator it=str.begin(); it!=str.end(); it++) cout << *it;
cout << '\n';
Poruszanie się po stringu iteratorem
Iterator – metoda (niezawodnego) poruszania się po zbiorze danych tego samego typu (tutaj znaki typu char)
using namespace std;
#include <iostream>
#include <string>
int main () {
string str ("Napis testowy");
for (string::iterator it=str.begin(); it!=str.end(); it++)
cout << *it;
cout << '\n';
Deklaracja iteratora o
nazwie it
it jest ustawiane na początku
tekstu
...dopóki it nie jest na
końcu tekstu
Przesuwamy it o jeden
znak w prawo
… i poruszanie się standardowo (indeksowaniem)
for (int i = 0; i<str.size(); i++)
cout << str[i];
AGREGACJA
Agregacja
Agregacja w programowaniu
obiektowym to sytuacja, gdy tworzy się nową klasę, używając jako
zmiennych składowych obiektów innych klas (tzw. „obiektów
składowych").
Nowa klasa może być zbudowana z dowolnej liczby obiektów (dowolnych typów). Liczba powiązanych obiektów może też się zmieniać w czasie.
19
Przykład agregacji
class Staw
{ Kaczka* kaczki[20];
};
class Kaczka { };
Staw Kaczka
0..1 0..*
Na diagramach UML agregację oznacza się pustym rombem.
Kaczka może istnieć samodzielnie, lub być przypisana
Agregacja jest określana jako relacja typu "zawiera".
Agregacja jest często przedstawiana w opozycji do dziedziczenia, które
polega na uszczegóławianiu typu ogólnego w celu utworzenia typu
szczególnego. Agregacja nie tworzy
podtypu, lecz nowy typ.
KOMPOZYCJA
Kompozycja jest szczególnym przypadkiem agregacji. Oznacza składanie się obiektu z obiektów
składowych, które nie mogą istnieć bez obiektu głównego.
część może należeć tylko do jednej całości;
usunięcie całości powoduje automatyczne usunięcie wszystkich jej części.
Kompozycja jest relacją typu „posiada”.
Związek między obiektami jest silny.
Przykład kompozycji
class Silnik {};
class Samochod { Silnik* sil;
public:
Samochod() {sil = new Silnik();}
~Samochod() {delete sil;}
};
• Samochód nie może istnieć bez Silnika, tworzy Silnik w konstruktorze, a niszczy w destruktorze
• Realizacja kompozycji zwykle należy do programisty (zapewnienie odpowiedniego tworzenia oraz niszczenia obiektów składowych)
• Na diagramach UML kompozycję oznacza się wypełnionym
Samochod Silnik
0..1 1
Kompozycja a agregacja
Kompozycją określa się sytuację, kiedy
• kompozyt (i czasem komponent) nie mogą istnieć oddzielnie,
• liczność komponentów nie zmienia się,
• związek między obiektami jest bardzo silny (np.
Odcinek i Trójkąt).
Agregacją określa się sytuację, kiedy
• kompozyt i komponent mogą istnieć bez siebie,
• liczność komponentów może się zmieniać dynamicznie,
• związek między obiektami jest o wiele słabszy
niż w kompozycji (np. Książka i Biblioteka).
Liczności w UML
Liczności obiektów na diagramach UML oznaczamy przedziałami, gdzie mogą występować liczby całkowite oraz
gwiazdka, która oznacza dowolną ilość (również zero).
Samochod Silnik
0..1 1
Staw Kaczka
0..1 0..*
Agregacja rekursywna
Agregacja może być rekursywna, czyli zawierać obiekty (lub odwołania do obiektów) tego samego typu.
Przykład: węzeł drzewa binarnego:
class Node{
Node *left, *right;
int liczba;
};
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
class Node{
public:
Node *left, *right;
int liczba;
Node (int ii):liczba(ii){}
};
ostream& operator<<(ostream& os, const Node& z) {
os << "(" << z.liczba << ")\n";
return os;
}
int main() {
Node n = Node(3);
Uniwersytet składa się z Wydziałów, które nie mogą istnieć samodzielnie i muszą być
przypisane do jakiegoś Uniwersytetu (kompozycja).
Każdy Wydział ma pewną liczbę pracujących w nim Profesorów (minimum 5), którzy nie są aż tak mocno powiązani z Wydziałem (mogą istnieć samodzielnie oraz być powiązani z jednym lub dwoma Wydziałami – agregacja).
Uniwersytet 1 1..20 Wydział 0..2 5..* Profesor