składowe statyczne

Download Report

Transcript składowe statyczne

C++
wykład 3 (7.03.2013)
Składowe statyczne
Funkcje wbudowane
Argumenty domyślne
Funkcje zaprzyjaźnione
Pola statyczne



Każdy obiekt danej klasy ma swój własny
zestaw pól z danymi.
Pole statyczne, z deklaratorem static, nie
jest częścią obiektu, istnieje poza
jakimkolwiek obiektem i jest wspólne dla
wszystkich obiektów danej klasy.
Pole statyczne istnieje przez cały czas życia
programu, nawet wtedy gdy nie utworzono
żadnego obiektu danej klasy.
Pola statyczne




Deklaracja pola statycznego w ciele klasy (w pliku
nagłówkowym) nie jest jego definicją.
Przykład klasy z deklaracją pola statycznego:
class data
{
int dz, mies, rok;
public:
data (int d, int m, int r)
: dz(d), mies(m), rok(r) {/*…*/}
static data poczatek_kalendarza;
};
Definicję pola statycznego należy umieścić poza klasą (w pliku
źródłowym).
Przykład definicji pola statycznego poza klasą:
data data::poczatek_kalendarza(15,10,1582);
Pola statyczne

Odniesienie się do pola statycznego poprzez
nazwę klasy:
klasa::pole_stat

Do pola statycznego można się też odnieść
poprzez jakikolwiek obiekt danej klasy:
klasa ob, *wsk;
// …
ob.pole_stat
wsk->pole_stat
Statyczne funkcje składowe



Statyczna funkcja składowa, z deklaratorem
static, nie odnosi się do żadnego obiektu
danej klasy.
Statyczna funkcja składowa może być
wywołana w dowolnym momencie, nawet
wtedy gdy nie utworzono żadnego obiektu
danej klasy.
Deklaracja statycznej funkcji składowej
znajduje się w ciele klasy (w pliku
nagłówkowym) a jej definicja jest poza klasą
(w pliku źródłowym).
Statyczne funkcje składowe




Przykład klasy z deklaracją statycznej funkcji składowej:
class data
{
int dz, mies, rok;
public:
data (int d, int m, int r)
: dz(d), mies(m), rok(r) {/*…*/}
static int roznica (data p, data k);
};
Przykład definicji statycznej funkcji składowej:
int data::roznica (data p, data k) {/*…*/}
W ciele statycznej funkcji składowej nie wolno odnosić się do
składowych instancyjnych ani do wskaźnika this.
Do statycznej funkcji składowej można odnieść się poprzez
nazwę klasy albo poprzez jakikolwiek obiekt danej klasy.
Funkcje wbudowane




Funkcje wbudowane, oznaczone deklaratorem
inline, są rozwijane w miejscu wywołania.
Ich definicja musi być znana w momencie kompilacji
a nie linkowania, dlatego nie tylko ich deklaracja ale
również definicja znajduje się w pliku nagłówkowym.
Deklarator inline to tylko sugestia dla kompilatora
aby wbudowywał funkcję w miejscu jej wywołania.
Funkcja inline zostanie skompilowane jako outline w
przypadku, gdy:



kompilujemy program do pracy z debuggerem,
funkcja jest rekurencyjna,
pobieramy w programie adres funkcji.
Funkcje wbudowane



Wbudowywanie funkcji w kod ma sens w przypadku
krótkich funkcji.
Funkcje wbudowane zwiększają rozmiar programu
wynikowego ale przyspieszają jego działanie.
Przykład funkcji wbudowanej:
inline int zaokraglenie (double d)
{
return d<0 ? int(d-.5) : int(d+.5);
}
Wbudowane funkcje składowe



W klasie też można definiować wbudowane
funkcje składowe.
Metoda zdefiniowana w klasie jest
traktowana jako inline.
Metody inline można także definiować poza
klasą.
Wbudowane funkcje składowe

Przykład klasy z metodami wbudowanymi:
class data
{
int dz, mies, r;
public:
data (int d, int m, int r);
int dzien (void) const;
int miesiac (void) const { return mies; }
inline int rok (void) const { return r; }
};
inline data::data (int d, int m, int r)
: dz(d), mies(m), r(r) {}
inline int data::dzien (void) const
{ return dz; }
Argumenty domyślne



Często mamy taką sytuację, że w ogólnym
przypadku funkcja wymaga podania większej
liczby argumentów niż w przypadku
najprostszym, albo że wartości niektórych
argumentów często się powtarzają.
Argumenty funkcji globalnych i funkcji
składowych w klasie mogą posiadać
argumenty domyślne.
Argumenty domyślne są formą przeciążania
nazwy funkcji.
Argumenty domyślne




Argumenty domyślne mogą występować tylko
na końcu listy argumentów.
Wartości domyślne nadawane argumentom
domyślnym podaje się tylko w deklaracji
funkcji, w definicji się tego nie powtarza.
Przy wywoływaniu funkcji z argumentami
domyślnymi można pomijać argumenty
domyślne od końca.
Wszystkie argumenty mogą być domyślne.
Argumenty domyślne

Przykład funkcji z argumentem domyślnym:
// deklaracja
void drukuj (int x, int podst=10);
// definicja
void drukuj (int x, int podst) {/*…*/}
// wywołania
drukuj(63); // wynik 63
drukuj(63,16); // wynik 3F
drukuj(63,2); // wynik 111111
Nienazwane argumenty




Jeśli argumentu nazwanego nie używa się w ciele
funkcji lub funkcji składowej, to kompilator będzie
generował ostrzeżenia.
Argumenty, których nie używa się w ciele funkcji
mogą nie posiadać swojej nazwy.
Również argumenty domyślne mogą nie posiadać
nazwy.
Przykład funkcji z nienazwanym argumentem:
void buczenie (int) { /*…*/ }
Funkcja bez argumentów

Jeśli funkcja nie ma żadnych argumentów, to
powinno się ją zadeklarować z argumentem void.

Przykład:
void buczenie (void);
// to jest równoważne z
// void buczenie ();
// w języku C byłoby to równoważne z
// void buczenie (...);
Funkcje zaprzyjaźnione




Problem z kwiatkami w domu w czasie
dalekiej podróży służbowej.
Funkcja, która jest przyjacielem klasy, ma
dostęp do wszystkich jej prywatnych i
chronionych składowych.
To klasa deklaruje, które funkcje są jej
przyjaciółmi.
Deklaracja przyjaźni może się pojawić w
dowolnej sekcji i jest poprzedzona słowem
kluczowym friend.
Funkcje zaprzyjaźnione

Przykład klasy z funkcją zaprzyjaźnioną:
// klasa z funkcją zaprzyjaźnioną
class pionek
{
int x, y;
// …
friend void raport (const pionek &p);
};
// funkcja, która jest przyjacielem klasy
void raport (const pionek &p)
{
cout << "(" << p.x << ", " << p.y << ")";
}
Funkcje zaprzyjaźnione




Nie ma znaczenia, w której sekcji (prywatnej,
chronionej czy publicznej) pojawi się deklaracja
przyjaźni.
Funkcja zaprzyjaźniona z klasą nie jest jej składową,
nie może używać wskaźnika this w stosunku do
obiektów tej klasy.
Jedna funkcja może się przyjaźnić z kilkoma
klasami.
Istotą przyjaźni jest dostęp do niepublicznych
składowych w klasie – sensowne jest deklarowanie
przyjaźni, gdy dana funkcja pracuje z obiektami tej
klasy.
Funkcje zaprzyjaźnione

Można także umieścić w klasie nie tylko
deklarację funkcji zaprzyjaźnionej, ale
również jej definicję; tak zdefiniowana
funkcja:




jest nadal tylko przyjacielem klasy;
jest inline;
może korzystać z typów zdefiniowanych w klasie.
Funkcją zaprzyjaźnioną może być funkcja
składowa z innej klasy.
Klasy zaprzyjaźnione





Możemy w klasie zadeklarować przyjaźń z inną
klasą, co oznacza, że każda metoda tej innej klasy
jest zaprzyjaźniona z klasą pierwotną.
Przykład:
class A
{
friend class B;
// …
};
Przyjaźń jest jednostronna.
Przyjaźń nie jest przechodnia.
Przyjaźni się nie dziedziczy.
Klasy zaprzyjaźnione

Dwie klasy mogą się przyjaźnić z wzajemnością:
class A;
class B
{
friend class A;
// …
};
class A
{
friend class B;
// …
};
Nowości z C++11 – wskaźnik
pusty nullptr




Wskaźnik pusty nullptr – zastępuje makro NULL albo 0.
Motywacja: w C NULL jest makrem preprocesora zdefiniowanym
jako ((void*)0); w C++ niejawna konwersja z void* do
wskaźnika innego typu jest niedozwolona, więc nawet takie proste
przypisanie jak char* c = NULL mogłoby być w tym przypadku
błędem kompilacji.
Sytuacja komplikuje się w przypadku przeciążania:
void foo(char*);
void foo(int);
Gdy programista wywoła foo(NULL), to wywoła wersję foo(int),
która prawie na pewno nie była przez niego zamierzona.
nullptr nie może być przypisane do typów całkowitych, ani
porównywane z nimi; może być porównywane z dowolnymi typami
wskaźnikowymi.
Nowości z C++11 – pętla for
oparta na zakresie





Zakresy reprezentują kontrolowaną listę pomiędzy dwoma jej punktami.
Kontenery uporządkowane są nad zbiorem koncepcji zakresu i dwa
iteratory w kontenerze uporządkowanym także definiują zakres.
Nowa pętla for została stworzona do łatwej iteracji po zakresie; jej
ogólna postać jest następująca:
for (TYP &x: kolekcja<TYP>) instrukcja;
Przykład:
int moja_tablica[5] = {1, 2, 3, 4, 5};
for(int &x: moja_tablica) { x *= 2; }
Pierwsza sekcja nowego for (przed dwukropkiem) definiuje zmienną,
która będzie użyta do iterowania po zakresie. Zmienna ta, tak jak
zmienne w zwykłej pętli for, ma zasięg ograniczony do zasięgu pętli.
Druga sekcja (po dwukropku), reprezentuje iterowany zakres. W tym
przypadku, zwykła tablica jest konwertowana do zakresu. Mógłby to być
na przykład std::vector albo inny obiekt spełniający koncepcję
zakresu.
Nowości z C++11 – uogólnione
wyrażenia stałe


Stałe wyrażenia zawsze zwracają ten sam wynik i
nie wywołują żadnych dodatkowych efektów
ubocznych – są one dla kompilatorów okazją do
optymalizacji, ponieważ kompilatory często
wykonują te wyrażenia w czasie kompilacji i
wstawiają ich wyniki do programu.
Słowo kluczowe constexpr pozwala programiście
zagwarantować, że funkcja lub konstruktor obiektu
są stałymi podczas kompilacji.
Nowości z C++11 – uogólnione
wyrażenia stałe

Zastosowanie constexpr do funkcji narzuca bardzo
ścisłe ograniczenia na to, co funkcja może robić:






funkcja musi posiadać typ zwracany różny od void;
zawartość funkcji musi być postaci return wyrażenie;
wyrażenie musi być stałym wyrażeniem po zastąpieniu
argumentu (to stałe wyrażenie może albo wywołać inne funkcje
tylko wtedy, gdy te funkcje też są zadeklarowane ze słowem
kluczowym constexpr albo używać inne stałe wyrażenia);
wszystkie formy rekurencji w stałych wyrażeniach są zabronione;
funkcja zadeklarowana ze słowem kluczowym constexpr nie
może być wywoływana, dopóki nie będzie zdefiniowana w swojej
jednostce translacyjnej.
Przykład:
constexpr int GetFive() {return 5;}
int someValues[GetFive() + 5];
Nowości z C++11 – uogólnione
wyrażenia stałe

Zmienne stałowyrażeniowe typu constexpr są niejawnie typu const –
mogą one przechować wyniki wyrażeń stałych lub stałowyrażeniowych
konstruktorów (czyli zdefiniowanych ze słowem kluczowym
constexpr).

Przykład:
constexpr double grawitacja = 9.8;
constexpr double grawitacjaKsiezyca = grawitacja / 6;

Stałowyrażeniowy konstruktor służy do konstrukcji wartości
stałowyrażeniowych z typów zdefiniowanych przez użytkownika,
konstruktory takie muszą być zadeklarowane jako constexpr.

Stałowyrażeniowy konstruktor musi być zdefiniowany przed użyciem w
jednostce translacyjnej (podobnie jak metoda stałowyrażeniowa) i musi
mieć puste ciało funkcji i musi inicjalizować swoje składowe za pomocą
stałych wyrażeń.
Destruktory takich typów powinny być trywialne.

Nowości z C++11 – referencja
do r-wartości




Obiekty tymczasowe (określane jako r-wartości), to
wartości stojące po prawej stronie operatora przypisania
(analogicznie zwykła referencja do zmiennej stojącej po
lewej stronie przypisania nazywa się l-wartością).
Argument w funkcji będący referencją do r-wartości
definiujemy jako TYP &&arg.
Argument będący r-referencją może być akceptowany
jako niestała wartość, co pozwala funkcjom na ich
modyfikację.
Argumenty r-referencyjne umożliwiają pewnym obiektom
na stworzenie semantyki przenoszenia za pomocą
konstruktorów przenoszących definiowanych jako
TYP::TYP (TYP &&arg) oraz przypisań
przenoszących.
Nowości z C++11 – referencja
do r-wartości

Przykład:
class Simple {
void *Memory; // The resource
public:
Simple() {
Memory = nullptr; }
// the MOVE-CONSTRUCTOR
Simple(Simple&& sObj) {
// Take ownership
Memory = sObj.Memory;
// Detach ownership
sObj.Memory = nullptr; }
Simple(int nBytes) {
Memory = new char[nBytes]; }
~Simple() {
if(Memory != nullptr) delete[] Memory; }
};
Nowości z C++11 – referencja
do r-wartości

Przykład:
Simple GetSimple() {
Simple sObj(10);
return sObj; }
// R-Value NON-CONST reference
void SetSimple(Simple&& rSimple) {
// performing memory assignment here
Simple object;
object.Memory = rSimple.Memory;
rSimple.Memory = nullptr;
// Use object...
delete[] object.Memory; }