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 coutendl.
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, 25 lutego 2017; 22:31