Witam w kolejnym odcinku kursu C. Dziś lepiej przyjrzymy się operatorom i wyrażeniom przypisania oraz wyrażeniom warunkowym. Napiszemy także prosty przykład pokazujący działanie kwalifikatora "const", który poznaliśmy w poprzednim odcinku kursu.
Spróbujmy może napisać prosty przykład:
#include <stdio.h> main() { const int a = 5; printf("a=%d\n",a); a = a +10; printf("a=%d\n",a); return(0); }
Na początku naszego programu deklarujemy zmienną "a" typu "int", która będzie stała ("const") i będzie miała wartość równą "5". Dalej funkcja "printf()" wypisze wartość zmiennej "a". W następnej linijce zmiennej "a" przypiszemy wartość "a+10", ale czy zmienna "a" nie była zadeklarowana jako stała ("const")? Dalej instrukcja "printf()" powinna wypisać nową wartość zmiennej "a", natomiast na końcu funkcja "exit()" zakończy działanie programu. Spróbujmy teraz skompilować nasz program. Oto co wypisze nam vbcc:
Kompilator wykrył, że próbujemy przypisać zmiennej "a" (która ma być typu "const") jakąś wartość, co oczywiście jest operacją nieprawidłową. Podsumowując - każda zmienna, która została zadeklarowana z kwalifikatorem "const" nie może zmienić swojej wartości w dalszej części programu, a nawet przypadkowa zmiana tej wartości zostanie "wyłapana" przez kompilator.
Poznaliśmy już wyrażenia przypisania takie jak:
a = a + 10;
Zmienna która występuje po lewej stronie operatora jest zapisana jeszcze raz po stronie prawej, co można skrócić przez:
a += 2;
Operator += jest nazywany operatorem przypisania. Większość operatorów
dwuargumentowych, czyli takich jak np. "+", może posiadać odpowiedni operator
przypisania "op=", gdzie "op" jest jednym z operatorów:
+ - * / %
lub operatorów bitowych (które poznamy później):
<< >> & | ^
Zatem ogólny schemat będzie wyglądał następująco:
wyrażenie1 op= wyrażenie2;
będzie równoważne z następującym wyrażeniem:
wyrażenie1 = (wyrażenie1) op (wyrażenie2);
Np.
a *= a+3;
będzie równoważne z:
a = a * (a+3);
Np.
a -= a+12;
będzie równoważne z:
a = a - (a+12);
Operatory wyrażenia mogą być przydatne przy bardziej skomplikowanych wyrażeniach, a nawet mogą pomóc kompilatorowi przy generowaniu bardziej efektywnego kodu. Dlatego warto przyzwyczaić się do takiego zapisu.
Przyjrzyjmy się programowi:
#include <stdio.h> int pobierz(void); main() { int a,b; printf("Program sprawdza czy pobrana cyfra jest wieksza od zera:n"); a = pobierz(); if (a > 0) { printf("Cyfra %d,jest +\n",a); } else { printf("Cyfra %d,jest -\n",a); } return(0); } int pobierz(void) { char s[100]; int c,wynik; int i = 0; while((c = getchar()) != 'n') { if(isdigit(c) == 0 && c != '-') { printf("\nTo nie jest cyfra\n"); return(0); } s[i++] = c; } s[i] = '\0'; wynik = atoi(s); return wynik; }
Zadaniem tego programu jest sprawdzenie czy podana przez użytkownika cyfra jest dodatnia czy ujemna oraz czy w ogóle jest cyfrą.
Przeanalizujmy teraz kod naszego programu. Na początku pobierana jest z klawiatury cyfra, za pomocą znanej już funkcji "pobierz()". Dalej instrukcja "if" sprawdza czy spełniony jest warunek "(a>0)", czyli "a" większe od zera. Jeżeli rzeczywiście "a" jest większe od zera, to przy pomocy instrukcji "printf()" na ekranie wypisywany jest tekst, że cyfra jest dodatnia (+); jeżeli warunek nie jest prawdziwy, czyli "a" nie jest większe od zera, to na ekranie wypisywany jest tekst, że cyfra jest ujemna (-). Skupmy się na instrukcji "if", którą można zastąpić następującym wyrażeniem:
[...] printf("Program sprawdza czy pobrana cyfra jest wieksza od zera:\n"); a = pobierz(); printf("Cyfra %d jest: %c\n",a,(a > 0) ? '+' :'-'); [...]
Jest to wyrażenie warunkowe utworzone z trzyargumentowego operatora "? : ". Przyjrzyjmy się teraz prostszemu przykładowi.
if(a > b) c = a; else c = b;
Całe to wyrażenie spowoduje że zmienna "c", przyjmie wartość zmiennej o wyższej wartości ("a" lub "b"). Wyrażenie warunkowe daje nam możliwość zapisania tego w inny sposób:
c = (a > b) ? a : b;
Wyrażenie to jest równoznaczne z użyciem instrukcji "if" i "else". Tutaj najpierw oblicza się wyrażenie pierwsze (u nas "a > b"). Jeśli jego wartość będzie prawdziwa (czyli różna od zera), to oblicza się wyrażenie drugie (u nas zmienna "a") i jego wartość staje się wartością całego wyrażenia warunkowego. Jeżeli wartość wyrażenia pierwszego "a>b" będzie fałszywa (równa zero), to oblicza się wartość wyrażenia trzeciego (u nas zmienna "b") i to właśnie ono staje się wartością całego wyrażenia warunkowego. Czyli, jeżeli "a" rzeczywiście będzie większe od "b", to zmienna "c" przybierze wartość zmiennej "a", jeżeli natomiast zmienna "b" będzie większa od zmiennej "a", to zmienna "c" przybierze wartość zmiennej "b". Może to wydawać się trochę zawiłe, ale nie jest trudne.
Teraz nic nie stoi na przeszkodzie żeby napisać sobie własną funkcję "max()", która będzie liczyła która ze zmiennych "a" i "b" jest większa:
int max(int a,int b) { int c = 0; c = (a > b) ? a : b; return c; }
Funkcji przekazujemy dwie wartości ("a" i "b"), zostaje zadeklarowana zmienna "c" typu "int" (integer). Następnie zmienna "c" przyjmuje wartość zmiennej, która jest wyższa od pozostałej. Zostało tutaj użyte wyrażenie warunkowe. Następnie funkcja "max()" poprzez "return" zwraca wartość zmiennej "c". Oczywiście funkcję "max()" można zapisać również przy użyciu "if" i "else":
int max(int a,int b) { int c = 0; if(a > b) c = a; else c = b; return c; }
Jak widać pierwsza wersja funkcji "max()" jest krótsza. Taki zapis daje nam bardziej zwięzły kod programu, ale czasem lepiej jest użyć instrukcji "if". Wybór należy już do osoby piszącej dany program.
Obie wersje funkcji "max()" mają pewien minus, a mianowicie nie zinterpretują poprawnie sytuacji w której zmienna "a" będzie równa zmiennej "b". Spróbujcie dodać do funkcji obsługę takiego zdarzenia. Analogiczna sytuacja ma miejsce w programie wypisującym czy dana zmienna jest większa czy mniejsza od zera. Co stanie się jeżeli zmienna przyjmie wartość "0"?
Wróćmy teraz do naszego wcześniejszego przykładu:
printf("Cyfra %d jest: %c\n",a,(a > 0) ? '+' :'-');
Tutaj warunek wklejony jest wprost do instrukcji printf. Jeżeli zmienna "a" jest większa od zera, to zwracany jest jeden znak "+" (plus), a w przeciwnym wypadku "-" (minus). Funkcja "printf()" wypisuje ten znak poprzez deklaracje "%c", co oznacza jeden znak "char" (tekstowy).
Jeśli elementy w wyrażeniu warunkowym miałyby inne typy (np: "float" i
"int") to typ wyniku będzie przekształcony zgodnie z regułami
przekształceń, np:
"f" jest typu "float", "a" jest typu "int".
(a > 0) ? f : a
Tutaj wartość wyrażenia będzie typu float i nieważne jest to czy wartość "a" będzie dodatnia czy ujemna.
Spróbujmy teraz napisać program obliczający mandat, jaki musi zapłacić osoba, która przekroczyła prędkość. Skorzystamy tutaj z następującej skali:
A oto nasz program:
#include <stdio.h> #define PREDKOSC1 60 #define PREDKOSC2 80 #define PREDKOSC3 100 #define PREDKOSC4 120 #define MANDAT1 100 #define MANDAT2 150 #define MANDAT3 200 int pobierz(void); main() { int predkosc,mandat; printf("Prosze podac predkosc: "); predkosc = pobierz(); if(predkosc < PREDKOSC1) { mandat = 0; } else if(predkosc < PREDKOSC2) { mandat = MANDAT1; } else if(predkosc < PREDKOSC3) { mandat = MANDAT2; } else if(predkosc < PREDKOSC4 || predkosc >= PREDKOSC4) { mandat = MANDAT3; } printf("Mandat za predkosc %d km,wyniesie %d zl\n",predkosc,mandat); return(0); } int pobierz(void) { char s[100]; int c,wynik; int i = 0; while((c = getchar()) != 'n') { if(isdigit(c) == 0 && c != '-') { printf("\nTo nie jest cyfra\n"); return(0); } s[i++] = c; } s[i] = '\0'; wynik = atoi(s); return wynik; }
Skompilujmy teraz nasz program i spróbujmy go uruchomić.
Na początku naszego przykładu występuje deklaracja kilku stałych
symbolicznych ("#define"). Są tutaj zadeklarowane wartości prędkości
("PREDKOSC1", "PREDKOSC2", itd) oraz wysokości mandatów ("MANDAT1",
"MANDAT2", itd). Dalej deklaracja znanej już nam z poprzednich
odcinków funkcji "pobierz()", która będzie pobierać z klawiatury
liczby całkowite ("int"). Przejdźmy do funkcji "main()".
Na wstępie deklarujemy zmienne "mandat" i "predkosc" jako "int",
następnie "printf()" zachęca nas do podania wartości prędkości:
printf("Prosze podac predkosc: ");
Dalej zmienna "predkosc" przyjmuje wartość zwróconą jej przez
funkcję "pobierz()" i następuje ciąg instrukcji warunkowych "if".
Kolejno zostaje sprawdzany warunek czy zmienna "predkosc" jest
mniejsza od zadeklarowanych wcześniej stałych, np:
if(predkosc < PREDKOSC1) { mandat = 0; }
co oznacza że, jeżeli wartość zmiennej "predkość" jest mniejsza niż wartość stałej PREDKOSC1 (kierowca nie przekroczył 60 km), to wartość zmiennej "mandat" będzie wynosić zero (kierowca nie przekroczył prędkości, więc nie zapłaci mandatu).
else if(predkosc < PREDKOSC2) { mandat = MANDAT1; }
Jeżeli zmienna "predkosc" będzie mniejsza niż stała symboliczna "PREDKOSC2" to wartość zmiennej "mandat" będzie równa MANDAT1. W tym przypadku kierowca nie przekracza 80 km, ale z racji tego że poprzedni warunek został ominięty (nie był prawdziwy), to musiał on przekroczyć prędkość 60km. Czyli prędkość z jaką się poruszał, będzie się zawierać w przedziale 60-80km, czyli takiej prędkości odpowiada mandat 100 zł (MANDAT1).
Jeżeli prędkośc z jaką poruszał się kierowca, będzie zawierać się w przedziale 100-120 km, to mandat będzie wynosił 200 zł, ale co jeśli kierowca będzie jechał np. 150 km? Nie dostanie mandatu wcale? Przyjmijmy że dostaje wtedy mandat w wysokości 200 zł Ma to odwzorowanie w naszej instrukcji warunkowej "else if"
else if(predkosc < PREDKOSC4 || predkosc >= PREDKOSC4) { mandat = MANDAT3; }
jeżeli wartość zmiennej "prędkość" będzie mniejsza, równa bądź większa niż stała symboliczna "PREDKOSC4" (120 km), to kierowca otrzyma mandat w wysokości "MANDAT3" (200 zł).
Nasz program można napisać bez użycia stałych symbolicznych, ale z nimi jest on bardziej otwarty na przyszłe zmiany, np. jeśli wartości mandatów spadłyby, to trzeba by było zmienić wpisy tylko przy "#define", a nie szukać ich po kodzie programu. Gdyby natomiast użyć zmiennych (np: typu "int"), to program wymagałby więcej pamięci.
W języku C istnieje sześć operatorów, które pozwalają na manipulację
bitami. Mogą one być stosowane jedynie do argumentów
char, short, int, long, tak samo ze znakiem liczby, jak i bez znaku:
& - bitowa koniunkcja (AND)
| - bitowa alternatywa (OR)
^ - bitowa różnica arytmetyczna (XOR)
<< - przesunięcie w lewo
>> - przesunięcie w prawo
~ - dopełnienie jedynkowe (operator jednoargumentowy)
Być może ktoś z was spotkał się już z podobnymi operatorami np. w
Amosie.
Nie będe tutaj opisywał zasady działania każdego z operatorów gdyż
chciałem tylko zasygnalizować ich występowanie w języku C.
Można powiedzieć krótko że służą one do zasłaniania i ustawiania odpowiednich
bitów. Pokaże tutaj tylko krótki przykład wykorzystania tych operatorów.
UWAGA!
Poniższy program będzie działał prawidłowo tylko na Classic Amidze.
Nie będzie działał np. na Pegasosie, czy AOne.
Program ten odwołuje się bezpośrednio do hardware`u Amigi, a mianowicie
włącza i wyłącza filtr dolnoprzepustowy Amigi.
Co to jest i do czego służy? Amigę wyposażono w filtr dolnoprzepustowy
o częstotliwości granicznej rzędu 7 kHz. W wyniku tego wysokie częstotliwości
dźwięku zostają obcięte. To w nich powstają szumy. Niestety filtr działa
jednocześnie na czterech kanałach dźwiękowych co czyni dźwięk "przytłumionym".
Włączenie filtru objawia się przyciemnieniem diody LED Amigi.
Filtr ten włącza się ustawiając bit #1 w rejestrze $bfe001.
Oto przykład:
#include <stdio.h> main() { int *led; led = 0xBFE001; printf("Naciśnij enter aby wyzerowac bit 1\n"); getchar(); *led = *led & 0x11111110; /* ************************************************** */ printf("Nacisnij enter aby ustawic bit 1\n"); getchar(); *led = *led | 0x00000001; /* ************************************************** */ printf("Koniec\n"); return(0); }
Nie będe opisywał na razie co to są wskaźniki itd. Tym zajmiemy się w następnych odcinkach kursu. W programie podkreślone są linijki kodu w których zastosowano operatory bitowe, a mianowicie bitową koniunkcję (AND, u nas "&" oraz bitową alternatywę (OR, u nas "|").
Program czeka na naciśnięcie klawisza enter (funkcja "getchar()"), a następnie zeruje pierwszy bit adresu $bfe001. Proszę spojrzeć na diodę LED Amigi i zobaczyć co się stanie. Po ponownym nacisnięciu entera pierwszy bit zostaje ustawiony (zobacz diodę LED). Ten prosty programik prezentuje praktyczne wykorzystanie operatorów bitowych. Na razie nie będą nam one potrzebne i nie będziemy się nimi zajmować, ale na pewno do nich wrócimy i wtedy je szerzej opiszę.
I to tyle na dzisiaj. W razie kłopotów proszę o kontakt.