dr inż. Krzysztof Białek - slupsk.spoleczna.pl · • Jerzy Grębosz “Symfonia C++” tom...
Transcript of dr inż. Krzysztof Białek - slupsk.spoleczna.pl · • Jerzy Grębosz “Symfonia C++” tom...
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
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
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
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
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
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
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