dr inż. Krzysztof Białek - slupsk.spoleczna.pl · • Jerzy Grębosz “Symfonia C++” tom...

149
Podstawy programowania dr inż. Krzysztof Białek 1

Transcript of dr inż. Krzysztof Białek - slupsk.spoleczna.pl · • Jerzy Grębosz “Symfonia C++” tom...

Podstawy programowania

dr inż. Krzysztof Białek

1

Cele i zakres przedmiotu

• Zapoznanie z istotą i metodyką programowania

• Opanowanie podstawowych technik programowania

strukturalnego

• Nabycie umiejętności zapisu algorytmów w wybranym

języku programowania

• Nauka czytania i analizy prostych programów w celu

przewidywania ich wyniku oraz poszukiwania błędów

2

WPROWADZENIE

3

Literatura

• do wykładu

– dowolny podręcznik do języka C++

• na laboratoriach

– Code::Blocks

4

Literatura

• Jerzy Grębosz “Symfonia C++” tom pierwszy

• S. Prata “Szkoła programowania, Język C++”

• Robert Lafore “Programowanie w języku C przy użyciu

Turbo C++”

• S. Lippman “Podstawy języka C++”

• K. Jamsa “Wygraj z C++”

5

Co to jest programowanie

• Ogólnie:

Tworzenie mechanizmów pozwalających na automatyzację

pracy danego urządzenia

• W informatyce:

Proces projektowania, tworzenia i poprawiania

kodu źródłowego programu z użyciem wybranego języka

programowania

6

Algorytm a program • Algorytm - opis czynności, które mają na celu

rozwiązanie postawionego problemu lub realizację zadania. Typowe formy zapisu algorytmów to - opis słowny - lista kroków - schemat blokowy - diagramy NS - pseudokod - kod źródłowy programu

• Program - algorytm zapisany w postaci umożliwiającej (zwykle po przetworzeniu) jego automatyczną realizację za pomocą systemu komputerowego. Postać ta zależy od użytego języka programowania.

• Kodowanie - proces „tłumaczenia” algorytmu z dowolnej postaci na wybrany język programowania

7

Od problemu do programu

1. Sformułowanie problemu (definicja zadania)

2. Analiza problemu

3. Wybór metody (metod) rozwiązania

4. Opracowanie algorytmu

5. Kodowanie (implementacja) programu

6. Testowanie programu

7. Sporządzenie dokumentacji

8

Definicje • Język programowania - Zrozumiały dla komputera sposób

przekazywania poleceń przez człowieka.

• Symbol - najmniejszy element języka

• Alfabet - zbiór wszystkich symboli danego języka

• Kod źródłowy programu - napis rozumiany jako ciąg symboli

należących do alfabetu

• Program wykonywalny - kod źródłowy przetworzony na postać

zrozumiałą dla komputera

• Składnia - kolejność występowania symboli w programie

• Gramatyka - zbiór reguł definiujących zbiór wszystkich możliwych

ciągów symboli poprawnych z punktu widzenia danego języka

• Semantyka - znaczenie poprawnego składniowo ciągu symboli czyli

akcja, która została zakodowana 9

Cechy programów

• Skuteczność - czy program dla różnych danych wejściowych realizuje

postawione zadanie

• Dokładność - stopień, w jakim uzyskane rozwiązanie problemu

odpowiada postawionemu celowi

• Wydajność - odpowiada szybkości działania programu dla określonego

sprzętu, na jakim działa.

Często jest przedmiotem porównania różnych rozwiązań, czasem

odnosi się do wymogów tzw. czasu rzeczywistego

• Czytelność - odnosi się do kodu źródłowego programu, oznacza

zrozumiałość i przejrzystość kodu

• Zużycie zasobów - konieczność dostępu programu do różnych

zasobów sprzętowych systemu, zwłaszcza pamięci RAM

10

Klasyfikacje języków programowania

• Paradygmat:

– Imperatywny, Strukturalny, Obiektowy, Funkcyjny, Logiczny

• Poziom (generacja)

– Asemblery, Języki wysokiego poziomu, Języki 4 generacji

• Przeznaczenie

– Języki ogólnego przeznaczenia i specjalizowane

• Tryb interpretacji

– Języki interpretowane i kompilowane

11

Poziom języka:

Kod maszynowy i asembler

Przykładowy fragment Interpretacja w postaci

programu w pamięci: symboli asemblera

8D 7D C0 lea edi,[ebp-40h]

B9 10 00 00 00 mov ecx,10h

B8 CC CC CC CC mov eax,0CCCCCCCCh

F3 AB rep dword ptr [edi]

C6 05 D8 25 42 00 0A mov byte ptr [c (004225d8)],0Ah

A0 D8 25 42 00 mov al,[c (004225d8)]

04 05 add al,5

A2 D8 25 42 00 mov [c (004225d8)],al

B8 01 00 00 00 mov eax,1

Zależność od sprzętu (procesora), nieprzenośność

Wysoka wydajność

Duża pracochłonność tworzenia programów

12

Poziom języka:

Języki wysokiego poziomu (3G)

• Dużo większa zrozumiałość tekstu programu dla człowieka

niż w przypadku asemblera

• Możliwość operowania zarówno na prostych danych, jak i

tworzenia złożonych struktur

• Konieczność przetworzenia kodu źródłowego na postać

wykonywalną (kod maszynowy)

• Przenośność

• Stosunkowo duża wydajność przy dużo krótszym czasie

tworzenia programu niż dla asemblera

• Duża uniwersalność w porównaniu z językami 4G 13

Poziom języka:

Języki wysokiego poziomu (3G) - c.d.

• Poprzedni fragment programu zapisany w języku C++:

c=10;

c+=5;

return 1;

• Fragment innego programu w języku Pascal:

for i:=1 to N do

begin

for j:=1 to m do

write(macierz[i,j]:4);

writeln;

end;

14

Poziom języka:

Języki wysokiego poziomu (3G) - c.d.

• Przykładowe języki wysokiego poziomu to:

– C, C++, C#

– Pascal, Fortran, Cobol, Algol

– Java, Python

– Basic

– PHP

– SmallTalk

– HTML

15

Poziom języka:

Języki czwartej generacji (4G) • Instrukcje symbolizują bardzo złożone działania

• Bardzo krótki kod źródłowy

(kilka linijek programu może odpowiadać nawet tysiącom

linii kodu w języku 3G)

• Specjalizacja

• Przykładowe języki:

• SQL

• Matlab

• TEX, LATEX

• PostScript

16

Tryby wykonania programu:

Interpretacja i interpreter • Interpreter - system pobierający instrukcje kodu

źródłowego, dekodujący je i wykonujący na bieżąco (po

jednej). Jego obecność w pamięci jest niezbędna do

wykonania programu.

• Umożliwia to programiście interaktywną pracę poprzez

podawanie pojedynczych rozkazów.

• Niska wydajność (konieczność dekodowania poleceń)

• Typowe języki interpretowane:

• BASIC

• Większość języków 4G

• Języki skryptowe

• Polecenia systemu operacyjnego 17

Tryby wykonania programu:

Kompilacja i kompilator

• Kompilator - program „tłumaczący” kod źródłowy w całości

na postać wykonywalną. Zwykle odbywa się to w 4 etapach:

– Analiza leksykalna - kontrola poprawności oraz rozpoznanie

poszczególnych symboli (znaków) kodu źródłowego

– Analiza składniowa (syntaktyczna) - analiza i kontrola

poprawności kodu źródłowego pod względem zgodności z

gramatyką języka

– Analiza semantyczna - analiza znaczenia (sensu)

poszczególnych struktur programu (częściowa kontrola)

– Generacja kodu - tworzenie kodu maszynowego,

przeznaczonego na konkretną platformę sprzętową,

realizującego zapisany program 18

Tryby wykonania programu:

Kompilacja i kompilator - c.d. • Wysoka wydajność

• Możliwości optymalizacji kodu

• Brak możliwości pracy interaktywnej

• Postać skompilowana jest samodzielnym programem i do wykonania nie wymaga obecności innych programów

• Przykładowe języki kompilowane: • Większość języków wysokiego poziomu, w tym C, C++, Pascal

• Szczególnym przypadkiem są koncepcje typu Java lub .NET, gdzie kod źródłowy podlega skompilowaniu do tzw. postaci pośredniej, która do pracy wymaga specjalnego programu zwanego maszyną wirtualną

19

Tryby wykonania programu:

Maszyna wirtualna

• Nowe koncepcje programowania (np. Java) wprowadziły

tryb pracy polegający na:

– kompilacji programu do postaci pośredniej,

niezrozumiałej bezpośrednio dla komputera

ale niezależnej od platformy sprzętowo-programowej

– interpretacji i wykonaniu programu w postaci pośredniej przez

zainstalowany program, zwany maszyną wirtualną

• Języki wykorzystujące ten tryb pracy to:

– Java (Java Virtual Machine)

– C# (.NET)

– Python

20

Paradygmaty programowania

• Programowanie imperatywne: Program to sekwencja poleceń wpływających na stan maszyny aż do uzyskania oczekiwanego wyniku

• Programowanie obiektowe: Program to zbiór obiektów porozumiewających się ze sobą. Obiekty to dane oraz operacje, jakie można na tych danych wykonać

• Programowanie funkcyjne: Program to funkcja, której wynik należy obliczyć. Z reguły bazuje ona na wynikach innych funkcji

• Programowanie logiczne: Program to zbiór przesłanek i hipoteza, którą należy udowodnić

21

Języki proceduralne i funkcyjne

• Są to podstawowe odmiany języków, pozwalające na

programowanie strukturalne.

• Procedura - fragment kodu programu opatrzony nazwą,

który można wywoływać poprzez jej podanie

• Funkcja - od procedury różni ją jedynie fakt zwracania

wartości określonego typu

• Definiowanie takich struktur pozwala na budowanie

większych, złożonych programów z gotowych elementów

• Program jest zazwyczaj ciągiem deklaracji oraz instrukcji lub

wyrażeń

22

Języki obiektowe • Podstawowymi elementami języka obiektowego są:

– Klasa - opis formalny zestawu danych oraz operacji, jakie można wykonać na tych danych

– Obiekt - konkretny egzemplarz danej klasy

• Możliwości definiowania klas obejmujące:

– Zawieranie się innych klas wewnątrz danej klasy

– Dziedziczenie, oznaczające zdefiniowanie podklasy

powodują, że sposób definiowania struktur danych jest zbliżony do ludzkiego postrzegania obiektów świata rzeczywistego

• Duża efektywność tworzenia złożonych systemów

• Wymagają specyficznego podejścia przy projektowaniu oprogramowania 23

Dlaczego C++?

• Uniwersalność (język ogólnego przeznaczenia)

• Możliwość programowania zarówno strukturalnego jak i

obiektowego

• Język wysokiego poziomu, ale z możliwościami dostępu do

zasobów sprzętowych

• Wydajność kodu wynikowego

• Przenośność (w części niezależnej od systemu)

• Liczne biblioteki

• Popularność

24

Budowa programu wykonywalnego • Główne typy plików związane z C++:

– Moduł źródłowy (.CPP)

– Plik nagłówkowy (.H)

– Biblioteka statyczna (.LIB)

– Skompilowany moduł (.OBJ)

– Program wykonywalny (.EXE) (na platformie PC)

– Biblioteka dołączana dynamicznie (.DLL)

• Zwykle programista tworzy pewną liczbę modułów

oraz plików nagłówkowych

• Następnie uruchamia on kompilator, który tworzy plik

wykonywalny programu

25

Tworzenie programu wykonywalnego .H

.CPP

.LIB

.H

.CPP .H

.LIB

.H

Kompilacja Kompilacja

.OBJ .OBJ

Konsolidacja (linkowanie)

.EXE, .LIB lub .DLL 26

Systemy SDK

(Sofware Development Kit) • Wspomaganie dla tworzenia złożonych projektów

• Kontrola nad procesem tworzenia programu

wykonywalnego, prezentacja błędów i ostrzeżeń

• Wspomaganie pisania kodu źródłowego

– Podświetlanie kolorami poszczególnych symboli

– „Podpowiadanie” kolejnych elementów programu

– Wyświetlanie informacji o wskazanym symbolu

• Kontekstowa pomoc dotycząca wybranego języka

programowania

• Biblioteki standardowe i dodatkowe (w tym GUI)

• Debugger 27

Komunikaty o błędach kompilacji • Podczas kompilacji generowane są komunikaty:

– Błędów (Error) - wskazują miejsca w programie niezgodne

z alfabetem lub gramatyką języka i niemożność dokonania

pełnej kompilacji.

– Ostrzeżeń (Warning) - wskazują miejsca, które pomimo

poprawności leksykalnej i składniowej, nasuwają

przypuszczenie, że programista popełnił błąd semantyczny, a

program może działać błędnie

• Błąd wskazywany jest za pomocą numeru linii lub

bezpośrednio w tekście programu i zwykle pokazuje

początek fragmentu, który nie odpowiada specyfikacji

języka. W rzeczywistości często zdarza się, że właściwy

błąd popełniono znacznie wcześniej.

28

Co to jest Debugger?

• Debugger - (ang. bug - pluskwa - błąd) program

umożliwiający interaktywne śledzenie wykonania programu

znacznie ułatwiając odnalezienie źródeł błędów. Typowe

możliwości debuggera:

– Ręczne uruchamianie kolejnych instrukcji

(trace into, step over)

– Ustawianie tzw. pułapek (breakpoints)

– Wyświetlanie wartości dowolnych wyrażeń (watch)

– Zatrzymanie programu na dowolnym etapie

• Użycie debuggera wymaga włączenia do programu

wykonywalnego dodatkowych informacji na etapie jego

kompilacji 29

PRZETWARZANIE DANYCH

W C++

30

Podstawy tworzenia kodu w C++ • Case-sensitive czyli rozmiar ma znaczenie

• Ignorowanie odstępów (whitespace - białe znaki)

– Spacje

– Tabulacje

– Znaki nowej linii

• Komentarze: symbol // lub para /* - */

• Układ graficzny i wielkość liter w służbie czytelności

• Konwencje

• Układ graficzny

• Nadawane nazwy

• Język

• Komentarze z grafiką 31

Kolejność kompilacji i wykonania

• Podstawową zasadą programowania jest zapis poleceń w

kolejności, w jakiej mają być wykonywane, od góry do dołu,

a jeżeli kilka instrukcji znajduje się w jednej linii kodu: od

lewej do prawej

• Kolejność ta nie zawsze dotyczy całości pliku programu ze

względu na strukturę programu w języku C++, można

jednak założyć, że obowiązuje wewnątrz każdego

wydzielonego bloku

• Kompilacja: zgodna z kolejnością znaków w pliku,

a więc od góry do dołu i od lewej do prawej

32

Proceduralna i obiektowa komunikacja

z użytkownikiem

/* proceduralnie: C / C++ */

#include <stdio.h>

void main(void)

{

printf(”Dzien ”);

printf(”dobry!\n”);

getchar();

}

// obiektowo: C++

#include <iostream>

void main(void)

{

std::cout << ”Dzien ” ;

std::cout << ”dobry” << endl ;

std::cin.get();

}

33

Proceduralna i obiektowa komunikacja

z użytkownikiem

• #include dyrektywa dołączenia tekstu zawartego w pliku

• stdio.h (StandardInputOutput) plik definicji funkcji Wej/Wyj

• iostream (InputOutputStream) plik definicji strumieni obiektowych

• main zastrzeżona nazwa głównej funkcji programu

• void typ danej “pustej”

• \n przejscie do nowego wiersza

• \t znak tabulacji

• \” znak cudzysłowu

• \\ jeden znak \

• endl manipulator przejścia do nowej linii

34

Proceduralna i obiektowa komunikacja

z użytkownikiem

// 2 przykład -proceduralnie

#include <stdio.h>

int x,y,s;

void main( )

{

printf (”Podaj x = ”);

scanf ( ”%d” , &x );

printf (”Podaj y = ”);

scanf ( ”%d” , &y );

s = x+y;

printf(”Suma x+y = %d\n”, s);

fflush(stdin);

getchar();

}

// 2 przykład -obiektowo

#include <iostream>

using namespace std;

int x,y,s;

void main( )

{

cout << ”Podaj x = ” ;

cin >> x ;

cout <<”Podaj y = ” ;

cin >> y ;

s = x+y;

cout<< ”Suma x+y=”<< s <<endl;

cin.ignore( INT_MAX, ’\n’ );

cin.get();

}

35

Dane i ich rodzaje • Pracę programu należy rozumieć jako przetwarzanie

danych

• Przykłady danych różnych rodzajów:

– liczba

– tekst

– znak

– adres w pamięci

– data i czas

– obraz, dźwięk, video

• Kodowanie w postaci kombinacji (ciągu) bitów

interpretowanych jako liczby

• Sposób kodowania danych to reprezentacja 36

Typy danych w C++ • Wszystkie dane w programie muszą mieć określony typ

definiujący jednoznacznie ich zapis w pamięci

• Typy całkowite

• Typy zmiennoprzecinkowe

• Typ znakowy

• Typy pochodne • Wskaźnik

• Referencja

• Typy złożone • Tablica

• Rekord lub klasa

• Unia

• Pole bitowe 37

Typy całkowitoliczbowe

• Stosowane do zapisu wartości niewymagających użycia

ułamków; ilości, numerów porządkowych itp.

• Typ int

• Zakres liczb - modyfikatory: short, long

typy: short int = short, long int = long

• Określenie znaku - modyfikatory: signed, unsigned

typy: signed int = signed

unsigned int = unsigned

signed/unsigned short [int]

signed/unsigned long [int]

• Typ znakowy char może być również traktowany jako liczba

całkowita

38

Typy całkowite i ich zakresy

• Rozmiar typu int zależy od kompilatora i z założenia powinien

odpowiadać szerokości szyny danych komputera, (obecnie 32b)

• Typy int, short i long domyślnie posiadają znak (signed)

• Dla typu char własność ta zależy od kompilatora

Rozmiar

Znak

1B = 8b

char

2B = 16b

short (int)

4B = 32b

long (int)

Ze znakiem

signed

-128

127

-32 768

32 767

-2 147 483 648

2 147 483 647

Bez znaku

unsigned

0

255

0

65 535

0

4 294 967 295

39

Typy zmiennoprzecinkowe • Zapis wartości zawierających ułamki lub zbyt dużych aby

reprezentować je za pomocą typów całkowitych

• Typy float, double i long double

• Różnią się one precyzją zapisu i zakresem dopuszczalnych

wartości oraz liczbą bajtów zajętej pamięci:

Typ Rozmiar Precyzja Zakres wartości

float 4B 7-8 cyfr (1.5*10-45 ... 3.4*10+38 )

double 8B 15-16 cyfr ( 5*10-324 ... 1.7*10+308)

long double 10B 19-20 cyfr ( 3.4*10-4932 ... 1.1*10+4932)

40

Stałe liczbowe w programie • Liczby całkowite wpisane wprost w tekście programu

traktowane są jako typ int, chyba że ich wartość

wykracza poza zakres tego typu. Wtedy stosowany jest typ całkowity o większym zakresie (np. long lub int64) lub

kompilacja kończy się błędem

• Znak U po liczbie wymusza utworzenie stałej o typie

nieznakowanym (unsigned)

• Znak L wymusza utworzenie stałej o typie long

• Para znaków UL po liczbie wymusza utworzenie stałej o

typie unsigned long

• Liczba zawierająca kropkę dziesiętną lub wartość w tzw. notacji naukowej traktowana jest jako double 41

Stałe liczbowe - przykłady

• 100 // int

• -23000U // unsigned int!

• 1L // long

• 5000000000 (5mld) // int64!

• 50UL // unsigned long

• 1.23234 // double

• 26. // double, 26.0

• .5 // double, 0.5

• -1.434e25 // double, -1.434*10^25

• 1e6 // double, 1 mln

• 5e-1 // double, 0.5 42

Stałe ósemkowe i szesnastkowe

• Liczby całkowite można podawać, oprócz postaci

dziesiętnej, również w kodzie ósemkowym lub

szesnastkowym

• Stała całkowita rozpoczynająca się od 0 traktowana jest

jako liczba ósemkowa, a od 0x jako szesnastkowa, np.

– 010 // 8

– -077 // -63

– 0xff // 255

– -0x10 // -16

43

Zmienne • Jeżeli w programie zachodzi potrzeba zapamiętania pewnej

wartości lub jej przetwarzania należy zarezerwować odpowiedni obszar w pamięci nadając mu nazwę oraz typ.

• Obszar taki wraz z nazwą i typem nazywa się zmienną.

• Zmienna zawsze zawiera wartość

• Składnia deklaracji (utworzenia) zmiennej:

typ nazwa_zmiennej;

typ nazwa_zmiennej1, nazwa_zmiennej2,...;

• Ograniczenia nazwy: litery wielkie i małe (bez polskich znaków), cyfry oraz znak ‘_’, nie może zaczynać się od cyfry, zwykle max. 32 znaki

• Przykłady:

int x;

unsigned long _Bardzo_Dluga_Nazwa_Zmiennej;

float Pole_Kola1, Pole_Kola2;

44

Korzystanie ze zmiennej • Zapamiętanie wartości w zmiennej odbywa się

dzięki operatorowi przypisania =

Składnia: zmienna = wartość;

• Od chwili utworzenia zmiennej użycie jej nazwy (za wyjątkiem lewej strony przypisania) oznacza odczyt wartości w niej zapisanej

• Przykłady

int x,y; // utworzenie zmiennych

x=25; // przypisanie wartości zmiennej x

y=x; // odczyt wartości ze zmiennej x

// i przypisanie tej wartości zmiennej y

45

Inicjalizacja zmiennej • Jeżeli chcemy nadać zmiennej pewną wartość początkową,

możemy dokonać zwykłego przypisania po jej utworzeniu, np.: int x;

x=5;

• Możliwe jest jednak połączenie obu czynności czyli nadanie

wartości zmiennej już w chwili tworzenia - inicjalizacja, np.:

int x=5;

float f, d=0.5;

long a1=100L, a2, a3=5;

• Powoduje to skrócenie zapisu, a w niektórych przypadkach

może być jedyną szansą nadania wartości zmiennej (np. zmienne typu const)

46

Wyrażenia

• Wyrażenie to ciąg symboli, któremu w danej chwili działania programu można przypisać konkretną wartość określonego typu

• Do budowy wyrażeń używa się operatorów oraz m.in. stałych i zmiennych odgrywających rolę operandów

• Przykłady wyrażeń:

1 x // wyrażenia bez operatora

-a 2*x+(a-1) // działania arytmetyczne

a>1 a==x+1 // relacje

x &&(a>1) // działania logiczne

47

Operatory • Operator to symbol oznaczający wykonanie pewnego działania.

Obiektem działania jest symbol lub symbole sąsiadujące z operatorem, zwane operandami.

• Operatory arytmetyczne jednoargumentowe:

+ -

Przykłady: -a +b // ‘+’ nie daje efektu

• Operatory arytmetyczne dwuargumentowe:

+ - * / % (tylko całkowite)

• Przykłady:

a+b x-y 2*p

rok/12.0 // dzielenie „arytmetyczne”

10/3 10%3 // dzielenie całkowite i reszta

48

Typ wyniku działań arytmetycznych • Jeżeli operandy działań arytmetycznych są całkowite , wynik jest typu

int, chyba że jeden z operandów był typu long

• Jeżeli chociaż jeden z operandów był zmiennoprzecinkowy, wynik jest

zmiennoprzecinkowy (takiego typu jak ten operand)

• Jeżeli zakres wartości typów operandów jest różny, wynik posiada typ o

zakresie odpowiadającym większemu z zakresów operandów

• Przykłady:

short x; float f;

x+2 // short + int = int

10+2.5 // int + double = double

f + 10.0 // float + double = double

f + 1 // float + int = float

2 * 1L // int + long = long

49

Operator konwersji • Opisane wyżej sytuacje dotyczą tzw. konwersji niejawnych czyli zmian

typu niezależnych od programisty

• Możliwa jest również zmiana typu poprzez konwersję jawną (tzw. rzutowanie) za pomocą operatora ()

• Składnia:

(typ) wyrażenie

• Przykłady:

short x=1; float f;

f= x / 5; // 0 dzielenie całkowite

f= (float) x / 5; // 0.2, dzielenie zwykłe

x= f * 5; // 0.2 * 5 = 1.0

x= (int) f * 5; // 0 * 5 = 0

x= 2* (int)(1.0/10+1);// 2*(int) 1.1 = 2*1=2

50

Operatory relacji

• Operatory relacji:

> (większe) < (mniejsze) >= (większe lub równe) <= (mniejsze lub równe)

oraz porównania:

== (równe) != (różne)

Są to operatory dwuargumentowe generujące wartość typu int równą 1 (prawda) lub 0 (fałsz) w zależności od prawdziwości relacji dla podanych operandów

• Przykłady:

1==0 // 0

2>=2 // 1

2<1 // 0

0!=0 // 0

x+1 > 2*y-5 // wynik zależy od zmiennych

// dla x=5 i y=2: 1

51

Operatory logiczne • Operatory logiczne:

! (negacja/not) && (i/and) || (lub/or)

Negacja jest jednoargumentowa, pozostałe są dwuargumentowe.

Wykonują działania logiczne przy założeniu, że wartość operandu

równa jest:

1 (prawda) jeżeli jest on różny od zera

0 (fałsz) jeżeli jest on równy zero

x y !x x && y x || y

0 0 1 0 0

0 1 1 0 1

1 0 0 0 1

1 1 0 1 1

52

Operatory bitowe • Operatory bitowe:

~ (negacja/not) & (i/and) | (lub/or) ^ (xor)

Działanie jest bardzo podobne do działania operatorów

logicznych, jednak w tym przypadku działania wykonywane są na

każdej z odpowiadających sobie par bitów.

x y ~x x & y x | y x ^ y

0 0 1 0 0 0

0 1 1 0 1 1

1 0 0 0 1 1

1 1 0 1 1 0

53

Operatory logiczne i bitowe

• Przykład:

10 && 25 // 1(nie-zero) && 25(nie-zero) = 1

10 & 25 // 00000000000000000000000000001010

// & 00000000000000000000000000011001

//------------------------------------

// = 00000000000000000000000000001000

// = 16

54

Operatory przesunięć bitowych • Operatory:

>> (przesunięcie bitowe w prawo)

<< (przesunięcie bitowe w lewo)

powodują przesunięcie wszystkich bitów lewego operandu o liczbę miejsc określoną przez prawy operand. Oba operandy muszą być całkowite.

Wypadające bity giną, a wchodzące przyjmują wartość 0 (Przy operatorze >> dla wartości ujemnych, z lewej strony wchodzą 1 - powielenie bitu znaku)

• Efekt działania operatora >> jest zbliżony do podzielenia liczby przez 2, a operatora << do pomnożenia.

• Przykłady:

50 << 1 // 50(32b): 00000000000000000000000000110010

// 100 : 00000000000000000000000001100100

-50 >> 2 // -50: 11111111111111111111111111001110

// -12: 11111111111111111111111111110011 55

Operatory działań z przypisaniem • Operator += (operator zwiększenia)

Zapis: x += 5;

oznacza: x = x + 5;

• Zaletą tego podejścia jest skrócenie zapisu oraz przyspieszenie

działania tej operacji (wygenerowany kod maszynowy jest krótszy).

Nieco obniża się czytelność tych operacji.

• Analogiczne operatory to:

-= *= /= %= &= |= ^= <<= >>=

• Przykład:

int x=5;

x+=10; // x=x+10; 15

x/=2; // x=x/2; 7 (dzielenie całkowite)

x%=5; // x=x%5; 2 (reszta z dzielenia)

x>>=1; // x=x>>1; 1 56

Wartość operacji przypisania • Wartość operacji przypisania równa jest zmiennej po przypisaniu, np. wyrażenie

a=5+5 ma wartość 10.

• Operacja przypisania może być więc użyta w wyrażeniach, np.:

zapis: y = 3+ (a=10); // nawias konieczny

oznacza: a = 10;

y = 3+a; // czyli 13

• Powoduje to skrócenie zapisu oraz przyspieszenie działania tych operacji kosztem spadku czytelności

• Analogicznie działają operatory:

-= *= /= %= &= |= ^= <<= >>=

• Przykłady:

int x, y;

y= 1+ (x=4); // x=4, y=1+4=5;

y= 2* (x+=11); // x=15, y=2*15=30;

y+= 5+(x/=3); // x=5, y=y+5+5=40

y= (x*=2) + (x+=3); // wartości niepewne (kolejność)!

57

Inkrementacja i dekrementacja • Inkrementacja to zmiana pewnej wartości na następną.

W praktyce zwykle oznacza to jej zwiększenie o 1 Dekrementacja to operacja odwrotna (zmniejszenie o 1)

• W języku C i C++ operatory tych działań to: ++ i --

• Zapis: ++x; lub x++; oznacza: x = x+1;

--x; lub x--; oznacza: x = x-1;

• Preinkrementacja Predekrementacja:

Postinkrementacja Postdekrementacja:

• Przykłady:

int x, y=5;

x= y++ +1; // x= 6; y=6;

x= --y *10 // x=50; y=5

y = ++x x = x+1;

y = x;

y = --x; x = x-1;

y = x;

y = x++ y = x;

x = x+1;

y = x--; y = x;

x = x-1;

58

Operator warunkowy • Wykorzystuje się go w sytuacji gdy chcemy przypisać zmiennej

jedną z dwóch wartości w zależności od pewnego warunku.

• Jest to jedyny operator trzyargumentowy (warunek oraz dwie

wartości alternatywne)

• Zapisywany jest za pomocą symboli: ? i :

• Składnia:

(warunek) ? wartosc_gdy_niezero : wartosc_gdy_zero

Warunek to dowolne wyrażenie, istotne jest czy jego wartość wynosi zero czy nie. Nawias jest obowiązkowy.

• Przykłady:

int x, y=5;

x= (y>0) ? 1 : 0; // x=1, y=5

y= (x--) ? 100 : 0; // x=0, y=100

y= 25 + ((x<5) ? 25 : 10)); // x=0, y=25+25=50 59

Operator ,

• Umożliwia on podanie kilku wyrażeń w sytuacji, gdy oczekiwane

jest tylko jedno, np.

x= (1, b=2);

Wartość całego wyrażenia w nawiasie równa jest wartości

najbardziej prawego z wyrażeń oddzielonych przecinkami: 2

Wartości wyrażeń wyliczane są w kolejności od lewej do prawej

• Przykłady:

int x, y;

x= (y=1, y+5); // x=6, y=1

y= (1,2,3,4,5); // y=5

x+= (++x, y++); // x=12, y=6

60

Operator sizeof

• Operator ten pozwala uzyskać liczbę bajtów konieczną do

zapisania podanej wartości lub podanego typu

• Składnia:

sizeof(wyrażenie)

sizeof(nazwa typu)

• Przykłady:

int x,y;

x= sizeof (int); // zwykle 4

x= sizeof(10.0/3); // sizeof(double), zwykle 8

x= sizeof(y)*8; /// liczba bitów dla int: 32

61

Priorytety operatorów

• Kolejność interpretowania operatorów nie jest dowolna, regulują

ją tzw. priorytety

• Na przykład w wyrażeniu:

x + 5 * a

pierwsze zostanie wykonane mnożenie (ma wyższy priorytet),

kolejność ta jest w tym wypadku podyktowana zasadami algebry

• Jeżeli oba działania dotyczące pewnego operandu mają

jednakowy priorytet, kolejność ich wykonania zależy od

kompilatora, zazwyczaj od lewej do prawej

• Zmianę kolejności można wymusić za pomocą nawiasów ( )

62

Priorytety poznanych operatorów 1 ( ) nawiasy

2 ++ -- postfix

3 ++ -- ~ ! sizeof + - prefix oraz jednoargumentowe

4 (typ) konwersja

5 * / % multiplikatywne

6 + - addytywne

7 << >> przesunięcie bitowe

8 < > <= >= relacje

9 == != porównanie

10 & bitowe and

11 ^ bitowe xor

12 | bitowe or

13 && logiczne and

14 || logiczne or

15 ? : warunkowy

16 = *= /= %= += -= >>= <<= &= ^= |= przypisanie

17 , przecinek 63

PROGRAM W JĘZYKU C++

64

Elementy programu w C++

• Deklaracja symbolu - informacja dla kompilatora o rodzaju symbolu i jego typie.

• Rodzaje deklaracji:

– Deklaracje typów (m.in. struktury danych)

– Deklaracje zmiennych

– Deklaracje funkcji

• Definicja symbolu - oznacza utworzenie elementu opisywanego przez symbol

• Często deklaracja symbolu jest zarazem jego definicją, np. int x;

informuje kompilator o tym, że x to będzie wartość typu int, ale zarazem nakazuje utworzenie miejsca w pamięci na odpowiednią zmienną.

• Program w C++ składa się z ciągu deklaracji i definicji 65

Funkcja main

• Instrukcje definiujące działanie programu (akcje) mogą wystąpić jedynie wewnątrz funkcji, program musi więc zawierać co najmniej jedną funkcję.

• Program musi koniecznie zawierać funkcję o nagłówku

int main() lub main()

albo

void main()

W pierwszej wersji program musi zwrócić wartość do OS

• W pewnych sytuacjach nagłówek funkcji main przyjmuje bardziej złożoną postać

• Wykonanie programu rozpoczyna się na początku funkcji main

• Najkrótszy prawidłowy program w C++:

void main()

{ } // Niektóre kompilatory wymagają typu int! 66

Deklaracje globalne • Symbole globalne są deklarowane na zewnątrz funkcji

• Są dostępne od miejsca deklaracji aż do końca pliku programu (we wszystkich funkcjach i deklaracjach występujących poniżej)

• Są tworzone na początku programu (jeszcze przed rozpoczęciem wykonania funkcji main) i istnieją aż do jego zakończenia

• Zmienne globalne zawsze są inicjalizowane wartością 0

• Przykład:

int zero; // nie trzeba pisać =0

float pi = 3.1415;

float dwa_pi = 2*pi; // już tu można użyć

void main()

{

float P, r=10;

P = pi*r*r; // użycie zmiennej pi

} 67

Instrukcje

• Instrukcje służą do opisu działań, akcji wykonywanych przez

program, a także do sterowania procesem ich wykonania.

• Każda instrukcja musi być zakończona znakiem ;

• Podstawowym działaniem programu są obliczenia, a instrukcja,

która je wykonuje przyjmuje postać wyrażenia, (zwykle w

postaci przypisania)

• Przykłady:

x=a*b; // przypisanie

a+=(x>3); // działanie z przypisaniem

i++; // inkrementacja

a+1; // bez efektu ale prawidłowe

68

Instrukcja blokowa • W wielu sytuacjach potrzebne jest wydzielenie grupy instrukcji

(lub deklaracji) poprzez ich zgrupowanie w jeden blok.

• Służą do tego znaki { (początek bloku) i } (koniec bloku)

• W składni języka C++ blok jest rodzajem instrukcji

• Symbole zadeklarowane wewnątrz bloku zwane są lokalnymi, są ważne tylko do końca bloku i istnieją tymczasowo

• Przykład:

int x=5,y;

{ // początek bloku

int a,b; // zmienne lokalne

int z=3*x;

y=2*x;

} // koniec bloku

x=z; // błąd - zmienna z straciła ważność 69

Instrukcja pusta ;

• Instrukcja pusta oznacza, że w danej sytuacji nie ma być

wykonywane żadne działanie.

• Jej użycie bywa przydatne podczas korzystania z innych,

bardziej złożonych instrukcji

• Składnia:

; // instrukcja pusta

; ; // dwie instrukcje puste

x=5; // nie każdy średnik to instrukcja pusta!

70

Instrukcja warunkowa

• Pozwala na wykonanie pewnych instrukcji w zależności od

spełnienia podanych warunków

• Składnia:

if (wyrażenie)

jedna_instrukcja; // jeżeli wyrażenie!=0

wyrażenie

instrukcja

zero nie-zero

71

Instrukcja warunkowa (2)

• Wersja instrukcji warunkowej z klauzulą else:

if (wyrażenie)

jedna_instrukcja1; // jeżeli wyrażenie!=0

else

jedna_instrukcja2; // jeżeli wyrażenie==0

wyrażenie

instrukcja1 instrukcja2

zero nie-zero

72

Przykłady instrukcji warunkowych • if (delta==0)

x0=-b/(2*a);

• if (delta<=0)

; // instrukcja pusta

else

{ // jedna instrukcja (blokowa)!!!

x1=(-b-sqrt(delta)) / (2*a);

x2=(-b+sqrt(delta)) / (2*a);

}

• if (alfa) // czyli: if (alfa!=0)

x=1/sin(alfa);

else // w przeciwnym wypadku

x=0; // czyli jeżeli alfa==0 73

Przykłady instrukcji warunkowych (2)

• Wybór największej spośród trzech liczb a, b, c:

if (a>b)

if (a>c)

max=a;

else // tzn. jeżeli c>=a

max=c;

else // tzn. jeżeli b>=a

if (b>c)

max=b;

else // tzn. jeżeli c>=b

max=c;

Jedna

instrukcja

(warunkowa)

74

Przykłady instrukcji warunkowych (3) • Klauzula else wiązana jest zawsze z ostatnim if

• if (a>0)

if (a<b)

x=a;

else // czyli jeżeli a>=b

x=0;

• if (a>0)

{ // blok wymusi inne wiązanie ‘else’

if (a<b)

x=a;

}

else // czyli jeżeli a<=0

x=0;

75

Instrukcja wielokrotnego wyboru • W sytuacji, gdy w zależności od wartości pewnego wyrażenia

chcemy podjąć jedną z kilku wersji działania, należy sprawdzić kolejno wszystkie możliwe wartości, np.:

if (x==wartosc1)

instrukcja1;

else

if (x==wartosc2)

instrukcja2;

else

if (x==wartosc3)

instrukcja3;

else

instrukcja_domyślna; 76

Instrukcja wielokrotnego wyboru (2) • Zamiast serii instrukcji if można zastosować instrukcję

wielokrotnego wyboru switch

• Składnia:

switch (wyrażenie) // nawias obowiązkowy

{

case wartość1: instrukcje1;

break; // wyskok

case wartość2: instrukcje2;

break; // wyskok

case wartość3: instrukcje3;

break; // wyskok

default: instrukcje_domyślne;

}; // obowiązkowe nawiasy klamrowe i średnik

77

Instrukcja wielokrotnego wyboru (3) • Klauzulę default można pominąć

• Instrukcje dla danego przypadku nie muszą być połączone w blok

switch (wyrażenie)

{

case wartość1: instrukcja11;

instrukcja12;

instrukcja13;

break;

case wartość2: instrukcje2;

break;

case wartość3: instrukcje3;

break; // to można pominąć

}; 78

Instrukcja wielokrotnego wyboru (4)

• Można również pominąć podanie jakiejkolwiek instrukcji dla danego przypadku. Zostanie wykonana pierwsza napotkana instrukcja.

switch (x)

{

case 1:

case 2: ; // instrukcja pusta

case 3: instrukcja123; // wykona się dla 1,2 i 3

break;

case 4:

case 5: instrukcja45; // wykona się dla 4 i 5

};

79

Instrukcja wielokrotnego wyboru (5) • Pominięcie break powoduje, że wykonywane są kolejne

instrukcje z listy (inne przypadki!).

Przykład:

x=0;

switch (a)

{

case 1: x++;

case 2: x+=2;

case 3: x+=3;

};

Po wykonaniu powyższej instrukcji:

dla a=1 -> x=6, dla a=2 -> x=5, dla a=3 -> x=3

80

Pętle

• W wielu sytuacjach istnieje potrzeba zaprogramowania pewnej

czynności, która powtarza się wielokrotnie w identycznej lub

podobnej postaci

• Konstrukcje programistyczne pozwalające na opis takich

sytuacji nazywają się pętlami

• Każda pętla pozwala na opis powtarzanej czynności (tzw.

wnętrze pętli) oraz posiada warunek powtórzenia lub

warunek wyjścia, który decyduje o zakończeniu powtarzania

• Warunek może być sprawdzany przed (na początku) lub po (na

końcu) wykonaniu wnętrza pętli

• W języku C++ istnieją trzy instrukcje pętli:

while do while for

81

Instrukcja while

• Pętla while używana jest w sytuacjach, gdy określone

działanie ma być wykonywane wielokrotnie dopóty, dopóki

podany warunek jest spełniony

• Sprawdzenie warunku odbywa się przed wykonaniem wnętrza,

więc możliwa jest sytuacja, że nie wykona się ono ani razu.

wyrażenie

działanie zero

nie-zero

82

Instrukcja while (2)

• Składnia:

while (wyrażenie)

jedna_instrukcja; // wykonaj jeżeli wyrażenie!=0

Jeżeli chcemy wykonać więcej instrukcji, łączymy je w blok:

while (wyrażenie)

{ // jedna instrukcja (blokowa)

instrukcja1;

instrukcja2;

...

}

83

Przykłady użycia instrukcji while • while (1) // pętla nieskończona

instrukcja;

• while (0)

instrukcja; // nie wykona się ani razu

• while (a<10)

{ // instrukcja blokowa

x=a*2;

a++;

}

• int i=1, x=0;

while (x+=i++ < 10) // sumowanie liczb aż będzie 10

; // instrukcja pusta 84

Instrukcja do while • Pętla do while różni się od pętli while tym, że sprawdzenie

warunku odbywa się po wykonaniu wnętrza

• Zawartość tej pętli zawsze wykona się co najmniej jednokrotnie

wyrażenie

działanie

zero

nie-zero

85

Instrukcja do while (2) • Składnia:

do

jedna_instrukcja;

while (wyrażenie); // jeżeli wyrażenie==0 -> zakończ

Dla grupy instrukcji: do

{ // jedna instrukcja (blokowa)

instrukcja1;

instrukcja2;

...

}

while (wyrażenie); 86

Przykłady użycia instrukcji do while int a=6; // Obliczanie silni z wartości a

unsigned long silnia=1;

do

{

silnia=silnia*a;

a--;

}

while (a>0);

do // wczytywanie liczby dodatniej

cin >> x; // z klawiatury

while (x<=0); // jeżeli ujemna - jeszcze raz

87

Instrukcja for

• Instrukcja for w większości języków programowania zwana jest

instrukcją iteracyjną i stosowana w przypadku, gdy znana jest

raczej liczba powtórzeń pętli niż warunek jej zakończenia

• Jej cechą szczególną jest fakt kontroli numeru iteracji

(powtórzenia)

Typowa postać (język Pascal):

for i:=a to b do instrukcja

(dla i zmieniającego się od a do b wykonuj instrukcję)

• W języku C++ instrukcja for również najczęściej stosowana

jest jako instrukcja iteracyjna ale jej składnia jest bardzo

elastyczna i pozwala na bardzo szeroki wachlarz zastosowań

88

Instrukcja for (2)

• Składnia:

for (instr_początkowa; wyrażenie; instr_iteracyjna)

jedna_instrukcja;

• Instrukcja początkowa - instrukcja wykonywana jednokrotnie, przed wykonaniem pętli i sprawdzeniem warunku

• Wyrażenie - warunek kontynuacji pętli, sprawdzany przed wyrażenie==0 - wnętrze pętli wykona się, wyrażene!= 0 - pętla się zakończy (jak w pętli while). Warunek sprawdzany jest przed wykonaniem pętli, a więc wnętrze pętli i instrukcja iteracyjna mogą nie zostać wykonane ani razu (podobnie jak w instrukcji while)

• Instrukcja iteracyjna - dodatkowa instrukcja wykonywana po każdym wykonaniu wnętrza pętli

89

Instrukcja for (3)

wyrażenie

Instrukcja

początkowa

zero

nie-zero

Działanie

Instrukcja

iteracyjna

90

Przykłady użycia instrukcji for • Typowe zastosowania pętli for jako instrukcji iteracyjnej:

for (i=0; i<10; i++) // kolejność rosnąca

x=i*10; // i zawiera numer powtórzenia (od 0)

for (x=100; x>0; x--) // kolejność malejąca

{

if (x==50) // instrukcja warunkowa

x--; // „ominięcie” iteracji

// wykorzystaj x //odliczanie 100 do 1 oprócz 50

}

silnia=1;

for (x=10; x; x--) // warunek nie musi być relacją

silnia*=x; 91

Inne przykłady użycia instrukcji for

for (;a!=5;a++) // bez instrukcji początkowej

instrukcja;

for (a=0;;a++) // bez warunku (nieskończona)

instrukcja;

for (i=0;i<10;) // bez instrukcji iteracyjnej

i+=3; // lepiej użyć pętli while

for (;;) // prosta pętla nieskończona

instrukcja;

92

Instrukcja break • Instrukcja break (wyskok) powoduje natychmiastowe

przerwanie wykonania instrukcji: for, while, do while lub switch i przejście do instrukcji następnej

• Umożliwia ona zakończenie pętli niezależne od narzuconego warunku, zwłaszcza przydatne przy pętlach nieskończonych.

• Przydatna w sytuacji, gdy dalsze wykonywanie pętli straciło z jakiegoś powodu sens

• Przykład:

liczba=57; // szukamy pierwiastka z 'liczba'

for (i=1; i<=10000; i++) // szukamy aż do 10000

if (i*i>=liczba)

{

x=i; // już znaleźliśmy

break; // dalej nie ma sensu

} 93

Instrukcja continue • Instrukcja continue powoduje natychmiastowe przerwanie

wykonania wnętrza pętli for, while i do while i przejście do wykonania następnego powtórzenia pętli

• Przydatna w sytuacji, gdy dalsze wykonywanie działań dla tego powtórzenia nie ma sensu lub gdy chcemy „ominąć” któreś z powtórzeń

• Przykład

while (x<100)

{

if (x%10==0) // x podzielne przez 10

{

x++;

continue; // ominięcie „okrągłych” x

}

a=x+5;

x++;

} 94

Instrukcja return • Instrukcja return powoduje natychmiastowe przerwanie

aktualnie wykonywanej funkcji.

• Jeżeli jest to funkcja main, następuje zakończenie programu

• Jeżeli nagłówek funkcji tego wymaga, należy po słowie return

podać zwracaną wartość odpowiedniego typu

• Jeżeli nagłówek funkcji main ma postać:

void main()

wystarczy samo return;,

Jeżeli ma postać: int main()lub main()

należy wywołać return wartość_typu_int;

95

„Wyklęta” instrukcja goto • Instrukcja goto powoduje skok do miejsca programu

oznaczonego podaną etykietą.

• Nie zaleca się użycia tej instrukcji ze względu na to, że jest sprzeczne z zasadami programowania strukturalnego i ma negatywny wpływ na czytelność kodu.

• Składnia:

goto etykieta;

W dokładnie jednym miejscu programu powinna znajdować się instrukcja poprzedzona podaną etykietą i znakiem :, np.:

if (x==0)

goto zero; // skok do etykiety zero

a=1/x;

zero: // etykieta

a=1;

96

Zestawienie poznanych instrukcji

1. Wyrażenie (obliczenia)

2. Warunek (if)

3. Wielokrotny wybór (switch)

4. Pętla (while, do while, for)

5. Skoki (break, continue, return, goto)

6. Instrukcja pusta

7. Instrukcja blokowa

97

FUNKCJE

98

Co to jest funkcja?

• Funkcja to wydzielony fragment programu (podpogram), któremu

nadano nazwę.

Może on zawierać deklaracje lokalne oraz instrukcje.

• Funkcję można wywołać w dowolnym miejscu programu (poniżej

jej deklaracji!) podając tylko jej nazwę z nawiasami ( )

• Funkcji używa się w sytuacji, gdy:

– Pewien niemały fragment programu powtarza się

kilkakrotnie w jednakowej lub zbliżonej formie

– Program jest długi i skomplikowany, dobrze jest wtedy

podzielić go na mniejsze zadania, budować całość z

mniejszych „klocków”

• W języku C++ działanie każdej funkcji wiąże się z

wygenerowaniem (zwracaniem) pewnej wartości, której typ

musi zostać określony w jej nagłówku. 99

Składnia definicji funkcji

• typ nazwa() // nagłówek (nawiasy konieczne)

{ // nawiasy klamrowe konieczne

deklaracje lokalne

instrukcje

}

• Jeżeli funkcja ma nie zwracać żadnej wartości, nadajemy jej typ void. Można ją wtedy nazwać procedurą.

• Jeżeli zwracany jest typ inny niż void, przed zakończeniem działania funkcji musi pojawić się instrukcja

return wyrażenie; // typ zgodny z nagłówkiem!

• Jeżeli funkcja jest typu void, można (ale nie trzeba) w dowolnym jej miejscu użyć instrukcji

return;

która powoduje natychmiastowe zakończenie działania funkcji.

100

Przykłady definicji funkcji (1) void pusta() // funkcja minimum

{ }

float pi() // funkcja minimum z typem

{

return 3.1415;

}

int signum() // sprawdzenie znaku

{ // globalnej zmiennej ‘a’

if (a<0) // (musi być zadeklarowana)

return -1;

else

return (a>0);

} 101

Przykłady definicji funkcji (2)

void odliczanie() // wypisanie na ekranie

{ // liczb od 10 do 1

int i; // zmienna lokalna

for (i=10; i>0; i--)

cout << i << endl; // wypisanie elementu

} // tu nie musi być ‘return’

void wypisz_dodatnia()

{

int liczba;

cout << "Podaj liczbę dodatnią: ";

cin >> liczba;

if (liczba <=0)

return; // wyskoczenie z funkcji

cout << liczba; // wypisanie liczby

} 102

Typowe błędy w definicji funkcji • Brak return w funkcji innej niż void

int BrakReturn1()

{ cout >> "Działa funkcja" }

// funkcja zwróci wartość przypadkową!!!

int BrakReturn2() // nie ma ‘return’ dla

{ if (i==5) // wszystkich możliwych

return 1; } // przypadków

• Zły typ zwracanej wartości

void ZlyTyp1()

{ return 1; } // funkcja jest typu void!

int ZlyTyp2()

{ return "abc"; } // niezgodność typów

int ZlyTyp3()

{ return 2.5; } // funkcja zwróci 2 !!! 103

Wywołanie funkcji

• W celu uruchomienia gotowej funkcji należy umieścić jej wywołanie wewnątrz innej funkcji (np. w funkcji main)

• Składnia:

nazwa_funkcji(); // nawiasy konieczne!

W tym przypadku wartość zwracana przez funkcję jest

ignorowana.

• Jeżeli funkcja jest innego typu niż void, można jej wywołanie

umieścić w wyrażeniu w celu wykorzystania wartości zwracanej

przez tę funkcję, np.

x=2*nazwa_funkcji()+1;

Oznacza to wykonanie funkcji i użycie zwróconej przez nią

wartości do obliczeń.

104

Wywołanie funkcji - schemat

main()

Ustaw();

Zeruj();

Ustaw();

Zeruj();

Zeruj()

a1=b1=0;

a2=b2=0;

Ustaw()

a1=b1=1;

a2=b2=2;

105

Przykłady wywołania funkcji (1)

int a1, a2, b1, b2; // zmienne globalne

void Ustaw()

{

a1=b1=1;

a2=b2=2;

}

void Zeruj()

{

a1=b1=a2=b2=0;

}

void main()

{

Ustaw(); // wywołanie funkcji ‘Ustaw’

Zeruj(); // wywołanie funkcji ‘Zeruj’

Ustaw();

Zeruj();

} 106

Przykłady wywołania funkcji (2)

float pi() // ta funkcja musi być pierwsza

{

return 3.1415;

}

float dwapi()

{

return 2* pi(); // wywołanie ‘pi’

}

void main()

{

float Pole, Obwod, r=5;

Pole = pi() * r*r; // wywołanie ‘pi’

Obwod= dwapi() * r; // wywołanie ‘dwapi’

} 107

Przekazywanie danych z/do funkcji • Większość funkcji potrzebuje do swego działania danych

wejściowych. Przekazanie ich do funkcji może odbywać

się poprzez:

– Zmienne globalne, które są dostępne zarówno w funkcji

wywołującej jak i w funkcji wywoływanej

– Parametry wywołania funkcji

• Dane wyjściowe, "wyprodukowane" przez funkcję

wywoływaną, można przekazać do funkcji wywołującej

poprzez:

– Wartość zwracaną w instrukcji return (tylko jedna wartość)

– Zmienne globalne

– Zapisanie danych pod adresem (wskaźnik lub referencja)

otrzymanym od funkcji wywołującej. 108

Przekazywanie wartości przez zmienne

globalne

float a,b,c,delta, x1,x2;

void Obliczdelta() // funkcja wymaga danych!

{

delta=b*b-4*a*c;

}

void ObliczDwa()

{

x1=(-b-sqrt(delta)) / (2*a); // wynik do zmiennych

x2=(-b+sqrt(delta)) / (2*a); // globalnych

}

void main()

{

a=2, b=3, c=-1; // dane dla funkcji!!!

Obliczdelta();

if (delta>0)

{

ObliczDwa();

cout << "x1=" << x1 << endl; // wypis wyniku

cout << "x2=" << x2 << endl;

} 109

Zmienne globalne i lokalne

• Nie wolno w tym samym zakresie zdefiniować dwóch symboli o tej samej nazwie, bez względu na ich typ

• Symbole definiowane wewnątrz funkcji nazywane są lokalnymi

• Symbole lokalne tworzone są podczas rozpoczęcia wykonania bloku i niszczone przy opuszczeniu najwęższego bloku, w którym zostały zadeklarowane

• Zmienne lokalne powstają na tzw. stosie i po utworzeniu mogą zawierać wartości przypadkowe, podczas gdy zmienne globalne zawsze inicjalizowane są zerami.

• Symbol lokalny o mniejszym zakresie może mieć tę samą nazwę co symbol o szerszym zakresie (np. globalny), ale utrudnia lub uniemożliwia jego wykorzystanie, przesłania go

• Do przesłoniętego symbolu globalnego można uzyskać dostęp dzięki operatorowi zakresu :: umieszczonemu przed nazwą, np. ::zmienna

110

Przykłady deklaracji zmiennych

globalnych i lokalnych int a=1,b=2; // deklaracje symboli globalnych

float a; // błąd: powtórzona nazwa ‘a’

int b() // błąd: powtórzona nazwa ‘b’

{}

void fun()

{

int a=3; // zmienne lokalne

float b=5; // przesłaniające globalne

{

int a=10; // zmienna podwójnie lokalna

cout << a; // 10

}

cout << a; // 3

cout << ::a; // 1

}

111

Parametry funkcji • Parametry wywołania funkcji pozwalają na przekazanie danych

wejściowych dla funkcji w sposób jawny i czytelny.

Jeżeli funkcja została zadeklarowana z parametrami, podczas

wywołania muszą być one podane

• Składnia definicji funkcji z parametrami:

typ NazwaFunkcji(typ1 par1, typ2 par2, itd.)

{ ... }

• Wywołanie funkcji z parametrami:

NazwaFunkcji(wartość_typu1, wartość_typu2, itd.)

• Liczba wartości podanych w wywołaniu i ich typy muszą być zgodne

z nagłówkiem funkcji

• Parametry funkcji można rozumieć jako deklaracje zmiennych

lokalnych, które „wypełniane” są przy uruchomieniu funkcji kopiami przekazanych wartości 112

Przykłady funkcji z parametrami float PoleKola(float r) // funkcja z 1 parametrem

{

return 3.1415*r*r; // użycie parametru 'r

}

float Delta(float a, float b, float c) // 3 parametry

{

return b*b-4*a*c; // użycie parametrów

}

void main()

{

float aa=1,bb=3,cc=-2, r=2; // inne r!

float P, D, x;

P=PoleKola(1);

P=PoleKola(r+2);

if (Delta(aa,bb,cc)==0)

x=-b/(2*a);

} 113

Przesłanianie zmiennych globalnych

przez parametry funkcji

int a=1;

void funkcja(int a) // ta sama nazwa ‘a’ (a=2)

{

a=a+5; // a=7

cout << a; // 7, użycie zmiennej lokalnej

}

void main()

{

cout << a; // 1, użycie zmiennej globalnej

funkcja(2);

cout << a; // 1, zmienna globalna bez zmian

}

114

Deklaracja funkcji (prototyp) • Do użycia istniejącej funkcji programiście (a także kompilatorowi

podczas kompilacji) potrzebna jest tylko wiedza o jej nazwie,

zwracanym typie oraz parametrach wywołania.

Tzw. ciało funkcji niezbędne jest dopiero na etapie tzw.

linkowania, a więc może znajdować się w dalszej części

programu lub nawet w oddzielnym pliku (np. w bibliotece).

• Niezbędnego opisu funkcji dostarcza jej deklaracja, zwana też

prototypem lub (ang. header - nagłówek). Składnia:

typ NazwaFunkcji(typ1 par1, typ2 par2, ...);

// uwaga na średnik !!!

• Jeżeli taka deklaracja znajdzie się w programie, to w dalszym

ciągu pliku można wywoływać tę funkcję normalnie, tak jakby

była w pełni zdefiniowana.

• Kompletna definicja tej funkcji musi jednak gdzieś się

znajdować: albo pod spodem albo w innym pliku projektu. 115

Przykład użycia prototypu

float potega(float a, int wyk); // prototyp

void main()

{

float x=potega(2,5); // wywołanie funkcji

}

float potega(float a, int wyk) // definicja funkcji

{ // zgodność nagłówków!

int i;

float p=1;

for (i=0; i<wyk; i++)

p*=a;

return p;

}

116

Biblioteki

• W wielu programach, jakie są tworzone powtarza się znaczna liczba różnych elementarnych zadań. W celu ułatwienia pracy programisty wiele z nich zostało już wcześniej zaprogramowane i zebrane w różnego rodzaju bibliotekach, zwykle dołączonych do kompilatora.

• Biblioteki zawierają przede wszystkim gotowe funkcje

• W celu skorzystania z funkcji bibliotecznej należy wcześniej albo samodzielnie wpisać jej prototyp (np. na podstawie dokumentacji) albo dokonać importu pliku nagłówkowego (ang. header file) za pomocą dyrektywy #include

• Próba wywołania w programie funkcji bibliotecznej bez podania wcześniej prototypu spowoduje błąd kompilacji (nieznany symbol)

• Zwykle domyślnie ustawiona jest opcja kompilatora nakazująca poszukiwanie we wskazanych bibliotekach tych funkcji, które w programie znalazły się jedynie w formie deklaracji (prototypu)

117

Preprocesor • Integralną częścią koncepcji języka C/C++ jest preprocesor czyli moduł

dokonujący pewnych operacji na tekście programu jeszcze przed jego

kompilacją

• Działa on w oparciu o tzw. dyrektywy, które są poleceniami rozpoczynającymi się od znaku # (hash), który musi być pierwszym

znakiem w danej linii programu

(nie licząc spacji, tabulacji lub ewentualnie komentarza ograniczonego parą znaków /* i */ )

• Standardowe dyrektywy to: #include

#define #undef

#if #ifdef #ifndef

#else #elif #endif

#line #error

#pragma 118

Dyrektywa #include • Składnia:

#include <nazwa_pliku>

lub #include ”nazwa_pliku”

• Dyrektywa ta powoduje, że preprocesor w jej miejsce wkleja podany plik w całości

• Zazwyczaj wklejanymi plikami są pliki nagłówkowe z rozszerzeniem .h . Zawierają one m.in. prototypy funkcji

• Podany plik poszukiwany jest w katalogach wyszczególnionych w opcjach kompilatora jako „include directories”. Znajdują się tam pliki nagłówkowe związane z bibliotekami kompilatora

• Jeżeli nazwę pliku podano w cudzysłowach (” ”), plik poszukiwany jest najpierw w katalogu, w którym znajduje się plik programu, a dopiero później w katalogach standardowych Wkleja się w ten sposób własne pliki nagłówkowe zawierające m.in. prototypy funkcji zdefiniowanych w różnych plikach 119

Dyrektywa #define • Składnia:

#define NAZWA ciąg znaków

lub #define NAZWA // ciąg pusty

• Każde wystąpienie w tekście programu symbolu NAZWA zostanie przed

kompilacją zamienione na podany ciąg znaków

• Jeżeli chcemy aby ciąg znaków zawierał więcej niż jedną linię, można

go kontynuować w linii następnej pod warunkiem, że na końcu poprzedniej pojawi się znak \ (backslash),

znak ten nie będzie wklejany jako część ciągu:

#define NAZWA ciąg znaków \

kontynuacja ciągu znaków\

kontynuacja ciągu znaków

• Dyrektywę tę wykorzystuje się również do tworzenia tzw. makr 120

Przykłady użycia dyrektywy #define • Tworzenie stałych

#define PI 3.1415

float x=PI/2;

#define N 10

...

for (i=1; i<N; i++)...

• Zamiana wybranych słów na inne

#define float double

W programie poniżej tej dyrektywy wszystkie napisy float zmienią się na double

• Usuwanie wybranych słów #define unsigned // zamiana na ciąg pusty

...

unsigned int x; // unsigned zniknie 121

Biblioteka iostream

• Każdy algorytm czy program wymaga wprowadzania danych

wejściowych i wyprowadzania wyniku

• Podstawowym sposobem wprowadzania danych jest ich wpisywanie z

klawiatury, a wyprowadzania danych - wyświetlanie na ekranie

• Tzw. operacje wejścia-wyjścia realizowane są za pomocą funkcji (lub

obiektów) z bibliotek. W języku C++ zwykle korzysta się w tym celu ze zbioru nagłówkowego iostream.h

(W języku C stosowano raczej zbiór nagłówkowy stdio.h)

• Biblioteka ta umożliwia wprowadzanie danych z klawiatury oraz ich

wyświetlanie na ekranie

• Biblioteka iostream.h jest skonstruowana w sposób obiektowy,

udostępnia więc przede wszystkim klasy i obiekty, ale również funkcje

(jako składniki klas) 122

Wyświetlanie tekstu: obiekt cout • cout jest obiektem odpowiadającym standardowemu strumieniowi

wyjściowemu, który domyślnie połączony jest z wyświetlaniem tekstu na

ekranie.

• Wyświetlenie tekstu uzyskuje się dzięki przedefiniowanemu operatorowi <<

• Składnia: cout << wyświetlany_element;

cout << element1 << element2 << element3;

• W celu przechodzenia na początek następnej linii zdefiniowano symbol endl

cout << 100.99 << endl;

• Wyświetlenie tekstu podanego wprost w programie wymaga

umieszczenia go w cudzysłowie:

cout << ”Liczba PI= ”<< 3.14 << endl; 123

Wprowadzanie danych: obiekt cin • cin jest obiektem odpowiadającym standardowemu strumieniowi

wejściowemu, który domyślnie połączony jest z wprowadzanie danych z

klawiatury

• Wprowadzanie danych uzyskuje się dzięki specjalnie zdefiniowanemu operatorowi >>

• Wprowadzanie danych odbywa się zawsze do pamięci, a więc musimy

podać nazwę zmiennej, która ma zapamiętać dane

• Składnia: int a,b,c;

cin >> a; // po znaku >> musi być zmienna

cin >> a >> b >> c;

• Zwykle najpierw wyświetla się żądanie podania danych, np.

cout << „Podaj swoją cenę: ”;

cin >> cena; 124

TABLICE I WSKAŹNIKI

125

Dane złożone

• Często do opisu pewnego obiektu lub grupy obiektów nie

wystarcza pojedyncza zmienna

• konieczne jest wtedy zdefiniowanie zbioru różnych zmiennych,

które tworzą pewną całość

• W języku C++ dla opisu takiej sytuacji stosuje się typy złożone,

które pozwalają pod jedną nazwą zmiennej pozwalają ukryć

wiele prostych wartości

• Najważniejsze typy złożone:

– tablica

– struktura (rekord)

– klasa (programowanie obiektowe)

126

Tablice

• Stosowane są gdy chcemy w sposób uporządkowany zapisać zbiór wartości tego samego typu, np.

– oceny poszczególnych studentów z grupy

– ceny produktów w ofercie

– nazwiska znajomych

• Każdy element tablicy ma swój indeks (numer), który jednoznacznie go identyfikuje

• Można powiedzieć, że tablica to uporządkowany zbiór zmiennych jednakowego typu

• Zdefiniowanie tablicy wymaga podania typu elementu oraz liczby elementów

• Ze względu na sposób uporządkowania elementów można wyróżnić tablice jednowymiarowe, dwuwymiarowe, itd.

• Rozmiar tablicy (liczba elementów) musi być określona już w trakcie pisania programu

127

Deklaracja tablicy jednowymiarowej

• Składnia:

typ nazwa[liczba_elementów]; // całkowita>0

• Przykłady

float oceny_grupy[30];

int duzy_lotek[6];

int a,b[10],c,d[2]; // b,d - tablice

• Inicjalizacja tablicy:

float punkt3D[3] = {0, 2, 1.5 };

int duzy_lotek[3+3]= {2,4,6,7}; // można mniej

Nie można podać więcej wartości niż zdefiniowano w tablicy.

b[0] b[1] b[2] b[3] b[4] b[5] b[6] b[7] b[8] b[9]

tablica b: 0 0 0 0 0 0 0 0 0 0 128

Korzystanie z tablicy

• Dostęp do tablicy odbywa się dla każdego elementu osobno, każdy z nich można wykorzystywać jak normalną zmienną, należy w tym celu podać nazwę tablicy oraz wyrażenie określające indeks pomiędzy znakami: ‘[‘ i ‘]‘

• Indeksy elementów w C++ zaczynają się zawsze od 0 !!! Jeżeli wielkość tablicy to N, indeksy elementów to 0 ... N-1

• Nie wolno korzystać z elementów o indeksach wykraczających poza zadeklarowany zakres

• Przykłady: int x,tablica[10];

tablica[0]=1;

tablica[5]=2;

tablica[6]=tablica[5]+2;

x=2*tablica[6];

tablica[x+1]=10; // obliczenie indeksu

tablica[10]=0; // błąd: poza tablicą!!!

jakie wartości są teraz w tablicy? 129

Algorytmiczny dostęp do tablicy

• Wykorzystanie zmiennych do określania indeksu umożliwia

dostęp do poszczególnych elementów za pomocą pętli, zwykle

jest to pętla for

• Przykładowy fragment programu: float tablica[10];

for (int i=0; i<5; i++) // zerowanie elementów 0-4

tablica[i]=0;

for (i=5; i<10; i++)

tablica[i]=i-1; // el. 5-9, wartości 4-8

tablica po wykonaniu programu: indeks 0 1 2 3 4 5 6 7 8 9

wartość 0 0 0 0 0 4 5 6 7 8

130

Sumowanie elementów tablicy

#include <iostream.h>

#define N 10

void main()

{

int i, tab[N]; // N to nie zmienna!

for (i=0; i<N; i++) // pobieranie danych

{

cout << "Podaj element " << i << ":";

cin >> tab[i];

}

int suma=0; // obowiązkowe wyzerowanie

for (i=0; i<N; i++)

suma+= tab[i]; // czyli suma=suma+tab[i]

cout << ”Średnia wartość:” << (float)suma/N << endl;

} 131

Wartość maksymalna / minimalna

float tab[10];

... // pomijamy wprowadzanie

float min=tab[0], max=tab[0];

int nrmin=0, nrmax=0;

for (int i=0; i<N; i++) // szukamy największego

if (tab[i]>max)

{

max=tab[i];

nrmax=i;

}

for (i=0; i<N; i++) // szukamy najmniejszego

if (tab[i]<min)

{

min=tab[i];

nrmin=i;

}

cout << ”Największy jest element: ” << nrmax;

cout << ", wynosi: " << max << endl;

cout << ”Najmniejszy jest element: ” << nrmin;

cout << ", wynosi: " << min << endl; 132

Szukanie i zliczanie elementów spełniających podane kryterium

float tab[10];

... // pomijamy wprowadzanie

int licznik=0; // wyzerowanie licznika

for (int i=0; i<N; i++)

{

cout << "element " << i << ": ";

if (tab[i]>0) // szukamy liczb dodatnich

{

cout << tab[i] << " jest dodatni\n";

licznik++;

}

}

cout << "Dodatnie : " << licznik << endl;

cout << "Niedodatnie: " << N-licznik << endl;

133

Tablica jako parametr funkcji

• Jeżeli funkcja przyjmuje tablicę jako parametr, wyjątkowo nie jest ona przesyłana jako kopia. Przesyłany jest adres tablicy źródłowej. Funkcja może więc zmienić zawartość tej tablicy

• W nagłówku funkcji można pominąć rozmiar tablicy ponieważ nie jest to deklaracja nowej tablicy (pamięć już przydzielono)

• Przykład:

void zeruj(float tab[], int n)

{

for (int i=0; i<n; i++)

tab[i]=0.0;

}

void main()

{

float tablica[10];

zeruj(tablica, 10);

}

134

Deklaracja tablicy dwuwymiarowej

• Można myśleć o takiej tablicy jak o macierzy prostokątnej

• Zawiera więc ona NxM elementów, które uporządkowane są w

N kolumn i M wierszy

• W składni języka C++ należy ją rozumieć jako tablicę tablic

• Składnia:

typ nazwa[M][N]; // tablica M tablic N-element.

• Przykład:

float oceny[30][5]; // 30 studentów po 5 ocen

• Dostęp do elementu (wiersz i, kolumna j):

nazwa[i][j];

np. oceny [1][3];

• Każdy z indeksów numerowany jest od 0!

30 ...

5

135

Korzystanie z tablicy dwuwymiarowej

• W celu przetworzenia wszystkich elementów tablicy z reguły

używa się dwóch zagnieżdżonych pętli for (pętla w pętli)

• Przykład: utworzenie i wydruk macierzy jednostkowej 10x10:

float macierz [10][10];

for (int i=0; i<10; i++) // kolejne wiersze

for (int j=0; j<10; j++) // kolejne kolumny

if (i==j)

macierz[i][j]=1;

else

macierz[i][j]=0;

// krócej: macierz[i][j]= (i==j);

for (i=0; i<10; i++)

{

for (j=0; j<10; j++)

cout << macierz[i][j] << " ";

cout << endl;

} 136

Tablica dwuwymiarowa jako parametr

funkcji

void zerujmacierz(float tab[][5], n) // n wierszy

{ for (int i=0; i<n; i++) // m musimy znać

for (int j=0; j<5; j++)

tab[i][j]=0.0;

}

void zerujwiersz(float tab[], m) // m - dł. wiersza

{

for (int i=0; i<n; i++)

tab[i]=0.0;

}

void main()

{

float tablica[10][5];

zerujmacierz(tablica,30);

zerujwiersz(tablica[2], 5); // zerujemy wiersz 2

}

137

Wskaźniki

• W wielu sytuacjach istnieje potrzeba przechowywania lub

przesyłania informacji na temat położenia zmiennej w pamięci.

• W języku C/C++ służą do tego zmienne o specjalnych typach

danych zwanych wskaźnikami (ang. pointer)

Zawierają one informacje o:

– adresie danej w pamięci -

– typie danej (a więc i o jej rozmiarze w bajtach)

• Każdemu z typów podstawowych można przypisać typ

wskaźnikowy przeznaczony do pokazywania na zmienne tego typu. Typ wskaźnikowy określany jest za pomocą operatora *,

jako typ * , np. wskaźnik do zmiennej typu int to int *

• Pobranie adresu zmiennej odbywa się za pomocą operatora &,

a pobranie wartości z adresu, na który pokazuje wskaźnik - za pomocą operatora * stojącego po jego lewej stronie

• Uwaga! Wskaźnik zawsze na coś pokazuje! 138

Wskaźniki - przykład

void main()

{

int a=10,b=20;

int *iwsk; // wskaźnik na int

iwsk=&a; // iwsk pokazuje teraz na a

b= *iwsk+1; // pobranie wartości spod adresu

iwsk=&b; // iwsk pokazuje teraz na b

float x, *fwsk; // fwsk jest typu float*

iwsk= &x; // błąd - inny typ wskaźnika!!!

fwsk=iwsk; // j.w.

fwsk=&x;

x=5;

cout << *fwsk<< endl; // wypisze 5

x=6;

cout << *fwsk<< endl; // wypisze 6 !

} 139

Operacje na wskaźnikach

• Porównywanie (operatory ==, !=, <, >, <=, >=) porównywane są adresy zapisane we wskaźnikach, ale typy bazowe muszą być jednakowe

• Odejmowanie wskaźników można odjąć dwa wskaźniki o jednakowych typach bazowych. Wynikiem będzie różnica adresów podzielona przez rozmiar typu bazowego (ile elementów dzieli te adresy)

• Dodawanie/odejmowanie od wskaźnika wartości całkowitej oznacza przesunięcie zapisanego adresu o podaną liczbę elementów. Liczba bajtów przesunięcia zależy od rozmiaru typu bazowego. Można korzystać również z operatorów ++ i --

• Wskaźnik zerowy (NULL) wartość 0 jako jedyna traktowana jest jako nie wskazująca na nic. Symbol NULL zdefiniowany jest w stdio.h, stdlib.h, i w iostream.h

140

Przykłady operacji na wskaźnikach

int *wsk1, *wsk2=NULL;

*wsk1=5; // uwaga! wskaźnik przypadkowy!!!

*wsk2=5; // tutaj zareaguje system

int a,b,c;

wsk1=&a;

wsk2=&c;

cout << "Adres a: " << wsk1 << end; // można wypisywać

cout << "Następny po a: " << wsk1+1 << endl;

cout << "Różnica c i a to: " << wsk2-wsk1 << endl;

cout << "większy: ((wsk1>wsk2) ? wsk1 : wsk2) << endl;

141

Typ void *

• Jest to wskaźnik uniwersalny, który może pokazywać na każdy typ danych

• Nie posiada jednak informacji o typie, a więc i o rozmiarze pokazywanego

elementu

• Nie można jednak stosować wobec niego operatora * (odczytywać danej, na

którą pokazuje)

• Nie można stosować do niego dodawania stałej, ani odejmować takich

wskaźników (nieznany jest rozmiar elementu)

• Można do niego przypisać wartość dowolnego innego wskaźnika

(niejawna konwersja typu)

• Przypisanie wartości wskaźnika void * do innego wskaźnika, a także odczyt

pokazywanej danej są możliwe ale wymagają jawnej konwersji

• Stosuje się go do wskazywania danych o nieokreślonej postaci

142

Przykłady użycia void *

void *adres;

int *iwsk;

float *fwsk;

int i=5,j;

float f;

adres=&i; // tu nie trzeba konwersji

iwsk=adres; // nie uda się

iwsk=(int *) adres; // konwersja jawna - uda się!

fwsk=(float *)adres; // to też się uda!

j=*adres; // to się nie uda

j=*iwsk;

f=*fwsk; // ciekawe co jest w f ???

143

Wskaźnik a tablica

• Nazwa tablicy (bez nawiasów []) jest wskaźnikiem do jej

zerowego (początkowego) elementu

• Jest to wskaźnik, którego wartości nie można zmienić (wskaźnik

stały). Można jednak używać go w różnych wyrażeniach

• Do wskaźnika wolno użyć nawiasów [], tak, jak gdyby był on

zadeklarowany jako tablica jednowymiarowa

• Przykłady:

int tab[10];

int *poczatek=tab; // wskazuje na tab[0]

int *koniec=tab+9; // wskazuje na tab[9]

int numer2= tab[2]; // tablica normalnie

numer2= *(tab+2); // tab. jako wskaźnik

numer2= *(poczatek+2); // wskaźnik normalnie

numer2= poczatek[2]; // wskaźnik jako tab

tab=&numer2; // błąd! wskaźnik stały

144

Typ char - stałe i zmienne

• Jest to typ znakowy, przydatny do przetwarzania tekstu, obsługi

klawiatury i ekranu

• Pozwala na zapamiętanie jednego znaku w postaci tzw. kodu

ASCII (jednobajtowa liczba całkowita)

• Domyślnie signed/unsigned zależy od kompilatora

• Wartość typu char można traktować w wyrażeniach jako liczbę

całkowitą, konwersje znak - liczba odbywają się automatycznie

• Stałe znakowe

– znak pomiędzy parą znaków ' i ', np. 'A', '0', '@'

– znak \ i kod ASCII pomiędzy parą znaków ' i ', np. '\65', '\0'

– W przypadku przypisania liczby do zmiennej znakowej, następuje

automatyczna konwersja na znak o podanym kodzie ASCII

• Znaki specjalne '\n'(new line),'\t'(tab), '\\', '\'', '\"'

145

Typy char[] i char *

• W celu zapisu tekstu tworzy się tablice znaków.

W języku C wprowadzono konwencję, zgodnie z którą tekst kończy się znakiem

'\0' (na który trzeba przewidzieć dodatkowy bajt w tablicy) - tzw. null-terminated

string

• Praca z taką tablicą wymaga zwykle przetwarzania każdego znaku z osobna.

Stosuje się do tego pętle, najczęściej pętlę for.

W całości można jedynie dokonać inicjalizacji tablicy.

Operacje na całym tekście można też wykonywać z udziałem wielu funkcji

bibliotecznych.

• Zmienne typu char* wskazują na pojedyncze znaki, opisuje się też w ten sposób

również cały tekst (wskazując pierwszy znak)

• Stałe tekstowe zapisuje się pomiędzy parą znaków " i "

Są one typu char*

• Przydatne funkcje obsługujące teksty (zdefiniowane jako tablice lub wskaźniki)

znaleźć można w pliku string.h 146

Przykłady pracy z tekstem

char nazwisko[31]; // max. 30 znaków + '\0'

char logo1[]= "ZPSB"; // wygodna inicjalizacja

char logo2[]={'Z','P','S','B','\0'}; // tak też można

char imie[20]="Michał"; // rozmiar jest większy

nazwisko="Kowalski" // Błąd! stały wskaźnik!

nazwisko[7]='a';

char *tekst=imie;

*tekst='A'; //wstawimy "ABC" do imie

*(tekst+1)='B';

tekst[2]='C';

tekst[3]=0; // koniec, konwersja z int

cin >> nazwisko; // musi wystarczyć miejsca!

cout << nazwisko<< endl; 147

Wskaźnik jako parametr funkcji (1)

• Funkcja może jako parametr przyjmować wskaźnik. Wartość wskaźnika jest kopiowana, więc wskazuje on na tę samą zmienną, co wskaźnik wysłany jako parametr

• Pozwala to funkcji na zmianę wartości tej zmiennej, co nie jest możliwe przy zwykłym przesyłaniu danych przez wartość

• Przykład:

void zeruj_ujemne (int *liczba)

{

if (*liczba<0)

*liczba=0;

}

void main()

{

int a=-5; // tutaj a=-5

int *wsk=&a;

zeruj_ujemne(wsk); // a tutaj już a=0

zeruj_ujemne(&a); // tak też można

} 148

Wskaźnik jako parametr funkcji (2)

• Wskaźnik służy też zwykle do przekazywania tablicy (także tekstu) jako parametr.

• Często wykorzystuje się też wskaźniki, jeżeli funkcja generuje kilka różnych wartości wynikowych (zwrócić można tylko jedną)

• Przykład (zliczanie wartości dodatnich i niedodatnich w tablicy):

void plusminus(int *tab, int n, int *plus, int *minus)

{

int pl=0, min=0; // zmienne lokalne

for (int i=0; i<n; i++)

if (tab[i]>0) pl++; // wskaźnik jako tabl.

else min++;

*plus=pl; // przekazanie wyników

*minus=min;

} // nie ma return, a funkcja zwraca wyniki

void main()

{

int tablica[20], ileplus, ileminus;

plusminus(tablica, 20, &ileplus, &ileminus);

} 149