2.1 Najprostsze programy
Przejdźmy zatem do omawiania samego języka C++.
Znający Javę (czy PHP)
z łatwością zauważą, że wywodzi się ona,
przynajmniej jeśli chodzi o składnię, właśnie od C/C++ (choć
semantycznie przypomina bardziej Smalltalk
czy język Ada).
Java nie jest tu zresztą wyjątkiem: wiele innych języków
nawiązuje swą składnią do C/C++. Tak więc znajomość C/C++
okaże się bardzo przydatna przy nauce również innych
współczesnych języków. W C/C++ występuje też sporo konstrukcji
specyficznych dla tego języka. Dotyczą one, między innymi,
operacji wejścia/wyjścia. Zauważmy tu, że operacje WE/WY
są z kolei różne dla C i C++. Posługiwać się będziemy raczej
tymi zdefiniowanymi w C++, choć znajomość wersji z języka C jest
bardzo przydatna, choćby przy czytaniu istniejących kodów.
Język C++ można pod wieloma względami traktować jak nadzbiór
klasycznego języka C. W zasadzie, być może po kilku niewielkich
zabiegach kosmetycznych, każdy program w C da się skompilować
za pomocą kompilatora C++. Oczywiście odwrotnie nie jest to zwykle
możliwe. To, czego będziemy się uczyć, to język C++; czasem
jednak warto wspomnieć o klasycznych konstrukcjach z C, choćby
dlatego, że stosuje się je często również w kodzie napisanym
zasadniczo w C++, jak i dlatego, że konstrukcje te bywają w pewnych
sytuacjach bardziej efektywne.
Pliki źródłowe zawierające kod C++ mają tradycyjnie rozszerzenie
'
.cpp', ale spotyka się też rozszerzenia
'
.C' i inne. Pliki z kodem w czystym C mają
zwyczajowo rozszerzenie '
.c'. Specjalny rodzaj
plików, tzw. pliki nagłówkowe, mają w C i C++ rozszerzenie
'
.h' lub, w C++, w ogóle nie mają rozszerzenia.
Rozpatrzmy zatem plik
helloWorld.cpp
zawierający
zasłużony program
Hello, World
(B. Kernighan, 1973) w języku C++:
P2:
helloWorld.cpp
'Hello, World' w C++
1. #include <iostream> ➊
2. using namespace std; ➋
3.
4. /*
5. Naukę każdego języka programowania
6. zaczynamy zawsze od Hello, World!!!!
7. */
8.
9. int main() { // Komentarze jak w Javie
10. cout << "Hello, World!" << endl; ➌
11. }
Jak łatwo się domyślić, uruchomienie tego programu powinno
spowodować wypisanie na ekranie słów 'Hello, World!'.
Znający Javę
zauważą, że struktura
tego programu jest różna od analogicznego programu w tym języku.
Pierwsza rzucająca się w oczy różnica to fakt, że nie występują
tu w ogóle klasy, bez których w Javie nie da się napisać żadnego
programu. W szczególności, funkcja
main
nie jest zawarta
w żadnej klasie: jest funkcją
globalną.
Ogólnie, program w C++ składa się z jednego lub kilku (zapisanych
w osobnych plikach) modułów.
Każdy moduł może zawierać dyrektywy preprocesora
(jeśli są potrzebne), deklaracje i/lub definicje zmiennych i funkcji
oraz, oczywiście, komentarze. Dyrektywy preprocesora poprzedzone
są znakiem ”, jak dyrektywa
#include <iostream>
w powyższym kodzie, i nie są przekazywane kompilatorowi —
służą jedynie do wstępnego przetworzenia tekstu programu.
Zadanie to wykonuje wspomniany już preprocesor.
Żadnej linii rozpoczynającej się od znaku
” kompilator w ogóle nie zobaczy!
Dokładnie jeden moduł musi zawierać funkcję o nazwie
main.
Wykonanie programu polega zasadniczo na wykonaniu
tej właśnie funkcji. Legalnym programem jest więc np.
int main() {
return 0;
}
lub nawet
int main(){}
Zajmijmy się jednak programem z pliku
helloWorld.cpp.
Na jego przykładzie przyjrzymy się podstawowym elementom
programu w C++:
- #include... (➊)
-
Włączenie do programu zawartości
pliku
iostream
(plik włączany w ten sposób nazywamy
plikiem nagłówkowym,
ang. header file). Dzięki temu w programie dostępne
będą definicje (lub tylko deklaracje – więcej o tym
powiemy później) narzędzia (w postaci najrozmaitszych klas,
funkcji, stałych itd.), służące do wykonywania
operacji wejścia/wyjścia (w skrócie, operacje we/wy),
a więc np. wczytywania z konsoli i wypisywania
na ekranie danych. Zauważmy, że instrukcja
#include
powoduje rzeczywiste włączenie pliku: równie dobrze
moglibyśmy w tym miejscu wpisać jego treść bezpośrednio do
naszego programu. Jest to więc zupełnie co innego niż
instrukcja
import
w Javie, gdzie chodzi
raczej o rozszerzenie przeszukiwanej przestrzeni nazw.
Sam plik
iostream
znajduje się w
znanym kompilatorowi katalogu: użycie nawiasów kątowych
(<...>) oznacza właśnie, że nie jest to nasz własny
plik, ale plik ze znanego kompilatorowi specjalnego katalogu
bibliotecznego, dostarczonego zwykle wraz z kompilatorem przez
producenta. Gdyby chodziło o dołączenie naszego własnego pliku,
co też jest możliwe, użylibyśmy podobnej instrukcji, ale zamiast
nawiasów kątowych umieścilibyśmy cudzysłowy. Tak więc
#include <bib.h> włącza standardowy plik
nagłówkowy o nazwie
bib.h
(dostarczany zwykle wraz
z kompilatorem), natomiast podobna dyrektywa
#include "bib.h" dołączyłaby plik
nagłówkowy
bib.h
z katalogu bieżącego, a tylko jeśli
w katalogu tym takiego pliku by nie było, poszukiwany byłby
w katalogu standardowym. Zauważmy, że instrukcja
#include nie jest przeznaczona dla
kompilatora. Włączenie pliku wykonywane jest przez
preprocesor, który zajmuje się
wyłącznie przetwarzaniem tekstu naszego programu
przed jego właściwą kompilacją —
to, co zobaczy kompilator, to tekst naszego programu
przetworzony przez preprocesor. Inne przydatne instrukcje
(dyrektywy) preprocesora
poznamy w jednym z następnych rozdziałów.
- using namespace std; (➋)
- Linia ta oznacza, że nazw (klas, stałych,
funkcji) niezdefiniowanych w naszym programie należy
szukać w przestrzeni nazw
std. W tej
przestrzeni nazw znajdują się właśnie nazwy obiektów dostarczone
przez dyrektywę preprocesora
#include <iostream>: jak widać, samo
dołączenie pliku
iostream
nie wystarcza — obiekty
tam zdefiniowane są „zamknięte” w przestrzeni nazw
std.
Dyrektywy
using
moglibyśmy tu nie zastosować,
ale wtedy musielibyśmy pisać na przykład
std::cout,
std::endl zamiast po prostu
cout
i
endl.
O przestrzeniach nazw powiemy więcej
w rozdziale o przestrzeniach nazw .
- Komentarze (linie 4-7 i 9)
- Komentarze mogą być wieloliniowe,
jeśli ograniczone są dwuznakami '
/*'
i '
*/', lub jednoliniowe: od dwuznaku '
//'
włącznie do końca bieżącej linii. Komentarzy w pierwszej
z tych form nie można zagnieżdżać (choć
niektóre kompilatory na to pozwalają). Komentarze zostaną
z tekstu programu wycięte (i zastąpione jednym znakiem odstępu)
już na wstępnym etapie przetwarzania i nie mają żadnego znaczenia
na dalszych etapach kompilacji.
- Funkcja
main
(linie 9-11)
- Funkcja
main
musi zwracać („obliczać”) wartość typu
int
(czyli liczbę całkowitą), co zaznaczamy pisząc
nazwę typu
int
przed nazwą funkcji.
Zauważmy, że
main
jest zawsze funkcją
globalną,
tzn. nie
jest i nie może być zanurzona w żadnej klasie (jak funkcja
o tej samej nazwie i podobnym przeznaczeniu w Javie).
Wartość typu
int
zwracana jest do systemu
i zwyczajowo powinna wynosić 0,
jeśli program kończy się pomyślnie. Niezerowa wartość
zwrócona oznacza zwykle niepowodzenie, którego naturę można
zakodować tą wartością i wykorzystać na przykład
w skrypcie powłoki, z którego uruchamialiśmy nasz program.
Jeśli zwracamy z funkcji
main
wartość niezerową,
to powinna to być wartość dodatnia mniejsza od 256
(system może traktować w specjalny sposób wartości ujemne).
Normalnie, jeśli funkcja deklaruje, że zwraca wartość
(czyli jest funkcją rezultatową), to musi to zrobić
(za pomocą instrukcji
return).
Pod tym względem
main
jest wyjątkiem: jeśli
nie ma w treści instrukcji
return, kompilator
doda ją sam.
Funkcja
main
pełni rolę
punktu wejścia do programu; wyjście sterowania z tej
funkcji powoduje zakończenie programu, choć, jak się
przekonamy, nie natychmiastowe — najpierw „zwijany”
jest stos (nie mówimy tu o programach wielowątkowych,
które zachowują się nieco inaczej).
Funkcja
main
może
mieć parametry, poprzez które system
przekazuje do programu argumenty wywołania. W C++ pierwszym
z tych argumentów, o numerze zero, jest zawsze nazwa samego
wywoływanego programu. Przekazywanie argumentów wywołania
wyjaśnimy bliżej niebawem.
Jeśli nie zamierzamy korzystać z argumentów wywołania, to
funkcję
main
można zadeklarować z pustą listą
parametrów (ale nawiasy są wymagane).
Niektórzy uważają, że lepszym stylem, którego należy się
trzymać nie tylko w odniesieniu do funkcji
main,
jest jawne wskazanie w takich przypadkach, że lista
parametrów jest pusta, poprzez użycie słowa kluczowego
void: '
int main(void)...'.
- Blokowanie (linie 9-11)
- Ujęcie kilku instrukcji w nawiasy klamrowe
('{' and '}')
powoduje, że
cały ten blok może być traktowany jako jedna
instrukcja wszędzie tam, gdzie składnia języka wymaga
pojedynczej instrukcji, a chcielibyśmy umieścić ich tam
wiele (nazywamy to instrukcją złożoną).
Blokowanie instrukcji ma też wpływ na widoczność
i zasięg zmiennych, o czym powiemy w dalszym ciągu.
Ciało (treść) funkcji ma formę instrukcji złożonej,
a zatem ujęte jest w nawiasy klamrowe i następuje po
nagłówku funkcji określającym, między innymi, typ
wartości zwracanej oraz liczbę i typ parametrów funkcji.
- Instrukcje (➌)
- Jest to jedyna instrukcja tego programu. Jak widzimy, instrukcje
kończą się średnikiem. Format zapisu programu jest
„wolny”; oznacza to, że
dowolna niepusta sekwencja białych znaków, włączając
znak nowej linii, jest równoważna jednemu odstępowi.
Pozwala to na pewną swobodę w zapisie programu: ze swobody
tej należy korzystać, aby pisany kod był jak najbardziej
czytelny (oczywiście żadnych spacji nie może być wewnątrz
słół kluczowych i nazw).
Ta konkretna linia powoduje wyprowadzenie na ekran napisu
podanego w cudzysłowach. Wykorzystany jest tu mechanizm
wyprowadzania danych właściwy dla C++ (a nieobecny w C).
Właśnie po to, by móc użyć tego mechanizmu, musieliśmy
w linii pierwszej dołączyć plik nagłówkowy
iostream.
Operacje wejścia i wyjścia, a więc wprowadzania danych,
na przykład z klawiatury, i ich wyprowadzania, na przykład
na ekran komputera, to temat długi i dość zagmatwany
(zresztą nie tylko w C/C++). Zajmiemy się nim w swoim czasie
(rozdział o operacjach we/wy )
bardziej szczegółowo, a poniżej zamieścimy parę
wstępnych informacji na ten temat, aby móc używać
podstawowych operacji we/wy w naszych programach.
Zauważmy, że w C++, jako języku w zasadzie proceduralnym,
instrukcje wykonywane są w takiej kolejności w jakiej pojawiają się
w programie (choć, jak się przekonamy, powtórzenia i skoki są możliwe).
Zanim przejdziemy do bardziej szczegółowego omówienia tych
i innych elementów programu w C++, kilka uwag wstępnych na temat
wprowadzania i wyprowadzania danych.
T.R. Werner, 21 lutego 2016; 20:17