Typy, operatory i wyrażenia.
Nazwy zmiennych
- Zmienna to ciąg liter lub cyfr.
- Pierwszym znakiem musi być litera.
- Znak podkreślenia traktowany jest jak litera.
- Zarezerwowane są pewne słowa kluczowe, które muszą być
pisane z małej litery:
auto |
double |
int |
struct |
break |
else |
long |
switch |
case |
enum |
register |
typedef |
char |
extern |
return |
union |
const |
float |
short |
unsigned |
continue |
for |
signed |
void |
default |
goto |
sizeof |
volatile |
do |
if |
static |
while |
Typy i rozmiary zmiennych
Typy podstawowe
- char - przechowywanie jednego znaku z lokalneog zioru
znaków.
- int - całkowity
- float - zmiennoprzecinkowy pojedyńczej precyzji
- double - zmiennoprzecinkowy podwójnej precyzji
Kwalifikatory typu
- short, long
Zasadniczo odnoczą się do typu
całkowitego. Pozwalają na operowanie różnymi zakresami liczb
całkowitych. W takich deklaracjach słowo int może zostać
pominięte.
short int x; short x;
int y; int y;
long int z; long z;
| |
Listing 1.1
|
short int
reprezentowany jest na co najmniej 16 bitach, long int na
co najmniej 32 bitach. Ponadto zakresy wartości spełniają
zależność short < int <
long
Dodatkowo dopuszczalny jest zapis long
double oznaczający typ zmiennopozycyjny o rozszerzonej
precyzji.
- signed, unsigned
Odnoszą się do obiektów
całkowitych lub znakowych.
Liczby unsigned są zawsze
większe lub równe zero.
Jeśli obiekt typu char
zajmuje 8 bitów, wówczas zmienna unsigned char przyjmuje
wartości od 0 do 255, signed char zaś od -128 do 127. To
czy zwykły obiekt typu char jest liczbą ze znakiem czy bez
znaku, zależy od maszyny, przy czym wartości znaków drukowalnych
są zawsze większe od zera.
signed int x;
unsigned int y;
| |
Listing 1.2
|
W plikach nagłówkowych
<limits.h> i <float.h> zdefiniowano stałe symboliczne
określające zakres zmienności dla każdego (łącznie ze
zmodyfikowanymi) z typów.
Stałe
Całkowite dziesiętne
Stała całkowita, jak 123 jest obiektem
typu
int.
Jesli na końcu występuje litera 'l' lub 'L', na
przykład 123l lub 123L to stała jest obiektem typu
long.
Stała nie mieszcząca się w zakresie typu
int
jest traktowana jak stała typu
long.
W stałych typu
unsigned na końcu występuje litera 'u' lub 'U'.
'ul' lub
'UL' oznacza stałą typu
unsigned long.
Całkowite ósemkowe
Stała taka rozpoczyna się od 0 (zero), na
przykład: 0123, 07, 0377.
Całkowite szesnastkowe
Sała taka rozpoczyna się od '0x' lub
'0X', na przykład: 0x123, 0X07, 0x377.
Zmiennopozycyjne
Typem stałej zmiennopozycyjnej jest typ
double. Jeśli na końcu podamy literę 'f' lub 'F' to jest ona
obiektem typu
float, jeśli zaś 'l' lub 'L' to obiektem typu
long double. Przykłady
123.4 |
+123.4 |
-123.4 |
1234e-1 |
+1234e-1 |
-1234e-1 |
12.34e+1 |
+12.34e+1 |
-12.34e+1 |
Znakowe
Stałą znakową tworzy jeden znak ujęty w apostrof, na
przykład 'a'.
Stała znakowa traktowana jest jak liczba
całkowita.
Pewne znaki niegraficzne mogą byc reprezentowane w
stałych znakowych i napisowych przez sekwencje specjalne:
Sekwencja |
Znaczenie |
\a |
alarm |
\b |
cofanie |
\f |
nowa strona |
\n |
nowy wiersz |
\r |
powrót karetki |
\t |
tabulacja pozioma |
\v |
tabulacja pionowa |
\0 |
znak pusty |
\\ |
znak \ |
\? |
znak ? |
\' |
znak ' |
\" |
znak " |
\ooo |
liczba ósemkowa |
\xhh |
liczba szesnastkowa |
Dwie ostatnie
sekwencje wykorzystywane są do tworzenia dowolnego bitowego wzorca
bajtu.
Napisowe
Jest to ciąg złożony z zera lub więcje znaków
zawarty między znakami cudzysłowu:
Stała napisowa jest
tablicą, której elementami są znaki. Zawsze jest ona zakończona
znakiem '\0'; zatem jej długość jest o jeden większa od ilości
znaków użytych w napisie. W szczególności zapis "a" jest czymś
istotnie różnym od zapisu 'a'. W pierwszy przypadku mamy do
czynienia z tablicą znaków zawierającą jeden znak, którym jest
litera 'a' oraz dodatkowo znak '\0'. W drugim zaś mamy znak o
wartości numerycznej kodu litery 'a' w maszynowy zbiorze znaków.
Wyliczeniowe
Wyliczenie jest listą wartości stałych
całkowitych.
enum boolean {TAK, NIE}
enum miesiace {STYCZEN=1, LUTY, MARZEC, KWIECIEN, MAJ}
enum dniparzyste {PON=0, WT=1, SR=0, CZW=1, PIA=0, SOB=1, NIE=0}
| |
Listing 1.4
|
Pierwsza nazwa na liście
wyliczenia ma wartość 0, następna 1 i tak dalej, chyba, że wystąpi
jawne podanie wartości. Jeśli nie podano jawnie wszystkich wartości
dla nazw, to kolejne nie sprecyzowane wartości stanowią postęp
arytmetyczny, który rozpoczyna się od ostatnio określonej wartości.
Nazwy wstępujące w różnych wyliczeniach muszą być różne. W tym samym
wyliczeniu wartości mogą się powtarzać. (patrz program)
#include <stdio.h>
enum bol {TAK=1, NIE=0};
enum jakis {T1,T2,T3=7,T4,T5=11,T6, T7=7};
int main(void)
{
enum bol x=TAK;
enum bol y=NIE;
enum jakis n1=T1;
enum jakis n2=T2;
enum jakis n3=T3;
enum jakis n4=T4;
enum jakis n5=T5;
enum jakis n6=T6;
enum jakis n7=T7;
printf("%d %d\n",x,y);
printf("%d %d %d %d %d %d %d\n",n1,n2,n3,n4,n5,n6,n7);
return 0;
}
| |
Listing 1.5 Użycie typu wyliczeniowego.
|
|
Output 1.5 Efekt działania programu z
listingu 1.5 |
Wyrażenia stałe
Są to wyrażenia, w których występują
wyłącznie stałe. Takie wyrażenia mogą być obliczane na etapie
kompilacji a nie podczas wykonania programu.
#define MAX 10
int tab1[13+1];
char tab[MAX+1];
| |
Listing 1.6 |
Deklaracje
Wszystkie zmienne muszą być przed użyciem
zadeklarowane. W deklaracji określa się typ a następnie wymienia
jedną lub więcej zmiennych tego typu. W deklaracji można także
nadawać zmiennym wartości początkowe.
char a;
int b,c,d;
float e=7.5;
double f=8.1,g=9.6;
const int temp=12;
| |
Listing 1.7
|
Zmiennym zewnętrznym
(globalnym) i statycznym nadaje się wartość zero. Zmiene
automatyczne bez jawnie określonej wartości początkowej mają
wartości przypadkowe (patrz program poniżej).
Przy deklaracji
dowolnej zmiennej można zastosować operator
const. Informuje
on, że wartość takiej zmiennej nie będzie zmieniany w programie.
#include <stdio.h>
int x; /* zmienna globalna bez nadania wartosci poczatkowej */
int y=5; /* zmienna globalna z nadaniem wartosci poczatkowej */
int main(void)
{
int m; /* zmienna wewnetrzna bez nadania wartosci poczatkowej */
int n=7; /* zmienna wewnetrzna z nadaniem wartosci poczatkowej */
printf("%d %d %d %d\n",x,y,m,n);
return 0;
}
| |
Listing 1.8 Definicja zmiennych zewnętrznych
(globalnych) i wewnętrznych.
|
|
Output 1.8 Efekt działania programu z
listingu 1.8 |
Deklaracje
występują zawsze na początku bloku.
Operatory
Przykłady użycia operatorów są na listingu 1.9
Arytmetyczne
- jednoargumentowe: +, -
- dwuargumentowe: +, -, *, /, % (modulo, reszta z dzielenia)
Relacyjne
>, <, >=, <=, == (porównanie), !=
Logiczne
Operator |
Znaczenie |
|| |
OR |
&& |
AND |
! |
NOT |
Bitowe
Operator |
Znaczenie |
| |
OR |
& |
AND |
~ |
NOT |
^ |
XOR |
>> |
RHS, Right Shift, przesunięcie bitowe w prawo |
<< |
LHS, Left Shift, przesunięcie bitowe w
lewo |
Zmniejszania/zwiększania
(dekrementacji/inkrementacji)
Operatory te występują w formie
przedrostkowej: --zmienna, ++zmienna
oraz przyrostkowej:
zmienna--, zmienna++.
Różnicę wyjaśnimy na przykładzie operatora
inkrementacji. Zapis ++n powoduje zwiększenie zmiennej n
przed jej użyciem, natomiast zapis n++ powoduje zwiększenie n
po użyciu jej poprzedniej wartości.
Przypisania
Operator |
Przykład użycia |
Znaczenie |
= |
x=7; |
przypisanie, x przyjmuje wartość stojącą po prawej stronie
operatora |
+= |
x+=y |
równoważny x=x+y |
-= |
x-=y |
równoważny x=x-y |
*= |
x*=y |
równoważny x=x*y |
/= |
x/=y |
równoważny x=x/y |
%= |
x%=y |
równoważny x=x%y |
<<= |
x<<=y |
równoważny x=x<<y |
>> |
x>>=y |
równoważny x=x>>y |
& |
x&=y |
równoważny x=x&y |
^ |
x^=y |
równoważny x=x^y |
| |
x|=y |
równoważny x=x|y |
Przecinkowy
,
Na przykład x,y. Wartością jego jest
wartość stojąca po prawej stronie.
Referencji
&
Dereferencji
*
Warunkowy
?: (Więcej patrz opis instrukcji
if-else).
Priorytety i łączność operatorów
(Im niżej w tabeli, tym
niższy priorytet)
Operatory |
Łączność |
() [] -> . |
lewostronna |
! ~ ++ -- + - * & (typ) sizeof |
prawostronna |
* / % |
lewostronna |
+ - |
lewostronna |
>> << |
lewostronna |
>= > <= < |
lewostronna |
== != |
lewostronna |
& |
lewostronna |
^ |
lewostronna |
| |
lewostronna |
&& |
lewostronna |
|| |
lewostronna |
?: |
prawostronna |
= += -= *= /= %= ^= |= >>= <<= |
prawostronna |
, |
lewostronna |
Jak należy rozumieć
łączność operatorów? Na przykład operator = jest prawostronnie
łączny a zatem wyrażenie
a=b=c=d=4;
równoważne jest wyrażeniu
a=(b=(c=(d=4)));
Nie określa się kolejności obliczania wartości argumentów
operatora. Na przykład w instrukcji
x=f()+g();
f może być obliczone przed g lub odwrotnie.
#include <stdio.h>
int main(void)
{
int x=20;
unsigned char b1=1; /* bitowo 00000001 */
unsigned char b2=3; /* bitowo 00000011 */
unsigned char b3;
int t;
int n1=3, n2=3;
int a=1,b=2,c=3,d=4;
/* modulo - reszta z dzielenia */
t=x%4;
printf("%d\n",t);
t=x%7;
printf("%d\n",t);
/* bitowe */
b3=b1 | b2; /* OR na bitach; wynik 00000011 czyli 3 dziesietnie */
printf("%d\n",b3);
b3=b1 & b2; /* AND na bitach; wynik 00000001 czyli 1 dziesietnie */
printf("%d\n",b3);
b3=~b1; /* NOT na bitach; wynik 11111110 czyli 254 dziesietnie */
printf("%d\n",b3);
b3=b1 ^ b2; /* XOR na bitach; wynik 00000010 czyli 2 dziesietnie */
printf("%d\n",b3);
b3=b1<<7; /* przesuniecie bitow o 3 w lewo; wynik 10000000 czyli 128 dziesietnie */
printf("%d\n",b3);
b2=b3>>2; /* przesuniecie bitow o 2 w prawo; wynik 00100000 czyli 32 dziesietnie */
printf("%d\n",b2);
printf("%d %d\n",++n1,n2++); /* inkrementacja przedrostkowa i przyrostkowa */
n1=3;
n2=n1++ + ++n1;
printf("%d %d\n",n1,n2);
t=(a,b,c,d);
printf("%d\n",t);
a=b=c=d; /* lacznosc prawostrona - wszstkie beda rowne 4, jesli lewostrona
to wszystkie by mialy wartosc 1 */
printf("%d %d %d %d\n",a,b,c,d);
return 0;
}
| |
Listing 1.9 Przykłady użycia operatorów
|
0
6
3
1
254
2
128
32
4 3
5 8
4
4 4 4 4
| |
Output 1.9 Efekt działania programu z
listingu 1.9 |
Sterowanie przebiegiem wykonania programu
W języku C wyrażeni staje się
instrukcją jeśli jest zakończone średnikiem. Nawiasy klamrowe { i } służą do
grupowania deklaracji i instrukcji w jedną instrukcję złożoną czyli blok. Taki
blok składniowo równoważny jest jednej instrukcji i może wystąpić wszędzie tam
gdzie może wystąpić instrukcja.
Instrukcja if-else
Instrukcja ta służy do podejmowania decyzji. Formalna
jej postać wygląda następująco:
if (wyrażenie)
instrukcja1
else
instrukcja2
| |
Listing 1.10 Instrukcja if-else
|
Część
else jest opcjonalna. Działanie
opisać można w następujący sposób. Najpierw oblicza sie wyrażenie. Jeśli jest
prawdziwe (za prawdziwe przyjmuje się każde wyrażenie, które zwróci wartość
różną od zera), to zostanie wykonana instrukcja1. W przeciwnym razie (jeśli
występuje część
else), wykonana zostanie instrukcja2. Schemat działania
przedstawia rysunek poniżej

Konstrukcja else-if
Uogólnieniem instrukcji
if-else jest
konstrukcja
else-if. Wbrew pozorom nie jest to żadna nowa instrukcja,
tylko inaczej zapisana instrukcja
if-else.
if (wyrażenie1)
instrukcja1
else if (wyrażenie2)
instrukcja2
else if (wyrażenie3)
instrukcja3
else if (wyrażenie4)
instrukcja4
else
instrukcja5
| |
Listing 1.11 Konstrukcja else-if
|
Instrukcja switch
Instrukcja
switch służy do podejmowania decyzji
wielowariantowych, w których sprawdza się, czy wartość pewnego wyrażenia pasuje
do jednej z kilku całkowitych stałych wartości, i wykonuje odpowiedni skok.
switch (wyrażenie)
{
case stałe_wyrażenie1:
instrukcje1
case stałe_wyrażenie2:
instrukcje2
case stałe_wyrażenie3:
instrukcje
default:
instrukcje
}
| |
Listing 1.12 Instrukcja switch
|
Jeśli jeden z przypadków jest zgodny z
wartością wyrażenia, to od niego rozpocznie się dalsze wykonanie programu.
Wszystkie wartość stałych_wyrażeń w przypadkach musza być różne. Przypadek
nazwany
default zostanie wykonany wtedy, kiedy żaden inny przypadek nie
jest zgodny z wartością wyrażenia. Przypadek ten nie jest obowiązkowy. Wszystkie
przypadki mogą występować w dowolnej kolejności. Poniważ przypadki znaczą tyle
co etykiety, toteż po wykonaniu instrukcji związanych z jednym przypadkiem
sterowanie przechodzi do następnego przypadku, chyba, że jawnie podjemie się
akcję przerywającą. Najczęściej do tego celu wykorzystuje się instrukcję
break (patrz przykład poniżej).
#include <stdio.h>
int main(void)
{
int x;
x=2;
switch (x)
{
case 1:
printf("switch 1.1\n");
case 2:
printf("switch 1.2\n");
case 3:
printf("switch 1.3\n");
default:
printf("switch 1 default\n");
}
x=7;
switch (x)
{
case 1:
printf("switch 2.1\n");
case 2:
printf("switch 2.2\n");
case 3:
printf("switch 2.3\n");
default:
printf("switch 2 default\n");
}
x=2;
switch (x)
{
case 1:
printf("switch 3.1\n");
break;
case 2:
printf("switch 3.2\n");
break;
case 3:
printf("switch 3.3\n");
break;
default:
printf("switch 3 default\n");
break;
}
x=7;
switch (x)
{
case 1:
printf("switch 4.1\n");
break;
case 2:
printf("switch 4.2\n");
break;
case 3:
printf("switch 4.3\n");
break;
default:
printf("switch 4 default\n");
break;
}
x=7;
switch (x)
{
case 1:
printf("switch 5.1\n");
break;
case 2:
printf("switch 5.2\n");
break;
case 3:
printf("switch 5.3\n");
break;
}
return 0;
}
| |
Listing 1.13 Przykłady użycia instrukcji switch
|
switch 1.2
switch 1.3
switch 1 default
switch 2 default
switch 3.2
switch 4 default
| |
Output 1.13 Efekt działania programu z listingu 1.13
|
Pętla while
while służy do powtażania (iterowania) pewnej
instrukcji.
while (wyrażenie)
instrukcja
| |
Listing 1.14 Instrukcja pętli while
|
Najpierw oblicza się wyrażenie. Jeśli jego
wartość jest różna od zera, to wykonuje się instrukcję, a następnie ponownie
oblicza wartość wyrażenia. Cykl ten powtarzany jest tak długo aż wartosć
wyrażenia stanie się zerem. Wtedy to sterowanie przechodzi do instrukcji
następującej po pętli
while. W ramach petli
while można w
szczególności użyć instrukcji
break lub
continue.
- break
powoduje przerwanie wykonywania pętli;
- continue
powoduje przerwanie bieżącego i wykonanie kolejnego
kroku pętli.
Schemat działania przedstawia rysunek poniżej
Pętla do-while
Zasadniczo działanie pętli
do-while podobne jest
do działania pętli
while z ta różnica, że warunek sprawdzany jest na
końcu każdego obrotu pętli. Stąd wniosek, że ta pętla wykona się zawsze co
najmniej jeden raz.
do
instrukcja
while (wyrażenie);
| |
Listing 1.15 Instrukcja pętli do-while
|
Podobniej jak to miało miejsce dla pętli
while, także i tutaj można wykorzystac instrukcje
break oraz
continue. Schemat działania przedstawia rysunek poniżej
Pętla for
Pętla for formalnie przedstawia się następująco
for (wyrażenie1;wyrażenie2;wyrażenie3)
instrukcja
| |
Listing 1.16 Instrukcja pętli for
|
i jest równoważna rozwinięciu
wyrażenie1;
while (wyrażenie2)
{
instrukcja
wyrażenie3;
}
| |
Listing 1.17 |
Podobniej
jak to miało miejsce dla wcześniejszych pętli, także i tutaj można wykorzystac
instrukcje
break oraz
continue. Schemat działania przedstawia
rysunek poniżej
Instrukcja skoku goto
Instrukcja
goto umożliwia skok
(przeniesienie wykonania) w inne miejsce programu - miejsce wskazane przez
etykietę (patrz przykład poniżej)
#include <stdio.h>
int main(void)
{
printf("Tekst 1\n");
goto etykietka1;
printf("Tekst 2\n");
etykietka1:
printf("Tekst 3\n");
return 0;
}
| |
Listing 1.18 Wykorzystanie instrukcji skoku
goto |
|
Output 1.18 Efekt działania programu z listingu 1.18
|