background image

Programowanie strukturalne w 

C++, pętle, instrukcje if, switch,

background image

Podstawowa budowa programu; 
strumienie

Najprostszy program w C++, jaki jest, każdy 

widzi: 

#include <iostream>

using namespace

 std;

int main()
  { //  pierwszy program

               

cout

 << 

"Hello, I'm Jan B.\n"

;

       

return

 0;

  }

 

// pierwszy program  – pierwszy program w C++

to  komentarz,  czyli  dowolny  opis  słowny.  Jest  on  całkowicie 

ignorowany  przez  kompilator,  natomiast  może  być  pomocny  dla 

piszącego i czytającego kod.

background image

Komentarze

  piszemy  w  celu  wyjaśnienia  pewnych  fragmentów  kodu 

programu, oddzielenia jednej jego części od drugiej, oznaczania funkcji 
i  modułów  itp.  Odpowiednia  ilość  komentarzy  ułatwia  zrozumienie 
kodu, więc stosuj je często 

W C++ komentarze zaczynamy od 

// 

(dwóch slashy), lub umieszczamy 

je między /*

 

i */, na przykład:

/*  Ten  komentarz  może  być  bardzo  długi  i  składać  się  z  kilku 
linijek. */

background image

                                  Rys.1.  Translacja programu C++

background image

Funkcja 

main()

Kiedy  uruchamiamy  nasz  program,  zaczyna  on  wykonywać  kod 

zawarty w funkcji main(). Od niej więc rozpoczyna się działanie aplikacji 

–  a  nawet  więcej:  na  niej  też  to  działanie  się  kończy.  Zatem  program 

(konsolowy)  to  przede  wszystkim  kod  zawarty  w  funkcji  main()  – 

determinuje on bezpośrednio jego zachowanie.

Rys.2. Struktura funkcji main()

background image

W  przypadku  rozważanej  aplikacji  funkcja  ta  nie  jest  zbyt 

obszerna, niemniej zawiera wszystkie niezbędne elementy.

Najważniejszym  z  nich  jest  nagłówek,  który  u  nas  prezentuje 

się następująco:

W C++ Builder  ta sama funkcja może  mieć postać:

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

//using namespace

 std;

//int 

main()     // standard

void main()
  {    //  pierwszy program   
     std::

cout

 << 

"Hello, I'm Jan B.\n"

     getch(); 
 //    return 0;  //standard
 }

std::cout  oznacza  tak  zwany  strumień  wyjścia.  Jego 

zadaniem jest wyświetlanie na ekranie konsoli wszystkiego, co doń 

wyślemy – a wysyłać możemy oczywiście tekst.

background image

Korzystanie z tego strumienia umożliwia zatem pokazywanie nam w oknie 

konsoli wszelkiego rodzaju komunikatów i innych informacji. Będziemy go 

używać  bardzo  często,  dlatego  musisz  koniecznie  zaznajomić  się  ze 

sposobem wysyłania doń tekstu.

Dołączanie plików nagłówkowych

#include 

<iostream>

 #include 

<conio.h>

Linijki które wcale nie są tak straszne, jak wyglądają na pierwszy rzut 

oka.  Przede  wszystkim  zauważmy,  że  zaczynają  się  one  od  znaku 

#

czym  niewątpliwie  różnią  się  od  innych  instrukcji  języka  C++.  Są  to 

bowiem specjalne polecenia wykonywane jeszcze przed kompilacją - tak 

zwane  dyrektywy.  Przekazują  one  różne  informacje  i  komendy, 

pozwalają więc sterować przebiegiem kompilacji programu.

background image

Pliki  nagłówkowe 

umożliwiają  korzystanie  z  pewnych  funkcji,  technik, 

bibliotek itp. wszystkim programom, które dołączają je do swojego kodu 
źródłowego.

Preprocesor 

"Chiałbym  się  kiedyś  dowiedzieć,  że  preprocesor  został  usunięty. 

Jednak  jedyny  realny  i  odpowiedzialny  sposób,  który  może  do  tego 

doprowadzić, polega na tym, żeby najpierw sprawić, że stanie się zbędny, 

po czym zachęcić ludzi do używania jego lepszych odpowiedników." 

Bjarne Stroustrup 

Preprocesor

 to specjalny mechanizm języka, który przetwarza tekst 

programu jeszcze przed jego kompilacją

.

background image
background image

             Rys. Linkowanie (łączenie) tworzy wolny od luki kod 
wykonywalny 

background image

Zwyczajowy przebieg budowania programu

języku 

programowania 

nieposiadającym 

preprocesora 

generowanie docelowego pliku z programem przebiega, jak wiemy, w 

dwóch etapach.

Pierwszym jest kompilacja, w trakcie której kompilator przetwarza 

kod  źródłowy  aplikacji  i  produkuje  skompilowany  kod  maszynowy, 

zapisany  w  osobnych  plikach.  Każdy  taki  plik  -  wynik  pracy 

kompilatora - odpowiada jednemu modułowi kodu źródłowego.

W drugim etapie następuje linkowanie (łączenie) skompilowanych 

wcześniej modułów oraz ewentualnych innych kodów, niezbędnych do 

działania  programu.  W  wyniku  tego  procesu  powstaje  gotowy 

program.

background image

 

Rys.  Najprostszy  proces  budowania  programu  z  kodu 

źródłowego

background image

Przy  takim  modelu  kompilacji  zawartość  każdego  modułu  musi 

wystarczać  do  jego  samodzielnej  kompilacji,  niezależnej  od  innych 

modułów. W przypadku języków z rodziny C oznacza to, że każdy moduł 

musi zawierać deklaracje używanych funkcji oraz definicje klas, których 

obiekty tworzy i z których korzysta.

Gdyby zadanie dołączania tych wszystkich deklaracji spoczywało na 

programiście, to byłoby to dla niego niezmiernie uciążliwe. Pliki z kodem 

zostały  ponadto  rozdęte  do  nieprzyzwoitych  rozmiarów,  a  i  tak 

większość  zawartych  weń  informacji  przydawałyby  się  tylko  przez 

chwilę. Przez tą chwilę, którą zajmuje kompilacja modułu.

Nic  więc  dziwnego,  że  aby  zapobiec  podobnym  irracjonalnym 

wymaganiom wprowadzono mechanizm preprocesora.

background image

Dodajemy preprocesor

Ujawnił się nam pierwszy cel istnienia preprocesora: w języku C(+

+)  służy  on  do  łączenia  w  jedną  całość  modułów  kodu  wraz  z 
deklaracjami,  które  są  niezbędne  do  działania  tegoż  kodu.  A  skąd 
brane  są  te  deklaracje?…  Oczywiście  -  z  plików  nagłówkowych. 
Zawierają  one  przecież  prototypy  funkcji  i  definicje  klas,  z  jakich 
można  korzystać,  jeżeli  dołączy  się  dany  nagłówek  do  swojego 
modułu.

Jednak  kompilator 

nic  nie  wie 

o  plikach  nagłówkowych.  On  tylko 

oczekuje, że zostaną mu podane pliki z kodem źródłowym, do którego 
będą się zaliczały także deklaracje pewnych zewnętrznych elementów 
-  nieobecnych  w  danym  module. 

Kompilator  potrzebuje  tylko  ich 

określenia „z wierzchu”, bez wnikania w implementację, gdyż ta może 
znajdować się w innych modułach lub nawet innych bibliotekach i staje 
się ważna dopiero przy linkowaniu

. Nie jest już ona sprawą kompilatora 

- on żąda tylko tych informacji, które są mu potrzebne do kompilacji.

Niezbędne  deklaracje  powinny  się  znaleźć  na  początku  każdego 

modułu

.  Trudno  jednak  oczekiwać,  żebyśmy  wpisywali  je  ręcznie  w 

każdym  module,  który  ich  wymaga.  Byłoby  to  niezmiernie  uciążliwe, 
więc  wymyślono  w  tym  celu  pliki  nagłówkowe…  i  preprocesor.  Jego 
zadaniem  jest  tutaj  połączenie  napisanych  przez  nas  modułów  oraz 
plików nagłówkowych w pliki z kodem, które mogą być bez przeszkód 
przetworzone przez kompilator.

background image

  

Rys. Budowanie programu C++ z udziałem preprocesora

background image

Skąd  preprocesor  wie,  jak  ma  to  zrobić?…  Otóż,  mówimy  o  tym 

wyraźnie,  stosując  dyrektywę 

#include

.  W  miejscu  jej  pojawienia  się 

zostaje po prostu wstawiona treść odpowiedniego pliku nagłówkowego.

Włączanie  nagłówków  nie  jest  jednak  jedynym  działaniem 

podejmowanym  przez  preprocesor.  Gdyby  tak  było,  to  przecież  nie 

poświęcalibyśmy  mu  całego  rozdziału.  Jest  wręcz  przeciwnie: 

dołączanie plików to tylko jedna z czynności, jaką możemy zlecić temu 

mechanizmowi - jedna z wielu czynności…

Wszystkie zadania preprocesora są różnorodne, ale mają też kilka 

cech wspólnych. Przyjrzyjmy się im w tym momencie.

background image

Dyrektywy

Polecenie  dla  preprocesora  nazywamy  jego  dyrektywą  (ang. 

directive).  Jest  to  specjalna  linijka  kodu  źródłowego,  rozpoczynająca  się 

od znaku 

# 

(hash), zwanego płotkiem: 

#

Na nim też może się zakończyć 

-  wtedy  mamy  do  czynienia  z  dyrektywą  pustą.  Jest  ona  ignorowana 

przez preprocesor i nie wykonuje żadnych czynności.

Przed hashem mogą znajdować się wyłącznie tzw. białe znaki, czyli 

spacje lub tabulatory. Zwykle nie znajduje się nic.

 

Bardziej praktyczne są inne dyrektywy, których nazwy piszemy zaraz 

za znakiem 

#

. Nie oddzielamy ich zwykle żadnymi spacjami (choć można 

to robić), więc w praktyce płotek staje się częścią ich nazw. Mówi się więc 

o  instrukcjach 

#include

#define

#pragma 

i  innych,  gdyż  w  takiej 

formie zapisujemy je w kodzie.

Dalsza część dyrektywy zależy już od jej rodzaju. Różne „parametry” 

dyrektyw poznamy, gdy zajmiemy się szczegółowo każdą z nich.

background image

Dyrektywy preprocesora kończą się zawsze przejściem do następnego 
wiersza.

Zapamiętaj!

Nie  kończ  dyrektyw  preprocesora  średnikiem.  Nie  są  to  przecież 
instrukcje 

języka 

programowania, 

lecz 

polecenia 

dla 

modułu 

wspomagającego kompilator

.

background image

Oto pliki należące do standardowej biblioteki C (włącznie z ISO C 9X). 
 assert.h  - makra do asercji   
ctype.h -klasyfikacje znaków typu char (isspace, isalpha    itd.)  
 errno.h - deklaracja errno   
fenv.h - środowisko dla liczb zmiennoprzecinkowych (ISO C    9X)   
float.h - definicje specjalne dla liczb zmiennoprzecinkowych   
limits.h  - makra określające granice dla typów ścisłych   
locale.h  - definicje lokali   
math.h   - funkcje matematyczne   
setjmp.h  - funkcje setjmp i longjmp   
signal.h
  - sygnały   
stdarg.h - narzędzia dla funkcji o zmiennej liście parametrów   
stddef.h  - standardowe definicje (ptrdiff_t i size_t    głównie)   
stdio.h   - operacje wejścia/wyjścia   
stdlib.h  - zespół funkcji użytkowych  
 string.h  - funkcje operujące na tablicach znaków   
time.h  - narzędzia do odczytywania, interpretacji i  prezentacji czasu  
              wchar.h  - obsługa "szerokiego" (wide-char) zestawu znaków  
 wctype.h  - wersja `ctype.h' dla szerokich znaków 

background image
background image

Dyrektywa #define

Dyrektywa ta pozwala tworzyć makrodefinicje. Pozwala ona zastąpić 
dowolny ciąg znaków (również pusty) identyfikatorem. Może służyć 
np. do definiowania stałych: 

#define przyciaganie_ziemskie 9.81 
Taka makrodefinicja może również posiadać argumenty, np. 
#define ctg( x ) 1/tan( x ) 
Znajomość tej dyrektywy raczej nie będzie Ci potrzebna; najwyżej 

do tego, żeby wiedzieć, co to jest. Jest to uniwersalne dosyć 
narzędzie, ale bardzo niebezpieczne i dające mnóstwo okazji do 
popełniania błędów. 

Kompilator  nie  stwierdzi  błędu  w  definicji  makra  (jeśli  np.  zdefiniujesz 
jako  desygnat  makra  jakąś  konstrukcję,  która  jest  niepoprawna 
składniowo w C++), a ewentualnie dopiero w miejscu, gdzie zostało ono 
użyte  (kompilator  nie  widzi  makr  ani  ich  używania;  preprocesor  jest 
właśnie  od  tego,  żeby  je  usuwać). 

W  przypadku  zastępowania  nimi 

wyrażeń  arytmetyczno-logicznych  należy  używać  dla  pewności  jak 
najwięcej  nawiasów,  oraz  -  o  czym  też  wielu  zapomina  -  NIE  WOLNO 
używać 

żadnych 

operatorów 

modyfikujących 

na 

zmiennych 

przekazywanych  jako  parametry  makra 

(tzn.  jako  argumenty  makra 

należy podawać wartości lub zmienne raczej, niż wyrażenia). 

Jednym też 

z typowych błędów jest zakończenie tej dyrektywy średnikiem

background image

Kompilacja warunkowa

Podstawową dyrektywą warunkową jest 

#if

. Jako  argument przyjmuje 

ona warunek, który ma być spełniony (pamiętaj jednak, że interpretuje 
go  preprocesor,  a  nie  kompilator!).  Najczęściej  jednak  do  kompilacji 
warunkowej  stosuje  się  makrowartowniki.  Są  to  puste  makra, 
definiowane  w  plikach  nagłówkowych,  dla  np.  zabezpieczenia  przed 
kilkakrotnym  wstawianiem  tego  samego  pliku.  Sprawdzenia  tego 
dokonujemy dyrektywami #ifdef #ifndef, czasem używa się też #if 
i funkcji preprocesora defined(): 
        #ifndef __STDLIB_H 
lub
        #if !defined( __STDLIB_H ) 
i dalej:
        #define __STDLIB_H        ... (tutaj deklaracje)        #endif 

background image

Inne dyrektywy

Znacznie rzadziej są w programach używane dyrektywy takie, jak: 

1. #error 

-  powoduje  wyrzucenie  błędu  kompilacji  z  podanym  jako 

argument  komunikatem  (używane  tylko  wespół  z  dyrektywami 

warunkowymi), 

2. #pragma 

-  zmienia  ustawienia  kompilatora  (użycie  tej  dyrektywy 

zależy wyłącznie od implementacji) 

3. #line 

- udaje, że następna linia jest inną linią z innego pliku 

Dyrektywa  pusta  (składająca  się  tylko  ze  znaku  `#')  jest  dopuszczalna  i 

nie daje żadnego efektu

background image

Instrukcje i bloki

Instrukcja prosta jest to wyrażenie występujące w przeznaczonym dla 

niego miejscu i zakończone średnikiem. Instrukcja złożona zaś, zwana też 

blokiem,  jest  to  jedna  lub  więcej  instrukcji  prostych,  ujętych  w 

{  }. 

Blok 

taki  - 

proszę  pamiętać

  - 

posiada  już  osobny,  lokalny  zasięg

toteż 

identyfikatory w nim definiowane mają zasięg tylko wewnątrz tego bloku i 

mogą  "przysłaniać

"  (ang.  hide

identyfikatory  znajdujące  się  w  wyższym 

zasięgu.

 No to może tak mały przykład: 

Zauważ,  że  zadeklarowano  zmienną  w  wyrażeniu  będącym 

argumentem  instrukcji 

if.

  W  C++  jest  to  dopuszczalne,  ale  proszę  się 

jednak  starać  tego  nie  nadużywać.  Zmienna  ‘a'  jak  widać  jednak,  ma 
zasięg  tylko  dla  instrukcji  podporządkowanej 

if

  (jest  podana  instrukcja 

prosta,  ale  można  też  podać  złożoną,  tak  jak  w  każdym  przypadku). 
Podobnie jest ze wszystkimi tego typu instrukcjami. 

Jak  widać,  mamy  trzy  różne  zmienne  x  o  różnych  zasięgach. 

Ponieważ  zdarza  się  deklarować  zmienne  lokalne  o  takich  samych 
nazwach,  jak  globalne,  dlatego  istnieje  operator  ::,  zwany  operatorem 
zasięgu.  Jego  jednoargumentowa  (przedrostkowa)  postać  nakazuje 
uzyskać identyfikator z najwyższego zasięgu. 

background image

#include <iostream>
using namespace std;
int x = 5;int main() 
 { //
 blok funkcji main       
    int x = 0; //
 zmienna lokalna dla bloku funkcji main       
      {  // lokalny blok                  
         int x = 2; cout << "Wewnętrzna: " << x << endl;     
       }        
    cout << "Lokalna: " << x << endl;       
    cout << "Globalna: " << ::x << endl;        
        if  (  int  a  =  x  +  2  >  0  )        cout  <<  a  <<  endl;    //  
instrukcja 

podporządkowana if ( )           return 0;} 

Operator  :: 

ten  istniał  już  w 

C

,  ale  tylko  jako  operator 

jednoargumentowy,  czyli  o  takim  znaczeniu,  jakie  tu  zostało  podane. 
Później  poznamy  ten  operator  w  wersji  dwuargumentowej.  W 
przeciwieństwie  do  swojego  jednoargumentowego  przodka,  ten 
dwuargumentowy jest operatorem jednym z częściej używanych w 

C+

+. 

Nadal zresztą nazywa się operatorem zasięgu

background image

Instrukcje sterujące

Słowa 
kluczowe C++

background image

Program  byłby  „głupi”,  gdyby  przebiegał  krokowo  od  początku  do 

końca. Dlatego też w programie praktycznie zawsze używa się instrukcji 

sterujących, na które składają się: 

 instrukcje odgałęzienia warunkowego: if, else ;
 pętle: while, do-while, for 
 instrukcje przełączające: switch/case
 instrukcje skoku: goto, break, continue;
 instrukcja powrotu z funkcji, return.

        

if

 ( <warunek> )              

            {nstr}        
                 

else

                

                    {instr}

Oczywiście 

else

 z całą resztą jest opcjonalne. 

if

background image

Przejdźmy zatem do pętli. Najprostszą pętlą z wyrażeniem 

warunkowym jest pętla 

`while

': 

        while ( <warunek> )         
          <instr>
 
Instrukcja ta nakazuje powtarzać <instr> dopóki spełniony jest 

<warunek>. Zwraca się uwagę, że <warunek> sprawdzany jest na 
początku, a więc przed pierwszym wejściem i każdym następnym 
powtórzeniem. Nieco inne możliwości prezentuje nam pętla do-while

Pętla 

while 

może nie wykonać się ani razu, jeżeli jej warunek będzie od 

początku nieprawdziwy.

do               
 {
  <instr>
 }while ( warunek ); 
Tu <warunek> sprawdzany jest na końcu pętli, toteż pętla wykona się 

bezwzględnie co najmniej raz. 

background image

                          

Rysunek  Działanie przykładowej pętli 

do 

        

for 

int i = 0; i < 5; i++ ) 

             {
                 
//instrukcje
                 ….
                 …..
               }

                 

background image

        

for (;;)        

        { // tu coś robimy...            
          // tu trzeba sprawdzić warunek                
           // i przerwać... tylko jak?                // (dalej)       
        } 

Pętla nieskończona

Do  przerywania  wykonywania  pętli  służy  instrukcja

  break

.  Może 

być  ona  używana  w  dowolnej  pętli  i  powoduje  wykonanie  skoku  do 

pierwszej  instrukcji  za  pętlą.  Jeśli  zależy  nam  z  kolei  na  skoku  na 

początek  bieżącej  pętli,  służy  do  tego  celu  słowo 

continue

.  Proszę 

pamiętać  jednak  że  słowo 

continue

  ma  zupełnie  inne  znaczenie  dla 

pętli

  while 

do-while

, niż  dla pętli  for!  W przypadku tych  pierwszych 

powoduje  normalny  skok  na  początek  pętli,  podczas  gdy  w  ‘for

powoduje  przejście  do  następnej  iteracji.  W  praktyce  więc  skacze  do 

wyrażenia <next> i dopiero potem jest sprawdzany <warunek>. 

background image

switch 

( <wartość> )        

   {              
      case <jedna_możliwość>: <instr> <instr> ...               
      case <druga_możliwość>: <instr> <instr> ...                              
     default: <instr> <instr> ...        
  } 

W  większości  przypadków  należy  zatem  kończyć  fragment  kodu 
rozpoczęty przez 

case 

instrukcją 

break 

- gwarantuje to, iż tylko jedna z 

możliwości ustalonych w 

switch 

zostanie wykonana.

Pytanie?,  co  się  stanie,  kiedy  umieszczę 

continue

  wewnątrz

 

switch 

(pytanie  sugeruje,  że  switch  rozpocznie  sprawdzanie  od 

początku).  Najlepsza  odpowiedź:      po  prostu,  continue  może  się 

pojawić tylko wewnątrz pętli ;*). 

Instrukcja continue w ogóle nie ma związku ze switch

.

background image

Instrukcja  przełączająca 

switch 

porównuje  po  kolei  <wartość>  z 

kolejnymi  wariantami.  Etykieta  default  -  jak  się  zapewne  można 

domyślać  -  jest  miejscem  skoku  w  przypadku  gdy  <wartość>  nie  pasuje 

do żadnego wariantu. 

Dlaczego radzę się jej wystrzegać? Dlatego, że jest bardzo prosta i 

zachęcająca w stosowaniu do tego stopnia, że 

większość programujących 

zapomina o tym, że bywa powolna

background image

Document Outline