Gorazd T Kurs C Wstęp do Programowania

background image

Wstęp do Programowania

Tomasz Gorazd

gorazd@wsb-nlu.edu.pl

Spis treści

1

Wykład 1

2

1.1

Czego potrzebujemy do kursu . . . . . . . . . . . . . . . . . .

2

1.2

Co robią kompilatory . . . . . . . . . . . . . . . . . . . . . . .

3

1.3

Pierwszy program . . . . . . . . . . . . . . . . . . . . . . . . .

3

1.4

Jak wygląda program? . . . . . . . . . . . . . . . . . . . . . .

4

1.5

Komentarze . . . . . . . . . . . . . . . . . . . . . . . . . . . .

5

1.6

Dowolność formatowania tekstu programu

. . . . . . . . . . .

5

2

Wykład 2

7

2.1

Zmienne . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

7

2.2

Słowa kluczowe . . . . . . . . . . . . . . . . . . . . . . . . . .

8

2.3

Stałe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9

2.4

Nadawanie wartości zmiennym . . . . . . . . . . . . . . . . . .

10

2.5

Wejście - Wyjście . . . . . . . . . . . . . . . . . . . . . . . . .

11

2.6

Operatory arytmetyczne . . . . . . . . . . . . . . . . . . . . .

13

2.7

Typ wyrażenia arytmetycznego

. . . . . . . . . . . . . . . . .

14

1

background image

3

Wykład 3

17

3.1

Prawda i Fałsz

. . . . . . . . . . . . . . . . . . . . . . . . . .

17

3.2

Instrukcja if . . . . . . . . . . . . . . . . . . . . . . . . . . . .

17

3.3

Instrukcja if . . . else

. . . . . . . . . . . . . . . . . . . . . . .

18

3.4

Blok instrukcji . . . . . . . . . . . . . . . . . . . . . . . . . . .

20

3.5

If - wielowariantowe . . . . . . . . . . . . . . . . . . . . . . . .

21

3.6

Trochę inne if. Wyrażenie warunkowe . . . . . . . . . . . . . .

22

3.7

Pętla for . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

24

3.8

Uwagi do pętli for

. . . . . . . . . . . . . . . . . . . . . . . .

27

4

Wykład 4

29

4.1

Tablice liczbowe . . . . . . . . . . . . . . . . . . . . . . . . . .

29

4.2

Tablice wielowymiarowe

. . . . . . . . . . . . . . . . . . . . .

32

4.3

Tablice znakowe . . . . . . . . . . . . . . . . . . . . . . . . . .

33

4.4

Operatory logiczne . . . . . . . . . . . . . . . . . . . . . . . .

34

4.5

Operatory przypisania cd. . . . . . . . . . . . . . . . . . . . .

36

4.6

Połączenie operatora przypisania z operatorami arytmetycznymi 37

4.7

Pętla while . . . . . . . . . . . . . . . . . . . . . . . . . . . .

38

5

Wykład 5

41

5.1

Pętla do . . . while . . . . . . . . . . . . . . . . . . . . . . . .

41

5.2

Instrukcja break . . . . . . . . . . . . . . . . . . . . . . . . .

43

5.3

Instrukcja switch . . . . . . . . . . . . . . . . . . . . . . . . .

45

5.4

Instrukcja goto . . . . . . . . . . . . . . . . . . . . . . . . . .

49

5.5

Instrukcja continue

. . . . . . . . . . . . . . . . . . . . . . .

51

2

background image

6

Wykład 6

53

6.1

Struktury

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

53

6.2

Definiowanie typów . . . . . . . . . . . . . . . . . . . . . . . .

58

6.3

Funkcje - wprowadzenie . . . . . . . . . . . . . . . . . . . . .

61

6.4

Funkcje - definicje i deklaracje funkcji . . . . . . . . . . . . .

63

6.5

Funkcje - zwracanie wartości . . . . . . . . . . . . . . . . . . .

66

7

Wykład 7

69

7.1

Tablice znakowe

. . . . . . . . . . . . . . . . . . . . . . . .

69

7.2

Inicjalizacja zmiennych . . . . . . . . . . . . . . . . . . . . . .

72

7.3

Funkcje - przekazywanie argumentów przez wartość . . . . . .

75

7.4

Funkcje - przesyłanie argumentów przez referencje . . . . . . .

78

7.5

Funkcje -argumenty domniemane . . . . . . . . . . . . . .

80

7.6

Wywoływanie funkcji przez samą siebie - rekurencja . . . . . .

82

7.7

Operator sizeof . . . . . . . . . . . . . . . . . . . . . . . . . .

83

8

Wykład 8

86

8.1

Funkcje - różne funkcje o tych samych nazwach

. . . . . . . .

86

8.2

Zakres ważności obiektów

. . . . . . . . . . . . . . . . . . . .

87

8.3

Obiekty globalne . . . . . . . . . . . . . . . . . . . . . . . . .

89

8.4

Zmienne automatyczne i statyczne . . . . . . . . . . . . . . . .

92

8.5

Programy składające się z kilku plików . . . . . . . . . . . . .

94

8.6

Modyfikatory const, register, volatile

. . . . . . . . . . . . 100

9

Wykład 9

102

9.1

Wskaźniki - definiowanie . . . . . . . . . . . . . . . . . . . . . 102

9.2

Wskaźniki - zastosowanie . . . . . . . . . . . . . . . . . . . . . 103

9.3

Wskaźniki do typu void . . . . . . . . . . . . . . . . . . . . . 108

9.4

Wskaźniki a tablice . . . . . . . . . . . . . . . . . . . . . . . . 111

9.5

Operacje arytmetyczne na wskaźnikach . . . . . . . . . . . . . 114

3

background image

10 Wykład 10

116

10.1 Wszystkie pliki nagłówkowe po kolei

. . . . . . . . . . . . . . 116

10.2 conio.h - Funkcje dotyczą trybu tekstowego . . . . . . . . . . . 117

10.3 alloc.h - Operacje na pamięci

. . . . . . . . . . . . . . . . . . 118

10.4 mem.h - Funkcje operujące na pamięci . . . . . . . . . . . . . 119

10.5 string.h - Wszelkie operacje na stringach . . . . . . . . . . . . 119

10.6 dos.h - Tylko kiedy piszemy aplikacje pod DOS

. . . . . . . . 120

10.7 dir.h - Funkcje obsługujące pliki i katalogi . . . . . . . . . . . 120

10.8 ctype.h - Funkcje informujące o znakach

. . . . . . . . . . . . 121

10.9 math.h - Funkcje matematyczne . . . . . . . . . . . . . . . . . 121

10.10stdlib.h - Przydatne funkcje, często używane . . . . . . . . . . 121

10.11time.h - Funkcje dotyczące czasu

. . . . . . . . . . . . . . . . 122

10.12stdio.h - Standardowe wejście i wyjście . . . . . . . . . . . . . 122

10.13graphics.h - Tryb graficzny . . . . . . . . . . . . . . . . . . . . 122

11 Wykład 11

126

11.1 Animacja - co ma robić komputer . . . . . . . . . . . . . . . . 126

11.2 Skąd komputer wie, czego chce użytkownik . . . . . . . . . . . 128

12 Wykład 12

138

12.1 Dynamiczna rezerwacja i zwracanie pamięci

. . . . . . . . . . 138

12.2 Jak to się robi w C . . . . . . . . . . . . . . . . . . . . . . . . 139

12.3 Alokacja miejsca dla tablic . . . . . . . . . . . . . . . . . . . . 141

12.4 Listy struktur . . . . . . . . . . . . . . . . . . . . . . . . . . . 143

12.5 Wskaźniki w argumentach funkcji . . . . . . . . . . . . . . . . 144

12.6 Jeszcze raz tablice w argumentach funkcji

. . . . . . . . . . . 145

12.7 Wskaźniki do obiektów typu const oraz wskaźniki typu const . 146

12.8 Wskaźniki do funkcji . . . . . . . . . . . . . . . . . . . . . . . 147

12.9 Tablice wskaźników do funkcji . . . . . . . . . . . . . . . . . . 149

12.10Wskaźnik do funkcji jako jeden z argumentów innej funkcji . . 151

4

background image

13 Wykład 13

154

13.1 Otwieranie i zamykanie pliku . . . . . . . . . . . . . . . . . . . 154

13.2 Podstawowe operacje na otwartych plikach . . . . . . . . . . . 156

13.3 Pisanie formatowane do pliku . . . . . . . . . . . . . . . . . . 160

13.4 Poruszanie się po pliku . . . . . . . . . . . . . . . . . . . . . . 164

13.5 Kasowanie plików . . . . . . . . . . . . . . . . . . . . . . . . . 166

13.6 Biblioteka io . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166

14 Wykład 14

167

14.1 Parametry funkcji main . . . . . . . . . . . . . . . . . . . . . 167

14.2 Funkcje ze zmienną ilością argumentów . . . . . . . . . . . . . 167

14.3 Kilka standardowych funkcji o zmiennej ilości argumentów . . 170

14.4 Operatory bitowe . . . . . . . . . . . . . . . . . . . . . . . . . 172

15 Wykład 15

175

15.1 Typ enum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

15.2 Unie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

15.3 Zmienne typu extern . . . . . . . . . . . . . . . . . . . . . . . 177

15.4 Operator przecinkowy

. . . . . . . . . . . . . . . . . . . . . . 177

15.5 Priorytety operatorów

. . . . . . . . . . . . . . . . . . . . . . 178

15.6 Preprocesor . . . . . . . . . . . . . . . . . . . . . . . . . . . 178

15.6.1 #include . . . . . . . . . . . . . . . . . . . . . . . . . . 178

15.6.2 #define

. . . . . . . . . . . . . . . . . . . . . . . . . . 179

15.6.3 Makrodefinicje

. . . . . . . . . . . . . . . . . . . . . . 180

15.6.4 Kompilacja warunkowa . . . . . . . . . . . . . . . . . . 181

15.6.5 Predefiniowane nazwy

. . . . . . . . . . . . . . . . . . 185

5

background image

1

Wykład 1

1.1

Czego potrzebujemy do kursu

Kurs ”Wstęp do Programowania” ma na celu nauczenie programowania w
języku C, z niewielkimi dodatkami języka C++. Myślę, że niniejszy kurs
powinien być wystarczający by to zrealizować. Dla chętnych podaję kilka
pozycji literatury, z których można korzystać podczas nauki.

Język C, B.W.Kernighan, D.M.Ritchie

Język C, Conor Sexton

Język C : Szkoła programowania, Stephen Prata

Język C : Dla wszystkich, Marek Lont

Przydatne mogą być wszelkie pozycje mające w tytule Język C lub
C/C++. Aby móc w pełni programować, prócz wiedzy na temat programo-
wania potrzebujemy jeszcze specyficznego oprogramowania, czyli programu
który zamieni treść napisanego przez nas programu na coś, co jest zrozu-
miałe dla procesora naszego komputera. Takie programy będziemy nazywać
kompilatorami.

Jeśli mamy na naszym komputerze zainstalowany system Linux (Unix),
możemy skorzystać z dostarczanego razem z systemem programu g++.

W systemach pochodzących od DOS - czyli wszystkich odmianach Win-
dows
- możemy skorzystać z programu Turbo C++, który można pobrać
bezpłatnie, po uprzednim zarejestrowaniu się, ze stron muzeum firmy Bor-
land
- bdn.borland.com/museum.

Przy nauce do tego wykładu można jednak korzystać z dowolnego kompi-
latora języka C++. Większość programów omawianych na wykładzie bę-
dzie można przy ich pomocy kompilować. Jednak, im program jest starszy
(szczególnie pod Windows), tym będzie on bardziej przydatny od strony
dydaktycznej.

Aby dokładnie dowiedzieć się, jak dany kompilator działa, należy zapoznać
się z jego dokumentacją.

6

background image

1.2

Co robią kompilatory

Tak naprawdę to kompilator jest jedynie jedną z części składowych wcze-
śniej wymienionych programów. Jeśli chodzi o Turbo C++, to można wy-
mienić trzy podstawowe jego części:

Edytor tekstu, przy pomocy którego będziemy pisać nasze programy.

Kompilator, tworzy z naszego programu wersję programu w języku maszy-

ny. Nie jest ona jednak pełna - brakuje tam wywoływanych przez nas
funkcji bibliotecznych.

Linker, łączy kod po kompilacji z bibliotekami.

Należy zaznaczyć, iż program nie koniecznie musimy pisać w edytorze tekstu
dostarczonym z kompilatorem. Może być on pisany w dowolnym edytorze
tekstowym (nie formatującym dodatkowo wpisanego tekstu) - np. Notatnik
w Windows.

1.3

Pierwszy program

Napiszmy więc nasz pierwszy program. Uruchommy program tc.exe. Następ-
nie otwórzmy nowy dokument. Wpiszmy poniższy tekst w okienku edytora.
Zapamiętajmy go pod nazwą pr1.cpp.

#include <stdio.h>

main()
{

printf("To program pierwszy");

}

Notatki:

Aby nasz program uruchomić wystarczy wejść do menu Run i wybrać opcję
Run. Można to zrobić szybciej naciskając kombinację klawiszy Ctrl-F9.

Aby zobaczyć wynik działania programu przyciśnijmy klawisze Alt-F5. Aby
znów powrócić do okienka edycji przyciskamy również Alt-F5.

7

background image

Aby uruchomić program pod systemem Linux korzystając z kom-
pilatora g++, należy:

1. Napisać tekst programu w dowolnym edytorze tekstowym i

zapamiętać jako pr1.cpp

2. Będąc w kartotece, gdzie znajduje się plik pr1.cpp, wywo-

łać g++ -o pr1 pr1.cpp

3. Program możemy teraz uruchomić przez ./pr1

Notatki:

Pierwszy nasz program napisany był w ”czystym” języku C. Z powodów
czysto dydaktycznych będziemy jednak na tym wykładzie stosować pewne
dodatki pochodzące z języka C++. Proszę zwrócić uwagę na sposób wypi-
sywania tekstu na ekranie. Linijka postaci:

cout << "To program pierwszy";

jest właśnie typowa dla C++. Jej odpowiednikiem w ”czystym” C jest:

printf("To program pierwszy");

Dlaczego stosujemy wersję z C++, wyjaśni się w dalszej części wykładu.

A oto nowa wersja pierwszego programu:

#include <iostream.h>

main()
{

cout << "To program pierwszy";

}

Notatki:

1.4

Jak wygląda program?

W każdym programie musi być funkcja main. Wszystkie instrukcje tej funk-
cji są zawarte pomiędzy znakami { oraz }. Każda instrukcja kończy się zna-
kiem ; (średnik). Oczywiście programy, które nauczymy się tu pisać będą
bardziej rozbudowane, wszystkie jednak podlegają powyższej zasadzie.

8

background image

1.5

Komentarze

Jeżeli piszemy dłuższe programy, szczególnie jeśli wracamy do nich po pew-
nym czasie, można nie pamiętać, w jakim celu napisaliśmy jakąś ich cześć.
Dotychczasowe nasze programy były krótkie, ale programy, które pisze się
profesjonalnie, mają często np. kilkadziesiąt tysięcy linii. W treści progra-
mów możemy napisać takie uwagi, które są ignorowane przez kompilator, nie
mają żadnego wpływu na sam program - to waśnie komentarze.

Komentarzem jest wszystko, co jest zawarte między znakami /* a znakami
*/. Takie komentarze mogą obejmować nawet kilka linii tekstu.

Drugim rodzajem komentarza jest tekst zaczynający się od znaków //. Koń-
cem takiego komentarza jest koniec linii.

/* A oto nasz kolejny program

napisany na pierwszym wykładzie
z komentarzem w wielu linijkach

*/

/*----------------------------------------------------------------------*/

#include <iostream.h>

// Tu mówię kompilatorowi, z jakich dodatkowych
// funkcji będę ewentualnie korzystał

main()

//Ta funkcja musi się znajdować w każdym programie

{

//Ciało funkcji zaczyna się znakiem {

cout << "To kolejny program";

//Tu właśnie wypisuje tekst na
//standardowe urządzenie wyjściowe
//(tu ekran)

}

//Ciało funkcji kończy się znakiem }

/*----------------------------------------------------------------------*/

Notatki:

1.6

Dowolność formatowania tekstu programu

Tak jak komentarze, przez kompilator ignorowane są w treści programu
wszystkie białe znaki, tzn. znaki spacji, tabulacji, nowych linii (między
kolejnymi instrukcjami). Tak więc nasz program może wyglądać tak:

9

background image

/*Przykład programu bez zbednych białych znaków*/

#include <iostream.h>

main() {cout << "To program pierwszy";}

Notatki:

lub też tak:

/*Przykład programu z białymi znakami, które są ignorowane*/

#include <iostream.h>

main() {
cout

<<

"To program pierwszy"

;

}

Notatki:

Tylko po co? Oczywiście taka dowolność jest potrzebna, by uczynić program
bardziej czytelnym. Należy stosować wcięcia w treści programu i czynić go
jak najbardziej czytelnym. Powinno się wybrać swój ulubiony sposób forma-
towania programu i tego sposobu trzymać się konsekwentnie.

Tekst programu powinien być przede wszystkim czytelny.

10

background image

2

Wykład 2

2.1

Zmienne

Zmienne to obiekty do przechowywania wartości określonego typu (rodza-
ju). To, co przechowuje taki obiekt, możemy zmieniać w trakcie działania
programu.

Podstawowe typy zmiennych:

Oznaczenie

Opis

IBM PC

int

liczby całkowite (...-2,-1,0,1,2 ...)

2 bajty

unsigned int

liczby całkowite dodatnie (0,1,2,3,4 ....)

2 bajty

short

tak jak int lecz mniejszy zakres liczb

2 bajty

long

tak jak int lecz większy zakres liczb

4 bajty

float

liczby rzeczywiste (np. 2.23, 3.14, -89,6)

4 bajty

double

tak jak float lecz większy zakres liczb

8 bajtów

long double

tak jak double lecz większy zakres liczb

8 bajtów

char

znaki alfanumeryczne ( A d G 3 5 - tak jak widać)

1 bajt

Każdy z tych typów całkowitoliczbowych możemy poprzedzić napisem unsi-
gned. Spowoduje to, że będziemy mieli do dyspozycji tylko liczby nieujemne,
ale za to największa dostępna dla nas liczba będzie dwa razy większa.

Jeśli chodzi o typ

char

, to może on być traktowany jako typ liczbowy. W

obiektach tego typu będą przechowywane kody ASCII znaków.

Aby ze zmiennych (tych obiektów w pamięci) móc korzystać, powinniśmy
mieć sposób, aby do nich się dostać. Robimy to poprzez nazwanie, czyli de-
finicję zmiennej
.

Definicja zmiennej - powiedzenie kompilatorowi, że dana nazwa reprezen-
tuje obiekt danego typu. Przez definicję zajmujemy automatycznie miejsce
w pamięci dla danej (tak właśnie nazwanej) zmiennej.

Jako nazw zmiennych możemy używać ciągów znaków złożonych z cyfr,
liter (alfabet angielski),

(znaku podkreślenia), zaczynających się od

litery lub znaku . Uwaga, małe i duże litery są dla C różne. Wiec np. nazwy

Cena

i

cena

uważane są za różne.

11

background image

Przykład definiowania zmiennych

int liczba;

// zdefiniowaliśmy zmienną o nazwie liczba do
//przechowywania liczby całkowitej

float z;

// zdefiniowaliśmy zmienną o nazwie z do
//przechowywania liczby rzeczywistej

long a, dluga;

// w jednej linii definicja dwóch zmiennych a
//i dluga typu long

Dalej przykład działającego programu z definiowaniem zmiennych.

/* Program pr2_1.cpp
demonstracja definiowania zmiennych
*/

#include<iostream.h>

main()
{

int liczba;
float a;
char znak;

cout << "Ten program nic nie robi, ale ma zmienne";

}

Notatki:

2.2

Słowa kluczowe

Nie wszystkie jednak ciągi znaków, o których mówiono w poprzednim roz-
dziale mogą być nazwami zmiennych. Niektóre z nich są zarezerwowane jako
pewne słowa z samego języka. Oto lista słów zarezerwowanych (podano rów-
nież te zarezerwowane z C++).

12

background image

asm

auto

break

case

catch

char

class

const

continue

default

delete

do

double

else

enum

extern

float

for

friend

goto

if

inline

int

long

new

operator

private

protected

public

register

return

short

signed

sizeof

static

struct

switch

template

this

throw

try

typedef

union

unsigned

virtual

void

volatile

while

2.3

Stałe

Mając zmienne chcielibyśmy, coś w nich przechowywać. To, co tam możemy
wstawić, to np. stałe.

Stałe liczbowe, przykłady:

23 45 -90 . . . stałe będące liczbami całkowitymi

Możemy stosować również zapis liczb całkowitych w systemie szesnastko-
wym
lub ósemkowym.

Zapisz szesnastkowy zaczyna się zawsze od symboli 0x.

0x12 - to zapisz szesnastkowy. Dziesiętnie jest to 18.

0xA2 - to jest dziesiętnie 162.

W zapisie szesnastkowym używamy cyfr oraz symboli A B C D E F

Zapis ósemkowy zaczyna się zawsze od 0 (zera) - używamy cyfr od 0 do 7

012 - to dziesiętnie 10, a

073 - to dziesiętnie 59

Stałe reprezentujące liczby rzeczywiste (zmiennoprzecinkowe)

41.9 3.14 -98.09 88876. 43.8

Uwaga, w zapisie stosujemy kropkę (.) a nie przecinek (,).

Stałe znakowe

’a’ - litera a

’7’- cyfra 7 traktowana jako znak a nie liczba

’P’ - litera P Znaki sterujące

13

background image

\b’ - cofnięcie

\f’ - nowa strona

\n’ - nowa linijka

\r’ - powrót karetki

\a’ - sygnał dźwiękowy

\t’ - znak tabulacji

Inne szczególne znaki

\\’ znak \

\”’ znak ”

\’ ’ apostrof

\0’ znak o kodzie 0

\?’ pytajnik

Stałe tekstowe - stringi: ciągi znaków zawarte między cudzysłowami.

”To program pierwszy”

”A to jakiś napis”

”To napis \n ze znakiem specjalnym”

2.4

Nadawanie wartości zmiennym

Aby do zdefiniowanej wcześniej zmiennej wstawić jakąś wartość, używamy
operatora =. Uwaga, znak ten nie ma nic wspólnego ze znanym z matema-
tyki operatorem porównania. Mówi on, że do miejsca pamięci reprezentowa-
nego przez zmienną z lewej strony znaku = ma być wstawiona wartość, która
znajduje się po jego prawej stronie. Wartość podstawiana może być np. stałą
lub inna zmienną.

Jeśli typ wyrażenia po prawej stronie jest inny niż typ zmiennej, możemy
napotkać na komplikacje. Np. jeśli zmienna jest typu całkowitoliczbowego a
wartość podstawiana jest rzeczywista, może dojść do obcięcia wielkość po
kropce dziesiętnej.

Uwaga, jeśli chcemy używać jakiejś zmiennej w programie, musi ona być
wcześniej w tym programie zdefiniowana.

14

background image

Oto przykład z nadawaniem wartości zmiennym:

/* Program pr2_2.cpp

Podstawianie wartości za zmienne

*/

main()
{

int liczba, bzyk;
float pi, pier_z_2, a;
char znak;

liczba = 3;

//Podstawienie pod zmienną liczba wartości 3.

bzyk = liczba;

//Zmienna bzyk otrzymuje tę samą wartość co
//zmienna liczba.

pi = 3.14;
pier_z_2 = 1.4142;
a = pi;

//Tu pod zmienną a podstawiamy wartość
//zmiennej pi,

a = 35;

//a tu za zmienną a podstawiamy liczbę 35

pi = 3.14115;

//Tu chcemy mieć dokładniejszą wartość pod pi

znak = ’z’;

//W zmiennej znak przechowujemy znak z

znak = ’\0’;

//a teraz znak o kodzie 0

}

Notatki:

2.5

Wejście - Wyjście

Wcześniej już zostało pokazane, jak na ekranie możemy wypisywać napisy. W
ten sam sposób możemy wypisać dowolną stałą jak również zawartość pozna-
nych przez nas zmiennych (poznamy później inne typy zmiennych, których
w ten sposób nie można wypisać).

Gdy chcemy wypisać zmienną

a

(typu liczbowego lub znakowego), piszemy:

cout << a;

Aby wypisać wartość

5

, napiszemy:

cout << 5;

Możemy też łączyć wypisywanie kilku wartości, również ze stringami:

15

background image

cout << "Zmienna a ma wartość " << a;

Oto działający przykład:

/* Program pr2_3.cpp

Wypisywanie wartości

*/

#include<iostream.h>

main()
{

int liczba;
float pi;

cout << "Ala ma kota";

//Wypisanie stringu.

cout << "Linia 1\nLinia 2\n";

//Piszemy dalej w tej samej linijce a
//następnie przechodzimy do nowej i
//piszemy Linia 2.

liczba = 5;
cout << liczba;

//Wypisanie zawartości zmiennej liczba - czyli 5.

pi = 3.14;
cout << pi;

//Jak wyżej, tylko ze zmienną zmiennoprzecinkową.

// A teraz ładniej. Tak też można.

cout << "\nZmienna liczba ma wartość " << liczba <<" a zmienna pi ";
cout << "ma wartość " << pi;

}

Notatki:

Oczywiście programowanie nie miałoby sensu, gdyby występujące w progra-
mach wartości były tam od zawsze zapisane. Aby program mógł mieć jakieś
użyteczne zastosowanie, powinien również czerpać wiadomości z otoczenia.
Program liczący np. pierwiastki równania kwadratowego powinien od użyt-
kownika pobierać to równanie (liczby występujące przy poszczególnych po-
tęgach).

Aby wczytać wartość i umieścić pod zmienną (liczbową)

a

, możemy napisać:

cin >> a;

Po tej instrukcji to, co poda użytkownik (umówmy się, że będzie tak dobry
i wpisze odpowiednią liczbę), zostanie umieszczone pod zmienną

a

.

16

background image

Popatrzmy na taki program:

/* Program pr2_4.cpp

Wczytywanie wartości liczbowych

*/

#include<iostream.h>

main()
{

int wiek;
float kurs;

//Wpierw wczytam liczbę całkowitą
cout << "Podaj swój wiek ";

//Linijka zachęty, bardzo ważna.

cin >> wiek;

//Tu wczytuję.

cout << "O masz " << wiek << " lat\n"; //Czy się zgadza ?

//Teraz wczytywanie liczby rzeczywistej.
cout << "Jaki dziś kurs dolara ? ";
cin >> kurs;
cout << "Widzę że za 1$ zapłacę " << kurs << " złotych\n";

}

Notatki:

2.6

Operatory arytmetyczne

Oczywiście, gdy mamy stałe i zmienne, chcemy na nich wykonywać jakieś
operacje arytmetyczne - dodawanie, odejmowanie, mnożenie itp. W języku
C mamy wszystkie operacje znane ze szkoły. Dodatkowo, symbole dla nich
są wszystkim znane. Możemy z nich tworzyć bardziej złożone wyrażenia przy
pomocy nawiasów okrągłych. Jeśli nie zastosujemy nawiasów, to najpierw są
wykonywane operacje o najwyższym priorytecie. Spośród dwóch operatorów
o tym samym priorytecie wykonywany jest najpierw ten z lewej strony.

Priorytet

Operator

Uwagi

3

-

jednoargumentowy -

3

+

jednoargumentowy +

2

*

mnożenie

2

/

dzielenie

2

%

reszta z dzielenia

1

-

odejmowanie

1

+

dodawanie

17

background image

Wyrażenie arytmetyczne możemy postawić zawsze po prawej stronie znaku
podstawienia.

/* Program pr2_5.cpp

Wyrażenia arytmetyczne

*/

main()
{

int liczba, n;
float pi, pier_z_2, a;

liczba = 3;
n = liczba + liczba;
n = (n - liczba) * 8;

//Pod n mamy wartość otrzymaną poprzez odjęcie
//od poprzedniej wartości zmiennej n wartości
//zmiennej liczba i pomnożenie wyniku przez 8.

liczba = 345 % n;

n = (n * 2) / 45;

pi = 3.14;
pier_z_2 = 1.4142 * 2 * pi;
a = -pi;
a = 35 + a * pi ;

//Najpierw wykona się mnożenie a potem dodawanie.

}

Notatki:

2.7

Typ wyrażenia arytmetycznego

Jeśli po prawej stronie podstawienia znajduje się wyrażenie arytmetyczne,
wyrażenie to jest wykonywane niezależnie od tego, za co będzie podstawiona
jego wartość. Sprawą bardzo ważną jest typ wynikowy wyrażenia arytme-
tycznego. Popatrzmy na przykład. Jeśli w programie znajduje się wyrażenie

int a, b;
float c;

a = 3;
b = 2;
c = a / b;

to prawie każdy powie, że jego wartością c jest teraz

1.5

. Jest to NIE-

PRAWDA i to niezależnie od tego, że zmienna

c

jest typu

float

. Najpierw

18

background image

zostanie obliczone

a/b

, a jego wartością jest

1

. Dlaczego? Jeżeli mamy wy-

rażenie arytmetyczne (na chwile tylko) z jednym operatorem, to typ wyniku
jest najmniejszym typem, w którym zmieszczą się obydwa argumenty wystę-
pujące w wyrażeniu. W tym wypadku tym typem jest

int

, i dlatego wartość

tego wyrażenia to

1

.

Jeśli mamy bardziej złożone wyrażenie arytmetyczne, to powyższa zasada
stosuje się do kolejno wykonywanych operacji. Następny przykład obrazuje,
jak liczą się wyrażenia arytmetyczne:

/* Program pr2_6.cpp

Typy wyrażeń arytmetycznych

*/
#include <iostream.h>

main()
{

int a;
float f;

f = 40 / 3;

//Obliczone wyrażenie ma typ int; następnie

//jest zamieniane na typ float, przed podstawieniem.

cout << "\nwartość f po f = 40/ 3 jest równa " << f;

f = 40.0 / 3;

//Obliczone wyrażenie ma typ double, następnie

//jest zamieniane na typ float, przed podstawieniem.

cout << "\nwartość f po f = 40.0/ 3 jest równa " << f;

a = 40.0 / 3;
cout << "\nwartość a po a = 40.0/ 3 jest równa " << a;

a = 40.0;
f = a / 3;
cout << "\nwartość f po f = a / 3 jest równa " << f;

a = 40.0;
f = a / 3.0;
cout << "\nwartość f po f = a / 3.0 jest równa " << f;

a = 40;
f = a / (3 + 0.0);
cout << "\nwartość f po f = a / (3 + 0.0) jest równa " << f;

}

Notatki:

Aby lepiej zrozumieć to zagadnienie, proszę napisać kilka podobnych przy-
kładów. Niezrozumienie powyższego zagadnienia jest, szczególnie dla począt-

19

background image

kujących programistów, źródłem wielu błędów.

20

background image

3

Wykład 3

3.1

Prawda i Fałsz

W języku C nie ma stałych ani zmiennych reprezentujących wartości logiczne
prawdy i fałszu. Każdy jednak zdaje sobie sprawę, że w programowaniu takie
pojęcia są niezbędne. W języku C przyjęto następującą konwencję:

Wartość zero - reprezentuje fałsz

Wartość różna od zera - reprezentuje prawdę

Nie musi być to wartość liczbowa. Może to być wartość jakiegoś wyrażenia,
zmiennej - np. typu char.

Od tego miejsca będziemy używać pojęć prawda i fałsz zgodnie z powyższa
definicją.

Mamy również możliwość porównywania wartości liczbowych lub znakowych

Jeśli a, b - to zmienne, stałe lub wyrażenia o wartościach liczbowych lub
znakowych to:

a == b

Prawda, jeśli a równe b, w przeciwnym przypadku fałsz

a != b

Prawda, jeśli a różne b, w przeciwnym przypadku fałsz

a < b

Prawda, jeśli a mniejsze niż b, w przeciwnym przypadku fałsz

a > b

Prawda, jeśli a większe od b, w przeciwnym przypadku fałsz

a <= b

Prawda, jeśli a mniejsze lub równe b, w przeciwnym przypadku fałsz

a >= b

Prawda, jeśli a większe lub równe b, w przeciwnym przypadku fałsz

!a

Prawda, jeśli a fałszem, fałsz gdy a jest prawda (przeczenie)

3.2

Instrukcja if

Instrukcja ta służy do sterowania przebiegiem programu. Ma ona postać

if (wyrażenie)

instrukcja1

Jeśli

wyrażenie

przyjmuje wartość niezerową (prawdy), to wykonuje się in-

strukcja

instrukcja1

. W przypadku przeciwnym nie wykona się żadna in-

strukcja.

21

background image

/* Program pr3_1.cpp

Instrukcja sterująca if

*/

#include <iostream.h>

main()
{

int a;

cout << "Podaj liczbę całkowitą-> ";
cin >> a;

//Wczytuję liczbę.

if (a == 3) cout << "O podałeś trójkę\n";

//Powyższe wykona się, gdy wczytaliśmy liczbę 3.

cout << "Koniec programu\n"; //To wykona się zawsze.

}

Notatki:

3.3

Instrukcja if . . . else

Instrukcja ta jest rozszerzeniem poprzedniej instrukcji.

if (wyrażenie)

instrukcja1

else

instrukcja2

Powoduje ona, że jeśli

wyrażenie

jest niezerowe, to wykonuje się

instrukcja1

,

w przeciwnym wypadku

instrukcja2

.

22

background image

/* Program pr3_2.cpp

Instrukcja sterująca if ... else

*/

#include <iostream.h>

main()
{

int a;

cout << "Podaj liczbę -> ";
cin >> a;

if ( a == 3)

//Sprawdzamy, czy a jest równe 3.

cout << "Teraz podałeś trójkę\n";

else

//Poniższe wykona sie, gdy a nie jest równe 3.

cout << "Nie podałeś trójki\n";

cout << "Koniec programu\n";

}

Notatki:

Następny przykład robi to samo co poprzedni, lecz proszę zwrócić uwagę na
warunek występujący po

if

.

/* Program pr3_3.cpp

Instrukcja sterująca if ... else
Przykład z liczbą w warunku pod if

*/

#include <iostream.h>

main()
{

int a;

cout << "Podaj liczbę -> ";
cin >> a;

if ( a - 3) //Sprawdzamy, czy a-3 jest równe zeru, czy nie.

cout << "Nie podałeś trójki\n";

else

cout << "Teraz podałeś trójkę\n";

cout << "Koniec programu\n";

}

Notatki:

23

background image

3.4

Blok instrukcji

Jeśli w instrukcji warunkowej chcemy wykonać więcej niż jedną instrukcję,
możemy zapisać je jako blok instrukcji i wtedy są one traktowane jako jedna
instrukcja. Blok tworzymy, umieszczając instrukcje pomiędzy znakami { i }.

Blok instrukcji

{

instrukcja1
instrukcja2
...
...
instrukcja ostatnia

}

/* Program pr3_4.cpp

Blok instrukcji

*/

#include <iostream.h>

main()
{

int a, licznik;

cout << "Podaj liczbę -> ";
cin >> a;

if ( a == 3)
{

//Jeśli warunek jest prawdziwy,
//wykonają się dwie poniższe instrukcje.

cout << "Teraz podałeś trójkę\n";
licznik = 1;

}
else
{

cout << "Nie podałeś trójki\n";
licznik = 0;

}

if(licznik) cout << "Widzę, że lubisz trójkę\n";
else

cout << "Oj, nie lubisz trójki\n";

}

Notatki:

24

background image

3.5

If - wielowariantowe

Często, sprawdzając coś w programie przy pomocy instrukcji if, po uzyskaniu
odpowiedzi jesteśmy zmuszeni zadać następne pytanie. Pomocna może tu być
poniższa konstrukcja:

if (wyrażenie1)

instrukcja1

else if (wyrażenie2)

instrukcja2

else if (wyrażenie3)

instrukcja3

else instrukcja4

lub (tak jest chyba czytelniej) tak jak pokazano poniżej:

if (wyrażenie1)

instrukcja1

else if (wyrażenie2)

instrukcja2

else if (wyrażenie3)

instrukcja3

else instrukcja4

Jeśli prawdą jest

wyrażenie1

, to wykonana będzie

instrukcja1

, jeśli nie, to

jeśli prawdą jest

wyrażenie2

, to wykonana będzie

instrukcja2

, jeśli nie, to

jeśli prawdą jest

wyrażenie3

, to wykonana będzie

instrukcja3

, jeśli nie, to

wykonana będzie

instrukcja4

Oczywiście ilość instrukcji if jest dowolna.

Aby taki zapis był czytelniejszy, należy stosować nawiasy klamrowe. Jeśli
mamy kilka if i else, to else należy zawsze do najbliższej z poprzednich
instrukcji if. Oczywiście, nawiasy klamrowe mają pierwszeństwo przed tą
zasadą.

25

background image

/* Program pr3_5.cpp

If - wielowariantowe

*/

#include <iostream.h>

main()
{

int a;

cout << "podaj liczbę -> ";
cin >> a;

if (a == 1) //Jeśli wpisano 1, to wykona się poniższa instrukcja

//i żadna z dalszych instrukcji.

cout << "Teraz podałeś jedynkę\n";

else if (a == 2)

cout << "Teraz podałeś dwójkę\n";

else if (a == 3)

cout << "Teraz podałeś trójkę\n";

else

cout << "Nie podałeś liczb 1, 2 ani 3\n";

}

Notatki:

3.6

Trochę inne if. Wyrażenie warunkowe

Wyobraźmy sobie, że mamy nadać wartość zmiennej

wynik

w zależności od

zmiennej

a

. Jak inaczej zapisać poniższy fragment programu:

if (a == 5)

wynik = 1;

else wynik = 2;

Właśnie tak:

wynik = (a == 5) ? 1 : 2;

Jest to przykład wyrażenia warunkowego. Jego postać jest następująca:

(warunek) ? wartość1 : wartość2;

Jeśli warunek jest spełniony, to wyrażenie przyjmuje wartość -

wartość1

,

w przeciwnym razie

wartość2

. Dodatkowo

wartość1

i

wartość2

mogą być

dowolnymi stałymi lub wartościami wyrażeń.

26

background image

Słowa warunek, tak jak powyżej, będziemy używać w dalszej części wykładu
dla dowolnych wyrażeń, gdzie prawda i fałsz są interpretowane tak, jak to
zdefiniowano poprzednio.

/* Program pr3_6.cpp

Wyrażenie warunkowe 1

*/

#include <iostream.h>

main()
{

int wynik, a;

cout << "Podaj liczbę całkowitą -> ";
cin >> a;

//Zmienna wynik przyjmie wartość zależną od zmiennej a.

wynik = (a == 3) ? 1 : 0;

if (wynik)

cout << "Wpisałeś 3";

else cout << "Nie wpisałeś 3";

}

Notatki:

/* Program pr3_7.cpp

Wyrażenie warunkowe 2

*/

#include <iostream.h>

main()
{

int a;

cout << "Podaj liczbę całkowitą -> ";
cin >> a;

//Poniżej wyrażenie warunkowe zwraca wartość będącą stringiem.

cout << ( (a == 3) ?

"Napisałeś trojkę\n" : "Nie lubisz trójki\n" );

}

Notatki:

27

background image

3.7

Pętla for

Pętle służą do wykonywania kilka razy tych samych instrukcji. Pierwsza z
pętli, które tu poznamy, to pętla for.

for (instr-początkowa; warunek; instr-kroku) instrukcja-pętli

Słowo

instrukcja-pętli

oznacza tu jak zwykle jedną instrukcje lub ich blok.

Wykonanie powyższej pętli obejmuje następujące kroki:

1. Wykonanie rozpoczyna się od wykonania instrukcji

instr-początkowa

.

2. Następuje sprawdzenie wyrażenia

warunek

. Jeśli jego wartość jest 0, to

pętla jest przerywana. W przeciwnym wypadku:

3. Wykonuje się

instrukcja-pętli

.

4. Wykonuje się

instr-kroku

i skaczemy do punktu 2.

Poniżej kilka przykładów zastosowania pętli for.

28

background image

/* Program pr3_8.cpp

Pętla for. Program wypisujący kolejne liczby naturalne

*/

#include <iostream.h>

main()
{

int i, ilosc;

cout << "Do ilu mam liczyć -> ";
cin >> ilosc;

cout << "Więc liczę od 1 do " << ilosc << "\n";

/*Poniższa pętla spowoduje wypisanie liczb od 1 do wartości

podanej przez użytkownika i zapisanej do zmiennej ilość.*/

for( i = 1; i <= ilosc; i = i+1)
{

cout << i;

//W każdym przebiegu pętli zmienna i ma inna wartość
//ze względu na instrukcje kroku.

cout << "\n";

}

cout << "No - jestem zmęczony... Cześć\n";

}

Notatki:

29

background image

/* Program pr3_9.cpp

Pętla for. Zliczający śpiących studentów ;)

*/

#include <iostream.h>

main()
{

int i, ilosc, licznik;
char znak;

cout << "Ilu jest dziś studentów na wykładzie -> ";
cin >> ilosc;

cout << "Sprawdzę czy uważają \n";

licznik = 0;

//Ponizsza pętla przebiega ilosc razy.
for( i = 1; i <= ilosc; i = i+1)
{

cout << "Czy student nr " << i << " śpi

(t/n) -> ";

cin >> znak;

//W zależności od użytkownika zwiększamy lub nie wartość
//w zmiennej licznik. Wykozystano tu instrukcję warunkową.
//Jest to o wiele zgrabniejsze niż stosowanie zwykłego if
//(choć może jeszcze nie dla wszystkich czytelne).

licznik = licznik + ( (znak == ’t’) ? 1 : 0 );

}
cout << "Tak więc liczba śpiących studentów wynosi " << licznik;
cout << "\n\n... Przynajmniej do tej pory\n";

}

Notatki:

30

background image

/* Program pr3_10.cpp

Pętla for. Program liczący silnię
Proszę zwrócić uwagę na wartości liczone przez program.
Dla dużych danych mogą być inne od oczekiwanych - dlaczego?

*/

#include <iostream.h>

main()
{

int n, i;
long silnia;

cout << "Podaj liczbę, której silnię mam obliczyć ";
cin >> n;

silnia = 1;

//Nadanie wartości początkowej zmiennej, pod którą
//będziemy przechowywać wartość silni.

for( i = 1; i <= n; i = i+1)

silnia = silnia * i;

//W tej pętli jest tylko jedna instrukcja.

cout << "A oto i wynik " << n <<"! = " << silnia;

}

Notatki:

3.8

Uwagi do pętli for

W schemacie:

for (instr-początkowa; warunek; instr-kroku) instrukcja-pętli

instr-początkowa

nie musi być tylko jedną instrukcją. Jeśli chcemy umieścić

tam więcej instrukcji, to oddzielamy je przecinkami.

Elementy

instr-początkowa

,

warunek

oraz

instr-kroku

mogą również zo-

stać pominięte. ZAWSZE jednak TRZEBA pisać średniki (;). Jeśli opuścimy

warunek

, to pętla będzie się wykonywać w nieskończoność - program się za-

pętli.

31

background image

/* Program pr3_11.cpp

Pętla for. Program liczący silnię na dwa sposoby
Przykłady opuszczania pewnych części w schemacie pętli

*/

#include <iostream.h>

main()
{

int n, i;
long silnia;

cout << "Podaj liczbę, której silnię mam obliczyć ";
cin >> n;

for( i = 1, silnia = 1; i <= n; i = i+1) // Dwie instrukcje początkowe.

silnia = silnia * i;

cout << "A oto i wynik " << n <<"! = " << silnia;

// Jeszcze raz obliczymy silnie dla n

for( i = 1, silnia = 1; i <= n; ) // Dwie instrukcje początkowe.

// Brak instrukcji kroku.

{

silnia = silnia * i;
i = i + 1;

//Tu zadbamy o instrukcję kroku.

}
cout << "\n\nA innym sposobem " << n <<"! = " << silnia;
cout << "\n\nZgadza się ? .....";

}

Notatki:

Pętla for jest typowa pętlą, którą stosuje się w przypadkach, gdy już na po-
czątku jej działania wiemy, ile razy ma się ona wykonać. W powyższych przy-
kładach wiedzieliśmy przed wykonaniem pętli, do ilu mamy liczyć, czy też
jaka jest wartość argumentu przy liczeniu silni. Oczywiście w programowaniu
spotkamy sytuacje, w których nie mamy takiej wiedzy - wtedy przydadzą się
nam inne pętle.

32

background image

4

Wykład 4

4.1

Tablice liczbowe

Często w programie potrzebujemy kilku zmiennych tego samego typu, prze-
chowujących wartości dotyczące podobnych zdarzeń. Przypuśćmy, że pisze-
my program, który zbiera informacje o ocenach studentów i liczy ich średnią.
Zmiennymi, o których była mowa, będą oceny studentów. Program ten mógł-
by wyglądać następująco:

/* Program pr4_1.cpp

Liczenie średniej ocen z kolokwium dla 5-ciu studentów.
Przykład z pojedynczymi zmiennymi.

*/

#include <iostream.h>

main()
{

int o1, o2, o3, o4, o5;

//5 zmiennych, bo 5-ciu studentów.

float srednia;

//Oceny całkowite, średnia może wyjść rzeczywista.

cout << "\nPodaj ocenę studenta nr 1 -> ";
cin >> o1;

cout << "\nPodaj ocenę studenta nr 2 -> ";
cin >> o2;

cout << "\nPodaj ocenę studenta nr 3 -> ";
cin >> o3;

cout << "\nPodaj ocenę studenta nr 4 -> ";
cin >> o4;

cout << "\nPodaj ocenę studenta nr 5 -> ";
cin >> o5;

srednia = (o1 + o2 + o3 + o4 +o5) / 5.0;

//UWAGA, napisaliśmy 5.0 nie 5

cout << "\n\n Średnia ocen wynosi " << srednia << "\n\a";

}

Notatki:

Gdybyśmy teraz chcieli zmienić ten program na taki, który liczy średnią dla
10-ciu studentów, musielibyśmy dopisać dodatkowo 5 zmiennych i 10 linijek.
A jak by to wyglądało przy 100 studentach?

33

background image

Rozwiązaniem tego problemu są tablice. Tablicę, która pomieści w sobie 5
ocen studentów definiujemy następująco:

int ocena[5];

Jest to tablica 5-cio elementowa o nazwie

ocena

. Każdy z elementów tej

tablicy jest typu

int

. Poszczególne elementy tej tablicy to:

ocena[0] ocena[1] ocena[2] ocena[3] ocena[4]

UWAGA, numeracja elementów tablicy zaczyna się od 0 (zero). Najważ-
niejszą chyba cechą tablic jest to, że indeksy (wartości w nawiasach kwadra-
towych) nie muszą być stałymi. Możemy tam wstawić zmienną typu całko-
witego. Tak więc, jeśli np. zmienna

i

ma wartości 3, to

ocena[i]

oznacza dokładnie

ocena[3]

Napiszmy jeszcze raz poprzedni program przy użyciu tablic.

34

background image

/* Program pr4_2.cpp

Liczenie średniej ocen z kolokwium dla 5-ciu studentów.
Przykład z tablicą.

*/

#include <iostream.h>

main()
{

int oceny[5];

//Tablica pięcioelementowa.

int suma, i, ilosc;
float srednia;

//Oceny całkowite, średnia może wyjść rzeczywista.

ilosc = 5;

//Zmienna potrzebna, by łatwiej zmieniać program.

for (i = 0; i < ilosc; i = i + 1)
{

cout << "\nPodaj ocenę studenta nr " << (i + 1) << " -> ";

cin >> oceny[i];

//W każdym przebiegu pętli wczytujemy
//do komórki o numerze i.

}

for (i = 0, suma = 0; i < ilosc; i = i + 1)

suma = suma + oceny[i];

srednia = suma / (ilosc + 0.0);

//Ta dziwna konstrukcja jest, by uzyskać poprawną średnią

cout << "\n\nŚrednia ocen wynosi " << srednia << "\n\a";

}

Notatki:

Jeśli teraz zechcemy, aby ten program liczył średnią dla 100 studentów, wy-
starczy zmienić jedynie dwie linijki. Musimy zmienić:

int oceny[5];

na

int oceny[100];

oraz

ilosc = 5;

na

ilosc = 100;

Przykłady definicji tablic:

float zaorbki[34];

//tablica o nazwie zarobki złożona z 34

// elementów typu float

long odleglosc[12];

//tablica o nazwie odleglosc złożona z 12
//elementów typu long

double ABC[28];

//tablica ABC złożona z 28 elementów typu double

35

background image

Ważną cechą tablic w C/C++ jest to, że zawsze w pamięci komputera zaj-
mują spójny obszar. Kolejne elementy tablicy następują w pamięci komputera
bezpośrednio jeden za drugim. UWAGA, tablice są definiowane statycznie.
Już przed kompilacją trzeba podać ich rozmiar. Nie jest poprawny program
typu:

int rozmiar;

cout << "Podaj rozmiar tablicy ";
cin >> rozmiar;

int tablica[rozmiar];

//BŁĄD rozmiar tablicy musi być znany przed
//kompilacją a tu zmienna rozmiar jest
//ustawiana w czasie działania programu

4.2

Tablice wielowymiarowe

Tak jak elementami tablic mogą być liczby, tak też elementami tablic mogą
być tablice. Takie tablice nazywamy tablicami wielowymiarowymi.

int Tab[5][9]; // Tab jest tablicą 5-cio elementową złożoną z 9-cio

//elementowych tablic o elementach typu int

Taką tablicę można również traktować, co łatwiej sobie wyobrazić, jako tabli-
cę o rozmiarach 5 x 9 złożoną z elementów typu int. Poszczególne elementy
takiej tablicy to:

Tab[0][0] Tab[0][1] Tab[0][2] .... Tab[0][8]
Tab[1][0] .........................Tab[1][8]
...........................................
...........................................
Tab[4][0]

....................... Tab[4][8]

Analogicznie możemy zdefiniować tablice dowolnego wymiaru - byle mieściły
się w pamięci.

Inne przykłady tablic wielowymiarowych:

char Znaki[4][3];

//Tablica 3x4 znakowa

float FT[3][4][6];

//Tu już tablica 3 wymiarowa 3x4x6 typu float

double Tab_D[9][67];//Każdy widzi

36

background image

/* Program pr4_3.cpp
Wyliczanie średnich ocen wyników kolokwium dla
studentów z 3 grup*/

#include<iostream.h>

main()
{

int wyniki[3][5];

//Tablica do przechowywania wyników.
//Mamy 3 grupy po 5-ciu studentów.

int nr_grupy, nr_stud, suma;
float srednia;

//Wczytujemy wyniki kolokwium.
for(nr_grupy = 0; nr_grupy <= 2; nr_grupy = nr_grupy+1)
//Pętla for odpowiedzialna za numer grupy. Indeksacja tablicy od 0.
{

cout << "\nGrupa nr " << (nr_grupy + 1);
for(nr_stud = 0; nr_stud <= 4; nr_stud = nr_stud +1)
//Pętla dotyczy poszczególnych studentów w przeglądanej grupie.
{

cout << "\nIlość punktów studenta nr " << (nr_stud +1) << " -> ";
cin >> wyniki[nr_grupy][nr_stud]; // Wczytujemy wyniki w odpowiednie

// miejsce w tablicy wyniki.

}

}

//Obliczę średnie wyniki dla wszystkich studentów.

for(suma = 0, nr_grupy = 0; nr_grupy <= 2; nr_grupy = nr_grupy+1)

for(nr_stud = 0; nr_stud <= 4; nr_stud = nr_stud +1)
{

suma = suma + wyniki[nr_grupy][nr_stud];

}

srednia = suma / 15.0;
cout << "\n\nŚrednia wyników ze wszystkich grup wynosi " << srednia;

}

Notatki:

4.3

Tablice znakowe

Z tablic typu znakowego możemy korzystać tak, jak z tablic liczbowych. Jed-
ną z ich istotnych cech (inne poznamy później) jest to, że mogą w sobie
przechowywać łańcuchy znakowe wpisywane przez użytkownika programu.
Mogą być również w całości wypisywane. Taka operacja z tablicami liczbo-
wymi nie dawałaby oczekiwanego rezultatu - proszę sprawdzić i spróbować
odpowiedzieć, dlaczego tak jest.

37

background image

#include<iostream.h>

main()
{

char imie[20];

cout << "\nJak masz na imię ? ";
cin >> imie;

//Wczytuje ciąg znaków

//Wypisuję ciąg znaków z tablicy imie.
cout << "\nWitaj " << imie;

}

4.4

Operatory logiczne

Poza poznanymi wcześniej operatorami porównania, możemy w wyrażeniach
logicznych stosować operatory sumy i iloczynu logicznego:

||

- suma logiczna

(lub)

&&

- iloczyn logiczny (i)

Priorytet operatora

&&

jest większy od priorytetu

||

. Z kolei, oba te operatory

mają priorytety mniejsze od operatorów porównywania.

Bardzo istotną cechą wyrażeń złożonych z operatorów

&&

i

||

jest to, że są

one obliczane od lewej do prawej, a liczenie to, jest przerywane, gdy już jest
pewne, czy wyrażenie jest prawdą czy fałszem.

38

background image

/* Program pr4_4.cpp

Operatory logiczne 1

*/

#include<iostream.h>

main() {

char znak;

cout << "\nWpisz dowolną literę -> ";
cin >> znak;

if ( (znak == ’t’) || (znak == ’T’) ) //Czy przyciśnięto T lub <shift>T.

cout << "\nWpisaleś małą lub dużą literę t\n";

else

cout << "\nNie wpisałeś ani małej ani dużej litery t\n";

}

Notatki:

/* Program pr4_5.cpp

Operatory logiczne 2
Kilka operatorów w jednym wyrażeniu

*/

#include<iostream.h>

main()
{

char znak;
int wiek;

cout << "\nCzy jesteś kobietą (t/n) ";
cin >> znak;

cout << "Podaj swój wiek -> ";
cin >> wiek;

//Poniżej, pod if, bardziej złożony warunek logiczny.

if ( ((znak == ’t’) || (znak == ’T’)) && (wiek <= 30) && (wiek >= 18) )

cout << "\nMoże pójdziemy na kawę? \n";

else

cout << "\nMiło cię było poznać. Do zobaczenia.";

}

Notatki:

Innym operatorem logicznym (wcześniej już wymienianym) jest jednoargu-
mentowy operator

!

(wykrzyknik). Jest to operator negacji.

39

background image

/* Program pr4_6.cpp

Operatory logiczne 3 - negacja

*/

#include<iostream.h>

main()
{

int czas, ilosc;

cout << "\nIle jeszcze minut do końca -> ";
cin >> czas;

if ( ! (czas <=10) ) //Negacja zastosowana do warunku.

cout << "\nNie wiem czy to jeszcze wytrzymam.";

else

cout << "\nNo może jeszcze mi się uda dożyć do

końca.";

cout << "\n\nIle złotych masz w portfelu? ";
cin >> ilosc;

if ( ! ilosc )

//Negacja zastosowana do zmiennej.

cout << "\nTo tyle co i ja.";

else

cout << "\nSzczęśliwiec !!!";

}

Notatki:

4.5

Operatory przypisania cd.

Często przydają się operatory zmniejszania i zwiększania wartości zmiennej
o 1. Jeśli

i

jest zmienną typu liczbowego, to:

++i ; // i = i+1 powiększenie przed użyciem zmiennej i
i++ ; // i = i+1 powiększenie po użyciu zmiennej i

--i ; // i = i-1 pomniejszenie przed użyciem zmiennej i
i-- ; // i = i-1 pomniejszenie po użyciu zmiennej i

40

background image

/* Program pr4_7.cpp

Przykład demonstrujący działanie operatorów ++ --

*/

#include<iostream.h>

main() {

int i;

i = 5;
cout << "\nWartość i++ = " << i++;
cout << "\nWartość i po operacji -> " << i;

i = 5;
cout << "\nWartość ++i = " << ++i;
cout << "\nWartość i po operacji -> " << i;

i = 5;
cout << "\nWartość i-- = " << i--;
cout << "\nWartość i po operacji -> " << i;

i = 5;
cout << "\nWartość --i = " << --i;
cout << "\nWartość i po operacji -> " << i;

}

Notatki:

Przy stosowaniu operatorów zwiększania i zmniejszania należy uważać pisząc
wyrażenia, w których następuje wielokrotne stosowanie tych operatorów do
jednej zmiennej. Jakie są wartości zmiennych

i

oraz

k

po wykonaniu poniż-

szego fragmentu?

i = 4;
j = (++i) * (++i);

Miejscem, w którym stosujemy omawiane operatory, jest np. pętla for.

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

4.6

Połączenie operatora przypisania z operatorami
arytmetycznymi

Jeśli

i

jest zmienną typu liczbowego,

u

-zmienną, stałą lub wyrażeniem typu

liczbowego wtedy to, możemy stosować następujące skróty:

41

background image

i += u;

to samo co

i = i + u;

i -= u;

to samo co

i = i - u;

i *= u;

to samo co

i = i * u;

i /= u;

to samo co

i = i / u;

i %= u;

to samo co

i = i % u;

/* Program pr4_8.cpp

Przykład demonstrujący działanie
poszerzonych operatorów przypisania

*/

#include<iostream.h>

main()
{

int i;

i = 5 ;
i += 3;
cout << "\nWartość i po i += 3

-> " << i;

i = 5 ;
i -= 3;
cout << "\nWartość i po i -= 3

-> " << i;

i = 5 ;
i *= 3;
cout << "\nWartość i po i *= 3

-> " << i;

i = 5 ;
i /= 3;
cout << "\nWartość i po i /= 3

-> " << i;

i = 5 ;
i %= 3;
cout << "\nWartość i po i %= 3

-> " << i;

}

Notatki:

4.7

Pętla while

Innym rodzajem pętli w C jest pętla while. Można ją zasymulować pętlą
for. Przydaje się jednak do jaśniejszego zapisywania programów.

while(wyrażenie) instrukcja_pętli

Wykonanie powyższej pętli obejmuje następujące kroki:

42

background image

1. Jeśli

wyrażenie

ma wartość różną od zera (prawda), wykonaj

instrukcja_pętli

.

Jeśli

wyrażenie

ma wartość równą zero (fałsz), przejdź do następnej

instrukcji występującej po pętli. Instrukcja

instrukcja_pętli

nie jest

wykonywana.

2. Po ew. wykonaniu instrukcji

instrukcja_pętli

przejdź do punktu 1.

Pętla for jest używana zazwyczaj wtedy, gdy ma się ona wykonać określoną,
czasami przed jej wykonaniem znaną, ilość razy. Gdy ilość przebiegów pętli
nie jest od razu znana, korzystamy z pętli while.

/* Program pr4_9.cpp

Pętla while

*/

#include<iostream.h>

main()
{

char znak;

znak = ’n’;

//Poniższa pętla będzie się wykonywała do czasu, aż
//nie wpiszemy znaku t lub T. Nie wiemy z góry, jak długo
//ta pętla będzie wykonywana.

while( (znak != ’t’) && (znak != ’T’))
{

cout << "Czy przerwać pracę (t/n) -> ";
cin >> znak;

}
cout << "Koniec programu.\n";

}

Notatki:

Następny przykład byłoby może łatwiej zapisać używając for (to zrobi czy-
telnik), my użyjemy pętli while.

43

background image

/* Program pr4_10.cpp

Pętla while - zamiast pętli for

*/

#include<iostream.h>

main()
{

int ilosc;

cout << "\nIle sekund do końca wykładu? -> ";
cin >> ilosc;
cout << "\nOdliczam ...";

while ( ilosc > 0 )
{

cout << ’\n’;
cout << ilosc <<" - cyk";
ilosc--;

}

cout << "\nNo to koniec";

}

Notatki:

44

background image

5

Wykład 5

5.1

Pętla do . . . while

Zapoznamy się tu z kolejnym (już ostatnim) rodzajem pętli w C. Jej postać
jest następująca:

do

instrukcja

while(warunek);

Wykonanie powyższej pętli przebiega następująco:

1. Wykonaj instrukcję

instrukcja

.

2. Jeśli

warunek

przyjmuje wartość niezerową(prawdy), przejdź do punk-

tu 1. W przeciwnym wypadku przerwij działanie pętli.

Różnica w stosunku do poznanej pętli while jest taka, że

warunek

sprawdzany

jest po wykonaniu instrukcji

instrukcja

a nie przed. Z tego też powodu

instrukcja pętli do . . . while wykona się zawsze przynajmniej jeden raz,
co nie koniecznie miało miejsce w przypadku pętli while. Popatrzmy na
przykład z poprzedniego wykładu dotyczący pętli while a zapisany teraz
przy pomocy do . . . while.

45

background image

/* Program pr5_1.cpp

Pętla do ... while - stosuje się ją tu wygodniej niż
pętlę while

*/

#include<iostream.h>

main()
{

char znak;

//Poniższa pętla będzie się wykonywała do czasu aż
//nie wpiszemy znaku t lub T. Nie wiemy z góry, jak długo
//ta pętla będzie wykonywana.

//Ważne jest to, że instrukcje tej pętli wykonają się
//przynajmniej jeden raz.

do
{

cout << "Czy przerwać pracę (t/n) -> ";
cin >> znak;

}while( (znak != ’t’) && (znak != ’T’) );

cout << "Koniec programu.\n";

}

Notatki:

Następny program pokazuje jak zastosować pętle do . . . while, gdy pytając
o potwierdzenie podczas wykonywania programu, chcemy tylko reagować na
przyciśnięcie klawiszy T,t,N,n i żadnych innych.

46

background image

/* Program pr5_2.cpp

Kody znaków
Przykład działania pętli do

...

while

*/

#include <iostream.h>

void main()
{

char znak, pytanie;
int kod;

cout << "\nProgram podający kody znaków\n";

//Jeśli uruchomiliśmy program, to chcemy, by przynajmniej raz coś
//zrobił. Dlatego zastosujemy pętlę do ... while.
do
{

//Wczytaj znak i wypisz jego kod ASCII
cout << "\nWpisz znak -> ";
cin >> znak;
kod = znak;
cout << "\nZnak - " << znak << "

Kod - " << kod;

//Poniższa pętla bedzie wykonywana do wpisania T,t,N lub n.
do
{

cout << "\nCzy liczymy dalej t/n ?";
cin >> pytanie;

}while( (pytanie != ’t’) && (pytanie != ’T’) &&

(pytanie != ’n’) && (pytanie != ’N’) );

}while( (pytanie == ’t’) || (pytanie == ’T’) );

}

Notatki:

5.2

Instrukcja break

Instrukcja break służy do przerywania działania pętli. Gdy program napotka
na tę instrukcję wewnątrz pętli for, while czy do . . . while, bez sprawdzania
jakichkolwiek warunków przechodzi do wykonania pierwszej instrukcji poza
tą pętlą.

UWAGA - jeśli instrukcja break znajduje się w zagnieżdżonych pętlach,
to przerywa ona działanie tylko pętli najbardziej zagnieżdżonej, w której się
znajduje.

47

background image

/* Program pr5_3.cpp

Instrukcja break
Przykład przerwania pętli

while przy pomocy break

Oczywiście zgrabniejszym rozwiązaniem byłoby zastosowanie
pętli do ... while z odpowiednim warunkiem - ćwiczenie.

*/

#include <iostream.h>

void main()
{

char znak;

//Warunek w poniższej pętli zawsze jest spełniony
//dlatego bez użycia instrukcji break program się zapętli.
while(1)
{

cout << "\nPierwsza instrukcja pętli while";
cout << "\nCzy przerywamy pętlę t/n? -> ";
cin >> znak;

//Jeśli podano t lub T, pętla zostanie przerwana.

if( (znak == ’t’) || (znak == ’T’) )

break;

//Jeśli wykona się break, poniższa linia zostanie pominięta.
cout << "\nOstatnia linia pętli while";

}
cout << "Tu już jesteśmy poza pętlą while";

}

Notatki:

Jak działa break w zagnieżdżonych pętlach, pokazuje następny przykład.

Ze względu na oszczędność miejsca w większych przykładach będę stosował
inne ustawianie nawiasów { i }. Proszę pamiętać, że nie ważne jaki sposób
formatowania wybierzemy - ważne, by w jednym programie stosować go kon-
sekwentnie i by był czytelny.

48

background image

/* Program pr5_4.cpp

Instrukcja break
Przykład przerwania pętli zagnieżdżonych.

*/

#include <iostream.h>

void main() {

char znak;

for ( ; ; ){

//Nieskończona pętla for.

cout << "\nPierwsza instrukcja pętli for";
cout << "\nCzy przerywamy pętlę for t/n? -> ";
cin >> znak;

//Poniższe break przerywa pierwszą pętle for.
if( (znak == ’t’) || (znak == ’T’) )

break;

do{

cout << "\nPierwsza instrukcja pętli do .. while";
cout << "\nCzy przerywamy pętlę do .. while t/n? -> ";
cin >> znak;

if( (znak == ’t’) || (znak == ’T’) )

break;

//Powyższe break przerywa drugą pętle - do .. while.

cout << "\nOstatnia linia pętli do ... while";

}while(1);

//Ten warunek też zawsze jest spełniony.

cout << "\nPierwsza instrukcja po pętli do ... while";
cout << "\nOstatnia linia pętli for";

}
cout << "Tu już jesteśmy poza pętlą for";

}

Notatki:

5.3

Instrukcja switch

Instrukcja switch służy do podejmowania decyzji wielowariantowych. Często
działanie programu, w zależności od np. wartości zmiennych, może przebie-
gać na kilka(kilkanaście) sposobów. Możemy takie sytuacje obsłużyć przy
pomocy if, lecz instrukcja switch będzie tu poręczniejsza.

49

background image

switch(wyrażenie)
{

case wartość_1:

instrukcja_1;

case wartość_2:

instrukcja_2;

.
.

case wartość_N:

instrukcja_N;

default:

instrukcja_default;

}
wyrażenie

- musi być typu całkowitego.

wartość_1 ... wartość_N

to stałe tego samego typu co

wyrażenie

.

Wykonanie:

1. Obliczana jest najpierw wartość wyrażenia -

wyrażenie

.

2. Następnie przeglądane są zgodnie z kolejnością napisaną w programie

wartość_1 ... wartość_N

. Gdy któraś z tych wartości (np.

wartość_K

) jest równa wartości wcześniej wyliczonego wyrażenia

wyrażenie

, pro-

gram zaczyna wykonywać instrukcje po

case wartość_K:

aż do ewen-

tualnego wystąpienia instrukcji break, która powoduje przeskok do
pierwszej instrukcji poza całą instrukcją switch.

3. Jeśli nie udało się znaleźć nic w punkcie 2, To program zaczyna wyko-

nywać instrukcje po

default:

UWAGA, etykieta

default:

nie musi być etykietą ostatnią - może wystę-

pować w dowolnym miejscu. Może być ona również pominięta.

W związku z uwagą w punkcie 2. instrukcja switch wygląda zazwyczaj na-
stępująco:

50

background image

switch(wyrażenie)
{

case wartość_1:

instrukcja_1;
break;

.
.
.

case wartość_N:

instrukcja_N;
break;

default:

instrukcja_default;
break;

}

Poniższe programy pokazują zastosowanie instrukcji switch.

51

background image

/* Program pr5_5.cpp

Instrukcja switch
Przykład stosowania instrukcji switch. Nie używam tu
zbyt wielu instrukcji break.

*/

#include <iostream.h>

void main()
{

int kod;

cout << "\nPodaj kod awarii -> ";
cin >> kod;

switch(kod)
{

case 3:

cout << "\n3 - Sprawdzam silnik";

case 2:

cout << "\n2 - Sprawdzam olej";

case 1:

cout << "\n1 - Sprawdzam światła";

break;

//To break, by nie wykonywać następnych instrukcji.

default:

cout << "\nNie znam takiego kodu";

}

}

Notatki:

52

background image

/* Program pr5_6.cpp

Przykład stosowania instrukcji switch.
Tu używam break przy każdej etykiecie

*/

#include <iostream.h>

void main()
{

int kod;

cout << "\nPodaj, co mam sprawdzić:"

"\n1 - swiatła\n2 - olej\n3 - silnik"
"\nCo wybierasz -> ";

cin >> kod;

switch(kod)
{

default:

//Przy takim stosowani break etykietę default:
//moge wstawić w tym miejscu.

cout << "\nNie znam takiego kodu";
break;

case 3:

//W tym przypadku sprawdzę silnik i nic więcej.

cout << "\n3 - Sprawdzam silnik";
break;

case 2:

cout << "\n2 - Sprawdzam olej";
break;

case 1:

cout << "\n1 - Sprawdzam światła";
break;

}

}

Notatki:

5.4

Instrukcja goto

Instrukcja goto służy do przeskoku z jednego miejsca programu do drugiego.

goto etykieta ;
.
.
.
etykieta:
.
.

Program, napotykając na instrukcje

goto etykieta;

jako następną wykonuje

instrukcję następującą po wierszu

etykieta:

. Etykieta to ciąg znaków (taki

53

background image

jak nazwa zmiennej) zakończony dwukropkiem (:). Etykieta może się również
znajdować przed instrukcją

goto

. Zakres ważności (widoczności) etykiety jest

ograniczony do funkcji, w której ona występuje.

cout << "\nWykonuję tę linię";

goto abc;

cout << "\nDo tej linii nigdy nie przejdę";

abc:

cout << "\n Właśnie tu przeskoczyliśmy";

UWAGA, instrukcja goto nie jest polecana gdyż sprawia, że program staje
się nieczytelny. Mogą wystąpić kłopoty z eleganckim skompilowaniem progra-
mu. Instrukcji goto można zawsze uniknąć, stosując poznane już instrukcje.

UWAGA, po instrukcji goto musi znaleźć się średnik, nie dwukropek (musi
mieć formę

goto etykieta ;

)

UWAGA, jako że instrukcja jest przez początkujących przez programistów
nadużywana, przyjmijmy zasadę, że będzie ona stosowana tam, gdzie jest ona
naprawdę konieczna. Dotyczy to programów oddawanych na ćwiczeniach. Z
każdej instrukcji goto trzeba się będzie wytłumaczyć.

Jedynym chyba miejscem, gdzie wskazane jest używanie instrukcji goto, jest
wychodzenie z zagnieżdżonych pętli. Przypominam, że poznana wcześniej in-
strukcja break powodowała tylko przerwanie działania najbardziej zagnież-
dżonej pętli. Oto przykład zastosowania goto.

54

background image

/* Program pr5_7.cpp

Instrukcja goto
Przykład pokazujący przydatność instrukcji goto przy wychodzeniu
z zagnieżdżonych pętli.

*/

#include <iostream.h>

void main()
{

int i, j, k;
char pytanie;

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

for(j = 1; j <= 5; j++)

for(k = 1; k <= 5; k++)
{

cout << "\ni = " << i << "

j = " << j << "

k = " << k ;

cout << "\n\nCzy masz już dosyć t/n ? -> ";
cin >> pytanie;

//Jeśli wpisano ’T’ lub ’t’ to wyskoczymy do linijki po
//etykiecie koniec: .
if( (pytanie == ’t’) || pytanie == ’T’) goto koniec;

//Instrukcją break nie poszłoby nam tak łatwo

}

koniec: cout << "\nNo i koniec pracy";

}

Notatki:

5.5

Instrukcja continue

Czasem zachodzi konieczność by przerwać w pewnym momencie jeden z prze-
biegów pętli i zacząć następny przebieg. Do tego właśnie służy instrukcja
continue. Ma ona postać

continue;

Może wystąpić wewnątrz dowolnej pętli (for, while, do . . . while). Program
napotykając tę instrukcję wewnątrz pętli przerywa aktualną iterację pętli i
przechodzi do następnej iteracji. Następna iteracja jest wykonywana o ile
prawdziwy jest warunek wejścia do tej pętli.

UWAGA, w pętli for(. . . ) wykonuje się wpierw instrukcja kroku, a następ-
nie sprawdzamy warunek wejścia do pętli.

55

background image

while (i < 10) // Linia 1
{

i++;
cout << "\n" << i;

continue;

// Teraz skaczemy do Linia 1

cout << "\nTa linia nie zostanie nigdy wykonana";

}

W powyższej pętli nigdy nie wykona się ostatnia linia.

/* Program pr5_8.cpp

Instrukcja continue
Wypisuję wszystkie liczby parzyste od 0 do zadanej liczby.
Oczywiście można to zrobić prościej.

*/

#include <iostream.h>

void main()
{

int i, zakres;

cout << "\nPodaj zakres -> ";
cin >> zakres;

for (i = 0; i <= zakres; i++)
{

if( (i % 2) == 1 ) continue;

//Do poniższej linii dojdziemy tylko dla i parzystych.
cout << "\n" << i;

}

}

Notatki:

56

background image

6

Wykład 6

6.1

Struktury

Tablice łączyły w jedno obiekty tego samego typu. Jeśli chcemy połączyć w
całość obiekty różnych typów użyjemy struktur.

Przypuśćmy, że w programie bazodanowym opracowujemy dane o pracowni-
kach. Dane dotyczące imienia, nazwiska, daty urodzenia, zarobków itd. może-
my przechowywać w oddzielnych zmiennych lub związać je ze sobą za pomocą
struktury. Zdefiniujmy zmienną osoba, która będzie przechowywać dane do-
tyczące pojedynczej osoby.

Przykładowa struktura:

struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}osoba;

W ten sposób zdefiniowaliśmy zmienną o nazwie

osoba

, która zawiera w sobie

5 pól: tablice znakowe

imie

,

nazwisko

,

data_urodz

, pole typu

int

o nazwie

zarobki

, oraz pole znakowe

plec

. Jeśli chcemy mieć kilka zmiennych takich

jak osoba, można to zrobić następująco:

struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}osoba1, osoba2, osoba3;

Często takie same zmienne strukturalne chcemy definiować w różnych miej-
scach programu. Aby za każdym razem nie wypisywać dokładnie jak nasza
struktura ma wyglądać, możemy określić wpierw nazwę typu zmiennej, a
potem używać tej nazwy do definicji zmiennych. Robimy to w sposób nastę-
pujący:

struct opis_osoby{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

};

57

background image

W ten sposób zdefiniowaliśmy typ ( pewną nazwę )

opis_osoby

, który bę-

dzie równoważny opisowi całej struktury. Nazwę tę możemy używać teraz tak
samo jak predefiniowane nazwy typów np.

int

,

float char ...

. Teraz jeśli

chcemy zdefiniować nową zmienną opisującą pracowników w naszym bazo-
danowym programie, piszemy:

opis_osoby osoba;

Możemy również łączyć ze sobą definicje typów i zmiennych tych typów.

struct opis_osoby{

// Tu zdefiniowaliśmy typ opis_osoby

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}osoba1;

//Tu definiujemy zmienną osoba1 która jest typu opis_osoby

opis_osoby kierownik; //Definicja zmiennej kierownik typu opis_osoby

Aby odwołać się do poszczególnych pól tak zdefiniowanych zmiennych, uży-
wamy operatora . (kropka).

struct opis_osoby{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}osoba;

//Poniżej wczytujemy do poszczególnych pól zmiennej osoba.
cin >> osoba.nazwisko;
cin >> osoba.zarobki;

//Tu wypiszemy płeć osoby
cout << osoba.plec;

58

background image

/* Program pr6_1.cpp

Struktury.

*/
#include<iostream.h>

main() {

struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}osoba1;

struct opis_osoby{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

};
opis_osoby kierownik;

cout << "Podaj dane osoby \n";
cout << "Imię -> ";
cin >> osoba1.imie;
cout << "Nazwisko -> ";
cin >> osoba1.nazwisko;
cout << "Data urodzenia -> ";
cin >> osoba1.data_urodz;

cout << "Podaj dane kierownika \n";
cout << "Nazwisko -> ";
cin >> kierownik.nazwisko;
cout << "Zarobki -> ";
cin >> kierownik.zarobki;
cout << "Płeć -> ";
cin >> kierownik.plec;

}

Notatki:

Możemy również tworzyć tablice struktur.

struct opis_osoby{

//Tu tylko definicja typu

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

};

// Zdefiniuję tablicę o nazwie pracownicy
// składającą się z 30 rekordów opisywanych przez opis_osoby

opis_osoby pracownicy[30];

59

background image

Teraz każdy element tablicy

pracownicy

jest strukturą. W ten sposób np.

nazwisko osoby zapisanej w tej tablicy w polu o numerze 4 to:

pracownicy[4].nazwisko;

/* Program pr6_2.cpp

Tablice struktur. */

#include<iostream.h>

main(){

struct opis_osoby{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

};
opis_osoby pracownicy[30];

int i;
char z;

i = 0;
do
{

cout << "Podaj dane osoby nr " << (i + 1) << ’\n’;
cout << "Imię -> ";
cin >> pracownicy[i].imie;
cout << "Nazwisko -> ";
cin >> pracownicy[i].nazwisko;
cout << "Data urodzenia -> ";
cin >> pracownicy[i].data_urodz;

cout << "Zarobki -> ";
cin >> pracownicy[i].zarobki;
cout << "Płeć -> ";
cin >> pracownicy[i].plec;

i++;
cout << "Czy chcesz dopisywać dalej (t/n)";
cin >> z;

}while( (z != ’n’) && (z != ’N’) && ( i < 30));

}

Notatki:

Można również używać jednej struktury w opisie innej struktury.

60

background image

struct opis_osoby{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

};

struct opis_szczegolowy{

//wewnątrz struktury typu opis_szczegołowy mamy
//pole dane_podstawowe typu strukturalnego opis_osoby
opis_osoby dane_podstawowe;

int numer_buta;

//dodajemy jeszcze pewne dane szczegółowe

}osoba;

Aby dostać się do pól takiej struktury, robimy to tak:

osoba.dane_podstawowe.nazwisko;

ale

osoba.numer_buta;

61

background image

/* Program pr6_3.cpp

Struktury jako pola struktur. */

#include<iostream.h>

main() {

struct opis_osoby{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

};

struct opis_szczegolowy{

opis_osoby dane_podstawowe;
int numer_buta;

}osoba;

cout << "Podaj dane osoby \n";
cout << "Imię -> ";
cin >> osoba.dane_podstawowe.imie;
cout << "Nazwisko -> ";
cin >> osoba.dane_podstawowe.nazwisko;
cout << "Data urodzenia -> ";
cin >> osoba.dane_podstawowe.data_urodz;

cout << "Zarobki -> ";
cin >> osoba.dane_podstawowe.zarobki;
cout << "Płeć -> ";
cin >> osoba.dane_podstawowe.plec;

cout << "\nInformacje dla wywiadu\n\n";
cout << "Numer buta -> ";
cin >> osoba.numer_buta;

}

Notatki:

6.2

Definiowanie typów

Podobnie jak w przypadku struktur, tak i dla innych typów danych nie mu-
simy od razu definiować zmiennej, lecz możemy wpierw opisać pewien (zło-
żony) typ, a potem jego nazwy używać do definicji zmiennych. Robimy to
przy pomocy słowa kluczowego typedef.

62

background image

//Poniżej nie definiujemy żadnej zmiennej. Mówimy tylko, że
//tablica opisuje typ będący tablicą o 30 elementach typu int

typedef int tablica[30];

tablica t1;

//Tu mówimy, że t1 jest typu tablica.
//Czyli t1 jest 30 elementową tablicą o elementach typu int

Od teraz słowa

tablica

możemy używać tak jak innych znanych już nazw

typów

int

,

float char ...

. Definicje typów mogą znaleźć się w dowolnym

miejscu programu, lecz zawsze przed ich użyciem do definiowania konkret-
nych zmiennych. W następnym przykładzie definicje te występują przed funk-
cją

main()

.

/* Program pr6_4.cpp

Definiowanie typów*/

#include<iostream.h>

typedef int tablica[30]; //tablica określa typ.

//W tym miejscu jeszcze
//nie zdefiniowaliśmy żadnej zmiennej.

typedef char napis[30];

main() {

tablica tab; //Tu definiujemy zmienną tab typu tablica, a więc

//30 elementową tablicę.

napis nazwisko, imie;

//Zmienne typu napis

int i;

cout << "Podaj imię -> ";
cin >> imie;

cout << "Podaj nazwisko -> ";
cin >> nazwisko;

for(i = 0; i <5; i++)
{

cout << "Podaj liczbę nr " << i << " -> ";
cin >> tab[i];

}

}

Notatki:

UWAGA - możemy też definiować, przy pomocy typedef typy będące
strukturami. Robi się to trochę inaczej niż poprzednio robiliśmy to ze struk-
turami. Przepiszmy przykład z opisem osoby, ale przy pomocy typedef.

63

background image

typedef struct{

//Słowo typedef mówi, że definiujemy nowy typ

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}opis_osoby;

//UWAGA, teraz nazwa typu występuje na końcu opisu

typedef struct {

//Definiujemy następny typ

opis_osoby dane_podstawowe; // Wykorzystujemy zdefiniowany wyżej typ

int numer_buta;

//Dodajemy jeszcze pewne dane szczegółowe

}opis_szczegolowy; //Dopiero tu nazwa typu

opis_szczegolowy osoba;

//Dopiero tu możemy zdefiniować zmienną,
//już poza typedef

64

background image

/* Program pr6_5.cpp

Definiowanie typów strukturalnych przy pomocy typedef. */

#include<iostream.h>

typedef struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}opis_osoby; // Teraz tu definiujemy nazwę typu.

typedef opis_osoby tablica_pracownikow[30];

main() {

//W samym programie nic się nie zmienia.

tablica_pracownikow pracownicy;
int i;
char z;

i = 0;
do
{

cout << "Podaj dane osoby nr " << (i + 1) << ’\n’;
cout << "Imię -> ";
cin >> pracownicy[i].imie;
cout << "Nazwisko -> ";
cin >> pracownicy[i].nazwisko;
cout << "Data urodzenia -> ";
cin >> pracownicy[i].data_urodz;

cout << "Zarobki -> ";
cin >> pracownicy[i].zarobki;
cout << "Płeć -> ";
cin >> pracownicy[i].plec;

i++;
cout << "Czy chcesz dopisywać dalej (t/n)";
cin >> z;

}while( (z != ’n’) && (z != ’N’) && ( i < 30));

}

Notatki:

6.3

Funkcje - wprowadzenie

Jeśli jakąś część programu chcemy wydzielić z jego całości, możemy ją zapisać
jako funkcję. Wtedy to, zamiast wpisywać tę część w miejscu jej użycia,
mówimy programowi jedynie aby wykonał taką a taką funkcję, której treść
zapisana jest gdzieś indziej. Program w C/C++ składa się z funkcji. Główną
z tych funkcji już znamy, to funkcja

main()

. Od tej funkcji program zawsze

zaczyna swoje działanie. Wewnątrz tej funkcji (jak i w dowolnej innej funkcji)

65

background image

są wywoływane różne inne funkcje. Funkcje wywoływane przez program mogą
być funkcjami napisanymi przez programistę lub też funkcjami dostarczanymi
wraz z kompilatorem czy systemem. Funkcję wywołujemy pisząc jej nazwę z
nawiasami, w których podajemy listę parametrów oddzielonych przecinkami:

nazwa_funkcji(parametr1, parametr2, ...., parametr_ostatni);

Często używaną funkcją biblioteczną jest np. funkcja licząca pierwiastek kwa-
dratowy. Ma tylko jeden parametr. Wywołanie może wyglądać np. tak:

sqrt(9);

Jako że funkcja zwraca wartość, możemy tę wartość np. podstawić pod zmien-
ną :

a = sqrt(9);

Aby wywołać funkcję, w miejscu wywołania funkcja musi być znana. Musi
więc być zdefiniowana przed miejscem wywołania, lub przed jej wywołaniem
musi wystąpić deklaracja tej funkcji. Aby móc wykorzystywać funkcję

sqrt

,

musimy powiedzieć programowi, jak ona wygląda. Funkcja ta jest dostarcza-
na z kompilatorem i dostępna w bibliotece

math

. Jej opis jest podany w pliku

math.h

. Aby użyć funkcji z tej biblioteki, musimy do programu dołączyć linię:

#include<math.h>

66

background image

/* Program pr6_6.cpp

Użycie funkcji sqrt przy liczeniu pierwiastków
równania kwadratowego. Wersja prymitywna :-). */

#include<iostream.h>
#include<math.h>

//Ta linia by móc użyć funkcję sqrt().

main(){

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

cout << "\n\nSzukanie pierwiastków równania ax^2 + bx + c = 0\n\n";
cout << "\na = ";
cin >> a;
cout << "\nb = ";
cin >> b;
cout << "\nc = ";
cin >> c;

delta = b*b - 4*a*c;
if(delta < 0) cout << "\nBrak rozwiązań";
else if (delta == 0){

x1 = (-b) / (2*a);
cout << "\x = " << x1;
}
else{
//Poniżej używam funkcji sqrt
x1 = (-b - sqrt(delta)) / (2*a);
x2 = (-b + sqrt(delta)) / (2*a);
cout << "\nx1 = " << x1 << "\nx2 = " << x2;
}

}

Notatki:

6.4

Funkcje - definicje i deklaracje funkcji

W tym rozdziale nauczymy się pisać własne funkcje.

Aby zdefiniować funkcję piszemy:

typ_zwracanej_wartości nazwa_funkcji(typ_arg1 arg1, ...,typ_ost arg_ost)
{

CIAŁO FUNKCJI

}

Zdefiniujmy funkcję wypisującą przekazaną do niej wartość

67

background image

void wypisz( int a)

//Typ funkcji void mówi, że funkcja nie zwraca
//żadnej wartości

//funkcja wypisująca wartość zmiennej a
{

cout << "Kazano mi wypisać wartość -> " << a;

}

Po takiej definicji można już używać tej funkcji pisząc np.:

wypisz(5);

/* Program pr6_7.cpp

Pisanie własnych funkcji.

*/

#include<iostream.h>

void dodaj( int a, int b)

//Typ funkcji void mówi, że funkcja nie
//zwraca żadnej wartości.

//funkcja wypisująca sumę przekazanych do niej wartości
{

cout << "\n\nSuma liczb " << a << " i " << b << " wynosi " << (a+b);

}

main()
{

int v = 5;
dodaj(v, 4);

// Wywołanie funkcji dodaj().

}

Notatki:

Do tej pory każda używana przez nas funkcja była zdefiniowana (w cało-
ści napisana) przed jakimkolwiek jej wywołaniem. Czasem takie definiowa-
nie funkcji może prowadzić do powstawania nieczytelnych programów. Jeśli
nie chcemy pisać całej funkcji przed jej wywołaniem, możemy tylko, przed
wywołaniem
, poinformować kompilator o funkcji, a samą funkcje zdefinio-
wać potem. Takie poinformowanie kompilatora nazywa się deklaracją funkcji.
Wygląda ona następująco:

typ_zwracanej_wartości nazwa_funkcji(typ_arg1 arg1, ...,typ_ost arg_ost);
lub też
typ_zwracanej_wartości nazwa_funkcji(typ_arg1, ...,typ_ost);
podajemy tylko typy argumentów

UWAGA NA ŚREDNIK!!!

68

background image

void wypisz( int a);

//Deklaracja funkcji wypisz.

// void wypisz( int ); //Można też tak, opuszczając nazwę argumentu.

//Dla kompilatora jest tylko istotne, jak
//funkcja wygląda

main()
{
.
.

wypisz(9); //Wywołanie funkcji

}

void wypisz( int a)

//Definicja funkcji dopiero tutaj

{

cout << "Kazano mi wypisać wartość -> " << a;

}

Aby móc używać funkcji, przed ich wywołaniem MUSI zawsze znaleźć się
jej definicja lub deklaracja. Wprawdzie w czystym C są od tego pewne od-
stępstwa, ale w praktyce będziemy stosować C++, i tam ta zasada zawsze
obowiązuje. Zapominamy więc o tych odstępstwach - dotyczy to też pytań
na egzaminie.

Opisane wcześniej użycie

#include<math.h>

jest właśnie deklaracją dostęp-

nych w bibliotece funkcji.

/* Program pr6_8.cpp

Pisanie własnych funkcji
Przed wywołaniem funkcji tylko deklaracja. */

#include<iostream.h>

void dodaj( int a, int b);

//Deklaracja funkcji dodaj().

main()
{

int v = 5;
dodaj(v, 4);

//Tu wywołujemy funkcję, chociaż jeszcze jej nie
//zdefiniowaliśmy. Kompilator wie jednak, jak ona wygląda.

}

void dodaj( int a, int b)

//Tu dopiero definiujemy funkcję.

//funkcja wypisująca wartość zmiennej a
{

cout << "\n\nSuma liczb " << a << " i " << b << " wynosi " << (a+b);

}

Notatki:

69

background image

6.5

Funkcje - zwracanie wartości

Jak już widzieliśmy na przykładzie funkcji bibliotecznej sqrt(), funkcja może
zwracać wartość. Funkcję, która zwraca wartość, należy tak zadeklarować,
by:

1.

typ_zwracanej_wartości

był różny od void. Z poznanych dotych-

czas typów, może to być dowolny typ liczbowy, znakowy oraz struk-
tury (UWAGA, przy pewnych typach struktur może dojść do
”błędów” - ale o tym w wykładzie o wskaźnikach). UWAGA -

typ_zwracanej_wartości

NIE może być tablicą (choć może być struk-

turą, której polami są tablice).

2. W ciele funkcji musi znaleźć się instrukcja

return (wartość);

lub

return (wyrażenie);

lub

return wartość;

lub

return wyrażenie;

gdzie

wartość

czy też

wyrażenie

są typu

typ_zwracanej_wartości

.

Deklaracja i wywołanie funkcji zwracającej wartość może wyglądać następu-
jąco:

70

background image

int dodaj(int a, int b) //Funkcja dodająca dwie liczby

// UWAGA NIE można napisać int dodaj(int a, b)

{

int c;
c = a + b;
return (c); // można było też prościej return a + b;

}

//Tę funkcję możemy wywołać np.:

w = dodaj(3, 7);

//Zmienna w ma teraz wartość 10

//lub tak

c = 5;
e = 7;
w = dodaj(c, e); //Pod w mamy teraz 12

71

background image

/* Program pr6_9.cpp

Pisanie własnych funkcji.
Funkcja zwracająca wartość. */

#include<iostream.h>

int dodaj( int, int);

//Deklaracja funkcji dodaj().

main()
{

int v, wynik;

v = 5;
wynik = dodaj(v, 4); //Za wynik podstawi się to,

//co zwróci funkcja dodaj().

cout << "\n\nSuma liczb " << v << " i 4 wynosi " << wynik;

}

//Definiujemy funkcje dodaj
int dodaj( int a, int b)

//Typ funkcji int mówi, że funkcja
//zwraca wartość typu int.

{

int c;

c = a + b;
return (c);

//Tu funkcja zwraca wartość

}

Notatki:

72

background image

7

Wykład 7

7.1

Tablice znakowe

Ten rodzaj tablic nie różni się od innych tablic liczbowych. W końcu zmien-
ne typu

char

przechowują kody ASCII znaków. Przyjęto jednak pewne kon-

wencje dotyczące tego rodzaju tablic. Jeśli tablica przechowuje tekst, to po
tekście w tablicy znajduje się znak o kodzie 0 ( ’\0’). Znak ten mówi róż-
nym funkcjom działającym na tekście o jego długości. W pewnych sytuacjach
wstawienie znaku ’\0’ dzieje się automatycznie - szczególnie tam gdzie wia-
domo, że z tekstem (a nie ze zbiorem niezależnych od siebie kodów) mamy
do czynienia. Jeśli np wykonamy

char tab[20];

cin >> tab;

i użytkownik poda z klawiatury tekst, np. ala, to w tablicy tab będą się
znajdowały następujące wartości:

tab[0] - ’a’

tab[1] - ’l’

tab[2] - ’a’

tab[3] - ’\0’

To, co jest w komórkach 4 -19, jest (zazwyczaj) nieistotne,

a wiemy o tym dlatego, że w komórce 3 jest wartość ’\0’.

W poniższym programie można przy pomocy debugger’a podejrzeć zawartość
odpowiedniej tablicy. Uwaga, jeśli wczytujemy coś z klawiatury przy pomocy

cin >> tab;

jako koniec tekstu uważany jest dowolny wprowadzony biały

znak lub <Enter>.

73

background image

/* Program pr7_1.cpp

Tablice znakowe.

*/

#include<iostream.h>

main() {

char tab[20];

cout << "Podaj swoje imię -> ";

//Ponizej wczytamy tekst do tablicy. Te pola
//w tab, które nie zostaną zapisane, będą miały stare wartości.
cin >> tab;

//Przy wypisywaniu tablicy tab - nawet jeśli imię było
//krótkie nie zostaną wypisane śmieci z tablicy
cout << "Witaj " << tab;

}

Notatki:

Na tablicach znakowych można działać tak, jak na zwykłych tablicach. Mamy
również kilka funkcji przeznaczonych szczególnie do tego typu tablic. Znaj-
dują się one w bibliotece

string

, więc korzystając z nich należy na początku

programu umieścić linijkę

#include <string.h>

Przykładowo fragment programu mógłby wyglądać następująco:

74

background image

#include <string.h>
.
.
char tab1[10], tab2[10], tab3[5];

cin >> tab1;

//Tu właśnie kopiuje string zawarty w tablicy tab1 do tablicy tab2
//przy pomocy funkcji strcpy.
strcpy( tab2, tab1);
strcpy( tab3, tab1); //UWAGA, tu może zdarzyć się coś niemiłego:

//w tab3 może być za mało miejsca.
//Program, gdyby w tab1 był za długi tekst,
//będzie pisał poza tablicą, NIE ostrzeże nas.

strncpy( tab3, tab1, 5) //Działa jak strcpy, ale kopiuje nie więcej

//niż liczba znaków podana przez
//ostatni parametr, tu 5. Nie będziemy więc
//mieli poprzedniego problemu.

tab3[4] = ’\0’; //Na wszelki wypadek, bo gdyby tekst w tab1 był za

//długi, strncpy mogłoby nie dopisać końcowego znaku ’\0’

strcat(tab1, tab2); //Po tym co, było w tab1, zapisujemy zawartość

//tab2. Zapisujemy od pozycji, w której był
//’\0’ w tab1, kasując go.

Aby poznać inne funkcje działające na ciągach znakowych, zobacz opis
string.h w pomocy twego kompilatora.

75

background image

/* Program pr7_2.cpp

Operacje na tablicach znakowych.

*/

#include<iostream.h>
#include<string.h>

main()
{

char tab1[10], tab2[10], tab3[5];

cout << "Podaj Twoje imię";
cin >> tab1;

strcpy(tab2, tab1);

cout << "Twoje imię to " << tab2 << ’\n’;

// strcpy(tab3, tab1);

//Tu mógł być błąd.

strncpy(tab3, tab1, 5);

//Na wszelki wypadek robimy to tak:

tab3[4] = ’\0’;

cout << "Cztery początkowe litery imienia to " << tab3 << ’\n’;

//Można też tak postępować z tablicą.

cout << "Pierwsza litera Twojego imienia to " << tab1[0] << ’\n’;

}

Notatki:

7.2

Inicjalizacja zmiennych

Dotychczas wartości zmiennym nadawaliśmy wewnątrz programu, przy po-
mocy operacji przypisania. Można to jednak robić już przy definicji zmiennej.

76

background image

//Definiujemy tu zmienną liczba typu int nadając jej od razu wartość 1
int liczba = 1;

//Poniżej nadając wartość tablicy nie musimy podawać wartości
//wszystkich komórek. Tu pola wartości pól 0 1 2 są odpowiednio
//równe 1 2 3, a pozostałe pola są ustawiane na 0.
int tablica[30] = {1, 2 ,3};

//A teraz nadajemy wartość zmiennej typu opis_szczegolowy (struktura)

opis_szczegolowy osoba = {{"Jan","Kowalski","10-07-1974",750,’m’}, 9};

//A teraz jak inicjalizować tablice znakowe

char tab[10] = {"ala"};

// do tablicy wstawiliśmy cztery znaki UWAGA
//wartość tab[3] została ustawiona na ’\0’

char tablica[10] = "ala"; //Można skrótowo i tak

//Można też tak
chat tab[10] = {’a’, ’l’, ’a’};

//Tu tylko przez "przypadek" tab[3]
//jest ustawione na ’\0’

Jeżeli przy inicjalizacji zmiennych nie podamy wszystkich wartości a tylko
początkowe, to pozostałe zostaną wyzerowane - tak jak pokazano powyżej
dla tablic.

77

background image

/* Program pr7_3.cpp

Nadawanie wartości początkowych zmiennym. */

#include<iostream.h>

typedef struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}opis_osoby;

typedef struct{

opis_osoby dane_podstawowe;
int numer_buta;

}opis_szczegolowy;

main() {

opis_osoby osoba = {"Jan","Kowalski", "01-02-1987",342,’m’};
opis_osoby nieznany = {""}; //Wszystkie pola wyzerowane.
opis_szczegolowy pan_henio =

{{"Henryk", "Mały", "02-03-1876", 25,’m’}, 25};

int licznik = 9;
float pi = 3.14;
char tab[10] = "ala";

char ttt[10] = {’a’, ’l’, ’a’};

cout << "\nTeraz możesz tych wartości używać.";

cout << "\nPan Henio ma na imię " << pan_henio.dane_podstawowe.imie;

cout << "\n\na z kolei w tablicy tab jest napis " << tab;

}

Notatki:

Można jeszcze tak definiować i inicjalizować tablice.

char tab[] = "ala";

//Nie, nie ma tu błędu, tak zdefiniowaliśmy tablicę
//znakową o czterech polach. W tab[3] jest ’\0’

char ttt[] = {’a’, ’l’, ’a’} //A tu tylko tablica 3-elementowa.

//Z taką tablicą nie możemy postępować
//jak z napisem (stringiem)

78

background image

/* Program pr7_4.cpp

Definiowanie i inicjalizowanie tablic. */

#include<iostream.h>

main()
{

char tab[] = "ala";

char ttt[] = {’a’, ’l’, ’a’};

cout << "W tab jest napis " << tab;

cout << "\n\nW ttt jest napis " << ttt;

}

Notatki:

7.3

Funkcje - przekazywanie argumentów przez war-
tość

Weźmy naszą funkcję

dodaj()

z poprzedniego rozdziału.

int dodaj(int a, int b) //Funkcja dodająca dwie liczby
{

int c;
c = a + b;
return (c); // można było też prościej return a + b;

}

Nazwy a i b, które występują w definicji funkcji, nazywamy argumenta-
mi formalnymi funkcji. Gdy zaś wywołujemy funkcje

dodaj(3, 7)

czy też

dodaj(c, e)

, wartości występujące w nawiasach nazywamy argumentami wy-

wołania funkcji.

Wewnątrz funkcji argumenty formalne są traktowane jako normalne zmienne.
Moglibyśmy tak napisać funkcje

dodaj()

:

int dodaj(int a, int b) //Funkcja dodająca dwie liczby
{

a = a + b; //a jest zmienną typu int, możemy za nią coś podstawić
return (a);

}

Co się dzieje, gdy wywołujemy funkcję

dodaj(3,7);

79

background image

Czy nie ma tu podstawienia czegoś za stałą? Przecież 3 odpowiada zmiennej

a

, a za nią coś podstawiamy w funkcji

dodaj()

? Nie, po wywołaniu funkcji

tworzone są dwie zmienne

a

i

b

, a ich wartość jest inicjalizowana odpowiednio

na 3 i na 7.

A co przy wywołaniu

c = 5;
e = 7;
dodaj(c, e);

Jaka jest teraz wartość zmiennej c? Jest ona równa 5. I tu program postę-
puje w identyczny sposób jak poprzednio. Tworzy dwie nowe zmienne

a

i

b

,

inicjalizuje je odpowiednio na takie wartości, jakie miały zmienne c i e i dalej
operacje przeprowadza na zmiennych a i b. Tak więc nie zmienia się wartość
zmiennej

c

.

UWAGA tak nie jest zawsze. Wyjątek stanowią tablice (o tym, że tak na-
prawdę nie jest to żaden wyjątek, dowiemy się na wykładzie o wskaźnikach).
Jeśli argumentem formalnym funkcji jest tablica, to argument aktualny może
być zmieniany.

void wstaw(char tab[20]) //Można było też napisać void wstaw(char tab[]).

//W wypadku tablic "wielowymiarowych" dotyczy
//to jedynie pierwszego wymiaru

{
tab[0] = ’A’;
}

// Po wywołaniu

char ttt[] = "ala";

wstaw(ttt);

// w ttt mamy Ala a nie ala

80

background image

/* Program pr7_5.cpp

Przekazywanie argumentów przez wartość,
parametry formalne.

*/

#include<iostream.h>

int dodaj( int, int);

main()
{

int v = 5, u = 3;

cout << "\n\nSuma liczb 6 i 43

wynosi "

<< dodaj( 6, 43);

cout << "\n\nSuma liczb " << v << " i " << u << " wynosi "

<< dodaj(v, u);

cout << "\n\nZmienna v ma po wywołaniu dodaj(v, u) wartość " << v;

}

int dodaj( int a, int b)
{

a = a + b;
return

a;

}

Notatki:

81

background image

/* Program pr7_6.cpp

Tablica jako parametr funkcji.

*/

#include<iostream.h>

void wstaw(char tab[20])
{

tab[0] = ’A’; //Tablica będąca argumentem wywołania

//zostanie w tym miejscu zmieniona.

}

main()
{

char ttt[] = "ala";

cout << "\n\nPrzed wywołaniem wstaw mam w ttt " << ttt;

wstaw(ttt);

cout << "\n\nPo wywołaniu wstaw mam w ttt " << ttt;

}

Notatki:

7.4

Funkcje - przesyłanie argumentów przez referencje

Rozdział ten prezentuje rozszerzenie języka C występujące dopiero w C++.

Czy z poprzedniego rozdziału wynika, że funkcja nie może (poza tablicami)
zmieniać wartości zmiennych występujących poza ciałem tej funkcji? Tak źle
nie jest. Jeden ze sposobów robienia tego poznamy w rozdziale o wskaźnikach.
Drugi poznamy już teraz, i jest to sposób banalnie prosty.

Jeśli chcemy, aby funkcja nie tworzyła kopii argumentu aktualnego, lecz dzia-
łała wprost na nim, wystarczy w definicji (i też deklaracji) funkcji przed
nazwą odpowiedniego argumentu formalnego postawić znaczek &.

82

background image

int dodaj(int &a, int b) //Przy wywoływaniu tej funkcji nie będzie

//tworzona nowa zmienna int a, lecz funkcja działa
//na zmiennej, z którą wywołujemy funkcję.
//Dla int b będzie tworzona nowa zmienna.

{

a = a + b; //W tej sytuacji podstawiamy za zmienną,

//z którą wywoływaliśmy funkcję dodaj

return (a);

}

c = 5;
e = 7;
w = dodaj(c, e); // pod w mamy teraz 12

// Zmienna c ma teraz wartość 12

//Można też tak jak poniżej - co może wydawać się dziwne,
//w tym przypadku w ciele funkcji utworzona będzie zmienna

w = dodaj(3, 7);

83

background image

/* Program pr7_7.cpp

Przekazywanie wartości do funkcji przez referencje

*/

#include<iostream.h>

int dodaj( int &, int);

main()
{

int v = 5, u = 3;

cout << "\n\nZmienna v ma przed wywołaniem dodaj(v, u) wartość " << v;

cout << "\n\nSuma liczb " << v << " i " << u << " wynosi ";
cout

<< dodaj(v, u);

//UWAGA, sprawdź co się stanie, jeśli w poprzedniej linii nie użyjemy
//powtórnie cout ??? i DLACZEGO ???

cout << "\n\nZmienna v ma po wywołaniu dodaj(v, u) wartość " << v;

//Próba bez zmiennych.

cout << "\n\nSuma liczb 6 i 43

wynosi " << dodaj( 6, 43);

}

int dodaj( int &a, int b)
{

a = a + b;
return

a;

}

Notatki:

7.5

Funkcje -argumenty domniemane

Opisywane tu rozszerzenie definicji funkcji występuje również dopiero w
C++.

Wyobraźmy sobie sytuację, że w naszym programie często obliczamy pole
kwadratu i prostokąta. Moglibyśmy do tego celu napisać dwie różne funk-
cje. Chcielibyśmy jednak mieć tylko jedną funkcje o nazwie

pole

, która, gdy

dostanie jeden parametr, wie, że ma liczyć pole kwadratu, a gdy dwa, pole
prostokąta. Człowiek w takiej sytuacji łatwo by się domyślił - a komputer -
dlaczego nie.

Najpierw jednak napiszmy funkcję o 2 parametrach liczącą powyższe pola,
wg. znanych nam zasad. W dodatku (to pewne udziwnienie, lecz się przyda),
jeśli liczymy pole kwadratu jako pierwszy parametr, podajemy długość jego

84

background image

boku a drugi ustawiamy na 0. Przy prostokącie podajemy jako parametr
długości jego boków.

//Funkcja licząca pole kwadratu, jeśli szerokosc == 0
//jeśli szerokosc != 0, liczymy pole prostokąta

int pole(int dlugosc, int szerokosc)
{

if(szerokosc == 0)

return (dlugosc * dlugosc);

else

return (dlugosc * szerokosc);

}

Teraz zmienimy tę funkcję tak, by spełniała nasze powyższe wymagania.
Chcemy, aby komputer postępował następująco: jeśli nie zostanie podany
drugi parametr, przyjmuje, że jest on ustawiony na 0.

//Funkcja licząca pole kwadratu jeśli szerokosc == 0
//jeśli szerokosc != 0 liczymy pole prostokąta

int pole(int dlugosc, int szerokosc = 0)
{

if(szerokosc == 0)

return (dlugosc * dlugosc);

else

return (dlugosc * szerokosc);

}

Teraz wywołanie:

pole(3);

jest rozumiane jako

pole(3 ,0);

Możemy też wywoływać

pole(3, 0);
pole(34, 12);

85

background image

/* Program pr7_8.cpp

Parametry domyślne w funkcji

*/

#include<iostream.h>

//Funkcja licząca pole kwadratu jeśli szerokosc == 0
//jeśli szerokosc != 0, liczymy pole prostokąta.

int pole(int dlugosc, int szerokosc = 0)
{

if(szerokosc == 0)

return (dlugosc * dlugosc);

else

return (dlugosc * szerokosc);

}

main()
{

cout << "\nPole kwadratu o boku 5 wynosi "

<< pole(5);

//UWAGA, tylko jeden argument.

cout << "\nPole prostokąta o wymiarach 5 x 4 wynosi "

<< pole(5, 4);

//Można też tak.

cout << "\nPole kwadratu o boku 6 wynosi "

<< pole(6, 0);

//UWAGA, dwa argumenty.

}

Notatki:

7.6

Wywoływanie funkcji przez samą siebie - rekuren-
cja

W języku C przy definiowaniu funkcji możemy użyć jej samej. Takie wywoła-
nie nazywamy wywołaniem rekurencyjnym. Typowym przykładem może być
definicja funkcji obliczającej silnię.

Ogólnie obliczanie silni dla argumentu

n

możemy opisać następująco:

n! = (n - 1)! * n

czyli najpierw oblicz silnię dla

n - 1

a następnie wynik pomnóż przez

n

.

Należy pamiętać o jednej, ważnej rzeczy. Tak opisana procedura nigdy się
nie skończy. Należy dodać do niej tzw. warunek stopu. Nasza funkcja może
więc wyglądać następująco:

86

background image

long silnia(unsigned int n)
{

if(n <= 1) return 1; //Tu między innymi dbam, aby funkcja się kiedyś

//zatrzymała, dla n = 1 lub 0

else

return n * silnia(n - 1); //W funkcji silnia wywołuje samą siebie

}

A tak działa to w programie:

/* Program pr7_9.cpp

Funkcja która wywołuje samą siebie.
Przykład klasyczny - liczenie silni.

*/

#include<iostream.h>

long silnia(unsigned int n);

main()
{

unsigned int n;

cout << "\nObliczę Ci silnię, tylko powiedz, dla jakiej wartości -> ";
cin >> n;

cout << "\nLiczę...\n";

cout << "\nA oto i wynik (o ile nie zaszalałeś z argumentem) "

<< n << "! = " << silnia(n);

}

long silnia(unsigned int n)
{

if(n <= 1) return 1;

//Tu między innymi dbam, aby funkcja się kiedyś
//zatrzymała, dla n = 1 lub 0.

else

return n * silnia(n - 1); //W funkcji silnia() wywołuję ją samą.

}

Notatki:

7.7

Operator sizeof

Operator zwracający wielkość (w bajtach) zmiennej lub typu. Można go uży-
wać, gdy np. nie wiemy, jakiego rozmiaru są podstawowe dane na danym
komputerze (w różnych systemach mogą być różne), ile komputer na zmien-
ną rezerwuje miejsca. Możemy też określić, ile miejsca zajmuje obiekt typu

87

background image

definiowanego przez użytkownika (ważne, gdy ten obiekt będzie zapisywany
na lub czytany z dysku). Postać tego operatora:

sizeof(nazwa_typu)

sizeof (nazwa obiektu)

Do typów definiowanych przez programistę możemy użyć tego operatora na-
stępująco:

typedef struct {

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}opis_osoby;

//UWAGA teraz nazwa typu występuje na końcu opisu

opis_osoby kierownik;

cout << "Wielkość typu opis_osoby" << "sizeof(opis_osoby);
cout << "Rozmiar zmiennej kierownik"

<< sizeof(kierownik);

A teraz zobaczmy, jak to działa w praktyce.

88

background image

/* Program pr7_10.cpp

Zastosowanie operatora sizeof. */

#include<iostream.h>

typedef struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;

}opis_osoby; //Teraz tu definiujemy nazwę typu.

typedef struct{

opis_osoby dane_podstawowe;
int numer_buta;

}opis_szczegolowy;

main() {

opis_osoby osoba = {"Jan","Kowalski", "01-02-1987",342,’m’};
opis_szczegolowy nieznany = {""};
char tab[] = {"ala"};
char ttt[] = {’a’, ’l’, ’a’};

cout << "Wielkość typu opis_szczegolowy = " << sizeof(opis_szczegolowy);

cout << "\n\nWielkość zmiennej nieznany typu opis_szczegolowy = "

<< sizeof(nieznany);

cout << "\n\nWielkość zmiennej osoba typu opis_osoby = "

<< sizeof(osoba);

cout << "\n\nWielkość tablicy tab = " << sizeof(tab);

cout << "\n\nWielkość tablicy ttt = " << sizeof(ttt);

cout << "\n\nWielkość typu long = " << sizeof(long);
cout << "\n\nWielkość typu float = " << sizeof(float);
cout << "\n\nWielkość typu double = " << sizeof(double);

}

Notatki:

89

background image

8

Wykład 8

8.1

Funkcje - różne funkcje o tych samych nazwach

W języku C++ (w C - nie) mogą wystąpić w tym samym zakresie ważności
różne funkcje mające te same nazwy. Taką sytuację nazywamy przeładowa-
niem funkcji.

Aby możliwe było zdefiniowanie dwu (lub więcej) różnych funkcji o tych
samych nazwach, muszą być one jakoś rozróżnialne. Tym, czym one się różnią,
są różne typy argumentów wywołania tych funkcji.

void dzwiek(int ile_razy);

//Ta funkcja powtarza ustalony dźwięk
//ile_razy razy

void dzwiek(float czestotliwosc, int dlugosc);

//Powyższa zaś funkcja gra dźwięk o zadanej
//częstotliwości przez okres dlugosc

//A teraz wywołanie tych funkcji

dzwiek(5);

//Tak jak i człowiek kompilator łatwo zauważy,

dzwiek(23.34, 45); //z którą funkcją ma do czynienia

90

background image

/* Program pr8_1.cpp

Funkcje o tych samych nazwach.

*/

#include<iostream.h>

void napisz(int a)
//Funkcja wypisuje wartość zmiennej a.
{

cout << "\nFunkcję wywołano z parametrem a = " << a;

}

void napisz(char tab[])
//Funkcja wypisuje zawartość tablicy tab.
{

cout << "\nW tablicy tab mam -> " << tab;

}

main()
{

char tablica[] = "Ala ma kota";

//Poniżej wywołanie dwóch różnych funkcji o tych samych
//nazwach. O tym, która będzie wywołana, decyduje typ parametru.

napisz(123);
napisz(tablica);

}

Notatki:

8.2

Zakres ważności obiektów

Nazwy deklarowane w programie nie zawsze są widoczne (nie zawsze mo-
gą być używane) w całym programie. Zasada widoczności nazw jest prosta.
Nazwa jest widoczna od miejsca definicji w bloku, w którym została zde-
finiowana. Jest również widoczna w blokach wewnątrz powyższego bloku.
Przypomnijmy, że blok to część programu zawarta w nawiasach klamrowych.
Jako blok uznajemy też cały program. Najczęściej spotykanym blokiem jest
funkcja. Tak więc np. w dwóch różnych funkcjach możemy używać zmien-
nych o tych samych nazwach. Zmienne te prócz nazwy nie mają ze sobą nic
wspólnego.

91

background image

void fun1()
{

int liczba;
char znak;

.
.
}

int fun2(int a)
{

int liczba;

.
//Nie możemy tu użyć zmiennej znak zdefiniowanej,
//powyżej jest ona niewidoczna (nie istnieje tu)
.
}

92

background image

/* Program pr8_2.cpp

Zakres zmienności zmiennych.*/

#include<iostream.h>

void fun1() {

int liczba;
int cyfra = 67;

liczba = 5;

}

int fun2(int a) {

int liczba; //Ta zmienna nie ma nic wspólnego ze zmienną

//liczba zdefiniowaną w fun1().

liczba = a;
a = 5;
cout << "\n\nNa koniec fun2() a = " << a << ’\n’;
return 0;

}

void main() {

int cyfra = 2;

//

liczba = 8;

//Tu byłby błąd, bo w tym miejscu
//nie jest znana zmienna liczba.

cout << "\nNa początku cyfra = " << cyfra;

//Zobaczmy, czy zmienna cyfra zmienia się w funkcjach.

fun1();
cout << "\nPo fun1() cyfra = " << cyfra;

fun2(cyfra);
cout << "\nPo fun2() cyfra = " << cyfra;

}

Notatki:

8.3

Obiekty globalne

Jak wynika z poprzedniego rozdziału, zmienna zadeklarowana na zewnątrz
bloku, ale przed tym blokiem jest w nim widoczna. Dotyczy to oczywi-
ście zmiennych zdefiniowanych poza wszystkimi funkcjami (też poza funkcją

main

), będą one widoczne we wszystkich funkcjach programu znajdujących

się poniżej definicji tych zmiennych.

93

background image

int kod;
.
.
int fun()
{
.
.

zmienna = kod;

// tu już mamy dostęp do zmiennej kod,

kod = 23;

//możemy ją również zmieniać

}

Zmienne globalne mogą być alternatywą dla używania argumentów funkcji.
Nie należy jednak tego nadużywać, gdyż program staje się nieczytelny.

/* Program pr8_3.cpp

Zmienne globalne.

*/

#include<iostream.h>

int globalna; //Zmienna widoczna we wszystkich

//poniższych funkcjach.

void fun1();

void main()
{

int cyfra = 2;

globalna = 3;

cout << "\nNa początku globalna = " << globalna;

fun1();
cout << "\nPo fun1() globalna = " << globalna;

}

void fun1()
{

int liczba;

globalna = 8;
liczba = 5;

//

cyfra = 4;

//Tak nie można, cyfra nie jest tu znana.

}

Notatki:

A co, jeśli przy istnieniu zmiennej globalnej zdefiniujemy w funkcji zmienną o

94

background image

takiej nazwie jak ta zmienna globalna? Wtedy po wejściu do funkcji powstaje
nowa zmienna, przysłaniająca zmienną globalną. Dotyczy to nie tylko funkcji
ale również dowolnych bloków programu. Zmienna zdefiniowana wewnątrz
bloku przysłania zmienne zdefiniowane na zewnątrz tego bloku.

int kod;

//Zmienna globalna

.
.
int fun()
{

int kod;

//Przysłaniamy zmienną globalną.
//Tworzy się zmienna lokalna w funkcji.

kod = 23; //To podstawienia zmienia zmienną lokalną.

}

95

background image

/* Program pr8_4.cpp

Przysłanianie zmiennych. */

#include<iostream.h>

void fun1();

int kod;

//Zmienna widoczna we wszystkich poniższych
//funkcjach, o ile nie jest przysłonięta.

void main() {

float liczba = 5.5;

kod = 3;
cout << "\nNa początku kod = " << kod;

fun1();
cout << "\nPo fun1() kod = " << kod;

{ //Otwórzmy sztuczny blok. To mógłby np. być blok pętli.

float liczba; //Przysłaniamy zmienną liczba.
liczba = 8.8;
cout << "\nW sztucznym bloku liczba = " << liczba;

}

cout << "\nPo sztucznym bloku liczba = " << liczba;

}

void fun1() {

int kod;

kod = 12;
cout << "\nNa końcu fun1() kod = " << kod;

}

Notatki:

8.4

Zmienne automatyczne i statyczne

Zmienne, które zdefiniowaliśmy w bloku jakiejś funkcji, przestają istnieć po
zakończeniu działania tej funkcji. Przy powtórnym wejściu do funkcji ich war-
tości mogą być dowolne. Te zmienne nazywamy zmiennymi automatycznymi.

96

background image

void fun()
{

int zmienna; //przy każdym wejściu do tej funkcji

//zmienna

ma nieokreśloną wartość.

.
.

zmienna = 5; //wartość zmiennej zmienna przy ponownym wejściu do

//funkcji fun() nie zależy od tego podstawienia

return;
}

/* Program pr8_5.cpp

Zmienne automatyczne. */

#include<iostream.h>

void licznik()
{

int ilosc = 1; //Definicja zmiennej automatycznej.

cout << "\nWywołanie funkcji licznik nr - " << ilosc;

ilosc++;

//Czy przy każdym ponownym wejściu do funkcji licznik
//wartość zmiennej ilosc będzie większa o 1 ???

}

main()
{

int i;

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

licznik();

}

Notatki:

Istnieje możliwość, aby zmienne definiowane wewnątrz funkcji nie znikały po
zakończeniu działania tej funkcji. To zmienne statyczne. Ich definicje poprze-
dzamy słowem

static

.

97

background image

void licznik()
{

static ilosc = 1; //Definicja zmiennej statycznej

//UWAGA wartość 1 przypisywana jest zmiennej
//tylko raz, przy tworzeniu zmiennej

// static int ilosc = 1; //Powyzsze jest temu równoważne

cout << "Wywołanie funkcji licznik nr - " << ilosc;

ilosc++;

//Przy każdym ponownym wejściu do funkcji licznik
//wartość zmiennej ilosc będzie większa o 1

}

/* Program pr8_6.cpp

Zmienne statyczne. */

#include<iostream.h>

void licznik()
{

static int ilosc = 1; //definicja zmiennej statycznej

cout << "\nWywołanie funkcji licznik nr - " << ilosc;

ilosc++;

//Czy przy każdym ponownym wejściu do funkcji licznik
//wartość zmiennej ilosc będzie większa o 1 ???

}

main()
{

int i;

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

licznik();

}

Notatki:

8.5

Programy składające się z kilku plików

Pisane dotychczas przez nas programy były niewielkie. Nie trudno wyobrazić
sobie jednak programy, które będą się składać z kilkudziesięciu tysięcy linii.
Jeśli taki program zapiszemy w jednym pliku, będzie on nieczytelny, trudno
będzie nam znaleźć miejsca, gdzie wystąpił błąd itd. Aby temu zapobiec,
możemy program podzielić na logiczne części i zapisywać je w oddzielnych
plikach, a następnie powiedzieć kompilatorowi i linkerowi, by uważał je za

98

background image

jeden program. Jak to się robi, zależy od konkretnego kompilatora i trzeba
to przeczytać w dokumentacji. W kompilatorze firmy Borland możemy np.
tworzyć tak zwane projekty, w których między innymi zapisujemy informacje
na temat kompilowanych i linkowanych plików. Tu dowiemy się, jak to robić
od strony programistycznej.

Jak już wspomniano na poprzednim wykładzie, aby konkretną funkcję uży-
wać, musi być ona znana w momencie jej wywoływania. Aby to zrobić, można
ją zdefiniować przed wszystkimi wywołaniami. Można ją także wcześniej za-
deklarować, a dopiero gdzieś dalej w pliku zdefiniować.

void fun(); //Tu deklaracja funkcji fun()

main()
{

.
.
fun();

//Tu wywołujemy funkcję fun()

}

void fun() //Dopiero tu definiujemy funkcję fun()
{

//Ciało funkcji fun()

}

A co zrobić, gdy program składa się z kilku plików i chcielibyśmy, aby jakaś
funkcja zdefiniowana w jednym z plików była widoczna i mogła być używana
w innym pliku?

Jeśli w jakimś pliku

plik_1

chcemy używać funkcji zdefiniowanej w pliku

plik_2

, to przed wywołaniem funkcji musi się w pliku

plik_1

pojawić jej

deklaracja. Kompilator będzie wiedział, z funkcją jakiego typu ma do czy-
nienia, a linker będzie się martwił, aby w miejscu wywołania wstawić dobrą
funkcję.

99

background image

=================================================
//plik_1

int fun(); //Tu deklaracja funkcji fun()
.
.
.

a = fun(); //Wywołanie funkcji fun()

=================================================
//plik_2

int fun()

//Dopiero tu definiujemy funkcję fun()

{

//ciało funkcji fun()
}
=================================================

/* Program pr8_7_1.cpp

Program składający się z kilku plików.
Plik nr 1.

*/

#include<iostream.h>

int fun(int); //Tu deklaracja funkcji fun().

//Jej definicja jest w innym pliku.
//Kompilatorowi wystarczy deklaracja.

void main()
{

int a;

a = fun(5); //Wywołanie funkcji fun().

cout << "\nFunkcja fun() zwróciła wartość " << a;

}

Notatki:

100

background image

/* Program pr8_7_2.cpp

Program składający się z kilku plików.
Plik nr 2.

*/

//W tym pliku definiujemy funkcję fun().
//Nie jest ona nigdzie w tym pliku używana.

int fun( int a)
{

return (2 * a +3);

}

Notatki:

Oczywiście, w prawdziwym programie, będziemy mieli kilka plików, w każ-
dym po kilkadziesiąt funkcji. Wpisywanie deklaracji funkcji do każdego pliku
powodowałoby pewne zamieszanie, program stawałby się nieczytelny. Aby,
w tej sytuacji, ułatwić sobie życie tworzymy tak zwane pliki nagłówkowe.
Te pliki poznaliśmy już przy okazji wywoływania funkcji z dodatkowych bi-
bliotek (np. string.h). Mają one zwyczajowo rozszerzenie .h. W takim pliku
piszemy jedynie deklaracje funkcji, a następnie tam gdzie te deklaracje są
potrzebne piszemy linię:

#include "nazwa_pliku.h"

zamiast wpisywania po kolei wszystkich deklaracji.

Powyższą linijkę należy rozumieć - w to miejsce wstaw plik

nazwa_pliku.h

.

W przykładach z poprzednich wykładów nazwa wstawianego pliku była pi-
sana w nawiasach

<>

. Tu zaś użyliśmy zwykłych cudzysłowów. Zasada jest

następująca:

Jeśli wstawiamy plik nagłówkowy dostarczony przez twórcę kompilatora, pi-
szemy

<>

, jeśli ten plik napisaliśmy sami, używamy

""

, pisząc w nich ścieżkę

dostępu do pliku, bezwzględną lub względem aktualnej kartoteki.

101

background image

=================================================
//plik_1

#include "plik_2.h"

//W plik_2.h znajduje się deklaracja

.

//funkcji fun()

.
a = fun(); // Tu wywołanie funkcji fun()

=================================================
//plik_2

int fun()

//Tu definiujemy funkcję fun

{

//ciało funkcji fun()
}

=================================================
//plik_2.h

int fun(); //Tu deklaracja funkcji fun()

=================================================

102

background image

/* Program pr8_8_1.cpp

Program składający się z kilku plików.
Zastosowanie plików nagłówkowych.
Plik nr 1 - wykorzystujący niezdefiniowane w nim funkcje.

*/

#include<iostream.h>

#include "pr8_8_2.h"

//Tutaj wstawiam plik z deklaracją funkcji
//fun1() i fun2().

void main()
{

int a;
float b;

a = fun1(5); //Wywołanie funkcji fun1().
cout << "\nFunkcja fun1() zwróciła wartość " << a;

b = fun2(3); //Wywołanie funkcji fun2().
cout << "\nFunkcja fun2() zwróciła wartość " << b;

}

Notatki:

/* Program pr8_8_2.cpp

Program składający się z kilku plików.
Zastosowanie plików nagłówkowych.
Plik nr 2 - definicja funkcji wykorzystanych
w pliku pr8_8_1.cpp

*/

int fun1( int a)
{

return (2 * a +3);

}

float fun2( float a)
{

return ( (a / 4) + 24 );

}

Notatki:

103

background image

/* Program pr8_8_2.h

Program składający się z kilku plików.
Zastosowanie plików nagłówkowych.
Plik nagłówkowy z deklaracją funkcji
z pliku pr8_8_2.cpp

*/

int fun1( int );

float fun2( float );

Notatki:

8.6

Modyfikatory const, register, volatile

Jeśli używamy w programie pewnej wartości stałej, to zamiast za każdym
razem ją wprost wpisywać, możemy zdefiniować zmienną, która będzie miała
stałą wartość. Za zmienną tą nie możemy nic podstawić, jej wartość będzie
zawsze taka sama. Używamy w tym celu modyfikatora

const

.

const float pi = 3.14;

//pi jest zmienną która już zawsze
//w programie będzie miała wartość 3.14.
//Jej wartość MUSI być zadana podczas definicji.

//Operacja

pi = 3.141; //Jest niepoprawna. Wartość zmiennej definiowanej jako

//const można ustawiać jedynie przy definicji.

Jeżeli zależy nam, aby dostęp do zmiennej był szybki, definiujemy ją z mo-
dyfikatorem

register

. Takich zmiennych nie możemy mieć zbyt wiele.

register int i; // Zmienna i będzie przechowywany w rejestrze, czyli

//w komórce, do której dostęp jest szybki.

Chcąc powiedzieć, że program ma być ostrożny z pewną zmienną definiujemy
ją z modyfikatorem volatile. Są to takie zmienne, które mogą się zmieniać
niezauważalnie dla programu, np. przez obcy program lub gdy zmienna od-
powiada stanowi urządzenia peryferyjnego.

volatile int napiecie; //Nie upraszczaj sobie sprawy w kontaktach

//z tą zmienną

104

background image

/* Program pr8_9.cpp

Zastosowanie modyfikatorów const, register, volatile.

*/

#include<iostream.h>

main()
{

const float pi = 3.14;

register i;

volatile int napiecie;

// pi = 3.141;

//Tak nie można, wystąpi błąd. Wartość
//takiej zmiennej ustala się przy definicji.

cout << "Pi = " << pi;

for(i = 0; i < 10; i++) //Może będzie szybciej.

cout << "\n" << i;

}

Notatki:

105

background image

9

Wykład 9

9.1

Wskaźniki - definiowanie

Wskaźniki to obiekty o tej własności, że wskazują na obiekty innego (dowol-
nego) typu. Aby definiować wskaźnik do typu TYP, piszemy:

TYP *nazwa_wskaźnika;

//O tym, że nie jest to zmienna typu TYP
//mówi nam gwiazdka

W praktyce wygląda to następująco:

int *wsk;

//wsk jest wskaźnikiem do obiektu typu int

char * znak_wsk; //Między * a nazwą może być spacja

moja_struktura *ws; //ws jest wskaźnikiem do (wcześniej zdefiniowanego)

//typu strukturalnego moja_struktura

/* Program pr9_1.cpp

Wskaźniki, przykład 1.

*/

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

//By móc korzystać z getch().

typedef struct{

int nr_buta;
double zuzycie_na_kilometr;
}moja_struktura;

void main()
{

int *wsk; //wsk jest wskaźnikiem do obiektu typu int.
char *znak_wsk; //Dlaczego jest tak dziwnie wypisywany

//dowiemy się w następnych przykładach.

moja_struktura *ws; //ws jest wskaźnikiem do

//typu strukturalnego moja_struktura.

//Mimo, że nie wiemy, co to jest, wypiszemy te zmienne.

cout << "\nNie wiem, co to jest, ale sobie wypiszę \nws = " << ws

<< "\nznak_wsk = " << znak_wsk << "\nwsk = " << wsk;

getch(); //Taka ciekawa funkcja czekająca na przyciśnięcie klawisza.

}

Notatki:

106

background image

Wskaźniki przechowują w sobie adres, gdzie dany obiekt się znajduje, oraz
informacje o typie wskazywanego obiektu. Uwaga, wskaźnik do obiektów
jednego typu nie nadaje się do wskazywania na obiekty innych typów.

9.2

Wskaźniki - zastosowanie

Aby używać wskaźników, musimy wpierw wiedzieć, jak otrzymać informację,
gdzie nasze obiekty się znajdują, jakie są ich adresy. Robimy to przy pomocy
symbolu

&

Adres obiektu

obiekt

to

&obiekt

Tak więc, mając wskaźnik do typu TYP oraz zmienną typu TYP, możemy
za wskaźnik podstawić adres obiektu.

int *wsk;

//wsk to wskaźnik do obiektów typu int

int zmienna;

//zmienna jest typu int

wsk = &zmienna;

//Teraz wsk wskazuje na zmienną zmienna

Nasuwa się pytanie, jak możemy działać z obiektem, mając jego adres?

Jeśli

wsk

jest wskaźnikiem do obiektu, to

*wsk

jest właśnie tym obiektem.

Można się tego było domyśleć, patrząc na sposób definicji wskaźników!

int *wsk;
int zmienna;

wsk = &zmienna;

zmienna = 3;

//Wartość zmiennej zmienna to 3

//Można teraz tak

*wsk = 5;

//Wartość zmiennej zmienna to teraz 5

107

background image

/* Program pr9_2.cpp

Używanie wskaźników do zmiany zmiennych,
na które wskazują.

*/

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

void main()
{

int *wsk; //wsk jest wskaźnikiem do obiektu typu int.
char *znak_wsk;

int liczba = 10;
char znak = ’c’;

wsk = &liczba;

//wsk wskazuje na liczba.

znak_wsk = &znak;

//znak_wsk wskazuje na znak.

cout << "\nznak = " << znak

<< "\na pod wskaźnikiem mamy -> " << (*znak_wsk)
<< "\nliczba = " << liczba
<< "\na wskaźnik pokazuje -> " << *wsk;

*wsk = 50;

//Zmieniamy liczba używając wskaźnika do niej.

znak = ’a’;
cout << "\nA po podstawieniu: \nznak = " << znak

<< "\na pod wskaźnikiem mamy -> " << (*znak_wsk)
<< "\nliczba = " << liczba
<< "\na wskaźnik pokazuje -> " << *wsk;

getch();

}

Notatki:

A co ze wskaźnikami do struktur?

108

background image

typedf struct{

int liczba;
char znak;
}moja_struktura;

moja_struktura obiekt = {123, ’z’};
moja_struktura *wsk;

wsk = &obiekt;

(*wsk).liczba = 4; //Tak dostajemy się do struktury wskazywanej

//przez wskaźnik.

//A jeśli ktoś woli, można też tak (przy pomocy strzałki).
//Prawie wszyscy używają tej formy.

wsk->liczba = 44;

109

background image

/* Program pr9_3.cpp

Używanie wskaźników do struktur. */

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

typedef struct{

int nr_buta;
double zuzycie_na_kilometr;
char typ;
}moja_struktura;

void main()
{

moja_struktura ms = {12, 9.5, ’c’};
moja_struktura *ws;

ws = &ms;
cout << "\nnumer buta = " << (*ws).nr_buta

<< "\nzuzycie na 100 km = " << (*ws).zuzycie_na_kilometr
<< "\ntyp = " << (*ws).typ;

cout << "\n\nZmiana parametrów" ;

//Trzy sposoby zmiany pól struktury.
ms.nr_buta = 9;
(*ws).zuzycie_na_kilometr = 11.3;
ws->typ = ’d’;

cout << "\nnumer buta = " << (*ws).nr_buta

<< "\nzuzycie na 100 km = " << ws->zuzycie_na_kilometr
<< "\ntyp = " << ms.typ;

getch();

}

Notatki:

Inicjalizację wskaźników możemy też robić w następujący sposób:

moja_struktura obiekt = {123, ’z’};
moja_struktura * wsk

= &obiekt;

110

background image

/* Program pr9_4.cpp

Inicjalizowanie wskaźników.

*/

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

typedef struct{

int nr_buta;
double zuzycie_na_kilometr;
char typ;
}moja_struktura;

void main()
{

moja_struktura ms = {12, 9.5, ’c’}; //Musimy mieć obiekt.
moja_struktura * ws = &ms;

//Inicjalizujemy ws.

cout << "\nCo tam w obuwiu\n\n";
cout << "\nnumer buta = " << (*ws).nr_buta

<< "\nzuzycie na 100 km = " << ws->zuzycie_na_kilometr
<< "\ntyp = " << ms.typ;

getch();

}

Notatki:

Wskaźniki w czasie programu mogą wskazywać raz na jeden, raz na drugi
obiekt.

moja_struktura obiekt_pierwszy = {123, ’z’};
moja_struktura obiekt_drugi = {1, ’a’};

moja_struktura *wsk;

wsk = &obiekt_pierwszy;

//Tu wskazujemy na pierwszy obiekt

wsk = &obiekt_drugi;

//A tu już wskazujemy na drugi obiekt

111

background image

/* Program pr9_5.cpp

Wskaźniki - wskazywanie na różne obiekty.

*/

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

typedef struct{

int nr_buta;
double zuzycie_na_kilometr;
char typ;
}moja_struktura;

void main()
{

moja_struktura kalosz = {12, 9.5, ’c’};
moja_struktura super_but = {22, 3.5, ’x’};

moja_struktura *ws;

ws = &kalosz; //Wskazujemy na zmienną kalosz.
cout << "\nCo tam w obuwiu\n\n";
cout << "\nnumer buta = " << (*ws).nr_buta

<< "\nzuzycie na 100 km = " << ws -> zuzycie_na_kilometr
<< "\ntyp = " << ws -> typ;

ws = &super_but; //wskazujemy na super_but
cout << "\n\n\nA teraz SUPER BUT\n";
cout << "\nnumer buta = " << (*ws).nr_buta

<< "\nzuzycie na 100 km = " << ws -> zuzycie_na_kilometr
<< "\ntyp = " << ws -> typ;

getch();

}

Notatki:

9.3

Wskaźniki do typu void

Definiując wskaźnik do obiektu danego typu, oprócz adresu obiektu ze wskaź-
nikiem związana jest wiedza o tym, jakiego typu jest wskazywany obiekt. Jeśli
wskaźnik zdefiniujemy następująco:

void *wsk;

to ze wskaźnikiem

wsk

jest związany adres, ale nie mamy żadnej wiadomości

o typie obiektu, na który

wsk

wskazuje.

Podstawową własnością wskaźnika tego typu jest to, że pod taki wskaźnik
możemy podstawić wskaźnik do dowolnego typu. Tracimy wtedy informację
o typie, ale zachowujemy adres.

112

background image

int *wsk_int;
float *wsk_float;

void *wsk;
.
.
//wsk_float = wsk_int;

//TAK NIE MOŻNA.

wsk = wsk_int;
wsk = wsk_float;

//Tak już można.

//Podstawienie odwrotne jest błędne.

//wsk_int = wsk;
//wsk_float = wsk;

//Takie podstawienia są błędne.

//Aby coś takiego dokonać, należy zastosować operator RZUTOWANIA.

wsk_int = (int *) wsk;

//Takie podstawienia są poprawne.

wsk_float = (float *) wsk;

//Mówimy kompilatorowi, by zmienił typ.

//Konwertować możemy też następująco
wsk_float = (float *) wsk_int;

//Jawna zmiana typu

113

background image

/* Program pr9_6.cpp

Wskaźniki - podstawianie i operator rzutowania. */

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

typedef struct{

int nr_buta;
double zuzycie_na_kilometr;
char typ;
}moja_struktura;

void main(){

moja_struktura ms = {12, 9.5, ’c’};
moja_struktura *wsk_but = &ms;
int *wsk_int, liczba = 15;
float *wsk_float, rzeczywista = 3.4;
void *wsk;

//wsk_float = wsk_int; //TAK NIE MOŻNA.

wsk_int = &liczba;
wsk_float = &rzeczywista;
wsk = wsk_int;
wsk = wsk_float; //Tak już można

// cout << *wsk; //tak nie można
cout << "\nPod wsk jako float mamy -> "<<(*((float *)wsk));

wsk = wsk_float;
cout << "\nPod wsk jako int mamy -> "<<*((int *)wsk)

<< " gdy wsk wskazuje na zmienna zmiennoprzecinkową";

// wsk_float = wsk_int; //To błąd.
wsk_float = (float *)wsk_int; //Tak można tylko po co?

wsk_int = (int *)wsk_but;

//Można też tak - ale po co?

cout << "\nPod wsk_int jako int mamy -> " << (*wsk_int)

<< " gdy wsk_int wskazuje na zmienną \nmoja_struktura ms";

getch();

}

Notatki:

Dygresja - odnośnie zmiany typów. Operator rzutowania możemy też
stosować ze wszystkimi typami podstawowymi, nie tylko ze wskaźnikami.

114

background image

int a;
float f = 3.0;
char c;

//Za każdym razem mówimy kompilatorowi, że godzimy się na
//ew. utratę pewnych informacji (gdy np wartość f jest za duża)
a = (int) (f + 2.3);
c = (char) f;

/* Program pr9_7.cpp

Operator rzutowania dla typów podstawowych.

*/

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

void main()
{

int liczba = 5;
float rzeczywista = 3.4;
char c;

liczba = (int) rzeczywista; //Tu kompilator wie, że my wiemy,

//co robimy.

cout << "\nliczba = " << liczba;
c = (char)rzeczywista; //Tu też kompilator mówi "Rób co chcesz".
cout << "\nc = " << c;

getch();

}

Notatki:

9.4

Wskaźniki a tablice

Definiując tablice, ich nazw używaliśmy zawsze z indeksem pewnej komórki.
Czym jest sama nazwa tablicy? Przy definicji tablicy jej nazwa jest równocze-
śnie wskaźnikiem na jej pierwszy element. Od poznanych wcześniej wskaź-
ników różni się jedynie tym, że może wskazywać tylko na jedno miejsce w
pamięci - na początek tablicy. Poza tym może być traktowana jak wskaźnik
do typu takiego jak typ elementu tablicy.

115

background image

float *wsk_f;
float tab[20];

//Poprawne jest podstawienie
wsk_f = tab;

//takie podstawienie to to samo co
wsk_f = &tab[0];

/* Program pr9_8.cpp

Nazwa tablicy jako wskaźnik.

*/

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

void main()
{

float *wsk_f, liczba;
float tab[20] = {1.1, 2.2, 3.3, 4.5};

//Poprawne jest podstawienie
cout << "\ntab = " << tab;
wsk_f = tab;
cout << "\nwsk_f = " << wsk_f;

//Takie podstawienie to to samo co:
wsk_f = &tab[0];
cout << "\nwsk_f = " << wsk_f;

//Poniższe podstawienie jest niepoprawne,
//tab może wskazywać tylko na jedno miejsce.
//tab = &liczba;

getch();

}

Notatki:

Jedną z ciekawszych własności wskaźników jest to, że jeśli do wskaźnika wska-
zującego na pewien element tablicy dodamy 1, to będzie on wskazywał na
następny element tablicy. Dzieje się tak, ponieważ wskaźnik oprócz adresu
niesie ze sobą wiedzę na temat typu wskazywanego elementu. Oczywiście,
gdy dodamy 1 do dowolnego wskaźnika, to będzie on wskazywał w pamięci
na miejsce oddalone od poprzedniego o wielkość wskazywanego typu.

116

background image

float *wsk_f;
float tab[20];
.
.
wsk_f = &tab[5]
wsk_f = wsk_f + 1; //można też wsk_f++;

//Powyższa linia to to samo co
wsk_f = &tab[6]

//Jesli teraz chcemy pójść dalej o 4 elementy, to robimy
wsk_f = wsk_f + 4;

//A teraz o trzy elementy do tyłu
wsk_f = wsk_f - 3; //Teraz odejmowanie

//UWAGA (jeszcze raz), nazwa tablicy jest adresem zerowego elementu.
//Nie możemy go zmieniać. Tak więc poprawne jest

wsk_f = tab + 8;

//lecz błędne jest
//tab = tab + 4;

// BŁĄD

117

background image

/* Program pr9_9.cpp

Dodawanie liczb do wskaźnika. */

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

void main(){

int i;
float *wsk_f;
float tab[7] = {1.1, 2.2, 3.3, 4.5, 6.3, 4.3, 6.6};

wsk_f = &tab[5];
cout << "\nwsk_f wskazuje na " << (*wsk_f)

<< " a tab[5] = " << tab[5];

wsk_f = wsk_f + 1; //można też wsk_f++;
cout << "\nwsk_f po dodaniu 1 wskazuje na " << (*wsk_f)

<< " a tab[6] = " << tab[6];

//Poniżej wypisujemy tablicę nie używając nawiasów [].

cout << "\nWypiszmy całą tablicę \n" ;
wsk_f = tab;
for(i = 0; i < 7; i++)
{

cout << "\ntab[" << i << "] = " << *(wsk_f++);

}

cout << "\n\nWypiszmy całą tablicę od końca\n" ;
wsk_f = &tab[6];
for(i = 6; i >= 0; i--)
{

cout << "\ntab[" << i << "] = " << *(wsk_f--);

}
getch();

}

Notatki:

9.5

Operacje arytmetyczne na wskaźnikach

Jak już wspomnieliśmy, do wskaźników możemy dodawać liczby całkowite.
Wskaźniki możemy od siebie odejmować. Jeśli dwa wskaźniki wskazują na
elementy jakiejś tablicy, to ich różnicą jest różnica między indeksami ele-
mentów.

118

background image

double *wsk1, *wsk2;
double tab[20];
int a;
.
.
wsk1 = &tab[3];
wsk2 = &tab[8];

a = wsk2 - wsk1;

// Teraz pod a mamy oczywiście 5

Dodatkowo wskaźniki można porównywać przy pomocy operatorów

== != < > <= >=

/* Program pr9_10.cpp

Odejmowanie i porównywanie wskaźników. */

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

void main()
{

double *wsk1, *wsk2;
double tab[20];
int a;

wsk1 = &tab[3];
wsk2 = &tab[8];
a = wsk2 - wsk1;
cout << "\nRóżnica w indeksach pomiędzy wskazywanymi elementami\n"

<< "wynosi " << a;

if( wsk1 < wsk2) cout << "\nwsk1 wskazuje na coś wcześniejszego niż wsk2";
else cout << "\nwsk2 wskazuje na coś wcześniejszego niż wsk1";

if(wsk1 != wsk2) cout << "\nwsk1 i wsk2 wskazują na różne elementy";

getch();

}

Notatki:

119

background image

10

Wykład 10

Oprócz funkcji, które możemy sami napisać, istnieją jeszcze bardzo użytecz-
ne funkcje dostarczone razem z kompilatorem. W tym wykładzie poznamy
niektóre z nich i nauczymy się, gdzie ich szukać. Przedstawione będą funkcje
dostarczone z kompilatorem Turbo C++. Niektóre z nich mogą nie wy-
stępować z innymi kompilatorami. Dotyczy to w dużej mierze kompilatorów
działających np. pod Linuxem.

To, co musimy wiedzieć, to nazwa funkcji, jakie parametry przyjmuje i w
jakiej jest bibliotece.

10.1

Wszystkie pliki nagłówkowe po kolei

Aby używać funkcji z danej biblioteki, musimy do programu przy pomocy
dyrektywy

#include

włączyć odpowiedni plik nagłówkowy.

alloc.h

assert.h

bcd.h

bios.h

complex.h

conio.h

ctype.h

dir.h

dos.h

errno.h

fcntl.h

float.h

fstream.h

generic.h

graphics.h

io.h

iomanip.h

iostream.h

limits.h

locale.h

math.h

mem.h

process.h

setjmp.h

share.h

signal.h

stdarg.h

stddef.h

stdio.h

stdiostr.h

stdlib.h

stream.h

string.h

strstrea.h

sys\stat.h

sys\timeb.h

sys\types.h

time.h

values.h

W jaki sposób dowiedzieć się, jakie w naszym systemie mamy biblioteki (i
odpowiadające im pliki nagłówkowe), zależy od systemu. Najlepiej poszukać
w dokumentacji. W środowisku Turbo C++ możemy zrobić to następująco:

1. Wchodzimy do menu Help

2. Wybieramy Contents

3. Jeśli chcemy mieć spis dostępnych funkcji, wybieramy Functions

4. Aby mieć spis wszystkich bibliotek. wybieramy Header files

W systemie pomocy Turbo C++ przyciskając kombinację klawiszy

<Alt> + F1

cofamy się o jeden ekran do tyłu.

120

background image

Aby lepiej zrozumieć działanie poszczególnych funkcji najlepiej uruchomić
przykładowe programy znajdujące się przy opisie poszczególnych funkcji w
systemie Help.

10.2

conio.h - Funkcje dotyczą trybu tekstowego

Funkcje z tej biblioteki służą pisanie po ekranie w trybie tekstowym. Przyda
się przy pisaniu aplikacji z systemem tekstowego menu, do tekstowych ani-
macji (zadanie - napisz Tetris).

char *cgets(char *str);

- czyta string z

konsoli do zadanej długości

void clreol(void);

- czyści do końca linii w okienku tekstowym

void clrscr(void);

- czyści okienko tekstowe

int cprintf(const char *format[, argument, ...]);

- wypisuje sformato-

wane wyjście na okienko tekstowe

int cputs(const char *str);

- wypisuje tekst na ekran - korzysta z BIOSu

int cscanf(char *format[, address, ...]);

- czyta sformatowane wejście

void delline(void);

- kasuje linię w oknie tekstowym

int getch(void);

- wczytuje znak bez echa - nie potrzeba wciskać Enter

int getche(void);

- jak wyżej, ale z echem

char *getpass(const char *prompt);

- czyta hasło

int gettext(int left, int top, int right, int bottom, void *destin);

- kopiuje tekst z okienka tekstowego do pamięci

void gettextinfo(struct text_info *r);

- daje informację o trybie teksto-

wym

void gotoxy(int x, int y);

- ustawianie kursora

void highvideo(void);

- ustawia wysoką intensywność znaków

int kbhit(void);

- sprawdza czy dostępny jest jakiś znak z klawiatury

void lowvideo(void);

- ustawia niską intensywność znaków

int movetext(int left, int top, int right,

int bottom, int destleft, int desttop);

- kopiuje tekst na

ekranie z jednego miejsca w drugie

121

background image

void normvideo(void);

- przywraca standardowe opcje tekstu

int putch(int c);

- wypisuje znak na ekran

int puttext(int left, int top, int right,

int bottom, void *source);

- kopiuje tekst z pamięci na ekran

void _setcursortype(int cur_t);

- ustawia typ kursora

void textattr(int newattr);

- ustawia atrybuty tekstu

void textbackground(int newcolor);

- ustawia kolor tła

void textcolor(int newcolor);

ustawia kolor tekstu

int ungetch(int ch);

- wkłada znak do bufora klawiatury - można używać

tylko raz, a potem musi być wczytanie znaku!!

int wherex(void);

int wherey(void);

- podają współrzędne x i y kursora

void window(int left, int top, int right, int bottom);

- definiuje ak-

tywne okno tekstowe

10.3

alloc.h - Operacje na pamięci

Wprawdzie na następnym wykładzie poznamy sposób zarządzania pamięcią
pochodzący z C++, ale funkcje tu zawarte są pomocne np. przy pisaniu
programów graficznych.

void *calloc(size_t nitems, size_t size);

- przydzielenie pamięci

void far *farcalloc(unsigned long nunits, unsigned long unitsz);

-

jak wyżej, tylko na dalekiej stercie

void *malloc(size_t size);

- przydzielenie pamięci

void far *farmalloc(unsigned long nbytes);

- przydzielenie pamięci dale-

kiej

void farfree(void far * block);

- zwolnienie pamięci (przy zajmowaniu

przy pomocy far....)

void free(void far * block);

- jak wyżej

unsigned long coreleft(void);

- sprawdza ilość dostępnej pamięci

unsigned long farcoreleft(void);

- jak wyżej

122

background image

10.4

mem.h - Funkcje operujące na pamięci

void *memccpy(void *dest, const void *src, int c, size_t n);

- kopio-

wanie obszaru pamięci aż do wystąpienia znaku c

void *memcpy(void *dest, const void *src, size_t n);

- kopiowanie n

znaków

void *memset(void *s, int c, size_t n);

- wypełnienie obszaru pamięci

void *memmove(void *dest, const void *src, size_t n);

- kopiuje obsza-

ry pamięci - działa dobrze, nawet gdy te obszary się pokrywają

10.5

string.h - Wszelkie operacje na stringach

Tu tylko wybrane funkcje, jest ich wiele. Ta biblioteka zawiera też bibliote-
mem.

char *strcat(char *dest, const char *src);

dolepianie do

dest

stringu z

src

char *strchr(const char *s, int c);

- szukanie znaku

c

w stringu

s

int strcmp(const char *s1, const char *s2);

porównanie stringów

char *strcpy(char *dest, const char *src);

- kopiowanie stringów

size_t strlen(const char *s);

- długość stringu

char *strlwr(char *s);

- zamiana dużych liter na małe w stringu

char *strncat(char *dest, const char *src, size_t maxlen);

- dokleja

część jednego stringu do drugiego z ograniczeniem długości dolepianego strin-
gu

int strncmp(const char *s1, const char *s2, size_t

maxlen);

- porów-

nywanie do pewnego miejsca

char *strncpy(char *dest, const char *src, size_t maxlen);

- kopiowa-

nie nie więcej niż maxlen znaków

char *strstr(const char *s1, const char *s2);

- szuka, czy w stringu

s1

występuje podstring

s2

123

background image

10.6

dos.h - Tylko kiedy piszemy aplikacje pod DOS

int getcbrk(void);

- daje status

<Ctrl><Break>

int setcbrk(int cbrkvalue);

- ustawia aktywność

<Ctrl><Break>

void ctrlbrk(int (*handler)(void));

- ustawia reakcję na

<Ctrl><Break>

na jakąś funkcję

void delay(unsigned milliseconds);

- zatrzymanie programu na ”parę” mi-

lisekund

void _dos_getdate(struct dosdate_t *datep);

unsigned _dos_setdate(struct dosdate_t *datep);

void getdate(struct date *datep);

void setdate(struct date *datep);

- powyższe podają ew. ustawiają datę

systemową

void gettime(struct time *timep);

void settime(struct time *timep);

jw., tylko z czasem

void sleep(unsigned seconds)

- zatrzymanie programu na ”parę” sekund

10.7

dir.h - Funkcje obsługujące pliki i katalogi

int chdir(const char *path);

- zmiana kartoteki

int findfirst(const char *pathname, struct ffblk *ffblk, int attrib);

- szukanie pliku wg. wzorca

int findnext(struct ffblk *ffblk);

- jak wyżej, tylko następny plik

int getcurdir(int drive, char *directory);

- zwraca aktualna kartotekę

int getdisk(void);

int setdisk(int drive);

- zwracają ew. ustalają numer aktualnego dysku

int mkdir(const char *path);

- twórz kartotekę

int rmdir(const char *path);

- kasuj kartotekę

char *searchpath(const char *file);

- szukaj pliku lub kartoteki

124

background image

10.8

ctype.h - Funkcje informujące o znakach

int isalnum(int c);

- czy znak jest alfanumeryczny

int isalpha(int c);

- czy litera?

int isascii(int c);

- czy znak ASCII

int isdigit(int c);

- czy cyfra

int islower(int c);

- czy mała litera

int isupper(int c);

- czy duża litera

int tolower(int ch);

- zamienia literę na małą

int toupper(int ch);

- zmienia literę na dużą

10.9

math.h - Funkcje matematyczne

Tu

jedynie

podamy

nazwy

funkcji

z

tej

biblioteki.

Większość

powinna

być

bez

problemu

rozpoznana.

W

przypadku

wąt-

pliwości

proszę

popatrzeć

do

systemu

pomocy

Turbo

C++.

abs

acos

asin

atan

atan2

atof

cabs

ceil

cos

cosh

exp

fabs

floor

fmod

frexp

hypot

labs

ldexp

log

log10

matherr

modf

poly

pow

pow10

sin

sinh

sqrt

tan

tanh

10.10

stdlib.h - Przydatne funkcje, często używane

W tej bibliotece zebrano kilka często używanych funkcji, które też często
znajdują się w innych bibliotekach.

double atof(const char *s);

- konwer-

tuje string do liczby rzeczywistej

int atoi(const char *s);

- jw., tylko do int

long atol(const char *s);

- jw., do long

void exit(int status);

- kończy działanie programu

char *getenv(const char *name);

- zwraca wartość zmiennej środowiskowej

int putenv(const char *name);

- ustawia zmienną środowiskową

125

background image

char *itoa(int value, char *string, int radix);

-konwertuje liczbę ty-

pu int do stringu, w zadanym systemie

char *ltoa(long value, char *string, int radix);

- jw., tylko liczba typu

long

int random(int num);

zwraca wartość losową

void randomize(void);

- inicjalizuje generator liczb losowych

int system(const char *command);

- wykonuje komendę systemową

10.11

time.h - Funkcje dotyczące czasu

asctime

clock

ctime

difftime

gmtime

localtime

stime

time

tzset

10.12

stdio.h - Standardowe wejście i wyjście

O tej bardzo ważnej bibliotece będziemy mówić na wykładzie o pli-
kach. Zawiera ona jednak nie tylko funkcje działające na plikach ale
też na standardowym wejściu i wyjściu. Poniżej jedynie te funkcje.

char *gets(char *string);

int printf(const char *format [, argument,.]);

int putchar(int c);

int puts(const char *s);

int scanf(const char *format [, ...]);

int sprintf(char *buffer, const char *format [, argument, ...]);

int sscanf(const char *buffer, const char*format [, address,...]);

10.13

graphics.h - Tryb graficzny

Do tej pory wszystkie pisane przez nas programy działały w trybie teksto-
wym. Biblioteka tu opisywana pozwoli na wprowadzenie grafiki. Programy te,
od tych w trybie tekstowym, będą się różniły pisaniem po ekranie. Takiej bi-
blioteki możemy nie spotkać w kompilatorach innych firm czy pod Linuxem.

126

background image

Mimo to zapewne są tam inne biblioteki graficzne, a idea pisania programów
będzie podobna.

Aby móc pisać na ekranie w trybie graficznym, trzeba wpierw ten tryb za-
inicjalizować. Gdy już z niego nie będziemy korzystać, trzeba go zamknąć.

void far initgraph(int far *graphdriver, int far *graphmode,

char far *pathtodriver);

- inicjalizuje tryb graficzny.

Wygląda skomplikowanie, ale jak zobaczymy na przykładzie łatwo ją używać

void far closegraph(void);

- zamyka tryb graficzny

Między powyższymi komendami mogą znajdować się wywołania funkcji gra-
ficznych. Ważną rzeczą jest, aby razem z programem graficznym dostarczyć
odpowiedni sterownik. Sterowniki znajdują się w podkatalogu Bgi katalogu,
gdzie został zainstalowany Turbo C++. Jedynym sterownikiem, który nas
interesuje jest,

Egavga.bgi

. Jego przed uruchomieniem musimy umieścić w

katalogu z programem. Oto przykładowy program z grafiką.

127

background image

/* Program pr10_1.cpp

Prosty program w grafice.

*/

#include <graphics.h>
#include <conio.h>

int main(void)
{

//Program sam rozpozna kartę graficzną.
int gdriver = DETECT, gmode;

//Tak inicjalizuję tryb graficzny.
initgraph(&gdriver, &gmode, "");

//Tu mogę już używać funkcji z trybu graficznego
//Więc teraz coś narysuję i to na zielono

setcolor(GREEN);
circle(50,50,50);
circle(300,50,50);
rectangle(150,50,200,150);
arc(175,50,225,318,150);

getch();

//Zamykam tryb graficzny.
closegraph();
return 0;

}

Notatki:

Poniżej dostępne funkcje z tej biblioteki. Po opis parametrów odsyłam do
pomocy w Turbo C++.

128

background image

arc

getgraphmode

graphresult

setactivepage

bar

getimage

imagesize

setallpalette

bar3d

getlinesettings

initgraph

setaspectratio

circle

getmaxcolor

installuserdriver

setbkcolor

cleardevice

getmaxmode

installuserfont

setcolor

clearviewport

getmaxx

line

setfillpattern

closegraph

getmaxy

linerel

setfillstyle

detectgraph

getmodename

lineto

setgraphbufsize

drawpoly

getmoderange

moverel

setgraphmode

ellipse

getpalette

moveto

setlinestyle

fillellipse

getpalettesize

outtext

setpalette

fillpoly

getpixel

outtextxy

setrgbpalette

floodfill

gettextsettings

pieslice

settextjustify

getarccoords

getviewsettings

putimage

settextstyle

getaspectratio

getx

putpixel

setusercharsize

getbkcolor

gety

rectangle

setviewport

getcolor

graphdefaults

registerbgidriver

setvisualpage

getdefaultpalette

grapherrormsg

registerbgifont

setwritemode

getdrivername

graphfreemem

restorecrtmode

textheight

getfillpattern

graphgetmem

sector

textwidth

getfillsettings

129

background image

11

Wykład 11

Na tym wykładzie zobaczymy, jak napisać prostą animację - prostą grę .
Dla uproszczenia będziemy pisać animację w trybie tekstowym dla DOS’u.
Wszystkie uwagi można zastosować do pisania animacji w grafice. Dodatkowo
można zapamiętać, że aby uniknąć efektu migotania ekranu, można używać
kilku (w tym kompilatorze dwóch) stron graficznych.

Używane pliki nagłówkowe:

conio.h

,

dos.h

,

stdlib.h

.

11.1

Animacja - co ma robić komputer

Program powinien na ekranie rysować poruszające obiekty. Naszym obiektem
będzie pojedynczy znak (w naszym wypadku będzie to

*

, ale można wybrać

dowolny znak). Potrzebujemy funkcji rysującej ten znaczek na ekranie, w
punkcie o współrzędnych podanych w parametrach wywołania funkcji.

//Funkcja rysująca na ekranie znaczek w punkcie
//o współrzędnych x,y przy pomocy znaku znak.
//Gdy za znak wstawię spację, to będę mazał obiekt.
//
void rysuj_znaczek(int x, int y, char znak)
{

gotoxy(x,y);
putch(znak);

}

Aby uzyskać efekt ruchu, będziemy rysować nowy znaczek, a zarazem ka-
sować poprzednio narysowany. To wszystko będzie wykonywane w (na razie
nieskończonej) pętli do . . . while. Do kasowania użyjemy również funkcji

rysuj_znaczek

, podając jako znak do rysowania spację. Uwaga, nie możemy

rysować znaczka zbyt szybko, bo go nie będzie widać, i będzie spadał zbyt
szybko. Popatrz do poniższego przykładu, że opóźniam nie tylko przez funk-
cje

sleep

, czy

delay

- dlaczego wyjaśni się później. Będziemy potrzebowali

dwu globalnych zmiennych:

int x_znaczka, y_znaczka;

do przechowywania aktualnej pozycji znaczka. Znaczek będzie spadał z góry
ekranu na dół zaczynając spadanie od wylosowanego miejsca na górze ekranu.
Wysokość i szerokość ekranu zapamiętamy jako stałe,

130

background image

const int wysokosc = 24;
const int szerokosc = 48;

Aby przygotować ekran dla działania programu, potrzebne nam będą dwie
linijki:

clrscr();

//Czyszczenie ekranu

_setcursortype(_NOCURSOR);

//Wyłączenie kursora

Nasz pierwszy programik będzie wyglądał następująco:

/* Program pr11_1.cpp

Prosta animacja - nr 1

*/

#include <conio.h>
#include <dos.h>
#include <stdlib.h>

const int wysokosc = 24;
const int szerokosc = 48;

//*********************************************
//Funkcja rysująca na ekranie znaczek w punkcie
//o współrzędnych x,y przy pomocy znaku znak.
//Gdy za znak wstawię spację, to będę mazał obiekt.
//
void rysuj_znaczek(int x, int y, char znak)
{

gotoxy(x,y);
putch(znak);

}

Notatki:

131

background image

void main()
{

int x_znaczka, y_znaczka;
int licznik = 0 ;

clrscr(); //Czyszczenie ekranu
_setcursortype(_NOCURSOR); //Wyłączenie kursora.

x_znaczka = 15; //Początkowe ustawienie współrzędnych znaczka.
y_znaczka = 1;

rysuj_znaczek(x_znaczka,y_znaczka, ’*’);

do

{
//Tu rysuje animacje
if ( (licznik++) % 10 == 0 ) //Rysuje raz na dziesięć przebiegów

//pętli.

{
rysuj_znaczek(x_znaczka,y_znaczka, ’ ’);
y_znaczka = y_znaczka % wysokosc + 1 ;
if(y_znaczka == 1)

x_znaczka = random(szerokosc) + 1;

rysuj_znaczek(x_znaczka,y_znaczka, ’*’);
}

delay(10);
}while( 1 );

_setcursortype(_NORMALCURSOR);

}

Notatki:

11.2

Skąd komputer wie, czego chce użytkownik

Drugim obiektem na ekranie będzie duży prostokąt

2x3

, złożony ze

znaczków

#

. Będzie on reagował na działanie użytkownika, będzie się on

poruszał po ekranie zgodnie z przyciskanymi klawiszami strzałek. Do tego
chcemy jeszcze dodać obsługę klawisza

<ESC>

, który będzie kończył działanie

programu.

Gdy program wykryje, że został przyciśnięty jakiś klawisz, powinien zacho-
wać się zgodnie z tym, jaki to klawisz, a następnie dalej animować spadanie
małego obiektu. Jeśli nic nie zostało wciśnięte, powinien kontynuować ani-
mację.

Do sprawdzenia, czy coś zostało przyciśnięte, wywołamy funkcję:

132

background image

kbhit();

Jak rozpoznać, co zostało przyciśnięte? Nie byłoby problemu, gdyby były to
zwykłe klawisze. Po prostu byśmy je przeczytali. Z naszych klawiszy takim
jest tylko ESC, którego kod to

27

. Gdy wciskamy klawisze specjalne (np.

strzałki,

<F1>, <F2>

, ....), do bufora klawiatury zawsze wchodzą dwa kody -

pierwszy to

0

, a drugi to właściwy kod klawisza. Jakie to kody, będzie widać

z następnego przykładu. Jako ćwiczenie można napisać program wczytujący
klawisze, mówiący, czy są one specjalnymi i podający ich kody. Napiszemy
funkcję, która będzie nam mówić, co zostało przyciśnięte - jeśli nic nie zostało
przyciśnięte, będzie zwracała -1. Aby nie było problemów z różnicami między
przyciskami specjalnymi i zwykłymi zdefiniujmy wartości:

//Definiuję nazwy klawiszy
const int GORA = 1;
const int DOL = 2;
const int LEWO = 3;
const int PRAWO = 4;
const int ESC = 0;

Te właśnie wartości będą zwracane po przyciśnięciu odpowiednich klawiszy.
A teraz nasza funkcja:

133

background image

//Funkcja czytająca strzałki i ESC
//
int daj_klawisz(){

int c;
int zwrot;
if (!kbhit()) return -1;

c = getch();

if (c != 0)

zwrot = (c == 27 ? ESC : -1);

else{

c = getch();

//Tu czytamy klawisz, bez wypisywania na ekranie

switch (c){

case 72:

zwrot = GORA;
break;

case 80:

zwrot = DOL;
break;

case 77:

zwrot = PRAWO;
break;

case 75:

zwrot = LEWO;
break;

default:

//To robimy gdy przyciśnięto klawisz
//który nas nie interesuje

zwrot = -1;

}

}
return zwrot;

};

Notatki:

Reakcją na strzałki będzie ruch obiektu na ekranie - potrzebujemy funkcji
rysującej taki obiekt :

134

background image

//Funkcja rysująca na ekranie obiekt - prostokąt (np z #) w punkcie
//o współrzędnych x,y przy pomocy znaku znak.
//Gdy za znak wstawię spację, to będę mazał obiekt
//
void rysuj_obiekt(int x, int y, char znak)
{

for(int i = 0; i < 3; i++)
{

gotoxy(x + i,y);
putch(znak);
gotoxy(x + i,y + 1);
putch(znak);

}

}

Zadbamy również, by obiekt nie szedł zbyt daleko w prawo, ani w lewo. Teraz
możemy to wszystko połączyć:

/* Program pr11_2.cpp

Prosta animacja - nr 2 */

#include <conio.h>
#include <dos.h>
#include <stdlib.h>

//Definiuję nazwy klawiszy
const int GORA = 1;
const int DOL = 2;
const int LEWO = 3;
const int PRAWO = 4;
const int ESC = 0;

//Współrzędne ekranu
const int wysokosc = 24;
const int szerokosc = 48;

Notatki:

135

background image

//********************************
//Funkcja czytająca strzałki i ESC
//
int daj_klawisz(){

int c;
int zwrot;
if (!kbhit()) return -1;

c = getch();
if (c != 0)

zwrot = (c == 27 ? ESC : -1);

else{

c = getch();
switch (c){

case 72: zwrot = GORA;

break;

case 80: zwrot = DOL;

break;

case 77: zwrot = PRAWO;

break;

case 75: zwrot = LEWO;

break;

default: zwrot = -1;

}

}
return zwrot;

};

Notatki:

//*********************************************
//Funkcja rysująca na ekranie obiekt (np prostokąt z #) w punkcie
//o współrzędnych x,y przy pomocy znaku znak.
//Gdy za znak wstawię spację, to będę mazał obiekt
//
void rysuj_obiekt(int x, int y, char znak){

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

{

gotoxy(x + i,y);
putch(znak);
gotoxy(x + i,y + 1);
putch(znak);

}

}

//*********************************************
//Funkcja rysująca na ekranie znaczek w punkcie
//o współrzędnych x,y przy pomocy znaku znak.
//Gdy za znak wstawię spację, to będę mazał obiekt
//
void rysuj_znaczek(int x, int y, char znak){

gotoxy(x,y);
putch(znak);

}

Notatki:

136

background image

void main(){

int klawisz;
int x, y; //to współrzędne obiektu
int x_znaczka, y_znaczka;
int licznik = 0 ;

clrscr();
_setcursortype(_NOCURSOR);
x = 10;
y = 10;
x_znaczka = 15;
y_znaczka = 1;

rysuj_obiekt(x, y, ’#’);
rysuj_znaczek(x_znaczka,y_znaczka, ’*’);

Notatki:

137

background image

do{

//Tu rysuję animację

//Poniższe if, by obiekt spadał raz na 10 przebiegów pętli.
//To po to, by dać szansę użytkownikowi przycisnąć strzałkę.
if ( (licznik++) % 10 == 0 ){

rysuj_znaczek(x_znaczka,y_znaczka, ’ ’);
y_znaczka = y_znaczka % wysokosc + 1 ;
if(y_znaczka == 1)

x_znaczka = random(szerokosc) + 1;

rysuj_znaczek(x_znaczka,y_znaczka, ’*’);

}
delay(10);

//Nastepne opóźnienie. Tu program faktycznie stoi

klawisz = daj_klawisz();
if(klawisz != -1){

switch(klawisz){

case GORA:

rysuj_obiekt(x, y, ’ ’);
if (y >=2) y--;
rysuj_obiekt(x, y, ’#’);
break;

case DOL:

rysuj_obiekt(x, y, ’ ’);
if (y <= wysokosc - 4) y++;
rysuj_obiekt(x, y, ’#’);
break;

case PRAWO:

rysuj_obiekt(x, y, ’ ’);
if (x <= szerokosc - 2) x++;
rysuj_obiekt(x, y, ’#’);
break;

case LEWO:

rysuj_obiekt(x, y, ’ ’);
if (x >= 2) x--;
rysuj_obiekt(x, y, ’#’);
break;

}

}

}while(klawisz != ESC);
_setcursortype(_NORMALCURSOR);

}

Notatki:

Teraz potrzeba nam interakcji między obiektami na ekranie - jak w każdej
grze. Zrobimy to, badając współrzędne obiektów w głównej pętli.

Przy okazji będziemy liczyć punkty. Do ich wypisywania przyda się
nam prawa cześć ekranu, która była niewykorzystywana. Robi to funkcja

wypisz_punkty()

.

138

background image

//*********************************
//Funkcja wypisująca punkty
//
void wypisz_punkty(int ilosc)
{

int x, y;

x = wherex();

//Patrzeę gdzie jest kursor,

y = wherey();

//by móc tam powrócić.

gotoxy(szerokosc + 4 ,10);

//Skaczę do miejsca wypisywania.

cprintf("Punkty: %i", ilosc);
gotoxy(x, y);

//Wracam na poprzednie miejsce.

}

Po dopisaniu tej funkcji, funkcja

main()

nowego programu może wyglądać

następująco:

139

background image

void main(){

int klawisz;
int x, y; //to współrzędne obiektu
int x_znaczka, y_znaczka;
int licznik = 0 ;
int punkty = 0;

clrscr();
_setcursortype(_NOCURSOR);
x = 10;
y = 10;
x_znaczka = 15;
y_znaczka = 1;

wypisz_punkty(0);
rysuj_obiekt(x, y, ’#’);
rysuj_znaczek(x_znaczka,y_znaczka, ’*’);

do{

//Tu rysuję animację

//Poniższe if, by obiekt spadał raz na 10 przebiegów pętli.
//To po to, by dać szanse użytkownikowi przycisnąć strzałkę.
if ( (licznik++) % 10 == 0 ){

rysuj_znaczek(x_znaczka,y_znaczka, ’ ’);
y_znaczka = y_znaczka % wysokosc + 1 ;
if(y_znaczka == 1)

x_znaczka = random(szerokosc) + 1;

rysuj_znaczek(x_znaczka,y_znaczka, ’*’);

//Badam czy znaczek trafił w obiekt
if ((x <= x_znaczka) && (x + 3 > x_znaczka) && (y == y_znaczka) ){

sound(1500);
wypisz_punkty(++punkty);
delay(10); //Pogramy chwilkę
nosound();

}

}

Notatki:

140

background image

delay(10);
klawisz = daj_klawisz();
if(klawisz != -1){

switch(klawisz){

case GORA:

rysuj_obiekt(x, y, ’ ’);
if (y >=2) y--;
rysuj_obiekt(x, y, ’#’);
break;

case DOL:

rysuj_obiekt(x, y, ’ ’);
if (y <= wysokosc - 4) y++;
rysuj_obiekt(x, y, ’#’);
break;

case PRAWO:

rysuj_obiekt(x, y, ’ ’);
if (x <= szerokosc - 2) x++;
rysuj_obiekt(x, y, ’#’);
break;

case LEWO:

rysuj_obiekt(x, y, ’ ’);
if (x >= 2) x--;
rysuj_obiekt(x, y, ’#’);
break;

}

}

}while(klawisz != ESC);
_setcursortype(_NORMALCURSOR);

}

Notatki:

Napisany program ma wiele niedociągnięć, ale na jego podstawie można napi-
sać prawie dowolną animację. Bez problemu można z niego zrobić program w
grafice. Jako zadanie domowe proszę wzbogacić ten programik o ”strzelanie”
po naciśnięciu spacji.

141

background image

12

Wykład 12

12.1

Dynamiczna rezerwacja i zwracanie pamięci

Jedną z głównych dziedzin zastosowania wskaźników jest dynamiczna rezer-
wacja obszarów pamięci. Jeśli np. mamy tablicę złożoną z 50 elementów typu
strukturalnego

osoba

zawierającego dane o pracownikach, to nasz program

jest ograniczony do obsługiwania 50-ciu osób. Z kolei, jeśli za każdym razem,
gdy dopisujemy nową osobę, możemy zająć obszar pamięci do przechowy-
wania danych o tej osobie, nasz program jest ograniczony jedynie wielkością
dostępnej pamięci. A oto jak to można robić przy pomocy operatora new.
Uwaga - operator new jest specyficzny dla języka C++. W następnym
podrozdziale dowiemy się, jak to robić w czystym C.

Aby zobaczyć, jak działa operator new, popatrzmy na poniższy przykład:

struct osoba{

char imie[30], nazwisko[30];
int wiek, zarobki;
};

.
.
osoba *wsk_osoba; //wsk_osoba teraz wskazuje na jakieś nieokreślone

//miejsce pamięci. Miejsca tego NIE powinniśmy używać.

.
.
wsk_osoba = new osoba; //Rezerwujemy w pamięci tyle miejsca ile jest

//potrzebne na zmienną typu osoba. I na to
//zarezerwowane miejsce wskazuje wsk_osoba

wsk_osoba -> wiek = 43;

// Możemy tej pamięci już używać

wsk_osoba -> zarobki = 4233;
.
.
//Jeśli już nie będziemy tej pamięci potrzebować, możemy ją zwrócić do
//systemu. UWAGA, należy to robić, gdyż zasoby pamięci są ograniczone.
//Oto jak zwracamy pamięć zajętą przez new

delete wsk_osoba;

// I z głowy :)

142

background image

UWAGA, jeśli nie ma odpowiedniej ilości pamięci aby wykonać rezerwacje,
operator

new

zwraca wskaźnik

NULL

.

UWAGA, operatorem

delete

możemy jedynie zwracać to, co zajęliśmy ope-

ratorem

new

. UWAGA, nie wolno dwa razy zwracać tej samej pamięci, MO-

ŻE DOJŚĆ DO KATASTROFY. Jedynie próba zwrócenia pamięci o ad-
resie

NULL

nie powoduje żadnych problemów. Program sam zorientuje się, że

tego nie wolno mu robić.

/* Program pr12_1.cpp

Rezerwacja pamięci przy pomocy new.

*/

#include<iostream.h>

typedef struct{

char imie[30], nazwisko[30];
int wiek, zarobki;
}osoba;

void main()
{

osoba *wsk_osoba;

wsk_osoba = new osoba;

//Rezerwacja miejsca.

//Teraz ten obszar należy do mnie. Mogę go używać.
cout << "Podaj imie -> ";
cin >> (wsk_osoba -> imie);
cout << "Podaj nazwisko -> ";
cin >> (wsk_osoba -> nazwisko);
cout << "Podaj zarobki -> ";
cin >> (wsk_osoba -> zarobki);

cout << "Osoba " << (wsk_osoba -> imie) << " "

<< (wsk_osoba -> nazwisko)
<< " zarabia " << (wsk_osoba -> zarobki) << " zł.";

delete wsk_osoba;

//Oddaję miejsce w pamięci.

}

Notatki:

12.2

Jak to się robi w C

Do rezerwacji pamięci używamy funkcji:

malloc farmalloc, calloc, farcalloc

a zwalniać ją można funkcjami:

143

background image

free, farfree

Jeśli chcemy zarezerwować więcej pamięci, tj. powyżej 64KB, należy używać
funkcji z prefiksem

far

.

Ilość dostępnej pamięci możemy sprawdzić funkcjami:

coreleft, farcoreleft

Wyniki zwracane przez te funkcje mogą być różne dla różnych modeli kom-
pilacji.

struct osoba{

char imie[30], nazwisko[30];
int wiek, zarobki;
};

.
.
osoba *wsk_osoba;

//wsk_osoba teraz wskazuje na jakieś nieokreślone
//miejsce w pamięci. Miejsca tego NIE powinniśmy używać.

wsk_osoba = (osoba *) calloc( 1, sizeof(osoba) ); //Rezerwuję
wsk_osoba->zarobki = 2032;
.
.
free(wsk_osoba); //Zwalnaiam, gdy nie potrzebuję.

144

background image

/* Program pr12_2.cpp

Obsługa pamięci w czystym C. */

#include<iostream.h>
#include<alloc.h>

typedef struct{

char imie[30], nazwisko[30];
int wiek, zarobki;
}osoba;

void main() {

osoba *wsk_osoba;

//Sprawdzę miejsce w pamięci.
cout << "W pamięci zostało " << coreleft() << " miejsca,\n";
cout << "a na dalekiej stercie " << farcoreleft() << "\n";

wsk_osoba = (osoba *) calloc( 1, sizeof(osoba) ); //Rezerwuję

//Teraz ten obszar należy do mnie. Mogę go używać.
cout << "Podaj imię -> ";
cin >> (wsk_osoba -> imie);
cout << "Podaj nazwisko -> ";
cin >> (wsk_osoba -> nazwisko);
cout << "Podaj zarobki -> ";
cin >> (wsk_osoba -> zarobki);

cout << "Osoba " << (wsk_osoba -> imie) << " "

<< (wsk_osoba -> nazwisko)
<< " zarabia " << (wsk_osoba -> zarobki) << " zł.";

free(wsk_osoba);

//Oddaję miejsce w pamięci

}

Notatki:

12.3

Alokacja miejsca dla tablic

Przy użyciu operatora

new

możemy łatwo rezerwować miejsca dla tablic.

Przydaje się np., gdy wolimy w dostępie do pamięci posługiwać się nota-
cją tablicową, a nie wskaźnikową. Można to też wykorzystać, aby oszukać
kompilator, który nie pozwala nam statycznie zdefiniować dostatecznie dużej
tablicy. Proszę zwrócić uwagę na specyficzne zastosowanie

delete

.

145

background image

float *wsk_tab;
.
.
wsk_tab = new float[20];

//Zajęcie miejsca na 20-sto elementową
//tablice o elementach float.

//UWAGA, TERAZ TROCHĘ INACZEJ: zwalnianie tak zajętego miejsca.
delete [] wsk_tab;

//!!!! zwalniamy tablicę

!!!!!!

//UWAGA, NIE WSZYSTKIE KOMPILATORY POTRZEBUJĄ [].

// delete wsk_tab;

//Tak trzeba robić w kompilatorze Borland’a.

/* Program pr12_3.cpp

Alokacja miejsca dla tablic C++. */

#include<iostream.h>
#include<string.h>

typedef struct{

char imie[30], nazwisko[30];
int wiek, zarobki;
}osoba;

void main()
{

osoba *tab; //Wprawdzie tab jest wskaźnikiem do osoba,

//ale może być traktowana jak tablica.

//Teraz zajmę miejsce na tablice.

cout << "\n\nTeraz tablica\n";
tab = new osoba[100];

//Wpiszę jakies dane do tablicy
for (int i = 0;

i < 100; i++)

{

strcpy( tab[i].nazwisko, "Kowalski");
tab[i].zarobki = 2000 + i;
cout << tab[i].zarobki << ", " << tab[i].nazwisko << "\n";

};

delete tab;

//Zwalniam tablicę.

}

Notatki:

146

background image

12.4

Listy struktur

Bardzo przydatną cechą wskaźników jest to, że mogą one występować ja-
ko składowe struktury i wskazywać na obiekt typu struktury, w której się
znajdują.

Uwaga, to co pokazano w następnym przykładzie, nie da się zrobić, gdy
poniższy typ definiujemy przy użyciu typedef. Właśnie po to były aż dwa
sposoby na definiowanie typu strukturalnego.

struct osoba{

char imie[30], nazwisko[30];
int wiek, zarobki;
osoba *nastepna;

//następna jest wskaźnikiem na następną
//osobę

};

.
.
osoba *pierwsza;

pierwsza = new osoba;

//Tu mamy miejsce na pierwsza osobę

pierwsza -> nastepna = new osoba; //Tu druga osoba, która jest

//wskazywana przez pole nastepna
//pierwszej osoby

(pierwsza->nastepna)->nastepna = NULL;

//niech pole nastepna drugiej
//osoby pokazuje na NULL

//Tak można robić, dopóki mamy wolną pamięć.

Przy pomocy takich operacji możemy zaimplementować listy wiązane, przy-
datne np. w sortowaniu danych. List takich użyjemy także tam, gdzie mamy
w programie z góry nieznaną ilość powiązanych elementów.

147

background image

/* Program pr12_4.cpp

Lista struktur. */

#include<iostream.h>

struct osoba{

char imie[30], nazwisko[30];
int wiek, zarobki;
osoba *nastepna; //By wskazywać następny elemeny listy.
};

void main(){

osoba *wsk_osoba, *pierwsza;

wsk_osoba = pierwsza = new osoba;

//Miejsce na pierwszą osobę.

pierwsza->nastepna = NULL;

//Zajmuję miejsce na następne 30 osób. TO BĘDZIE LISTA.

for(int i = 0; i < 30; i++){

wsk_osoba->nastepna

= new osoba;

wsk_osoba = wsk_osoba->nastepna;
wsk_osoba->nastepna = NULL;

//Listę kończę NULL’em.

};

//A teraz zwolnię zajętą pamięć. Poniższy sposób jest wzorcowy
//do zwalniania list, których nie znamy. O długości mówi NULL.

wsk_osoba = pierwsza->nastepna; //By wiedzieć, gdzie zaczyna się

//lista, gdy skasujemy jej początek

while(1)
{

delete pierwsza;
if(wsk_osoba == NULL) break;
pierwsza = wsk_osoba;
wsk_osoba = pierwsza->nastepna;

}

}

Notatki:

12.5

Wskaźniki w argumentach funkcji

Jak już wcześniej powiedziano, gdy chcemy w ciele funkcji zmieniać parame-
try aktualne, możemy je do funkcji przekazywać przez referencje (dostępne
tylko w C++). Przez takie postępowanie może czasem dojść do błędów.
Gdy np. wywołujemy

fun(a)

, nie od razu wiemy, czy wartość

a

może zostać

zmieniona w funkcji

fun

, czy też nie. Aby to stwierdzić, musimy zobaczyć,

jak wygląda definicja funkcji.

Bardziej zalecanym, dostępnym już w C sposobem jest przesyłanie argumen-
tów przez adres. W takiej funkcji nie możemy zmienić przesyłanego adresu,
ale to, co przez ten adres jest wskazywane TAK. Jak można się przeko-

148

background image

nać, przeglądając opisy większości funkcji bibliotecznych, ich parametrami
są wskaźniki.

/* Program pr12_5.cpp

Wskaźniki jako argumenty funkcji. */

#include<iostream.h>

void fun(int *wsk_int){

//Argumentem tej funkcji jest wskaźnik do
//zmiennej typu int.

cout << "W fun wsk_int wskazuje na "

<< (*wsk_int);

//Widzimy to, na co wskazuje
//wskaźnik wsk_int.

//Mogę też zmieniać komórki, na którą wskazywał wsk_int.

*wsk_int = 3;

//Sam wskaźnik wsk_int pozostał bez zmian.
}

void main(){

int a;

a = 5;
fun(&a); //Jako parametr fun() podajemy adres a, czyli &a.

//Przy takim wywołaniu możemy się domyślać,
//że wartość a ulegnie zmianie.

cout << "\nPod a mamy " << a ; //Pod a jest teraz 3.

}

Notatki:

12.6

Jeszcze raz tablice w argumentach funkcji

Teraz stało się jasne, dlaczego mogliśmy zmieniać tablice w ciele funkcji, dla
których te tablice były parametrem. Jako że nazwa tablicy to wskaźnik do
jej zerowego elementu, przesyłając tablice do funkcji nie mogliśmy jedynie
zmieniać tego wskaźnika. Z kolei, już bez problemów, mogliśmy zmieniać to,
na co ten wskaźnik wskazywał.

Skoro więc nazwa tablicy i wskaźnik do pierwszego elementu tablicy to to
samo, następujące deklaracje są równoważne:

void fun(float tab[]);

149

background image

oraz

void fun(float *tab);

We wnętrzach obu tych funkcji możemy stosować zapisy typu:

tab[5];

czy też

*(tab + 5)

//Patrz na pierwszą definicję -

//to już znane z poprzednich wykładów.

/* Program pr12_6.cpp

Tablica jako argumenty funkcji. */

#include<iostream.h>

void fun(int *bla)
{

for(int i = 0; i < 10; i++)
{
bla[i] = i;

//Zmieniam to, co było pod
//adresem bla.

}

}

void main(void)
{

int tab[10];

fun(tab);
for(int i = 0; i < 10; i++)
{

cout << "\ntab[" << i << "] = " << tab[i];

}

}

Notatki:

12.7

Wskaźniki do obiektów typu const oraz wskaźniki
typu const

Jeśli z kolei nie chcemy, aby obiekt wskazywany przez wskaźnik był zmieniany,
możemy do niego wskazać wskaźnikiem do typu

const

.

150

background image

const int *wsk; //Czytamy: wsk jest wskaźnikiem do typu const int
.
.
//*wsk = 5;

//Tu kompilator pokaże błąd.

//*(wsk + 2) = 3;

//Tu też błąd.

//wsk[4] = 10; //Następny błąd.

Możemy również deklarować wskaźniki, które mogą wskazywać tylko na jedno
miejsce. Są to wskaźniki typu

const

.

int tab[10] = {0}
int tablica[10] = {0}
.
.
int * const staly_wsk = tab; //Wskaźnik stały

//staly_wsk = tablica; //Tutaj błąd wsk może tylko pokazywać na tab

//i tego nie wolno zmieniać.

12.8

Wskaźniki do funkcji

Jeśli już na wszystko wskazywaliśmy, to może spróbować wskazać na funkcje.
Czy więc wskaźnik może zawierać adres do miejsca w pamięci, gdzie znajduje
się jakaś funkcja ?

Uwaga, nazwa funkcji jest adresem miejsca w pamięci, gdzie ta funkcja się
znajduje.

151

background image

int (* wsk_fun) ();

//wsk_fun jest wskaźnikiem na funkcję, która
//ma pustą listę argumentów
//oraz zwraca wartość typu int.

float (* wsk_fun_float) (int, double);
//wsk_fun jest wskaźnikiem na funkcję, która ma dwa argumenty,
//odpowiednio typu int i double, oraz zwraca wartość typu float.
//A jak za te wskaźniki podstawić wskaźniki do funkcji?
//Wpierw potrzebujemy tych funkcji

int pierwsza();

//Deklarujemy je.

float druga(int a , double licz);

//A teraz podstawmy
wsk_fun = pierwsza;

//UWAGA NIE wsk_fun = pierwsza(); bo to by
//powodowało wywołanie funkcji pierwsza.
//Też błąd kompilacji.

wsk_fun_float = druga;

//A jak posłużyć się tymi wskaźnikami, aby wywołać te funkcje - prosto
a = ( * wsk_fun)();

//Można to czytać: Skocz do podanego adresu
//i wywołaj funkcję, która się tam znajduje.

b = ( * wsk_fun_float)(23, pi);

//ew. z parametrami.

//Na szczęście można też tak
a =

wsk_fun();

//Można to czytać: Wywołaj spod podanego
//adresu funkcję, która się tam znajduje.

b =

wsk_fun_float(23, pi);

//Tu z parametrami.

152

background image

/* Program pr12_7.cpp

Wskaźniki do funkcji. */

#include<iostream.h>

int pierwsza(){

cout << "\nTo ja Twoja funkcja pierwsza \n" ;
return 5;

}

float druga(int a , double licz){

cout << "\nA to funkcja druga \nPrzesłałeś mi parametry -> "

<< a <<" oraz " << licz;

return 3.14;

}

void main(){

int (* wsk_fun) (); //wsk_fun jest wskaźnikiem na funkcję, z pustą

//listą argumentów, która zwraca wartość typu int.

float (* wsk_fun_float) (int, double); // wsk_fun jest wskaźnikiem na

//funkcję, która ma dwa argumenty odpowiednio typu
//int i double, oraz zwraca wartość typu float.

wsk_fun = pierwsza;

//Podstawiamy pod wskaźniki.

wsk_fun_float = druga;

cout << "\nPierwsze wywołanie\n";
wsk_fun();
wsk_fun_float(8, 4.12);

cout << "\n\n\nDrugie wywołanie\n";
(* wsk_fun)();
(* wsk_fun_float)(5, 23.42);

}

Notatki:

12.9

Tablice wskaźników do funkcji

Możemy oczywiście zdefiniować tablice wskaźników do funkcji. Potem te
funkcje wywoływać nie przez podanie nazwy funkcji, tylko podając indeks
w tablicy.

153

background image

int fun();

//To tylko deklaracja funkcji fun

int (* (tab_fun[10]) )(); //tab_fun to 10-cio elementowa tablica

//zawierająca wskaźniki do funkcji nie mających
//argumentów i zwracających wartość typu int

.
.
tab[4] = fun;

//Pod indeks 4 w tablicy wstawiamy adres funkcji fun

.
.
//Teraz wywołamy funkcję fun
(tab[4])();

Tablice ze wskaźnikami do funkcji możemy też inicjalizować inaczej, już przy
jej definicji.

int fun_1();

//To tylko deklaracja funkcji fun_1

int fun_2();

//Deklaracja funkcji fun_2

int (* (tab_fun[10]) )() = {fun_1, fun_2};
//odpowiednio na miejscu 0 i 1 w tablicy tab_fun są adresy
//funkcji fun_1 i fun_2

154

background image

/* Program pr12_8.cpp

Tablice funkcji. */

#include<iostream.h>

//Definiuję kilka funkcji.
void w_prawo(){

cout << "\nSkręcam w prawo --->>>\n" ;

}
void w_lewo(){

cout << "\n<<<--- Skręcam w lewo\n" ;

}
void gazu(){

cout << "\n!!!Wciskam do dechy!!!\n" ;

}
void stop(){

cout << "\n!!!Ostre hamowanie!!!\n"

<< "A nie mówiłem abyś zapiął pasy\n";

}

void main(){

//Zainicjalizowana tablica funkcji.
void (* tab[4] )() = {w_prawo, w_lewo, gazu, stop};
int wybor;

cout << "\nZapnij pasy - ruszamy\n przyciśnij klawisz gazu\n";
while(1){

cout << "\nTwój wybór\n"

<< "0 - skręć w prawo\n" << "1 - skręć w lewo\n"
<< "2 - więcej gazu\n" << "3 - S T O P\n"
<< "4 - już wysiadam\n";

cin >> wybor;
if( wybor == 4) break;
else (tab[wybor])(); //Wywołanie odpowiedniej funkcji.

}
cout << "No to cześć\n";

}

Notatki:

12.10

Wskaźnik do funkcji jako jeden z argumentów
innej funkcji

Jeśli chcemy, aby pewna funkcja wywoływała inną funkcję, i to taką, która
będzie znana dopiero w trakcie działania programu, możemy używać funk-
cji, której argumentem będzie wskaźnik do funkcji. Gdy podczas działania
programu dowiemy się, która funkcja ma być wykonana, wtedy jej adres po-
dajemy jako argument aktualny.

155

background image

int fun1();
int fun2();

void FUNKCJA( int (*wsk_fun)()){ //w tej funkcji wywołamy fun1 lub fun2

wsk_fun(); //wywołanie tej funkcji,

}

//której adres przyszedł jako parametr

void main(){

FUNKCJA(fun1);

//Można np. tak

FUNKCJA(fun2);

//albo tak

}

156

background image

/* Program pr12_9.cpp

Funkcja jako parametr innej funkcji. */

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

void bar(){

cout << "\nCo podać ?\n"

<< "1

- Piwo\n"

<< "2

- Mleko\n\n";

if( getch() == ’1’) cout << "Proszę piwo\n";
else cout << "Proszę mleko\n";

}
void szatnia(){

cout << "\nSłużę \n" << "1

- Oddanie ubrania\n"

<< "2

- Odebranie ubrania\n\n";

if( getch() == ’1’) cout << "Proszę oto bloczek\n";
else cout << "Proszę oto pańskie ubranie\n";

}

//Funkcja wybor ma jako parametr adres funkcji
void wybor(char *komentarz, void (*wsk_fun)()){

cout << komentarz << ’\n’;
wsk_fun();

//Tu wywołanie funkcji będącej

}

//argumentem funkcji wybor

void main(){

cout << "Twój wybór:\n" << "1 - idziesz do baru\n"

<< "2 - idziesz do szatni\n";

cout << "Za pierwszym razem\n";
if (getch() == ’1’) wybor("Wybrany został bar", bar);
else

wybor("Wybrana została szatnia", szatnia);

cout << "Twój wybór:\n"

<< "1 - idziesz do baru\n"

<< "2 - idziesz do szatni\n";

cout << "Za drugim

razem\n";

if (getch() == ’1’) wybor("Wybrany został bar", bar);
else

wybor("Wybrana została szatnia", szatnia);

}

Notatki:

157

background image

13

Wykład 13

Aby zapamiętywać dane nie tylko na czas działania programu, a też na na-
stępne sesje z programem, możemy dane zapisywać na dysku w postaci pli-
ków. Przez pliki można także rozumieć urządzenia, np. porty. Gdy dalej bę-
dziemy mówić o plikach i podawać ich nazwę, pamiętajmy o już przypisanych
nazwach do portów, odpowiednio:

lpt1, lpt2, com1, com2, prn

itp. Te pliki

są ze startem programu otwarte.

Aby obsługiwać pliki, użyjemy funkcji z

<stdio.h>

13.1

Otwieranie i zamykanie pliku

Aby móc pisać do pliku lub z niego czytać musimy ten plik otworzyć Otwie-
ranie pliku odbywa się przy pomocy funkcji

fopen

i ma postać:

wsk_plik = fopen("nazwa_pliku", "tryb");

Od tej chwili

wsk_plik

będzie nam reprezentować ten otwarty plik.

Parametr

"tryb"

to string, zawierający (w zależności od tego, jakiego pliku

potrzebujemy) jeden z następujących zestawów znaków:

r

- otwórz plik tylko do czytania.

w

- stwórz plik do pisania. Jeśli plik istnieje, będzie wpierw skasowany.

a

- otwarcie do dopisywania na końcu pliku. Jeśli plik nie istnieje, będzie

utworzony.

r+

- otwarcie istniejącego pliku do pisania i czytania.

w+

- tworzenie nowego pliku do czytania i pisania. Jeśli plik istnieje, będzie

wpierw skasowany.

a+

- otwarcie do dopisywania i czytania. Otwarcie (lub stworzenie, jeśli nie

istnieje) i ustawienie się na końcu pliku.

Dodatkowo do stringu trybu możemy dopisać:

t

- jeśli plik ma być otwarty w trybie tekstowym.

b

- tryb binarny.

Jeśli

ani

t

ani

b

nie zostało podane plik otwierany jest w zależności od wartości

zmiennej

_fmode

- default’owo ustawiona jest ona na

O_TEXT

, ale można ją

ustawić na

O_BINARY

.

158

background image

Funkcja

fopen

zwraca wskaźnik do predefiniowanej struktury

FILE

, opisującej

plik.

FILE *plik;

plik = fopen("moj.txt", "wb");

//lub tak

plik = fopen("../dane/twoj.txt", "a+t"); //tak gdy lubimy Unix
plik = fopen("..\\dane\\twoj.txt", "a+t"); //tak gdy lubimy Windows

Jeśli nie udało się otworzyć pliku,

fopen

zwraca

NULL

.

Zamykanie pliku dokonujemy za pomocą funkcji

fclose

, której parametrem

jest wartość zwrócona przez

fopen

. Uwaga, zamykanie pliku jest bardzo

ważne, gdyż często operacje na plikach dokonywane są przez bufor w pamięci.
Dopiero zamknięcie pliku faktycznie aktualizuje plik.

FILE *plik;

plik = fopen("moj.txt", "wb");

fclose(plik); /Plik zamknięty

/* Program pr13_1.cpp

Otwieranie i zamykanie pliku.

*/

#include<stdio.h>

void main()
{

FILE *plik;

plik = fopen("lolo.txt", "wt"); //Otwieram plik lolo.txt do pisania.

//Jeśli nie było takiego pliku,
//zostanie utworzony.

fclose(plik);

//Teraz zamykam plik.

//W aktualnej kartotece pojawił się pliku lolo.txt
}

Notatki:

159

background image

13.2

Podstawowe operacje na otwartych plikach

fgetc

- wczytanie kolejnego znaku z pliku

fputc

- zapisanie kolejnego znaku

na pliku

Zapiszmy coś do pliku:

FILE *plik;

plik = fopen("moj.txt", "wb");
fputc(’A’, plik);

//wpisanie do pliku moj.txt znaku A

fclose(plik);

Teraz nawet inny program może z tego czytać:

FILE *plik;
int c;

plik = fopen("moj.txt", "rb");
c = fgetc(plik); //Odczytuję znak z początku pliku
fclose(plik);

160

background image

/* Program pr13_2.cpp

Pliki - pisanie i czytanie. fputc(), fgets().

*/

#include<stdio.h>
#include<iostream.h>

void main()
{

FILE *plik;
char c;

plik = fopen("lolo.txt", "wt"); //Otwieram plik lolo.txt do pisania.

fputc(’A’ ,plik);
fputc(’L’ ,plik);
fputc(’A’ ,plik);
fclose(plik);

//Teraz zamykam plik.

cout << "Czytam z pliku 3 znaki\n";

plik = fopen("lolo.txt", "rt"); //Otwieram plik lolo.txt do czytania.

for(int i = 0; i < 3; i++)
{

c = (char)fgetc(plik);
cout << c;

}
fclose(plik);

//Teraz zamykam plik.

}

Notatki:

Jeśli w pliku chcemy zapisywać lub wczytywać z niego większe porcje niż 1
znak, możemy użyć odpowiednio funkcji

fwrite

i

fread

, których deklaracje

wyglądają następująco:

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);

size_t fread(void *ptr, size_t size, size_t n, FILE *stream);

Gdzie

size

i

n

są odpowiednio wielkością i ilością jednostek, które do pliku

chcemy zapisać, lub z pliku wczytać.

Pierwszym z argumentów jest adres miejsca, skąd (lub dokąd) chcemy zapisać
(odczytać) dane na (z) pliku.

161

background image

FILE *plik;

long tab[10];
.
.
plik = fopen("moj.txt", "wb");

fwrite((void *)tab, sizeof(long), 10, plik);

fclose(plik);

//Teraz wczytamy

plik = fopen("moj.txt", "rb");

fread((void *)tab, sizeof(long), 10, plik);

fclose(plik);

162

background image

/* Program pr13_3.cpp

Pliki - pisanie i czytanie większymi porcjami
fwrite() i fread(). Przykład 1.

*/

#include<stdio.h>

void main()
{

FILE *plik;

long tab[10] = {1,2,3,4,5,6,7,8,9,10};

plik = fopen("liczby.baz", "wb");

//Zapisuję całą tablicę na dysk.
fwrite((void *)tab, sizeof(long), 10, plik);
fclose(plik);

//Zeruję całą tablicę.
for(int i = 0; i < 10; i++) tab[i] = 0;

//Teraz wczytamy tablicę z pliku.

plik = fopen("liczby.baz", "rb");

fread((void *)tab, sizeof(long), 10, plik);
fclose(plik);

}

Notatki:

163

background image

/* Program pr13_4.cpp

Pliki - pisanie i czytanie większymi porcjami
fwrite() i fread(). Przykład 2.

*/

typedef struct{

char imie[30], nazwisko[30], data_urodz[11];
int zarobki;
char plec;
}opis_osoby; // Teraz tu definiujemy nazwę typu

#include<stdio.h>
#include<mem.h>

void main(){

FILE *plik;

opis_osoby osoba = {"Jan","Kowalski", "01-02-1987",342,’m’};
opis_osoby pusta = {""};

plik = fopen("osoby.baz", "wb");

//Zapisuję całą zmienną osoba na dysk.
fwrite((void *)&osoba, sizeof(opis_osoby), 1, plik);
fclose(plik);

//Zeruję zmienną osoba.
memcpy((void *)&osoba, (void *)&pusta, sizeof(opis_osoby));

//Teraz wczytamy do zmiennej osoba z pliku.

plik = fopen("osoby.baz", "rb");

fread((void *)&osoba, sizeof(opis_osoby), 1, plik);
fclose(plik);

}

Notatki:

13.3

Pisanie formatowane do pliku

int fprintf(FILE *plik, const char *format[, argument, ...]);

Powyższe wypisuje do pliku

plik

dane w formie podanej przez string

format

.

Do pliku piszemy dokładnie to, co zapisane w stringu

format

, po modyfikacji.

Do tego stringu, w miejsca zaczynajace się znakiem

%

, zostaną wstawione

odpowiednio zinterpretowane argumenty występujące po formacie. Po znaku

%

mogą występować następujące znaki z podaną interpretacją:

d

- zmienna int dziesiętnie

o

- jw., ósemkowo

164

background image

x

- jw., szesnastkowo

ld, lo, lx

- odpowiednio jak wyżej, tylko long

c

- znak

s

- string

e

- double w postaci z wykładnikiem

f

- double

n

- wskaźnik do int

p

- jw.

Przed każdym z tych znaków może wystąpić określenie pola, w którym wpi-
sujemy argument:

-

- wyrównaj do lewej

+

- wypisz ze znakiem

liczba

- szerokość pola

.liczba

- ilość miejsc po przecinku

FILE *plik;

long l = 100;
char c = ’C’;
float f = 1.2345554;

plik = fopen("moj.txt", "wb+");

fprintf(plik, "Liczba long %ld \nliczba float %5.3f

\nstring %s",

l, f, "ala ma kota");

fclose(plik);

Zasada

formatowania

jest

dokładnie

taka

sama,

jak

dla

funkcji

printf, scanf

, odnoszące się do wypisywania na ekran i czytania z klawia-

tury, które to urządzenia są specyficznymi plikami.

165

background image

/* Program pr13_5.cpp

Pliki - pisanie sformatowane.

*/

#include<stdio.h>

void main()
{

FILE *plik;

int l = -100;
float f = 1.2345554;

plik = fopen("bla.txt", "w+");

//Wypisuje w sposób sformatowany. Przydaje się to przy
//robieniu wydruków, czy też raportów.
fprintf(plik, "Liczba int %+20d \nliczba float %-+15.3f

\nstring %20s",

l, f, "ala ma kota\nbla");

fclose(plik);

}

Notatki:

Odpowiednikiem

fprintf

, gdy chodzi o wczytywanie sformatowanego tekstu,

jest funkcja

fscanf

.

int fscanf(FILE *plik, const char *format[, address, ...]);

Uwaga, inaczej niż w

fprintf

po stringu formatującym nie występują zmien-

ne, tylko ich adresy.

FILE *plik;

int l;
float f;

plik = fopen("moj.txt", "r");

fscanf(plik, " %d, %f", &l, &f); //wczytuję z pliku liczbę int i float,

//które są oddzielone przecinkiem

fclose(plik);

166

background image

/* Program pr13_6.cpp

Pliki - czytanie sformatowane.
Aby ten program działał potrzeba pliku moj.txt
napisanego w dowolny edytorze tekstowym o następującej
pierwszej linijce:
123,343.23,ala ma kota

*/

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

void main()
{

FILE *plik;

float f;
long l;
char tab[30];

plik = fopen("moj.txt", "rt");

fscanf(plik, "%ld,%f,%s", &l, &f, tab);
fclose(plik);

//Wypisywanie na ekran przy pomocy printf().
printf("Z pliku wczytałem:\nlong -%ld\nfloat - %5.2f\nstring - %s\n",

l, f, tab);

getch();

}

Notatki:

Formatowane zapisywanie do plików jest przydatne przy tworzeniu wydru-
ków, raportów i plików, które mają być czytane przez ludzi. Czytamy pliki
w sposób sformatowany zazwyczaj, jeśli zostały stworzone przez człowieka w
jakimś tekstowym edytorze. Proszę zauważyć, że liczby, które były zapisywa-
ne w sposób niesformatowany, są nieczytelne przy oglądaniu pliku w edytorze
tekstowym.

Uwaga, przy wczytywaniu stringów spacja jest traktowana jako jego koniec.
Jeśli chcemy wczytać z pliku string ze spacjami, zastosujemy funkcję

fgets

,

która wczytuje linię, albo zadaną ilość znaków. Gdy w linii jest mniej znaków
wczytuje całą linię.

char *fgets(char *s, int n, FILE *stream);

Linia z pliku wczytywana jest do (zazwyczaj tablicy)

s

. Jest wczytywane nie

więcej niż

n

znaków. Uwaga, wczytany string jest kończony znakiem

’\0’

.

Jeśli wczytaliśmy całą linię, to przedostatnim znakiem jest w trybie teksto-
wym,

’\n’

. W trybie binarnym jest to

’\n’

w Unixie i

"\r\n"

w Windows -

167

background image

tak tam jest reprezentowany koniec linii.

/* Program pr13_7.cpp

Pliki - wczytywanie linii.

*/

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

void main()
{

FILE *plik;

char tab[130];

plik = fopen("moj.txt", "r");

//Wczytuję linię z pliku lecz nie więcej niż 50 znaków.
fgets(tab, 50, plik);
fclose(plik);

cout << "Z pliku moj.txt wczytałem linię:\n" << tab;
getch();

}

Notatki:

13.4

Poruszanie się po pliku

Ustawienie wskaźnika na określonym miejscu pliku odbywa się przy pomocy
funkcji

fseek()

.

int fseek(FILE *plik, long offset, int skad);

gdzie

skad

może przybierać wartość:

SEEK_SET

0

początek pliku

SEEK_CUR

1

pozycja aktualna

SEEK_END

2

koniec pliku

Aby dowiedzieć się, gdzie jesteśmy zastosujmy funkcję

ftell()

.

long int ftell(FILE *plik);

Zwraca ona pozycję na pliku, licząc od początku.

Aby dowiedzieć się, czy nie jesteśmy poza końcem pliku, mamy funkcję

feof()

.

168

background image

int feof(FILE *plik);

Zwraca wartość różną od zera (prawdę), gdy próbowaliśmy czytać (UWA-
GA
, MUSIELIŚMY PRÓBOWAĆ CZYTAĆ) po końcu pliku.

/* Program pr13_8.cpp

Pliki - poruszanie się po pliku. */

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

void main(){

FILE *plik;
long miejsce;
char c;
char tab[130];

plik = fopen("moj.txt", "rb");
miejsce = ftell(plik);
cout << "\nJestem na miejscu " << miejsce;

c = fgetc(plik);
miejsce = ftell(plik);
cout << "\nA teraz jestem na miejscu " << miejsce;

cout << "\nSkaczę na koniec";
fseek(plik , 0, SEEK_END);
miejsce = ftell(plik);
cout << "\nA teraz jestem na miejscu " << miejsce;

fseek(plik , -6, SEEK_END);
miejsce = ftell(plik);
cout << "\nA teraz jestem na miejscu " << miejsce;

fseek(plik , 0, SEEK_SET);
cout << "\n\nWypiszę zawartość całego pliku:\n";
while(1){

c = fgetc(plik);
if( feof(plik) ) break;
cout << c;

}

fclose(plik);
cout << "\n\nNo to koniec - na dzisiaj :)\n";
getch();

}

Notatki:

169

background image

13.5

Kasowanie plików

Dowiedzieliśmy się jak tworzyć pliki i z nimi pracować. Czasem zachodzi
jednak potrzeba wykasowania pliku z dysku. Aby to zrobić, możemy użyć
funkcji:

int unlink(const char *nazwa_pliku);

Oczywiście, jeśli kasujemy plik, nie może być on w tym czasie otwarty.

13.6

Biblioteka io

W Turbo C++ istnieje jeszcze inna biblioteka obsługująca pliki. Jej plik
nagłówkowy to

io.h

. Istnieją tam prawie wszystkie funkcje odpowiadające

tym z biblioteki

stdio

. Obydwie biblioteki można znaleźć pod Unixem.

Biblioteka

io

nie występuje w standardzie ANSI C. Aby dowiedzieć się

więcej o tej bibliotece, proszę przejrzeć system pomocy.

170

background image

14

Wykład 14

14.1

Parametry funkcji main

Dotychczas funkcja

main()

była deklarowana jako

void main();

czyli jako funkcja bezargumentowa. Tak naprawdę jest to funkcja dwuargu-
mentowa. Argumenty te będziemy nazywać odpowiednio

argc

- jest on typu

int

, i

argv

typu

char *[]

. Parametr

argc

zawiera liczbę ciągów znakowych,

które zostały przekazane z linii komend do programu (jako pierwszy z ar-
gumentów uznajemy nazwę programu). Parametr

argv

to tablica złożona z

poszczególnych parametrów.

Dodatkowo, powinniśmy deklarować funkcję

main()

tak, by zwracała wartość

typu

int

. Przy poprawnym zakończeniu programu, powinna ona zwracać

wartość

0

. Wartość tę otrzymuje system i może stwierdzić jak zakończyło się

działanie programu.

/* Program pr14_1.cpp

Parametry funkcji main()

*/

#include <stdio.h>
#include <iostream.h>

int main(int argc, char *argv[])
{

cout << "Program został wywołany z następującymi parametrami:\n";

for (int i =0 ; i < argc; i++)
{

cout << "Argument " << i << " = " << (argv[i]) << "\n" ;

}
return 0;

}

Notatki:

14.2

Funkcje ze zmienną ilością argumentów

Do tej pory tworzyliśmy funkcje, które miały z góry zadaną liczbę argumen-
tów. Wyjątkiem były jedynie funkcje, które mogły mieć argumenty domyślne,
ale i wtedy znana była ich ilość. Można się zapytać, czy tak zawsze trzeba -

171

background image

odpowiedź brzmi nie. Możemy tworzyć funkcje, które przyjmują różne ilości
argumentów.

Czy takie funkcje będą użyteczne. Wyobraźmy sobie funkcję, która np. mnoży
swoje argumenty lub znajduje ich największy wspólny podzielnik - niewąt-
pliwie mogą się przydać. Robi się to w sposób następujący:

Funkcje tego typu charakteryzują się tym, że na miejscu ostatniego parame-
tru znajduje się symbol

...

(trzy kropki). Przed symbolem

...

musi znaj-

dować się przynajmniej jeden ”nazwany parametr”. Nie wolno wywoływać
tego typu funkcji z mniejszą liczbą argumentów niż liczba argumentów na-
zwanych. Aby ”dostać się” do argumentów oznaczonych

...

używamy trzech

makr:

void va_start(va_list ap, lastfix);
type va_arg(va_list ap, type);
void va_end(va_list ap);

których nagłówki znajdują się w

stdarg.h

.

Przypuśćmy, że mamy funkcję

void fun(int a, ...);

Aby z nich korzystać, potrzebna nam będzie zmienna typu

va_list

- repre-

zentująca listę argumentów, np.

va_list ap;

Teraz inicjalizujemy przeglądanie argumentów przez

va_start(ap, a);

Tutaj

a

jest nazwą ostatniego nazwanego parametru.

Następnie, wywołując kolejno makro

va_arg

, otrzymujemy kolejne, nienazwa-

ne argumenty. Drugim argumentem wywołania

va_arg

jest typ oczekiwanego

argumentu, np. jeśli kolejny argument jest typu

float

, wywołujemy:

float liczba;
liczba = va_arg( ap, float);

Po zakończeniu przeglądania listy argumentów powinniśmy wywołać:

va_end(ap);

Uwaga, niestety nie wszystko jest tak piękne. Wewnątrz funkcji

nie potrafimy dowiedzieć się, z iloma parametrami została wywołana funk-
cja, ani jaki był ich typ. Ta informacja powinna przyjść w jakiś sposób z
argumentami.

Teraz przykład funkcji dodająca pewną ilość liczb całkowitych.

172

background image

/* Program pr14_2.cpp

Funkcje ze zmienną ilością parametrów.
Przykład 1.

*/

#include <stdio.h>
#include <iostream.h>
#include <stdarg.h>

//Funkcja dodaje liczby aż nie napotka na argument 0.
//W ten sposób mówię, z iloma argumentami wywołano funkcję.
int dodaj(int a, ...){

int suma; //Zmienna do zapamiętywania sumy (częściowej).
int arg;

//Tu będę zapisywał poszczególne argumenty.

suma = a;

va_list ap;
va_start(ap, a);

//Wiem jakiego typu są argumenty, więc jako drugi
//parametr w va_arg podam int.
while ((arg = va_arg(ap, int)) != 0)

{
suma += arg;
}

return suma;

}

int main(){

int wynik;
wynik = dodaj(1,2,3,4,5,6,7,8,0);
cout << "\n Wynik pierwszego dodawania -> " << wynik;
return 0;

}

Notatki:

173

background image

/* Program pr14_3.cpp

Funkcje ze zmienną ilością parametrów.
Przykład 2.

*/

#include <stdio.h>
#include <iostream.h>
#include <stdarg.h>

//Funkcja dodaje tyle liczb, jaką wartość ma pierwszy parametr
int dodaj_inne(int a, ...){

int suma; //Zmienna przechowująca sumę częściową.
int arg;

//Tu będę zapisywał poszczególne argumenty.

suma = 0;

va_list ap;
va_start(ap, a);

//Wiem, że wszystkie argumenty aktualne mojej
//funkcjii są typu int.
while (a > 0 ){

arg = va_arg(ap,int);
suma += arg;
a--;

}
return suma;

}

int main(){

int wynik;

wynik = dodaj_inne(8,12,12,13,43,52,62,17,8);
cout << "\n Wynik drugiego dodawania -> " << wynik;
return 0;

}

Notatki:

14.3

Kilka standardowych funkcji o zmiennej ilości ar-
gumentów

W trakcie tego wykładu już kilkakrotnie spotkaliśmy się z funkcjami o zmien-
nej ilości argumentów. Prawie wszystkie funkcje działające na otwartych pli-
kach są takie. Popatrzmy np. na funkcję

printf

- też działającą na pliku,

dość specyficznym, czyli ekranie.

int printf(const char *format[, argument, ...]);

Najistotniejszy jest tu dla nas parametr

format

. Spróbujmy sobie wyobrazić,

jak wewnątrz może wyglądać ta funkcja. Pierwszy parametr wywołania tej

174

background image

funkcji to string. Aby dowiedzieć się ile jest pozostałych argumentów, wystar-
czy ten parametr przeczytać znak po znaku i policzyć ilość wystąpień znaku

%

. To, jakiego typu są poszczególne dodatkowe argumenty, również nie jest

trudne do sprawdzenia. Jeśli znaleźliśmy już znaczek

%

, to ostatni znak po

nim, a przed spacją mówi nam o typie - jeśli to jest np.

f

to jest to zmienna

typu

float

.

Innymi poznanymi funkcjami tego typu są np:

fprintf, scanf, fscanf

i

wiele innych.

Istotnym przykładem funkcji ze zmienną ilością parametrów z biblioteki

stdio

są funkcje:

int sprintf(char *bufor, const char *format [, argument, ...]);
int sscanf(const char *bufor, const char*format [, adres,...]);

Zasada ich działania jest analogiczna do funkcji

fprintf

i

fscanf

, z tą różnicą,

że piszemy/czytamy nie do/z pliku lecz do/z bufora w pamięci (zazwyczaj
tablicy znakowej).

175

background image

/* Program pr14_4.cpp

Formatowane pisanie/ czytanie z/do
bufora pamięci.

*/

#include <stdio.h>
#include <iostream.h>

int main()
{

char bufor[256];
int wiek, wzrost;
float nr_buta;

cout << "\nPodaj wiek(int), numer buta(float)"

<<" i wzrost(int) oddzielone spacjami:\n";

//Wczytam linię wejściową do bufora.
gets(bufor);

//Przy interpretacji spacje z bufora są ignorowane.
//Uwaga, stringi są interpretowane zachłannie
//- do wystąpienia dowolnego białego znaku.
sscanf(bufor, "%d%f%d", &wiek, &nr_buta, &wzrost);
cout << "\nMasz " << wiek << " lat"

<< "\nNosisz buty nr " << nr_buta
<< "\nTwój wzrost to " << wzrost << "\n\n";

//Teraz ze zmiennych utworzę napis w tablicy bufor.
sprintf(bufor, "Wiek %d, But %f, Wzrost %d\n",

wiek, nr_buta, wzrost);

cout << bufor;
return 0;

}

Notatki:

14.4

Operatory bitowe

Operatory bitowe, działają wprost na zapisie binarnym informacji. Operują
na argumentach typu całkowitego.

Przesunięcie w lewo:

zmienna << ilość_miejsc

Operacja ta powoduje przesunięcie w lewo zapisu binarnego zmiennej

zmienna

o

ilość_miejsc

miejsc.

int liczba, wynik;

liczba = 14402;
wynik = liczba << 3;

176

background image

Zapis w systemie binarnym wartości zmiennych

liczba

i

wynik

są następu-

jące:

liczba

0100 0000 0001 0010

wynik

0000 0000 1001 0000

Przesunięcie w prawo:

zmienna >> ilość_miejsc

Operacja ta powoduje przesunięcie w prawo zapisu binarnego zmiennej

zmienna

o

ilość_miejsc

miejsc.

Uwaga, jeśli zmienna jest typu unsigned lub ma wartość nieujemną, to bra-
kujące bity z lewej strony są uzupełniane zerami.

Uwaga, jeśli zmienna zawiera liczbę ujemną, to brakujące bity mogą być
uzupełniane zerami lub jedynkami. TO ZALEŻY OD TYPU KOMPUTERA.

int liczba, wynik;

liczba = 14402;
wynik = liczba >> 3;

Zapis w systemie binarnym wartości zmiennych

liczba

i

wynik

są następu-

jące:

liczba

0100 0000 0001 0010

wynik

0000 1000 0000 0010

Bitowe operatory :

|

- suma bitowa

&

- iloczyn bitowy

~

- negacja bitowa

^

- różnica symetryczna

int l, p, a, b, c, d;
l = 3855;
p = 4080;

a = l | p;
b = l & p;
c = ~l;
d = l ^ p;

177

background image

Poszczególne wartości wyglądają bitowo następująco:

l

0000 1111 0000 1111

p

0000 1111 1111 0000

a

0000 1111 1111 1111

b

0000 1111 0000 0000

c

1111 0000 1111 0000

d

0000 0000 1111 1111

Uwaga, zwróć uwagę na różnice pomiędzy operatorami:

&&

i

&

||

i

|

Mamy również zmodyfikowane operatory przypisania:

<<=

>>=

&=

|=

^=

~=

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

int main(){

unsigned int liczba, pom;

cout << "Podaj liczbę naturalna: ";
cin >> liczba;
cout << "\nPoszczególne bity w pamięci komputera to:\n";
for(int i = 0; i < 16; i++ )
{

cout << "Bit nr " << i << " jest równy ";

//Obliczę i-ty bit przez bitową koniunkcję z potęgą dwójki.
cout << ( ( (int)pow(2,i) & liczba) == 0 ? 0 : 1) << "\n";

}
cout << "Po ustawieniu 4 pierwszych bitów na 1 liczba ma wartość: ";

pom = liczba | 0xF000; //Bitowa koniunkcja z 1111000000000000.

//Przydał się zapis szesnastkowy.

cout << pom;
getch();

}

Notatki:

178

background image

15

Wykład 15

15.1

Typ enum

Czasami dane występujące w programie łatwiej wyrazić jest nie przy pomocy
liczb, a ich własnych nazw. Takim przykładem są np. dni tygodnia. Czasem
byłoby logiczniej napisać pętlę:

for(dzien = pon; dzien <= czw; dzien++) {...}

Okazuje się, że tak można. Trzeba tylko wpierw zdefiniować typ wyliczeniowy:

enum tydzien {niedz, pon, wt, sr, czw, pt, sob};

//Wlaściwie słowo tydzien nie jest tu istotne i można tak
enum {niedz, pon, wt, sr, czw, pt, sob};

Definicja ta tworzy stałe o nazwach w nawiasie, o wartościach kolejno

0,1,..6

. Możemy wymusić jednak inne wartości dla zmiennych, pisząc np.

enum tydzien {niedz, pon = 8, wt, sr, czw, pt = 24, sob};

Jakie wartości otrzymują stałe - widać. Te, które nie zostały wprost usta-
wione, otrzymują wartości o jeden większe od występujących bezpośrednio
przed nimi. W tej definicji

niedz

otrzymuje wartość

0

;

/* Program pr15_1.cpp

Typ enum.

*/

#include <iostream.h>

int main()
{

enum {niedz, pon, wt, sr, czw, pt, sob};
int dzien;

for(dzien = pon; dzien <= czw; dzien++)

cout << "\nDzien nr " << dzien;

return 0;

}

Notatki:

15.2

Unie

Unia to typ danych pomyślany po to, byśmy w jednym miejscu pamięci mogli

179

background image

zapisywać wartości rożnych typów. Kompilator sam zadba, by te wartości
tam się zmieściły. Tak naprawdę, unie to struktury, których wszystkie pola
są w tym samym miejscu pamięci.

union

int_lub_long {
int

i;

long

l;

} liczba;

//Dostęp do poszczególnych pól tak jak w strukturach
liczba.l = 213876;
liczba.i = 24

/* Program pr15_2.cpp

Unie.

*/

#include <iostream.h>

union

long_inny {
long

l;

//Następne pole to podzielenie pola l na 2 części.
//Najpierw bity młodsze bo tak w pamięci jest zapisywany typ long
struct{
int mlodszy, starszy;
}i2;

};

int main()
{

long_inny liczba;

liczba.l = 2138736;
cout << "liczba.l = 2138736;";
cout << "\nPole l = " << liczba.l

<< "\nPole i2.starszy = " << liczba.i2.starszy
<< "\nPole i2.mlodszy = " << liczba.i2.mlodszy;

cout << "\n\n\nliczba.i2.starszy = 24;";
liczba.i2.starszy = 24;
cout << "\nPole l = " << liczba.l

<< "\nPole i2.starszy = " << liczba.i2.starszy
<< "\nPole i2.mlodszy = " << liczba.i2.mlodszy;

return 0;

}

Notatki:

180

background image

15.3

Zmienne typu extern

Mówiliśmy już o zmiennych globalnych, które są widoczne w całym pliku
programu. Jeśli program składa się z kilku plików, a chcielibyśmy, by jakaś
zmienna globalna z jednego pliku była widoczna w innym, postępujemy na-
stępująco. W jednym z plików następuje definicja zmiennej. W plikach, w
których ta zmienna ma być widoczna, występuje jej deklaracja poprzedzona
słowem

extern

.

/*---------------------
Plik nr 1
*/
//Definicje zmiennych
int liczba = 1;
float tablica[20];

/*---------------------
Plik nr 2
*/
//Tu tylko deklaracje. Nie możemy nadawać wartości przy deklaracji
extern int liczba;

//Przy tablicach nie trzeba podawać rozmiaru
extern float tablica[];
/*---------------------*/

15.4

Operator przecinkowy

Zapewne nie dla wszystkich było jasne, co dzieje się w instrukcji for kiedy
inicjalizowaliśmy więcej niż jedną zmienną i poszczególne operacje oddzie-
laliśmy przecinkami. W języku C/C++ przecinek jest operatorem. Jeśli
dwa wyrażenia (zmienne) połączymy przecinkiem to wynik takiej operacji
ma wartość i typ prawego wyrażenia (zmiennej). Poszczególne wyrażenia są
obliczane od lewej do prawej. Operator ten jest lewostronnie łączny.

//Po podstawieniu
b = 12;
a = 2 * b, b - 4;
//a ma wartość 8

181

background image

15.5

Priorytety operatorów

Jeśli w wyrażeniu znajduje się więcej niż jeden operatorów i brak jest nawia-
sów, mówiących o kolejności działań, wyrażenie to oblicza się przy zastoso-
waniu poniższych priorytetów działań. Operatory znajdujące się wyżej mają
większy priorytet. Jeśli operatory mają ten sam priorytet, stosujemy podaną
łączność. Jako że trudno jest zapamiętać te priorytety, poleca się używać od-
powiedniej ilości nawiasów, nawet nadmiarowo, aby uzyskać lepszą czytelność
zapisu.

Operator

Łączność

() [] -> .

lewostronna

! ~ + - ++ -- & * sizeof new

delete

prawostronna

* / %

lewostronna

+ -

lewostronna

<< >>

lewostronna

< <= > >=

lewostronna

== !=

lewostronna

&

lewostronna

^

lewostronna

|

lewostronna

&&

lewostronna

||

lewostronna

?:

prawostronna

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

prawostronna

,

lewostronna

15.6

Preprocesor

Preprocesor przegląda i przetwarza plik programu przed przekazaniem go w
ręce kompilatora. Rozkazy dla preprocesora wydajemy za pomocą dyrektyw.
Można je rozpoznać po tym, że pierwszym znakiem (nie licząc spacji i tabu-
latora) w linii zawierającej dyrektywę jest znak

#

. Znamy już dobrze jedną z

takich dyrektyw:

15.6.1

#include

#include "nazwa_pliku"

182

background image

czy też ”prawie” równoważnie

#include<nazwa_pliku>

Oznacza ona, że chcemy, aby w miejscu jej wystąpienia został wstawiony plik
o nazwie

nazwa_pliku

.

15.6.2

#define

#define

nazwa

ciąg_znaków_który_nazwa_będzie_oznaczać

Oznacza to, że w miejscu, gdzie wystąpi w pliku

nazwa

, ma ona być zamie-

niona na

ciąg_znaków_który_nazwa_będzie_oznaczać

. Proszę pamiętać, że

dzieje się to jeszcze przed przekazaniem pliku kompilatorowi.

//Przy takim zapisie
#define rozmiar 8

int tablica[rozmiar];

//linia 1

------------------------------------------
//Do kompilatora zostanie przekazane

int tablica[8];

Preprocesor nie przejmuje się składnią C on po prostu zamienia ciągi znaków
”jak leci”.

Uwaga, jedynym miejscem, gdzie

nazwa

nie zostanie zastąpiona przez

ciąg_znaków_który_nazwa_będzie_oznaczać

, są wnętrza stringów.

Można

też tak:

#define wypisz cout << "bala bla bala"
.
.
wypisz ;

//linia 2

------------------------------------------
//linia 2 wyglądać będzie po obróbce przez preprocesor następująco:

cout << "bala bla bala";

//linia 2

#undef nazwa

Odwołuje definicję nazwy

nazwa

uczynioną przez

#define

.

183

background image

/* Program pr15_3.cpp

Preprocesor - #define.

*/

#include <iostream.h>

#define ROZMIAR 8

#define WYPISZ cout << "\n\nbala bla bala"

int main()
{

int tablica[ROZMIAR];

//UWAGA, ROZMIAR to nie zmienna, dla
//kompilatora to dokładnie 8

cout << "ROZMIAR tablicy tablica wynosi " << ROZMIAR;
//Tylko drugie wystąpienie ROZMIAR zostanie zastąpione
//przez 8. Pierwsze nie, bo jest wewnątrz stringu.

WYPISZ; //To nie wywołanie funkcji. W to miejsce wstawione

//zostanie dokładnie to, co po #define WYPISZ.

return 0;

};

Notatki:

15.6.3

Makrodefinicje

Makrodefinicje to coś na wzór funkcji tworzonych przy pomocy preprocesora.

//Obliczanie kwadratu liczby
#define KWADRAT(a)

( (a) * (a) )

//To jedna z bardziej znanych makrodefinicji
//Obliczanie maksimum
#define MAX(a,b)

(

( (a) >= (b) ) ? (a) : (b) )

Uwaga, w pierwszym wyrazie po

#define

nie może być żadnych białych

znaków!!!

Jeśli teraz np. preprocesor napotka na linie

b = KWADRAT(3.89 +5);
c = MAX(v+5, u);

Zamieni je na

b = ( (3.89 + 5) * (3.89 + 5) );
c = (

( (v+5) >= (u) ) ? (v+5) : (u) );

184

background image

Teraz widać, po co było nam potrzebne tak wiele nawiasów - jako parametry
mogą występować wyrażenia.

Uwaga, stosowanie takich definicji może prowadzić do błędów, np. popatrz,
co się stanie przy wywołaniu:

b = KWADRAT(a++);

Stosowanie makrodefinicji jest czasem przydatne - szczególnie tam, gdzie
chcielibyśmy otrzymać ”funkcje” działające na różnych typach danych. Przy-
kładem jest

MAX

, która dział dobrze dla wszystkich argumentów liczbowych.

Problem w stosowaniu makrodefinicji polega na tym, że przy wystąpieniu
błędów trudno jest je zlokalizować i usunąć.

/* Program pr15_4.cpp

Preprocesor - makrodefinicje.

*/

#include <iostream.h>

#define KWADRAT(a)

( (a) * (a) )

#define MAX(a,b)

(

( (a) >= (b) ) ? (a) : (b) )

int main()
{

cout << "\n5 do kwadratu = " << KWADRAT ( 5 );
cout << "\n\n5.34 do kwadratu = " << KWADRAT ( 5.34 );

//Nie musimy pisać dwu funkcji dla różnych typów.

cout << "\n\nWiększą z liczb 3 i 5.34 jest " << MAX(3, 5.34);

int a = 5;
//NIEBEZPIECZENSTWO !!!
cout << "\n\n6 do kwadratu = " << KWADRAT ( ++a );
cout << "\n\nTeraz pod zmienna a mamy

" << a;

//Otrzymane wyniki są inne niż te, których mogliśmy oczekiwać.

return 0;

}

Notatki:

15.6.4

Kompilacja warunkowa

Jeśli chcemy, by w pewnych warunkach kompilator kompilował część pliku,
możemy zastosować kompilację warunkową.

185

background image

#if wyrażenie

//Instrukcje kompilowane, gdy wyrażenie jest prawdziwe

#endif

lub tak

#if wyrażenie

//Instrukcje kompilowane, gdy wyrażenie jest prawdziwe

#else

//Instrukcje kompilowane, gdy wyrażenie jest fałszywe

#endif

lub tak

#if wyrażenie1

//Instrukcje kompilowane, gdy wyrażenie jest prawdziwe

#elif wyrażenie2

//Instrukcje kompilowane, gdy wyrażenie1 jest fałszywe
//a wyrażęnie2 prawdziwe

#elif wyrażenie3
.
.
#else
.
.
#endif

Uwaga,

wyrażenie

,

wyrażenie1

,

wyrażenie2

muszą być wielkościami stałymi,

których wartość znana jest przed kompilacją.

186

background image

#define WERSJA 1

#if (WERSJA == 1)

#include "stary.h"
rodzaj = 5;

#else

#include "nowy.h"
rodzaj = 9;

#endif

W warunku można też używać operatora

defined(nazwa)

, który sprawdza,

czy dana

nazwa

została już zdefiniowana.

#define WERSJA 1

#if (defined(WERSJA))

#include "stary.h"
rodzaj = 5;

#else

#include "nowy.h"
rodzaj = 9;

#endif

Uwaga, linia:

#if (defined(WERSJA))

jest równoważna linii

#ifdef WERSJA

Linia:

#if (!defined(WERSJA))

jest równoważna linii

#ifndef WERSJA

187

background image

/* Program pr15_5.cpp

Preprocesor - kompilacja warunkowa. */

#include <iostream.h>
#define WERSJA 1

#if (WERSJA == 1)

void fun(int a){

cout << "\nZwiększam parametr o 2 ----- a = " << (a +=2) ;

}

#else

void fun(int a){

cout << "\nZwiększam parametr o 30 ----- a = " << (a +=30) ;

}

#endif

int main(){

#ifdef WERSJA

#if WERSJA == 1

cout << "Wersja 1 programu";

#else

cout << "Wersja 2 programu";

#endif

#else

cout << "BŁĄD KRYTYCZNY - NIE ZDEFINIOWANO WERSJI";

#endif

#ifdef WERSJA

fun(5);

#endif

return 0;

}

Notatki:

Po wstępnym przetworzeniu poprzedniego programu do kompilatora zostanie
przekazany tekst:

188

background image

/* Program pr15_5_1.cpp

Preprocesor - kompilacja warunkowa.
To co zrobił preprocesor z pr15_5.cpp */

//Za poniższą linijkę zostanie wstawiony plik - brak tu
//jednak miejsca, by go wstawić.
#include <iostream.h>

void fun(int a){

cout << "\nZwiększam parametr o 2 ----- a = " << (a +=2) ;

}

int main(){

cout << "Wersja 1 programu";
fun(5);
return 0;

}

Notatki:

15.6.5

Predefiniowane nazwy

Preprocesor dostarcza nam też pewnych predefiniowanych nazw, które mo-
żemy używać w programach. Są to:

__LINE__

- numer linii

__NAME__

- nazwa kompilowanego pliku

__DATE__

- data kompilacji

__TIME__

- Czas w momencie kompilacji

/* Program pr15_6.cpp

Preprocesor - predefiniowane nazwy.

*/

#include<iostream.h>

int main()
{

cout << "\nWłaśnie skompilowałem program " << __FILE__;

cout << "\n\nTa instrukcja znajduje się w linii " << __LINE__;

cout << "\n\nKompilacja zaczęła się dnia " << __DATE__

<< " o godzinie " << __TIME__ ;

return 0;

}

Notatki:

189


Document Outline


Wyszukiwarka

Podobne podstrony:
Projektowanie oprogramowania Wstep do programowania i techniki komputerowej
2011-2012 wstęp do P program, wstęp do psychologii k
zarz procesami planowanie, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
PHP Praktyczne wprowadzenie R 4 Wstęp do programowania Proste skrypty PHP
klas sys komp, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Wstęp do programu z poprawką, bierzmowanie
entropia kodowanie, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
e Wstep do programowania DS
All, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
def informatyka, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
pradygmaty prog, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Wilkosz, Wstęp do programowania, kolokwia KD1-09 10l
Wilkosz, Wstęp do programowania, kolokwia K2-08 09l
srod programowania translatory, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
jezyk bnf ebnf, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Wilkosz, Wstęp do programowania, kolokwia K2-10 11l
System operacyjny, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI

więcej podobnych podstron