Wprowadzenie
Historia
Język C został napisany przez B. Kernighan'a i D. Ritchie'go. Początkowo język ten był przeznaczony do tworzenia oprogramowania systemowego (przy jego pomocy został napisany system operacyjny UNIX). W miarę upływu czasu stał się językiem ogólnego przeznaczenia.
Pierwsze wersje systemu UNIX były rozpowszechniane w szkołach wyższych wraz z pełnym kodem źródłowym napisanym w języku C. Dlatego język ten dość szybko stał się bardzo popularny. Ponieważ, korzystało z niego wiele osób, więc stworzono amerykański standard tego języka - ANSI C. Był on znacznie rozszerzony w stosunku do wersji Kernighan'a i Ritchie'go. W latach osiemdziesiątych powstały kolejne roszerzenia języka C - umożliwiające programowanie obiektowe. Ich autorem był Bjarne Stroustrup. Swój język nazwał C++ - wskazując, że jest to lepsze C. Również ta wersja doczekała się w krótkim czasie oficjalnego standardu (ANSI).
Wiadomości ogólne
Program w języku C składa się z wielu oddzielnie kompilowanych modułów źródłowych. Każdy z plików źródłowych jest kompilowany do pliku zawierającego kod pośredni. Następnie wszystkie te pliki są łączone w program wykonywalny. Łączenia dokonuje program łączący tzw. linker.
Błąd! Nie zdefiniowano zakładki.
W większości wersji systemu operacyjnego UNIX standardowo dostępny jest kompilator języka C. Jeśli kompilatora nie ma w systemie, wówczas można go dokupić za dodatkową opłatą. W systemie UNIX do kompilacji programów napisanych w języku C służy program o nazwie cc, który oprócz kompilacji wykonuje również łączenie programu z bibliotekami.
Program łączący (linker) ma nazwę ld, może być wywołany bezpośrednio przez użytkownika lub przez program kompilatora cc. Program cc w rzeczywistości jest jedynie programem głównym, który w celu wykonania swojego zadania uruchamia inne programy systemowe:
Poszczególne bloczki odpowiadają kolejnym etapom przetwarzania programu wejściowego. Poczynając od góry:
cpp - program preprocesora. Wykonuje znajdujące się w tekście programu dyrektywy preprocesora (#include, #define, itp.);
cc1 - pierwszy etap kompilacji programu. Po jego zakończeniu otrzymujemy tzw. kod pośredni kompilatora języka C. Postać tego kodu jest identyczna we wszystkich systemach UNIX;
cc2 - zamiana kodu pośredniego na tekst programu w asemblerze;
as - tłumaczenie programu w asemblerze na kod maszynowy;
ld - łączenie otrzymanego kodu maszynowego z funkcjami bibliotecznymi. Otrzymujemy wykonywalny program w kodzie maszynowym.
Zastosowanie dwuprzebiegowej kompilacji pozwala na stworzenie dodatkowej płaszczyzny przenośności. Ponieważ format kodu pośredniego jest ściśle zdefiniowany i identyczny w różnych wersjach systemu UNIX, kompilatory języków innych niż C (np. Fortran, Pascal) nie muszą generować zależnego od procesora kodu maszynowego, lecz jedynie kod pośredni, który następnie jest tłumaczony na kod maszynowy. Oznacza to, że np. kompilator fortranu napisany dla systemu AIX v3.2 działąjącego na procesorze RISC 6000 można przez prostą rekompilację przenieść na SCO UNIX, który działa na procesorze Intel 386/486.
Składnia wywołania kompilatora języka C
Przykłady:
1. Podstawowy schemat kompilacji:
cc first.c
Powoduje kompilację i linkowanie z bibliotekami standardowymi pliku o nazwie first.c. W wyniku otrzymujemy program wykonywalny o nazwie a.out.
2. Kompilacja i łączenie do pliku o podanej nazwie:
cc -o first first.c
Powoduje skompilowanie pliku first.c i utworzenie programu wykonywalnego o nazwie first.
3. Kompilacja do kodu pośredniego pojedynczego pliku (bez wykonywania łączenia):
cc -c first.c
W wyniku wykonania tej instrukcji powstanie plik o nazwie first.o, zawierający kod pośredni skompilowanego programu first.c
4. Kompilacja i łączenie programu składającego się z kilku modułów:
cc -o m m1.c m2.c m3.c
Pliki z kodem źródłowym o nazwach m1.c m2.c m3.c zostaną skompilowane, a następnie połączone w jeden program wykonywalny o nazwie m.
5. Łączenie modułów zawierających kod pośredni:
cc -o m m1.o m2.o m3.o
W wyniku połączenia modułów m1.o m2.o m3.o powstanie program wykonywalny o nazwie m.
Pierwszy program
Podany program wypisuje na ekranie tekst Hello world!"
#include <stdio.h>main(){printf(Hello world!\n");}
Program w języku C składa się z funkcji. Funkcja jest wydzieloną częścią programu, realizującą pewne zadanie. Kompletny program musi zawierać funkcję o nazwie main" - od niej rozpoczyna się wykonanie programu. Funkcja main" może być umieszczona w dowolnym miejscu. Do programu można dołączać pliki zawierające nagłówki (opis) funkcji zdefiniowanych w innych plikach lub funkcji systemowych. Dokonuje się tego za pomocą dyrektywy #include <nazwa_pliku>". Plik "stdio.h" zawiera nagłówki standardowych funkcji Wejścia/Wyjścia. Jedną z nich jest funkcja printf" służąca do wypisywania wartości różnych typów na ekranie. Każda instrukcja w języku C musi być zakończona średnikiem ';'. Instrukcje składające się na kod funkcji umieszcza się w nawiasach klamrowych '{', '}'.
Uwaga: duże i małe litery w języku C są rozróżniane!
Język C - opis
Identyfikatory
Identyfikator (nazwa) służy do nazywania obiektów wchodzących w skład programu napisanego w języku C (zmiennych, typów, funkcji itp).
Przykładowe identyfikatory:
i, liczba, j1, J1, data_urodzenia, _koniec
Przykłady niepoprawnych identyfikatorów:
2rok, 1_kwietnia, ab$, czary!mar, a-b
Nie należy używać identyfikatorów mających dwa znaki podkreślenie obok siebie (są one poprawne z punktu widzenia składni języka C), ponieważ mogą być one używane przez twórców kompilatora do tworzenia bibliotek, makr itp.
Słowa kluczowe
Niektóre identyfikatory zostały zastrzeżone przez twórców języka. Służą one do zapisu konstrukcji jakie są dopuszczalne w języku C. Dlatego nazywa się je słowami kluczowymi. Słowa kluczowe nie mogą być użyte jako nazwy zmiennych, typów lub funkcji i nie są poprawnymi identyfikatorami w sensie składni języka C. W języku ANSI C występują następujące słowa kluczowe:
auto
break
case
char
const
continue
default
do
double
else
enum
extern
float
for
goto
if
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
Zmienne
Zmienną określany jest pewien obszar w pamięci komputera, w którym mogą być przechowywane dane. Z punktu widzenia osoby piszącej program, zmienna posiada następujące cechy podstawowe:
· nazwa (identyfikator)
· typ
· wartość
Nazwa zmiennej pozwala wskazać w programie, o który fragment pamięci nam chodzi. Łatwiej jest posługiwać się nazwą niż adresem liczbowym (łatwiej zrozumieć napis printf(imię); niż np. printf(*0x12342);) Kompilator dokonując tłumaczenia napisanego programu zamienia wszystkie nazwy zmiennych na odpowiednie adresy w pamięci komputera. Wszystkie nazwy zmiennych przed użyciem muszą być zadeklarowane.
Wartość zmiennej jest tym, co przechowujemy w obszarze pamięci określanym przez nazwę. Wartość może się zmieniać w dowolnym momencie w czasie wykonania programu. Wartością może być liczba całkowita, zmiennoprzecinkowa (ułamek dziesiętny), adres w pamięci komputera (tzw. wskaźnik), tekst itp. W momencie deklaracji wartość zmiennej lokalnej (zadeklarowanej wewnątrz funkcji) jest nieokreślona tzn. jej wartość jest przypadkowa; zmienne globalne (deklarowane poza funkcjami) są inicjowane na zero.
Typ zmiennej określa jaką wartość można wpisać do obszaru wskazywanego przez nazwę (czy będzie to liczba całkowita, zmienno-przecinkowa ... , czy też inny rodzaj danej). W zależności od rodzaju wartości (typu zmiennej), inny będzie rozmiar pamięci potrzebny do jej zapamiętania. Kompilator na podstawie typu określa jaką ilość pamięci należy przydzielić zmiennej i jakie operacje są na niej dopuszczalne.
Typy danych
Typy proste
· char - typ znakowy. Można za jego pomocą przechowywać znaki w kodzie ASCII (American Standard Code for Information Interchange) lub innym stosowanym na danej maszynie. Bezpiecznie można więc przechowywać liczby z zakresu 0 .. 127. Na ogół typ char ma 1 bajt długości w związku z czym można za jego pomocą przechowywać liczby z zakresu -128 .. 127 (jeśli jest ze znakiem) lub 0 .. 255 (jeśli jest bez znaku).
· int - typ całkowity. Zmienne tego typu typu mogą przyjmować wartości całkowite dodatnie lub ujemne.
· short int - typ całkowity krótki
· long int - typ całkowity długi
· float - typ zmiennoprzecinkowy pojedynczej precyzji.
· double - typ zmiennoprzecinkowy podwójnej precyzji.
· long double - typ zmiennoprzecinkowy podwójnej precyzji długi.
· void - typ pusty oznaczający brak wartości (stosowany w ANSI C). —adna zmienna nie może być typu void. Tylko parametry przekazywane do funkcji mogą być typu void (oznacza wtedy, że do funkcji nic się nie przekazuje) lub zwracane przez funkcję (funkcja nic nie zwraca). Oprócz tego typ void może być stosowany przy tworzeniu pewnych typów złożonych.
Dla każdego z typów całkowitych: int, short int, long int oraz char możliwe są następujące modyfikatory:
unsigned - typ bez znaku (tylko wartości dodatnie)
W ANSI C możliwy jest również modyfikator signed oznaczający typ ze znakiem.
Przyk³ady:
int a; unsigned long int b; float c; long double xxx; char znak;
Uwagi:
· Jeśli w pewnym miejscu w programie powinna wystąpić nazwa typu, a nie jest ona wpisana, to kompilator domyślnie przyjmuje typ int.
· Podanie nazwy typu numerycznego bez modyfikatorów jest równoznaczne z przyjęciem, że jest to typ ze znakiem (z wyjątkiem typu char - zmienne tego typu mogą być pamiętane ze znakiem lub bez w zależności od kompilatora).
· Do określania wielkości pamięci potrzebnej do zapamiętania zmiennej danego typu służy operator sizeof (typ).
· Kompilator zapewnia, że prawdziwe będą następujące zależności:
sizeof(char) sizeof(short) sizeof(int) sizeof(long)
sizeof(float) sizeof(double) sizeof(long double)
sizeof(typ) = sizeof(signed typ) = sizeof(unsigned typ)
Przyk³ad:
#include <stdio.h>
void main(void) { int a,b; int wynik = 0;
printf(Liczba1 = "); scanf(%d", &a); printf("Liczba2 = "); scanf("%d", &b); wynik = a+b; printf(%d + %d = %d\n", a, b, wynik); }
Typy pochodne
Zmienne wskazujące (wskaźniki)
Wskaźniki służą do wskazywania na inne zmienne lub pewien obszar w pamięci komputera:
Wskaźniki deklaruje się pisząc przed nazwą zmiennej znak '*', np:
int *p;
Podany zapis określa typ zmiennej na jaki może wskazywać wskaźnik (w tym wypadku wskaźnik p będzie mógł wskazywać na zmienną typu int). Typ zmiennej, na jaką może wskazywać wskaźnik, jest wykorzystywany przez kompilator podczas tłumaczenia niektórych operacji. Jeśli chcemy, by wskaźnik wskazywał na obszar pamięci nieokreślonego typu, musimy zadeklarować go jako wskaźnik na void, czyli:
void *mem;
W programie nazwa zmiennej zadeklarowanej jako wskaźnik, określa ten wskaźnik. Nazwa poprzedzona gwiazdką określa zmienną wskazywaną przez wskaźnik:
*p = 5;
Należy pamiętać, że wskaźniki w momencie deklaracji mają wartość nieokreśloną lub równą 0. Aby wskaźnik wskazywał na pewną zmienną należy nadać mu odpowiednią wartość. Jednym ze sposobów jest użycie operatora nadania adresu (&):
int a; int *p;a=5; p = &a; *p = 10; printf(Liczba: %d\n", a);
Tablice
Tablica jest zbiorem elementów tego samego typu. Każdy element tablicy ma numer. Numer pierwszego elementu w tablicy jest zawsze równy zero. W języku C nie można deklarować tablic wielowymiarowych, jest jednak możliwa deklaracja tablic zawierających tablice, co odpowiada tablicom wielowymiarowym w innych językach. Deklaracja tablicy ma postać:
Przykład:
int arr[10];
Każdy element deklarowanej tablicy będzie typu typ_elementu, pierwszy element będzie miał numer 0, drugi - 1, ... , ostatni - rozmiar-1. Tablicę można inicjować podając w deklaracji po jej nazwie i znaku równości listę wartości oddzielonych przecinkami i zamkniętych w nawiasach klamrowych. Jeśli tablica jest inicjowana w deklaracji, to nie jest konieczne podawanie jej rozmiaru. Możliwość ta jest dostępna we wszystkich kompilatorach ANSI C.
int a1[5] = {1,5,3,4,2}; int a2[] = {1,5,6,3,4,5,6};
Tablic używa się w programie podając nazwę zmiennej tablicowej oraz numer elementu, którego operacja ma dotyczyć ujęty w nawiasy kwadratowe. Jako numer elementu może służyć stała całkowita, zmienna typu całkowitego lub dowolne wyrażenie, którego wynikiem jest liczba całkowita. Nawiasy kwadratowe zawierające numer elementu tablicy nazywane są operatorem indeksowania.
int a[10]; int i;i = 5; a[5] = 10; a[a[5] - 5] = 4;
Możliwe jest zadeklarowanie tablicy tablic (odpowiadającej tablicy dwu- lub więcej wymiarowej):
int a[10][15];
Powyższa instrukcja deklaruje 10-cio elementową tablicę a, której polami są 15-sto elementowe tablice zmiennych typu int. Odwołanie do elementów tablicy następuje w sposób naturalny - najpierw podaje się numer tablicy, potem numer elementu wewnątrz tej tablicy:
a[4][5] = 10;
Niepoprawne jest: a[10][9] = 6;
Struktury
Struktura jest zbiorem elementów różnych typów. Każdy element struktury nazywany jest polem. Definicja struktury ma następującą postać:
Składnia definicji pola jest taka sama jak składnia definicji pojedynczej zmiennej. Nazwa pola (odpowiadająca nazwie zmiennej) jest nazwą lokalną widoczną tylko wewnątrz struktury. Struktura może posiadać nazwę. Można wtedy deklarować zmienne, będące strukturami opisanymi w definicji. Bezpośrednio po definicji struktury można podać nazwy zmiennych, które będą tymi strukturami. Dlatego możliwe jest również definiowanie struktur bez nazwy - definiuje się wtedy od razu odpowiednie zmienne.
struct osoba { char nazwisko[25]; char imie[10]; int wiek; } klient;
W pewnych sytuacjach może istnieć potrzeba poinformowania kompilatora, że dana struktura zostanie zdefiniowana później. Możliwa jest wtedy predefinicja w postaci:
Struktura taka nie musi być zdefiniowana, aż do momentu, w którym kompilator nie będzie musiał obliczyć jej rozmiaru, tzn. do momentu deklaracji pola lub zmiennej tego typu, wywołania operatora sizeof, itp.
Mając zdefiniowaną strukturę o określonej nazwie, można używać jej do definicji zmiennych lub pól innej struktury tak jak nowego typu:
Po zadeklarowaniu zmiennych strukturowych można odwoływać się do nich jako całości lub do poszczególnych pól. W szczególności można przypisywać jedną zmienną strukturową drugiej (tego samego typu) za pomocą pojedynczego operatora przypisania. Odwołanie do pola struktury jest możliwe przy użyciu operatora '.'. Z lewej strony podaje się nazwę zmiennej strukturowej, z prawej nazwę pola:
struct osoba { char nazwisko[25]; char imie[10]; int wiek; };
void main(void) { struct osoba klient;
printf(Nazwisko: "); scanf(%s", klient.nazwisko); printf(Imie: "); BRP> scanf(%s", klient.imie); BRP> printf(Wiek: "); BRP> scanf(%d", &klient.wiek);printf(Klient: %s %s; %d lat\n", klient.imie, klient.nazwisko, klient.wiek); }
Struktury podobnie jak tablice można inicjować w deklaracji podając wartości kolejnych pól na liście zamkniętej w nawiasy klamrowe. Można również inicjować tablice struktur (we wszystkich kompilatorach możliwości inicjalizacji w deklaracji są dostępne od ANSI C):
struct complex {double re, double im}; struct complex l1 = {12.1, 45.7};...
marcik13