02.Struktura i elementy programu.doc

(100 KB) Pobierz
STRUKTURY

5

 

1. Podstawowe elementy języka C++

 


13

2. Struktura i elementy programu

2.              Struktura i elementy programu

2.1.              Deklaracje i definicje

Jak już wspomniano, wszystkie wielkości występujące w programie muszą być przed ich użyciem zadeklarowane. Deklaracje ustalają nieodzowne odwzoro­wanie pomiędzy strukturami danych i operacjami, a reprezentującymi je konstrukcjami programowymi. Każda deklaracja wiąże podany przez użytkownika identyfikator z odpowiednim typem danych. Większość deklaracji, znanych jako deklaracje definiujące lub definicje, powoduje także utworzenie definiowanej wielkości, tj. przydzielenie (alokację) jej fizycznej pamięci i ewentualne zainicjowanie. Pozostałe deklaracje, nazywane deklaracjami referencyjnymi lub deklaracjami, mają znaczenie informacyjne, ponieważ jedynym ich zadaniem jest podanie deklarowanej nazwy i jej typu do wiadomości kompilatorowi. Tak zadeklarowany identyfikator musi być w programie zdefiniowany –  albo później w tym samym, albo w oddzielnym pliku z kodem źródłowym. Dla danego identyfikatora może wystąpić wiele deklaracji referencyjnych, ale tylko jedna deklaracja definiująca (przypadki takie występują najczęściej w programach wieloplikowych). Oczywistą jest zasada, że żaden identyfi­kator nie może być użyty w programie przed jego punktem deklaracyjnym w kodzie źródłowym. Deklaracja zakończona średnikiem nazywa się instrukcją deklaracji.

 

Najczęściej deklarowanymi wielkościami są zmienne. Zmienną określa się jako pewien obszar pamięci o zadanej symbolicznej nazwie, w którym można przechowywać wartości, interpretowane zgodnie z zadeklarowanym typem zmiennej. Język C++ uogólnia to pojęcie: wymieniony obszar pamięci może nie posiadać nazwy, może mieć nie jedną lecz kilka nazw, zaś adres początku obszaru pamięci może być dostępny dla programisty. Przykład 2.1 ilustruje różnorodność możliwych deklaracji i definicji.

 

Przykład 2.1.

 

char znak;

char litera = 'A';

char* nazwa = "Cplusplus";

extern int ii;

int j = 10;

const double pi = 3.1415926;

enum day { Mon, Tue, Wed, Thu, Fri, Sat, Sun };

struct mystruct;

struct complex { float re, im;};

complex zespolone;

typedef complex punkt;

class myclass;

template<class T> abs(T x) { return x < 0 ? -x : x; }

 

Dyskusja. Jak widać z powyższego przykładu, w deklaracji można umieścić o wiele więcej informacji dla kompilatora, niż jedynie wskazać, że dana nazwa jest związana z określonym typem. Tylko 3 spośród nich

 

extern int ii;              struct mystruct;              class myclass;

 

są deklaracjami referencyjnymi, a zatem muszą im towarzyszyć odpowiednie definicje. Definicje struktury mystruct i klasy myclass muszą się znaleźć w tym samym pliku, w którym podano ich deklaracje, zaś definicja zmiennej ii musi być podana w jednym z plików programu. Pozostałe deklaracje są jednocześnie definicjami:

-      Wielkości znak, litera oraz j są zmiennymi typów podstawowych. Stosownie do ich typów kompilator przydzieli im odpowiednie obszary pamięci. Zauważmy także, że zmienne litera, nazwa oraz j zostały zainicjowane odpowiednimi dla ich typów wartościami.

-      Wielkość pi typu double jest stałą symboliczną, o czym informuje słowo kluczowe (deklarator stałej) const na początku deklaracji. Wartość tej stałej (3.1415926) jest znana kompilatorowi, zatem nie trzeba dla niej rezerwować pamięci.

-      Wielkości day i complex są definicjami nowych typów; identyfikator zespolone jest zmienną typu complex, zaś identyfikator punkt jest zastępczą nazwą (synonimem) dla complex.

 

 

 

2.1.1.              Deklaracje stałych

Język C++ pozwala definiować szczególnego rodzaju “zmienne”, których wartości są ustalone i niezmienne w programie. Jeżeli definicję zmiennej zainicjowanej, np.

 

int cyfra = 7;

 

poprzedzimy słowem kluczowym const

 

const int cyfra = 7;

 

to przekształcimy w ten sposób symboliczną zmienną cyfra z pierwszej definicji w stałą symboliczną o tej samej nazwie. Wartość tak zdefiniowanej stałej symbolicznej pozostaje niezmienna w programie, a każda próba zmiany tej wartości będzie sygnalizowana jako błąd. Słowo kluczowe const, które zmienia interpretację definiowanej wielkości, jest nazywane modyfikatorem typu. Nazwa uzasadniona jest tym, że const ogranicza możliwości użycia definiowanej wielkości tylko do odczytu, ale zachowuje informację o typie ( w przykładzie jak wyżej typ stałej cyfra został zdefiniowany jako int). Dzięki temu stałe symboliczne w języku C++ można używać zamiast literałów tego samego typu.

Zauważmy też, że skoro nie można zmieniać wartości stałej symbolicznej po jej zdefiniowaniu, to musi ona być zainicjowana. Np. zapis

const double pi;

jest błędny, ponieważ wielkość pi nie została zainicjowana.

Podobnie jak wszystkie obiekty programu, stała symboliczna może mieć tylko jedną definicję. Po zdefiniowaniu jest ona (domyślnie) widoczna tylko w pliku, w którym umieszczono jej definicję. W większych programach, składających się z wielu plików, możemy uczynić ją widoczną dla innych plików, umieszczając w nich deklaracje, informujące kompilator o tym, że w jednym z plików programu znajdzie jej definicję. Informację tę przekazujemy kompilatorowi za pomocą słowa kluczowego extern, umieszczonego przed deklaracją. Np. stałą cyfra możemy udostępnić innym plikom programu, umieszczając w nich deklaracje o postaci:

extern const int cyfra;

F Uwaga. Starsze kompilatory języka C++ “odgadują” typ stałej symbolicznej na podstawie jej wartości numerycznej i formatu definicji. Jednak standard wymaga jawnego podawania typu każdej stałej.

2.1.2.              Wyliczenia

Alternatywnym, a często bardziej przydatnym sposobem definiowania symbolicz­nych stałych całkowitych jest użycie do tego celu typu wyliczenio­wego (ang. enumerated type). Wyliczenie deklaruje się ze słowem kluczowym enum, po którym następuje wykaz stałych całkowitych (ang. enumerators) oddzielonych przecinkami i zamkniętych w nawiasy klamrowe. Wymienionym stałym są przypisywane wartości domyślne: pierwszej z nich – wartość 0, a każdej następnej – wartość o 1 większa od poprzedzającej. Np. wyliczenie:

enum {mon, tue, wed, thu, fri, sat, sun};

definiuje siedem sta³ych ca³kowitych i przypisuje im wartoci od 0 do 6. £atwo zauwaæyź, æe powyæszy zapis jest krótszy niæ sekwencja deklaracji sta³ych:

 

const int mon = 0;

const int tue = 1;

  .

  .

  .

const int sun = 6;

 

Stałym typu wyliczeniowego można również przypisywać wartości jawnie, przy czym wartości te mogą się powtarzać. Np. deklaracja:

 

enum { false, fail = 0, pass, true = 1 };

 

przypisuje wartość 0 do false i fail (do false domyślnie) oraz wartość 1 do pass true (do pass domyślnie).

W wyliczeniach można po enum umieścić identyfikator, który stanie się od tego momentu nazwą nowego typu. Np.

 

enum days { mon, tue, wed, thu, fri, sat, sun };

 

definiuje nowy typ days. Pozwala to od tej chwili deklarować zmienne typu days, np.

 

days anyday = wed;

 

W taki sam sposób można wprowadzić zapis, który imituje typ bool (jeśli nasz kompilator nie zawiera implementacji tego typu)

 

enum bool {false, true};

bool found, success;

//lub np. bool success = true;

 

Deklaracje zmiennych typu wyliczeniowego można również umieszczać pomiędzy zamykającym nawiasem klamrowym a średnikiem, np.

 

enum bool {false,true} found, success;

F Uwaga. Typ wyliczeniowy jest podobny do typów char oraz shortint w tym, że nie można na jego wartościach wykonywać żadnych operacji arytmetycznych. Gdy wartość typu wyliczeniowego pojawia się w wyrażeniach arytmetycznych, to jest niejawnie przekształcana do typu int przed wykonaniem operacji.

2.2.              Dyrektywy preprocesora

Kompilacja programu źródłowego, napisanego w języku C++, przebiega zwykle w czterech lub pięciu kolejnych krokach; produktem końcowym jest kod ładowalny. W pierwszym kroku uruchamiany jest program, nazywany preprocesorem. Preprocesor przetwarza te wiersze tekstu programu, które zaczynają się znakiem # w pierwszej kolumnie. W wierszach tych zapisujemy polecenia, nazywane dyrektywami. W odróżnieniu od instrukcji, które są wykonywane po zakończeniu kompilacji i uruchomieniu programu, dyrektywy są poleceniami do natychmiastowego wykonania – przed rozpoczęciem właściwego procesu kompilacji. Ponieważ dyrektywy przywołują predefinio­wane wielkości biblioteczne, z reguły umieszcza się je w pierwszych wierszach tekstu programu.

2.2.1.              Dyrektywa #include

Dyrektywa #include pozwala zebrać razem wszystkie fragmenty programu źródłowego w jeden moduł, nazywany plikiem źródłowym lub jednostką translacji. Jeżeli dyrektywa ta występuje w postaci:

 

#include <nazwa-pliku.h>

 

to nazwa-pliku.h odnosi się do standardowego, predefiniowanego pliku nagłówkowego (bibliotecznego), umieszczonego w standardowym katalogu plików dołączanych (ang. standard include directory). Polecenie o takiej postaci zleca preprocesorowi poszukiwanie pliku tylko w standardowych miejscach bez przeszukiwania katalogu zawierającego plik źródłowy.

 

Jeżeli dyrektywa #include występuje w postaci:

 

#include "nazwa-pliku.h"

 

to  preprocesor zakłada, że plik o nazwie nazwa-pliku.h został założony przez użytkownika. W takim przypadku poszukiwanie pliku zaczyna się od katalogu bieżącego; jeżeli wynik poszukiwania jest niepomyślny, to jest ono kontynuo­wane w katalogu standardowym.

F Uwaga 1. W implementacjach Unix-owych standardowym katalogiem dla plików nagłówkowych jest często /usr/include/CC. Jeżeli jest inaczej, to można wymusić początkową ścieżkę poszukiwania, dodając opcję -I w wierszu rozkazowym, np.

                            $ CC -I incl -I/usr/local/include myprog.cc.

W implementacji C++ firmy Borland pod systemem MS-DOS katalogiem standardowym będzie katalog c:\borlandc\include

F Uwaga 2. Zarówno dla standardowych, jak i przygotowanych przez użytkownika plików nagłówkowych można w programie podać jawnie pełną ścieżkę do pliku, np.

                            #include</usr/local/include/CC/iostream.h>

                            czy też < c:\borlandc\include\iostream.h >

 

2.2.2.              Dyrektywa #define

 

Najprostsza postać dyrektywy #define ma składnię:

 

#define nazwa

 

co czytamy: “zdefiniuj identyfikator nazwa”. Dyrektywę #define używa się najczęściej w kontekście z dyrektywą #include, dyrektywami #if#elif#else#endif oraz #ifdef#else#endif i #ifndef#endif. Konteksty te stosuje się głównie w celu uniknięcia wielokrot­nego definiowania tego samego identyfikatora i/lub wielokrotnego wstawiania tego samego pliku bibliotecznego do pliku źródłowego.

 

Dyrektywę #define można również zapisać w postaci:

 

#define nazwa sekwencja-znaków

 

co czytamy: “zastępuj wszystkie wystąpienia identyfikatora nazwa podaną sekwencją znaków”.

 

Przykład 2.2.

 

...

Zgłoś jeśli naruszono regulamin