PROGRAMOWANIE – PRZYKŁADY
IDE C++ WinBGI C# Wpadki

Pierwszy program C++

Ile cyfr ma liczba? Algorytm Program w Borland C++ Program w MinGW C++ Przestrzenie nazw Program w Visual C++ Przenośność kodu Następny przykład Program w Visual C# Kontakt

Ile cyfr ma liczba?

Zazwyczaj w podręcznikach i kursach programowania pierwszy program wypisuje na monitorze krótki tekst powitania, jak np. Witaj świecie!. W naszym przypadku zadaniem pierwszego programu będzie wykonanie prostych obliczeń. Dokładniej, dla wczytanej z klawiatury wartości całkowitej ma on wyznaczyć liczbę jej dziesiętnych cyfr znaczących i wypisać wynik na monitorze. Wczytywana liczba może być ujemna, równa zero lub dodatnia.

Algorytm

Do obliczeń potrzebne są dwie zmienne typu całkowitego, nazwijmy je nk. Pierwsza przyjmuje na początku wartość wczytaną z klawiatury, zaś druga wartość zero. Algorytm, jaki się narzuca, polega na powtarzaniu kroku obejmu­jącego dwie operacje:

Każde takie dzielenie jest równoważne usunięciu ostatniej cyfry z dziesiętnego zapisu wartości n. Zatem po każdym kroku złożonego z tych dwóch operacji wartością k jest liczba usuniętych do tej pory cyfr n. Ten proces iteracyjny, nazywany pętlą do—while (wykonuj... dopóki...) lub krócej pętlą do, jest wykonywany dopóty, dopóki jest spełniony warunek konty­nuacji pętli, czyli dopóki n jest różne od zera. Algorytm ten można łatwo zapisać w postaci instrukcji języka C i C++ (z komentarzami w stylu C++):

k = 0;
do
{
    k++;              // k = k + 1;
    n /= 10;          // n = n / 10;
} while (n != 0);

Zerowa wartość n oznacza, że warunek konty­nuacji pętli nie jest już dłużej spełniany, wobec tego pętla zostaje zakończona. Wtedy wszystkie cyfry dziesiętne wczytanej na początku wartości n są już usunięte, a ich liczba jest równa wartości k. Gwoli wyjaśnienia, w dwóch komentarzach C++ (tekst zaczynający się od znaków //) podane jest znaczenie operatorów ++ (zwiększ o 1) i /= (podziel przez).

Zamiat pętli do, w której warunek kontunuacji sprawdzany jest po każdym kroku, można użyć pętli while (dopóki... wykonuj...), w której warunek konty­nuacji sprawdzany jest przed każdym krokiem. Konse­kwencją tej zamiany kolejności jest mniejsza o 1 liczba powtórzeń w pętli while, dlatego wstępnie zmiennej k przypisana zostaje wartość 1 zamiast 0. Ponadto wyrażenie reprezen­tujące operację dzielenia n przez 10 trzeba umieścić w warunku konty­nuacji pętli, ponieważ powinna ona poprzedzać porównanie nowej wartości n z zerem. Zatem nieco inny algorytm wiodący do tego samego rozwiązania można wyrazić w C i C++ następująco:

k = 1;
while ((n /= 10) != 0)
{
    k++;
}

Warunek kontynuacji pętli można skrócić do postaci n/=10, gdyż w językach C i C++ wartość niezerowa oznacza warunek spełniony, zaś zero – niespełniony. Można również pominąć nawiasy {}, gdy obejmują jedną instrukcję. Prowadzi to do kodu:

k = 1;
while (n /= 10)
    k++;

Powyższą iterację można przedstawić w językach C i C++ za pomocą instrukcji for, która pozwala na tworzenie bardzo uniwer­salnych, a jednocześnie wydajnych pętli. Jej składnia jest następująca:

for (wyrażenie1 ; wyrażenie2 ; wyrażenie3)
    instrukcja

Pierwsze wyrażenie, zwane inicjującym, i trzecie, zwane modyfikującym, jest zazwyczaj przypisaniem lub wywołaniem funkcji, drugie jest wyrażeniem warunkowym. Dowolne z nich można opuścić, średnik musi jednak pozostać. Wyrażenie inicjujące jest obliczane tylko raz – przed rozpo­częciem pętli, wyrażenie warunkowe – przed każdym krokiem pętli, modyfi­kujące – po każdym kroku. Pętla jest kończona, gdy warunek nie jest spełniony. Przedsta­wiony wyżej kod można zapisać w zwartej postaci:

for (k = 1; n /= 10; k++)
    ;

Kolejne wyrażenia tej instrukcji odpowiadają operacjom:

Wszystkie potrzebne obliczenia są już zawarte w tych wyrażeniach, dlatego pętli for podpo­rządkowana jest instrukcja pusta – samotny średnik. Można by go zapisać tuż za nawiasem kończącym zapis pętli, lecz wyodrę­bnienie go zwiększa czytelność kodu.

Program w Borland C++

Omówiony wyżej prosty algorytm można bezpośrednio zamieścić w kodzie źródłowym programu w języku C lub C++, ale zanim to nastąpi, trzeba najpierw utworzyć projekt programu. Projekt wskazuje kompila­torowi pliki, które mają być skompilowane i połączone w jeden program wynikowy. Oprócz plików źródłowych projekt może obejmować inne rodzaje plików, jak np. pliki binarne utworzone wcześniej w wyniku kompilacji plików źródłowych lub biblioteki przydatnych funkcji i innych gotowych do wykorzystania elementów. Po uruchomieniu środowiska Relo 2.0 z kompilatorem Borland C++ 5.5 przystę­pujemy do utworzenia projektu naszego pierwszego programu:

1 Otwieramy okno kreatora nowego projektu za pomocą polecenia File New, a następnie na zakładce Projects tego okna zaznaczamy ikonę Console Application (rys.) i naciskamy przycisk Next.
2 W polu Project Name kolejnego okna (rys.) wpisujemy nazwę nowego projektu, np. Lcyfr1, i naciskamy przycisk Create.
3 W tym momencie został utworzony projekt programu, którego kod źródłowy (szablon aplikacji konsolowej) jest pokazany w oknie edytora (rys.). Kod ten można edytować, kompilować i testować. Dla utrzymania porządku na dysku dobrze jest już teraz zapisać wszystko w odpowiednim miejscu, wybierając polecenie File Save All. W rozpatrywanym przypadku utworzony folder projektu zawiera na razie tylko dwa pliki:
  • Lcyfr1.fde – plik tekstowy projektu,
  • Lcyfr1.cpp – kod żródłowy programu.
Szablon aplikacji konsolowej stanowi treść pliku tekstowego console.cpp znajdującego się w folderze config/templates środowiska Relo, więc można go wcześniej zmienić, by był bardziej użyteczny. Na przykład można usunąć zazwyczaj zbędne argumenty argcargv.
4 Ten program można nawet skompilować za pomocą polecenia Project Make. Pojawia się wówczas okienko Compile (rys.) informujące, że kod źródłowy jest bezbłędny, ale kompilator ma do niego dwa zastrzeżenia. W dolnej części okna głównego podane jest wyjaśnienie: w funkcji main występują argumenty argcargv, które nie są używane. W trakcie kompilacji w folderze projektu programu utworzone zostały nowe trzy pliki, z których najważniejszy jest Lcyfr1.exe – kod wynikowy programu.
5 Program wynikowy można uruchomić, wybierając polecenie Project Run. Wtedy typowe okno aplikacji konsolowej z czarnym tłem (rys.) zawiera jedynie migający kursor. Dzieje się tak dlatego, że funkcja getchar z biblioteki standardowej stdio języka C czeka na naciśnięcie klawisza na klawiaturze. W momencie naciśnięcia klawisza odpowia­dający mu znak zostanie przesłany do programu, który kończy działanie, bo instrukcja return kończy wykonanie jego funkcji głównej main.

Udogodnieniem w środowisku Relo jest pasek narzędziowy z najczęściej stosowanymi poleceniami i skróty klawiszowe.

Najwyższy czas przystąpić do edycji kodu źródłowego programu. Program ma tylko jedną funkcję o nazwie main, z której usuwamy zbędne argumenty argcargv. Funkcja jest typu int, co oznacza, że zwraca wartości całkowite. Uściślając, wartość zwrotną funkcji określa instrukcja return zawarta w nawiasach klamrowych {}, zatem jest nią 0. Przyjęło się, że wartość 0 funkcji main oznacza pomyślne wykonanie programu, a każda inna wskazuje na jakiś problem. Informacja o tym, jak zakończył się program, nazywana kodem powrotu programu, może być wyko­rzystana do współpracy z innymi programami. Notabene, w Borland 5.5 funkcja main może nie zwracać żadnej wartości, czyli być typu nieokreślo­nego void, ale z uwagi na nowszą specyfikację języka C++ pozosta­jemy przy wymaganiu, że jest ona typu int.

Rozbudowę kodu źródłowego rozpoczynamy od umieszczenia na początku funkcji main definicji dwóch zmiennych nk typu int oraz opraco­wanego wyżej kodu algorytmu. Ponadto wstawiamy operacje wejścia-wyjścia, gdyż program przed obliczeniami wczytuje wartość n (pobiera daną) z klawiatury, a po wykonaniu obliczeń wypisuje wartość k (wypro­wadza wynik) do okna konsoli.

Dyrektywa #include informuje, że do projektu zostaje włączony plik nagłówkowy stdio.h, który zawiera deklaracje stanowiące interfejs do funkcji standar­dowych wejścia-wyjścia języka C, m.in. do funkcji wejścia scanf i do funkcji wyjścia printf. Użyjemy zatem funkcji scanf do wczytania liczby n i funkcji printf do wypisania wyniku k. Zastąpimy też funkcję getchar funkcją getch (na forach interne­towych można znaleźć liczne bezowocne dyskusje nt. wyższości jednej funkcji nad drugą), a właściwie jej nowszym odpowie­dnikiem _getch (w Borland 5.5 jest to ta sama funkcja, ale w nowszych środowiskach progra­mowania tak nie jest i funkcja getch jest traktowana jako przesta­rzała). Ta zamiana wymaga włączenia do projektu pliku nagłówkowego conio.h. Oto kod powstałego programu:

#include <stdio.h>
#include <conio.h>

int main()
{
    int n, k;
    printf("Liczba calkowita: ");
    scanf("%d", &n);
    for (k = 1; n /= 10; k++)
        ;
    printf("Cyfr: %d\n", k);
    _getch();
    return 0;
}

Funkcja scanf wymaga co najmniej dwóch argumentów. Pierwszy jest łańcuchem (ciągiem znaków pomiędzy cudzysłowami) zawierającym symbole formatowania precyzujące typ wczytywanych danych, zaś kolejne są adresami (wskaźnikami do) obszarów pamięci, w których te dane mają być zapamiętane. W rozpatrywanym przypadku symbol %d oznacza, że ma być wczytana liczba całkowita dziesiętna (int), a &n, że po konwersji na postać binarną ma być ona zapamiętana w obszarze pamięci przydzie­lonym zmiennej n. Najczę­ściej używa się następu­jących symboli forma­towania:

Z kolei funkcja printf wymaga co najmniej jednego argumentu. Pierwszy jest zawsze łańcuchem, który zawiera symbole formatowania, gdy występują dalsze argumenty. Każdy symbol wskazuje na format, w jakim ma być wyprowa­dzona kolejna wartość. Po znaku % mogą wystąpić dodatkowe znaki uściślające sposób formatowania. Na przykład symbol %3d określa, że wartość typu int ma być wyprowa­dzona do pola o szerokości 3 znaków, zaś %8.4lf, że wartość typu double ma być wyprowa­dzona do pola o szerokości 8 znaków, z których 4 mają być przeznaczone na zapis części ułamkowej.

W powyższym programie pierwsza funkcja printf wypisuje jedynie tekst Liczba calkowita i dwukropek. Gdyby jej nie było, użytkownik byłby zagubiony, gdyż widziałby jedynie migający kursor i nie wiedziałby, na co komputer oczekuje. Druga funkcja printf wypisuje wynik obliczeń poprze­dzony krótkim tekstem, po czym przechodzi na początek następnego wiersza (\n jest znakiem nowego wiersza).

Częstym źródłem problemów w wywołaniach funkcji printfscanf jest niewłaściwy dobór symboli formato­wania do typów przesyłanych danych, np. użycie %f zamiast %lf w celu pobrania wartości typu double przez funkcję scanf, a szczególnie pospolitym błędem jest pominięcie znaku & w parametrze wywołania tej funkcji, co kończy się przesłaniem wczytanej wartości w nieodpowiednie miejsce pamięci lub próbą uzyskania dostępu do obszaru pamięci niedostę­pnego dla programu. Dlatego w drugiej wersji programu wyznacza­jącego liczbę cyfr dziesiętnych danej liczby całkowitej wykorzystamy bardziej intuicyjne i mniej podatne na błędy obiektowe wejście-wyjście języka C++ zamiast tradycyjnego C. Aby zachować pierwszą wersję programu, tworzymy nowy projekt wersji drugiej na bazie pierwszej:

1 Jeśli projekt programu Lcyfr1 nie jest otwarty, wybieramy polecenie File Open Projekt, a następnie, nawigując w razie potrzeby między folderami, znajdujemy ikonę pliku Lcyfr1.fde i klikamy na niej lewym przyciskiem myszy.
2 Wybieramy polecenie File Save Project As... i po utworzeniu odpowiedniego folderu lub znalezieniu przygoto­wanego wcześniej zapisujemy projekt pod inną nazwą, np. Lcyfr2.fde.
3 Za pomocą polecenia File Save As... zapisujemy kod źródłowy programu np. pod nazwą Lcyfr2.cpp. W folderze powinny się wówczas znajdować dwa pliki:
  • Lcyfr2.fde – plik tekstowy projektu,
  • Lcyfr2.cpp – kod żródłowy programu.

Edycję rozpoczynamy od zamiany nazwy pliku nagłówko­wego stdio.h w dyrektywie #include na iostream.h, która teraz informuje, że do projektu programu zostają włączone deklaracje standar­dowych strumieni wejścia-wyjścia języka C++. Strumień cout (console output) jest obiektem wyświetla­jącym informację na ekranie (w oknie konsoli), strumień cin (console input) obiektem wczytującym informację wpisaną na klawiaturze, a operatory >><< określają kierunek przesyłania danych. Zamiast znaku nowego wiersza, oznaczanego sekwencją specjalną \n, można używać manipulatora endl (end of line). Po zastoso­waniu strumie­niowego wejścia-wyjścia C++ nawet tak prosty kod programu zyskuje na czytelności i zrozumieniu:

#include <iostream.h>
#include <conio.h>

int main()
{
    int n, k;
    cout << "Liczba calkowita: ";
    cin >> n;
    for (k = 1; n /= 10; k++)
        ;
    cout << "Cyfr: " << k << endl;
    _getch();
    return 0;
}

Wywołanie funkcji _getch w powyższym kodzie można zastąpić instrukcją

system("PAUSE");

która również wstrzymuje wykonanie programu do momentu naciśnięcia klawisza na klawiaturze, ale dodatkowo wypisuje komunikat Press any key to continue... (naciśnij dowolny klawisz, aby kontynuować). Dyrektywę #include włączającą do projektu plik nagłówkowy conio.h można wówczas usunąć, gdyż funkcja system jest zdefiniowana w bibliotece iostream. Jeżeli program wykonawczy jest uruchamiany bezpośre­dnio z okna konsoli za pomocą polecenia DOS, to żadna z tych funkcji nie jest potrzebna. Do okna aplikacji konsolowej można przejść bezpośrednio ze środowiska, wywołując z menu Tools polecenie Command Prompt. Ma ono wtedy tło białe zamiast czarnego (rys.).

Program w MinGW C++

Podobnie jak poprzednio, po załadowaniu środowiska Code::Blocks z kompilatorem MinGW C++ rozpoczynamy pisanie nowego programu od utworzenia jego projektu:

1 Przywołujemy kreatora nowego projektu za pomocą polecenia File New Project..., a następnie w oknie New from template zaznaczamy ikonę Console application (rys.) i naciskamy przycisk Go.
2 W następnym oknie (rys.) możemy wybrać język programo­wania C lub C++. Pozostajemy przy drugiej opcji, naciskając przycisk Next.
3 W kolejnym oknie podajemy tytuł projektu, jego lokalizację i nazwę pliku. W rozpatrywanym przykładzie tytułem projektu ma być Lcyfr1, a jego folderem C:\Cpp\MCpp\Lcyfr\. Plik projektu będzie miał nazwę Lcyfr1.cbp (Code Blocks Project). W czwartym polu generowana jest przez kreatora projektów pełna ścieżka dostępu do pliku projektu (można ją modyfikować). Naciskamy przycisk Next, aby przejść dalej.
4 Ostatnie okno z grupy Console application dotyczy wyboru trybu kompilacji projektu (rys.). W trybie Debug do programu wynikowego włączane są dodatkowe narzędzia ułatwiające śledzenie jego wykonania i wykrywanie błędów (wersja testowa programu), zaś w trybie Release włączane są tylko narzędzia podstawowe (wersja osta­teczna programu przeznaczona dla użytkownika końcowego). Pozosta­wienie ustawień domyślnych umożliwia kompilację zarówno wersji Debug, jak i Release. Naciskamy przycisk Finish, aby utworzyć projekt.
5 W tym momencie został utworzony folder projektu, w którym został zapisany plik projektu i plik z szablonowym kodem źródłowym aplikacji konsolowej. W rozpatrywanym przypadku folderem projektu jest C:\Cpp\Mcpp\Lcyfr\Lcyfr1, a plikami:
  • Lcyfr1.cbp – plik tekstowy projektu,
  • main.cpp – kod źródłowy programu.
Jeżeli drzewo plików znajdujące się w lewym oknie nie jest rozwinięte, klikamy na ikonce + (plus), aby je rozwinąć, i podwójnie na nazwie pliku main.cpp, aby go otworzyć w oknie edytora (rys.).
6 Program kompilujemy, wybierając polecenie Build Build. Kompilacja kończy się pomyślnie, bez żadnych uwag kompilatora. Świadczy o tym informacja podana w dole okna głównego (rys.). W trakcie kompilacji w folderze projektu utworzone zostały nowe foldery i pliki. Program wynikowy Lcyfr1.exe znajduje się w podfolderze bin\Debug, gdyż kompilacja była prowadzona w trybie Debug (zob. pasek narzędziowy okna, p. 5). Po przełą­czeniu trybu na Release i ponownej kompilacji pojawia się wyraźnie mniejsza objęto­ściowo druga wersja programu wynikowego Lcyfr1.exe w podfolderze bin\Release.
7 Program wynikowy uruchamiamy za pomocą polecenia Build Run. Wtedy pojawia się okno aplikacji konsolowej (rys.) z napisem Hello world! (Witaj świecie!) wyprowadzonym przez strumień cout. Okno się jednak nie zamyka, pomimo że następną instrukcją jest return, która kończy wykonanie programu. Dzieje się tak dlatego, że środowisko Code::Blocks, urucha­miając program konsolowy we własnej konsoli, zatrzymuje ją po zakończeniu programu i dodatkowo wypisuje kod jego powrotu, czas wykonania i komunikat Press any key to continue (Naciśnij dowolny klawisz, aby kontynuować).

Udogodnieniem w środowisku Code::Blocks jest pasek narzędziowy z najczęściej stosowanymi polece­niami i skróty klawiszowe.

Kod źródłowy programu wyznacza­jącego liczbę cyfr danej liczby całkowitej może być w przypadku standardowego w C wejścia-wyjścia taki sam dla kompilatora MinGW C++ jak dla Borland C++. Warto jednak uwzględnić dwie modyfikacje. Po pierwsze, wskazana jest zrezygnacja z funkcji _getch i biblioteki conio, ponieważ środowisko Code::Block samo wstrzymuje zamknięcie okna aplikacji konsolowej po zakończeniu jej działania. Po drugie, można znacznie rozszerzyć zakres wczytywanej wartości, korzystając z nowszego standardu języka C++ określa­jącego nowy typ całkowity long long (zob. tabelę typów standar­dowych C++). Symbolem formato­wania liczb tego typu jest %I64d. Pierwsza wersja rozpatry­wanego programu ma więc następującą postać:

#include <stdio.h>

int main()
{
    long long n;
    int k;
    printf("Liczba calkowita: ");
    scanf("%I64d", &n);
    for (k = 1; n /= 10; k++)
        ;
    printf("Cyfr: %d\n", k);
    return 0;
}

Projekt drugiej wersji programu korzysta­jącego ze standar­dowych w C++ strumieni wejścia-wyjścia cincout najprościej jest utworzyć od podstaw, gdyż szablon aplikacji konsolowej uwzględnia biblio­tekę iostream. Wzorując się na drugiej wersji programu napisanego dla kompila­tora Borland C++, łatwo dochodzimy do kodu źródłowego drugiej wersji tego programu dla kompila­tora MinGW C++:

#include <iostream>

using namespace std;

int main()
{
    long long n;
    int k;
    cout << "Liczba calkowita: ";
    cin >> n;
    for (k = 1; n /= 10; k++)
        ;
    cout << "Cyfr: " << k << endl;
    return 0;
}

Przestrzenie nazw

Nie wdając się w szczegóły, język C++ można traktować jako nadzbiór języka C wzbogacony o wiele udogo­dnień, m.in. o klasy umożliwiające programowanie obiektowe. Nieco inne nazewnictwo pliku nagłówkowego włączanego do programu za pomocą dyrektywy #include (brak rozsze­rzenia .h) ma na celu wskazanie, że jest to biblio­teka przezna­czona dla języka C++. Z kolei dyrektywa using orzeka, że program używa przestrzeni nazw (name space) std.

Przestrzenie nazw zapobie­gają kolizji nazw poprzez ograniczanie zasięgu ich widoczności. Gdyby na przykład w natłoku licznych plików nagłówko­wych włączanych do projektu dwie funkcje miały tę samą nazwę, kompilator nie byłby w stanie rozstrzygnąć, która funkcja ma być wywołana. Rozwiązaniem jest zdefiniowanie tych funkcji w różnych przestrze­niach nazw. Aby taką funkcję wywołać, należy podać nie tylko jej nazwę, lecz także nazwę przestrzeni, w której ta funkcja została zdefi­niowana, np. poprzez użycie tzw. nazwy kwalifikowanej

nazwa_przestrzeni::nazwa_funkcji

Narzędzia biblioteki standardowej iostream, takie jak strumień wejściowy cin, strumień wyjściowy cout i manipulator endl, są zdefi­niowane w przestrzeni nazw std. Aby je używać, można korzystać z ich nazw kwalifikowanych: std::cin, std::coutstd::endl. Innym sposobem jest umieszczenie dyrektywy

using namespace std;

która określa, że w dalszej części kodu można odwoływać się do wszystkich nazw przestrzeni std bezpo­średnio. W rozpatrywanym przypadku dyrektywa znajduje się na początku, więc obejmuje cały kod programu, ale można ją umieszczać wewnątrz funkcji, wtedy ma zasięg lokalny. Można również deklarować użycie wybranej nazwy bez jej kwalifi­kowania, np.:

using std::cin;
cin >> n;

Program w Visual C++

Visual Studio Community 2017 pozwala tworzyć programy z przeznaczeniem dla kilku platform w kilku językach progra­mowania, w tym w C++ i C#. Aby utworzyć projekt nowego programu w C++ dla platformy Windows, po urucho­mieniu środowiska postępujemy następująco:

1 Uruchamiamy kreatora nowego projektu za pomocą polecenia Plik Nowy Projekt... (alternatywnie możemy skorzystać z łącza Utwórz nowy projekt... w oknie startowym środowiska). Po ukazaniu się okna Nowy projekt dokonujemy następu­jących ustawień (rys.):
  • w lewym panelu zaznaczamy opcję Visual C++,
  • w części centralnej okna zaznaczamy opcję Aplikacja konsolowa systemu Windows,
  • w pierwszym polu na dole okna wpisujemy nazwę projektu,
  • w drugim polu wybieramy lub wpisujemy lokalizację rozwiązania (folder nadrzędny),
  • w trzecim polu zmieniamy nazwę rozwiązania, gdy ma być inna niż nazwa projektu.
Rozwiązanie może obejmować jeden lub większą liczbę projektów. W rozpatrywanym przypadku projekt Lcyfr1 będzie na razie jedynym projektem rozwiązania Lcyfr, dla którego zostanie utworzony podfolder w folderze C:\Cpp\VCpp. Aby utworzyć projekt (i rozwiązanie, bo go jeszcze nie ma), naciskamy przycisk OK.
2 W tym momencie został utworzony folder rozwiązania zawierający szereg podfolderów i plików, a w oknie edytora został pokazany szablonowy kod źródłowy programu (rys.). W rozpatrywanym przypadku rozwiązanie zajmuje folder C:\Cpp\VCpp\Lcyfr, w którym m.in. znajdują się:
  • Lcyfr1.sin – plik rozwiązania zawierający podstawowe informacje o projekcie,
  • Lcyfr1 – folder projektu,
  • Lcyfr1.vcxproj – plik projektu (w folderze Lcyfr1),
  • Lcyfr1.cpp – kod źródłowy programu (w folderze Lcyfr1).
Pliki Lcyfr1.sinLcyfr1.vcxproj mogą później posłużyć do otwarcia zapisanego projektu. Mianowicie, po wybraniu polecenia Plik Otwórz Projekt/rozwiązanie... znajdujemy nazwę któregokolwiek z nich i klikamy na niej dwukrotnie lewym przyciskiem myszy. Alternatywnie możemy otworzyć projekt, korzystając z listy ostatnio używanych projektów w oknie startowym środowiska.
Uwaga: Zaktualizowana wersja Visual Studio zamiast trzech plików stdafx.h, stdafx.cpptargetver.h tworzy dwa pliki pch.hpch.cpp, a także plik z funkcją main o nieco innej zawartości.
3 Plik Lcyfr1.cpp nie jest jedynym, który zawiera kod źródłowy programu. Hierar­chiczna struktura aplikacji w oknie eksplo­ratora rozwiązań (rys.) pokazuje, że część kodu znajduje się w trzech (lub dwóch) dodatkowych plikach. Dwa z nich, stdafx.hstdafx.cpp (lub pch.hpch.cpp), służą do tworzenia i dołączania do projektów tzw. prekompi­lowanego nagłówka (ang. Precompiled Header). W pliku stdafx.h umieszcza się dyrektywy #include włączające nagłówki plików często używanych, ale rzadko zmienianych, co pozwala na wstępną ich kompilację i wielo­krotne włączanie raz skompi­lowanego kodu do projektów, a tym samym znaczne skrócenie czasu kompilacji dużych projektów. Prekompi­lowany nagłówek może zawierać również inne elementy kodu typowe dla plików nagłówko­wych, jak np. deklaracje zmiennych, funkcji i klas. W przypadku małych projektów użyte­czność obu plików jest wątpliwa, można je więc usunąć, zmieniając ustawienia właściwości projektu. Trzeci plik o nazwie targetver.h określa najwyższą dostępną platformę Windows, dla której tworzona jest aplikacja. Jeśli istnieje, można go zmodyfi­kować, by skompi­lować aplikację dla wcześniej­szej platformy, można go również usunąć z projektu.
4 Przechodzimy do ustawień właściwości projektu, wybierając polecenie Projekt Właściwości... (ostatnia pozycja). Rozwijamy w lewym panelu okna węzeł C/C++ i wybieramy zakładkę Prekompi­lowane nagłówki, a następnie w części centralnej okna wybieramy pozycję Prekompi­lowany nagłówek, rozwijamy strzałką w dół listę dostępnych opcji i wybieramy z niej Prekompi­lowane nagłówki nie są używane (rys.). Dalej, przecho­dzimy do wiersza Prekompi­lowany plik nagłówka i usuwamy tekst stdafx.h (lub pch.h). Potwier­dzamy ustawienia, naciskając przycisk Zastosuj.
5 Sensowne wydaje się, aby opisana w p. 4 zmiana ustawień została przepro­wadzona dla obydwu trybów kompi­lacji: Debug (wersja testowa programu) i Release (wersja ostateczna dla użytko­wnika końcowego). Przejście do ustawień dla drugiego trybu ułatwia lista Konfi­guracja usytuowana w lewym górnym rogu okna Strony właści­wości (rys.). Aby uniknąć dwukro­tnego otwierania i zamykania okna, możemy ją rozwinąć za pomocą strzałki w dół, a następnie wybrać alterna­tywny tryb kompilacji. Po ustawieniu opcji dla nowego trybu zamykamy okno przyciskiem OK.
6 Po wyłączeniu prekompilowanego nagłówka możemy usunąć pliki stdafx.h, stdafx.cpptargetver.h (lub pch.hpch.cpp). Klikając prawym przyci­skiem w oknie eksplo­ratora rozwiązań na nazwie każdego z nich, wybieramy z menu podrę­cznego polecenie Usuń i w okienku Microsoft Visual Studio (rys.) naciskamy przycisk Skasuj.
7 Kompilacja, wywołana za pomocą polecenia Kompilowanie Kompiluj rozwiązanie, wykazuje błąd spowodo­wany brakiem pliku stdafx.h (lub pch.h) usuniętego z projektu (p. 6). Czerwona linia falista sygnali­zuje miejsce wystąpienia błędu. Gdy przesu­niemy kursor myszki na oznaczony linią tekst, pojawia się krótkie wyjaśnienie.
8 W przypadku usunięcia pliku stdafx.h błąd można naprawić, zastępując kod wiersza czwartego na przykład sugerowaną dyrektywą włącza­jącą do projektu plik nagłówkowy standar­dowej w C biblioteki wejścia-wyjścia:
#include <stdio.h>
Natomiast w przypadku usunięcia pliku pch.h należy jedynie usunąć błędną dyrektywę włączającą ten plik do projektu. Po tej poprawce program daje się pomyślnie skompilować. Program wynikowy można uruchomić, wybierając polecenie Debugowanie Uruchom bez debugowania. Jak widać (rys.), środowisko uruchamia program we własnej konsoli i zatrzymuje ją po jego zakończeniu, oczekując na naciśnięcie dowolnego klawisza.
Uwaga: Zaktualizowana wersja Visual Studio w oknie konsoli wyświetla tekst Hello World!

Udogodnieniem w środowisku Visual Studio jest paski narzędziowe z ikonami często używanych poleceń i skróty klawiszowe. Paski narzędzi i menu można zmieniać, dodając i usuwając polecenia, a także przenosząc je w inne miejsce lub tworząc własne. W razie potrzeby można wrócić do ustawień domyślnych. Dostosowanie menu i pasków narzędzi do własnych potrzeb umożliwia polecenie Narzędzia Dostosuj... .

Przejdźmy wreszcie do edycji kodu źródłowego programu wyzna­czania liczby cyfr danej liczby całkowitej korzysta­jącego ze standar­dowej w języku C biblio­teki wejścia-wyjścia stdio. Wydaje się, że powinien on być taki sam jak w przypadku kompi­latora MinGW C++. Okazuje się jednak, że kompi­lator Visual C++ traktuje funkcję scanf jako poten­cjalnie niebezpieczną i przeciwstawia się jej użyciu, propo­nując w zamian jej nowszy odpo­wiednik scanf_s lub wyłą­czenie zabezpie­czeń dotyczących zanie­chania przesta­rzałych funkcji (use _CRT_SECURE_NO_WARNINGS). Skorzy­stamy z pierwszej propo­zycji, zastę­pując przy okazji symbol formato­wania %I64d bardziej intuicyjnym %lld:

#include <stdio.h>

int main()
{
    long long n;
    int k;
    printf("Liczba calkowita: ");
    scanf_s("%lld", &n);
    for (k = 1; n /= 10; k++)
        ;
    printf("Cyfr: %d\n", k);
    return 0;
}

Inne rozwiązanie polega dodaniu na początku kodu źródłowego dyrektywy

#define _CRT_SECURE_NO_WARNINGS

lub na zmianie właściwości projektu. W tym celu otwieramy okno Strony właściwości projektu, rozwijamy węzeł C/C++, wybieramy zakładkę Preprocesor i pozycję Definicje prepro­cesora. Następnie po rozwi­nięciu listy za pomocą strzałki w dół wybieramy opcję <Edytuj...> i wpisujemy do listy okienka, które się pojawiło, nazwę _CRT_SECURE_NO_WARNINGS (rys.). Na koniec zatwier­dzamy zmianę, naciskając przycisk OK.

Druga wersja programu, oparta na standardowej w C++ bibliotece wejścia-wyjścia iostream, nie nastręcza żadnych problemów. Rozpoczy­namy od utworzenia nowego projektu o nazwie Lcyfr2, który będzie po Lcyfr1 drugim projektem istnie­jącego już rozwiązania Lcyfr (rys.).

Podobnie jak w przypadku Lcyfr1, we właści­wościach nowego projektu rezygnujemy z prekompi­lowanego nagłówka, usuwamy również zbędne pliki stdafx.h, stdafx.cpptargetver.h (lub pch.hpch.cpp). Kod źródłowy programu może być taki sam jak dla MinGW C++:

#include <iostream>

using namespace std;

int main()
{
    long long n;
    int k;
    cout << "Liczba calkowita: ";
    cin >> n;
    for (k = 1; n /= 10; k++)
        ;
    cout << "Cyfr: " << k << endl;
    return 0;
}

Przenośność kodu

Wydawałoby się, że przeniesienie programu do innego kompilatora nie powinno pociągać żadnych zmian jego kodu źródłowego. Przedsta­wione wyżej przykłady tworzenia tego samego, nawet bardzo prostego programu dla trzech różnych kompi­latorów języka C++ pokazują jednak, że tak nie jest. Wraz z powstawaniem coraz to nowszych standardów języka co jakiś czas tworzone są nowe, ulepszone wersje kompi­latorów, które mogą rozmaicie interpre­tować dotychcza­sowy kod źródłowy programu, wykazując jego niezgodność z nową specyfikacją języka i wymuszając aktuali­zację.

Nazwa w językach C i C++ jest ciągiem znaków spośród liter i cyfr rozpoczy­nającym się od litery, przy czym duże i małe litery są odróżniane. Znak podkre­ślenia _ trakto­wany jest jak litera, może więc wystę­pować w dowolnym miejscu nazwy, nawet na początku. W Visual Studio 2017 można w nazwach, w odróżnieniu od Borland C++ i MinGW C++, używać polskich znaków diakry­tycznych: Ą, ą, Ć, ć, Ę, ę, Ł, ł, Ń, ń, Ó, ó, Ś, ś, Ź, ź, Ż, ż.

Różnice interpretacyjne kodu źródłowego C++ przez kompilatory tego języka są też widoczne w przypadku nagłówka funkcji głównej main programu. Pomijając argu­menty, które ta funkcja może mieć, można spotkać trzy wersje jej nagłówka:

int main() Funkcja zwraca wartość całkowitą (kod powrotu programu, zazwyczaj 0)
main() Funkcja zwraca wartość całkowitą (jak wyżej)
void main() Funkcja nie zwraca żadnej wartości (kod powrotu nieokre­ślony)

Kompilator Borland 5.5 dopuszcza wszystkie wersje nagłówka, MinGW 5.1 wersję pierwszą i drugą, zaś Visual 2017 pierwszą i trzecią. Najlepiej więc trzymać się pierwszej wersji.

Pliki nagłówkowe języka C++ nie mają rozsze­rzenia nazwy .h, w przeciwieństwie do plików nagłówkowych języka C nadal poprawnych i używanych w C++. Zatem właściwym sposobem włączania do programu standar­dowej w C++ biblioteki strumie­niowej iostream jest

#include <iostream>

W przypadku kompilatora Borland C++ 5.5 można w odniesieniu do biblioteki iostream użyć w pliku nagłówkowym nazwy iostream.h i odwoływać się do zdefinio­wanych w niej obiektów bezpo­średnio, bowiem ich nazwy znajdują się wówczas w globalnej przestrzeni nazw. Kompi­latory MinGW C++ i Visual C++ nie dopuszczają takiej możliwości.

Rozmiary podsta­wowych typów danych C++ przyjęte w rozpatrywanych trzech kompila­torach są zgodne poza dwoma wyjątkami:

Warto podkreślić, że wszystkie trzy kompi­latory domyślnie uwzglę­dniają znak liczby w wartościach 1-bajtowych typu char. Przy tym ustaleniu są to wartości z przedziału od –128 do 127 (znakom tabeli ASCII przypi­sane są liczby od 0 do 127). Gdyby nie uwzglę­dniać znaku liczby, byłby to przedział od 0 do 255. Domyślne ustawienie kompi­latora można zmienić, określając opcję:

Opcja Kompilator Polecenie
–K Borland Tools Options Project Compiler Options
–funsigned–char MinGW Project Build options Other compiler options
/J Visual Projekt Właściwości C/C++ Wiersz polecenia Opcje dodatkowe

Nowsze kompilatory traktują niektóre funkcje dotychcza­sowych bibliotek jako przesta­rzałe i zalecają używanie ich nowszych odpowie­dników. Przykładami takich funkcji są getch z biblioteki conioscanfstdio. Kompilator Visual C++ proponuje zastą­pienie ich funkcjami _getchscanf_s.

Spośród trzech rozpatrywanych kompilatorów Visual C++ jest najbardziej rygory­styczny i jednocześnie skuteczny w wykrywaniu niezgo­dności kodu źródło­wego z najnowszą specy­fikacją języka C++. W przeciwieństwie do dwóch pozostałych, nie generuje błędnego kodu funkcji zwracającej wartości typu long double zamiast oczeki­wanych double lub float (zob. przekazy­wanie wartości zmiennopo­zycyjnej funkcji). Środo­wisko Visual Studio, pomimo swojej ogro­mności, jest dla progra­mistów najle­pszym narzę­dziem do wytwa­rzania oprogra­mowania.


Opracowanie przykładu: lipiec 2018