Informacje wstępne

Uwaga, nie jest to w żadnym razie wyczerpujący kurs języka C, a jedynie najważniejsze informacje dotyczące programowania w nim.

  1. Kod w języku C to plik tekstowy czytany przez kompilator od lewej do prawej od góry do dołu
  2. Kod składa się z zestawu słów kluczowych, które są informacją o tym co jest oczekiwane do wykonania przez komputer
  3. Zmienna to narzędzie programisty do przechowywania danych.
  4. Zmienne deklarujemy następująco TYP NAZWA;, gdzie TYP określa jaki rodzaj danych chcemy przechować np.:
    • int: liczba całkowita
    • float lub double: liczba rzeczywista
    • char: liczba całkowita o wielkości 1 bajta (0..256 lub -128..127), zmienna przechowuje też pojedynczą literę (ważne: w C litery to tylko kody liczbowe np. 'A' == 65)
    • void: wartość pusta, patrz poniżej
  5. Tablica to indeksowana sekwencja zmiennych tego samego typu występująca pod jedną nazwą. Co to oznacza? Że jeśli oczekujemy N liczb całkowitych, to nie tworzymy N zmiennych o różnych nazwach, ale raczej jedną zmienną tablicową o wielkości N
  6. Tablice deklarujemy poprzez podanie wielkości w nawiasach kwadratowych po nazwie: TYP NAZWA[WIELKOŚĆ];
  7. Instrukcja to wyrażenie (rozkaz wykonania czegoś) zakończone średnikiem (to bardzo ważne!) np.:
    • zmienna = wartość; - przypisanie wartości zmiennej
      • oprócz operatora przypisania "=" istnieje jeszcze szereg innych: "+" dodawanie, "-" odejmowanie, "*" mnożenie, "/" dzielenie, "%" reszta z dzielenia
    • tablica[indeks] = wartość; - wykorzystanie tablicy
    • if (warunek) instrukcja; - warunkowe wykonanie instrukcji
    • if (warunek) instrukcja1; else instrukcja2; - warunkowe wykonanie instrukcji z obsługą zarówno sytuacji gdy warunek zachodzi i nie zachodzi
  8. Warunek to wyrażenie składające się z jednego lub więcej testów logicznych najczęściej takiej postaci: zmienna1 OPERATOR zmienna2, gdzie OPERATOR to m.in.: "==" czy równe? (bardzo ważne: pojedynczy znak "równa się" to operator przypisania, a podwójny to operator sprawdzenia równości!), "!=" czy różne?, "<" i "<=" czy mniejsze/mniejsze lub równe?, ">" i ">=" czy większe/większe lub równe?
  9. Testy logiczne łączymy operatorami: "&&" AND, "||" OR
  10. Blokiem kodu nazywamy dowolny kod umieszczony między nawiasami klamrowymi {}
  11. W szczególnym przypadku blok uznawany jest przez kompilator jako instrukcja. Co to oznacza? Że jak najbardziej prawidłowe są konstrukcje: if (warunek) { instrukcja1; instrukcja2; ...; instrukcjaN; }
  12. Zmienna dostępna jest tylko w obrębie bloku, w którym została zadeklarowana. Jeśli była umieszczona na głównym poziomie, poza jakimkolwiek blokiem, to staje się tzw. zmienną globalną widoczną w całym programie.
  13. Pętle są podobne do instrukcji warunkowych, z tą różnicą, że warunek sprawdzany jest cyklicznie i instrukcje wykonują się dopóki jest spełniony. Uwaga, w szczególności kompilatory C "nie ingerują" w zamysł programisty i można bardzo łatwo stworzyć nieskończone pętle, tzn. takie których warunek wyjścia nigdy nie zostanie spełniony. To najprostszy sposób na 100% użycia CPU
  14. W C mamy trzy rodzaje pętli:
    • while (warunek) { CIAŁO_PĘTLI }, wykonuj ciało pętli dopóki spełniony jest warunek
    • do { CIAŁO_PĘTLI } while (warunek);, jw. ale warunek sprawdzany jest na końcu. Czyli ciało pętli wykona się co najmniej raz! W przypadku pętli while() {} tak nie musi być
    • for(INICJALIZACJA; WARUNEK; ZMIANA) { CIAŁO_PĘTLI }, to bardzo wszechstronna i często używana pętla w C. W nawiasie podajemy trójkę, która określa nam stan początkowy, oczekiwany stan końcowy oraz sposób aktualizacji stanu w każdym przebiegu pętli. Przykład: int i; for(i = 0; i < 10; i++) { ... }. Ta pętla rozpocznie od i równego zero i dopóki spełniony będzie warunek i < 10 program wykona ciało pętli zakończone instrukcją i++ (zwiększ i o jeden)
  15. Funkcje to nazwane bloki kodu, które można wykonywać dla zadanych argumentów i które zwracają jakąś wartość (w szczególnym przypadku wartość pustą)
  16. Funkcje deklarujemy następująco: TYP_ZWRACANY NAZWA(ARGUMENTY) { CIAŁO_FUNKCJI }, gdzie ARGUMENTY to lista wpisów TYP NAZWA oddzielonych przecinkami. Przykład: float mul(float x, float y) { return x*y; }. Lista argumentów może być pusta.
  17. Jak widać na powyższym przykładzie, osobne słowo kluczowe return służy do zwrócenia wartości funkcji. Jeśli jest ona typu void, to return nie musi się pojawić, ale może w postaci bezargumentowej np. w takiej sytuacji: void f() { if (warunek) return; instrukcja1; instrukcja2; ...; instrukcjaN }, Funkcja f() zakończy działanie jeśli zajdzie warunek (ponieważ instrukcja return powoduje natychmiastowe wyjście z funkcji). W przeciwnym razie wykonane zostaną odpowiednie instrukcje.
  18. Każdy program musi posiadać dokładnie jedną funkcję main() o następującym prototypie: int main(int argc, char **argv)

Struktura programu

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
    return 0;
}

Objaśnienie:

  1. #include <biblioteka> - program będzie wykorzystywał daną bibliotekę
  2. stdio.h - standard i/o, biblioteka wejścia/wyjścia, czyli służąca do czytania i do zapisu danych
  3. stdlib.h - standard library, biblioteka standardowa C, zawiera zestaw interesujących i pomocnych funkcji
  4. int main(int argc, char **argv)
    • funkcja main() zwraca liczbę całkowitą, która jest tzw. kodem błędu. Przy poprawnym zakończeniu programu oczekiwane jest zero
    • argument argc informuje o tym ile argumentów przekazano w linii poleceń
    • argument argv to tablica tych argumentów

Na co warto zwrócić uwagę

  1. Średniki na końcu instrukcji!
  2. Klamry muszą być poprawnie zamknięte. Zalecane jest używać klamr także dla pojedynczych instrukcji w przypadku if oraz pętli
  3. Język C nie posiada osobnego typu logicznego (większość innych języków posiada). Ma to swoje plusy i minusy. W języku C przyjęto założenie, że wszystko co jest różne od zera oznacza PRAWDĘ, a wartość zero oznacza FAŁSZ. Dlatego też możliwe są np. takie konstrukcje: if (x*f(x)). Jak widać nie ma tutaj żadnego operatora, więc domyślnie sprawdzane jest czy wyrażenie w nawiasie jest PRAWDĄ (różne od zera)
  4. Ostrzeżenia kompilatora bardzo rzadko kiedy są pomijalne. Warto kompilować przy maksymalnym poziomie ostrzeżeń (-Wall) i poprawiać kod, nawet jeśli "wszystko działa"
  5. C jako niskopoziomowy język ogranicza w niewielkim stopniu, ale za to wymaga i surowo karze ;). Jest mnóstwo sytuacji jak najbardziej poprawnych składniowo, które skompilują się bez ostrzeżenia, ale których pliki wykonywalne również bez ostrzeżenia, natomiast konsekwentnie i bezwzględnie, wywalają się podczas uruchomienia. Najprostszy przykład: próba odczytu z tablicy elementów o indeksach ujemnych
  6. Styl programowania jest bardzo ważny:
    1. Kod ma być przede wszystkim czytelny dla innych programistów. Nawet jeśli nie przewidujemy takowych w żadnej dającej się wyobrazić przyszłości :)
    2. Zmienne powinny być nazywane spójnie w obrębie danego programu. Jeśli stosujemy angielskie nazwy, niech będą wszystkie angielskie
    3. Dla języka C historycznie przyjmuje się, że nazwy pisane są małymi literami, a znakiem odstępu jest dolne podkreślenie "_". Nie jest to żaden wymóg formalny języka, ale dobra praktyka. Przykład: licz_wystapienia() albo is_open
    4. Nazywanie zmiennych i funkcji to sztuka :). Trzeba umiejętnie połączyć niekoniecznie współgrające cechy. Krótsza nazwa jest lepsza od dłuższej, ale "nicniemówiąca" jest znacznie gorsza od "samotłumaczącej się" :). Czasem lepiej jest skrócić wyrazy jeśli nie powoduje to pogorszenia zrozumienia np. "nr_konta", ale w ogólności patrz podpunkt 1 - czytelność jest najważniejsza i to powinno dyktować warunki nazywania zmiennych
    5. Praktyczna zasada jest taka, że im większy zakres zmiennej, tym łatwiejsza powinna być jej rola do zrozumienia. Dlatego zmienne globalne powinny być szczególnie dobrze nazwane i najlepiej jeszcze opatrzone komentarzem. A w obrębie małej pętli można pozwolić sobie na nieco tajemnicy :) np. nazywając zmienną po prostu "ile" - oczywiście jeśli jedno spojrzenie na ciało pętli od razu tłumaczy jaką wielkość zliczamy
    6. Kod powinien być minimalny - jeśli można bez utraty ogólności z czegoś zrezygnować lub zrobić to prościej, należy tak postąpić
    7. Zmienne powinny mieć minimalny możliwy zakres. Jeśli wykorzystujemy dane tylko w obrębie pętli, należy zadeklarować odpowiadające im zmienne już w ciele pętli
    8. W programowaniu obowiązuje zasada DRY (Don't Repeat Yourself). Jeżeli podobne lub nawet te same operacje mają zostać wykonane, należy utworzyć funkcję na tyle ogólną by obejmowała swoją funkcjonalnością cały zakres wymaganych operacji. Następnie w kodzie wywołujemy po prostu tę funkcję
    9. Komentarze są bardzo ważne, ale też nie można z nimi przesadzać. Powinny one tłumaczyć kod, ale nie na poziomie kursu z języka C :). Rolą komentarzy w kodzie jest przekazanie komunikatu innym programistom - dlaczego ten kod wygląda tak, a nie inaczej? dlaczego funkcja oczekuje akurat takich danych? itp.
    10. Wcięcia... Każde otwarcie bloku kodu powinno przesunąć cały kod w swoim obrębie (np. o 4 spacje), a zamknięcie cofnąć to do poprzedniej pozycji. Przykład:
if (warunek) {
    int zmienna;
    zmienna = funkcja();
    if (zmienna > 10) {
        int i;
        for (i = 0; i < 10; i++) {
            funkcja_numer_dwa(i);
        }
    }
}