Środowisko programisty/Wprowadzenie do C

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania

Skąd wziąć kompilator języka C

Do pisania programów w C potrzebne są:

  1. Edytor tekstów. Może być dowolny, chociaż niekoniecznie najmądrzejszym posunięciem będzie pisanie w Wordzie.
  2. Kompilator C.

Można zakupić jeden z wielu komercyjnych kompilatorów języka C np:

albo użyć jednego z darmowych kompilatorów. Podczas tego kursu będziemy używać gcc, którego windowsową wersją jest DJGPP.

Linux

Instalacja gcc pod Linuksem nie nastręcza zazwyczaj trudności, wystarczy uruchomić menedżer pakietów, odnaleźć w nim gcc i zaznaczyć go do instalacji.

Windows

Pod Windows należy:

  1. Wejść do repozytorium pakietów, którego adres znajduje się na stronie DJGPP.
  2. Ściągnąć co najmniej pliki: djdev???.zip, bnu?????.zip i gcc????.zip. (???? oznaczają numer wersji, który się od czasu do czasu zmienia).
  3. Utworzyć katalog, w którym ma zostać zainstalowane DJGPP (np. c:\djgpp).
  4. Rozpakować wszystkie pliki *.zip.
  5. Ustawić zmienną środowiskową DJGPP (np. w autoexec.bat), na nazwę pliku konfiguracyjnego (zazwyczaj C:/DJGPP/DJGPP.ENV), należy pamiętać, aby w wartości zmiennej używać znaków / zamiast \.
  6. Do zmiennej środowiskowej PATH dodać katalog zawierający pliki wykonywalne (zazwyczaj C:\DJGPP\BIN)

Pomocny w instalacji DJGPP może być internetowy instalator

Kompilacja i uruchamianie programów

Przykładowy program

Aby uruchomić program w C, trzeba go najpierw napisać. Programy w C piszemy w dowolnym edytorze tekstu i zapisujemy w pliku o zwyczajowo przyjętym rozszerzeniu .c. Przyjrzyjmy się następującemu programowi:

#include <stdio.h>
int main()
{
  printf("Raz\n");
  return 0;
}

Program ten wypisuje na ekran napis Raz po czym kończy swoje działanie.

Kompilacja

Spróbujmy skompilować nasz program. Załóżmy, że nazwaliśmy go (to typowe) test.c. Uruchamiamy w shellu polecenie gcc i oto, co się dzieje:

krzysiek@krzysiek-desktop:~/Tmp$ gcc test.c
krzysiek@krzysiek-desktop:~/Tmp$

Wbrew pozorom to, że system nic nie wypisał na ekran, nie oznacza niczego złego. Po prostu program się poprawnie skompilował, ale nie wiemy, jak go uruchomić. Sprawdźmy, za pomocą polecenia ls, jakie inne pliki powstały w katalogu:

krzysiek@krzysiek-desktop:~/Tmp$ ls
a.out  test.c
krzysiek@krzysiek-desktop:~/Tmp$

Kompilator gcc domyślnie tworzy plik wykonywalny o nazwie a.out, a dla nas wygodniej by było gdyby nazywał się on jakoś inaczej, co uzyskujemy za pomocą opcji -o kompilatora. Czyli:

krzysiek@krzysiek-desktop:~/Tmp$ rm a.out
krzysiek@krzysiek-desktop:~/Tmp$ gcc test.c -o test
krzysiek@krzysiek-desktop:~/Tmp$ ls
test  test.c
krzysiek@krzysiek-desktop:~/Tmp$

No to teraz uruchamiamy nasz program:

krzysiek@krzysiek-desktop:~/Tmp$ test
krzysiek@krzysiek-desktop:~/Tmp$

Nic się nie stało. Dlaczego? Dlatego, że test jest wbudowanym poleceniem shella i aby uruchomić nasz program, potrzebujemy poprzedzić jego nazwę ścieżką:

krzysiek@krzysiek-desktop:~/Tmp$ ./test
Raz
krzysiek@krzysiek-desktop:~/Tmp$

W ten sposób napisaliśmy i uruchomiliśmy nasz pierwszy program w C

Błędy kompilacji

Co by się stało, gdyby nasz program miał jakieś błędy? Sprawdźmy.

Najpierw błąd, który nie jest groźny. W naszym programie używamy funkcji printf, a żeby jej użyć, dołączamy plik nagłówkowy stdio.h, w którym ta funkcja jest opisana. Co się stanie, jeśli nie dołączymy tego pliku?

int main()
{
  printf("Raz\n");
  return 0;
}

Spróbujmy skompilować i uruchomić ten program:

krzysiek@krzysiek-desktop:~/Tmp$ rm test
krzysiek@krzysiek-desktop:~/Tmp$ gcc test.c -o test
test.c: In function ‘main’:
test.c:3: warning: incompatible implicit declaration of built-in function ‘printf’
krzysiek@krzysiek-desktop:~/Tmp$ ./test
Raz
krzysiek@krzysiek-desktop:~/Tmp$

Kompilator ostrzegł nas (warning), że w linii trzeciej pliku test.c jest problem polegający na użyciu "nieznanej" funkcji printf, ale plik został skompilowany i program można uruchomić.

Weźmy teraz następujący przykład:

#include <stdio.h>
int main()
{
  printf("Raz\n");
}

Brak w nim instrukcji return, która powinna kończyć działanie funkcji main. Skompilujmy ten program:

krzysiek@krzysiek-desktop:~/Tmp$ gcc test.c -o test
krzysiek@krzysiek-desktop:~/Tmp$

Kompilator nie zgłosił żadnych błędów ani ostrzeżeń, ale program ma wadę. Dzieje się tak dlatego, że ostrzeżenia zostały podzielone na kilka kategorii, z których domyślnie zgłaszane są jedynie najważniejsze. Aby wyświetlić wszystkie ostrzeżenia należy użyć opcji -Wall.

krzysiek@krzysiek-desktop:~/Tmp$ gcc -Wall test.c -o test
test.c: In function ‘main’:
test.c:5: warning: control reaches end of non-void function
krzysiek@krzysiek-desktop:~/Tmp$

Na koniec program z błędem:

#include <stdio.h>
int main()
{
  printf("Raz\n);
  return 0;
}

brakuje w nim znaku " po \n

krzysiek@krzysiek-desktop:~/Tmp$ gcc test.c -o test
test.c: In function ‘main’:
test.c:4: error: missing terminating " character
test.c:5: error: syntax error before ‘return’
krzysiek@krzysiek-desktop:~/Tmp$

W tym przypadku program nie został skompilowany, więc nie można go uruchomić, a kompilator zgłosił błąd w czwartej linii (missing terminating " character).


Zmienne

Zmienne w C definiuje się podając ich nazwę poprzedzoną typem.

int a;

To jest deklaracja zmiennej a o typie int. Podstawowe typy proste w C to:

Nazwa Znaczenie
char pojedynczy znak
int liczba całkowita (typowo cztery bajty)
double liczba rzeczywista (typowo osiem bajtów)

Deklarując zmienne można podać ich wartości początkowe, można deklarować tabele oraz wiele zmiennych w jednej instrukcji:

int b = 10, tabela[20];

Jeżeli zmienna jest zadeklarowana wewnątrz funkcji, to jest to zmienna lokalna i jest widoczna tylko wewnątrz tej funkcji, w preciwnym wypadku zmienna jest globalna.

Standardowe wyjście

Do wypisania na standardowe wyjście (czyli w naszym przypadku na ekran), można użyć funkcji printf. W tym celu należy do programu dołączyć plik nagłówkowy stdio.h za pomocą dyrektywy #include. Należy to zrobić na początku programu.

#include <stdio.h>

Następnie można już wypisywać zarówno zmienne jak i stałe za pomocą instrukcji printf. Np.:

#include <stdio.h>
int main() {
  printf("Wypisujemy na ekran\n");
  return 0;
}

Program ten wypisze na ekran napis Wypisujemy na ekran i przejdzie do następnej linii (do tego służy \n). Zmienne wypisuje się również za pomocą funkcji printf. Przykłady:

#include <stdio.h>
int main() {
  int i = 1;
  float j = 1.3;
  char* s = "to jest napis";
  printf("%d\n", i);  // Wypisuje 1
  printf("%f\n", j);  // Wypisuje 1.300000
  printf("%s\n", s);  // Wypisuje to jest napis
  printf("%d tu %f tam %s\n", i, j, s); // Wypisuje 1 tu 1.300000 tam to jest napis
  return 0;
}

Program ten wypisuje:

1
1.300000
to jest napis
1 tu 1.300000 tam to jest napis

Jak łatwo zaważyć, pierwszym parametrem funkcji printf jest napis, który może zawierać elementy formatujące np. %d (wypisywana będzie liczba całkowita), %f (liczba rzeczywista), %s (napis). Po tym napisie następuje ciąg zmiennych, które zostaną wypisane zgodnie z formatowaniem ustalonym przez elementy formatujące.

Instrukcje warunkowe

if

W przypadku, kiedy chcemy, aby jakiś fragment programu był wykonany tylko po spełnieniu określonego warunku, możemy użyć instrukcji if. Ma ona następującą postać:

if (warunek) 
  instrukcja

instrukcja zostanie wykonana tylko wtedy kiedy warunek będzie spełniony.

Przykład:

if (i > 0)
  printf ("I jest dodatnie\n");

Oczywiście zamiast pojedynczej instrukcji można użyć całego bloku zawartego w nawiasy klamrowe:

if (i > 0) {
  printf ("I jest dodatnie\n");
  printf ("jak również nieujemne\n");
}

W przypadku, kiedy chcemy, aby w zależności od warunku logicznego została wykonana jedna albo druga instrukcja, możemy użyć formy:

if (warunek) 
  instrukcja_na_tak
else 
  instrukcja_na_nie

Na przykład:

if (i > 0)
  printf ("I jest dodatnie\n");
else
  printf ("I nie jest dodatnie, ale może jest zerem?\n");

Często w programach w C instrukcje if i elese zlewa się w jeden duży blok:

if (liczba_punktow > 90)
  printf ("Dostałeś 5\n");
else  if (liczba_punktow > 70)
  printf ("Dostałeś 4\n");
else  if (liczba_punktow > 50)
  printf ("Dostałeś 3\n");
else printf ("Dostałeś 2\n");

switch

W przypadku, kiedy w zależności od wartości jednej zmiennej chcemy wykonać jeden z wielu bloków, możemy użyć instrukcji switch.

switch (zmienna) {
 case wartosc1: instrukcja1;
 case wartosc2: instrukcja2;
 case wartosc3: instrukcja3;
 ...
 default: instrukcja_domyslna;
}

Zachowanie instrukcji switch może być mylące. Przykładowo dla następującego programu:

i = 1;
switch (i) {
 case 1: printf("OK\n");
 case 2: printf("Zle\n");
 case 3: printf("Zle\n");
}

oczekujemy, że wypisane zostanie OK, podczas gdy w rzeczywistości wypisywane jest zarówno OK jak i dwa napisy Zle. Dzieje się tak dlatego, że instrukcja switch po napotkaniu odpowiedniej wartości zmiennej, wykonuje wszystkie instrukcje aż do napotkania break.

Instrukcja domyślna zostanie wykonana, gdy żadna wartość nie zostanie dopasowana do zmiennej. Dobry program, wypisujący OK jeśli i ma wartość 1 i Zle jeśli i ma wartość 2 lub 3 wygląda następująco:

switch (i) {
 case 1: printf("OK\n"); break;
 case 2:
 case 3: printf("Zle\n"); break;
 default: printf("Tego tu nie powinno być\n");
}

Pętle

Do wielokrotnego wykonywania tego samego fragmentu programu w języku C można wykorzystać pętle.

while

Pętla while ma następują składnię:

while (warunek) {
  instrukcje
}

Pętla while działa w następujący sposób:

  1. Najpierw jest obliczana wartość logiczna warunku, czyli sprawdzane jest czy warunek jest spełniony.
  2. Jeśli warunek jest spełniony, to wykonywane są instrukcje, po czym ponownie sprawdzane jest, czy warunek jest spełniony.
  3. Dzieje się tak, aż do momentu, w którym warunek przestaje być spełniony.

Aby zrozumieć dokładnie działanie pętli while, przyjrzyjmy się następującemu kodowi:

int i;
int j;
i = 1;
j = 0;
while (i < 3) {
  i = i + 2;   
  j = j + i;   
  i = i - 1;   
}
  1. Przed wejściem do pętli zmienne i oraz j mają odpowiednio wartości 1 i 0.
  2. Po pierwszym przejściu pętli i oraz j mają wartości 2 i 3.
  3. Po drugim przejściu pętli i oraz j mają wartości 3 i 7, a więc nie jest spełniony warunek (i < 3) i program nie wejdzie do pętli po raz kolejny.

Zwróćmy uwagę na to, że w trakcie pierwszego przejścia pętli, po instrukcji i = i + 2, wartość i wynosiła 3, czyli nie był spełniony warunek logiczny (i < 3), ale program nie wyskoczył z pętli. Dlaczego tak się stało? Ponieważ wartość wyrażenia logicznego jest wyliczana po wykonaniu wszystkich instrukcji z wnętrza pętli, a w naszej pętli znajduje się instrukcja i = i - 1, po wykonaniu której i przyjęło wartość 2 i warunek był już spełniony.

Istnieje również druga postać instrukcji while, w krórej warunek jest sprawdzany po, a nie przed wykonaniem instrukcji. Posiada ona następującą składnię:

do {
  instrukcje
} while (warunek)

for

Pętla for ma następującą składnię:

for (inicjalizacja; warunek logiczny; akcja)
{
  instrukcje
}

Działa ona w sposób następujący:

  1. Wykonywana jest inicjalizacja
  2. Sprawdzany jest warunek logiczny; jeśli jest on niespełniony, to pętla kończy swoje działanie
  3. Wykonywane są instrukcje
  4. Wykonywana jest akcja

Przyglądnijmy się następującemu programowi:

int i;
int j;
j = 0;
for (i = 1; i <= 10; i = i + 1) { 
  j = j + i;
}

Sumuje on liczby od 1 do 10.

  1. Na początku pętli for wartość i jest ustawiana na 1
  2. Następnie i jest dodawane do j
  3. Potem wartość i jest powiększana o 1
  4. I jest sprawdzane czy i nie jest większe do 10
  5. Po wyjściu z pętli j ma wartość 55 (czyli suma liczb od 1 do 10) a i ma wartość 11

break, continue

Wewnątrz każdej pętli mogą pojawić się instrukcje break i continue.

Instrukcja break wychodzi z pętli (wyskakuje poza nią)

Przyjrzyjmy się następującemu fragmentowi programu:

i = 0;
while (1) {
  i = i + 1;
  if (i > 100) break;
}

Ponieważ 1 odpowiada logicznej prawdzie, więc pętla while (1) {...} mogłaby być pętlą nieskończoną. Po wejściu do niej program nigdy by się nie zakończył. W naszym przykładzie po osiągnięciu przez i wartości 101 zacznie być spełniony warunek (i > 100) i zostanie uruchomiona instrukcja break, która wyjdzie z pętli.

Instrukcja continue przechodzi do warunku logicznego, który jest sprawdzany przy wejściu do pętli.

Zadania

  1. Napisz program wypisujący liczby parzyste od 2 do 16
  2. Napisz program wypisujący liczby niepodzielne przez 5 z przedziału od 1 do 100. Reszta z dzielenia przez jakąś liczbę jest uzyskiwana przez operator %. Np (i % 4) to reszta z dzielenia i przez 4.
  3. Napisz kawałek programu, który sprawdza, czy para liczb (k,m) sumuje się do liczby większej niż 100 i wypisuje odpowiedź na to pytanie na ekran.