je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr...

246
2. DEKLARACJE I WYRAŻENIA Elementy programu W programowania są wymagane dwie rzeczy: dane (zmienne) oraz instrukcje (kod źródłowy lub funkcje). Zmienne są podstawowymi elementami tworzącymi program. Instrukcje natomiast informują komputer, co powinien zrobić ze zmiennymi. Zanim można zastosować zmienne, trzeba je zadeklarować, a następnie poinformować język C, jakich ich typ ma zastosować. Po zadeklarowaniu zmiennych można zacząć ich używać. W języku C podstawową strukturą jest funkcja. Kilka funkcji połączonych ze sobą tworzy program. Podstawowa struktura programu Podstawowymi elementami programu są: deklaracje danych, funkcje oraz komentarze.Spójrzmy jak po połączeniu mogą one utworzyć prosty program napisany w języku C. Podstawowa struktura programu zawierającego jedną funkcję ma postać: /****************************************** * ...Komentarze nagłówka... * *****************************************/ ...Deklaracje danych... int main () { ...Instrvkcje programu… return (0); }

Upload: hangoc

Post on 30-Apr-2018

217 views

Category:

Documents


4 download

TRANSCRIPT

Page 1: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

2. DEKLARACJE I WYRAŻENIAElementy programu

W programowania są wymagane dwie rzeczy: dane (zmienne) oraz instrukcje (kod źródłowy lub funkcje). Zmienne są podstawowymi elementami tworzącymi program. Instrukcje natomiast informują komputer, co powinien zrobić ze zmiennymi.Zanim można zastosować zmienne, trzeba je zadeklarować, a następnie poinformować język C, jakich ich typ ma zastosować.Po zadeklarowaniu zmiennych można zacząć ich używać. W języku C podstawową strukturą jest funkcja. Kilka funkcji połączonych ze sobą tworzy program.

Podstawowa struktura programuPodstawowymi elementami programu są: deklaracje danych, funkcje oraz komentarze.Spójrzmy jak po połączeniu mogą one utworzyć prosty program napisany w języku C.

Podstawowa struktura programu zawierającego jedną funkcję ma postać:

/****************************************** * ...Komentarze nagłówka... * *****************************************/ ...Deklaracje danych... int main () { ...Instrvkcje programu… return (0); }

Komentarze nagłówka zawierają informacje na temat programu, natomiast deklaracje danych opisują dane, które będą wykorzystywane przez program.Jedyną zastosowaną tutaj funkcją jest main. Nazwa main ma specjalne znaczenie, ponieważ jest pierwszą wywoływaną funkcją. Inne funkcje są wywoływane bezpośrednio lub pośrednio z tej funkcji. Funkcja main rozpoczyna się:

int main () { a kończy:

return (0); }

Page 2: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wiersz return (0); jest stosowany w celu poinformowania systemu operacyjnego, że program zakończył poprawnie swoje działanie z wartością Status=0. Wartość różna od zera oznacza błąd. Im wyższa zwrócona wartość, tym błąd jest poważniejszy. Zwykle status o wartości 1 jest stosowany w przypadku większośc prostych błędów, takich jak brakujący plik lub błąd składni polecenia.

Przyjrzyjmy się teraz programowi Hello

/******************************************** * hello – program wyswietlajacy komunikat * * ”Witaj swiecie” * ********************************************/ #include <stdio.h> int main () { /* powiedz ”Witaj swiecie” */ printf(”Witaj swiecie\n”); return (0); }

Na początku tego programu znajduje się okno komentarzy zawarte między symbolami “/*” i “*/”. Za tym oknem znajduje się poniższy wiersz:

#include <studio.h>

Powyższa instrukcja informuje język C, że zostanie zastosowany standardowy pakiet operacji wejścia-wyjścia. Instrukcja ta jest deklaracją danych1. Główny program zawiera instrukcję:

printf("Witaj swiecie\n");

Powyższy wiersz jest instrukcją nakazującą językowi C wyświetlenie na ekranie komunikatu "Witaj swiecie". Język C w celu ustawienia końca instrukcji stosuje znak średnika w prawie ten sam sposób, w jaki jest stosowana kropka na końcu zdania. Pojedyncza instrukcja może mieć długość kilku wierszy. W podobny sposób jak ma to miejsce w przypadku kilku instrukcji języka C znajdujących się w jednym wierszu jest możliwe umieszczenie kilku zdań w tym samym wierszu. Niezależnie od tego 1 Z technicznego punktu widzenia, instrukcja nakazuje pobranie z dołączonego pliku zestawu deklaracji danych.

2

Page 3: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

program jest bardziej czytelny, jeśli każda instrukcja jest umieszczona w oddzielnym wierszu.Standardowa funkcja printf jest stosowana do wyświetlania komunikatu. Program zawarty w bibliotece jest procedurą lub funkcją języka C, która została napisana i umieszczona w bibliotece lub zbiorze przydatnych funkcji. Programy te wykonują takie operacje jak sortowanie, operacje wejścia-wyjścia, funkcje matematyczne oraz operacje na plikach.Aby uzyskać pełną listę funkcji zawartych w bibliotekach, należy zajrzeć do dokumentacji języka C.Program Hello jest jednym z najprostszych programów napisanych w języku C. Nie zawiera żadnych obliczeń i wyświetla tylko na ekranie pojedynczy komunikat. Po zrozumieniu działania tego programu możesz być pewien, że wykonałeś kilka poprawnych operacji.

Wyrażenia prosteAby zdefiniować operację arytmetycznę, są w tym celu stosowane wyrażenia. Pięć podstawowych operatorów stosowanych w języku C zostało zawarte w tabeli 2.1.

Tabela 2.1. OperatoryOperator Funkcja

* Mnożenie/ Dzielenie+ Dodawanie- Odejmowanie% Moduł (zwraca resztę z dzielenia)

Mnożenie (*), dzielenie (/) i moduł mają pierwszeństwo przed dodawaniem (+) i odejmowaniem (-). W celu zmiany kolejności wykonywania operacji należy zastosować nawiasy okrągłe. W takim razie operacja:

(1+2)*4

daje w wyniku 12, natomiast: 1+2*4 równa się 9.

Wydruk 2.1 wylicza wartość wyrażenia (1+2)*4.

Wydruk 2.1.

int main() { (1+2)*4; return (0); }

3

Page 4: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Oprócz obliczania wartości koncowej nic innego nie jest wykonywane. Program ten wygeneruje ostrzeżenie typu "null effect" (bez efektu), oznaczające że jest on poprawny, ale zawiera bezużyteczną instrukcję.Wniosek z tego taki, że należy zapisywać wyniki obliczeń.

Zmienne i ich zakresJęzyk C umożliwia zapamiętywanie wartości pod postacią zmiennych. Każda zmienna jest identyfikowana przez nazwę.Ponadto, każda zmienna posiada swój typ. Typ zmiennej informuje język, jaka zmienna zostanie użyta oraz jakie liczby (rzeczywiste, całkowite) może przechowywać. Nazwy zmiennych zaczynają się literą lub znakiem podkreślenia poprzedzone przez dowolną literę, cyfrę lub znak podkreślenia. Jednakże, aby uniknąć pomyłek, należy używać różnych nazw zmiennych i nie opierać się na wielkości liter.Nic nie przeszkodzi nam w utworzeniu nowej zmiennej o nazwie rozpoczynającej się znakiem podkreślenia, ale takie nazwy są zwykle zarezerwowane dla zastosowań do celów wewnętrznych i systemowych.Większość programistów języka C stosuje zmienne o nazwach zawierajęcych tylko małe litery. Niektóre nazwy, takie jak int, while, for i float, mają dla języka C specjalne znaczenie i są określane terminem słów zastrzeżonych (ang. reserved words). Z tego też powodu nie mogą być używane jako nazwy zmiennych.Poniżej zawarto przykład kilku nazw zmiennych:

average /* srednia wszystkich ocen */pi /* liczba pi z szescioma miejscami po przecinku */numer_of_students /* liczba studentow na zajeciach */

Poniżej zawarto niepoprawne nazwy zmiennych:3rd_entry /* rozpoczyna się cyfrą */all$done /* zawiera znak "$" */the end /* zawiera spacje */int /* słowo zastrzeżone */

Należy unikać podobnych nazw zmiennych. Na przykład poniżej zawarto niepoprawny dobór nazw:

total /*calkowita liczba pozycji w aktualnym wpisie*/totals /* liczba wszystkich wpisow */

O wiele lepszym zestawem nazw będzie:

entry_total /*calkowita liczba pozycji w aktualnym wpisie*/all_total /* liczba wszystkich wpisow */

4

Page 5: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Deklaracja zmiennychW języku C przed zastosowaniem zmiennej należy dokonać jej deklaracji.Deklaracja zmiennej spełnia trzy funkcje:1. Określa nazwę zmiennej.2. Określa typ zmiennej (całkowity, rzeczywisty, znakowy).3. Zawiera opis zmiennej. Deklaracja zmiennej answer może mieć postać:

int answer; /* wynik wyrazenia */

Słowo kluczowe int informuje język C, że zmienna ta jest typu całkowitego. (Typ całkowity zostanie omówiony poniżej). Zmienna ma nazwę answer. Znak średnika oznacza koniec instrukcji, natomiast komentarz służy do opisu znaczenia zmiennej. (Wymaganie, aby każda deklaracja zmiennej w języku C posiadała komentarz, jest jedną z zasad stylu programowania.).Ogólna postać deklaracji zmiennej zosta1a zawarta poniżej:

typ nazwa; /* komentarz */

gdzie typ jest jednym z typów zmiennych języka C (int, float), natomiast nazwa określa dowolną poprawną nazwę zmiennej. Powyższa deklaracja określa, co oznacza zmienna oraz jaką pełni rolę. Deklaracje zmiennych są zawarte powyżej sekcji main () programu.

Typ całkowityJednym z typów zmiennych jest typ całkowity. Liczby całkowite nie posiadają części ułamkowej. Liczby takie jak 1, 87 oraz -222 są liczbami całcowitymi. Liczba 8,3 nie jest liczbą całkowitą, ponieważ posiada cęść dziesiętną. Ogólna postać deklaracji typu całkowitego została zawarta poniżej:

int nazwa; /* komentarz */

Kalkulator z wyświetlaczem 8-cyfrowym może wykonywać tylko obliczenia na liczbach w przedziale od -99 999 999 do 99 999 999. Jeś1i spróbujesz dodać 1 do 99 999 999, wystąpi błąd przepełnienia. Komputery posiadają podobne ograniczenia. Ograniczenie liczb całkowitych jest uzależnione od implementacji, co oznacza, że jest inne na różnych komputerach. Kalkulatory stosują liczby dziesiętne (0 - 9), natomiast komputery liczby dwójkowe (0 - 1) zwane bitami. Osiem bitów tworzy bajt. Liczba bitów używana do przechowywania liczby całkowitej jest inna na różnych komputerach. W celu wyświetlenia liczby są zamieniane z systemu dwójkowego na dziesiętny.

5

Page 6: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Na większości komputerów pracujących pod systemem UNIX liczby całkowite mają długość 32 bitów (4 bajty), co odpowiada zakresowi od 2 147 483 647 (2 do potęgi 31-1) do -2 147 483 648. Na komputerach osobistych większość komputerów stosuje tylko długość 16 bitów (2 bajty), tak więc odpowiada to zakresowi od 32 767 (2 do potęgi 15 - 1) do -32 768. Są to powszechnie stosowane rozmiary. Standardowy plik naglówkowy limits.h definiuje stałe określające różne wartości graniczne. Standard języka C nie podaje precyzyjnych długości liczb. Programy wykorzystujące typ całkowity o określonym rozmiarze (na przykład 32 bity) często się zawieszają po uruchomieniu na różnych komputerach.

Instrukcja przypisaniaZmiennej jest przypisywana wartość poprzez zastosowanie instrukcji przypisania. Na przykład instrukcja:

answer = (1 + 2) * 4;

jest przypisaniem. Zmiennej answer po lewej stronie znaku równości (=) została przypisana wartość wynikowa wyrażenia (1+2)*4 znajdującego się po prawej stronie. Znak średnika oznacza koniec instrukcji.Instrukcje przypisania są stosowane do przypisywania zmiennej wartości.Ogólna postać instrukcji przypisania została zawarta ponizej:

zmienna = wyrazenie;

Znak równości (=) jest stosowany w instrukcji przypisania. Powyższa instrukcja oznacza dosłownie tyle: oblicz wyrażenie, a następnie przypisz jego wartość wynikową zmiennej.W wydruku 2.2 zmienna term zostanie zastosowana do przechowania liczby całkowitej wykorzystanej w dwóch dalszych wyrazeniach.

Wydruk 2.2.

[Plik: term/term.c] int term; /* zmienna term zastosowana w dwoch wyrazeniach */ int term_2; /* iloczyn dwoch zmiennych term */ int term_3; /* iloczyn trzech zmiennych term */ int main() { term=3*5; term_2=2*term; term_3=3*term; return (0); }

6

Page 7: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Z powyższym programem jest związany pewien problem. Jak możemy stwierdzić, czy on działa? W tym celu należy wyświetlić wyniki obliczeń.

Funkcja printfFunkcja printf służy do wyświetlania wyników. Jeśli zastosujemy instrukcję:

printf ("Dwa razy %d wynosi %d\n", term, 2*term);

program wyświetli komunikat:

Dwa razy 15 wynosi 30

Symbol specjalny %d jest określany terminem wartości konwersji typu całkowitego. W momencie, gdy funkcja printf napotka symbol %d, wyświetla wartość wyrażenia następnego na liście znajdującej się za łańcuchem komunikatu. Jest to określane terminem listy pararnetrów.Ogołna postać funkcji printf została zawarta poniżej:

printf (komunikat, wyrażenie-1, wyrażenie-2, ...);

gdzie komunikat określa komunikat, który zostanie wyświetlony. Wszystko, z wyjątkiem konwersji %d, co jest zawarte wewnątrz tego parametru, zostanie dokładnie wyświetlone. Wartość wyrażenia wyrażenie-1 jest wyświetlana w miejscu pierwszego symbolu %d, natomiast wartość wyrażenia-2 jest wyświetlana w miejscu drugiego symbolu %d, i tak dalej.Rysunek 2.1 przedstawia elementy funkcji printf, które połączone razem generują ostateczny wynik.

Komunikat ″Dwa razy %d wynosi %d\n" informuje funkcję printf, aby wyświetlła słowa Dwa razy oddzielone spacją, wartość pierwszego wyrażenia, a następnie za kolejną spacją słowo wynosi, ponownie spację i wreszcie wartość drugiego wyrażenia oraz znak końca wiersza (określony symbolem \n).Wydruk 2.3 zawiera program, który wylicza wartość zmiennej term, a następnie wyświetla ją za pośrednictwem dwóch funkcji printf.

7

int term = 15; ("Dwa razy %d wynosi %d \n", term, 2*term);printf

Sekcja form atowania Sekcja wyrazenia

Rys. 2.1

Page 8: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 2.3.

#include <stdio.h> int term; /* zmienna zastosowana w dwoch wyrazeniach */ int main() { term=3*5; printf("Dwa razy %d wynosi %d\n", term, 2*term)' printf("Trzy razy %d wynosi %d\n", term, 3*term); return (0); }

Liczba konwersji %d występująca w komunikacie powinna być zgodna z liczbą wyrażeń w funkcji printf. Język C nie sprawdza tego warunku. Jeśli została zdefiniowana zbyt duża liczba wyrażeń, nadmiarowe wyrażenia zostaną zignorowane. Jeśli natomiast jest zbyt mało wyrażeń, język C wygeneruje dla brakujących wyrażeń przypadkowe liczby.

Typ zmiennoprzecinkowyZe względu na sposób wewnętrznego przechowywania, liczby rzeczywiste znane są również pod pojęciem liczb zmiennoprzecinkowych. Liczby 5.5, 8.3 oraz -12.6 są takimi liczbami. Język C stosuje znak kropki dziesiętnej w celu odróżnienia liczb całkowitych i zmiennoprzecinkowych. Tak więc 5.0 jest liczbą zmiennoprzecinkową, natomiast 5 liczbą całkowitą. Liczby zmiennoprzecinkowe muszą zawierać znak kropki dziesiętnej. Do takich liczb można zaliczyć 3.14159, 0.5, 1.0 oraz 8.88.Pomimo że jest możliwe opuszczenie cyfr znajdujących się przed kropką dziesiętną i wpisanie zamiast 0.5 liczby .5, to jednak dodatkowe zero poprawia czytelność w przypadku stosowania liczb zmiennoprzecinkowych. Podobna zasada dotyczy zapisania liczby 12. zamiast 12.0. Zero w notacji zmiennoprzecinkowej należy zapisać jako 0.0.Dodatkowo liczba może zawierać wartość wykładniczą w postaci:

e ± exp

Na przykład liczba 1.2e34 jest skróconym zapisem liczby 1.2×10 do potęgi 34.Poniżej pokazano postać deklaracji zmiennej typu zmiennoprzecinkowego:

float zmienna; /* komentarz */

Dla przypomnienia, liczby zmiennoprzecinkowe, które mogą być stosowane przez komputer posiadają ograniczony przedział. Limit ten zmienia się w szerokim zakresie na różnych komputerach. W przypadku stosowania funkcji printf do wyświetlania liczb zmiennoprzecinkowych jest używana

8

Page 9: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

konwersja %f. Aby wyświetlić wyrażenie 1.0/3.0, należy zastosować poniższe wyrażenie:

printf("Wynikiem operacji jest %f\n", 1.0/3.0);

Porómnanie dzielenia liczb zmiennoprzecinkowychi dzielenia liczb całkowitych

Operator dzielenia ma specjalne znaczenie. Istnieje duża różnica pomiędzy dzieleniem liczb całkowitych a dzieleniem liczb zmiennoprzecinkowych. W przypadku dzielenia liczb całkowitych wynik operacji jest obcinany (część ułamkowa jest odrzucana). Tak więc wynikiem dzielenia 19 / 10 jest 1.Jeśli dzielna lub dzielnik jest liczbą zmiennoprzecinkową, jest wykonywana operacja dzielenia liczb zmiennoprzecinkowych. Tak więc wynikiem dzielenia 19.0/10.0 jest 1.9. (Operacje dzielenia 19 / 10.0 oraz 19.0 / 10 również zaliczają się do dzielenia zmiennoprzecinkowego, jednak ze względu na czytelność zapis 19.0/10.0 jest bardziej zalecany). W tabeli 2.2 zawarto kilka przykładów wyrażeń.

Tabela 2.2. Przykłady wyrażeń

Wyrażenie Wynik Typ wyniku1+21.0 + 2.019/1019.0/10.0

33.011.9

Liczba całkowitaLiczba zmiennoprzecinkowa Liczba całkowita Liczba zmiennoprzecinkowa

Język C pozwala na przypisanie wyrażenia typu całkowitego zmiennej typu zmiennoprzecinkowego. Dokonuje on automatycznej konwersji z typu całkowitego na zmiennoprzecinkowy. Podobna konwersja ma miejsce w przypadku, gdy liczba zmiennoprzecinkowa zostanie przypisana zmiennej typu całkowitego. Na przykład:

int integer; /* zmienna calkowita */ float floating; /* zmienna zmiennoprzecinkowa */ int main () { floating=1.0/2.0; /* przypisanie zmiennej floating wartosci 0.5* / integer=1/3; /* przypisanie zmiennej integer wartosci 0 */ floating=(1/2)+(1/2) /*przypisanie zmiennej floating wartosci 0.0*/ floating=3.0/2.0; /*przypisanie zmiennej floating wartosci 1.5*/ integer=floating; /* przypisanie zmiennej integer wartosci 1 */ return (0); }

9

Page 10: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Należy zauważyć, że wyrażenie 1/2 jest typu całkowitego, przez co jego wynikiem jest liczba całkowita 0 uzyskana po wykonaniu dzielenia liczb całkowitych.

Typ znakowyTyp znakowy char określa pojedyncze znaki. Deklaracja zmiennej znakowej ma postać:

char zmienna; /* komentarz*/

Znaki są zawierane w apostrofy ('). 'Á', 'a' i '!' są stałynu znakowymi. Znak lewego ukośnika (\) jest określany terminem znaku ucieczki. Umożliwia on wpisanie za nim znaku specjalnego. Na przykład kombinacja znaków \" pozwala na wstawienie wewnątrz łańucha podwójnego znaku cudzysłowu. Znak apostrofu jest wstawiany poprzez kombinację \'. Symbol \n jest znakiem nowego wiersza. Powoduje on, że urządzenie wyjścia przechodzi do początku następnego wiersza. Kombinacja znaków \ \ pozwala wstawić sam znak lewego ukośnika. Znaki mogą być również określane za pomocą zapisu \nnn, gdzie nnn jest kodem ósemkowym wybranego znaku. Tabela 2.3 zawiera zebrane znaki specjalne.

Tabela 2.3. Znaki specjaine

Znak Nazwa Funkcja\b\f\n\r\t

\'\"

\\\nnn

BackspaceWysuw stronyNowy wierszPowrótTabulator

ApostrofPodwójny cudzysłówLewy ukośnik

Przesuwa kursor w lewo o jeden znakPrzechodzi do góry nowej stronyPrzechodzi do następnego wierszaPrzechodzi do początku aktualnego wierszaPrzeskok do końca następnego znaku tabulacji(ograniczenie do ośmiu kolumn)Znak „'”Znak „"”

Znak „\”Numer znaku nnn (zapis ósemkowy)

Znaki są zawierane w apostrofach, natomiast inny typ danych – łańcuchowy - w znakach cudzysłowu. Dobrym sposobem nabycia umiejętności rozpoznawania różnicy pomiędzy apostrofem a cudzysłowem jest pamiętanie, że pojedyncze znaki są zawarte w apostrofach. Łańcuchy składają się z dowolnej liczby znaków (nawet jednego) i są zawarte w znakach cudzysłowu. W przypadku znaków w funkcji printf jest stosowana konwersja % c. W wydruku 2.4 jest wykonywana zamiana kolejności wyświetlania trzech znaków.

10

Page 11: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 2.4. #include <stdio.h> char char1; /* pierwszy znak */ char char2; /* drugi znak */ char char3; /* trzeci znak */ int main() { char1=’A’; char2=’B’; char3=’C’; printf("Znaki %c%c%c sa odwroceniem znakow %c%c%c\n", char1, char2, char3, char3, char2, char1); return (0); }

Po uruchomieniu program wyświetli komunikat:

Znaki ABC sa odwroceniem znakow CBA

3. TABLICE, KWALIFIKATORY ORAZ WPROWADZANIE ZNAKÓW

TabliceTablica jest zbiorem kolejnych obszarów pamięci służących do przechowywania danych. Każda pozycja tablicy jest nazywana elementem. Liczba elementów tablicy jest określana terminem wymiaru tablicy. Typowa deklaracja tablicy ma postać:

/* lista danych, które zostana posortowane i usrednione */ int data_list [3];

W powyższym przykładzie została zadeklarowana zmienna data_list jako tablica trójelementowa. Zmienne data_list[0], data_list[1], data_list[2] są niezależnymi zmiennymi. Aby odwołać się do elementu tablicy, należy posłużyć się liczbą zwaną indeksem, która znajduje się w nawiasach kwadratowych ([]). Odliczanie rozpoczyna się od zera. Tak więc, trzy elementy są liczone od zera do dwóch.Zdrowy rozsądek podpowiada, że w momencie deklarowania tablicy data_list o wymiarze trzech elementów element data_list [3] powinien być poprawny. Niestety tak nie jest i element data_list [3] jest nieprawidłowy.

W wydruku 3.1 jest obliczana suma oraz średnia pięciu liczb.

11

Page 12: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 3.1. #include <stdio.h> float data[5]; /* sumowane i usredniane dane */ float total; /* suma danych */ float average; /* srednia dla danych */ int main() { data[0] = 34.0; data[1] = 27.0; data[2] = 45.0; data[3] = 82.0; data[4] = 22.0; total = data[0] + data[1] + data[2] + data[3] + data[4]; average = total / 5.0; printf("Suma %f Srednia %f\n", total, average); return (0); }Program wyświetli poniższy komunikat:

Suma 210.000000 Srednia 42.000000

ŁańcuchyŁańcuch jest ciągiem znaków. Język C nie posiada wbudowanego typu łańcuchowego. Zamiast tego łańcuchy są tworzone przy użyciu tablicy znaków. Tak naprawdę łańcuchy są tylko tablicami znaków, z którymi jest związanych kilka ograniczeń. Jednym z nich jest znak specjalny '\0' (NULL) stosowany do zaznaczania końca łańcucha. Oto przykład zastosowania znaku ' \0':

char name[4]; int main () { name[0]=’p’; name[1]=’i’; name[2]=’o’; name[3]=’t’; name[4]=’r’; name[5]=’\0’; return (0); }

Powyższy program tworzy tablicę znaków składającą się z sześciu elementów. Należy zauważyć, że musiał zostać przydzielony jeden znak dla znacznika końca łańcucha. Stałe łańcuchowe składają się z tekstu zawartego

12

Page 13: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

w znakach cudzysłowu (" "). Pierwszym parametrem funkcji printf jest stała łańcuchowa. Język C nie pozwala przypisać jednej tablicy do innej, tak więc nie jest możliwe zapisanie poniższego przypisania:

name="Piotr"; /* instrukcja niepoprawna */

Aby wykonać powyższą operację, zastosuj standardową funkcję strcpy służącą do kopiowania stałej łańcuchowej do zmiennej. (Funkcja strcpy kopiuje cały łańcuch łącznie ze znacznikiem jego końca). Aby zainicjować zmienną name o wartości Piotr, napiszemy poniższy program:

#include <string.h> char name[4]; int main() { strcpy(name, "Piotr"); /* poprawna instrukcja */ return (0); }

Język C stosuje łańcuchy o zmiennej długości. Na przykład poniższa deklaracja:

#include <string.h>char string[50];int main (){strcpy (string, "Piotr");}

tworzy tablicę (zmienna string), która może przechowywać do 50 znaków. Wymiar tablicy wynosi 50, ale długość łańcucha ma wartość 3. Zmienna string może przechowywać dowolny łańcuch o długości do 49 znaków. (Jeden znak jest zarezerwowany dla znacznika NULL końca łańcucha).Stałe łańcuchowe i znakowe bardzo się różnią. Łańcuchy są zawarte w znakach cudzysłowu ("), natomiast znaki w apostrofach ('). Z tego też powodu znak "Χ" jest łańcuchem jednoznakowym, a znak 'Υ' jest tylko pojedynczym znakiem. (Łańcuch "Χ" zajmuje 2 bajty, jeden dla znaku Χ i jeden dla znacznika jego końca (\0). Znak 'Y' zajmuje jeden bajt).Istnieje kilka standardowych funkcji, które wykorzystują zmienne łańcuchowe. Zostały one zawarte w tabeli 3.1.

13

Page 14: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Tabela 3.1. Częściowa lista funkcji przetwarzających łańcuchy

Funkcja Opisstrcpy (łańcuch1, łańcuch2)strcat (łańcuch1, łańcuch2)length = strlen (łańcuch)strcmp (łańcuch1, łańcuch2)

Kopiuje łańcuch2 do łańcuch 1Dokleja łańcuch 2 przy końcu łańcuch1 Przekazuje długość łańcucha. Zwraca 0 jeśli łańcuch1 jest równy łańcuchowi2, w innym przypadku zwraca wartość różną od zera

Do wyświetlenia zmiennych łańcuchowych funkcja printf stosuje konwersję %s. Zostało to pokazane w wydruku 3.2.

Wydruk 3.2.

#include <string.h>#include <stdio.h>char name [30]; imie osoby int main (){

strcpy (name, "Piotr"); /* inicjalizacja zmiennej name */printf ("Imie brzmi %s\n", name);return (0);

}

W wydruku 3.3 jest łączone imię i nazwisko w jeden łańcuch.

Wydruk 3.3.#include <string.h>#include <stdio.h>char first[100]; /*imie*/char last[100]; /*nazwisko*/char full_name[200] /*imie i nazwisko*/int main (){

strcpy(first, "Piotr"); /*inicjalizacja zmiennej first*/strcpy(last, "Nowak"); /*inicjalizacja zmiennej last*/strcpy(full_name, first); /*full="Piotr"*//* Uwaga: strcat nie strcpy */strcat(full_name, " "); /*full="Piotr"*/strcat(full_name,last); /*full="Piotr Nowak"*/printf("Imie i nazwisko: %s\n",full_name);return(0); }

14

Page 15: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Działanie programu polega na inicjalizacji zmiennej first poprzez przypisanie jej imienia (Piotr). Nazwisko (Nowak) zostało przypisane zmiennej last. Aby utworzyć imię i nazwisko, do zmiennej full_name zostało skopiowane imię, a następnie przy użyciu funkcji strcat dodano spację. Aby dołączyć nazwisko, ponownie zastosowano funkcję strcat.Rozmiar zmiennej łańcuchowej ma wartość 100, ponieważ mamy pewność, że nikt, kogo bierzemy pod uwagę, nie posiada imienia dłuższego niż 99 znaków. (Jeśli jednak wprowadzimy imię dłuższe niż 99 znaków, program nie zadziała prawidłowo). To, co tak naprawdę się stanie, będzie polegało na próbie zapisu do pamięci, która nie powinna być udostępniona. Po uzyskaniu dostępu mogłoby dojść do zawieszenia programu, wyświetlania nieprawidłowych wyników lub innego nieprzewidywalnego zachowania.

Program wyświetli poniższy komunikat:

Imie i nazwisko: Piotr Nowak

Wprowadzanie łańcuchówStandardowa funkcja fgets służy do wprowadzania łańcuchów z

klawiatury. Ogólna jej postać została zawarta poniżej:

fgets (nazwa, sizeof (nazwa), stdin);

gdzie nazwa oznacza zmienną łańcuchową. Argumentami funkcji są:

nazwa

Określa nazwę tablicy znakowej. Łańcuch łącznie ze znacznikiem jego końca jest wprowadzany do tablicy.

sizeof (nazwa)

Określa maksymalną liczbę wprowadzanych znaków (plus jeden znak dla znacznika końca łańcucha). Funkcja sizeof pozwala w wygodny sposób ograniczyć liczbę wprowadzanych znaków do liczby maksymalnej, która może być przechowana przez zmienną.

stdin

Określa wczytywany plik. W tym przypadku plik jest standardowym wejściem lub klawiaturą.

W wydruku 3.4 jest wczytywany tekst wprowadzony z klawiatury, a następnie jest obliczana jego długość.

15

Page 16: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 3.4.#include <string.h>#include <stdio.h>char line[100]; /* tekst wprowadzony z klawiatury */int main() {

printf("Wpisz tekst: ");fgets(line, sizeof(line), stdin);printf("Dlugosc tekstu wynosi: %d\n", strlen(line));return(0); }

Po uruchomieniu programu zostanie wyświetlony poniższy komunikat:

Wpisz tekst: testDlugosc tekstu wynosi: 5

Łańcuch test ma tylko 4 znaki. Skąd zatem wziął się dodatkowy znak? Funkcja fgets dodaje do łańcucha znacznik jego końca. Tak więc piąty znak jest znakiem nowego wiersza (\n).Załóżmy, że chcemy zmienić nazwę programu, który "prosi" użytkownika o podanie imienia i nazwiska. W wydruku 3.5 został zawarty program takiego programu.

Wydruk 3.5.#include <stdio.h>#include <string.h>char first[100]; /* imie wspolpracownika */char last[100]; /* jego nazwisko */

/* Imie i nazwisko wspolpracownika (po polaczeniu) */char full[200];int main( ) {

printf("Wpisz imie: ");fgets(first, sizeof(first), stdin);printf("Wpisz nazwisko: );fgets(last, sizeof(last), stdin);strcpy(full, first);strcat(full, " ");strcat(full, last);printf("Imie i nazwisko brzmi %s\n", full)return(0);}

16

Page 17: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Po uruchomieniu programu zostaną wyświetlone następujące wyniki:

% name2Wpisz imie: PiotrWpisz nazwisko: NowakImie i nazwisko brzmi: Piotr Nowak%

Chcieliśmy, aby napis "Piotr Nowak" został wyświetlony w jednym wierszu. Co się zatem stało? Funkcja fgets pobiera cały łańcuch łącznie ze znakiem końca wiersza. Aby uniknąć dzielenia łańcucha, należy przed jego wyświetleniem usunąć ten znak.

Na przykład imię "Piotr" zostanie zapisane w następujący sposób:

first[0]='P'first[1]='i'first[2]='o'first[3]='t'first[4]='r'first[5]='\n'first[6]='\0' /* koniec lancucha */

Poprzez przypisanie zmiennej first[6] wartości NULL('\0') można skrócić łańcuch o jeden znak i uniknąć niepotrzebnego nowego wiersza. Operacja ta może być wykonana za pomocą poniższej instrukcji:

first[6]='\0';

Aby rozwiązać ten problem potrzebny będzie bardziej uniwersalny algorytm. Długość tego łańcucha jest indeksem znacznika jego końca. Znak położony o jeden znak wcześniej jest tym, który powinien zostać usunięty. Tak więc, aby obciąć łańcuch, należy zastosować poniższą instrukcję:

first[srtlen(first)-1]='\0';

W wydruku 3.6 został zawarty zmodyfikowany program.

Wydruk 3.6.

#include <stdio.h>#include <string.h>char first[100]; /* imie wspolpracownika */char last[100]; /* jego nazwisko */

/* Imie i nazwisko wspolpracownika (po polaczeniu) */

17

Page 18: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

char full[100];int main() {

printf("Wpisz imie: ");fgets(first, sizeof(first), stdin);

/* obcina ostatni znak */first [strlen (first) -1]='\0';printf("Wpisz nazwisko: ");fgets(last, sizeof (last), stdin);

/* obcina ostatni znak */last[strlen(last)-1]='\0';strcpy(full, first);strcat(full, " ");strcat(full, last);printf("Imie i nazwisko brzmi: %s\n", full);return(0);

}

Po uruchomieniu program wyświetli następujące wyniki:

Wpisz imie: PiotrWpisz nazwisko: NowakImie i nazwisko brzmi: Piotr Nowak

Tablice wielowymiaroweTablice mogą posiadać więcej niż jeden wymiar. Deklaracja tablicy dwuwymiarowej ma następującą postać:

type zmienna[wymiar1][wymiar2]; /* komentarz */

Przykładem takiej tablicy jest:

int matrix[2][4]; /* zwykla macierz */

Należy zauważyć, że język C nie stosuje notacji matrix[10,12] używanej w innych językach.Aby uzyskać dostęp do elementu tablicy matrix, należy zastosować instrukcję:

matrix[1][2]=10;

Język C umożliwia programiście zastosowanie dowolnej ilości wymiarów (ograniczoną tylko przez ilość dostępnej pamięci). Dodatkowe wymiary mogą być dodane za pomocą instrukcji:

four_dimensions[10][12][9][5];

18

Page 19: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wprowadzanie liczbJak gotąd, wprowadzaliśmy tylko proste łańcuchy, ale przecież spodziewamy się czegoś więcej. Chcielibyśmy wprowadzać również liczby. Funkcja scanf działa podobnie jak funkcja printf, tyle że zamiast wyświetlania liczb, wczytuje je. Funkcja scanf w prosty i przystępny sposób umożliwia wczytywanie liczb, ale prawie zawsze to i tak nie działa. Funkcja ta jest dobrze znana z tego, że nie radzi sobie zbyt rewelacyjnie z obsługą znaku końca wiersza, co w efekcie sprawia, że jest ona bezużyteczna dla wszystkich (no, może poza ekspertami).Jednakże istnieje znakomity sposób na ominięcie tej wady funkcji scanf - po prostu jej nie używać. Zamiast niej, do wczytywania danych wejściowych można zastosować funkcję fgets, a następnie funkcję sscanf do konwersji tekstu na liczby. (Nazwa sscanf wywodzi się od słów "string scanf". Funkcja sscanf jest podobna do funkcji scanf, ale zamiast standardowego wejścia przetwarza łańcuchy).Zwykle zmienna line jest używana do przechowywania tekstu wprowadzanego z klawiatury:

char line[100]; /* tekst wprowadzony z klawiatury */

Aby przetworzyć wprowadzony tekst, należy zastosować poniższe instrukcje:

fgets(line, sizeof (line), stdin);sscanf(line, format,&zmienna1, &zmienna2...);

W tym przypadku funkcja fgets pobiera zawartość zmiennej line, a następnie przetwarza ją funkcja sscanf. Parametr format określa łańcuch, podobnie jak ma to miejsce w przypadku funkcji printf. Należy zwrócić uwagę na znak "&" na początku nazwy zmiennej.Symbol ten służy do zaznaczenia, że funkcja sscanf zmieni wartość powiązanych z nią zmiennych.

Jeśli zapomnisz wstawić na początku każdej zmiennej stosowanej w funkcji sscanf znak "&", wynikiem jej działania będzie komunikat błędu "Segmentation violation core dumped (Wykonano zrzut obrazu pamięci wskutek błędu segmentacji)" lub „Illegal memory access (Niedozwolona próba dostępu do pamięci)". W niektórzch przypadkach losowo wybrana zmienna lub instrukcja zostanie zmieniona. W systemie UNIX błąd jest ograniczony tylko do aktualnie uruchomionego programu, natomiast w systemach MS-DOS/Windows pozbawionych ochrony pamięci błąd ten z łatwością może spowodować większe szkody. W systemach MS-DOS/Windows pominięcie znaku "&" może spowodować zawieszenie programu lub samego systemu.

19

Page 20: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W wydruku 3.8 funkcja sscanf została użyta do wczytania liczby wprowadzonej przez użytkownika, a następnie jej podwojenia.

Wydruk 3.8.

#include <stdio.h>char line[100]; /* tekst wczytany z konsoli */int value; /* wartosc do podwojenia */int main(){printf("Podaj wartosc: ");fgets(line, sizeof(line), stdin);sscanf(line, "%d", &value);printf("Dwukrotna wartoscia %d jest %d\n", value, value*2);return(0);}

Program ten wczytuje pojedynczą liczbę, a następnie ją podwaja. Należy zauważyć, że na końcu łańcucha Podaj wartosc: nie ma symbolu \n. Nie ma go tam celowo, ponieważ nie chcemy, aby po naciśnięciu klawisza Enter komputer wyświetlił nowy wiersz. Na przykład efekt uruchomienia programu może wyglądać tak:

Podaj wartosc: 12Dwukrotna wartoscia 12 jest 24

Po zastąpieniu łańcucha Podaj wartosc: łańcuchem Podaj wartosc: \n wynik będzie miał następujcą postać:

Podaj wartosc:12Dwukrotna wartoscia 12 jest 24

Inicjalizacja zmiennychJęzyk C umozliwia inicjalizację zmiennych w deklaracji zmiennej. Na przykład poniższa instrukcja deklaruje zmienną counter typu całkowitego oraz przypisuje jej wartość 0:

int counter = 0; /* liczba pudel dotad policzonych */

Tablice również mogą być w ten sposób inicjalizowane. Element listy musi być zawarty w nawiasach klamrowych {}. Na przykład:

/* Numery produktow dla wytwarzanych czesci */int product_codes[3]={10, 972, 45};

20

Page 21: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Poprzednia inicjalizacja jest równoznaczna poniższej:

produc_ codes[0] = 10;product_codes[1] = 972;product_codes[2] = 45;

Liczba elementów zawarta w nawiasach klamrowych nie musi być zgodna z wymiarem tablicy. Jeśli natomiast będzie zbyt wiele liczb, zostanie wyświetlone ostrzeżenie. Jeśli natomiast będzie zbyt mało liczb, język C dokona inicjalizacji nadmiarowych elementów i przypisze im wartość 0.Jeśli nie zostanie określony rozmiar, język C ustali go na podstawie liczb elementów listy inicjalizacji. Na przykład przy użyciu instrukcji możemy zainicjalizować zmienną product_codes:

/* Numery produktow dla wytwarzanych czesci */int product_codes[] = {10, 972, 45};

Inicjalizacja tablic wielowymiarowych jest podobna do tablic jednowymiarowych. Zestaw nawiaśow [] określa każdy wymiar. Poniższa deklaracja:

int matrix [2][4]; /* zwykla macierz */

może być potraktowana jako deklaracja tablicy o wymiarze 2 z elementami, które są tablicami o wymiarze 4. Taka tablica jest inicjalizowana za pomocą następującej instrukcji:

/* zwykla macierz */int matrix [2][4] ={

{1, 2, 3, 4},{10, 20, 30, 40}

};

Łańcuchy mogą być inicjalizowane w podobny sposób. Na przykład, aby zainicjalizować zmienną name i przypisać jej wartość "Piotr", należy zastosować poniższą instrukcję:

char name[]={'P', 'i', 'o', 't', 'r', '\0'};

Język C stosuje specjalny zapis służący do inicjalizacji łańcuchow. Polega on na zawieraniu łańcucha w znakach cudzysłowu (" "), co upraszcza operację inicjalizacji. W poprzednim przykładzie można było napisać:

char name[]="Piotr";

Wymiar zmiennej name ma wartość 4, ponieważ język C alokuje miejsce dla znaku końca łańcucha „\0".

21

Page 22: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Poniższa deklaracja:

char string [50] = "Piotr";

jest równoznaczna tej:

char string [50];

strcpy (string, "Piotr");

Tablica zawierajęca 50 znaków została utworzona, ale długość łańcucha ma wartość 3.

Typ całkowityJęzyk C jest uważany za język średniego poziomu, ponieważ pozwala na bliski kontakt z rzeczywistymi komponentami sprzętowymi komputera. Język C dostarcza szczegółowych informacji na temat stosowanego sprzętu.Dla przykładu większość typów komputerów umożliwia stosowanie liczb o różnej długości. Język BASIC daje programiście do dyspozycji tylko jeden typ numeryczny. Pomimo że takie ograniczenie upraszcza programowanie, to jednak przez to język BASIC jest wyjątkowo mało wydajny. Język C daje programiście do wyboru kilka różnych odmian typów całkowitych, tak więc ma on możliwość pełnego wykorzystania stosowanego urządzenia.Identyfikator typu całcowitego int informuje język C, aby zastosować najbardziej optymalny rozmiar (dla wykorzystywanego typu komputera) liczby całkowitej. Może to być od dwóch do czterech bajtów w zależności od typu komputera. (Niektóre mniej popularne platformy sprzętowe stosują rozmiar typu całkowitego, taki jak 9 lub 40 bitów).Czasem do przechowania liczb większych, niż na to pozwala typ całkowity int, należy zastosować dodatkowe typy. Poniższa deklaracja:

long int answer; /* wynik obliczen */

jest stosowana w celu określenia dla zmiennej typu całkowitego long int. Kwalifikator long informuje język C, że należy przydzielić dla liczby dodatkowe miejsce. Jeśli będą używane małe liczby, a ponadto zależy nam na zredukowaniu zajmowanej przez nie przestrzeni, stosujmy kwalifikator short. Na przykład:

short int year; /* Rok uwzgledniajacy czesc 19 хх */

22

Page 23: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Język C gwarantuje, że rozmiar zajmowanego przez typy całkowite miejsca opiera się na zależności short <= int <= long. W praktyce, typ short prawie zawsze zajmuje 2 bajty, typ long cztery bajty, natomiast typ int dwa lub cztery bajty.Typ short int zwykle zajmuje 2 bajty lub 16 bitów. Dla liczby jest przeznaczone zazwyczaj 15 bitów oraz 1 bit dla znaku. Format ten daje w efekcie zakres od -32 768 (-215) do 32767 (215-1). Typ unsigned short int przeznacza dla liczby wszystkie 16 bitów, сo odpowiada zakresowi od 0 do 65 535 (216). Wszystkie deklaracje int są domyślnie typu signed (ze znakiem), tak więc poniższa deklaracja:

signed long int answer; /* ostateczny wynik */

jest identyczna z:

long int answer; /* ostateczny wynik */

Na końcu należy wspomnieć o typie całkowitym char mającym niewielki zakres. Zmienne typu char zajmują 1 bajt. Mogą one również przechowywać liczby w zakresie od -128 do 127 (typ signed char) lub od 0 do 255 (typ unsigned char). W przeciwieństwie do typu całkowitego, typ char nie posiada domyślnie wartości signed (ze znakiem). Ustawienie to jest zależne od kompilatora2. Bardzo małe liczby całkowite mogą być wyświetlane przy użyciu konwersji %d.Nie jest możliwe bezpośrednie odczytywanie wartości zmiennej typu char. Aby taką zmienną odczytać, trzeba najpierw przypisać wartość zmiennej typu int, a następnie zastosować instrukcję przypisania. Poniżej zawarto przykładowy program:

#include <stdio.h>signed char ver_short; /* zmienna typu char */char line[100]; /* bufor wejsciowy */int temp; /* wartosc tymczasowa */int main(){

/* wprowadzenie wartosci zmiennej typu char */fgets (line, sizeof (line), stdin);sscanf (line, "%d", &temp);very_short = temp;}

Tabela 3.2 zawiera typy konwersji liczb całkowitych stosowane w funkcjach printf i sscanf.

23

Page 24: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Tabela 3.2. Typy konwersji liczb całkowitych funkcji printf i sscanf

%Konwersja Typ całkowity% hd% d% 1d% hu% u% lu

(signed) short int(signed) int(signed) long intunsigned short inunsigned intunsigned long int

Deklaracja typu long int umożliwia w programie w jawny sposób określenie w razie potrzeby dodatkowej dokładności (kosztem większego wykorzystania pamięci). Zmienne typu short int zajmują mniej miejsca, ale mają bardziej ograniczony zakres. Najmniej miejsca zajmują zmienne typu char. Również one posiadają najmniejszy zakres.Liczby typu unsigned (bez znaku) umożliwiają podwojenie zakresu wartości dodatnich kosztem wyłączenia liczb ujemnych. Są one stosowane w przypadku, gdy mamy pewność, że nie wystąpią liczby ujemne. Przykładem tego są liczniki oraz indeksy.Typy całkowite, które zostaną zastosowane, zależą od określonego programu i wymaganych zakresów zmiennych.

Typ zmiennoprzecinkowyTyp zmiennoprzecinkowy float równiez posiada kilka odmian. Typ float oznacza normalną dokładność (zazwyczaj 4 bajty). Typ double oznacza podwójną dokładność (zazwyczaj 8 bajtów). Zmienna typu double daje programiście wielokrotność zakresu i dokładności zmiennych typu float (pojedyncza dokładność).Kwalifikator long double oznacza rozszerzoną dokładność. W przypadku niektórych systemów typ ten jest równoznaczny typowi double, natomiast w pozostałych daje dodatkową dokładność. Wszystkie zmienne typu zmiennoprzecinkowego zawsze przechowują wartości ze znakiem.Tabela 3.3 zawiera typy konwersji stosowane w funkcjach printf i sscanf dla liczb zmiennoprzecinkowych.

Tabela 3.3. Typy konwersji w funkcjach printf i sscanf dla liczb zmiennoprzecinkowych

%Konwersja Typ zmiennoprzecinkowy Uwagi% f% lf% Lf

floatdoublelong double

tylko funkcja printf 3

tylko funkcja sscanfnie jest dostępna dla wszystkich kompilatorów

24

Page 25: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Na niektórych typach komputerów instrukcje zawierające zmienne typu float są wykonywane szybciej (ale mniej dokładnie) niż instrukcje ze zmiennymi typu double. Instrukcje zawierające zmienne typu double są wykonywane dokładniej, ale odbywa się to kosztem czasu i zajmowanej pamięci. W większości przypadków typ float jest wystarczający, ale jeśli jest istotna dokładność, należy zastosować typ double

Deklaracje stałychCzasem zaistnieje potrzeba zastosowania wartości, która się nie zmienia, tak jak liczba . Słowo kluczowe const oznacza zmienną, której wartość jest stała. Na przykład, aby przypisać wartość stałej PI, należy zastosować poniższą instrukcję:

const float PI=3.1415927; /* stala- liczba Pi */

Zgodnie z przyjętą konwencją w nazwach zmiennych są używane tylko małe litery, natomiast w nazwach stałych wielkie. Jednakże sam język nie narzuca takiej reguły i w efekcie bardziej egzotyczne style programowania stosują różne konwencje.Stałe muszą być zainicjalizowane w momencie deklaracji i nie mogą zmieniać swojej wartości. Na przykład, jeśli spróbyjemy zmienić wartość stałej PI na 3.0, zostanie wygenerowany komunikat błędu:

PI = 3.0; /* instrukcja niedozwolona */

Stałe typu całkowitego mogą być stosowane jako parametr wymiaru deklarowanej tablicy:

/* Maksymalna liczba elementow listy. */const int TOTAL_MAX=50;float total_list[TOTAL_MAX]; /*Suma wartosci dla kazdej kategorii*/

Taki sposób wykorzystania stałych typu całkowitego jest w języku C stosunkowo nowym pomysłem i nie jest jeszcze obsługiwany przez wszystkie kompilatory.

Stałe szesnastkowe i ósemkoweLiczby całkowite są określane jako łańcuch cyfr, taki jak 1234, 88, -123. Następujące łańcuchy są liczbami dziesiętnymi (o podstawie 10): 174 lub 17410. Komputery opierają się na liczbach w systemie dwójkowym (o podstawie 2). Przykładem jest liczba 10 101 110. System ósemkowy (o podstawie 8) jest z łatwością konwertowany na system dwójkowy i odwrotnie. Każda grupa trzech cyfr (23=8) może być przekształcona w pojedynczą cyfrę w systemie ósemkowym. Tak więc, liczba 10101110 może być zapisana jako 10 101 110, a następnie zamieniona na liczbę ósemkową

25

Page 26: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

256. Liczby w systemie szesnastkowym (o podstawie 16) posiadają podobne możliwości konwersji, jednak jednocześnie mogą być używane tylko 4 bity.Język C zawiera odpowiednie konwencje przedstawiania wartości ósemkowych i szesnastkowych. Zera prowadzące są stosowane do oznaczenia stałych zapisanych w systemie ósemkowym. Na przykład liczba 0123 jest 123 w systemie ósemkowym i 83 w systemie dziesiętnym. Rozpoczęcie liczby od symbolu „0x" oznacza stałą zapisaną w systemie szesnastkowym (o podstawie 16). Tak więc, liczba 0x15 oznacza 21 w systemie dziesiętnym. Tabela 3.4 zawiera kilka liczb wyrażonych we wszystkich trzech systemach.

Tabela 3.4. Przykłady liczb całkowitych

Podstawa 10 Podstawa 8 Podstawa 16

6

9

15

06

011

017

0 6

0 9

0 F

Operatory specjalneJęzyk C nie tylko zawiera bogaty zestaw deklaracji, ale również posiada dużą liczbę operatorów do specjalnych zastosowań.Często programista chce dokonać inkrementacji zmiennej (wzrostu wartości o 1). Przy zwykłej instrukcji przypisania operacja taka miałaby następującą postać:

total_entries=total_entries+1;

Język C pozwala wykonać to typowe zadanie w o wiele prostszy sposób. Operator ++ jest stosowany przy operacji inkrementacji:

++total_ entries;

Podobny operator -- służy do dekrementacji (obniżenia wartości o 1) zmiennej:

--number_left;/* jest identyczna jak */number_left=number_left-1;

Załóżmy, że chcemy wykonać inkrementację o wartość 2 zamiast 1. Aby ją wykonać, należy zastosować poniższy zapis:

total_entries+=2;

Zapis ten jest równoznaczny poniższemu:

total_entries=total_entries+2;

26

Page 27: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Każdy z prostych operatorów zawarty w tabeli 3.5 może być zastosowany w podobny spośob.

Tabela 3.5. Operatory specjalne

Operator Przykład zastosowania Instrukcja równoznaczna

+=

-=

*=

/=

=

x+=2;

x-=2;

x*=2;

x/=2;

x=2;

x=x+2;

x=x-2;

x=x*2;

x=x/2;

x=x2;

Efekty uboczneNiestety język C daje programiście możliwość stosowania efektów ubocznych. Efekt uboczny jest operacją, która jest wykonywana przy okazji realizacji przez instrukcję podstawowej operacji. Poniżej zawarto poprawny kod żródłowy języka C:

size = 5;result = ++size;

Pierwsza instrukcja przypisuje zmiennej size wartość 5. Druga instrukcja przypisuje zmiennej result wartość zmiennej size (podstawowa operacja), a następnie zwiększa wartość zmiennej size (efekt uboczny).W jakiej kolejności powyższe operacje są wykonywane? Jako odpowiedź na to pytanie istnieją cztery możliwości.

1. Zmiennej result zostanie przypisana wartość zmiennej size(5), a następnie zmienna size zostanie inkrementowana. Ostatecznie zmienna result bedzie miała wartość 5, a zmienna size – 6.

2. Zmienna size zostanie inkrementowana, a następnie zmiennej result zostanie przypisana wartość zmiennej size (6). Ostatecznie zmienna result oraz zmienna size będą miały wartość 6.

3. Odpowiedź zależy od zastosowanego kompilatora i będzie inna na różnych typach komputerów.

4. Jeśli kod źródłowy nie zostanie zapisany w takiej postaci, nie musimy w ogóle zadawać tego typu pytań.Poprawna odpowiedź na powyższe pytanie jest zawarta w punkcie 2, czyli inkrementacja wystąpi przed operacją przypisania. Niezależnie od tego, odpowiedź z punktu 4 jest o wiele lepsza. Podstawowe operacje wykonywane w języku C są już i tak wystarczająco złożone, aby jeszcze zajmowac się efektami ubocznymi.

27

Page 28: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Niektórzy programiści bardzo cenią sobie zwartość kodu. Takie podejście jest pozostałością z dawnych skazów, gdy koszt pamięci był znaczący. Mam nadzieję, że sztuka programowania rozwinęła  się na tyle, że teraz przejrzystość programu jest bardziej ceniona niż jego zwartość. (Uznane powieści czytane przez wiele osób nie zostały napisane językiem skrótów).Język C posiada dwa rodzaje operatora ++. Pierwszy z nich ma postać zmienna++, a drugi ++zmienna. W przypadku pierwszego rodzaju:

number=5;result=number++;

operator wyznacza wartość wyrażenia, a następnie inkrementuje wartość liczby. Zmienna result ma wartość 5. Drugi rodzaj operatora:

number = 5;result = ++number;

najpierw zwiększa wartość liczby, a następnie wyznacza wartość wyrażenia. Zmienna result ma wartość 6. Zastosowanie w podobny sposób operatorów ++ i -- może doprowadzić do dziwnego programu:

o = --o - o--;Nietypowość powyższej instrukcji wynika stąd, ze wygląda ona tak jakby ktoś używał alfabetu Morse'a. Programista nie czyta takiej instrukcji, ale raczej ją analizuje. Jeśli nigdy wcześniej nie stosowaliśmy operatora ++ lub -- jako elementu dowolnej instrukcji lub nie były one używane razem, różnica pomiędzy nimi może być niezauważalna.

++х czy х++Dwa rodzaje operatora inkrementacji są określane odpowiednio terminem prefiksu (++x) oraz postfiksu (x++). Który rodzaj operatora inkrementacji stosować? Tak naprawdę w języku C nie ma to znaczenia. Jednakże jeśli są używane obciążające operatory języka C++, prefiks (++x) jest bardziej wydajny. Tak więc, aby nabrać dobrych nawyków w programowaniu w języku C++, lepiej stosować prefiks.

Jeszcze o efektach ubocznychBardziej złożone efekty uboczne mogą nawet sprawić kłopot kompilatorowi języka C. Spójrzmy na poniższy fragment programu:

value=1;result=(value++*5)+(value++*3);

W programzie tym są wykonywane następujące operacje:1. Pomnożenie wartości zmiennej value przez 5, a następnie dodanie do niej liczby 1.2. Pomnożenie wartości zmiennej value przez 3, a następnie dodanie do niej liczby 1.3. Zsumowanie wyników dwóch operacji mnożenia.

28

Page 29: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Krok 1. i 2. są pod względem priorytetu równorzędne (w przeciwieństwie do poprzedniego przykładu), zatem kompilator może zadecydować o kolejności ich wykonywania. Przypuśćmy, że kompilator najpierw wykona krok 1. Zostało to pokazane na rysunku 3.1.

Teraz załóżmy, że kompilator najpierw wykona krok 2. Zostało to pokazane na rysunku 3.2.Poprzez zastosowanie pierwszej metody otrzymujemy w wyniku wartość 11, natomiast wykorzystując metodę drugą otrzymujemy wynik o wartości 13. Wartość wynikowa tego wyrażenia jest niejednoznaczna. Zależnie od tego, jaki kompilator został zastosowany, wynik może mieć wartość 11 lub 13. Co

gorsza, niektóre kompilatory zmieniają swoje działanie, jeśli zostanie włączona optymalizacja. Tak więc, po włączeniu optymalizacji działanie programu może zostać przerwane. Poprzez użycie operatora ++ w środku dłuższego wyrażenia pojawił się problem. (Problem tem nie jest jedynym problemem, jaki może wywołać operator ++ lub --). Aby uniknąć kłopotów i sprawić, że problem będzie jak najbardziej prosty, należy zawsze umieszczać sam operator ++ lub – w oddzielnym wierszu.

29

Rysunek 3.1. Wyznaczenie wartości wyrażenia pierwszą metodą

result = (value++ * 5) + (value++ * 3);

1 2* 5

5 + 6

* 3

11

zm ienna va lue

zm ienna va lue

++ operacjaWyznaczenie wartości pierwszego wyrażenia

Wyznaczenie wartości drugiego wyrażenia

result = (value++ * 5) + (value++ * 3);

2 1 5

10 + 3

3

13

value value

++ operacjaWyznaczenie wartości pierwszego wyrażenia

Wyznaczenie wartości drugiego wyrażenia

Rysunek 3.2. Wyznaczenie wartości wyrażenia drugą metodą

Page 30: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

4. Instrukcje sterujące i warunkowe

Obliczenia i wyrażenia są tylko niewielką częścią języka programowania. Instrukcje sterujące i warunkowe są nieodzowne. Określają one kolejność, w jakiej będą wykonywane instrukcje programu.Jak dotąd, tworzyliśmy programy liniowe. W takich programach instrukcje są wykonywane w sposób liniowy, jedna po drugiej. Na tym wykładzie dowiemy się, w jaki sposób przy użyciu instrukcji rozgałęziających oraz pętli zmieniać przepływ sterowania programu. Instrukcja rozgałęziające w zależności od wartości warunku decydują o wykonaniu określonego segmentu programu. Pętle służą do kilkukrotnego powtarzania wykonywania określonego segmentu programu lub do momentu wystąpienia żądanej wartości warunku.

Instrukcja ifInstrukcja if pozwala zdefiniować określone warianty wykonywania instrukcji programu. Ogólna postać instrukcji if została zawarta poniżej:

if (warunek) instrukcja;Jeśli warunek ma wartość true (wartość różna od zera), instrukcja zostanie wykonana. Jeśli natomiast warunek ma wartość false (zero), instrukcja nie zostanie wykonana. Załóżmy, że tworzymy program obsługi faktur, w którym w momencie, gdy klient nie ma żadnych zaległych należności lub dokonał zakupu na kredyt (jest winien kwotę ujemną), zostanie wyświetlony komunikat. W języku C taki program będzie miał postać:

if (total_owed <= 0) printf ("Nie jestes nic winien.\n");Operator <= jest operatorem relacji, który jest równoznaczny warunkowi logicznemu mniejszy lub równy. Powyższa instrukcja oznacza: „Jeśli wartość zmiennej total_owed jest mniejsza lub równa zeru, należy wyświetlić komunikat". Pełna lista operatorów relacji została zawarta w tabeli 4.1.

Tabela 4.1. Operatory relacji

Operator Znaczenie<=<>>== =! =

Mniejszy lub równyMniejszy niżWiększy niżWększy lub równyRównyNegacja

30

Page 31: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wiele instrukcji może być pogrupowane poprzez umieszczenie ich w nawiasach klamrowych {}. Przykładem jest:

if (total_owed <= 0) {++ zero_count;printf ("Nie jestes nic winien.\n");}

Dla zwiększenia czytelności instrukcje zawarte w nawiasach klamrowych posiadają wcięcia. Ułatwia to programiście szybkie zorientowanie się, które instrukcje zostaną wykonane warunkowo. Jak się później okaże, błedne zastosowanie wcięć może spowodować, że program stanie się mało czytelny.

Instrukcja elseAlternatywną postacią instrukcji if jest poniższa instrukcja:

if (warunek) instrukcja;else instrukcja;

Jeśli warunek ma wartość true (wartość różna od zera), jest wykonywana pierwsza instrukcja. Jeśli natomiast warunek ma wartość false (zero), zostanie wykonana druga instrukcja. W naszym przykładowym programie do wystawiania faktur komunikat był wyświetlany tylko wtedy, gdy klient nie miał żadnych zaległych płatności. W rzeczywistości na pewno chcielibyśmy poinformować klienta, na jaką kwotę zalega z platnościami:

if (total_owed <= 0) printf ("Nie jestes nic winien.\n");else printf ("Jestes winien %d zlotych \n", total_owed);

Przyjrzyjmy się teraz poniższemu fragmentowi programu (z nieprawidłowo zastosowanymi wcięciami):

if (count < 10) * instrukcja warunkowa if #1 */if ((count % 4) = = 2) /* instrukcja warunkowa if #2 */printf, ("Stan: Blady \n”);

elseprintf ("Stan: Opalony \n" );

Dotyczy programistów języka PASCAL: w przeciwieństwie do tego języka, język C wymaga wstawienia średnika na końcu instrukcji poprzedzającej słowo kluczowe else.W powyższym programie można wyróżnić dwie instrukcje warunkowe if oraz jedną else. Do której instrukcji warunkowej if należy instrukcja else?

31

Page 32: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

a. Czy do instrukcji if # 1?b. Czy do instrukcji if # 2?c. Jesłi nigdy jeszcze nie napisałeś takiego programu, nie przejmuj się tym.

Poprawną odpowiedzią jest odpowiedź „c". Zgodnie z zasadami składni języka C, instrukcja else jest połączona z najbliższą instrukcją if, tak więc odpowiedź „b" składniowo jest również poprawna. Niezależnie od tego, pisanie programu w ten sposób narusza zasadę czytelności. Program źródłowy powinien być jak najbardziej prosty i czytelny. Powyższy fragment programu powinien być napisany w następujący sposób:

if (count < 10) { /* instrukcja warunkowa if #1 */if ((count % 4) == 2) /* instrukcja warunkowa if #2 */

printf ("Stan: Blady \n");else

printf ("Stan: Opalony \n"); }

W naszym oryginalnym wydruku nie mogliśmy w prosty sposób określić, do której instrukcji if należy instrukcja else. Jednakże po dodaniu dodatkowych nawiasów klamrowych została zwiększona czytelność oraz przejrzystośc, dzięki czemu program stał się bardziej zrozumiały.

Jak uniknąć stosowania funkcji strcmpFunkcja strcmp porównuje dwa łańcuchy, a następnie zwraca wartość zero, jeśli są identyczne lub wartość różną od zera, jeśli się różnią. Aby sprawdzić, czy dwa łańcuchy są identyczne, należy użyć poniższego programu:

/* Sprawdzenie prawdziwosci wyrazenia lancuchl = = lancuch2 */if (strcmp(lancuch1, lancuch2) = = 0)

printf ("Lancuchy sa identyczne \n");else

printf ("Lancuchy nie sa identyczne \n");Niektórzy programiści pomijają komentarz oraz klauzulę == 0. Tym sposobem dochodzi do wystąpienia następującego problemu:

if (strcmp(lancuch1, lancuch2))printf ("......");

Na pierwszy rzut oka program ten oczywiście dokona porównania dwóch łańcuchów, a następnie w przypadku ich zgodności wykona funkcję printf. Niestety ta oczywistość nie oznacza niczego dobrego. Jeśli dwa łańcuchy są identyczne, funkcja strcmp zwróci wartość zero, a funkcja

32

Page 33: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

printf nie zostanie wykonana. Z powodu takiego zachowania funkcji strcmp należy być ostrożnym przy jej stosowaniu i zawsze opisywać w komentarzu jej działanie. Pomocne będzie również umieszczenie komentarza zawierającego informacje na temat funkcji programu.

PętlePętle umożliwiają w programie powtarzanie dowolną ilość razy określony fragment programu lub do momentu wystąpienia jakiegoś warunku. Na przykład pętle są stosowane do zliczania liczby wystąpienia określonego słowa w dokumencie lub do wyznaczenia liczby kont, na ktorych jest ujemne saldo.

Instrukcja whileInstrukcja while jest stosowana w momencie, gdy konieczne jest w programie wykonanie kilkakrotnie tej samej operacji. Ogólna postać instrukcji while została zawarta poniżej:

while ( warunek) instrukcja;Program będzie wykonywał instrukcję zawartą wewnątrz instrukcji while do momentu, gdy warunek przyjmie wartość false (zero). (Jeśli na początku warunek ma wartość false, instrukcja nie zostanie wykonana). W wydruku 4.1 umieszczonym (rozpatrzymy niżej) zostaną obliczone liczby Fibonacciego, które są mniejsze od 100. Ciąg Fibonacciego ma następującą postać:

1 1 2 3 5 8Elementy ciągu są obliczane z poniższych równań:

112 = 1 + 13 = 1 + 25 = 2 + 3itd.

Ogólne równanie ma postać:

fn = fn-1 + fn-2

Jest to matematyczne równanie, w którym występują niewiadome (fn). Matematycy posługują sią bardzo zwięzłym stylem nazewnictwa zmiennych. W przypadku programowania posługiwanie się takim stylem może okazać się niebezpieczne, tak więc lepiej przetłumaczyć niewiadome na postać bliższą językowi C. Tabela 4.2 zawiera take translację.

33

Page 34: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Tabela 4.2. Translacja z języka matematyki na nazewnictwo języka C

Zapis matematyczny Zapis w języku C

fn

fn-1

fn-2

next_numbercurrent_numberold_number

W języku C równanie jest zapisywane w następującej postaci:

next_number = current_number + old_number;Chcemy teraz zdefiniować pętlę, która będzie wykonywana do momentu przekroczenia wartości 100. Pętla while ma następującą postać:

while (current_number < 100)Będzie ona powtarzała obliczenia i wyświetlanie wyników aż do momentu osiągnięcia ustalonej wartości granicznej.

Rysunek 4.1. Wyznaczanie wartości ciągu Fibonacciego

Na rysunku 4.1 został pokazany przebieg wykonywania programu. Na początku zmienna current_number i old_number mają wartość 1. Wyświetlana jest aktualnie obliczona wartość ciągu. Następnie jest obliczana wartość zmiennej next_number (wartość 2) oraz wartość ciągu poprzez przypisanie wartości zmiennej next_number zmiennej current_number, a także wartości zmiennej current_number zmiennej old_number. Operacja ta jest powtarzana do momentu obliczenia ostatniej wartości ciągu i wtedy pętla while kończy swoje działanie.

34

następna aktualna poprzedniawartość wartość wartość

następna aktualna poprzedniawartość wartość wartość

printf("% d\n",curren t_num ber);next_num ber = current_num ber _num ber; = o ld

2 = 1 + 1

old_num ber = current_num ber; current_num ber = next_num ber;

2 1 1

Page 35: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Tym sposobem program wychodzi poza obszar pętli. Pierwsze dwie wartości ciągu Fibonacciego to 1 i 1, dlatego też inicjalizujemy odpowiednie zmienne poprzez przypisanie im tych wartości. Pełny program źródłowy programu został zawarty w wydruku 4.1.

Wydruk 4.1.#include <stdio.h>int old_number; /* poprzednia wartosc ciagu Fibonacciego */int current_number; /*aktualna wartosc ciagu Fibonacciego */int next_number; /* nastepna wartosc ciagu */int main(){

/* poczatek programu */old_number=1;current_number=1;printf("1\n"); /* Wyswietla pierwsza wartosc ciagu*/while (current_number < 100) {

printf("%d\n", current_number);next_number = current_number + old_number;old_number = current_number;current_number = next_number;

}return (0);}

Instrukcja breakInstrukcja while posłużyła do obliczenia liczb Fibonacciego mniejszych niż 100. Działanie pętli zostaje przerwane w momencie, gdy warunek w instrukcji while osiągnie wartość false (zero). Pętle mogą być też przerywane w dowolnym momencie poprzez zastosowanie instrukcji break.Załóżmy, że chcemy zsumować wartości ciągu liczb, ale nie wiemy, ile ich jest. W takiej sytuacji konieczne jest poszukanie sposobu, który pozwoli programowi stwierdzić, że został osiągnięty koniec listy zawierającej wartości ciągu. W wydruku 4.2 w celu zaznaczenia końca listy została użyta liczba zero (0).

Należy zauważyć, że instrukcja while zaczyna się tak jak poniżej:while (1) {

Taki program pozostawiony sam sobie będzie wykonywał pętlę w nieskończoność, poniewaz instrukcja while zostanie zakończona tylko

35

Page 36: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

wtedy, gdy wynikiem pierwszego równania będzie 0. Jedyny sposób zakończenia działania takiej pętli polega na zastosowaniu instrukcji break.Po znalezieniu znaku znacznika końca listy (0), aby zakończyć działanie pętli, należy zastosować poniższą instrukcję:

if (item==0)break;

Wydruk 4.2.#include <stdio.h>char line[100]; /* zmienna przechowujaca dane wejsciowe */int total; /* suma dotychczas dodanych wartosci ciagu */int item; /* nastepna pozycja, ktora zostanie dodana do listy */

int main(){

total = 0;while (1) {

printf("Aby dodac wpisz znak # \n");printf(" lub w celu zatrzymania wpisz 0:");fgets(line, sizeof(line), stdin);sscanf(line, "%d",' &item);if (item == 0)break;total += item;printf("Suma: %d\n", total);}

printf("Suma koncowa wynosi: %d\n", total);return (0);

}

Instrukcja continueInstrukcja continue jest podobna do instrukcji break. Różnica pomiędzy nimi polega na tym, że instrukcja continue zamiast kończyć działanie pętli ponawia od początku jej wykonywanie. Na przykład, jeśli zmodyfikujemy poprzedni program tak, aby sumował tylko liczby większe od zera, będzie on miał postać zawartą w wydruku 4.3.

36

Page 37: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 4.3

#include <stdio.h>char line[100]; /* zmienna przechowujaca dane wejsciowe */int total; /* suma dotychczas dodanych wartosci ciagu */int item; /* nastepna pozycja, ktora zostanie dodana do listy */int minus_items; /* ilosc wartosci ujemnych */

int main(){

total = 0;minus_items = 0;while (1) {

printf("Aby dodac wpisz znak #\n");printf(" lub w celu zatrzymania wpisz 0:");fgets(line, sizeof(line), stdin);sscanf(line, "%d", &item);if (item == 0)

break;if (item < 0) {

++minus_items;continue;

}total += item;printf("Sums: %d\n", total);

}printf("Sums koncowa wynosi: %d\n", total);printf(",w tym zostalo pominietych %d wartosci ujemnych\

n", minus_items);return (0);

}

Zastosowanie instrukcji przypisania a efekt ubocznyJęzyk C posługuje się instrukcją przypisania prawie wszędzie. Na przykład instrukcja przypisania może być umieszczona wewnątrz innej instrukcji przypisania:

/* nie nasladuj takiego stylu programowania */average = total_value / (number_of_entries = last _first);

Można wygłądać również tak:

/* taki styl programowania jest zalecany */number_of_entries = last - first;average = total_value / number_of_entries;

37

Page 38: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W pierwszej wersji programu przypisanie zmiennej number_of_entries jest umieszczone wewnątrz wyrażenia. Programy powinny być proste i czytelne, zatem nie powinno się stosować tego typu instrukcji przypisania. Najważniejsza zasada sztuki programowania brzmi: „Przede wszystkim program ma być prosty".

Język C umożliwia programiście umieszczanie instrukcji przypisania jako warunku instrukcji while. Oto przykład:

/* nie nasladuj takiego stylu programowania */while ((current_number = last_number + old_number) < 100)printf ("Wartosc wynosi: %d\n", current_number);

Unikaj takiego sposobu programowania. Zauważ, o ile bardziej przejrzyste rozumowanie jest zawarte w poniższej wersji programu:

/* taki styl programowania jest zalecany */while (1) {current_number = last_number + old_number;if (current_number >= 100)break;printf ("Wartosc wynosi: %d\n", current_number);}

Pytanie 4.1. Z jakiegoś bliżej nieokreślonego powodu prodram zawarty w wydruky 4.4 stwierdza, će nikt nie jest winien nawet złotówki. Dlaczego tak jest?

Wydruk 4.4.

#include <stdio.h>char line[80]; /* zmienna przechowujaca dane wejsciowe */int balance_owed; /* kwota zaleglosci */int main(){

printf("Wpisz kwote zaleglosci w zlotych:");fgets(line, sizeof(line), stdin);sscanf(line, "%d", &balance_owed);

if (balance_owed = 0)printf("Nie jestes nic winien.\n");

elseprintf("Jestes winien %d zlotych.\n", balance_owed);return (0);

}

38

Page 39: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

OdpowiedziOdpowiedź 4.1. Program ten demonstruje jeden z najczęstszych i przyprawiających o ból głowy błędów programistów języka C. Problem ten polega na tym, że język C pozwala umieszczać instrukcje przypisania wewnątrz instrukcji warunkowej if.Poniższa instrukcja:

if (balance_owed = 0)stosuje zamiast podwójnego znaku równości (= =) znak równości (=). Język C przypisuję zmiennej balance_owed wartość 0, a następnie sprawdza wynik (który ma wartość 0).Jesłi wynik ma wartość różną od zera, instrukcja warunkowa if zostanie wykonana. Ponieważ w powyższej instrukcji wynik ma wartośc 0 (false), wykonywana jest instrukcję else, po czym program wyświetla niepoprawny wynik.Następująca instrukcja:

if (balance_owed = 0)

jest równoznaczna instrukcjom:

balance_owed =0;if (balance_owed != 0)

Instrukcja powinna mieć następującą postać:

if (balance_owed = = 0)

Omówiony błąd jest najczęściej popełnianym błędem przez początkujących programistów języka C.

5 Proces tworzenia programuProgramowanie jest czymś więcej niż tylko pisaniem programu źrodłowego. Tworzone oprogramowanie przechodzi pełny cykl produkcyjny. Rodzi się, dorasta, dojrzewa i wreszcie umiera, by ustąpić miejsca młodszemu i nowszemu produktowi. Zrozumienie takiego cyklu jest istotne, ponieważ jako programista spędzicie niewiele czasu przy pisaniu nowego programu. Większość czasu programista poświęca modyfikowaniu i usuwaniu błędów w stworzonym programie. Oprogramowanie nie jest zawieszone w próżni. Musi zawierać dokumentację oraz być na bieżąco konserwowane, rozwijane i sprzedawane. Pomimo że ostateczna postać naszego programu zawiera mniej niż 100 wierszy, to jednak zasady zastosowane przy jego tworzeniu będą nadal obowiązywać w przypadku pisania programów zawierających wiele tysięcy wierszy programu.Tworzenie programu składa się z następujących czynności:

39

Page 40: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

• Wymagania - Programy powstają w momencie, gdy ktoś ma pomysł i zaczyna go praktycznie realizować. Dokument zawierający stawiane wymagania opisuje w ogólnych zarysach, jakie cele stoją przed programem.

• Specyfikacja programu - Specyfikacja jest opisem funkcji, jakie będzie realizował program. Na początku jest wykorzystywana specyfikacja wstępna słuząca do opisania funkcji realizowanych przez program. Następnie w miarę upływu czasu, gdy program staje się coraz bardziej doskonały, definiowana jest specyfikacja. Po zakończeniu tworzenia programu specyfikacja służy jako pełny opis funkcji przez niego realizowanych.

• Projekt programu - Programista wykonuje ogólny projekt programu. Projekt ten powinien zawierać podstawowe algorytmy, definicje modułów, formaty oraz struktury danych.

• Kodowanie - Następnym krokiem jest pisanie kodu programu. W tym kroku najpierw jest wykonywany prototyp, a następnie jest on wypełniany. Daje to w efekcie kompletny program.

• Testowanie - Programista powinien przygotować procedury testujące, a następnie posługiwać się nimi podczas testowania programu. Jeśli jest to możliwe, programista powinien przekazać obowiązki związane z testowaniem programu innej osobie.

• Wykrywanie błędów - Niestety niewiele programów działa za pierwszym razem. Muszą one być ponownie przetestowane i poprawione.

• Pełna wersja - Tym terminem określa się kompletny program z dołączoną dokumentacją gotowy do sprzedaży.

• Konserwacja - Programy nigdy nie być będą doskonałe. Błędy będą wykrywane i należy je usuwać. Krok ten jest fazą procesu programowania związaną z konserwacją kodu.

• Nowa wersja i aktualizacja - Po upłynięciu określonego czasu od daty wypuszczenia na rynek pełnej wersji programu użytkownicy zaczną domagać się jego modyfikacji, nowych funkcjonalności lub bardziej wydajnych algorytmów. Wtedy też jest tworzona nowa specyfikacja i proces tworzenia oprogramowania zaczyna się od początku.

PrzygotowanieSystem operacyjny umożliwia grupowanie plików w katalogach. Podobnie jak ma to miejsce w przypadku segregatorów przechowujących razem dokumenty w szafce, tak katalogi slużą do przechowywania pogrupowanych plików. (System Windows używa zamiast katalogi terminu foldery). W tym przykładzie zostanie utworzony prosty kalkulator. Wszystkie pliki tego programu zostaną umieszczone w katalogu calc. W systemie UNIX

40

Page 41: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

utworzymy w naszym katalogu domowym nowy katalog, a następnie otworzymy go w sposób pokazany w poniższym wydruku:

% cd ~ % mkdir calc % cd ~/calc

W systemie MS-DOS należy wpisać:

C:\> cd \ C:\> mkdir calc C:\> cd \calc C:\CALC>

Tworzenie powyższego katalogu jest bardzo proste. W miarę tworzenia kolejnych programów prawdopodobnie zdefiniujemy bardziej złożoną strukturę katalogów. Aby uzyskać więcej informacji na temat tworzenia struktury katalogów lub folderów, zajrzyjmy do dokumentacji stosowanego systemu operacyjnego.

SpecyfikacjaNa potrzeby tego zadania postawimy następujące wymaganie: ˛˛stworzyć program który spełnia rolę czterofunkcyjnego kalkulatora”. Przeważnie określone wcześniej wymagania są nieprecyzyjne i niekompletne. Programista udoskonala wtendy takie wymagania i przekształca je do postaci, w której zawarty jest dokładny opis tworzonego programu. Pierwszym krokiem jest utworzenie wstępnej specyfikacji zawierającej funkcje, które będą realizowane przez program, oraz sposób ich wykorzystania. Dokument ten nie opisuje wewnętrznę struktury programu lub algorytmów w nim wykozystanych. Przykładowa specyfikacja tworzonego kalkulatora czterofunkcyjnego została zawarta poniżej w ramce zatytułowanej ˛˛Program calc: Kalkulator czterofunkcyjny”.Tworzenie wstępnej specyfikacji służy dwóm celom. Pierwszy z nich to oczywiście konieczność przekazania jej szefowi (lub klientowi) w celu zatwierdzenia wcześniejszych wzajemnych ustaleń. Drugi daje możliwość przekazania specyfikacji kolegom po fachu w celu uzyskania ich ewentualnych spostrzeżeń i sugestii.Wstępna specyfikacja została przekazana dalej i w efekcie pojawiły się następujące komentarze:• Jak zamierzamy zakończyć działanie programu?• Co się stanie, jeśli spróbujemy wykonać operację dzielenia przez zero?Po zapoznaniu się z tymi pytaniami został dodany nowy operator q służący do zakończenia programu oraz dodano następujący komunikat:

41

Page 42: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

“Dzielenie przez zero spowoduje wyswietlenie komunikatu bledu, natomiast zawartosc wynikowego rejestru nie ulegnia zmianie”.

Program calc: Kalkulator czterofunkcyjny

Wstępna specyfikacja

Ostrzeżenie: Dokument ten jest wstępną specyfikacją. Każde podobieństwo do istniejącego lub wycofanego oprogramowania jest czysto przypadkowe.

Program calc umożliwia użytkownikowi zamianę komputera wartego 2000 dolarów na kalkulator czterofunkcyjny za 2 dolara. Program ten wykonuje operacje dodawania, odejmowania, mnożenia oraz dzielenia prostych liczb całkowitych.

Po uruchomieniu programu nastąpi wyzerowanie rejestru wyniku i wyświetlenie jego zawartości. Użytkownik może następnie wprowadzić operator i liczbę. Wynik zostanie zaktualizowany i wyślwietlony. Następujące operatory są dozwolone:

Operator Znaczenie

+-*/

DodawanieOdejmowanieMnozenieDzielenie

Poniżej zawarto przykład działania programu (użytkownik wprowadza wartości zaznaczone pogrubioną czcionką):

calcWynik: 0Podaj operator i liczbę: +123 Wynik: 123Podaj operator i liczbę: - 23Wynik: 100Podaj operator 1 liczbę: / 25Wynik: 4Podaj operator i liczbę: * 4Wynik: 160

42

Page 43: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Projekt programuPo zatwierdzeniu wstępnej specyfikacji można rozpocząć przygotowywanie projektu programu. Na tym etapie programista przygotowuje plan dalszej pracy. W przypadku większych projektów programistycznych angażujących sporo ośob program zostanie podzielony na moduły, które są następnie przydzielane poszczególnym programistom. Również na tym etapie są definiowane formaty plików, projektowane struktury danych oraz wybierane podstawowe algorytmy.Nasz prosty kalkulator nie wykorzystuje żadnych plików i nie wymaga żadnych wymyślnych struktur danych. Jedyne, co na tym etapie pozostaje do zrobienia, to przygotowanie głównego algorytmu. Posługując się pseudokodem, czymś pośrednim pomiędzy językiem polskim a prawdziwym językiem programowania, można określić następujący algorytm:

Pętla Wczytaj operator i liczbę Wykonaj obliczenie Wyświetl wynikiKoniec pętli

PrototypPo zakończeniu etapu tworzenia projektu programu można przejść do pisania programu. Jednak zamiast próby napisania kompletnego kodu programu, a następnie usuwania błędów zastosujemy metodę określaną terminem szybkiego prototypowania. Polega ona na implementacji jak najmniejszego fragmentu specyfikacji, która jednak realizuje określoną operację. W naszym przypadku rozpoczniemy od stworzenia kalkulatora, który będzie wykonywał tylko jedną z czterech docelowych funkcji. Po stwierdzeniu, że ta jedna funkcja działa prawidłowo, na podstawie takiej nadbudowy dodamy pozostałe funkcje. Również przekazanie prototypu do kierownictwa pozwala na pewną dozę obserwacji, a nawet zabawy, która pozwoli wyrobić sobie pozytywne zdanie na temat projektu i celów z nim związanych.Bezproblemowa komunikacja jest kluczem do programowania na wysokim poziomie. Im więcej przełożony zobaczy, tym lepiej. Program pierwszej wersji kalkulatora czterofunkcyjnego jest zawarty w wydruku 5.1.

43

Page 44: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 5.1

#include <stdio.h> char line[100]; /* zmienna przechowujaca dane wejsciowe */ int result; /* wynik obliczen */ char operator; /* operator podany przez uzytkownika */ int value; /* wartosc podana za operatorem */ int main() { result = 0; /* inicjalizacja zmiennej result */ /* petla wykonywana w nieskonczonosc */ /* (lub do momentu wykonania instrukcji break) */ while (1) { printf("Wynik: %d\n", result); printf("Podaj operator i liczbe: "); fgets(line, sizeof(line), stdin); sscanf(line, "%c %d", &operator, &value); if (operator = '+') { result += value; } else { printf("Nieprawidlowy typ operatora %c\n", operator); } } }

Program rozpoczyna działanie od inicjalizacji zmiennej result poprzez przypisanie jej wartości 0. Głównym elementem programu jest pętla rozpoczynająca się od instrukcji:

while (1) {

Pętla ta będzie wykonywana aż do momentu napotkania instrukcji break. Fragment powyższego programu:

printf("Podрj operator i liczbe: "); fgets(line, sizeof(line), stdin); sscanf(line, "%c %d", &operator, &value);

prosi użytkownika o podanie operatora i liczby. Po wprowadzeniu tych danych przetwarza je funkcja sscanf, a następnie przypisuje zmiennym operator oraz value. Kolejną operacją jest sprawdzenie operatorów. Jeśli operator jest znakiem (+), jest wykonywana poniższa instrukcja:

if (operator = '+') { result += value;

44

Page 45: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Jak dotąd, zastosowaliśmy tylko operator dodawania (+). Po sprawdzeniu poprawnego działania operatora dodawania można dodać inne operatory poprzez użycie kolejnych instrukcji if.Jeśli natomiast zostanie podany niepoprawny operator, poniższa instrukcja:

} else { printf("Nieprawidlowy typ operatora %c\n", operator); }

wyświetli komunikat błędu informujący użytkownika, że wprowadził nieznany operator.

Plik MakefilePo napisaniu kodu źródłowego programu należy go skompilować i skonsolidować. Dotąd kompilator był uruchamiany ręcznie. Taka metoda jest trochę, nużąca i podatna na błędy. Również większe programy składają się z wielu modułów i są znacznie trudniejsze do skompilowania w sposób ręczny. Na szczęście, zarówno pod systemem UNIX, jak i MS-DOS/Windows istnieje program narzędziowy o nazwie make1, który zajmuje się szczegółami kompilacji. Można wykorzystać poniższy przykład jako wzór, a następnie w miejscu nazwy calc wstawić nazwę swojego programu. Program ten szuka pliku o nazwie Makefile, który zawiera opis procesu kompilacji programu i automatycznie uruchamia kompilator.Ponieważ plik Makefile zawiera zasady kompilacji jest on dostosowany do typu kompilatora. Poniżej zawarto zestaw plików Makefile dla wszystkich kompilatorów które będziemy ropatrzywać.

System UNIX [Plik: calcl/makefile.unx] #---------------------------------------------------------# # Plik Makefile dla systemow UNIX # # stosujacych kompilator jezyka GNU C # #---------------------------------------------------------# CC=cc CFLAGS=-g # # Znaczniki kompilatora: # -g -- wlaczenie wykrywania bledow calc1: calc1.c $(CC) $(CFLAGS) -o calc1 calc1.c clean: rm -f calc1

1 W środowisku programowania Microsoft Visual C++ program ten nosi naywę nmake

45

Page 46: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Program narzędziowy make jest odpowiedzialny za jedną z najnieprzyjemniejszych niespodzianek przytrafiających się użytkownikom. Poniższy wiersz:

$(CC) $(CFLAGS) -o calcl calcl.c

musi zaczynać się znakiem tabulacji. Osiem znaków spacji go nie zastąpi. Połączone ze sobą znak spacji i tabulacji również nie pomogą. Wiersz musi rozpoczynać się znakiem tabulacji. Uruchom edytor i upewnij się, ze potrafisz rozróżnić znak tabulacji od kilku spacji.

System UNIX z kompilatorem Free Software Foundation(Fundacja Wolnego Oprogramowania) – gcc

[Plik: calcl/makefile.gcc] #---------------------------------------------------------# # Plik Makefile dla systemow UNIX # # stosujacych kompilator jezyka GNU C # #----------------------------------------------------------# CC=gcc CFLAGS=-g –D__USE_FIXED_PROTOTYPES__ -ansi # # Znaczniki kompilatora: # -g -- wlaczenie wykrywania bledow # -Wall -- wlaczenie wyswietlania wszystkich ostrzezen (nie # stosowane odkad powoduje bledne dzialanie kompilatora) # -D__USE_FIXED_PROTOTYPES__ # -- nakazuje kompilatorowi stosowanie poprawnych naglowkow # -ansi -- Nie uzywa rozszerzen standardu GNU. # Pozostaje przy ANSI C calc1: calc1.c $(CC) $(CFLAGS) -o calc1 calc1.c clean: rm -f calc1

46

Page 47: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Kompilator Borland C++

[Plik: calc1/makefile.bcc] # # Plik Makefile dla kompilatora Borland C++ firmy Borland # CC=bcc # # Znaczniki: # -N -- kontrola przepelnienia stosu # -v -- wlaczenie wykrywania bledow # -w -- wlaczenie wyswietlania ostrzezen # -ml -- ustawia model wielkopamieciowy # CFLAGS=-N -v -w -ml calc1.exe: calc1.c $(CC) $(CFLAGS) -ecalc1 calc1.c clean: erase calc1.exe

Kompilator Turbo C++

[Plik: calc1/makefile. tcc] #----------------------------------------------------------# # Plik Makefile dla systemow MS-DOS # # stosujacych kompilator Turbo C # #----------------------------------------------------------# CC=tcc CFLAGS=-v -w -ml calc1.exe: calc1.c $(CC) $(CFLAGS) -ecalc1.exe calc1.c clean: del calc1.exe

47

Page 48: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Kompilator Visual C++ [Plik: calc1/makefile.msc] #-------------------------------------------------------------------# # Plik Makefile dla systemow MS-DOS stosujacych # # kompilator Microsoft Visual C++ # #------------------------------------------------------------------# CC=cl # # Znaczniki: # AL -- kompilacja dla modelu wielkopamieciowego # Zi -- wlaczenie wykrywania bledow # Wl -- wlaczenie wyswietlania ostrzezen # CFLAGS=/AL /Zi /Wl calc1.exe: calc1.c $(CC) $(CFLAGS) calc1.c clean: erase calc1.exe

Aby skompilować program, wystarczy wpisać polecenie make. Polecenie make określi, które programy w procesie kompilacji są wymagane, po czym je wykona.Program make wykorzystuje datę modyfikacji plików w celu określenia, czy kompilacja jest konieczna. Po wykonaniu kompilacji jest tworzony plik wynikowy. Data modyfikacji pliku wynikowego jest późniejsza niż data modyfikacji odpowiedniego pliku źródłowego. Jeśli plik źródłowy zostanie zmodyfikowany, jest aktualizowana data jego modyfikacji, a plik wynikowy traci ważność. Program make porównuje te daty i jeśli plik źródłowy został zmodyfikowany później niż plik wynikowy, program make ponownie kompiluje plik wynikowy.

TestowaniePo bezbłędnym skompilowaniu programu można przejść do fazy testowania. Na początku należy stworzyć procedurę testującą. Dokument ten jest prostą listą kroków, które należy wykonać, aby mieć pewność, że program działa poprawnie. Można wyróżnić dwa powody, dla których tworzy się procedurę testującą:• Jeśli zostanie wykryty błąd, trzeba potrafić ponownie go wywołać.• Jeśli program zostanie zmodyfikowany, konieczne będzie ponowne

wykonanie procedury testującej w celu upewnienia się, że nowa wersja programu nie wpłynęła niekorzystnie na działanie dowolnego innego fragmentu programu, który do tej pory funkcjonował bez zarzutu.

48

Page 49: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Procedura testowa przyjmie na początku następującą postać:

Wykonaj ponizsze operacje: + 123 Wynikiem powinna być wartość 123 + 52 Wynikiem powinna być wartoścć 175 x 37 Powinien zostać wyświetlony komunikat błędu

Po uruchomieniu programu uzyskamy następujące wyniki:

Wynik: 0 Podaj operator i liczbę: + 123 Wynik: 123 Podaj operator i liczbę: + 52 Wynik: 175 Podaj operator i liczbę: x 37 Wynik: 212

Coś oczywście jest nie w porządku. Po wpisaniu wartości x37 powinien zostać wyświetlony komunikat błędu, ale tak się nie stało. W programie jest błąd, dlatego też rozpoczniemy fazę usuwania błędów. Jedną z zalet początkowego tworzenia małych poprawnie działających prototypów jest możliwość wczesnego wyizolowania napotkanych błęndów.

Wykrywanie błędówPierwszą czynnością, jaką należy wykonać, jest sprawdzenie kodu programu w celu przekonania się, czy jesteśmy w stanie wykryć błąd. W takim małym programie możemy z łatwością zauważyć pomyłkę. Jednakże przypuśćmy, że zamiast 21 wierszy programu mamy do czynienia z programem o wiele większym zawierającym 5000 wierszy. Przeglądanie takiego programu w podobny sposób byłoby znacznie trudniejsze, tak więc najwyższa pora, aby przejść do następnej fazy.Większość systemów zawiera programy wykrywające w języku C błędy. Jednakże każdy z nich jest inny. Niektóre systemy nie posiadają programu wykrywającego błędy. W takim przypadku konieczne będzie wykorzystanie funkcji printf do wyświetlania komunikatów o charakterze diagnostycznym. Technika ta jest prosta. Polega na umieszczeniu funkcji printf w miejscach, w których masz pewność, że dane są pozbawione błędu (ale nie zaszkodzi upewnić się, że są naprawdę prawidłowe). Następnie należy umieścić funkcję printf w miejscach, w których dane zawierają błędy. Uruchom program i kontynuuj wstawianie funkcji printf aż do momentu, gdy zostanie wydzielony fragment programu, w którym występuje błąd. Nasz program po dodaniu diagnostycznych funkcji printf przyjmie następującą postać:

49

Page 50: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

printf("Podaj operator i liczbe: "); fgets(line, sizeof(line), stdin); sscanf("%d %c", &value, &operator); printf("## after scanf %c\n", operator); if (operator +') { print("## after if %c\n", operator); result += value

Znak ## na początku każdej funkcji printf jest używany do wskazania funkcji, która tymczasowo spełnia rolę diagnostyczną. Po zakończeniu fazy wykrywania błędów znak ## pomaga odnaleźć powiązane z nim instrukcje, a następnie usunąć je.

Ponowne uruchomienie programu spowoduje wyświetlenie poniższych wyników:

Wynik: 0 Podaj operator i liczbe: + 123 Wynik: 123 Podaj operator i liczbe: + 52 ## za funkcja scanf + ## za instrukcja 1f + Wynik: 175 Podaj operator i liczbe: x 37 ## za funkcja scanf x ## za instrukcja if + Wynik: 212

W powyższym wydruku możemy zauważyć, że coś jest nie w porządku z instrukcją warunkową if. Z jakiegoś powodu operatorem przed instrukcją if jest x, natomiast za nią jest operator +. Po bliższym przyjrzeniu okazuje się, że został popełniony błąd już omawiany polegający na zastosowaniu znaku = zamiast ==. Po usunięciu tego błędu program działa prawidłowo. Na podstawie tak działającego programu można dodać obsługę innych operatorów takich jak „-”, „*” oraz „/”. Ostateczny kod programu jest zawarty w wydruku 5.2.

50

Page 51: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 5.2 #include <stdio.h> char line[100]; /* zmienna przechowujaca dane wejsciowe */ int result; /* wynik obliczen */ char operator; /* operator podany przez uzytkownika */ int value; /* wartosc podana za operatorem */ int main() { result = 0; /* inicjalizacja zmiennej result */ /* petla wykonywana w nieskonczonosc +/ /* (lub do momentu wykonania instrukcji break) */ while (1) { printf("Wynik: %d\n", result); printf("Podaj operator i liczbe: "); fgets(line, sizeof(line), stdin); sscanf(line, "%c %d", &operator, &value); if ((operator == 'q') || (operator = 'Q')) break; if (operator == '+') { result += value; } else if (operator == '-') { result -= value; } else if (operator == '*') { result *= value; } else if (operator == '/') { if (value == 0) { printf("Błąd:Proba dzielenia przez zero\n"); printf(" operacja zignorowana\n"); } else result /= value; } else { printf("Nieprawidlowy typ operatora %c\n", operator); } } return (0); }

Po uzupełnieniu procedury testującej o nowe operatory wykonujemy ją ponownie. Oto wyniki:

51

Page 52: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

+ 123 Wynikiem powinna być wartość 123+ 52 Wynikiem powinna być wartość 175x 37 Powinien zostać wyświetlony komunikat błędu- 175 Wynikiem powinna być wartość 0+ 10 Wynikiem powinna być wartość 10/ 5 Wynikiem powinna być wartość 2/ 0 Blad dzielenia przez zero* 8 Wynikiem powinna być wartość 16q Program powinien zakończyć działanie

W czasie testowania programu stwierdzamy, że niespodziewanie program działa poprawnie. Słowo “ wstępny ” zostanie usunięte ze specyfikacji, a następnie zostanie ona przygotowana do sprzedaży łącznie z programem i procedurą testową.

KonserwacjaDobrzy programiści przed podjęciem decyzji o sprzedaży pełnej wersji poddają program długim i rygorystycznym procedurom testowym. Po zakupie takiego oprogramowania użytkownik uruchamia je i prawie natychmiast znajduje błąd. Z tym właśnie związany jest etap konserwacji oprogramowania. Na tym etapie błędy są usuwane i program jest testowany (w celu upewnienia się, że poprawki nie wywołały innych problemów), a następnie przekazywany do działu sprzedaży.

AktualizacjaPomimo że program został oficjalnie wydany w pełej wersji, nie oznacza to końca pracy nad nim. Po upływie kilku miesięcy od daty sprzedaży ktoś może zadać następujące pytanie: “ Czy jest możliwe dodanie operatora modułu?”. W takiej sytuacji dokonujemy zmian w specyfikacji, modyfikujemy program, aktualizujemy procedurę testową i ostatecznie ponownie przekazujemy program do sprzedaży.W miarę upływu czasu coraz więcej osób zwraca się z kolejnymi pytaniami dotyczącymi zmian w oprogramowaniu. Wkrótce nasz program posiada funkcje trygonometryczne, regresję liniową, funkcje statystyczne, arytmetykę binarną oraz operacje finansowe. Nasz projekt jest oparty na koncepcji stosowania operatorów jednoznakowych. Jednak niedługo wyczerpie się zapas dostępnych znaków. W tym momencie program wykonuje operacje, które znacznie przekraczają zakres początkowego projektu. Wcześniej czy później znajdziemy się w sytuacji, w której program nie będzie już tak znakomity i konieczne będzie napisanie go od nowa. W takiej sytuacji należy utworzyć wstępną specyfikację i rozpocząc cały proces od początku.

52

Page 53: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Elektroniczna archeologiaElektroniczna archeologia jest sztuk zagłębiania się w kodzie w celu odkrywania zadziwiających rzeczy (w rodzaju – jak i dlaczego program się uruchamia).Niestety większość programistów nie rozpoczyna projektu na etapie projektowania. Zamiast tego są od razu przydzielani do konserwacji lub aktualizacji i muszą zająć się najgorszą z możliwych prac – zrozumieniem i modyfikacją programu napisanego przez kogoś innego.Komputer może w znacznym stopniu pomoć w trafnym odnalezieniu myśli zawartej w kodzie innego programisty. Jest dostępnych wiele narzędzi słuzących do analizowania i formatowania programu źródłowego. Należą do nich:• Odsyłacze – programy te mają nazwy takie jak xref, cxref oraz

cross. System V Unix zawiera program narzędziowy cscope. Odsyłacz wyświetla listę zmiennych i wskazuje, gdzie każda zmienna jest użyta.

• Programy tworzące wcięcia – Programy, takie jak cb oraz indent, otwierają kod programu, a następnie umieszczają wcięcia w prawidłowy sposób (zasady umieszczania prawidłowych wcięć są definiowane przez producenta narzędzia).

• Upiększacze druku – Upiększacz druku, taki jak vgrind lub cprint, w celu wydruku na drukarce laserowej ładuje program źródłowy i informacje dotyczące jego formatowania.

• Wykresy wywołań – W systemie System V Unix program cflow może być zastosowany do analizy programu. W innych systemach można posłużyć się ogólnodostępnym programem narzędziowym calls, który generuje wykresy wywolań. Wykresy wywołań przedstawiają wzajemne wywołania w programie.

Których z powyższych narzędzi używać? Zależy to od własnych potrzeb. Różni programiści inaczej pracują. Niektóre z metod sprawdzania programu rozpatrzymy dalej. Wybierz najbardziej potrzebne narzędzia i po prostu ich używaj.

Redagowanie kodu programuWydrukuj kod źródłowy programu i nanieś na nim swoje uwagi. Użyj w tym celu czerwonego lub niebieskiego atramentu, który pozwoli Ci odróżnić wydruk od naniesionych uwag. Aby podkreślić ważne fragmenty, użyjmy pisaka. Naniesione uwagi są bardzo przydatne. Można ich użyć w programie jako komentarzy, a następnie ponownie wydrukować program i nanieść kolejne uwagi.

53

Page 54: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Zastosowanie debugeraDebuger jest wspaniałym narzędziem pomagającym w zrozumieniu idei działania określonej operacji. Większość debugerów pozwala użytkownikowi wiersz po wierszu przejść program - sprawdzając zmienne i wyjaśniając szczegóły związane z wykonywanymi instrukcjami. Po zapoznaniu się z działaniem programu należy nanieść własne uwagi i zamieścić je w programie w postaci komentarzy.

Edytor tekstu jako przeglądarkaJednym z najlepszych narzędzi służących do zapoznania się z programem napisanym przez innego programistę jest edytor tekstu. Przypuśćmy, że chcemy dowiedzieć, jaką funkcję spełnia zmienna sc. Aby znaleźć pierwsze wystąpienie w kodzie zmiennej sc, należy użyć funkcji szukania. Następnie odszukamy kolejne miejsce, w którym ta zmienna została zastosowana. Kontynuujemy tę operację do momentu, aż będziemy wiedzieć dokładnie, jaką rolę pełni zmienna sc.Załóżmy, że stwierdziłiśmy, że zmienna sc pełni rolę licznika w ciągu. Po otwarciu programu w edytorze tekstu jest możliwa za pomocą funkcji wyszukiwania po całym dokumencie prosta zamiana nazwy zmiennej sc na sequence_counter. (Ostrzeżenie: Przed dokonaniem zamiany upewnimy się, że zmienna sequence_counter nie została już zadeklarowana. Zwrócimy również uwagę na dodatkowe zamiany, takie jak zmiana sc w słowie “escape”).Aby program był zrozumiały, nie możemy zapomnieć o dodaniu przy deklaracji zmiennej komentarza.

Umieszczanie komentarzyNie obawiaj się zamieszczania posiadanych informacji w formie komentarzy niezależnie od ich ilości. Poniżej zawarto niektóre dołączone komentarze:

int state; /* kontroluje okreslony stan maszyny */ int rmxy; /* Czy ma to cos wspolnego z korekcja koloru? */

Natomiast ten komentarz jest trochę zagadkowy:

int idn; /* ??? */

Oznacza to, że nie mam pojęcia, jaką ta zmienna pełni rolę. Nawet wtedy, gdy nie jest znane przeznaczenie zmiennej, taki komentarz będzie oznaczał, że coś jednak trzeba z tym zrobić. W przypadku analizy programu napisanego przez innego programistę, poprzez dodawanie kolejnych komentarzy i poprawianie stylu struktura będzie coraz bardziej wyrazista.

54

Page 55: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Umieszczanie własnych uwag (komentarzy) poprawia czytelność programu i pozwoli kolejnym programistom lepiej go zrozumieć.Załóżmy, że mamy przed sobą poniższy program napisany przez kogoś reprezentującego styl programowania, zwany “im krócej, tym lepiej”. Naszym zadaniem będzie stwierdzenie, do czego ten program słuzy. Najpierw należy nanieść ołówkiem własne komentarze, podobnie jak na rysunku 5.2.

Rysunek 5.2. Zwięźle napisany program

Ten program wymaga kilku poprawek. Po jego przejrzeniu i zastosowaniu zasad wymienionych w tym akapicie w efekcie uzyskamy zrozumiały program z poprawnymi komentarzami, tak jak w wydruku 5.3.

55

Page 56: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 5.3. /** gra guess - prosta zgadywanka * * Zastosowanie: * * Nalezy wybrac losowo liczbe z zakresu od 0 do 100. Gracz ma do dyspozycji * * zakres, z ktorego musi wybrac liczbe. Jesli gracz wybierze prawidlowa liczbe, * * wygrywa. W innym przypadku zakres wartosci jest zmieniany zgodnie * * z liczba wybrana przez gracza nastepnie gra jest kontynuowana. * * Ograniczenia: * * Liczba losowa jest generowana przez instrukcje rand() % 100. Poniewaz * * funkcja rand() zwraca liczbe z zakresu 0 <= rand() <= maxint w efekcie * * czesciej moga wystepowac liczby mniejsze. */ #include <stdio.h> #include <stdlib.h> int number_to_guess; /* odgadywana liczba losowa */ int low_limit; /* aktualna dolna wartosc zakresu dla gracza */ int high_limit; /* aktualna gorna wartosc zakresu dla gracza */ int guess_count; /* liczba trafien gracza */ int player_number; /* liczba podana przez gracza */ char line[80]; /* zmienna przechowujaca dane wejsciowe */ int main() { while (1) { /* nie do konca prawdziwa liczba losowa, patrz - ograniczenia */ number_to_guess = rand() % 100 + 1; /* inicjalizacja zmiennych petli */ low_limit = 0; high_limit = 100; guess_count = 0; while (1) { /* podanie graczowi wartosci zakresu i pobranie od niego liczby */ printf("Zakres %d - %d\n", low_limit, high_limit); printf("Wartosc[%d]? ", guess_count); ++guess_count; fgets(line, sizeof(line), stdin); sscanf(line, "%d", &player_number); /* czy gracz trafil? */ if (player_number == number_to_guess) break; /* dopasowanie zakresu do nastepnej proby */ if (player_number < number_to_guess) low_limit = player_number; else high_limit = player_number; } printf("Trafione\n"); }

56

Page 57: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

}6

Dodatkowe instrukcje sterujące

Instrukcja forInstrukcja for umożliwia programiście wykonywanie określoną ilość razy segmentu programu źródłowego. Ogóna postać instrukcji for została zawarta poniżej:

for (wyrażenie początkowe; warunek; iteracja) zawartość instrukcji;

Instrukcja ta jest równoważna poniższej:

wyrażenie początkowe; while (warunek) { zawartość instrukcji; iteracja; }

Na przykład w wydruku 6.1 w celu dodania pięciu liczb została użyta pętla while.

Wydruk 6.1.

#include <stdio.h> int total; /* suma wszystkich liczb */ int current; /* aktualna wartosc podana przez uzytkownika */ int counter; /* licznik petli while */ char line[80]; /* zmienna przechowujaca dane wejsciowe */ int main() { total = 0; counter = 0; while (counter < 5) { printf("Jak liczba? "); fgets(line, sizeof(line), stdin); sscanf(line, "%d", &current); total += current; ++counter; } printf("Suma calkowita wynosi: %d\n", total); return (0); }

57

Page 58: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Ten sam program stosujący instrukcję for może być napisany jeszcze raz. Został on zawarty w wydruku 6.2.

Wydruk 6.2. #include <stdio.h> int total; /* suma wszystkich liczb */ int current; /* aktualna wartosc podana przez uzytkownika */ int counter; /* licznik petli while */ char line[80]; /* zmienna przechowujaca dane wejsciowe */ int main() { total = 0; for (counter = 0; counter < 5; ++counter) { printf("Jaka liczba? "); fgets(line, sizeof(line), stdin); sscanf(line, "%d", &current); total += current; } printf("suma calkowita wynosi: %d\n", total); return (0); }

Należy zauważyć, że zmienna counter przyjmuje wartości w zakresie 0 - 4. Zwykle pięć elementów jest liczone kolejno 1, 2, 3, 4, 5, ale programowanie w języku C będzie o wiele lepszej jakości, jeśli liczenie będzie rozpoczynane od zera, po czym kolejnym elementom będą odpowiadały liczby 1, 2, 3, 4. Liczenie począwszy od jedynki jest głównym powodem występowania błędów przepełnienia tablicy. Dokładne sprawdzenie dwóch odmian powyższego programu uwidoczni podobieństwa występujące pomiędzy nimi. Wiele innych języków programowania nie pozwala zmieniać wewnątrz pętli zmiennej sterującej (w tym przypadku zmiennej counter). Język C nie ma takich ograniczeń. Można w dowolnej chwili zmieniać zmienną sterującą, jak również wychodzić poza pętlę i wracać do niej. Ogólnie rzecz ujmując, jest możliwe wykonywanie operacji, które w przypadku języków PASCAL oraz FORTRAN byłyby nieosiągalne. Pomimo że język C daje wolną rękę w wykonywaniu tak szalonych operacji, to jednak nie oznacza to, że należy je realizować.

Pytanie 6.1. Po uruchomieniu programu w wydruku 8.3 jest wyświetlany następujący komunikat:

Stopnie w skali Celsjusza:101 Stopnie w skali Fahrenheita:213

i nic poza tym. Dlaczego tak się dzieje?

58

Page 59: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 6.3 #include <stdio.h> /** Program generuje wykres konwersji stopni w skali Celsjusza * na stopnie w skali Fahrenheita dla wartosci od 0 do 100. * Aktualna wartosc temperatury w skali Celsjusza */ int celsius; int main() { for (celsius = 0; celsius <= 100: ++celsius); printf("Stopnie w skali Celsjusza:%d Stopnie w skali Fahrenheita:%d\n", celsius, (celsius * 9) / 5 + 32); return (0); }

Odpowiedź 6.1. Problem jest związany ze średnikiem znajdującym się na końcu instrukcji for. Zawartość instrukcji for znajduje się pomiędzy nawiasami okrągłymi i średnikiem. W takim przypadku zawartość taka nie jest widoczna, nawet pomimo że funkcja printf jest wcięta, ale nie jest częścią instrukcji for. Wcięcie to jest mylące. Kompilator języka C nie zwraca uwagi na wcięcia. Program nie wykona żadnej operacji aż do momentu, gdy poniższe wyrażenie będzie miało wartość false (celsius = = 101).

celsius <= 100

Wtedy też zostanie wykonana funkcja printf.

Pytanie 6.2. W wydruku 6.4 program wczytuje listę pięciu liczb i oblicza liczbę wystąpień w pobranych danych cyfry 3 i 7. Dlaczego jest wyświetlany nieprawidłowy wynik?Wydruk 6.4 #include <stdio.h> char line[100]; /* dane wejsciowe */ int seven_count; /* ilosc siodemek w danych */ int data[5]; /* dane zawierajace liczbe 3 i 7 */ int three_count; /* ilosc trojek w danych */ int index; /* indeks danych */ int main() { seven_count = 0; three_count = 0; printf ("Podaj 5 liczb\n"); fgets(line, sizeof(line), stdin); sscanf(line, "%d %d %d %d %d", &data[1], &data[2], &data[3], &data[4], &data[5]); for (index = 1; index <= 5; ++index) { if (data[index] == 3) ++three_count; if (data[index] == 7) ++seven_count; } printf("Trojek: %d Siodemek: %d\n", three_count, seven_count); return (0); }

59

Page 60: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Po uruchomieniu programu i podaniu liczb: 3 7 3 0 2 zostanie wyświetlony następujący wynik:

Trojek: 4 Siodemek: 1

Odpowiedź 6.2. Problem wynika stąd, że liczba jest przypisywana zmiennej data[1] za pośrednictwem zmiennej data[5]. W języku C zakres prawidłowych wartości indeksu tablicy zawiera się od 0 do wartości wymiar-tablicy-1 lub tak jak w tym przypadku od 0 do 4. Zmienna data[5] jest nieprawidłowa. Po jej zastosowaniu program może się dziwnie zachowywać. W tym przypadku wartość zmiennej three_count została zmieniona. Rozwiązanie tego problemu polega na zastosowaniu tylko wartości zmiennych od data[0] do data[4]. W tym celu trzeba odpowiednio zmodyfikować funkcję sscanf:

sscanf (line, "%d %d %d %d %d",&data[0], &data[l], &data[2], &data[3], &data[4]);

Należy również dokonać edycji pętli for z postaci:

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

na postać:

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

Doświadczeni programiści języka C powinni przyjrzeć się powyższej pętli for. Muszą natychmiast zorientować się, że coś jest nie tak. Można wyróżnić dwa symptomy potwierdzające takie przypuszczenie:1. Pętla for zaczyna się od wartości indeksu równej 1.2. W pętli występuje operator <=, a w języku C większość pętli for zaczyna się wartością indeksu równa 0. W celu jej zakończenia jest używany operator <.

Instrukcja switchInstrukcja switch jest podobna do szeregu instrukcji if/else.Ogólna postać instrukcji switch jest następująca:

switch (wyrażenie) { case stała1: instrukcja .... break; case stała2: instrukcja ....

60

Page 61: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* przeskok */ default: instrukcja .... break; case stała3: instrukcja .... break; }

Instrukcja switch wyznacza wartość wyrażenia i przechodzi do jednej z etykiet case. Duplikaty etykiet są niedozwolone, tak więc tylko jedna etykieta case zostanie wybrana. Wyrażenie musi wyznaczyć wartość typu całkowitego, znakowego lub wyliczeniowego.Etykiety case mogą być ustawione w dowolnej kolejności, ale muszą być stałymi. Etykieta default może być umieszczona w dowolnym miejscu instrukcji switch. Żadne dwie etykiety case nie mogą zawierać identycznej wartości.W momencie, gdy język C napotka instrukcję switch, wyznacza wartość wyrażenia, a następnie szuka etykiety case z nią zgodnej. Jeśli taka etykieta nie zostanie znaleziona, jest używana etykieta default. Jeżeli natomiast nie zdefiniowano etykiety default, instrukcja nie wykonuje żadnej operacji.Instrukcja switch jest bardzo podobna do instrukcji języka PASCAL - case. Główna różnica polega na tym, że język PASCAL pozwala na umieszczenie za etykietą tylko jednej instrukcji, a język C dopuszcza wiele instrukcji. Język C będzie wykonywał instrukcje do momentu napotkania instrukcji break. W języku PASCAL nie jest możliwe przejście z jednej etykiety case do następnej, natomiast w języku C tak.Kolejną różnicą pomiędzy instrukcją switch języka C a instrukcją case języka PASCAL jest to, że język PASCAL wymaga umieszczenia na końcu instrukcji default. W języku C instrukcja default może znajdować się w dowolnym miejscu.Wydruk 6.5 zawiera szereg instrukcji if oraz else:

61

Page 62: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 6.5. Skladnia instrukcji if oraz else if (operator = '+') { result += value; } else if (operator == '-') { result -= value; } else if (operator == '*') { result *= value; } else if (operator == '/') { if (value == 0) { printf ("Blad: Dzielenie przez zero\n"); printf (" operacja zignorowana\n"); } else result /= value; } else { printf ("Nieprawidlowy typ operatora %c\n", operator); }

Powyższy fragment programu może zostać z łatwością przepisany z zastosowaniem instrukcji switch. W instrukcji tej zostanie użyta dla każdej operacji inna etykieta case. Klauzula default będzie odpowiedzialna za obsługę wszystkich nieznanych operatorów.Przepisanie kodu programu z zastosowaniem instrukcji switch nie tylko go uprości, ale również stanie się on przez to bardziej czytelny. Zmodyfikowany program calc został zawarty w wydruku 6.6.

Wydruk 6.6. #include <stdio.h> char line[100]; /* zmienna przechowujaca dane wejsciowe */ int result; /* wynik obliczen */ char operator; /* operator podany przez uzytkownika */ int value; /* wartosc podana za operatorem */ int main() { result = 0; /* inicjalizacja zmiennej result */ /* petla wykonywana w nieskonczonosc (lub do momentu wykonania instrukcji break) */

62

Page 63: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

while (1) { printf("Wynik: %d\n", result); printf("Podaj operator i liczbe: "); fgets(line, sizeof(line), stdin); sscanf(line, "%c %d", &operator, &value); if ((operator == 'q') ׀׀ (operator == 'Q')) break; switch (operator) { case '+': result += value; break; case '-'; result -= value; break; case '*': result *= value; break; case '/': if (value == 0) { printf("Blad: Dzielenie przez zero\n"); printf (" operacja zignorowana\n"); } else result /= value; break; default: printf("Nieprawidlowy typ operatora %c\n", operator); break; } } return (0); }

Instrukcja break znajdująca się wewnątrz instrukcji switch informuje komputer, aby za instrukcją switch kontynuował wykonywanie programu. Jeśli instrukcja break nie występuje, zostanie wykonana następna instrukcja.

Na przykład w poniższym kodzie:

63

Page 64: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

control = 0; /* nie zalecany sposob programowania */ switch (control) { case 0: printf("Wyzerowanie\n"); case 1: printf("Inicjalizacja\n"); break; case 2: printf("Przetwarzanie\n"); }

W tym przypadku, gdy zmienna control == 0, program wyświetli następujący komunikat:

Zerowanie Inicjalizacja

Etykieta case 0 nie jest zakończona instrukcją break. Po wyświetleniu komunikatu Zerowanie program przejdzie do następnej instrukcji (etykieta case 1), a następnie wyświetli komunikat Inicjalizacja.W powyższym kodzie występuje problem ze składnią. Nie można stwierdzić, czy program przeskoczy z etykiety case 0 do etykiety case 1 ani czy programista nie zapomni umieścić instrukcji break. Aby zapobiec takiej sytuacji, sekcja etykiety case zawsze powinna być zakończona instrukcją break lub komentarzem /* Przeskok */. Zostało to pokazane w poniższym wydruku:

/* bardziej zalecany sposob programowania */ switch (control) { case 0: printf("Wyzerowanie\n"); /* Przeskok */ case 1: printf("Inicjalizacja\n"); break; case 2: printf("Przetwarzanie\n"); }

64

Page 65: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Ze względu na fakt, że etykieta case 2 występuje jako ostatnia, nie jest w niej wymagane umieszczenie instrukcji break. Instrukcja break spowoduje przeskoczenie na koniec instrukcji switch, czyli w miejsce, w którym już się znaleźliśmy.Przypuśćmy, że nieznacznie zmodyfikujemy program i dodamy do instrukcji switch kolejną etykietę case:

/* program zawierajacy niewielki blad */ switch (control) { case 0: printf("Wyzerowanie\n"); /* Przeskok */ case 1: printf("Inicjalizacja\n"); break; case 2: printf("Przetwarzanie\n"); case 3: printf("Zakonczenie\n"); }

Po przypisaniu zmiennej control wartości 2 program wyświetli następujący komunikat:

Przetwarzanie Zakończenie

Taki wynik nie jest miłym zaskoczeniem. Problem wynika stąd, że etykieta case 2 nie jest już ostatnią etykietą case. W tym miejscu dokonujemy przeskoku. (W sposób niezamierzony - w innej sytuacji należałoby umieścić komentarz /* Przeskok */). Instrukcja break jest teraz wymagana. Jeśli instrukcja break będzie zawsze stosowana, nie trzeba się zastanawiać, czy jest ona naprawdę potrzebna.

65

Page 66: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* to juz jest prawie to */ switch (control) { case 0: printf("Wyzerowanie\n"); /* Przeskok */ case 1: printf("Inicjalizacja\n"); break; case 2: printf("Przetwarzanie\n"); break; }

Na sam koniec zadamy następujce pytanie: “Co się stanie, gdy zmienna control przyjmie wartość 5?". W takim przypadku ze względu na fakt, że nie istnieją zgodne klauzule case lub default, cała instrukcja switch zostanie pominięta.W tym wydruku programista nie dołączył instrukcji default, ponieważ zmienna control nigdy nie przyjmie wartości innych niż 0, 1 lub 2. Jednakże zmiennym można przypisywać dziwne wartości, tak więc aby temu zapobiec, należy zastosować bardziej ograniczony styl programowania. Styl taki został zaprezentowany w poniższym wydruku:

/* Ostateczna wersja */ switch (control) { case 0: printf("Wyzerowanie\n"); /* Przeskok */ case 1: printf("Inicjalizacja\n"); break; case 2: printf("Przetwarzanie\n"); break; default: printf("Blad wewnetrzny, wartosc kontrolna (%d) nieznana\n", control); break; }

66

Page 67: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Pomimo że klauzula default nie jest wymagana, to jednak powinna być dodana do każdej instrukcji switch. Nawet mimo tego że klauzula default może mieć poniższą postać,

default: /* nic nie robi */ break;

to jednak powinna być dołączona. Przynajmniej metoda ta informuje, że dane o wartościach poza zakresem będą ignorowane.

Instrukcja switch, break oraz continueInstrukcja break jest stosowana na dwa sposoby. Zamieszczona wewnątrz instrukcji switch powoduje, że wykonywanie programu przechodzi na koniec instrukcji switch. Jeśli natomiast instrukcja break zostanie zawarta wewnątrz pętli for lub while, zakończy jej wykonywanie. Instrukcja continue może być stosowana tylko wewnątrz pętli. Instrukcja ta spowoduje przejście wykonywanego programu na koniec pętli. Rysunek 6.2 pokazuje zarówno instrukcję continue, jak i break zawartą wewnątrz instrukcji switch.Kod programu pokazany na rysunku 6.2 ma za zadanie wykonanie konwersji liczby typu całkowitego wyrażonej w kilku różnych systemach na liczby wyrażone w innych systemach. Aby określić wartość liczby w systemic ósemkowym, należy wpisać znak o (skrót od octal (ósemkowy)), a następnie podać samą liczbę. Polecenie q kończy działanie programu. Przykład uruchomienia programu został zawarty poniżej:

Podaj typ konwersji oraz liczbe: o 55 Wynikiem jest liczba 45 Podaj typ konwersji i liczbe: q

Polecenie help ma specjalne zastosowanie w przypadku, gdy za poleceniem nie ma być wyświetlona liczba. Mimo wszystko w wyniku wykonania polecenia help zostanie wyświetlonych kilka wierszy tekstu, ale bez liczb. Instrukcja continue jest stosowana wewnątrz instrukcji switch w celu rozpoczęcia pętli. Wewnątrz instrukcji switch instrukcja continue jest wykonywana w pętli, natomiast instrukcja break należy do bloku instrukcji switch.Istnieje również instrukcja break znajdująca się poza blokiem instrukcji switch, która umożliwia użytkownikowi zakończenie działania programu. Na rysunku 6.2 został zawarty schemat działania programu.

67

Page 68: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Rysunek 6.2. Instrukcje switch/continue

68

Page 69: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

7Zakres zmiennych i funkcje

Zakres i klasa zmiennejWszystkie zmienne posiadają dwa atrybuty - zakres i klasę. Zakres zmiennej jest obszarem programu, w którym zmienna może być stosowana. Zmienna globalna może być zastosowana w dowolnym miejscu (stąd nazwa - globalna), a zatem jej zakresem jest cały program.Zmienna lokalna posiada natomiast zakres, który jest ograniczony do bloku, gdzie została zadeklarowana i nie może być używana poza nim. Blok jest fragmentem programu zawartym w nawiasach klamrowych ({}). Rysunek 7.1 pokazuje różnicę pomiędzy zmienną lokalna i globalną.

Rysunek 7.1. Zmienne lokalne i globalne

Zmienna lokalna może być zadeklarowana z identyczną nazwą, jaką posiada zmienna globalna. Zwykle zakresem zmiennej count (pierwsza deklaracja) byłby cały program. Deklaracja drugiej zmiennej lokalnej count ma pierwszeństwo przed deklaracją zmiennej globalnej znajdującej się wewnątrz niewielkiego bloku, w którym została zadeklarowana zmienna lokalna count. W tym bloku zmienna globalna count jest ukryta. Można również zagnieżdżać deklaracje zmiennych lokalnych oraz ukrywać takie zmienne. Rysunek 7.2 pokazuje zmienną ukrytą.Zmienna count została zadeklarowana zarówno jako zmienna lokalna, jak i zmienna globalna. Zwykle zakresem zmiennej globalnej count jest cały program, jednakże w momencie deklaracji zmiennej wewnątrz bloku ten egzemplarz zmiennej staje się aktywną zmienną na obszarze całego bloku. Na obszarze tego bloku zmienna globalna count została ukryta przez zmienną lokalną count. Wyróżniony na rysunku blok pokazuje, w którym miejscu zakres zmiennej globalnej count jest ukryty.

69

Page 70: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Problem pojawia się w momencie, gdy zostanie dodana poniższa instrukcja:

count = 1;

Nie można w prosty sposób stwierdzić, do której zmiennej count powyższa instrukcja się odnosi. Czy jest to zmienna globalna count, czy może zmienna zadeklarowana powyżej funkcji main lub może zmienna znajdująca się w wewnątrz pętli while? W takiej sytuacji należy nadać tym zmiennym różne nazwy, takie jak total_count, current_count oraz item_count.

Rysunek 7.2. Zmiennukryte

Klasa zmiennej może być stała lub tymczasowa. Zmienne globalne są zawsze stałe. Są one deklarowane i inicjalizowane przed uruchomieniem programu oraz istnieją aż do jego zakończenia. Zmienne tymczasowe są pobierane z fragmentu pamięci zwanego stosem (ang. stack) znajdującego się na początku bloku. Jeśli zostanie pobranych zbyt wiele zmiennych tymczasowych, pojawi się błąd przepełnienia stosu. Miejsce zajmowane przez zmienne tymczasowe po osiągnięciu końca bloku jest zwalniane do stosu. Zmienne tymczasowe są inicjalizowane za każdym razem, gdy następuje wejście do bloku.Rozmiar stosu zależy od systemu operacyjnego i stosowanego kompilatora. W przypadku wielu systemów UNIX dla programu jest automatycznie przydzielany stos o największym dostępnym rozmiarze. W innych systemach domyślny przydzielany rozmiar stosu jest ustawiany za pomocą przełącznika kompilatora. W systemach MS-DOS/Windows rozmiar stosu musi być mniejszy niż 65 536 bajtów. Pozornie może wydawać się, że jest to spora ilość miejsca, jednakże kilka większych tablic potrafi taką przestrzeń dość

70

Page 71: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

szybko wykorzystać. W związku z tym należy wziąć pod uwagę tworzenie wszystkich dużych tablic jako tymczasowych.Zmienne lokalne są zawsze tymczasowe, chyba że zostaną zadeklarowane przy użyciu słowa kluczowego static.Słowo kluczowe static ma zupelnie odmienne znaczenie w sytuacji, gdy jest stosowane w połączeniu ze zmiennymi globalnymi. Oznacza ono, że zmienna jest zmienną lokalną aktualnego pliku. Wydruk 7.1 pokazuje różnicę pomiędzy zmiennymi stałymi i tymczasowymi. Nazwy zmiennych zostały dobrane w sposób oczywisty: temporary jest zmienną tymczasowa natomiast permanent jest zmienną stałą. Zmienna temporary jest inicjalizowana za każdym razem, gdy jest tworzona (na początku bloku instrukcji for). Zmienna permanent jest inicjalizowana tylko raz w momencie uruchomienia programu.

Wydruk 7.1.

#include <stdio.h>int main() {

int counter; /* licznik petli */for (counter = 0; counter < 3; ++counter) {

int temporary = 1; /* zmienna tymczasowa */static int permanent = 1; /* zmienna stala */

printf("Wartosc tymczasowa %d Wartosc stala %d\n",temporary, permanent);

++temporary;++permanent;}return (0);}

Wewnątrz pętli obydwie zmienne są inkrementowane. Jednakże na początku pętli zmiennej temporary została przypisana wartość 1. Jest to widoczne w wydruku 7.1.Wynikiem działania programu jest następujący komunikat:

Wartosc tymczasowa 1 Wartosc stala 1Wartosc tymczasowa 1 Wartosc stala 2Wartosc tymczasowa 1 Wartosc stala 3

Zmienne tymczasowe są czasem określane terminem zmiennych automatycznych, ponieważ jest im przydzielane miejsce w sposób automatyczny. Kwalifikator auto może być zastosowany do zaznaczenia

71

Page 72: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

zmiennej tymczasowej, jednak w praktyce nie jest to prawie w ogóle stosowane.

Tabela 7.1 zawiera opis różnych sposobów deklaracji zmiennej.

Tabela 7.1. Modyfikatory deklaracji zmiennej

Miejsce deklaracji Zakres Klasa Ilość inicjalizacjiPoza wszystkimi blokamistatic - poza wszystkimi blokamiWewnątrz bloku

static - wewnątrz bloku

GlobalnyGlobalny1

Lokalny

Lokalny

StałaStała

Tymczasowa

Stała

RazRaz

Przy każdym wejściu do blokuRaz

FunkcjeFunkcje umożliwiają grupowanie często wykorzystywanego programu źródłowego w postaci zwartego modułu, który może być następnie wielokrotnie stosowany. Mieliśmy już do czynienia z jedną funkcją - main. Jest to specjalna funkcja wywoływana na początku programu. Wszystkie pozostałe funkcje są w sposób bezpośredni lub pośredni wywoływane z funkcji main.Załóżmi, że tworzymy program, który obliczy pole powierzchni trzech trójkątów. Moglibyśmy zapisać odpowiedni wzór trzy razy lub utworzyć funkcję realizującą to zadanie. Każda funkcja powinna się rozpoczynać blokiem komentarza zawierającego następujące pola:

NazwaNazwa funkcji

OpisOpis operacji wykonywanych przez funkcję

ParametryOpis każdego parametru funkcji

Wartość zwracanaOpis wartości zwracanej przez funkcję

Dodatkowe pola, takie jak format plików, odniesienia lub uwagi, mogą być dołączone.

1 Deklaracja z zastosowaniem słowa kluczowego static zamieszczona na zewnątrz bloków oznacza, że zmienna jest zmienną lokalną pliku, w którym została ona zdeklarowana.

72

Page 73: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Funkcja obliczająca pole powierzchni trójkąta rozpoczyna się w następujący sposób:

/************************************************************** Funkcja trojkat -- oblicza pole powierzchni trojkata ** ** Parametry: ** width -- szerokosc trojkata ** height -- wysokosc trojkata ** Wartosc zwracana: ** pole powierzchni trojkata **************************************************************/

Funkcja w poprawny sposób zaczyna się wierszem:

float triangle (float width, float height)

Słowo kluczowe float określa typ funkcji. Dwoma parametrami funkcji są parametr width oraz height. Również one są typu float.Język C stosuje typ przekazywania parametrów określany terminem „wywołanie po wartości". Po wywołaniu funkcji triangle poprzez zastosowanie poniższego programu,

triangle (1.3, 8.3);

język C podstawia wartości parametrów (w tym przypadku 1.3 oraz 8.3) do parametrów funkcji (width oraz height), a następnie rozpoczyna wykonywanie kodu funkcji. W przypadku takiej formy przekazywania parametrów funkcja nie może za pośrednictwem parametrów zwracać danych do instrukcji ją wywołującej2. W języku C określanie typu funkcji nie jest konieczne. Jeśli typ funkcji nie został zadeklarowany, zostanie zastosowany typ domyślny int. Jednakże w takiej sytuacji nie jest możliwe określenie, czy ma być użyty typ domyślny (int) czy po prostu programista zapomniał zadeklarować typ. Aby uniknąć takich niejednoznaczności zawsze, należy określić typ funkcji i nie stosować typu domyślnego.Funkcja oblicza pole powierzchni poprzez wykonanie poniższej instrukcji:

area=width*height/2.0;

Zmienna znajdująca się po lewej stronie przechowuje wartość zwracaną. Przekazanie wartości zwracanej jest realizowane przy użyciu następującej instrukcji:

2 Stwierdzenie to nie jest do końca prawdziwe. Jak dowiemy  się rozdziale 13., „Wskaźniki proste", poprzez zastosowanie pewnych sztuczek można sprawić, że język C po zastosowaniu wskaźników przekaże z powrotem informacje.

73

Page 74: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

return (area);

Wydruk 7.2 zawiera kompletny kod funkcji triangle.

Wydruk 7.2.

#include <stdio.h>/************************************************************** Funkcja trojkat -- oblicza pole powierzchni trojkata ** Parametry: ** width -- szerokosc trojkata ** height -- wysokosc trojkata ** Wartosc zwracana: ** pole powierzchni trojkata **************************************************************/float triangle(float width, float height){

float area; /* pole powierzchni trojkata */area = width * height / 2.0;return (area);}

Poniższy wiersz,

size = triangle (1.3, 8.3);

powoduje wywołanie funkcji triangle. Język C przypisuje wartość 1.3 parametrowi width oraz wartość 8.3 parametrowi height.

Jeśli funkcje odpowiadałaby pokojom w naszym mieszkaniu, parametry byłyby drzwiami pomiędzy nimi. W tym przypadku wartość 1.3 jest przekazywana poprzez drzwi o nazwie width. Drzwi określające parametry otwierają się tylko w jedną stronę. Możliwe jest tylko wejście przez nie. Instrukcja return jest stosowana w celu przekazania wartości zwracanej na zewnątrz funkcji. W przypadku funkcji triangle zmiennej lokalnej area jest przypisywana wartość 5.4, a następnie jest wykonywana instrukcja return (area);. Wartość zwracana tej funkcji ma wartość 5.4. Zatem poniższa instrukcja,

size = triangle (1.3, 8.3);

spowoduje przypisanie zmiennej size wartości 5.4.

W wydruku 7.3 jest obliczane pole powierzchni trzech trójkątów.

74

Page 75: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 7.3. #include <stdio.h>/************************************************************** Funkcja trojkat -- oblicza pole powierzchni trojkata ** Parametry: ** width -- szerokosc trojkata ** height -- wysokosc trojkata ** Wartosc zwracana: ** pole powierzchni trojkata ***************************************************************float triangle(float width, float height){

float area; /* pole powierzchni trojkata */area=width*height/2.0;return (area);

}int main(){

printf("Trojkat #1 %f\n"', triangle(1.3, 8.3));printf("Trojkat #2 %f\n"', triangle(4.8, 9.8));printf("Trojkat #3 %f\n"', triangle(1.2, 2.0));return (0);

}

Jeśli zaistnieje potrzeba zastosowania funkcji, zanim zostanie ona zdefiniowana, należy w celu poinformowania o niej kompilatora zadeklarować ją w podobny sposób, jak ma to miejsce w przypadku zmiennej. W tym celu w funkcji triangle trzeba zastosować poniższą deklarację:

/* oblicza pole powierzchni trojkata */float triangle (float width, float height);

Deklaracja ta jest określana terminem prototypu funkcji. W momencie deklaracji prototypu funkcji nazwy zmiennych nie są wymagane. Powyższy prototyp funkcji może być swobodnie zapisany w takiej postaci:

float triangle (float, float);

Jednakże stosowanie dłuższej postaci deklaracji prototypu zawiera więcej informacji, a poza tym tworzenie prototypów jest bardzo proste przy użyciu operacji Wytnij i Wklej dostępnych w edytorze tekstu.Mówiąc wprost, stosowanie prototypów w przypadku niektórych funkcji jest opcjonalne. Jeśli nie zdefiniowano żadnego prototypu, kompilator języka C zakłada, że funkcja zwraca wartośc typu int i pobiera dowolną liczbę

75

Page 76: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

parametrów. Pominięcie deklaracji prototypu pozbawia kompilatora języka C wartościowej informacji, którą mógłby się posłużyć w celu sprawdzenia wywołań funkcji. Większość kompilatorów posiada przełącznik czasu kompilacji, który ma za zadanie ostrzegać programistę o wywołaniach funkcji nie posiadających prototypów.

Funkcje bez parametrówFunkcja może zawierać dowolną liczbę parametrów lub żadnego. Nawet wtedy, gdy funkcja nie posiada parametrów nadal, należy stosować nawiasy okrągłe:

value = next_index();

Deklaracja prototypu funkcji bez parametrów wiąże się z zastosowaniem pewnej sztuczki. Nie można wprost zastosować poniższej instrukcji,

int_next_index();

poniewaz kompilator języka C po wykryciu pustych nawiasów okrągłych stwierdzi, ze ma do czynienia z deklaracją funkcji nawiązującej do stylu K&R (od nazwisk twórców Kernigham-Ritchie). Słowo kluczowe void służy do wskazania pustej listy parametrów. Prototyp funkcji next_index przyjmie teraz następującą postać:

int next_index (void);

Słowo kluczowe void jest również stosowane w celu wskazania, że funkcja nie zwraca wartości. (Słowo kluczowe void jest podobne do podprogramu języka FORTRAN lub procedury w języku PASCAL). Na przykład ponizsza funkcja wyświetla wynik, ale nie zwraca wartości:

void print_answer (int answer){

if (answer < 0) {printf ("Wynik jest niepoprawny\n");return;

}printf ("Wynikiem jest %d\n", answer);

}Pytanie 7.1. W wydruku 7.4 powinna zostać wyliczona długość łańcucha3, ale zamiast tego program usilnie twierdzi, ze wszystkie łańcuchy mają zerową długość. Dlaczego tak jest?Wydruk 7.43 Funkcja wykonuje tę samą operację, co funkcja standardowa strlen.

76

Page 77: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/******************************************************************** Pytanie brzmi: ** Dlaczego ten program zawsze stwierdza, ze ** dlugosc dowolnego lancucha ma wartosc 0? ** ** Ponizej utworzono przykladowa funkcje main. ** Jej zadaniem bedzie wczytanie lancucha, a nastepnie ** wyswietlenie jego dlugosci. *********************************************************************/

#include <stdio.h>/******************************************************** Funkcja length -- oblicza dlugosc lancucha **Parametry: ** string - lancuch, ktorego dlugosc ** zostanie obliczona **Wartosc zwracana: ** dlugosc lancucha *********************************************************/

int length(char string[ ]){

int index; /* indeks wewnatrz lancucha *//*Petla jest wykonywana do momentu osiagniecia konca lancucha*/for (index = 0; string[index] != '\0'; ++index)

/* nic nie robi */return (index);

}int main() {

char line[100]; /* dane wprowadzone przez uzytkownika */while (1) {

printf("Wpisz tekst:");fgets(line, sizeof(line), stdin);

printf("Dlugosc wynosi (lacznie ze znakiem nowego wiersza): %d\n", length(line)); }}Odpowiedź 7.1. Wyjaśnienie, że pętla for nie wykonywała żadnej operacji (oprócz inkrementacji wartości indeksu), kosztowało programistę mnóstwo kłopotów. Na końcu pętli for brakuje średnika (;). Język C będzie przetwarzał program do momentu napotkania instrukcji return (index), po czym dołączy ją do pętli for. Poprawna postać programu została zawarta w wydruku 7.5.

77

Page 78: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 7.5

#include <stdio.h>int length(char string[ ]){

int index; /* indeks wewnatrz lancucha *//** Petla jest wykonywana do momentu osiagniecia konca lancucha

*/for (index = 0; string[index] != '\0'; ++index)

continue; /* nic nie robi */return (index);

}int main(){

char line(100]; /* dane wprowadzone przez uzytkownika */while (1) {

printf("Wpisz tekst:");fgets(line, sizeof(line), stdin);

printf("Dlugosc wynosi (lacznie ze znakiem nowego wiersza) : %d\n", length(line));}}

Programowanie strukturalneInżynierowie oprogramowania, doskonaląc techniki programowania, spędzają przy tym mnóstwo czasu i kosztuje ich to sporo wysiłku. Wynikiem tych działań jest określenie w najlepszym tego słowa znaczeniu optymalnej metodologii programowania - w każdym miesiącu innej. Niektóre z tych opracowań uwzględniają schematy blokowe, programowanie zstępujące (ang. top-down programming) i wstępujące (ang. bottom-up programming), programowanie strukturalne oraz projektowanie zorientowane obiektowo (ang. Object Oriented Designing - OOD).Po zapoznaniu się z funkcjami można przejść do zagadnień związanych z technikami programowania strukturalnego służących do tworzenia programów. Techniki te mają za zadanie podział lub strukturyzację programu na małe precyzyjnie określone funkcje. Sprawiają one, że program jest łatwiejszy do napisania, a ponadto jest bardziej zrozumiały. Nie chcę przez to powiedzieć, że taka metoda jest jedyną słuszną propozycją dla programisty. Tak się jednak składa, że sprawdza się ona najlepiej.

78

Page 79: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Pierwszy krok, jaki trzeba wykonać w procesie programowania, polega na określeniu docelowych funkcji wykonywanych przez program. Następnie należy zdefiniować strukturę programu.Po wykonaniu tych czynności można rozpocząć kodowanie. W przypadku gdy jest tworzony dokument, zwykle zaczyna się od układu każdej jego części zaznaczonej przez pojedyncze zdanie. Pozostała ich zawartość zostanie uzupełniona później. Tworzenie programu jest podobnym procesem. Rozpoczyna się od projektu, który staje się następnie główną funkcją programu. Szczegóły zostaną ukryte wewnątrz innych funkcji. W wydruku 7.6 zostały rozwiązane wszystkie problemy świata.

Wydruk 7.6. Rozwiąż wszystkie problemy świata

int main (){

init ();solve_problems();finish_up ();return (0);

}

Oczywiście pozostałe szczegóły zostaną uzupełnione trochę później.Rozpoczniemy od utworzenia funkcji main. Jej długość nie powinna przekroczyć trzech stron. Jeśli jednak będzie dłuższa, należy zastanowić się nad podzieleniem jej na dwie mniejsze i prostsze funkcje. Po utworzeniu funkcji main można zająć się pisaniem pozostałych.Tego typu programowanie strukturalne jest określane terminem programowania zstępującego. Polega ono na rozpoczynaniu od góry (funkcja main) i stopniowym przechodzeniu w dół. Innym typem programowania jest programowanie wstępujące. W metodzie tej najpierw są tworzone funkcje najbardziej zagnieżdżone, następnie testowane, a dopiero później na takiej podbudowie jest definiowana reszta. Lepiej stosować programowanie wstępujące, gdy mamy do czynienia z nowymi funkcjami standardowymi, których dotąd nie stosowaliśmy. Aby upewnić się, że naprawdę wiemy, jakie jest działanie określonej funkcji, piszemy niewielki program, a dopiero potem realizujemy kolejne zadania. Tak więc w praktyce obydwie techniki są wykorzystywane. W wyniku uzyskuje się mieszankę, w której przeważa programowanie zstępujące z domieszką programowania wstępującego. Inżynierowie oprogramowania określają taką metodologię terminem chaosu.Podstawowa zasada, jaką należy przestrzegać w programowaniu, brzmi: „Wykorzystuj to, co się sprawdza najlepiej".

79

Page 80: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

RekurencjaRekurencja ma miejsce wtedy, gdy funkcja w sposób pośredni lub bezpośredni wywołuje samą siebie. Niektóre funkcje, takie jak silnia, w sposób naturalny same wykorzystują algorytm rekurencji.Funkcja rekurencji musi spełniać dwie podstawowe zasady:

• Musi posiadać koniec.• Musi zmniejszać stopień złożoności problemu.

Definicja silni ma następującą postać:

fact (0) = 1fact (n) = n * fact (n - 1)

W języku C powyższa definicja przybiera postać:

int fact (int number){

if (numer = = 0)return (1);

/* inaczej */return (number*fact(number-1));}

Powyższa definicja spełnia wspomniane obie zasady. Po pierwsze, posiada koniec (określony warunkiem = = 0). Po drugie, upraszcza początkowy problem, ponieważ obliczenie wartości wyrażenia fact (number - 1) jest prostsze niż w przypadku wyrażenia fact(number) .Silnia jest spełniona tylko dla warunku number >= 0 .Co się stanie, gdy spróbujemy obliczyć wartość wyrażenia fact(-3)? Działanie programu zostanie przerwane wyświetleniem błędu przepełnienia stosu lub podobnego komunikatu. Wyrażenie fact(-3) wywołuje wyrażenie fact(-4 ), a ono wywołuje wyrażenie fact(-5), i tak dalej. Nie istnieje w tym przypadku koniec. Błąd tego typu jest określany mianem błędu rekurencji zmierzającej do nieskończoności.Wiele operacji wykonywanych w sposób iteracyjny może być zrealizowanych poprzez zastosowanie rekurencji. Przykładem jest dodawanie elementów tablicy. Zdefiniujmy funkcję dodającą elementy m-n tablicy zgodnie z następującymi zasadami:•Jeśli istnieje tylko jeden element, zostanie zastosowane zwykłe sumowanie.• W pozostałych przypadkach zostanie zastosowana suma składająca się z

pierwszego elementu oraz sumy reszty elementów.W języku C funkcja ta będzie miała postać:

int sum (int first, int last, int array[ ]){

80

Page 81: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

if (first = = last)return (array[first]);

/* inaczej */return (array[first] + sum(first+1, last, array));}

Poniżej zawarto przykład:

Suma(1 8 3 2) =1 + Suma(8 3 2) =

8 + Suma(3 2) =3 + Suma(2) =

23+2=5

8+5=131 +13 = 14

Odpowiedz = 14

81

Page 82: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

8. Preprocesor języka C

Instrukcja #defineW wydruku 8.1 są inicjalizowane dwie tablice (data oraz twice). Każda tablica zawiera 10 elementów. Przypuśćmy, że chcemy zmodyfikować program tak, aby zastosował tablice 20-elementowe, a następnie zmienił rozmiar tablicy (w dwóch miejscach) oraz wartość graniczną indeksu (w jednym miejscu). Pomijając fakt ilości pracy z tym związanej, dokonanie wielu zmian może być powodem wystąpienia błędów.

Wydruk 8.1. int data[10]; /* okreslone dane */ int twice[10]; /* dwukrotna ilosc danych */ int main() { int index; /* indeks danych */ for (index = 0; index < 10; ++index) { data[index] = index; twice[index] = index * 2; } return (0); }

Chcielibyśmy napisać prosty program, w którym zdefiniujemy stałą przechowującą rozmiar tablicy, a następnie pozwolimy na to, aby język C dopasował odpowiednio rozmiar dwóch tablic. Jest to możliwe poprzez zastosowanie instrukcji #define. W wydruku 8.2 zawarto wersję programu z wydruku 8.1.

Wydruk 8.2. #define SIZE 20 /* operuje na 20 elementach */ int data [SIZE]; /* okreslone dane */ int twice[SIZE]; /* dwukrotna ilosc danych */ int main() {

int index; /* indeks danych */for (index = 0; index < SIZE; ++index) {

data[index] = index; twice[index] = index * 2; } return (0); }

Program zawarty w wierszu #define SIZE 20 działa jak polecenie specjalnego edytora tekstu globalnie zmieniające wartość zmiennej SIZE na 20. Program ten pozwoli zaoszczędzić sporo czasu oraz nie wywoła wątpliwości związanych z dokonywanymi zmianami.

82

Page 83: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wszystkie polecenia preprocesora rozpoczynają się znakiem „#" umieszczonym w pierwszej kolumnie. Pomimo że język C ma dość swobodny format nie jest tak w przypadku preprocesora. Format ten zależy od znaku „#" znajdującego się w pierwszej kolumnie. Jak się później okaże preprocesor nie ma żadnych informacji na temat języka C. Może on również być zastosowany do edycji prodramów napisanych w językach innych niż język C.Można z łatwością zapomnieć o tym, że preprocesor i kompilator języka C stosują inną składnię. Jednym z najczęstszych błędów popełnianych przez początkujących programistów jest próba używania składni języka C w dyrektywach preprocesora.Dyrektywa preprocesora kończy się znakiem końca wiersza. Format ten różni się od formatu języka C, rzykład poniższe polecenie,

#define FOO barspowoduje, że preprocesor zastąpi słowo „FOO" słowem „bar" w każdym miejscu jego wystąpienia. Powszechną praktyką w programowaniu jest stosowanie w nazwach makr samych dużych liter. Tym sposobem można bardzo łatwo odróżnić zmienną (same małe litery) od makra (same wielkie litery).Ogólna postać prostej instrukcji #define została pokazana poniżej:#define nazwa podstawieniegdzie nazwa może być dowolnym poprawnym identyfikatorem języka C, natomiast podstawienie może być dowolnym łańcuchem. Dopuszczalne jest zastosowanie poniższej definicji, #define FOR_ALL for (i = 0; i < ARRAY_SIZE; i++)którą można zastąpić w następujący sposób:

/** Czyszczenie tablicy*/FOR_ALL {

data [i] = 0;}

Jednak definiowanie makr w taki sposób nie jest zalecaną metodą programowania. Tego typu definicje sprawiają, że przepływ sterowania w programie jest mniej zrozumiały. W tym wydruku programista, który chciałby dowiedzieć się, jakie operacje wykonuje pętla, musiałby najpierw poszukać na początku programu definicji FOR_ALL.Jeszcze mniej zalecaną metodą programowania jest definiowanie makr, które wykonują globalną operację podstawienia w prostych elementach składni języka C. Na przykład można zdefiniować następujące instrukcje: #define BEGIN { #define END } …

if (index = = 0)BEGIN Printf ("Start\n");END

83

Page 84: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Problem polega na tym, że to już nie jest programowanie w języku C, ale w mieszance języka C i Pascal. Można zetknąć się ze skrajnościami kiedy taka mimikra może być zastosowana w skryptach powłoki Bourne, która wykorzystuje dyrektywy preprocesora do zdefiniowania języka bardzo podobnego do języka Algol-68.Poniżej zawarto przykładowy fragment programu:

IF (x GREATER_THAN 37) OR (Y LESS_THAN 83) THENCASE value OF

SELECT 1:start();

SELECT 3:backspace();

OTHERWISE:error();

ESACFI

Większość programistów, widząc taki program, najpierw złorzeczy, a dopiero potem za pomocą edytora zamienia jego program źródłowy na bardziej przystępną wersję języka C. Preprocesor może wywołać nieprzewidywalne problemy, ponieważ nie sprawdza poprawności składni języka C. Przykładowo w wydruku 8.3 po wykonaniu instrukcji w wierszu 11. zostanie wygenerowany błąd.

Wydruk 8.3. 1 #define BIG_NUMBER 10 ** 1023 main ()4 {5 /* indeks stosowany przy obliczeniach */6 int index;78 index = 0;910 /* w nastepnym wierszu wystepuje blad skladni */11 while (index < BIG_NUMBER) {12 index = index * 8;13 }14 return (0);15 }

Problem występuje w wierszu 1. w instrukcji #define, ale komunikat błędu wskazuje na wiersz 11. Definicja zawarta w wierszu 1. powoduje, że preprocesor modyfikuje zawartość wiersza 11. do poniższej postaci:

84

Page 85: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

while (index < 10 ** 10)

Ze względu na fakt, że znak „**" jest nieprawidłowym operatorem, efektem tej modyfikacji jest błąd skladni.Pytanie 8.1. W wydruku 8.4 wygenerowany wynik ma wartość 47 zamiast spodziewanej wartości 144. Dlaczego tak jest? (Zapoznaj się z poniższą wskazówka).

Wydruk 8.4#include <stdio.h>#define FIRST_PART 7#define LAST_ PART 5#define ALL PARTS FIRST_PART + LAST_PARTint main() {

printf("Kwadrat obu czesci ma wartosc: %\n",ALL_PARTS * ALL_PARTS);return (0);}

Wynik może nie być tak oczywisty. Na szczęście, język C umożliwia uruchomienie programu za pomocą preprocesora i następnie przejrzenie danych wyjściowych.W systemie UNIX poniższe polecenie

% cc -E prog.cspowoduje wysłanie danych wyjściowych preprocesora na standardowe wyjście.W systemie MS-DOS/Windows polecenie

C: > cpp prog. C

wykona identyczną operację.

Uruchomienie powyższego programu za pomocą preprocesora wygeneruje w efekcie:

# 1 "first.c"# 1 "/usr/include/stdio.h" 1

plus wydruk zawartości pliku dołączanego <stdio.h>

#2 "first.c" 2main () {printf("Kwadrat obu czesci ma wartosc: %d\n",7 + 5 * 7 + 5);return (0) ;}

85

Page 86: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Odpowiedź 8.1. Po sprawdzeniu programu przez preprocesor funkcja printf jest zamieniania do poniższej postaci:

printf("Kwadrat obu czesci ma wartosc: %d\n", 7+5*7+5);

Wynikiem równania 7+5*7+5 jest liczba 47. Należy zawrzeć w nawiasach okrągłych wszystkie wyrażenia występujące w makrach. Jeśli definicja symbolu _ALL_PARTS zostanie zmodyfikowana do postaci,

#define ALL_PARTS(FIRST_PART+LAST_PART)

program będzie działał poprawnie.

Pytanie 8.2. W wydruku 8.5 jest generowane ostrzeżenie mówiące, że zmienna counter została użyta, zanim ją zadeklarowano. Ostrzeżenie to jest zaskakujące, ponieważ deklaracja zmiennej powinna być wykonana przez pętlę for. Wyświetlane jest również w wierszu 11. dziwne ostrzeżenie „nulleffect" (efektu wartości zerowej).

Wydruk 8.51 /* uwaga: spacje sa BARDZO wazne */23 #include <stdio.h>45 #define MAX =1067 int main()8 {9 int counter;1011 for (counter =MAX; counter > 0; --counter)12 printf("Czesc\n");1314 return (0);15 }

Trzeba prawdzić dane wyjściowe preprocesora.Odpowiedź 8.2. Preprocesor jest bardzo naiwnym programem. W momencie definiowania makra wszystko, co znajduje się za identyfikatorem, jest jego częścią. W tym przypadku definicja symbolu MAX dosłownie sprowadza się do wyrażenia =10. Po zamianie instrukcji for przyjmuje ona następującą postać:

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

86

Page 87: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W języku C jest możliwe obliczenie wyniku, a następnie jego odrzucenie. (W niektórych kompilatorach spowoduje to wygenerowanie ostrzeżenia typu null-effect (efektu wartości zerowej)). W przypadku powyższej instrukcji program sprawdzi, czy zmienna counter ma wartość 10, a następnie odrzuci wynik. Rozwiązanie problemu polega na usunięciu z definicji operatora „=".

Pytanie 8.3. W wydruku 8.6 program oblicza nieprawidłową wartość zmiennej value. Dlaczego tak się dzieje?

Wydruk 8.6#include <stdio.h>#define SIZE 10#define FUDGESIZE -2;int main(){

int size;/* rozmiar rzeczywiscie stosowany */size = FUDGE;printf("Rozmiar ma wartosc %d\n", size);return (0);}

Odpowiedź 8.3. Podobnie jak w miało to miejsce przy poprzednio omawianym problemie, preprocesor nie przestrzega zasad obowiązujących w składni języka C. W tym przypadku programista w celu zakończenia instrukcji użył średnika, ale preprocesor dołączył go jako część definicji stałej SIZE. Instrukcja przypisania dla stałej SIZE po zamianie przyjmie następującą postać:

size = 10; -2;;

Dwa średniki występujące na jej końcu nie są szkodliwe, ale ten w środku jak najbardziej. Powyższa instrukcja nakazuje językowi C wykonanie dwóch operacji:• Przypisanie zmiennej size wartości 10.• Obliczenie wartości -2, a następnie jej odrzucenie (wykonanie tej instrukcji spowoduje wyświetlenie ostrzeżenia typu null-efect (efektu wartości zerowej)). Po usunięcie średników problem przestanie istnieć.

Pytanie 8.4. W wydruku 8.7 prawdopodobnie zostanie wyświetlony komunikat „Blad krytyczny; Zatrzymanie”, po czym po odebraniu nieprawidłowych danych nastąpi zakończenie działania programu. W przypadku, gdy odebrane dane są prawidłowe, również następuje zakończenie działania programu. Dlaczego tak się dzieje?

87

Page 88: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 8.71 #include <stdio.h>2 #include <stdlib.h>34 #define DIE \5 fprintf(stderr, " Blad krytyczny: Zatrzymanie\n");exit(8);67 int main() {8 /* wartosc losowa dla celow testowych */9 int value;1011 value 1;12 if (value < 0)13 DIE;1415 printf("To jeszcze nie koniec\n");16 return (0);17 }

Odpowiedź 8.4. Wynik działania preprocesora został zawarty poniżej:void exit();main () {

int value;value = 1;if (value < 0)

printf("Blad krytyczny: Zatrzymanie\n"); exit(8);printf("To jeszcze nie koniec\n");return (0);

}

Problem polega na tym, że za inśtrukcją warunkową if znajdują się dwie instrukcje. Zwykle zostałyby one umieszczone w dwóch oddzielnych wierszach. Spójrzmy na ten sam program, ale z właściwie zastosowanymi wcięciami:

#include <stdio.h>#include <stdlib.h>main() {

int value; /* wartosc losowa dla celow testowych "/value = 1;if (value < 0)

printf("Blad krytyczny: Zatrzymanie\n");exit(8);printf("To jeszcze nie koniec\n");return (0);}

88

Page 89: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Mająć taką postać, można z łatwością stwierdzić, dlaczego zawsze następuje przerwanie działania programu. Poprzez zastosowanie pojedynczego makra preprocesora informacja, że za instrukcją if są jeszcze dwie inne instrukcje, nie była widoczna.Rozwiązanie tego problemu polega na zawarciu wszystkich makr skladających się z wielu instrukcji w nawiasach klamrowych ({ } ). Poniżej został zaprezentowany przykład:

#define DIE (printf("Blad krytyczny: Zatrzymanie\n");elit(8);}

Porównanie słów kluczomych #define i constSłowo kluczowe const jest wykorzystywane od niedawna. Zanim się pojawiło, dyrektywa #define była jedynym dostępnym słowem kluczowym służącym do definiowania stałych, a zatem jest ono spotykane w większości obecnie dostępnego programu.Niezależnie od tego użycie słowa kluczowego const z kilku powodow jest bardziej zalecane od stosowania dyrektywy #define. Po pierwsze, dyrektywa #define do momentu uruchomienia makra nie jest sprawdzana. Poza tym, słowo kluczowe const stosuje składnię języka C, natomiast dyrektywa #define posługuje się własną składnią. Dodatkowo słowo kluczowe const dostosowuje się do ogólnych zasad dotyczących zakresów języka C, a stałe zdefiniowane przy użyciu dyrektywy #define nie są ograniczane żadnymi zakresami.W większości przypadków instrukcja const jest bardziej wskazana od dyrektywy #define. Poniżej zawarto dwie metody definiowania tej samej stałej:

#define MAX 10 /* Definicja wartosci wykorzystujacej preprocesor *//* (Definicja taka moze z latwoscia spowodowac problemy) */

const int MAX = 10; /* Definicja stalej jezyka C typu calkowitego *//* (pewniejsza metoda) */

Niektóre kompilatory nie umożliwiają stosowania stałych służących do określania rozmiaru tablicy. Co prawda, powinny na to pozwalać, ale widocznie nie zostały jeszcze dostosowane do obowiązującego standardu.Dyrektywa #define może służyć do definiowania tylko prostych stałych. Za pomocą instrukcji const można zdefiniować prawie każdy typ stałej języka C, włączając w to elementy, takie jak struktury klas. Poniżej zawarto przykładowy wydruk:

struct box {int width, height; /* Rozmiar okna w pikselach */

};

89

Page 90: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

const box pink_box = (1.0, 4.5); /* Rozmiar rozowego okna przechowujacego dane wejsciowe */

Dyrektywa #define jest jednak niezastąpiona w przypadku kompilacji warunkowej oraz innych specjalistycznych zastosowań.

Kompilacja warunkowaJeden z problemów, z jakim mają do czynienia programiści, jest związany z pisaniem programu, który będzie wykonywalny na wielu różnych platformach. W teorii program źródłowy języka C jest przenaszalny, ale już w praktyce różne systemy operacyjne posiadają przeróżne udziwnienia, które muszą być brane pod uwagę. Na przykład już mówiliśmy zarówno o kompilatorze języka C pracującego pod systemem MS-DOS/Windows, jak i pod systemem UNIX. Pomimo że są one prawie identyczne, istnieją różnice, które uwidaczniają się szczególnie w przypadku, gdy zaistnieje konieczność zastosowania zaawansowanych funkcji systemu operacyjnego.Kolejny problem związany z przenoszeniem programu wynika z faktu, że opracowany standard kwestię ostatecznej postaci niektórych funkcji oferowanych przez język pozostawia twórcom oprogramowania. Na przykład rozmiar typu całkowitego jest ustalany w procesie właściwej implementacji.Poprzez zastosowanie kompilacji warunkowej preprocesor daje programiście dużą elastyczność w zakresie modyfikacji wygenerowanego programu. Przypuśćmy, że chcemy w trakcie tworzenia programu dodać do niego program źródłowy po wykonanej operacji wykrywania błędów, a następnie usunąć go z wersji produkcyjnej. Możemy to zrobić poprzez dołączenie takiego programu w sekcji #ifdef/#endif:

#ifdef DEBUGprintf ("Zmienna compute_hash ma wartosc, value %d hash %d\n", value,

hash);#endif /* symbol DEBUG */

Nie jest konieczne umieszczanie za instrukcją #endif komentarza /*symbol DEBUG */, ale tego typu komentarz jest bardzo przydatny.

Jeśli na początku programu znajduje się następująca dyrektywa,

#define DEBUG /* Wlacza wykrywanie bledow */

zostanie dołączona funkcja printf. Jeśli natomiast program zawiera dyrektywę,

#undef DEBUG /* Wylacza wykrywanie bledow */

funkcja printf zostanie pominięta.Mówiąc wprost, instrukcja #undef DEBUG nie jest wymagana. Jeśli nie ma instrukcji #define DEBUG, symbol DEBUG nie jest definiowany.

90

Page 91: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Instrukcja #undef DEBUG jest stosowana w celu wyraźnego zaznaczenia, że symbol DEBUG służy do kompilacji warunkowej i jest aktualnie wyłączony.Dyrektywa #ifndef spowoduje, że program źródłowy zostanie skompilowany jeśli symbol nie został zdefiniowany:

#ifndef DEBUGprintf ("Program w wersj i produkcyjnej, nie wlaczono wykrywania bledow\n");#endif /* symbol DEBUG */

Dyrektywa #else odwraca znaczenie warunkowości. Przykładem tego jest poniższy program:

#ifdef DEBUGprintf ("Wersja testowa. Wykrywanie bledow jest wlaczone\n");

#else DEBUGprintf ("Wersja produkcyjna\n");

#endif /* symbol DEBUG */

Programista może zażyczyć sobie tymczasowe usunięcie fragmentu programu. Jedna z często stosowanych metod polega na zawarciu takiego programu w znakach /* */ komentarza. Metoda ta może być przyczyną problemów, co zostało pokazane w poniższym wydruku:

1: /***** Zawarcie tego fragmentu jako komentarza2: section_report();3: /* obsluga konca fragmentu programu */4: dump_table();5: **** koniec komentarza */

W powyższym programie w wierszu 5. został wygenerowany błąd. Dlaczego tak się stało? Bardziej zalecaną metodą jest zastosowanie instrukcji #ifdef służącej do usuwania programu:

#ifdef UNDEFsection_report();/* obsluga konca fragmentu programu */dump table();

#endif / symbol UNDEF */

Oczywiście po zdefiniowaniu symbolu UNDEF program zostanie dołączony, zatem nie powinno się dopuszczać do komputera kogoś, kto może to zrobić.Przełącznik kompilatora -Dsymbol umożliwia definiowanie symboli w wierszu poleceń. Przykładem jest poniższe polecenie, w którym

% cc -DDEBUG -g -o prog prog.c

91

Page 92: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

jest kompilowany program prog. c oraz dołączany program źródłowy zawarty pomiędzy instrukcją #ifdef DEBUG i #endif DEBUG, nawet pomimo tego że w programie nie występuje instrukcja #define DEBUG.Inną postacią opcji -Dsymbol jest -Dsymbol=wartość. Na przykład poniżej symbol MAX przyjmuje wartość 10:

cc -MAX=10 -o prog prog.c

Należy zauważyć, że programista może nadpisać opcje wiersza poleceń dyrektywami zawartymi w programie. Na przykład poniższa dyrektywa

#undef DEBUG

spowoduje, że symbol DEBUG nie zostanie zdefiniowany, niezależnie od tego, czy będzie użyty przełącnik -DDEBUG.Większość kompilatorów języka C automatycznie definiuje niektóre symbole zależne od systemu. Na przykład kompilator Turbo C++ definiuje symbol _TURBOC_ oraz _MSDOS_ . Kompilator zgodny ze standardem ANSI definiuje symbol _STDC_ . Większość kompilatorów systemu UNIX definiuje nazwę systemu (np. SUN, VAX, Celerity), jednak nie są one zbyt często wymienione w dokumentacji. Symbol _unix_ jest zawsze definiowany na wszystkich platformach pracujących pod systemem UNIX.

Pliki dołączaneDyrektywa #include umożliwia programiście zastosowanie programu źródłowego znajdującego się w innym pliku.Na przykład w prezentowanych programach została zastosowana następująca dyrektywa:

#include <stdio.h>

Dyrektywa ta informuje preprocesor, że należy załadować plik stdio.h (plik nagłówkowy standardowego wejścia/wyjścia), a następnie wstawić go do programu. Pliki dołączane do innych programów określane są terminem plików nagłówkowych (ang. header files). Większość dyrektyw #include jest umieszczana w nagłówku programu. Nawiasy ostre (<>) wskazują że plik jest standardowym plikiem nagłówkowym. W przypadku systemu UNIX pliki takie znajdują się w katalogu /usr/include. W systemie MS-DOS/Windows są umieszczone w dowolnym katalogu, który został określony podczas instalacji kompilatora.Biblioteka standardowa zawiera pliki definiujące struktury danych oraz makra wykorzystywane przez jej funkcje. Na przykład funkcja printf jest funkcją biblioteki, która wysyła dane do standardowego wyjścia. Struktura FILE używana przez funkcję printf i powiązane z nią funkcje jest zdefiniowana w pliku stdio.h.

92

Page 93: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Czasem programista może zechcieć utworzyć własny zestaw plików dołączanych. Lokalne pliki dołączane są szczególnie przydatne do przechowywania stałych oraz struktur danych w przypadku, gdy program składa się z kilku plików. Są również bardzo pomocne przy przekazywania informacji w sytuacji, gdy nad pojedynczym projektem pracuje kilku programistów. Lokalne pliki dołączane mogą być definiowane przy użyciu znaków cudzysłowu (" ") otaczających ich nazwy. Poniżej zawarto odpowiedni przykład:

#include "defs.h"

Nazwą pliku defs.h może być dowolna dopuszczalna nazwa. Definicja pliku może sprowadzać się do samej nazwy defs.h, ścieżki względnej ../../data.h lub ścieżki bezwzględnej /root/include/const.h. (W systemic MS-DOS/Windows należy użyć jako separatora katalogów zamiast znaku (/)znaku (\)).Ścieżki bezwzględne nie powinny być stosowane w dyrektywach #include, poniewaz sprawiają one, że program jest trudny w przenoszeniu na inne platformy. Pliki dołączane mogą być zagnieżdżane, ale możliwość taka może być przyczyną problemów. Załóżmy, że w pliku const.h zostało zdefiniowanych kilka przydatnych stałych. Jeśli pliki data.h oraz io.h zawierają dołączony plik const.h, a ponadto w programie zostaną dodane poniższe instrukcje,

#include "data.h"#include "io.h"

zostaną wygenerowane błędy, ponieważ w pliku const.h preprocesor dwukrotnie ustawi definicje. Dwukrotne zdefiniowanie stałej nie jest poważnym błędem, ale wykonanie takiej operacji w przypadku definicji struktury danych lub sumy zbiorów jest niedopuszczalne i należy unikać takich sytuacji.Jednym ze sposobów uniknięcia tego problemu jest sprawdzenie, czy plik const.h nie został już dołączony, a ponadto, czy nie zawiera definicji symboli, które zostały wcześniej zdefiniowane. Dyrektywa #ifndef symbol jest prawdziwa, jeśli symbol nie został już zdefiniowany. Dyrektywa ta jest odwrotnością dyrektywy #ifdef. Spójrzmy na poniższy program:

#ifndef _CONST_H_INCLUDED_/* definicja stalych */#define _CONST_H_INCLUDED_#endif /* symbol _CONST_H_INCLUDED_ */

Po dołączeniu pliku const.h dodawana jest też definicja symbolu _CONST_H_INCLUDED_. Jeśli symbol ten jest już zdefiniowany (ponieważ plik został dołączony wcześniej), dyrektywa #ifndef warunkowo ukrywa wszystkie definicje, tak aby nie były przyczyną problemów.Dosłownie wszystko może być umieszczone w pliku nagłówkowym. Można tu wymienić nie tylko definicje i typy, ale również program, dane inicjalizacji i nawet menu z wczorajszego obiadu. Jednak by być zgodnym z

93

Page 94: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

regułami poprawnego programowania, należy ograniczyć zawartość pliku nagłówkowego tylko do typów i definicji.

Makra parametryzowaneJak dotąd, zostały omówione tylko proste dyrektywy #define oraz makra, ale przecież makra mogą zawierać parametry. Poniższe makro oblicza kwadrat liczby:

#define SQR(x) ((x) * (x)) /* kwadrat liczby */

Nie wolno umieszczać spacji pomiędzy nazwą makra i nawiasami.Po uruchomieniu makra parametr x zostanie zastąpiony przez łańcuch zawierający następujące argumenty:

SQR(5) jest zamieniane na ((5) * (5) )

Parametry makra zawsze należy umieszczać w nawiasach okrągłych. W wydruku 8.8 został zaprezentowany problem, który występuje, jeśli powyższa zasada nie jest przestrzegana.

Wydruk 8.8#include <stdio.h>#define SQR(x) (x * x)int main() {

int counter; /* licznik petli */for (counter=0; counter<5; ++counter) {

printf("x %d, liczba x podniesiona do kwadratu daje %d\n",counter+1, SQR(counter+1));

}return (0);}

Pytanie 8.5. Dlaczego w wydruku 8.8 został wyświetlony wynik? Spróbuj uruchomić program na Twoim komputerze. Dlaczego właśnie jest taki wynik, a nie inny? Spróbuj sprawdzić dane wyjściowe preprocesora.Odpowiedź 8.5. Program wyświetlił następujący wynik:

x 1 liczba x podniesiona do kwadratu daje 1x 2 liczba x podniesiona do kwadratu daje 3x 3 liczba x podniesiona do kwadratu daje 5x 4 liczba x podniesiona do kwadratu daje 7x 5 liczba x podniesiona do kwadratu daje 9

Problem występuje w wyrażeniu SQR (counter+1). Wyrażenie to po zamianie przyjmuje następującą postać:

SQR(counter + 1)(counter + 1 * counter + 1)

94

Page 95: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W efekcie makro nie działa prawidłowo. Rozwiązanie problemu polega na umieszczeniu parametrów w nawiasach:

#define SQR(x) ((x) * (x))

Ogólna zasada programowania „Rób to jak najprościej" nakazuje umieszczanie operatora inkrementacji (++) lub dekrementacji (--) tylko w oddzielnym wierszu. Stosowania tej zasady w przypadku parametrów makra może doprowadzić do nieprzewidywalnych wyników. Zostało to pokazane w wydruku 8.9.

Wydruk 8.9#include <stdio.h>#define SQR(x) ((x) * (x))int main() {

int counter; /* licznik petli */counter = 0;while (counter < 5)

printf("kwadrat liczby x d daje %d\n", counter, SQR(++counter));return (0);

}

Pytanie 8.6. Dlaczego w wydruku 8.9 nie jest wyświetlany oczekiwany wynik? O ile każdorazowo wzrośnie wartość licznika?Odpowiedź 8.6. Licznik przy każdym przejściu przez pętlę jest inkrementowany o dwa. Inkrementacja taka ma miejsce, ponieważ po wywołaniu makra

SQR(++counter)

jest ono zamieniane do postaci:

((++counter) * (++counter))

Pytanie 8.7. W wydruku 8.10 jest wyswietlana informacja, że nie została zdefiniowana zmienna number, a jedyną zmienną w programie jest zmienna counter.

Wydruk 8.10#include <stdio.h>#define RECIPROCAL (number) (1.0 / (number))int main() {

float counter; /* licznik tabeli */for (counter = 0.0; counter < 10.0;counter += 1.0) {

printf("1/%f =%f\n", counter, RECIPROCAL(counter));}return (0);}

95

Page 96: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Odpowiedź 8.7. Jedyna różnica pomiędzy makrem parametryzowanym a makrem bez parametrów polega na umieszczaniu nazwy makra w nawiasach okrągłych. W tym przypadku za definicją makra RECIPROCAL występuje spacja, a zatem nie jest to makro parametryzowane. Jest to zwykłe makro zastępujące tekst RECIPROCAL poniższym łańcuchem:

(number) (1.0/number)

Usunięcie spacji znajdującej się pomiędzy nazwą RECIPROCAL i parametrem (number)zlikwiduje problem.

Funkcje zaawansowaneNie będziemy rozpatrzywać pełnej listy dyrektyw preprocesora języka C. Wśród bardziej zaawansowanych funkcji można wyróżnić złożoną postać dyrektywy #if stosowanej przy kompilacji warunkowej oraz dyrektywę #pragma służącą do wstawiania w pliku poleceń zależnych od kompilatora.

PodsumowaniePreprocesor języka C jest bardzo przydatnym składnikiem języka C. Ze względu na całkiem odmienny sposób działania musi być traktowany jako oddzielne narzędzie nie związane z podstawowym kompilatorem.Problemy występujące w definicji makr często nie uwidaczniają się w momencie samej definicji, ale są przyczyną błędów pojawiających się w czasie uruchamiania programu. Jeśli będziemy przestrzegać kilka prostych zasad, możemy zmniejszyć prawdopodobieństwo wystąpienia problemów:• Umieszczaj parametry makr oraz dyrektywy #define służące do definicji stałych w nawiasach okrągłych.• W przypadku definiowania makra zawierającego więcej niż jedną instrukcję zawieraj program w nawiasach klamrowych ({}).• Preprocesor nie jest językiem C. Nie wolno używać operatora „=" oraz średnika. Jeśli zrozumiałeś powyższe zasady, możesz być pewien, że najgorsze masz za sobą.

96

Page 97: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

9. Operacje na bitachBit jest najmniejszą jednostką informacji. Zwykle bit jest reprezentowany przez wartość 0 lub 1. (Inne formy zapisu to: on-off (włączony-wyłączony), true-false (prawda- fałsz), yes-no (tak-nie)). Operacje na bitach mają na celu kontrolowanie komputera na najniższym poziomie. Pozwalają one programiście zajrzeć do wnętrza komputera. Wiele języków programowania wysokiego poziomu nigdy nie skorzysta z operacji na bitach. Programowanie niskopoziomowe takie jak ma miejsce w przypadku pisania sterowników urządzeń lub tworzenia grafiki na poziomie pikseli jest związane z wykorzystaniem operacji na bitach.Osiem bitów tworzy bajt reprezentowany w języku C przez typ znakowy char1.Bajt może zawierać następujący ciąg bitów:

01100100

Taka struktura bitów może być również zapisana w postaci szesnastkowej jako 0x64. (W języku C jest stosowany prefiks „0x" wskazujący na liczbę szesnastkową (o podstawie 16)). Zapis szesnastkowy jest wygodny do przedstawiania danych binarnych, ponieważ każda cyfra zapisana w systemie szesnastkowym reprezentuje w systemie dwójkowym 4 bity. W tabeli 9.1 zostały zebrane zależności pomiędzy systemem szesnastkowym i dwójkowym.Tabela 9.1. Konwersja pomiędzy systemem szesnastkowym i dwójkowym

System szesnastkowy

System dwójkowy

System szesnastkowy

System dwójkowy

01234567

00000001001000110100010101100111

89ABCDEF

10001001101010111100110111101111

Na podstawie powyższej tabeli widać, że liczba szesnastkowa 0xAF odpowiada liczbie binarnej 10101111.W funkcji printf stosowany zapis szesnastkowy ma postać %x, natomiast ósemkowy 0. Po wykonaniu poniższego programu

1 Z technicznego punktu widzenia standard języka C nie określa liczby bitów w znaku, jednak na tyle, na ile mi wiadomo, na każdej platformie znak w języku C ma długość 8 bitów.

97

Page 98: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

int number = 0xAF;printf ("Liczba ma wartosc %x %d %o\n", number, number, number);

zostanie wyświetlony następujący komunikat:

af 175 257

Operatory bitoweOperatory bitowe umożliwiają programiście działanie na pojedynczych bitach. Na przykład typ całkowity short int przechowuje liczby o długości 16 bitów (w przypadku większości platform). Operatory bitowe traktują każdy bit niezależnie. Dla porównania operator dodawania traktuje 16 bitów jako jedną liczbę l6-bitową.Operatory bitowe pozwalają programiście ustawiać, usuwać, testować oraz wykonywać inne operacje na bitach. Zostały one zebrane w tabeli 9.2.

Tabela 9.2. Operatory bitowe

Operator Znaczenie&|^.<<>>

Koniunkcja bitowaBitowa alternatywaRóżnicja symetrycznaNegacja bitowaOperator przesunięcia w lewoOperator przesunięcia w prawo

Operatory te działają z dowolnymi zmiennymi typu całkowitego lub znakowego.

Koniunkcja bitozva and (&)Operator and porównuje dwa bity. Jeśli obydwa z nich mają wartość 1,wynik też ma wartość 1. Wyniki zastosowania operatora and zostały zebrane w tabeli 9.3.

Tabela 9.3. Operator and

Bit 1 Bit 2 Bit 1&Bit 2

0011

0101

0001

Gdy dwie ośmiobitiwe zmienne (zmienne typu znakowego char) są ze sobą porównywane, operator and działa na każdym bicie niezależnie. Poniższy kod programu wykonuje taką operację:

98

Page 99: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

int c1, c2c1 = 0x45;c2 = 0x71;printf ("Wynik operacji %x & %x = %x\n", c1, c2, (c1 & c2));

Program po uruchomieniu wyświetli poniższy wynik:

Wynik operacji 45 & 71 = 41

Jest tak, ponieważ:

c1 = 0x45; w systemie dwójkowym 01000101& c2 = 0x71; w systemie dwójkowym 01110001------------------------------------------------------------

= 0x41 w systemie dwójkowym 01000001

Koniunkcja bitowa and (&) jest podobna do operatora logicznego and (&&). W przypadku operatora logicznego and, jeśli obydwa porównywane argumenty mają wartość true (różną od zera), wynik również ma wartość true (1). W przypadku koniunkcji bitowej and (&), jeśli porównywane bity argumentów mają wartość true (1), odpowiadające im bity wynikowe też mają wartość true (1). Zatem koniunkcja bitowa and (&) działa niezależnie na każdym bicie, a operator logiczny and (&&) traktuje argumenty jako jedną całość.W wydruku 9.1 została pokazana różnica pomiędzy operatorem & i &&.

Wydruk 9.1#include <stdio.h>int main() {

int i1, i2; /*dwie losowe liczby calkowite */i1 = 4;i2 = 2;/* Zalecany sposob pisania instrukcji warunkowej */if ((i1 != 0) && (i2 != 0))

printf("Obie liczby sa rozne od zera\n");/* Krotszy sposob zapisania powyzszej instrukcji *//* Program skladniowo poprawny, ale styl nie zalecany */if (i1 && i2)printf("Obie liczby sa rozne od zera\n");/* Nieprawidlowe uzycie operatora bitowego jest przyczyna bledu */

if (il & i2)printf("Obie liczby sa rozne od zera\n");return (0);}

99

Page 100: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Pytanie. Dlaczego po wykonaniu trzeciej instrukcji warunkowej nie zostanie wyświetlony komynikat Obie liczby sa rozne od zera?Odpowiedź: Operator & jest operatorem koniunkcji bitowej and. Wynikiem jego działania jest zero.

il = 4 00000100i2 =2 00000010-------------------------------& 00000000

Wynikiem zastosowania operatora bitowego & jest zero, a instrukcja warunkowa ma wartość false. Jeśli programista zastosował pierwszą postać

((il ! =0) & & (i2 != 0))

oraz omyłkowo zamiast operatora & zastosował operator &&,

if ((i1 != 0) & (i2 != 0))

wtedy program nadal będzie działał poprawnie.

(i1 !=0) ma wartość true (wynik = 1)(i2 != 0) ma wartość true (wynik = 1)

Ponieważ wyrażenia składowe są prawdziwe (true), zatem całe wyrażenie też ma wartość true2.Operator koniunkcji bitowej and może być używany do sprawdzania, czy liczba jest parzysta czy nieparzysta. W przypadku systemu o podstawie 2 ostatnią cyfrą wszystkich liczb parzystych jest zero, natomiast ostatnią cyfrą w przypadku wszystkich liczb nieparzystych jest 1. Poniższa funkcja stosuje operator koniunkcji bitowej and w celu wybrania tej ostatniej cyfry. Jeśli jest nią zero (liczba parzysta), wynik funkcji ma wartość true.

int even(const int value){

return ((value & 1) = = 0);}

Powyższa procedura posługuje się metodą programowania określaną technicznym terminem „uroczej sztuczki". Zwykle „uroczych sztuczek" należy unikać, gdzie tylko jest to możliwe. Bardziej zalecana w tym przypadku metoda programowania polega na zastosowaniu operatora modułu (%).

2 Niedługo po tym, jak znalazłem błąd pokazany w tym programie, powiedziałem do mojego kolegi z pracy:„Teraz już rozumiem, jaka jest różnica między operatorem & (i) i && (i i)", a on mnie zrozumiał. Sposób, w jaki poznajemy język, zawsze mnie fascynował, a fakt, że potrafiłem wypowiedzieć takie zdanie i dodatkowo ktoś jeszcze bez problemu je zrozumiał, tym bardziej mnie zadziwił.

100

Page 101: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Bitowa alternatywa ( | )Bitowa alternatywa (znana również jako operator or) jest operatorem porównującym swoje dwa argumenty. Jeśli jeden z bitów ma wartość 1, wynik też ma wartość 1. W tabeli 9.4 zawarto możliwe wartości dla operatora bitowej alternatywy.

Tabela 9.4. Operator bitowej alternatywy

Bit1 Bit2 Bit1 | Bit20011

0101

0111

W przypadku bajta uzyskamy następujące rezultaty:

i1 = 0x47 01000111 | i2 = 0x53 01010011-------------------------------------------------- = 0x57 01010111

Różnica symetryczna ( ^ )Różnica symetryczna (znana również jako operator xor) jest operatorem, którego wynik ma wartość 1 wtedy, gdy tylko jeden z jego dwóch argumentów ma wartość 1. W tabeli 9.5 zebrano możliwe wartości dla operatora różnicy symetrycznej.

Tabela 9.5. Operator różnicy symetrycznej

Bit1 Bit2 Bit1 ^ Bit20011

0101

0110

W przypadku bajta uzyskamy następujące rezultaty:

il = 0x47 01000111^ i2 = 0x53 01010011-------------------------------------------------

=0x14 00010100

101

Page 102: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Negacja bitowa not (~)Operator negacji bitowej not (zwany również jako operator odwrócenia lub bit przełączający) jest operatorem jednoargumentowym, który zwraca negację wartości swojego argumentu. Wartości operatora zostały zawarte w tabeli 9.6.

Tabela 9.6. Operator negacji bitowej not

Bit ~ Bit01

10

c =0x45 01000101-------------------------------~c =0xBA 10111010

Operatory przesunięcia (<<, >>)Operator przesunięcia w lewo przesuwa dane w lewą stronę o określoną liczbę bitów. Wszystkie bity przesunięte na lewą stronę znikająą. Nowe bity pochodzące z prawej strony są zerami. Operator przesuniącia w prawo wykonuje identyczną operację, ale w przeciwną stronę. Poniżej zawarto przykład działania operatorów przesunięcia:

c=0x1C 00011100c<<1c>>2

c=0x38c=0x07

0011100000000111

Operacja przesunięcia w lewo o jeden (x << 1) jest równoważna operacji pomnożenia przez 2 (x * 2). Operacja przesunięcia w lewo o dwa (x << 2) jest równoważna operacji pomnożenia przez 4 (x * 4 lub * 22). Można zauważyć zależność zawartą w powyższych operacjach. Przesunięcie w lewo o n miejsc jest równoważne pomnożeniu przez 2n. Zatem dlaczego zamiast przesuwać lepiej nie pomnożyć? Przesuwanie jest szybszą operacją niż mnożenie, a więc operacja:

i = j <<3 /* mnozenie j przez 8 (2**3) */

jest szybsza od:

i = j * 8;

Czy trwałoby to krócej, gdyby kompilatory nie były tak inteligentne i zamieniały operację mnożenia przez potęgę liczby 2 na przesuwanie?Wielu programistów wykorzystuje tę zależność, aby przyspieszyć działanie swoich programów kosztem ich czytelności. Kompilator jest na tyle

102

Page 103: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

inteligentny, aby automatycznie wykonać taką operację zwiększającą szybkość działania programu. Rezultatem wymuszenia operacji przesunięcia jest tylko pogorszenie czytelność kodu programu.Operator przesunięcia w lewo wykonuje operację mnożenia, natomiast operator przesunięcia w prawo dzieli. A zatem wyrażenie:

q = i >> 2;

jest takie samo jak:

q = i / 4;

Dla przypomnienia, sztuczka ta jest sprytna, ale nie powinna być stosowana w programie, który ma być zgodny z najnowszymi zalecanymi trendami w programowaniu.

Dokładny opis operatora przesunięcia w prawoOperatory przesunięcia w prawo są szczególnie podstępne. W momencie gdy zmienna jest przesuwana w prawo, język C dąży do wypełnienia czymś wolnego miejsca po niej pozostawionego. W przypadku zmiennych ze znakiem, język C stosuje wartość bitu znaku, natomiast dla zmiennych bez znaku jest używane zero. W tabeli 9.7 zebrano niektóre przesunięcia.

Tabela 9.7. Przykłady operatorów przesunięcia w prawo

Typ znakowy signedchar (ze znakiem)

Typ znakowy signedchar (ze znakiem)

Typ znakowy unsignedchar (bez znaku)

WyrażenieWartośćbinarna >> 2WynikWypełnienieOstateczny wynik(binarny)Ostateczny wynik(typ całkowity short int)

9>>2

0000 1010>>2??00 0010bit znaku (0)

0000 0010

2

-8 >> 2

1111 1000>>2??11 1110bit znaku (1)

1111 1110

-2

248 >> 2

1111 1000>>2??11 1110zero

0011 1110

62

Ustawianie, usuwanie i testowanie bitówTyp znakowy char zawiera osiem bitów. Każdy z nich może być potraktowany jako oddzielny znacznik.

103

Page 104: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Operacje na bitach mogą posłużyć do umieszczenia ośmiu jednobitowych wartości w pojedynczym bajcie. Załóżmy, że tworzymy niskopoziomowy program komunikacyjny. Dla dalszych zastosowań znaki będą przechowywane w buforze o rozmiarze 8 kilobajtów. Łącznie z każdym znakiem zostanie również zapisany zestaw znaczników statusu. Znaczniki te zostały zebrane w tabeli 9.8.

Tabela 11.8. Znaczniki statusu komunikacji

Nazwa Opis

ERRORFRAMING_ERRORPARITY_ERRORCARRIER_LOSTCHANNEL_DOWN

Po wystąpieniu dowolnego błędu przyjmuje wartość true

Dla tego znaku wystąpił błąd ramkiZnak miał nieprawidłową parzystośćSygnał nośny zanikłUrządzenie komunikacyjne straciło zasilanie

Możemy przypisać każdy z tych znaczników jego własnej zmiennej typu znakowego. Oznaczałoby to, że dla każdego buforowanego znaku będzie wymaganych pięć bajtów do przechowania jego statusu. W przypadku buforu o większej pojemności wymagania związane z wolną przestrzenią wzrosną. Jeśli jednak, zamiast przypisywać każdemu znacznikowi statusu oddzielny bit ośmiobitowego znaku statusu, zmniejszy się wymagania związane z potrzebną przestrzenią, to wtedy zaoszczędzi się 80 procent początkowo zajmowanego miejsca.W tabeli 9.9 zebrano numery bitów, które zostaną przypisane znacznikom.

Tabela 9.9. Przypisanie bitów

Bit Nazwa

01234

ERRORFRAMING_ERRORPARITY_ERRORCARRIER_LOST

CHANNEL_DOWN

Bity są numerowane zgodnie z konwencją kolejno 76543210. Zostało to pokazane w tabeli 9.9. Na rysunku 9.1 widać, że bit 4 ma ustawioną wartość 1.

104

Page 105: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Stałe dla każdego bitu zostały zawarte w tabeli 9.10.Tabela 9.10. Wartości bitów

Bit Wartość binarna Stała szesnastkowa76543210

1000000001000000001000000001000000001000000001000000001000000001

0x800x400x200x100x080x040x020x01

Definicje mogłyby mieć następującą postać:

/* Po wystapieniu dowolnego bledu przyjmuje wartosc true */const int ERROR = 0x01;

/ Dla tego znaku wystapil blad ramki */const int FRAMING_ERROR = 0x02;

/* Znak mial nieprawidlowa parzystosc */const int PARITY_ERROR = 0x04;

/* Sygnal nosny zanikl /const int CARRIER_LOST = 0x08;

/* Urzadzenie komunikacyjne stracilo zasilanie */const int CHANNEL_DOWN = 0x10;

Taka metoda definiowania bitów jest w pewnym sensie kłopotliwa. Czy jesteśmy w stanie powiedzieć (bez zaglądania do tabeli), który numer bitu jest reprezentowany przez stałą 0x10? Tabela 9.11 pokazuje, w jaki sposób zdefiniować bity przy użyciu operatora przesunięcia w lewo (<<).Pomimo że niełatwo określić, który bit jest reprezentowany przez stałą 0x10, jest to już proste, gdy wiemy, który bit jest skojarzony z reprezentacją 1<<4.W takim razie znaczniki mogą być zdefiniowane w następujący sposób:

/* Po wystapieniu dowolnego bledu przyjmuje wartosc true */const int ERROR (1 << 0);

105

7 6 5 4 3 2 1 00 0 0 1 0 0 1 0

Rysunek 9.1. Numery bitów

Page 106: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* Dla tego znaku wystapil blad ramki */const int FRAMING_ERROR = (1<<1);

Tabela 9.11. Operator przesunięcia w lewo oraz definicja bitów

Reprezentacjaw języku C

Odpowiednik w systemie binarnym (o podstawie 2)

Wynik (binarny) Numer bitu

1<<01<<11<<21<<31<<41<<51<<61<<7

00000001 << 000000001 << 100000001 << 200000001 << 300000001 << 400000001 << 500000001 << 600000001 << 7

0000000100000010000001000000100000010000001000000100000010000000

Bit 0Bit 1Bit 2Bit 3Bit 4Bit 5Bit 6Bit 7

/* Znak mial nieprawidlowa parzystosc */const int PARITY_ERROR = (1 << 2);

/* Sygnal nosny zanikl /const int CARRIER_LOST = (1 << 3);

/* Urzadzenie komunikacyjne stracilo zasilanie */const int CHANNEL_DOWN = (1<<4);

Po zdefiniowaniu bitów można teraz je zmodyfikować. Aby ustawić bit, należy zastosować operator | (bitowa alternatywa). Oto przykład:

char flags = 0; /* ustawia wartosc wszystkich znacznikow na 0 */flags |= CHANNEL_DOWN; /* kanal wlasnie zanikl */

Aby sprawdzić bit, najpierw w celu zamaskowania bitów, należy zastosować operator &:

if ((flags & ERROR) != 0)printf ("Ustawiono znacznik bledu\n");

elseprintf("Nie wykryto bledu\n");

Usunięcie bitu jest trudniejszą operacją. Załóżmy, że chcemy usunąć bit PARITY_ERROR. W zapisie dwójkowym bit ten ma wartość 00000100. Utworzymy maskę, która ma ustawione wszystkie bity oprócz usuwanego bitu (11111011). Operacja ta zostanie wykonana przy użyciu operatora negacji bitowej ~. Po jej wykonaniu maska jest porównywana za pomocą operatora koniunkcji bitowej and z liczbą, która usunie bit.

106

Page 107: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

PARITY_ERROR 00000100~PARITY_ERROR 11111011Flags 00000101--------------------------------------flags & ~PARITY_ERROR 00000001

W języku C należy zastosować poniższą instrukcję:

flags &= ~PARITY_ERROR; /* Kto sie przejmuje parzystoscia */

Pytanie 9.1. W wydruku 9.2 znacznik HIGH_SPEED działa poprawnie, natomiast znacznik DIRECT_CONNECT nie. Dlaczego tak się dzieje?

Wydruk 9.2#include <stdio.h>const int HIGH_SPEED = (1<<7); /* modem jest szybki */

/* stosowane jest polaczenie sztywne */const int DIRECT_CONNECT = (1<<8);

char flags = 0; /* zerowanie */int main(){

flags |= HIGH_SPEED; /* ustawiono wysoki transfer */flags |= DIRECT_CONNECT; /* nastapilo bezposrednie polaczenie */

if ((flags & HIGH_SPEED) != 0)printf("Ustawiono wysoki transfer\n");

if ((flags & DIRECT_CONNECT) != 0)printf("Ustawiono bezposrednie polaczenie\n");

return (0);}

Odpowiedź 9.1. Znacznik DIRECT_CONNECT został zdefiniowany za pomocą wyrażenia (1>>8 ) jako bit o numerze 8, jednakże osiem bitów zmiennej typu znakowego jest ponumerowanych kolejno 76543210. Zatem bit o numerze 8 nie istnieje. Rozwiązanie tego problemu polega na zadeklarowaniu zmiennej flags jako zmiennej typu całkowitego short int o długości 16 bitów.

Grafika bitmapowa

107

Page 108: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Obecnie coraz więcej komputerów jest wyposażonych w urządzenie graficzne. W przypadku komputerów osobistych można wyróżnić takie karty graficzne, jak EGA lub VGA, natomiast system UNIX oferuje nakładkę graficzną X Windows. W grafice bitmapowej każdy piksel ekranu jest opisany za pomocą jednego bitu pamięci. Na przykład na rysunku 9.2 jest przedstawiona bitmapa o rozmiarze 14 na 14 pikseli wyświetlana na ekranie monitora. Aby można było zobaczyć dokładnie poszczególne bity, została ona powiększona.Przypuśćmy, że jestęsmy w posiadaniu niewielkiego urządzenia graficznego posiadającego czarno-

biały wyświetlacz o wymiarach 16 na 16 pikseli. Chcemy ustawić bit o współrzędnych 4,7. Bitmapa będzie teraz tablicą bitów wyświetlona przez urządzenie została pokazana na rysunku 9.4.Jest jednak pewna niedogodność. W języku C nie istnieje typ danych odpowiedni dla tablicy bitów. Najbliższa tym wymogom jest tablica bajtów.

108

Bitm apa

Bitm apa pow iększonaRys. 9 .2. B itmapa - w idok ększony norm alny pow ii

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 150123456

9

78

10111213

1514

Pikse l (4 ,7) ów

ustaw iony w tablicy b ito w ym iarze 16x16

Rysunek 9.3. Tablica bit ów

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 70123456

9

78

10111213

1514

Bajt 0 Bajt 1

Ten sam pikseł ustawiony w tablicy bajtów o wymiarze 2x16

Rysunek 9.4. Tablica bajtów

Page 109: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Tak więc, tablica bitów o wymiarze 16 na 16 pikseli będzie teraz tablicą bajtów o wymiarze 2 na 16 pikseli. Zostła ona pokazana na rysunku 9.4.Przyjrzyjmy się teraz, co będzie potrzebne do wykonania transformacji indeksów x,y na indeksy byte_x, byte_y, bit_index oraz bit.Indeks byte_y jest identyczny jak indeks y. Z tego też powodu transformacja jest prosta:

byte_y = y;

Bajt zawiera 8 bitów, a zatem na osi X przykładowy indeks bajtów jest osiem razy większy od indeksu bitów. Efektem transformacji jest następującą instrukcję:

byte_x = x / 8;

Ustawmy teraz indeks bitów. Indeks ten zaczyna się od zera, dochodzi do wartości 7, po czym wraca do zera. To pozwala nam zapisać następującą instrukcję:

bit_index = x % 8;

Zdefiniujmy teraz sam bit. Indeks bitów dla zera wskazuje bit położony najbardziej po lewej stronie lub bit reprezentowany przez liczbę 1000 00002

lub 0x80. Indeks bitów dla wartośi 1 wskazuje na nastęny bit położony obok poprzedniego bitu i jest reprezentowany przez liczbę 0100 00002 lub 0x80 >> 1. Zatem ustawiany bit będzie określony za pomocą poniższego wyrażenia:

bit = 0x80 >> bit_index;

Pełny algorytm wygląda następująco:

byte_y = y;byte_x = x / 8;bit_index = x % 8;bit = 0x80 >> bit_index;graphics [byte_x][ byte_y] |= bit;

Powyższy algorytm może zostać wyrażony w postaci pojedynczego makra:

#define set_bit (x, y) graphics [ (x)/8] [y] |= (0x80 >> ((x)%8))

Na przykład, aby zdefiniować piksel określony bitem 4,7, należy ustawić czwarty bit bajta 0,7. Makro wygeneruje następującą instrukcję:

bit_array[0] [7] |= (0x80 >> (4));

W wydruku 9.3 jest rysowana przekątna wzdłuż tablicy, a następnie jest ona wyświetlana na ekranie terminala.

109

Page 110: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 9.3.#include <stdio.h>#define X_SIZE 40 /* wymiar tablicy w kierunku osi X */#define Y_SIZE 60 /* wymiar tablicy w kierunku osi Y */

/* Ze wzgledu na umieszczanie 8 bitow w bajcie stosujemy X_SIZE/8 */char graphics[X_SIZE / 8][Y_SIZE]; /* dane bitmapy */#define SET_BIT(x, y) graphics[(x)/8][y] |= (0x80 >> ((x)%8))int main() {

int loc; /* aktualnie ustawiane polozenie */void print graphics(void) /* wyswieltenie danych */for (loc = 0; loc < X_SIZE; ++loc)

SET_BIT(loc, loc);print graphics();return (0);

}/********************************************************************* funkcja print_graphics -- wyswietla bitmape skladajaca sie ** z tablicy bitow (zbioru zawierajacego znaki 'X' i '.'). **********************************************************************/

void print_graphics(void){int x; /* aktualna wartosc wspolrzednej x */int y; /* aktualna wartosc wspolrzednej y */unsigned int bit; /* bit sprawdzany w aktualnym bajcie */for (y = 0; y < Y_SIZE; ++y) {

/* petla wykonywana dla kazdego bajtu tablicy */for (x = 0; x < X_SIZE / 8) ++x) {

/* przetwarzanie kazdego bitu*/for (bit = 0x80; bit > 0; bit = (bit >> 1)) {

if ((graphics[x] [y] & bit) != 0)printf("X");

elseprintf(“.”);

}}printf("\n");

}}

Powyższy program definiuje bitmapę jako tablicę:

char graphics[X_SIZE / 8] [Y_SIZE]; /* dane bitmapy */

110

Page 111: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Stała X_SIZE/8 została zastosowana, ponieważ zdefiniowano stałą X_SIZE (wyrażoną w bitach), która odpowiada wartości X_SIZE/8 wyrażonej w bajtach.

Główna pętla for

for (loc = 0; loc < X_SIZE; ++loc)set_bit(loc, loc);

rysuje przekątną w poprzek tablicy bitmapy.Ponieważ nie dysponujemy graficznym wyświetlaczem, musimy wykonać symulację tej operacji za pomocą procedury print_graphics.Pętla

for (y=0; y< Y_SIZE;++y) {

wyświetla każdy rząd, natomiast pętla

for (x = 0; x < X_SIZE / 8; ++x) {

przetwarza każdy bajt w rzędzie. Poniższa pętla przetwarza osiem bitów w każdym bajcie

for (bit 0x80; bit > 0; bit = (bit >> 1))

i posługuje się nietypowym licznikiem. Pętla ta ustawia bit początkowy zmiennej bit na 7 (bit położony najbardziej na lewo). Przy kolejnych iteracjach pętli bit jest przesuwany za pomocą wyrażenia bit=(bit>>1) w prawo o jeden bit. Po osiągnięciu ostatniego bitu pętla kończy swoje działanie.Poniżej pokazano cykle wykonane przez licznik pętli:

Zapis binarny Zapis szesnastkowy0000 0000 1000 00000000 0000 0100 00000000 0000 0010 00000000 0000 0001 00000000 0000 0000 10000000 0000 0000 01000000 0000 0000 00100000 0000 0000 0001

0x800x 400x200x100x080x040x020x01

Poniżej zawarto najważniejszą część pętli:

if ((graphics [x] [y] & bit) != 0)printf("X");

elseprintf(".");

111

Page 112: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Powyższy program sprawdza poszczególne bity i w przypadku, gdy są one ustawione, zapisuje znak „X", a jeś1i nie - zapisuje znak „.".Pytanie 9.2. W wydruku 9.4 pierwsza pętla działa poprawnie, natomiast druga nie. Dlaczego tak się dzieje?

Wydruk 9.4#include <stdio.h>int main(){

short int i; /* licznik petli */signed char ch; /* licznik petli innego typu */

/* dziala poprawnie */for (i = 0x80; i != 0; i = (i >> 1)) {

printf("Licznik i jest rowny &x (%d)\n", i, i);}

/" nie dziala poprawnie */for (ch = 0x80; ch != 0; ch = (ch >> 1)) {

printf("Licznik ch jest rowny x (%d)\n", ch, ch);}

return (0);}

Odpowiedź 9.2. Problem polega na tym, że zmienna ch jest typu znakowego o długości 8 bitów. Wartość 0x80 wyrażona w zakresie 8 bitów jest liczbą 1000 00002. Pierwszy bit - bit znaku - jest ustawiony. Po wykonaniu dla zmiennej ch przesunięcia w prawo bit znaku posłuży jako wypełnienie. Tak więć, wyrażenie 1000 00002 >> 1 odpowiada liczbie 1100 00002.Zmienna i spełnia swoją funkcję, nawet pomimo tego że zawiera znak, ponieważ jej długość wynosi 16 bitów. Wartość 0x80 wyrażona w zakresie 16 bitów jest liczbą 0000 0000 1000 00002. Należy zauważyć, że ustawiony bit nie jest równy bitowi znaku.Rozwiązanie problemu polega na zadeklarowaniu zmiennej ch jako zmiennej bez znaku (unsigned).

112

Page 113: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

10 Typy złożone

StrukturyZałóżmy, że piszemy program obsługujący magazyn. W magazynie znajdują się skrzynie, które zawierają różne części. Wszystkie części znajdujące się w jednej skrzyni są jednakowe, dlatego też nie musimy się martwiść o to, że zawartość skrzyń zostanie wymieszana.Na temat każdej skrzyni znane są następujące informacje:

• Nazwa części w niej przechowywanej (łańcuch o długości 30 znaków).• Liczba części określona ręcznie (liczba całkowita).• Cena (grosze - liczba całkowita).

W poprzednich wykładach do przechowywania grupy podobnych typów danych były stosowane tablice. Jednak w tym przykładzie mamy do czynienia z mieszanką dwóch liczb całkowitych i tekstu.Zamiast stosować tablicę zostanie użyty nowy typ danych określany terminem struktury. W przypadku tablicy wszystkie elementy są tego samego typu i są ponumerowane. W strukturze każdy element lub pole posiada nazwę oraz własny typ danych.Ogólna definicja struktury ma następującą postać:

struct nazwa_struktury {typ_pola nazwa_pola; /* komentarz */typ_pola nazwa_pola; /* komentarz */............

} nazwa_zmiennej;

Na przykład zdefiniujemy skrzynię, która będzie przechowywać kable do drukarki. Definicja struktury ma następującą postać:

struct bin {char name[30]; /" nazwa czesci */int quantity; /* liczba czesci w skrzyni */int const; / cena pojedynczej czesci (w groszach) */

} printer_cable_bin; /* miejsce przechowywania kabli do drukarki */

Powyższa definicja informuje język C o dwóch rzeczach. Pierwsza z nich określa format instrukcji struct bin. Instrukcja ta definiuje nowy typ danych, który może posłużyć do deklaracji innych zmiennych. Zmienna printer_cable_bin również jest deklarowana z użyciem tej instrukcji. Ponieważ struktura bin została zdefiniowana można ją zastosować w celu deklaracji dodatkowych zmiennych:

struct_bin_terminal_cable_box; /* miejsce przechowywania kabli do terminala */

113

Page 114: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Część definicji o nazwie nazwa_struktury może być pominięta:

struct {char name[30]; /* nazwa czesci */int quantity; /* liczba czesci w skrzyni */int cost; /* cene pojedynczej czesci (w groszach) /

} printer_cable_bin; /* miejsce przechowywania kabli do drukarki */

Zmienna printer_cable_bin została zdefiniowana, ale nie został utworzony żaden typ danych. Innymi słowy, zmienna ta będzie jedyną zmienną w programie zadeklarowaną w powyższej strukturze. Typem danych takiej zmiennej jest struktura anonimowa.Część nazwa_zmiennej może również być pominięta. Poniższy przykład definiuje strukturę typu nie zawierającą zmiennych:

struct bin {char name[30]; /* nazwa czesci */int quantity; /* liczba czesci w skrzyni */int cost; /* cena pojedynczej czesci (w groszach) */};

Można teraz w celu deklaracji zmiennych, takich jak printer_cable_bin, zastosować nowy typ danych (struct bin).W skrajnym przypadku zarówno część nazwa_zmiennej, jak i szęść nazwa_struktury mogą być pominięte. Części te są pod względem składni poprawne, ale zupełnie nieprzydatne.Zadeklarowaliśmy zmienną printer_cable_bin zawierającą trzy pola o nazwach: name, quantity i cost. Aby uzyskać do nich dostęp, użyjmy poniższej instrukcji:

zmienna.pole

Na przykład jeśli nagle okaże się, że cena kabli wzrosła do 12.95 złotych, musimy wykonać, następującą instrukcję:

printer_cable_bin.cost =1295; /* 12.95 zlotych jest nowa cena */

Aby obliczyć wartość całej zawartości skrzyni, wykonujemy poniższą instrukcję:

total_cost = printer_cable_bin.cost * printer_cable_bin.quantity;

Struktury mogą być inicjalizowane w chwili deklaracji poprzez umieszczenie listy elementów w nawiasach klamrowych ({ } ):

114

Page 115: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* Kable do drukarki */struct bin {

char name[30]; /* nazwa czesci */int quantity; /* liczba czesci w skrzyni */int cost; /* cena pojedynczej czesci (w groszach) */

} printer_cable_bin = {"Kable do drukarki", /* nazwa czesci znajdujacej sie w skrzyni */

0, /* na poczatku skrzynia jest pusta */1295 /* cena -- 12.95 zlotych */

};

UnieStruktura jest stosowana do definicji typu danych zawierającego kilka pól. Każde pole ma przydzieloną oddzielną przestrzeń, którą zajmuje. Przykładowo poniższa struktura:

struct rectangle {int width;int height;};

jest przechowywana w pamięci. Unia jest podobna do struktury, ale ma przydzielone jedno wspólne miejsce, w których jest przechowywanych wiele różnych nazw pól:

union value {long int i_value; /* zmienna typu calkowitego */float f _value; /* zmienna typu zmiennoprzecinkowego */};

Pole i_value oraz f_value dzielę tę samą przestrzeń pamięci.Strukturę można sobie wyobrazić jako wielkie pudło podzielone na kilka różnych komórek, z których każda ma inną nazwę. Unia jest też takim pudłem, ale tylko z jedną komórką i kilkoma różnymi etykietami umieszczonymi w jej wnętrzu.Rysunek 10.1 przedstawia struturę z dwoma polami każdemu polu jest przypisana inna część struktury. Unia zawiera tylko jedną komórkę, do której są przypisane różne nazwy.

115

z m ie n n a h e ig h t

z m ie n n a w id th

struktu ra restangle

z m ien n a i_ v a lu e / z m ie n n a f_ v a lu e

struktu ra va lue

Sche m a t s truk tu ry

Sc hem at u n ii

Rysunek 10.1. Struktura i unia

Page 116: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W przypadku struktury pola nie oddziałują wzajemnie na siebie. Modyfikacja jednego pola nie wpływa na pozostałe. W przypadku unii wszystkie pola zajmują tę samą przestrzeń, a więc tylko jedno z nich w danej chwili może być aktywne. Innymi słowy, jeśli jakaś wartość zostanie przypisana zmiennej i_value, przypisanie wartości zmiennej f_value spowoduje usunięcie starej wartości, którą przechowuje zmienna i_value.W wydruku 10.1 pokazano przykład zastosowania unii.

Wydruk 10.1. Zastosowanie unii/* Deklaracja zmiennej przechowujacej liczbe calkowita lub rzeczywista (ale nie obydwie) */union value {

long int i_value; /* Liczba rzeczywista */float f_value; /* Liczba zmiennoprzecinkowa */

} data;int i; /* Losowa liczba calkowita */float f; /* Losowa liczba zmiennoprzecinkowa */main(){

data.f _value = 5.0;data.i_value = 3; /* nadpisana wartosc data.f_value */i = data.i_value; /* prawidlowo */f = data.f_value; /* nieprawidlowo, wywola nieprzewidywalne wyniki */data.f_value = 5.5; /* przypisanie jakiejs wartosci f-value/i-value */i = data.i_value; /* nieprawidlowo, wywola nieprzewidywalne wyniki */return(0);

}

Unie są często wykorzystywane w branży telekomunikacyjnej. Załóżmy, że posiadamy taśmę umieszczoną w innym miejscu i chcemy wysłać do niej cztery komunikaty: „otwórz", „zamknij", „przeczytaj" oraz „zapisz". Dane zawarte w tych czterech komunikatach zmieniają się w szerokim zakresie zależnie od samych komunikatów.Komunikat „otwórz" musi zawierać nazwę taśmy, natomiast komunikat „zapisz" musi przenosić dane, które zostaną zapisane. Komunikat „przeczytaj" musi zawierać jak największą liczbę znaków, które zostaną odczytane, natomiast komunikat „zamknij" nie zawiera żadnych dodatkowych informacji.

116

Page 117: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

#define DATA_MAX 1024 /* Maksymalna ilosc danych do zapisu i odczytu */

struct open_msg {char name[30]; /* Nazwa tasmy */

};struct read_msg {

int length; /* Maksymalna predkosc odczytu */};struct write_msg {};

int length; /* Ilosc bajtow do zapisania */char data[DATA_MAX]; /* Zapisywane dane */};

struct close_msg {};const int OPEN_CODE=0; / Kod otwieranego komunikatu */const int READ_CODE=1; /* Kod odczytywanego komunikatu */const int WRITE_CODE=2 /* Kod zapisywanego komunikatu */const int CLOSE_CODE=3; /* Kod zamykanego komunikatu */struct msg {

int msg; /* Typ komunikatu */union {

struct open_msg open_data;struct read_msg read_data;struct write_msg write_data;struct close_msg close_data} msg_data;

};

Instrukcja typedefJęzyk C umożliwia programiście zdefiniowanie swoich własnych typów danych poprzez zastosowanie instrukcji typedef. lnstrukcja ta jest środkiem, dzięki któremu program może rozszerzyć podstawowe typy danych języka C. Ogólna postać instrukcji typedef jest następująca,

typedef deklaraca_ typu;

gdzie deklaracja_typu jest identyczna jak deklaracja zmiennej, poza tym że zamiast nazwy_ zmiennej jest użyta nazwa typu. Na przykład instrukcja

typedef int count;

definiuje nowy typ count, który jest taki sam jak typ całkowity.Tak więć poniższa deklaracja

117

Page 118: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

count flag;

jest identyczna jak ta deklaracja

int flag;

Na pierwszy rzut oka powyższa instrukcja niewiele różni się od tej:

#define count intcount flag;

Jednak instrukcje typedef mogą być stosowane do definiowania bardziej złożonych obiektów, co wykracza poza zakres możliwości prostej instrukcji #define. Oto przykład:

typedef int group[10];

Został teraz zdefiniowany nowy typ danych o nazwie group określający tablicę dziesięciu liczb całkowitych:

main() {typedef int group[10]; /* Utworzenie nowego typu 'group' */group totals; /* Przypisanie nowego typu danych zmiennej */for (i = 0; i < 10; i++)

totals[i] = 0;return (0);}

Częstym pryzkładem użycia instrukcji typedef jest definiowanie nowej struktury. Oto przykład:

struct complex _struct {double real;double imag;

};typedef struct complex _struct complex;complex voltag1 = {3.5, 1.2};

Typ wyliczeniowy enumWyliczeniowy typ danych został stworzony dla zmiennych, które przechowują tylko ograniczony zbiór wartości. Dostęp do tych wartości odbywa się poprzez nazwę (etykietę). Kompilator wewnętrznie przypisuje każdej takiej nazwie liczbę całkowitą. Rozważmy przykład aplikacji, w której zmienna będzie przechowywać dni tygodnia. Aby utworzyć wartości odpowiadające kolejnym dniom tygodnia w typie danych week_days, możemy zastosować deklarację const. Ma ona następującą postać:

118

Page 119: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

typedef int week_day; /* definicja typu week_days */ const int SUNDAY =0; const int MONDAY =1; const int TUESDAY =2; const int WEDNESDAY =3; const int THURSDAY =4; const int FRIDAY =5; const int SATURDAY =6; /* zastosowanie typu */ week_day today = TUESDAY;

Taka metoda jest niewygodna. Lepsza metoda polega na zastosowaniu typu wyliczeniowego enum:

enum week_day {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY};

/* zastosowanie typu */ enum week_day today = TUESDAY;

Ogólna postać instrukcji enum jest następująca:

enum nazwa_typu { etykieta-1, etyki eta-2, ... } nazwa_zmiennej

Podobnie jak w przypadku struktur, część nazwa_typu lub nazwa_zmiennej może być pominięta. Etykiety mogą być dowolnymi identyfikatorami dopuszczalnymi w języku C, ale zazwyczaj zawierają same małe litery.Język C implementuje typ wyliczeniowy enum jako zgodny z typem całkowitym, dlatego też całkowicie poprawne jest następujące określenie,

today = 5; /* 5 nie jest typu week_day */

chociaż niektóre kompilatory po napotkaniu takiej instrukcji wyświetlą ostrzeżenie. W języku C++ typ wyliczeniowy enum jest oddzielnym typem i nie jest kompatybilny z typem całkowitym.

Konwersja typów (rzutowanie)Czasem konieczna jest zamiana jednego typu zmiennej na inny. Zadanie to jest realizowane poprzez operacę rzutowania (ang. typecast operation). Ogólna postać rzutowania jest następująca:

(typ) wyrażenie

Operacja rzutowania nakazuje językowi C obliczenie wartości wyrażenia, a następnie konwersję jej typu na określony przez element typ. Rzutowanie jest szczególnie przydatne przy przetwarzaniu liczb całkowitych i zmiennoprzecinkowych:

119

Page 120: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

int won, lost; /* liczba gier dotad wygranych/przegranych */ float ratio; /* wspolczynnik wygrane/przegrane */ won = 5; lost = 3; ratio=won/lost; /*wspolczynnik ma wartosc 1.0 (niepoprawna wartosc)*/ /* Ponizsze wyrazenie obliczy prawidlowa wartosc wspolczynnika */ ratio = ((float) won) / ((float) lost);

Kolejnym powszechnym zastosowaniem operacji rzutowania jest konwersja wskaźników z jednego typu na inny.

Struktury upakowane oraz pola bitoweStruktury upakowane umożliwiają deklarowanie struktur w taki sposób, że zajmują one minimalną ilość miejsca. Na przykład poniższa struktura zajmuje 6 bajtów (na platformie 16-bitowej).

struct item { unsigned int list; /* jesli pozycja jest na liscie, pole ma wartosc true */ unsigned int seen; /* jesli pozycja zostala przypisana polu, /* wtedy ma ono wartosc true */ unsigned int number; /* numer pozycji */ };

Na rysunku 10.2 został pokazany schemat przechowywania zmiennych struktury upakowanej. Każda struktura zajmuje sześć bajtów dostępnej przestrzeni (dwa bajty dla każdego pola typu całkowitego).Pola list oraz seen mogą przechowywać tylko dwie wartości - 0 i 1- tak więc do ich reprezentacji jest wymagany tylko jeden bit. Nie planujemy nigdy przekroczyć granicy 16383 pozycji (0x3fff lub 14 bitów). W takim razie można zmodyfikować definicję struktury przy użyciu pól bitowych, co polega na umieszczeniu na końcu każdego pola znaku dwukropka oraz liczby określającej liczbę bitów, które zostaną przypisane polu. W efekcie struktura będzie zajmować tylko dwa bajty przestrzeni. Struktura ma postać:

120

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

{{{

pole list pole seen pole num ber

Rysunek 10.2. Struktura zwykła

Page 121: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

struct item { unsigned int list:1; /* jesli pozycja jest na liscie */ /* wtedy pole ma wartosc true */ unsigned int seen:1; /* jesli pozycja zostala przypisana polu, */ /* wtedy ma ono wartosc true */ unsigned int number:14; /* numer pozycji */ };

W tym przykładzie proilustrujemy kompilator, aby wykorzystał po jednym bicie dla pól list i seen oraz 14 bitów dla pola number. Tym sposobem dane mogą być upakowane na obszarze tylko dwóch bajtów. Zostało to pokazane na rysunku 10.3.

Struktury upakowane powinny być stosowane z umiarem. Wynikowy program, który wykonuje operację rozpakowania danych przechowywanych przez pola bitowe, ma stosunkowo dużą objętość i działa wolno. Jeśli nie występuje problem z brakiem wolnej przestrzeni, struktury upakowane nie powinny być stosowane.W temacie Preprocesor języka C, konieczne było zapisanie danych tekstowych oraz pięciu znaczników statusu liczących łącznie 8000 znaków. W takim przypadku zastosowanie dla każdego znacznika oddzielnego bajta pochłonęłoby sporą część przestrzeni (pięć bajtów dla każdego wprowadzonego znaku). Aby pomieścić pięć znaczników w jednym bajcie, zastosowano operacje bitowe. Inna metoda polegałaby na wykorzystaniu struktury upakowanej. Zostało to pokazane poniżej:

struct char_and_status { char character; /* Znak przekazany przez urzadzenie */ int error:1; /*Po wystapieniu dowolnego bledu przyjmuje wartosc true*/ int framing_error:1; /* Wystapil blad ramki */ int parity_error:1; /* Znak mial nieprawidlowa parzystosc */ int carrier_lost:1; /* Sygnal nosny zanikl */ int channel down:1; /* Urzadzenie komunikacyjne stracilo zasilanie */

Zastosowanie struktur upakowanych w przypadku znaczników jest bardziej czytelne i generuje mniej błędów niż przy użyciu operatorów bitowych. Jednak operatory bitowe dają programiście większą elastyczność, dlatego też

121

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

{ { >

pole lis t po le seen

pole num ber

Rysunek 10.3. Struktura upakowana

Page 122: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

sam powinien zdecydować, jaką metodę zastosować w zależności od tego, która z nich jest dla nas bardziej zrozumiała i przystępna.Tablice strukturStruktury i tablice mogą być ze sobą łączone. Załóżmy, że chcemy zarejestrować czas, jaki potrzebuje biegacz na jedno okrążenie w biegu składającym się z czterech rund. Aby zapisać czas, należy zdefiniować następującą strukturę:

struct time { int hour; /* godzina (zegar 24-godzinny ) */ int minute; /* 0-59 */ int second; /* 0-59 */ }; const int MAX_LAPS = 4; /* tylko cztery okrazenia */ /* najlepszy czas okrazenia w ciagu dnia */ struct time lap[MAX_LAPS];

Zastosujemy powyższą strukturę w taki oto sposób:

/* Biegacz wlasnie minal punkt pomiarowy */ lap[count].hour = hour; lap[count].minute = minute; lap[count].second = second; ++count;

Powyższa tablica może być zainicjalizowana przy uruchomieniu programu.Inicjalizacja tablicy struktur jest podobna do inicjalizacji tablic wielowymiarowych. Ma ona następującą postać:

struct time start_stop[2] = { {10, 0, 0}, {12, 0, 0} };

Załóżmy, że chcemy napisać program obsługujący listę wysyłkową. Etykiety kopert składają się z 5 wierszy i mają szerokość 60 znaków. Należy zdefiniować strukturę służącą do przechowywania nazwisk i adresów. Lista wysyłkowa dla większości wydruków będzie sortowana według nazwiska, natomiast na potrzeby samej listy zostanie zastosowane sortowanie według kodu pocztowego. Struktura takiej listy wysyłkowej ma następującą postać:

struct mailing { char name[60]; /* nazwisko, imie */ char address1[60]; /* dwa wiersze zawierajace ulice */ char address2[60]; char city[40]; char state[2]; /* dwuliterowy skrot */

122

Page 123: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

long int zip; /* kod pocztowy */ };

Można teraz zadeklarować tablicę przechowującą zawartość listy wysyłkowej:

/* Lista wysylkowa */ struct mailing list[MAX_ENTRIES];

Pole state składa się z dwóch elementów, ponieważ zostało przewidziane do przechowywania dwóch znaków. Pole to nie jest typu łańcuchowego, ponieważ nie została przydzielona wystarczająca ilość miejsca dla znaku końca łańcucha ('\ 0 ').

PodsumowanieStruktury i unie są zaliczane do grupy bardziej efektywnych elementów języka C. Dzięki nim nie jesteś już ograniczony tylko do wbudowanych typów danych języka C – możemy definiować własne typy. Jak się okaże w następnych rozdziałach, w celu tworzenia bardzo złożonych i wydajnych struktur danych struktury mogą być łączone ze wskaźnikami.

11. WSKAŹNIKI PROSTEIstnieją elementy i wskaźniki do nich. Zrozumienie różnicy pomiędzy nimi jest bardzo ważne. Zostało to pokazane na rysunku 11.1.Tutaj w celu zobrazowania elementu posłużyliśmy się sześcianem. Nazwa zmiennej jest umieszczona poniżej jego dolnej krawędzi. W tym przypadku zmienna ma nazwę thing. Jej wartością jest liczba 6.

Adresem zmiennej thing jest wartość 0x1000. Adresy są automatycznie przypisywane każdej zmiennej przez kompilator. Zwykle nie musimy się w ogóle przejmować adresami zmiennych, ale musimy wiedzieć o ich istnieniu.Wskaźnik thing_ptr wskazuje na zmienną thing. Wskaźniki są też określane terminem zmiennych adresowych (ang. address variables), ponieważ zawierają adresy innych zmiennych. W tym przypadku wskaźnik zawiera adres 0x1000. Ponieważ jest to adres zmiennej thing, można powiedzieć, że wskaźnik thing_ptr wskazuje na zmienną thing.Zmienne i wskaźniki są bardzo podobne do ulic i numerów domów. Na przykład adresem może być: “Krakowska 10”. Domy mogą być różnej

123

0 1 0 0 0w s k a z n ik th in g _ p tr

Elem ent W skaznik

z m ie n n a th in g 0 1 0 0 0

Rysunek 11.1. Element i jego wskaźnik

Page 124: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

wielkości i o różnym wyglądzie. Adresy są w przybliżeniu tego samego formatu (ulica, miasto, województwo, kod pocztowy). Tak więc adres “Krakowska 10” może wskazywać na bardzo duży biały budynek, natomiast adres “Warszawska 16” może kierować do jednopokojowej chaty, ale nadal oba adresy mają identyczny format.Podobna sytuacja ma miejsce w języku C. Pomimo że elementy mogą mieć różne rozmiary, to jednak wskaźniki są jednego rozmiaru (stosunkowo niewielkiego).Wielu początkujących programistów ma problemy ze wskaźnikami i ich zawartością. Aby temu zapobiec, wszystkie zmienne wskaźnikowe będą zakończone rozszerzeniem _ptr. Taką konwencję zalecamy stosować we własnych programach. Pomimo że taka notacja nie jest zbyt popularna, to jednak jest wyjątkowo pożyteczna.Wiele różnych zmiennych adresowych może wskazywać na ten sam element. Jest to również zgodne z prawdą w przypadku ulic. W tabeli 11.1 zebrano listę lokalizacji ważnych usług dostępnych w małym mieście.W tym przypadku w budynku państwowym świadczonych jest kilka usług. Pomimo że jest to ten sam adres, wskazują na niego trzy różne wskaźniki.

Tabela 11.1. Książka adresowa miasta Mielec

Usługa (nazwa wskaźnika)

Adres (wartość adresu) Budynek (zmienna)

Straż pożarna

Policja

Biuro projektowe

Stacja benzynowa

Główna ulica 1

Główna ulica 1

Główna ulica 1

Główna ulica 2

Ratusz

Ratusz

Ratusz

Stacja benzynowa CPN

Jak się okaże w dalej, wskaźniki mogą posłużyć jako szybki i prosty sposób dostępu do tablicy. W kolejnych rozdziałach zostaną omówione wskaźniki stosowane przy tworzeniu nowych zmiennych oraz złożonych struktur danych, takich jak listy dowiązań i drzewa. Wskaźnik jest deklarowany poprzez umieszczenie gwiazdki (*) na początku nazwy zmiennej znajdującej się w deklaracji:

int thing; /* definicja zmiennej thing */ int *thing_ptr; /* definicja wskaznika do zmiennej thing */

124

Page 125: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W tabeli 11.2 zostały zebrane operatory stosowane w połączeniu ze wskaźnikami.

Tabela 11.2. Operatory stosorvane ze wskaźnikami

Operator Znaczenie*&

Dereferencja (odwołanie się do danych wskazywanych przez wskaźnik)Pobranie adresu zmiennej (wskazuje na adres zmiennej)

Operator & zwraca adres zmiennej będącej wskaźnikiem. Operator * zwraca obiekt, na który wskazuje wskaźnik. Operatory te z łatwością mogą wywołać zamieszanie. W tabeli 11.3 zawarto składnię różnych operatorów wskaźnikowych.

Tabela 11.3. Składnia operatorów rvskaźnikowych

Kod w języku C Opisthing&thingthing_ptr

*thing_ptr

Zwykła zmienna thingWskaźnik do zmiennej thingWskaźnik do liczby całkowitej (może być lub nie wartością zmiennej thing)Liczba całkovita

Przyjrzyjmy się typowym zastosowaniom różnych operatorów wskaźnikowych:

int thing; /* Deklaracja zmiennej thing typu calkowitego */ thing = 4;

Thing jest zmienną. Deklaracja int thing nie zawiera operatora *, a zatem zmienna, thing nie jest wskaźnikiem:

int *thing_ptr; /* Deklaracja wskaznika zmiennej thing */

Zmienna thing_ptr jest wskaźnikiem. Operator * w deklaracji oznacza, że jest to wskaźnik. Dodano również do jego nazwy rozszerzenie _ptr:

thing_ptr = &thing; /* Wskaznik do zmiennej thing */

Wyrażenie &thing jest wskaźnikiem do zmiennej thing. Zmienna thing jest obiektem. Operator pobrania adresu & pobiera adres obiektu (wskaźnika), a zatem wyrażenie &thing jest wskaźnikiem. Po przypisaniu go do wskaźnika thing_ptr uzyskujemy:

*thing_ptr = 5; /* Ustawienie wartosci zmiennej thing na 5 */

125

Page 126: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* Moze wskazywac lub nie na */ /* okreslona zmienna typu calkowitego */

Wyrażenie *thing_ptr wskazuje na zmienną thing. Zmienna thing_ptr jest wskaźnikiem. Operator dereferencji * informuje język C, aby szukał danych w zmiennej, na którą wskazuje, a nie w samym wskaźniku. Należy zauważyć, że wskazuje on na dowolną zmienną typu całkowitego, a zatem może (lub nie) wskazywać na konkretną zmienną thing.Poniżej zawarto przykład nieprawidłowego zastosowania operacji wskaźnikowych. Poniższa instrukcja:

*thing

jest niepoprawna. W instrukcji tej do języka C jest kierowane żądanie do obiektu wskazywanego przez zmienną thing. Ponieważ zmienna thing nie jest wskaźnikiem, a więc powyższa instrukcja jest nieprawidłowa. Operacja:

&thing_ptr

jest poprawna, ale trochę dziwna. thing_ptr jest wskaźnikiem. Operator pobrania adresu & pobiera wskaźnik do obiektu (w tym przypadku wskaźnika thing_ptr ).Wynikiem operacji jest wskaźnik do wskaźnika.W wydruku 11.1 pokazano przykład zastosowania wskaźników. Zadeklarowano jeden obiekt, jedną zmienną thing oraz wskaźnik thing_ptr. Poniższa instrukcja deklaruje zmienną thing:

thing = 2;

Instrukcja thing_ptr = &thing;powoduje przypisanie wskaźnikowi thing_ptr adresu zmiennej thing. Od tego momentu zmienna thing oraz wyrażenie *thing_ptr są jednakowe.

Wydruk 11.1#include <stdio.h>int main() { int thing_var; /* definicja zmiennej thing_var */ int *thing_ptr; /* definicja wskaznika do zmiennej thing_var */ thing var = 2; /* przypisanie wartosci zmiennej thing_var */ printf("Zmienna thing_var ma wartosc %d\n", thing_var); thing_ptr=&thing_var; /* wskaznik wskazuje na zmienna thing_var */ *thing_ptr = 3; /* wskaznik thing_ptr wskazuje na zmienna thing_var, */ /* a wiec jej wartosc wynosi teraz 3 */ printf("Zmienna thing_var ma wartosc %d\n", thing_var);

126

Page 127: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* kolejne wywolanie funkcji printf */ printf ("Zmienna thing_var ma wartosc %d\n", *thing_ptr); return (0); }

Kilka wskaźników może wskazywać na tę samą zmienną:

1: int something; 2: 3: int *first_ptr; /* pierwszy wskaznik */ 4: int *second_ptr; /* drugi wskaznik */ 5: 6: something = 1; /* przypisanie zmiennej wartosci */ 7: 8: first_ptr = &something; 9: second_ptr = first_ptr;

W wierszu 8. w celu przypisania wskaźnika first_ptr do zmiennej something zostal użyty operator &. Ponieważ first_ptr oraz second_ptr są wskaźnikami, możemy wykonać bezpośrednie przypisanie zawarte w wierszu 9.

Po wykonaniu powyższego fragmentu programu uzyskamy sytuację zobrazowaną na rysunku 11.2.Należy zauważyć, że spośród trzech zmiennych tylko jedna z nich jest typu całkowitego (zmienna something). Poniższe instrukcje są sobie równoważne:

something = 1; *first_ptr = 1; *second_ptr = 1;

Wskaźniki jako argumenty funkcjiW języku C parametry są przekazywane do funkcji przez wartość. Oznacza to, że parametry są przekazywane funkcji tylko w jeden sposób. Jedynym wynikiem działania funkcji jest pojedyncza wartość zwracana. Ograniczenie to może być ominięte przy użyciu wskaźników.

127

w s k a n ik se k o n d _ p tr

ź0 x 1 0 0 0

z m ie n n a so m e th in g 0 x 1 0 0 0

w s k a n ik f i rs t_ p tr

ź0 x 1 0 0 0

Rysunek 11.2. Dwa wskaźniki i zmienna

Page 128: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wyobraźmy sobie, że są dwie osoby – Piotr i Paweł. Za każdym razem, gdy się spotykają tylko Piotr może mówić, a Paweł musi ograniczyć się do słuchania. W jaki sposób Paweł może przekazać Piotrowi dowolną informację? To proste: Piotr musi jedynie powiedzieć Pawłowi: “Chciałbym, abyś zostawił swoją odpowiedź w skrzynce pocztowej znajdującej się pod adresem Krakowska 10/15”.W języku C jest stosowana podobna metoda przekazywania informacji z funkcji do jej wywołania. W wydruku 11.2 funkcja main żąda, aby funkcja inc_count zwiększyła wartość zmiennej count.Bezpośrednie przekazanie żądania nie zadziała, dlatego też jest przekazywany wskaźnik. (“To jest adres zmiennej, dla której ma być zwiększona wartość”). Należy zauważyć, że prototyp funkcji inc_count zawiera obok parametru wyrażenie int *. Oznacza to, że jedyny parametr funkcji jest wskaźnikiem do zmiennej typu całkowitego, a nie samą zmienna.

Wydruk 11.2 #include <stdio.h> void inc_count(int *count_ptr) { ++(*count_ptr); } int main() { int count = 0; /* licznik petli */ while (count < 10) inc_count(&count); return (0);

} Powyższy program został w graficzny sposób omówiony na rysunku 11.3. Należy zauważyć, że sam parametr nie jest modyfikowany. Modyfikowane jest to, na co on wskazuje.Istnieje jeszcze jeden specjalny wskaźnik

o nazwie NULL. Wskaźinik ten nie wskazuje niczego. (Odpowiada mu wartość liczbowa równa 0). Standardowy

128

Rysunek 11.3. Wywolanie funkcji inc_count

Rysunek 11.4. Wskaźnik NULL

Page 129: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

plik dołączany locale.h zawiera definicję stałej NULL. (Plik ten zwykle nie jest bezpośrednio dołączany, ale odwołują się, do niego inne pliki dołączane, takie jak stdio.h oraz stdlib.h). Wskaźnik NULL został w graficzny  sposób omówiony na rysunku 11.4.Wskaźniki stałychDeklarowanie wskaźnika stałej jest trochę nietypowe. Na przykład następująca deklaracja:

const int result = 5;

informuje język C, że zmienna result jest stałą, a zatem instrukcja:

result = 10; /* niepoprawna */

jest niepoprawna. Poniższa deklaracja:

const char *answer_ptr = "Czterdziesci dwa";

nie informuje języka C, że zmienna answer_ptr jest stała, ale że są nią dane, na które ona wskazuje. Dane te nie mogą być modyfikowane, ale wskaźnik do nich tak. W tym miejscu jeszcze raz upewnimy się, że rozumiemy różnicę pomiędzy zmiennymi i wskaźnikami do nich.Czym jest answer_ptr? Jest to wskaźnik. Czy można go zmodyfikować? Tak, ponieważ jest to tylko wskaźnik. Na co on wskazuje? Wskazuje na tablicę const char. Czy można modyfikować dane, na które wskazuje wskaźnik answer_ptr? Nie można, ponieważ jest to stała.W języku C ma to następującą postać:

answer_ptr = "Piecdziesiat jeden”; /* Prawidlowo (wskaznik */ /* answer_ptr jest zmienna) */*answer_ptr = ’X’; /* Nieprawidlowo (wskaznik *answer_ptr jest stala) */

Jeśli słowo kluczowe const zostanie wstawione za operatorem *, język C „będzie wiedział”, że wskaźnik jest stałą.Oto przykład:

char *const name_ptr = "Test";

Czym jest name_ptr? Jest to wskaźnik stałej. Czy może być modyfikowany? Nie. Na co on wskazuje? Na znak. Czy można modyfikować dane, na które wskazuje wskaźnik name_ptr? Tak, można.

name_ptr = "Nowy"; /* Nieprawidlowo (wskaznik name_ptr jest stala) */*name_ptr = 'B'; /* Prawidlowo (wskaznik * name_ptr jest znakiem) */

Można również umieścić słowo kluczowe const w obydwu miejscach, tworząc wskaźnik, który nie może wskazywać na niemodyfikowalny element.

129

Page 130: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

const char *const title_ptr = "Tytul";

Wskaźniki i tabliceJęzyk C pozwala wykonywać operacje arytmetyczne (dodawanie i odejmowanie) z użyciem wskaźników. Zapiszmy następujący program:

char array[5]; char *array_ptr = &array[0];

W powyższym przykładzie wyrażenie *array_ptr jest tym samym co wyrażenie array[0], natomiast wyrażenie *(array_ptr+1) jest identyczne z wyrażeniem array[1], a wyrażenie *(array_ptr+2) jest równoznaczne wyrażeniu array[2], i tak dalej. Należy zwrócić uwagę na zastosowanie nawiasów. Arytmetyka wskaźnikowa została wyjaśniona w sposób graficzny na rysunku 11.5.

Rysunek 11.5. Tablica wskaźników

Jednak wyrażenie (*array_ptr)+1 nie jest tym samym, co wyrażenie array[1]. Dla tego że +1 jest poza nawiasami, dlatego też dodane zostało za operatorem dereferencji, a zatem wyrażenie (*array_ptr)+1 jest tym samym, co wyrażenie array[0]+1.Na pierwszy rzut oka taka metoda może wydawać się skomplikowanym sposobem reprezentacji prostych indeksów tablicy. Teraz zaczęliśmy od prostych operacji arytmetycznych na wskaźnikach, ale w kolejnych tematach zostaną użyte bardziej złożone wskaźniki, które lepiej radzą sobie z bardziej skomplikowanymi funkcjami.Elementy tablicy są przypisywane do kolejnych adresów. Na przykład element tablicy array[0] może być umieszczony pod adresem 0xff000024, natomiast element tablicy array[1] pod adresem 0xff000025, i tak dalej. Taka struktura oznacza, że do odnalezienia

130

Page 131: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

dowolnego elementu tablicy może być zastosowany wskaźnik. W wydruku 11.3 są wyświetlane elementy i adresy prostej tablicy zawierającej znaki.

Wydruk 13.3 #include <stdio.h> #define ARRAY_SIZE 10 /* Liczba znakow w tablicy */ /* Tablica do wyswietlenia */ char array[ARRAY_SIZE] = "0123456789"; int main() { int index; /* Indeks tablicy */ for (index = 0; index < ARRAY_SIZE; ++index) { printf("&array[index]=0x%p (array+index)=0x%p array[index]=0x%x\n", &array[index], (array+index), array[index]); } return (0); }

W przypadku wyświetlania wskaźników należy zastosować specjalny wzorzec konwersji %p.Program po uruchomieniu wyświetli następujące wyniki:

&array[index] (array+index) array[index] 0x20a50 0x20a50 0x30 0x20a51 0x20a51 0x31 0x20a52 0x20a52 0x32 0x20a53 0x20a53 0x33 0x20a54 0x20a54 0x34 0x20a55 0x20a55 0x35 0x20a56 0x20a56 0x36 0x20a57 0x20a57 0x37 0x20a58 0x20a58 0x38 0x20a59 0x20a59 0x39

Znaki zajmują jeden bajt, dlatego też elementom tablicy znaków zostaną przypisane wartości kolejnych adresów. Ścionka typu short int zajmuje dwa bajty, a zatem w przypadku tablicy typu short int wartości adresów będą zwiększane o dwa. Czy to oznacza, że wyrażenie array+1 może być zastosowane tylko w przypadku znaków? Odpowiedź brzmi - nie. W języku C operacje arytmetyczne na wskaźnikach są skalowane automatycznie, a więc powyższe wyrażenie może być zastosowane. W tym przypadku wyrażenie array+1 wskazuje na element 1.Język C pozwala zapisać operacje na tablicach o prostszy sposób. Zamiast instrukcji:

131

Page 132: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

array_ptr = &array[0];

mozna użyć jej krótszej wersji:

array_ptr = array;

Język C zaciera różnice istluejące pomiędzy wskaźnikami i tablicami, w wielu przypadkach traktując je tak samo. Użyjemy teraz zmiennej array jako wskaźnika, a język C automatycznie wykona odpowiednią konwersję.W wydruku 11.4 jest obliczana liczba elementów, których wartość jest róźna od zera. Po znalezieniu elementu o wartości 0 program się zatrzymuje. Nie jest wykonywana kontrola ograniczenia, dlatego też w tablicy musi występować przynajmniej jeden element o wartości zero.

Wydruk 11.4#include <stdio.h>int array[] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3};int index;int main() { index = 0; while (array[index] != 0) ++index; printf("Liczba elementow przed wystapieniem wartosci zero %d\n", index); return (0);}

Wydruk 11.5 jest inną wersją programu z wydruku 11.4 wykorzystującą wskaźniki.

Wydruk 11.5 #include <stdio.h> int array[] = {4, 5, 8, 9, 8, 1, 0, 1, 9, 3}; int *array_ptr; int main() { array_ptr = array; while ((*array_ptr) != 0) ++array_ptr; printf("Liczba elementow przed wystapieniem wartosci zero %d\n", array_ptr - array); return (0); }

Należy zauważyć, że aby sprawdzić zawartość tablicy, należy zastosować operator dereferencji (*). Operator ten jest stosowany w następującej instrukcji:

While ((*array_ptr) != 0)

132

Page 133: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Aby zmodyfikować sam wskaźnik, nie jest potrzebny żaden operator. Na przykład poniższa instrukcja

++ array_ptr;

zwiększa wartość wskaźnika, ale nie danych, na które on wskazuje.W wydruku 11.4 jest użyte wyrażenie ((array[index]) != 0). Wyrażenie to żąda od kompilatora wykonania operacji indeksowania, które trwa dłużej niż operacja wykonana przez operator dereferencji zastosowany w wyrażeniu ((*array_ptr) != 0).Wyrażenie array_ptr - array zawarte na końcu kodu programu oblicza, na który element tablicy wskazuje wskaźnik array_ptr.Po przekazaniu tablicy do procedury język C automatycznie zamieni ją na wskaźnik. Tak naprawdę, jeśli przed tablicą zostanie wstawiony operator &, język C wygeneruje ostrzeżenie. W wydruku 11.6 zostały pokazane różne metody przekazywania tablicy do funkcji.

Wydruk 13.6 #define MAX 10 /* Rozmiar tablicy */ /******************************************************** * init_array_1 -- Tablica bez wartosci zero * * Parametry * * data -- tablica rowna zero * ********************************************************/ void init_array_1(int data[]) { int index; for (index = 0; index < MAX; ++index) data[index] = 0; } /******************************************************** * init_array_2 -- Tablica wartosci zero * * Parametry * * data_ptr -- wskaznik do tablicy rownej zero * ********************************************************/ void init_array_2(int *data_ptr) { int index; for (index = 0; index < MAX; ++index) *(data_ptr + index) = 0; } int main () { int array[MAX]; void init_array_1(); void init_array_2(); /* pierwsza metoda inicjalizacji tablicy */ init_array_1(array); /* druga metoda inicjalizacji tablicy */ init_array_1(&array[0]); /* tez dziala, ale kompilator wygeneruje ostrzezenie */ init_array_1(&array);

133

Page 134: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

/* podobnie jak w pierwszej metodzie, ale funkcja jest inna */ init_array_2(array); return (0); }Jak nie używać wskaźnikówGłównym celem tego wykałdu jest nauczenie tworzenia przejrzystego i czytelnego programu. Niestety niektórzy nadal wierzą, że pisany przez nich program powinien być jak najbardziej zwarty. Takie przekonanie doprowadza do sytuacji, gdzie operatory ++ oraz -- są stosowane wewnątrz innych instrukcji.W wydruku 11.7 pokazano kilka przykładów jednoczesnego zastosowania operatorów inkrementacji i wskaźników.

Wydruk 11.7. Niepoprawne użycie wskaźników/*Program ten prezentuje styl programowania, ktory NIE powinien byc powielany*//* Niestety zbyt wielu programistow go stosuje */int array[10]; /* tablica zawierajaca przetwarzane dane */int main() { int data_ptr; /* Wskaznik do danych */ int value; /* Dane */ data_ptr=&array[0]; /* Wskazuje na pierwszy element */ value=*data_ptr++; /* Pobiera element #0, wskaznik data_ptr */ /* wskazuje na element #1 */ value=*++data_ptr; /* Pobiera element #2, wskaznik data_ptr */ /* wskazuje na element #2 */ value=++*data_ptr; /* Inkrementuje wartosc elementu #2, zwraca */ /* jego wartosc */ /* Zostaje tylko wskaznik data_ptr */

134

v a l u e = * d a t a _ p t r + + ;

v a l u e = * + + d a t a _ p t r ;

v a l u e = + + * d a t a _ p t r ;

P o b ie ra w arto ść w ska z yw a n ąp rze z w ska źn ik data_ ptr

In k re m e n tu je w arto śc i w skaź n ika data_ ptr

In k re m e n tu je w arto śc i w skaź n ika data_ ptr

P o b ie ra w arto ść w ska z yw a n ąp rze z w s ka źn ik p o

w ykon a n iu in kre m e nta c ji

data_ ptr S to su je o p e rato r de re fe ren c ji

i zw ra c a w a r to ś ć ta b lic y array

In k re m e n tu je w arto ść ta b lic y array

Rys. 11.6. Analiza operacji stosujących wskaźniki

Page 135: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Aby zrozumieć instrukcje zawarte powyżej oraz ich przeznaczenie, należy z uwagą przyjrzeć się każdej z nich. W momencie, gdy zajmuję się konserwacją już napisanego programu, nie chciałbym jeszcze mieć na głowie analizy przeznaczenia każdej instrukcji, a więc nie piszcie swoich programów w podobny sposób. Na rysunku 11.6 powyższe instrukcje zostały poddane analizie.Poniższy przykład popada trochę w skrajność, ale pokazuje jak efekty boczne mogą z łatwością wywołać zamieszanie.Wydruk 11.8 zawiera przykład programu, który najprawdopodobniej nas zainteresuje. Program ten kopiuje łańcuch z jednego miejsca (q) w inne (p).

Wydruk 11.8. Ukryte zastosowanie wskaźników void copy_string(char *p, char *q) { while (*p++ = *q++); }

Przy odrobinie chęci programista zrozumie działanie powyższego programu. Jednak zadanie to stanie się o wiele łatwiejsze, jeśli program będzie podobny do tego z wydruku 11.9.

Wydruk 13.9. Lepiej opisane zastosowanie wskaźników /************************************************************* * copy_string -- Kopiuje jeden lancuch do innego. * * Parametry * * dest -- miejsce wstawienia lancucha * * source -- miejsce pobrania lancucha * **************************************************************/ void copy_string(char *dest, char *source) { while (1) { *dest = *source; /* konczy po skopiowaniu konca lancucha */ if (*dest == '\0') return; ++dest; ++source; } }

Użycie wskaźników do podziału łańcuchaZałóżmy, że mamy łańcuch w postaci “Ostatni - Pierwszy”. Chcemy go podzielić na dwie części, z których jedna będzie przechowywać imię, natomiast druga nazwisko.

135

Page 136: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Potrzebna będzie funkcja wyszukująca w łańcuchu znak “/”. Funkcja standardowa strchr wykona tę operację za nas. W programie tym w celu demonstracji działania funkcji postanowiliśmy stworzyć jej kopię.Funkcja ta jako argumenty pobiera wskaźnik do łańcucha (string_ptr) oraz szukany znak (find). W funkcji jest najpierw wykonywana pętla while do momentu znalezienia szukanego znaku lub wskutek zatrzymania przez instrukcję programu zawartego poniżej.

while (*string_ptr != find) {

Następnie jest dokonywane sprawdzenie, czy nie została przekroczona długość łańcucha. W takim przypadku wskaźnik (string_ptr) wskaże na znak końca łańcucha. Jeśli przed znalezieniem szukanego znaku zostanie osiągnięty koniec łańcucha, funkcja zwróci wartość NULL:

if (*string_ptr == '\0') return (NULL);

Jeśli dojdziemy do tego miejsca i nadal szukany znak nie zostanie znaleziony, a ponadto nie doszliśmy do końca łańcucha, przesuwamy wskaźnik do następnego znaku i w celu ponownego rozpoczęcia operacji wracamy na początek pętli:

++string_ptr; }

Program główny wczytuje pojedynczy wiersz i usuwa z niego znak nowego wiersza. W celu określenia położenia znaku “/” jest wywoływana funkcja my_strchr.W tym miejscu wskźanik last_ptr wskazuje na pierwszy znak nazwiska, natomiast wskaźnik first_ptr wskazuje na znak “/”. Kolejną operacją

jest podzielenie łańcucha poprzez zastąpienie znaku “/” znakiem końca łańcucha (NULL lub \0). Teraz wskaźnik last_ptr wskazuje tylko na nazwisko, natomiast wskaźnik first_ptr wskazuje na łańcuch pusty. Przesunięcie wskaźuka first_ptr do następnego znaku sprawi, że będzie on wskazywał na początek imienia.Kolejność operacji wykonanych w celu podziału łańcucha została pokazana na rysunku 11.7.

136

smith/j

nho

la s t_ p tr

f irs t_ p tr

la s t_ p tr smith

\0j

nho

* f irs t_ p tr

smith

j

nho

la s t_ p tr

f irs t_ p tr

P o

strchr

P o

*first_ptr = '\0 ';

P o

first_ptr++;

Rysunek 11.7. Podział łańcucha

Page 137: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W wydruku 11.10 został zawarty kompletny program, który demonstruje zastosowanie wskaźników i tablic znaków do prostych operacji przetwarzania łańcucha.

Wydruk 11.10 #include <stdio.h> #include <string.h> #include <stdlib.h> /**************************************************************** * my_strchr -- szuka znaku w lancuchu * * Kopia funkcji standardowej, * * utworzona dla celow demonstracyjnych. * * Parametry * * string_ptr --- lancuch wyszukujacy * * find -- szukany znak * * Wartosc zwracana * * wskaznik wskazujacy pierwsze wystapienie znaku * * w lancuchu lub wartosc NULL w przypadku bledu * *****************************************************************/ char *my_strchr(char *string_ptr, char find) { while (*string_ptr != find) { /* kontrola konca lancucha */ if (*string_ptr == '\0') return (NULL); /* nie znaleziony */ ++string_ptr; } return (string_ptr); /* znaleziony */ } int main() { char line[80]; /* dane wejsciowe */ char *first_ptr; /* wskaznik do imienia */ char *last_ptr’; /* wskaznik do nazwiska */ fgets(line, sizeof(line), stdin); /* usuniecie znaku nowego wiersza */ line[strlen(line)-1] = '\0'; last_ptr = line; /* nazwisko na poczatku wiersza */ first_ptr = my_strchr(line, '/'); /* szuka znaku '/' */ /* wykrywanie bledu "/ if (first_ptr NULL) { fprintf(stderr, "Blad: Nie znaleziono znaku / w %s\n", line); exit (8); }

137

Page 138: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

*first_ptr = '\0'; /* ustawienie wartosci zero */ ++first_ptr; /* przesuniecie do pierwszego znaku imienia */ printf("Imie:%s Nazwisko:%s\n", first_ptr, last_ptr); return (0); }Pytanie 11.1. Program z wydruku 11.11 powinien wyświetlić następujący wynik,

Nazwa pliku: tmp1

ale zamiast tego uzyskujemy coś takiego: Nazwa pliku: !_@$#ds80

(Uzyskane wyniki mogą się róznić). Dlaczego tak się dzieje?

Wydruk 11.11 #include <stdio.h> #include <string.h> /***************************************************************** * tmp_name -- zwraca nazwe pliku tymczasowego * * Pryz kazdym wywolaniu tej funkcji, zostanie zwrocona * * inna nazwa pliku. * * Wartosc zwracana * * Wskaznik do nowej nazwy pliku. * *****************************************************************/ char *tmp_name(void) { char name[30]; /* Generowana nazwa */ static int_sequence = 0; /* Kolejna liczba dla ostatniej cyfry */ ++sequence; /* Przesuniecie do nastepnej nazwy pliku */ strcpy(name, "tmp"); /* ale dla kolejnej cyfry */ name[3] = sequence + '0'; /* koniec lancucha */ name[4] = '\0'; return(name); } int main() { char *tmp_name(void); /* pobranie nazwy pliku tymczasowego */ printf("Nazwa pliku: %s\n", tmp_name()); return(0); }

Odpowiedź 11.1. Problem polega na tym, że zmienna name jest zmienną tymczasową. W momencie wywołania funkcji kompilator rezerwuje miejsce dla zmiennej name i następnie po zakończeniu jej działania zwalnia je. Funkcja przypisuje zmiennej name poprawną wartość i zwraca do niej

138

Page 139: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

wskaźnik. Jednak po zakończeniu działania funkcji zmienna name znika i pozostaje wskaźnik wskazujący na nieprawidłową wartość.Rozwiązanie polega na zastosowaniu deklaracji name static. W ten sposób zmienna name będzie zmienną stałą i nie zniknie wraz z zakończeniem działania funkcji.Wskaźniki i strukturyW temacie Typy złożone, została zdefiniowana struktura dla listy wysyłkowej:

struct mailing { char name[60]; /* nazwisko, imie */ char address1[60]; /* dwa wiersze zawierajace ulice */ char address2[60]; char city[40]; char state[2]; /* dwuliterowy skrot */ long int zip; /* kod pocztowy */ } list[MAX_ENTRIES];

Lista wysyłkowa musi być często sortowana według nazwy i kodu pocztowego. Możliwe byłoby sortowanie samych wpisów, ale każdy z nich ma długość 226 bajtów. Jest to spora ilość danych do przetworzenia. Jedno z rozwiązań tego problemu polega na zadeklarowaniu tablicy wskaźników, a następnie wykonaniu sortowania wskaźników:

/* Wskaznik do danych */ struct mailing *list_ptrs[MAX_ENTRIES]; int current; /* aktualny wpis na liscie wysylkowej */ for (current = 0; current == number_of_entries; ++current) list_ptrs[current] = &list[current]; /* Sortuje wskaznik list_ptrs wedlug kodu pocztowego */

Teraz zamiast konieczności przetwarzania struktury o rozmiarze 226 bajtów można przesuwać 4-bajtowe wskaźniki. Operacja sortowania będzie o wiele szybsza. Wyobraźmy sobie magazyn pełen dużych ciężkich skrzyń, które trzeba szybko odnajdywać. Można by poustawiać je alfabetycznie, ale wiązałoby się to z mnóstwem pracy. Zamiast tego można każdemu miejscu przypisać numer, zapisać nazwę i numer w kartach indeksowych, a następnie posortować je według nazwy.

Argumenty wiersza poleceńProcedura main tak naprawdę pobiera dwa argumenty. Są nimi: argc oraz argv2:2 Właściwie nazwa argumentów może być dowolna. Jednak w przypadku 99,9% programów są one nazywane argc i argv. Gdy większości programistów przytrafi się ten brakujący 0,1%, denerwują się po czym zmieniają nazwy argumentów na argc i argv.

139

Page 140: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

main(int argc, char *argv[]) {

Jeśli zauważymy, że argumenty te są ustawione w kolejności alfabetycznej, w łatwy sposób możemy zapamiętać, który z nich jest pierwszy.Parametr argc określa liczbę argumentów występujących w wierszu poleceń (włączając w to nazwę programu). Tablica argv zawiera same argumenty. Na przykład, jeśli program args zostanie uruchomiony z poniższego wiersza poleceń,

args to jest test

zostanie wyświetlony następujący wynik:

argc = 5 argv[0] = "args" argv[1] = "to" argv[2] = "jest" argv[3] = "test" argv[4] = NULL

Powłoka systemu UNIX przed wysłaniem do programu opcji wiersza poleceń przetwarza znaki wieloznaczne, takie jak *, ? oraz [].Kompilatory Turbo C++ oraz Borland C++ przetwarzają znaki wieloznaczne w przypadku, gdy do programu jest dołączany plik WILDARG.OBJ. Prawie wszystkie polecenia systemu UNIX stosują standardowy format wiersza poleceń. Standard ten został przeniesiony do innych środowisk. Typowe polecenie systemu UNIX ma następującą postać:

polecenie opcje plik1 plik2 plik3 ...

Opcje są poprzedzane znakiem “-” i zwykle jest to jedna litera. Na przykład opcja –v włącza dla określonego polecenia tryb pełnej informacji. Jeśli opcja dodatkowo pobiera, parametr, jest on umieszczany za symbolem opcji. Na przykład opcja -m1024 ustawia maksymalną liczbę symboli na 1024, natomiast opcja -ooutfile ustawia plik wyjściowy o nazwie outfile.Przyjrzyjmy się tworzeniu programu, który wczytuje argumenty wiersza poleceń i odpowiednio je przetwarza. Program ten formatuje i drukuje pliki. Część dokumentacji programu została zawarta poniżej:

Print_file [-v] [-lliczba] [-onazwa] [plikl] [plik2] …

gdzie: -v włącza tryb pełnej informacji, w czasie działania programu są

wyświetlane różne komunikaty

140

Page 141: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

-lliczba ustawia rozmiar strony wyrażony w liczbie wierszy (wartość domyślna = 66)

-onazwa ustawia nazwę pliku wyjściowego (nazwa domyślna = print.out)plik1, plik2,... jest to lista plików do wydrukowania. Jeśli nie określono

żadnego pliku, jest drukowany plik print.inPetla while została zastosowana w celu przetwarzania opcji wiersza poleceń. Pętla ma następującą postać:

while ((argc > 1) && (argv[1][0] == '-') ) {

Zawsze istnieje jeden argument. Jest nim nazwa programu. Wyrażenie (argc>1) szuka dodatkowych argumentów. Pierwszy argument otrzymuje numer 1. Pierwszy znak pierwszego argumentu jest określony przez tablicę argv[1][0]. Jeśli jest to znak “-”, mamy do czynienia z opcją.Na końcu pętli znajduje się poniższy program:

--argc; ++argv; }

Instrukcje te pobierają argument. Liczba argumentów jest dekrementowana w celu wskazania o jedną opcję mniej, natomiast wskaźnik wskazujący na pierwszą opcję jest inkrementowany, co wiąże się z przesunięciem listy o jedno miejsce w lewo. (Uwaga: po wykonaniu pierwszej inkrementacji element tablicy argv[0] nie wskazuje już na nazwę programu).Instrukcja switch ma na celu przetwarzanie opcji. Znakiem 0 argumentu jest znak “-”. Znakiem 1 jest znak opcji, a zatem w celu przetworzenia opcji można użyć poniższego wyrażenia:

switch (argv[1][1]) {

Opcja –v nie zawiera żadnych argumentów. Ustawia ona tylko znacznik.Opcja -o pobiera nazwę pliku. Zamiast kopiowania całego łańcucha wystarczy ustawić wskaźnik znakowy out_file wskazujący na nazwę części łańcucha. W tym miejscu wiemy, że:

argv[1][0] = '-' argv[1][1] = 'o' argv[1][2] = pierwszy znak z nazwy pliku’

Poniższa instrukcja ustawia wskaźnik out_file tak, aby wskazywał na łańcuch:

out file = &argv[1][2];

Adres operatora & ma na celu pobranie adresu pierwszego znaku nazwy pliku wyjściowego. Operacja ta jest poprawna, ponieważ przypisujemy adres do wskaźnika znakowego out_file.Opcja -1 pobiera jako argument liczbę całkowitą. Funkcja standardowa atoi jest stosowana do konwersji łańcucha na liczbę całkowitą. Z poprzedniego

141

Page 142: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

przykładu wiemy już, że tablica argv[1][2] jest pierwszym znakiem łańcucha zawierającego łiczbę. Łańcuch ten jest przekazywany do funkcji atoi.Teraz wszystkie opcje są analizowane i jest wykonywana pętla. Polega to na wywołaniu dla każdego argumentu funkcji do_file. W wydruku 11.12 został zawarty program print.

Wydruk 11.12 /********************************************************* * Program: Print * * Cel: formatuje plik do wydruku * * Zastosowanie: print [opcje] plik(i) * * Opcje: * * -v wlacza tryb pelnej informacji * * -o<plik> wysyla dane wyjsciowe do pliku * * (plik domyslny=print.out) * * -1<wiersze> ustawia liczbe wierszy na strone.* * (wartosc domyslna=66). * *********************************************************/ #include <stdio.h> #include <stdlib.h> int verbose = 0; /* tryb pelnej informacji (domyslnie = wylaczony) */ char *out_file="print.out"; /* nazwa pliku wyjsciowego */ char *program_name; /* nazwa programu (dla bledow) */ int line_max = 66; /* liczba wierszy na strone */ /******************************************************** * do_file -- fikcyjna funkcja obslugujaca plik * * Parametr name -- nazwa pliku do wydruku * *********************************************************/ void do_file(char *name) { printf("Tryb pelnej informacji %d wiersze %d Plik wejsciowy %s Plik wyjsciowy %s\n”, verbose, line_max, name, out_file); } /************************************************************************* * zastosowanie - powiedz uzytkownikowi, jak uzywac programu * * i nastepnie go zamknij * **************************************************************************/ void usage(void) { fprintf(stderr,"Program ma format: %s [opcje] [lista plikow]\n", program_name); fprintf(stderr,"Opcje\n"); fprintf(stderr," –v Tryb pelnej informacji\n"); fprintf(stderr," -1<liczba> Liczba wierszy\n"); fprintf(stderr," -o<nazwa> Ustawia nazwe pliku wyjsciowego\n");

142

Page 143: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

exit (8); } int main(int argc, char *argv[]) { /* zapisanie nazwy programu dla przyszlych zastosowan */ program name = argv[0]; /* Petla wykonywana dla kazdej opcji. * Jest przerywana jesli braknie argumentow * lub jesli pobierze argument bez znaku ’-’. */ while ((argc > 1) && (argv[1][0] == '-')) { /* argv[1][1] jest faktycznym znakiem opcji */ switch (argv[1][1]) { /* -v tryb pelnej informacji */ case 'v': verbose = 1; break; /* -o<nazwa> nazwa pliku wyjsciowego * [0] jest znakiem ’-’ * [1] jest znakiem "o" * [2] poczatek nazwy pliku */ case 'o': out_file = &argv[1][2]; break; /* -1<liczba> ustawia maksymalna liczbe wierszy */ case '1': line_max = atoi(&argv[1][2]); break; default: fprintf(stderr,"Nieprawidlowa opcja %s\n", argv[1]); usage (); } /* przesuwa liste argumentow o jeden w gore zmniejsza licznik o jeden */ ++argv; --argc; } /* W tym miejscu wszystkie opcje zostaly przetworzone. * Nalezy sprawdzic, czy na liscie nie ma juz zadnych plikow. * Jesli ich nie ma, nalezy przetworzyc dane ze standardowego wejscia. */ if (argc == 1) { do_file("print.in"); } else { while (argc > 1) { do_file(argv[1]); ++argv; --argc;

143

Page 144: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

} } return (0); }

Jest to jeden ze sposobów przetwarzania listy argumentów. Zastosowanie pętli while oraz instrukcji switch jest łatwe i proste do zrozumienia. Jednak metoda ta ma drobne ograniczenie, a mianowicie argument musi być umieszczony bezpośrednio za opcją. Na przykład opcja -odata.out zadziała, ale opcja „-o data.out” już nie. Bardziej zaawansowana metoda analizy opcji sprawiłaby, że program byłby przystępniejszy, ale metody tutaj opisane są przeznaczone do wykorzystania w takich właśnie prostych programach.Pytanie 11.2. Po usunięciu blędu z funkcji próbujemy ją zastosować dla dwóch nazw pliku. Program zawarty w wydruku 11.13 powinien wyświetlić następujący wynik,

Nazwa pliku: tmp1 Nazwa pliku: tmp2

ale tak nie jest.Co program wyświetla i dlaczego tak się dzieje?

Wydruk 11.13 #include <stdio.h> #include <string.h> /********************************************************************************* * tmp_name -- zwraca nazwe pliku tymczasowego * * Za kazdym wywolaniem tej funkcji jest zwracana nowa nazwa pliku. * * Ostrzezenie: Powinno pojawic sie tutaj ostrzezenie, ale jesli je wstawimy, * * tym samym odpowiemy na pytanie. * * Wartosc zwracana -- Wskaznik do nazwy nowego pliku. * ********************************************************************************** char *tmp_name(void) { static char name[30]; /* Generowana nazwa */ static int sequence = 0; /* Numer kolejny dla ostatniej cyfry */ ++sequence; /* Przejscie do nastepnej nazwy pliku */ strcpy(name, "tmp"); /* ale dla kolejnej cyfry */ name[3] = sequence + '0'; /* znak konca lancucha */ name[4] = '\0'; return(name); } int main() { char *tmp_name(void); /* pobranie nazwy pliku tymczasowego */ char *name1; /* nazwa pliku tymczasowego */

144

Page 145: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

char *name2; /* nazwa pliku tymczasowego */ name1 = tmp_name(); name2 = tmp_name(); printf("Nazwa pliku: %s\n", name1); printf("Nazwa pliku: %s\n", name2); return(0); }

Odpowiedź 11.2. Po pierwszym wywołaniu funkcja tmp_name zwraca wskaźnik do zmiennej name . Istnieje tylko jedna zmienna name. Po drugim wywołaniu funkcji tmp_name zmienna name jest modyfikowana i funkcja zwraca do niej wskaźnik. W efekcie uzyskujemy dwa wskaźniki wskazujące na tę samą zmienn name.Kilka funkcji standardowych zwraca wskaźniki wskazujące na łańcuchy typu static. Drugie wywołanie jednej z tych funkcji spowoduje nadpisanie wartości zwróconej przy ich pierwszym wywolaniu. Rozwiązanie tego problemu polega na skopiowaniu wartości w następujący sposób:

char name1[100]; char name2[100]; strcpy(name1, tmp_name()); strcpy(name2, tmp_name());

Problem ten jest znakomitą ilustracją podstawowej definicji wskaźnika. Wskaźnik nie służy do tworzenia nowego miejsca, w którym będę przechowywane dane. Wskazuje jedynie na dane, które znajdują się w innym miejscu.Problem ten jest również dobrym przykładem nieprawidłowo zdefiniowanej funkcji. Objawia się on w tym, że użycie samej funkcji jest samo w sobie zdradliwe. Lepiej przemyślana funkcja zawierałaby bardziej pewny program. Na przykład funkcja taka mogłaby pobrać dodatkowy parametr w postaci łańcucha, z którego zostanie utworzona nazwa pliku. Oto jej postać:

void tmp_name(char *name_to_return);

145

Page 146: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

12 Pliki - operacje wejścia-wyjścia

Plik jest zbiorem powiązanych ze sobą danych. Język C traktuje plik jako ciąg bajtów. Wiele plików jest zapisywanych na dysku, jednak urządzenia, takie jak terminale, drukarki oraz czytniki płyt CD, również mogą przechowywać pliki.Biblioteka języka C zawiera dużą liczbę funkcji służących do wykonywania operacji na plikach. Deklaracje struktur oraz funkcje używane przez funkcje manipulujące plikami są zawarte w standardowym pliku dołączanym <stdio.h>.Przed wykonaniem dowolnej operacji na pliku, należy wprowadzić na początku programu poniższą instrukcję:

#include <stdio.h>

Deklaracja zmiennej plikowej ma następującą postać:

FILE *zmienna_plikowa; /* komentarz */

Oto przykład jej zastosowania:

#include <stdio.h> FILE *int_file; /* plik zawierajacy dane wejsciowe "/

Zanim plik może być użyty musi zostać otwarty za pomocą funkcji fopen. Funkcja ta zwraca wskaźnik do struktury otwieranego pliku. Ma ona następującą postać,

Zmienna_plikowa = fopen(nazwa, tryb);

gdzie:

zmienna_plikowa – jest zmienną plikową. W przypadku wystąpienia błedu jest zwracana wartość NULL.

nazwa – jest właściwą nazwą pliku (np. data.txt, temp.dat).

tryb – określa, czy plik będzie odczytywany (znacznik r) czy zapisywany (znacznik w). Znacznik b może zostać dodany w celu wskazania pliku binarnego. Pominięcie znacznika b oznacza, że plik jest plikiem tekstowym (ASCII). Niżej rozpatrzymy pliki biname i tekstowe.

Znaczniki mogą być ze sobą łączone. Tak więc, znacznik wb zostanie użyty w przypadku zapisu pliku binarnego.

146

Page 147: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Funkcja zwraca uchwyt pliku, który będzie wykorzystywany przez kolejne operacje wejścia-wyjścia. Jeśli wystąpi błąd wejścia-wyjścia, zostanie zwrócona wartość NULL. Oto przykład:

FILE *in_file; /* plik do odczytu */ in_file = fopen("input.txt", "r"); /* otwarcie pliku wejsciowego */ if (in_file == NULL) { /* kontrola poprawnosci */ fprintf(stderr, "Blad: Nie mozna bylo otworzyc pliku ’input.txt’\n"); exit (8); }

Funkcja fclose sluży do zamykania pliku. Ma następującą postać:

status = fclose (zmienna_plikowa);

lub

fclose (zmienna_plikowa);

Zmienna status ma wartość 0, jeśli funkcja fclose zakończyła poprawnie swoje działanie, lub wartość różną od zera w przypadku wystąpienia błędu. Jeśli nie interesuje nas zmienna status, można zastosować drugą postać funkcji, która zamyka plik i nie zwraca żadnej wartości.Język C dysponuje trzema domyślnie otwieranymi plikami. Zostały one zawarte w tabeli 12.1.

Tabela 12.1. Pliki standardowe

Plik Opis

stdinstdoutstderr

Standardowe wejście (do odczytu)Standardowe wyjście (do zapisu)Standardowy błąd (do zapisu)

Funkcja fgetc odczytuje pojedynczy znak zawarty w pliku. Jeśli zostaną odczytane wszystkie dane zawarte w pliku, funkcja zwróci stałą EOF (stała EOF (End Of File - Koniec pliku) jest zdefiniowana w pliku stdio.h). Należy zauważyć, że funkcja fgetc zwraca liczbę całkowitą, a nie znak. Jest to konieczne, ponieważ stała EOF musi być wartością liczbową.W wydruku 12.1 jest obliczana liczba znaków zwartych w pliku input.txt.

147

Page 148: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 12.1 #include <stdio.h> const char FILE_NAME[ ] = "input.txt"; #include <stdlib.h> int main() { int count = 0; /* Liczba przetworzonych znakow */ FILE *in_file; /* plik wejsciowy */ /* znak lub znacznik EOF pobrany z pliku wejsciowego */ int ch; in_file = fopen(FILE_NAME, "r"); if (in_file == NULL) { printf("Nie mozna otworzyc pliku %s\n", FILE_NAME); exit(8); } while (1) { ch = fgetc(in_file); if (ch == EOF) break; ++count; } printf("Liczba znakow w pliku %s wynosi %d\n", FILE_NAME, count); fclose(in_file); return (0); }

Podobna funkcja fputc służy do zapisywania pojedynczych znaków. Ma następującą postać:

fputc (znak, plik);

Funkcje fgets i fputs pobierają jednocześnie jeden wiersz. Wywołanie funkcji fgets ma postać:

String_ptr = fgets(łańcuch, rozmiar, plik);

gdzie:

string_ptr w przypadku poprawnej operacji odczytu jest równy wartości łańcuch

lub NULL, jeśli zostanie osiągnięty koniec pliku lub wystąpi błąd.

łańcuch jest tablicą znaków, w której funkcja umieszcza łańcuch.

148

Page 149: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

rozmiarjest rozmiarem tablicy znaków. Funkcja fgets dokonuje operacji odczytu do momentu napotkania wiersza zawierającego znak „\n” lub wczytania znaków w liczbie określonej wyrażeniem size-1, po czym kończy łańcuch, wstawiając znak “\0”.

Jeś1i podany rozmiar jest zbyt duży, może to być powodem problemów. Język C dysponuje operatorem sizeof, który pozwala w wygodny sposób dokonać sprawdzenia rozmiaru parametru.Operator sizeof zwraca rozmiar swojego argumentu wyrażony w bajtach. Oto przykład:

long int array[10]; /* (Kazdy element zajmuje 4 bajty) */ char string[30];

Wynikiem instrukcji sizeof(string) jest liczba 30. Wartość ta nie jest równoznaczna długości łańcucha string. Wartość ta może zawierać się w przedziale od 0 do 29 znaków. Funkcja sizeof zwraca liczbę bajtów (zajmowanych lub nie) przez łańcuch string. Typ całkowity long int zajmuje 4 bajty, a zatem funkcja sizeof(array) zwraca wartość 40.Operator sizeof jest szczególnie przydatny w przypadku stosowania funkcji fgets. Dzięki niemu nie trzeba się zastanawiać nad długością łańcucha lub (co gorsza) nad tym, co się stanie, jeśli ktoś zmieni tę wartość.Na przykład w poniższym programie:

char string[100]; ... fgets(string, sizeof(string), in_file);

funkcja fputs działa podobnie jak funkcja fgets poza tym, że zamiast odczytywania łańcucha zapisuje go. Funkcja fputs ma następujący format:

string_ptr = fputs(łańcuch, plik);

Parametry funkcji fputs są podobne do parametrów funkcji fgets. Funkcja fputs nie wymaga rozmiaru łańcucha, ponieważ pobiera go z zapisywanego wiersza zawierającego łańcuch. Operacja zapisu kończy się w momencie napotkania znaku „\0”.

Funkcje konwersjiJak dotąd, została omówiona operacja zapisu znaków i łańcuchów. Tutaj omówimy niektóre z bardziej zaawansowanych operacji wejścia-wyjścia i konwersji.Aby wysłać liczbę do drukarki lub terminala, należy zamienić ją na znaki. Drukarka rozpoznaje znaki, a nie liczby. Na przykład liczba 567 w celu jej wydruku musi być zamieniona na trzy znaki - 5, 6 i 7.

149

Page 150: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Funkcja fprintf dokonuje konwersji danych i zapisuje je do pliku. Ogólna postać funkcji fprintf jest następująca:

count = fprintf (plik, format, parametr1, parametr2, ...);

gdzie:

count – jest równa liczbie przesłanych znaków lub w przypadku wystąpienia błędu ma wartość - 1.

format – opisuje sposób, w jaki argumenty zostaną wydrukowane.

parametr1, parametr2,... – konwertowane i przesyłane parametry.

Funkcja fprintf posiada jeszcze dwie funkcje podobne do siebie. Są to: printf oraz sprintf. Funkcja printf() była już stosowana i jej pierwszy argument stdout jest identyczny jak w przypadku funkcji fprintf. Funkcja sprintf różni się od funkcji fprintf tylko tym, że jej pierwszy argument jest łańcuchem. Oto przykład:

char string[40]; /* nazwa pliku */ int file_number = 0; /* aktualny numer pliku w tym segmencie */ sprintf(string, "file.%d", file_number); ++file_number; out_file = fopen(string, "w");

Funkcja scanf również posiada dwie funkcje podobne do siebie. Są to: fscanf oraz sscanf. Funkcja fscanf ma następującą postać:

numer = fscanf(plik, format, &parametrl, ... );

gdzie:

numer jest liczbą perametrów, które zostały poprawnie zamienione. Jeśli na wejściu

znajdowały się dane, ale nie została wykonana żadna konwersja, jest zwracana wartość 0. Jeśli na wejściu nie ma danych, jest zwracana stała EOF.

plik – plik otwierany do odczytu.

format – opisuje odczytywane dane.

parametr1 – jest pierwszym odczytywanym parametrem.

Funkcja sscanf jest podobna do funkcji fscanf. Różni się jedynie tym, że zamiast pliku szuka łańcucha.W przypadku napotkania w danych wejściowych znaku końca wiersza funkcja scanf może zachować się bardzo dziwacznie. Często bywa tak, że w celu przywrócenia funkcji do normalnego działania użytkownik musi wprowadzić dodatkowe znaki powrotu karetki.Można uniknąć tego problemu poprzez użycie funkcji fgets wczytującej z pliku wiersz oraz funkcji sscanf, która go przetwarza. Funkcja fgets prawie zawsze bezproblemowo wczytuje pojedynczy wiersz.

150

Page 151: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

W wydruku 12.2 został zawarty program, który próbuje wczytać ze standardowego wejścia (klawiatury) dwa parametry. Po ich wczytaniu wyćwietla komunikat, którego zawartość zależy od liczby ości parametrów rzeczywiście wprowadzonych.

Wydruk 12.2. Zastosowanie wartości zwracanej przez funkcję sscanf char line[100]; /* Dane wprowadzone z klawiatury */ int count, total; /* Liczba wpisow oraz wartosc calkowita */ int scan_count; /* Liczba przeszukanych parametrow */ fgets(line, sizeof(line), stdin); scan_count = sscanf(line,"%d %d", &count, &total); switch (scan_count) { case EOF: case 0: printf("Nie znaleziono zadnej liczby\n"); break; case 1: printf("Znaleziono zmienna 'count'(%d), ale zmiennej 'total' nie\n", count); break; case 2: printf("Znaleziono zarowno zmienna 'count'(%d), jak i zmienna 'total'(%d)\n", count, total); break; default: printf("To nie powinno byc mozliwe\n"); break; }

Pytanie 12.1. Niezależnie od tego, jaka nazwa pliku zostanie określona w programie z wydruku 12.3, nie może on go znaleźć. Czym jest to spowodowane?Odpowiedź 12.1. Problem polega na tym, że funkcja fgets pobiera cały wiersz, łącznie ze znakiem nowego wiersza (\n). Jeśli na przykład mamy plik o nazwie sam program wczyta łańcuch sam\n, a następnie poszuka pliku o takiej nazwie. Ponieważ taki plik nie istnieje,program wyświetli komunikat błędu.Rozwiązanie problemu polega na usunięciu z nazwy znaku nowego wiersza:

name[strlen(name)-1] = '\0'; /* usuwa ostatni znak */

Komunikat błędu w tym przypadku jest niezbyt adekwatny. Co prawda, plik nie został otwarty, ale programista mógł przekazać użytkownikowi bardziej precyzyjną informację. Czy próbujemy otworzyć plik wejściowy czy wyjściowy? Jaka jest nazwa pliku, który próbujemy otworzyć? Nawet nie wiemy, czy wyświetlany komunikat jest błędem, ostrzeżeniem, czy może tylko częścią zwykłej operacji. Lepszy komunikat błędu wyświetli poniższa funkcja:

151

Page 152: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

fprintf (stderr, "Blad: Nie mozna otworzyc pliku wejsciowego %s.\n", name);

Należy zauważyć, że powyższy komunikat będzie również pomocny w wykryciu błędu formalnego. Po wpisaniu nazwy sam komunikat błędu przyjmie poniższą postać:

Blad: Nie mozna otworzyc pliku wejsciowego sam

Powyższy komunikat informuje, że probujemy otworzyć plik o nazwie zawierającej znak nowego wiersza.

Wydruk 12.3 #include <stdio.h> #include <stdlib.h> int main() { char name[100]; /* nazwa szukanego pliku */ FILE *in_file; /* plik wejsciowy */ printf("Jaka jest nazwa pliku? "); fgets(name, sizeof(name), stdin); in_file = fopen(name, "r"); if (in_file == NULL) { fprintf(stderr, "Plik nie mogl byc otwarty\n"); exit(8); } printf("Plik zostal znaleziony\n"); fclose(in_file); return (0); }

Pliki binarne i tekstowe (ASCII)Do tej pory posługiwaliśmy się plikami ASCII. Skrót ASCII wywodzi się od słów American Standard Code for Information Interchange (standardowy amerykański kod wymiany informacji). Kod ASCII zawiera 95 drukowalnych znaków oraz 33 kody sterujące. Pliki ASCII są plikami tekstowymi. Po napisaniu programu, plik go zawierający prog.c jest plikiem ASCII.Terminale, klawiatury oraz drukarki rozpoznają dane znakowe. Aby wyświetlić na ekranie liczbę 1234, należy najpierw poddać ją konwersji do postaci czterech znaków („1”, „2”, „3”, „4”). Podobnie, aby odczytać Iiczbę wprowadzoną na klawiaturze, jej typ musi zostać zamieniony ze znakowego na całkowity. Zadanie to jest realizowane przez funkcją sscanf.Znakowi kodu ASCII „0” odpowiada wartość 48, znakowi „1” wartość 49, i tak dalej. Aby zamienić pojedynczą cyfrę kodu ASCII na Iiczbę całkowitą należy ją odjąć. Oto przykład takiej operacji:

int integer; char ch; ch = '5'; integer = ch - 48; printf("Liczba calkowita %d\n", integer);

152

Page 153: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Zamiast pamiętać, że znak „0” odpowiada wartości 48, wystarczy odjąć znak „0” w następujący sposób:

integer = ch - '0';

Komputery przetwarzają dane binarne. Po wczytaniu z pliku tekstowego liczby program musi zastosować funkcję sscanf w celu jej konwersji na znak. Jest to czasochłonna operacja.Pliki binarne nie wymagają żadnej konwersji. Zwykle zajmują mniej miejsca niż pliki ASCII. Wadą plików binarnych jest to, że nie mogą być bezpośrednio wysyłane do drukarki lub terminala. Jeśli kiedykolwiek spotkamy się z sytuacją, gdzie z drukarki wysuwa się długi wydruk zawierający na samej górze kilka znaków wyglądających podobnie jak te „!E#(@$%@^Aa^AA^^JHC%^X,” to już wiemy, jaki jest skutek próby wydrukowania pliku binarnego.Pliki ASCII są przenaszalne (w większości przypadków). Mogą być przenoszone bez większego problemu z jednej platformy na inną. Pliki binarne nie są prawie z całą pewnością przenaszalne.Który typ pliku trzeba stosować? W większości przypadków będzie to plik ASCII. W przypadku niewielkiej ilości danych czas związany z konwersją nie wpłynie znacząco na wydajność programu. (Jakie to ma znaczenie, czy zajmie to 0,5 sekundy zamiast 0,3 sekundy?). Pliki ASCII pozwalają w prosty sposób dokonać sprawdzenia poprawności zawartych w nich danych.Tylko wtedy, gdy mamy do czynienia z dużą ilością danych zajmującą sporo miejsca i wpływającą na wydajność, konieczne jest zastosowanie formatu binarnego.

Znak końca wierszaW zamierzchłych czasach PK (Przed Komputerami) istniało magiczne urządzenie zwane Teletype Model 33. Ta zadziwiająca maszyna posiadała rejestr przesuwany, na który składal się napęd z wirnikiem oraz klawiatura z pamięcią typu ROM zawierająca w podstawie dźwignie i sprężyny. Urzędzenie to było wyposażone w klawiaturę, drukarkę oraz czytnik papierowej taśmy perforowanej.Było ono w stanie przy użyciu modemu linią telefoniczną przekazywać wiadomości z prędkością 10 znaków na sekundę.Teletype posiadał jeden problem, mianowicie przesunięcie głowicy drukującej z prawej strony na lewą zajmowało 0,2 sekundy. Czas ten odpowiadał wprowadzeniu dwóch znaków. Zatem jeśli drugi znak był wprowadzany w momencie, gdy głowica drukująca znajdowała się w środku karetki, był on gubiony.Producenci urządzenia rozwiązali ten problem poprzez zastosowanie dwóch znaków końca wiersza: znaku <RETURN> (znak powrotu karetki) służącego

153

Page 154: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

do pozycjonowania głowicy drukującej w położeniu lewego marginesu oraz znaku <LINE FEED> (znak wysuwu wiersza) powodującego przesunięcie kartki o jeden wiersz w górę.Po pojawieniu się pierwszych komputerów niektórzy projektanci zdali sobie sprawę, że dwa znaki końca wiersza niepotrzebnie zajmują miejsce (w tym czasie pamięć była bardzo droga). Niektórzy uznali, że znak <LINE FEED> będzie znakiem końca wiersza, natomiast inni, że będzie nim znak <RETURN>. Byli też tacy desperaci, którzy pozostali przy obydwu znakach.W systemie UNIX jako znak końca wiersza jest używany znak <LINE FEED>. Znak początku nowego wiersza „\n” jest określony poprzez kod 0x0A (LF lub <LINE FEED>). W systemie MS-DOS/Windows są stosowane dwa znaki: <LINE FEED> oraz <RETURN>. W systemie firmy Apple jest używany znak <RETURN>.W systemic MS-DOS/Windows twórcy kompilatorów napotkali pewien problem. Co zrobić w przypadku starych programów napisanych w języku C, w których jako znak nowego wiersza był stosowany tylko znak <LINE FEED>? Rozwiązanie tego problemu polegało na dodaniu do biblioteki operacji wejścia-wyjścia kodu, który usuwał znaki <RETURN> z pliku wejściowego ASCII, a następnie zamieniał w pliku wyjściowym znak <LINE FEED> na kombinację znaków <LINE FEED> <RETURN>.W systemie MS-DOS/Windows ma znaczenie, czy plik jest otwierany jako tekstowy czy jako binarny. Zawarty poniżej znacznik b ma za zadanie określać plik binarny:

/* otwarcie pliku ASCII do odczytu */ ascii_file = fopen("nazwa", "r"); /* otwarcie pliku binarnego do odczytu */ binary_file = fopen("nazwa", "rb");

Po otwarciu w systemie MS-DOS/Windows pliku, który zawiera tekst jako pliku binarnego, program będzie musiał obsługiwać znaki powrotu karetki <RETURN>. Po otwarciu tego pliku jako pliku tekstowego znaki <RETURN> są automatycznie usuwane przez funkcje przetwarzające zawartość pliku.Pytanie 12.2 Fukcja fputs może być użyta w celu zapisu pojedynczego bajta pliku binarnego. W wydruku 12.4 program zapisuje do pliku test.out liczby od 0 do 127. Program uruchomiony pod systemem UNIX działa bez zarzutów, tworząc plik o rozmiarze 128 baitów, natomiast pod systemem MS-DOS/Windows wynikowy plik ma rozmiar 129 bajtów. Co jest tego powodem?Odpowiedź 12.2. Problem polega na tym, że jest tworzony plik ASCII, a miał to być plik binarny. W systemic UNIX nie ma różnicy między plikiem tekstowym i binamym, a zatem program działa poprawnie. W systemic MS-DOS/Windows znak końca wiersza jest przyczyną problemu. Po wstawieniu

154

Page 155: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

do pliku znaku nowego wiersza (0x0a) zostanie do niego dodany znak powrotu karetki (0x0d). Należy pamiętać, że w systemic MS-DOS/Windows znakiem końca wiersza jest kombinacja <RETURN> <LINE FEED> lub inaczej (0x0d,0x0a). Poprzez wykonanie edycji pliku w pliku wyjściowym zostanie dodany dodatkowo znak powrotu karetki (0x0d).Aby zapisać plik binarny, należy taki plik otworzyć, stosując znacznik b:

out_file = fopen("test.out", "wb");

Wydruk 14.4 #include <stdio.h> #include <stdlib.h> #ifndef _MSDOS_ #include <unistd.h> #endif _MSDOS_ int main() { int cur_char; /* aktualnie zapisywany znak */ FILE *out_file; /* plik wyjsciowy */ out_file = fopen("test.out", "w"); if (out_file == NULL) { fprintf(stderr,"Nie mozna otworzyc pliku wyjsciowego\n"); exit (8); } for (cur_char = 0; cur_char < 128; ++cur_char) { fputc(cur_char, out_file); } fclose(out_file); return (0); }

Poniżej pokazano zrzut zawartości pliku systemu MS-DOS/Windows w formacie szesnastkowym:

000:0001 0203 0405 0607 0809 0d0a 0b0c 0d0e 010:0f10 1112 1314 1516 1718 191a 1b1c 1d1e 020:1f20 2122 2324 2526 2728 292a 2b2c 2d2e 030:2f30 3132 3334 3536 3738 393a 3b3c 3d3e 040:3f40 4142 4344 4546 4748 494a 4b4c 4d4e 050:4f50 5152 5354 5556 5758 595a 5b5c 5d5e 060:5f60 6162 6364 6566 6768 696a 6b6c 6d6e 070:6f70 7172 7374 7576 7778 797a 7b7c 7d7e 080:7f

155

Page 156: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Programiści tworzący programy w systemic UNIX nie musz się przejmować biblioteką języka C, która automatycznie koryguje tworzone pliki ASCII. W systemic UNIX plik jest plikiem, a zatem nie ma różnicy pomiędzy plikiem binarnym i tekstowym. I faktycznie jest możliwe utworzenie pliku, który w połowie jest plikiem tekstowym, a w drugiej połowie plikiem binarnym.

Binarne operacje wejścia-wyjściaBinarne operacje wejścia-wyjścia są realizowane przez dwie funkcje. Są to: fread oraz fwrite. Funkcja fread ma następującą postać:

Read_size = fread(data_ptr, 1, rozmiar, plik);

gdzie:

read_size – określa rozmiar wczytywanych danych. Jeśli wartość ta jest mniejsza od wartości rozmiar, oznacza to, że został osiągnięty koniec pliku lub że wystąpił błąd.

data_ptr – jest wskaźnikiem do wczytywanych danych. Jeśli dane nie są typu znakowego, wskaźnik ten musi być poddany rzutowaniu do typu znakowego (char *).

rozmiar – określa liczbę wczytywanych bajtów.

plik – określa plik wejściowy.

Oto przykład zastosowania funkcji fread: struct { int width; int height; } rectangle; int read size; read_size = fread((char *)&rectangle, 1, sizeof(rectangle), in_file); if (read_size != sizeof(rectangle)) { fprintf(stderr,"Nie mozna wczytac struktury rectangle\n"); exit (8); }

W powyższym przykładzie jest wczytywana struktura rectangle. Operator & zamienia ją na wskaźnik. Operator sizeof jest stosowany w celu określenia ilości wczytywanych bajtów oraz dla sprawdzenia poprawności operacji odczytu.Funkcja fwrite ma podobną postać wywołania, jak funkcja fread:

Write_size = fwrite (data_ptr, 1, rozmiar, plik);

Aby programowanie było prostsze, zawsze należy stosować w funkcji fread oraz fwrite jako drugi argument wartość 1. Tak naprawdę obie

156

Page 157: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

funkcje zostały stworzone z myślą o odczytywaniu elementów tablicy obiektów. Drugi parametr określa rozmiar obiektu, natomiast trzeci przechowuje liczbę obiektów.

Problemy z buforowaniemBuforowane operacje wejścia-wyjścia nie dokonują natychmiastowego zapisu do pliku. Zamiast tego, dane są przechowywane w buforze do momentu, aż będzie ich wystarczająca ilość lub gdy nastąpi moment opróżnienia bufora. Poniższy program ma na celu po zakończeniu kolejnego etapu wyświetlenie komunikatu zawierającego aktualny status:

printf ("Start"); do_step_1(); printf ( “Etap 1 zakonczony"); do_step_2(); printf("Etap 2 zakonczony"); do_step_3(); printf("Etap 3 zakonczony\n");

Zamiast wyświetlać komunikat po zakończeniu każdego etapu funkcja printf umieszcza go w buforze. Tylko wtedy po zakończeniu działania programu bufor zostanie opróżniony i wszystkie komunikaty w nim zawarte zostaną wyświetlone jednocześnie.Funkcja fflush wymusza opróżnienie bufora. A zatem poprawnie napisany program będzie miał następującą postać:

printf ("Start" ); fflush(stdout); do_step_1(); printf("Etap 1 zakonczony"); fflush(stdout); do_step_2(); printf("Etap 2 zakonczony"); fflush(stdout); do_step_3(); printf("Etap 3 zakonczony\n"); fflush(stdout);

Niebuforowane operacje wejścia-wyjściaW przypadku buforowanych operacji wejścia-wyjścia dane są buforowane, a następnie przesyłane do pliku, natomiast niebufurowane operacje wejścia-wyjścia są natychmiast zapisywane do pliku.Jeśli upuścimy na podłogę kilka spinaczy do papieru, możemy niejako podnieść je w trybie buforowanym lub niebuforowanym. W trybie buforowanym w celu podniesienia spinacza posłużymy się prawą dłonią, a następnie przełożymy go do lewej. Proces ten będzie powtarzany do

157

Page 158: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

momentu, gdy lewa dłoń będzie pełna, a wtedy wrzucimy jej zawartość do pudełka znajdującego się na biurku.W trybie niebuforowanym podniesiemy spinacz i natychmiast wrzucimy go do pudełka. W tym przypadku nie istnieje pojęcie buforu w lewej dłoni.W większości przypadków buforowane operacje wejścia-wyjścia powinny być stosowane zamiast operacji niebuforowanych. W przypadku niebuforowanych operacji wejścia-wyjścia każdy odczyt lub zapis wymaga wywołania systemowego. Każde wywołanie systemowe jest dodatkowym obciążeniem. Buforowane operacje wejścia-wyjścia minimalizują takie wywołania.Niebuforowane operacje wejścia-wyjścia powinny być używane tylko wtedy, gdy jest odczytywana lub zapisywana spora ilość danych binarnych lub gdy jest wymagany bezpośredni dostęp do urządzenia lub pliku.Wracając do przykładu ze spinaczami - jeśli podnosimy niewielkie elementy takie jak spinacze, prawdopodobnie skorzystamy z bufora znajdującego się w lewej dłoni, ale jeśli będą to kule armatnie (które są o wiele większe), to z pewnością nie skorzystamy z takiego bufora.Wywołanie systemowe open jest stosowane do otwierania plików niebuforowanych. Definicja makra używanego przez to wywołanie różni się w zależności od systemu operacyjnego. Tutaj mówiliśmy o systemach UNIX oraz MS-DOS/Windows, a zatem w celu uzyskania odpowiednich plików wykonaliśmy kompilację warunkową (#ifdef/#endif): #ifndef _MSDOS_ /* jesli nie jest to system MS-DOS, */ #define _UNIX_ /* zatem jest to system UNIX */ #endif _MSDOS_ #ifdef _UNIX_ #include <sys/types.h> /* definicja pliku dla systemu plikow systemu UNIX */ #include <sys/stat.h> #include <fcntl.h> #endif _UNIX_ #ifdef _MSDOS_ #include <stdlib.h> #include <fcntl.h> /* definicja pliku dla systemu plikow systemu DOS */ #include <sys\stat.h> #include <io.h> #endif _MSDOS_ int deskryptor_pliku; deskryptor_pliku = open(nazwa, znaczniki); /* istniejacy plik */ deskryptor_pliku = open(nazwa, znaczniki, tryb); /* nowy plik */

gdzie:

deskryptor_plikujest liczbą całkowitą służącą do identyfikacji pliku, w którym zostaną wykonane wywołania operacji odczytu, zapisu i zamknięcia. Jeśli ma on wartość mniejszą od zera, oznacza to, że wystąpił błąd.

nazwa – określa nazwę pliku.

158

Page 159: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

znaczniki – znaczniki są zdefiniowane w pliku nagłówkowym fcntl.h. Zostały one zebrane w tabeli 12.2.

tryb – oznacza tryb ochrony pliku. Zwykle dla większości plików ma on wartość 0644.

Tabela 12.2. Znaczniki operacji otwarcia

Znacznik OpisO_RDONLYO_WRONLYO_RDWRO_APPENDO_CREAT

O_TRUNCO_EXCLO_BINARY

O_TEXT

Otwarty w trybie do odczytuOtwarty w trybie zapisuOtwarty w trybie do odczytu i zapisuDodaje nowe dane na końcu plikuTworzy plik (po ustawienie tego znacznika jest wymagane użycie trybu ochrony pliku )Jeśli istnieje plik, jest on obcinany do długości zeroJeśli istnieje plik, występuje błądOtwarty w trybie binarnym (tylko w systemie MS-DOS/Windows)Otwarty w trybie tekstowym (tylko w systemie MS-DOS/Windows)

Na przykłaad, aby otworzyć istniejący plik data.txt w trybie tekstowym i tylko do odczytu, należy użyć poniższej instrukcji:

Data_fd = open("data.txt", O_RDONLY);

W następnym przykładzie pokazano, jak utworzyć plik o nazwie output.dat w trybie zapisu:

Out_fd = open("output.dat", O_CREAT ׀ O_WRONLY, 0644);

Należy zauważyć, że znaczniki połączono za pomocą operatora or (׀). Jest to szybki i prosty sposób łączenia ze sobą wielu znaczników.Po uruchomieniu dowolnego programu są już otwarte trzy pliki. Zostały one opisane w tabeli 12.3.

Tabela 12.3. Standardowe pliki niebuforowane

Numer pliku Nazwa symboliczna Opis 012

STDIN_FILENOSTDOUT_FILENOSTDERR_FILENO

Standardowe wejścieStandardowe wyjścieStandardowa obsługa błędów

159

Page 160: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Nazwy symboliczne są zdefiniowane w pliku nagłówkowym unistd.h. Jest to stosunkowo nowy element języka, zresztą bardzo rzadko wykorzystywany. (Nazwy symboliczne są naprawdę warte zastosowania, ale i tak większość osób z nich nie korzysta).Wywołanie read ma następującą postać:

Read_size = read(deskryptor_pliku, bufor, rozmiar);

gdzie:

read_size – określa liczbę odczytywanych bajtów. Zero oznacza osiągnięcie końca pliku, natomiast wartość ujemna wskazuje na wystąpienie błędu.

deskryptor_pliku – jest deskryptorem otwartego pliku.

bufor – jest wskaźnikiem do miejsca, w którym zostaną odczytane dane.

rozmiar – określa rozmiar odczytywanych danych.

Wywołanie write ma następującą postać:

Write_size = write (deskryptor_pliku, bufor, rozmiar);

gdzie:

write_size – określa liczbę zapisywanych bajtów. Wartość ujemna oznacza, że wystąpił błąd.

deskryptor_pliku – jest deskryptorem otwartego pliku.

bufor – jest wskaźnikiem do miejsca, w którym zostaną zapisane dane.

rozmiar – określa rozmiar zapisywanych danych.

I wreszcie wywołanie close zamykające plik ma następującą postać:

znacznik = close (deskryptor_pliku)

gdzie:

znacznik – przyjmuje wartość ujemną, jeśli wystąpi błąd lub zero w przypadku poprawnie wykonanej operacji.

deskryptor_pliku – jest deskryptorem otwartego pliku.

W wydruku 12.5 jest kopiowany plik. Została zastosowana niebuforowana operacja wejścia- wyjścia ze względu na wymagany duży rozmiar bufora. Zastosowanie buforowania do wprowadzenia 1 kilobajta danych do bufora, a następnie przeniesienie tych danych do bufora o rozmiarze 16 kB nie ma sensu.

160

Page 161: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Wydruk 12.5 /******************************************* * copy - kopiuje jeden plik do innego. * * Zastosowanie * * copy <z> <do> * * <z> -- plik zrodlowy * * <do> -- plik docelowy * *******************************************/ #include <stdio.h> #ifndef _MSDOS_ /* jesli nie jest to system MS-DOS, */ #define _UNIX_ /* zatem jest to system UNIX*/ #endif /* _MSDOS_ */ #include <stdlib.h> #ifdef _UNIX_ #include <sys/types.h> /* definicja pliku systemu plikow systemu UNIX */ #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #endif /* _UNIX_ */ #ifdef _MSDOS_ #include <fcntl.h> /* definicja pliku systemu plikow systemu DOS */ #include <sys\stat.h> #include <io.h> #endif _MSDOS_ #ifndef O_BINARY #define O_BINARY 0 /* definicja znacznika, jesli jeszcze nie istnieje */ #endif /* O_BINARY */ #define BUFFER_SIZE (16 * 1024) /* uzycie bufora o rozmiarze 16Kb */ int main(int argc, char *argv[]) { char buffer[BUFFER_SIZE]; /* bufor danych */ int in_file; /* deskryptor pliku zrodlowego */ int out_file; /* deskryptor pliku docelowego */ int read_size; /* ilosc bajtow odczytanych w ostatniej operacji */ if (argc != 3) { fprintf(stderr, "Blad: Nieprawidlowa liczba argumentow\n"); fprintf(stderr, "Format funkcji: copy <z> <do>\n"); exit(8); } in_file = open(argv[1], O_RDONLY ׀ O_BINARY); if (in_file < 0) { fprintf("Blad: Nie mozna bylo otworzyc pliku %s\n", argv[1]); exit(8); } out_file=open(argv[2], O_WRONLY ׀ O_TRUNC ׀ O_CREAT ׀ O_BINARY, 0666); if (out_file < 0) { fprintf("Blad: Nie mozna bylo otworzyc pliku %s\n", argv[2]);

161

Page 162: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

exit(8); } while (1) { read_size = read(in_file , buffer, sizeof(buffer)); if (read_size == 0) break; /* koniec pliku */ if (read_size < 0) { fprintf(stderr, „Blad: Blad odczytu\n” ); exit(8); } write(out_file, buffer, (unsigned int) read_size); } close(in_file); close(out_file); return (0); }

Pytanie 12.3. Dlaczego w wydruku 12.5 w sytuacji, gdy nie było możliwe otwarcie pliku źródłowego, wykonano zrzut zawartości pamięci, a nie został wyświetlony komunikat błędu?Odpowiedź 12.3. Problem jest związany z wywołaniem funkcji fprintf. Pierwszy parametr tej funkcji powinien być plikiem, ale jest nim łańcuch. Zastosowanie łańcucha w sytuacji, gdy program spodziewa się pliku, jest powodem wykonania zrzutu pamięci.Należy w tym miejscu wspomnieć o kilku rzeczach dotyczących powyższego programu. Po pierwsze, rozmiar buforu jest określony jako stała, a więc można z łatwością zmieniać tę wartość. W programie jego autor zamiast pamiętać, że 16 kB jest równe 16 384 bitów zastosował wyrażenie (16*1024) . Wynikiem takiego wyrażenia jest stała o rozmiarze 16 kB.W przypadku nieprawidłowego użycia programu zostanie wyświetlony komunikat błędu. Ma on na celu poinformowanie, w jaki sposób używać programu, aby uniknąć tego typu sytuacji.W momencie ostatniej operacji odczytu może się zdarzyć, że bufor nie zostanie w całości wykorzystany. Z tego też powodu wartość read_size służy do określenia liczby zapisywanych bajtów.

Tworzenie formatów plikuZałóżmy, że tworzymy program generujący wykresy. Definicja jego parametrów, takich jak wysokość, szerokość, wartości graniczne oraz skala, powinny być zawarte w pliku konfiguracyjnym. Od autora wymaga się również napisania programu przyjaznego dla użytkownika, który zada operatorowi pytania, a następnie odpowiedzi zapisze w pliku konfiguracyjnym, tak aby dodatkowo nie była konieczna nauka obsługi edytora tekstu. W jaki sposób musimy zaprojektować plik konfiguracyjny?Jeden ze sposobów ma następującą postać:

162

Page 163: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

wysokosc (w centymetrach) szerokosc (w centymetrach) x dolna granica x gorna granica y dolna granica y gorna granica x skala y skala

Standardowy plik konfiguracyjny plotera wygląda tak:

10.0 7.0 0 100 30 300 0.5 2.0

Plik taki nie zawiera wszystkich danych, ale patrząc na nie można stwierdzić, że trudno określić, jakę przykładowo wartość ma dolna granica y. Rozwięzanie tego problemu polega na zastosowaniu w pliku komentarzy. Poniżej pokazano zawartość pliku konfiguracyjnego programu, który nie tylko zapisuje dane, ale również komentarze opisujące je.

10.0 wysokosc (w centymetrach) 7.0 szerokosc (w centymetrach) 0 x dolna granica 100 x gorna granica 30 y dolna granica 300 y gorna granica 0.5 x skala 2.0 x skala

Teraz można powiedzieć, że taki plik jest czytelny. Załóżmy, że jeden z użytkowników uruchamia program obsługi plotera i wpisze nieprawidłową nazwę pliku, wskutek czego program zamiast danych konfiguracyjnych plotera załaduje menu na dzisiejszy obiad. Program prawdopodobnie będzie bardzo „zdziwiony”, gdy spróbuje wygenerować wykres, którego osie będą opisane przez łańcuch „BLT na bialym” oraz „Pieczen w sosie”.Efekt końcowy może być taki, że zostaniesz obrzucany jajami. W związku z tym musimy określić sposób identyfikacji wczytywanego pliku jako pliku konfiguracyjnego plotera. Jeden z nich polega na wstawieniu w pierwszym wierszu pliku słów „Plik konfiguracyjny plotera”. Dzięki temu w sytuacji, gdy ktoś spróbuje załadować nieprawidłowy plik, program wyświetli odpowiedni komunikat błędu.

163

Page 164: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

Takie rozwiązanie likwiduje problem ładowania nieprawidłowych plików, ale co się wydarzy, gdy zaistnieje konieczność rozszerzenia możliwości programu w postaci dodania rysowania wykresów w skali logarytmicznej? Można po prostu dodać do pliku konfiguracyjnego kolejny wiersz, ale co się wtedy stanie z tymi wszystkimi starszymi plikami? Na pewno nie możemy poprosić właścicieli tych plików o ich usunięcie. Najlepszym rozwiązaniem (z punktu widzenia użytkownika) będzie dalsza obsługa plików starszego formatu. Zadanie to najprościej można zrealizować poprzez wstawienie w pliku numeru wersji.Po wykonaniu tej operacji standardowy plik wygląda następująco:

Wykres Plik konfiguracyjny 1.0 log wykres logarytmiczny lub zwykly 10.0 wysokosc (w centymetrach) 7.0 szerokosc (w centymetrach) 0 x dolna granica 100 x gorna granica 30 y dolna granica 300 y gorna granica 0.5 x skala 2.0 y skala

W przypadku plików binarnych numer identyfikacyjny jest zazwyczaj umieszczany w jego pierwszych czterech bajtach. Numer ten jest określany terminem magicznego numeru. Magiczny numer powinien być inny dla każdego typu pliku.Jedna z metod wyboru magicznego numeru polega na rozpoczęciu czterema pierwszymi literami nazwy programu (np. list), a następnie jego zamianie na liczbę szesnastkową 0x6c607374. Kolejnym krokiem jest dodanie wartości 0x80808080 do uzyskanej liczby, co w efekcie wygeneruje magiczny numer 0xECE0F3F4.Powyższy algorytm generuje magiczny numer, który najprawdopodobniej jest unikalny. Górny bit jest ustawiany dla każdego bajta w celu ustawienia go w trybie binamym oraz dla uniknięcia pomyłek między plikami binamymi i tekstowymi.W momencie zapisu i odczytu pliku binarnego zawierającego wiele różnych typów struktur programista może łatwo stracić orientację. Na przykład można wczytać nazwę struktury zamiast spodziewanego jej rozmiaru. Tego typu błąd jest zwykle wykrywany dopiero w dalszej części programu. Aby wykryć taki błąd wcześniej, programista może w tym celu wstawić na początku każdej struktury magiczne numery.Jeśli teraz program wczyta nazwę struktury i magiczny numer nie jest poprawny, oznacza to, że coś jest nie w porządku.Magiczne numery stosowane w strukturach nie muszą mieć w każdym bajcie ustawionego górnego bitu. Zastosowanie magicznych numerów tylko w przypadku znaków ASCII umożliwia łatwą identyfikację początku struktury w pliku zawierającym zrzut pamięci.

164

Page 165: Je41i zamierzasz zbudowac dom, bqdziesz …wieik.info/wp-content/uploads/2017/04/materialy/semestr i... · Web viewMożna tu wymienić nie tylko definicje i typy, ale również program,

165