Wykład 1
Typy, operatory i wyrażenia. Sterowanie przebiegiem wykonania programu

Typy, operatory i wyrażenia.

Nazwy zmiennych

  1. Zmienna to ciąg liter lub cyfr.
  2. Pierwszym znakiem musi być litera.
  3. Znak podkreślenia traktowany jest jak litera.
  4. 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

Kwalifikatory typu

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:
"To jest napis"
"a"
""
Listing 1.3
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.
1 0
0 1 7 8 11 12 7
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.
0 5 4206596 7
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

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. 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
Tekst 1
Tekst 3
Output 1.18 Efekt działania programu z listingu 1.18