© 2012
Grzegorz Łukawski & Maciej Lasota
Politechnika Świętokrzyska w Kielcach
Standardowe wejście i wyjście,
preprocesor, stałe,
makrodefinicje, make.
Programowanie
w języku C
comm
int
znak = getc(stdin);
fprintf(stdout,
"Tekst"
);
fgets(str, 32, stdin);
Standardowe wejście i wyjście
stdin
Standardowe wejście, domyślnie klawiatura.
stdout
Standardowe wyjście, domyślnie ekran (konsola).
stderr
Standardowe wyjście błędów, ekran (konsola).
Standardowe wejście i wyjście każdego programu można zastąpić
dowolnym plikiem:
./program <we.txt
./program >wy.txt
./program <we.txt >wy.txt
./program >>wyniki.txt
Standardowe wejście i wyjście – potoki
Wejścia i wyjścia różnych programów można ze sobą łączyć z użyciem
tzw. potoków:
ls | ./program
./program | more
ls -l | wc | cat | more
Polecenia systemowe i programy
system(
"polecenie i argumenty"
);
system(
"ls -l >>spis.txt"
);
system(
"inny_program"
);
system(
"C:\\programy\\wget.exe"
);
Preprocesor to część kompilatora języka C zajmująca się
wstępnym przetwarzaniem kodu źródłowego.
Działaniem preprocesora steruje się z pomocą dyrektyw
preprocesora (słowa kluczowe zaczynające się od znaku #).
Preprocesor
Stałe symboliczne
Przyjęło się, że nazwy stałych symbolicznych zawsze są pisane wielkimi
literami.
#define
ILOSC
5
#define
NAZWA
”Moj program wersja 1”
#define
PI
3.1415
#define
PI2
2*PI
#define
ILE
32
// ...
int
tablica[ ILE ];
memset(tablica, 0, ILE *
sizeof
(
int
));
puts( NAZWA );
W porównaniu do stałych symbolicznych mają dwie zalety:
1) można utworzyć do nich wskaźnik,
2) ich stosowanie nie powoduje zwielokrotnienia danych w kodzie
wynikowym.
Zmienne o ustalonej wartości
Są to zmienne, których wartości nie wolno zmienić – jeśli system
operacyjny na to pozwala, mogą znaleźć się w obszarze pamięci tylko do
odczytu.
const
int
Ilosc = 5;
const
char
*Nazwa = ”Moj program wersja 2”;
Definicja typów z pomocą dyrektywy #define
#define
ULONG
unsigned
long
int
#define
BYTE
char
ULONG licznik = 100;
BYTE dane[16];
Kompilacja warunkowa (1)
#define
DEBUGOWANIE 1
// ...
#if
DEBUGOWANIE==1
// Czy wartość 1?
printf(
"** n=%d, x=%d, suma=%f\n"
,n,x,suma);
if
(wskaznik==NULL)
printf(
"** Brak wskaźnika!\n"
);
#endif
// Koniec bloku
#define
WINDOWZ 1
// ...
#if
WINDOWZ==1
char
*sciezka =
"C:\\dane\\plik.txt"
;
#else
char
*sciezka =
"~/dane/plik.txt"
;
#endif
Kompilacja warunkowa (2)
#define
LICZ 10000
#if
LICZ<256
unsigned
char
licznik = LICZ;
#elif
LICZ<65536
unsigned
short
licznik = LICZ;
#else
unsigned
long
licznik = LICZ;
#endif
Kompilacja warunkowa (3)
Typ wyliczeniowy
Typ wyliczeniowy (enum) służy do tworzenia zmiennych (typów)
zawierających tylko wybrane z góry ustalone wartości.
enum
Nazwa {W1, W2, WN};
enum
Kierunek {GORA, DOL, LEWO, PRAWO};
enum
Kierunek kierunek = GORA;
void
przesun(
enum
Kierunek kier,
int
x,
int
y) {
// ...
}
W rzeczywistości wartości typu wyliczeniowego przechowywane są jako
liczby typu int.
Typ wyliczeniowy
Domyślnie przypisywane są kolejne wartości począwszy od zera, można to
zmienić w momencie deklaracji:
enum
Kierunek {GORA=10, DOL, LEWO, PRAWO};
printf(
"%d\n"
, GORA);
// 10
printf(
"%d\n"
, DOL);
// 11
enum
Kierunek {GORA=10, DOL=10, LEWO=5, PRAWO=3};
enum
Kierunek kierunek = GORA;
// Przypisanie nieistniejącej stałej!
kierunek = (
enum
Kierunek)7;
Typ wyliczeniowy
Makra tym różnią się od funkcji, że w miejscach wywołania makrodefinicji
wstawiany jest kod do niej przypisany (realizuje to preprocesor)*.
* Podobnie do makr zachowują się funkcje poprzedzone słowem „inline” (C99, C++).
Makrodefinicje
Makrodefinicje to sekwencje instrukcji deklarowane z pomocą operatora
#define. Mogą „udawać” funkcje wyposażone w opcjonalne argumenty
(bez typów danych):
#define
nazwa
instrukcje
#define
nazwa
(parametry)
instrukcje
Makrodefinicje – przykłady
#define
DrukLinie
printf(
"-------------\n"
)
#define
DrukLiczbe(n)
printf(
"Liczba = %ld\n"
, n)
#define
Zeruj(x)
memset(&x, 0,
sizeof
(x))
// Makro złożone z wielu wierszy:
#define
DrukZnaki(z, n)
for
(i=0;i < n;i++) {\
if
(i) putchar(
','
);\
putchar(z);\
}
#define
SwapInts(x,y)
{
int
z; z=y; y=x; x=z;}
Makrodefinicje – zastosowania
Makrodefinicje mogą zmieniać wartości zmiennych podanych jako
argumenty bez użycia wskaźnika, np.:
int
srednia(
int
x1,
int
x2,
int
x3,
int
x4) {...}
#define
srednia2(a, b)
srednia(a, a, b, b)
#define
getchar()
getc(stdin)
Pozwalają tworzyć nowe wersje już istniejących funkcji, np.:
Dyrektywa #undef
Dyrektywa #undef unieważnia definicję stałej lub makrodefinicji – od
momentu jej wystąpienia w pliku źródłowym dana stała/makro przestaje
istnieć:
#undef
identyfikator
W przypadku dużych programów kod źródłowy zaleca się podzielić na kilka
oddzielnych plików ".c".
Dla każdego pliku źródłowego można utworzyć tzw. plik nagłówkowy ".h",
w którym znajdą się definicje typów, stałych oraz prototypy funkcji
i danych udostępnianych do innych „modułów”.
Podział programu na moduły
UWAGA!
Tylko w jednym pliku źródłowym programu może znaleźć się jedna
funkcja main (więcej jeżeli występuje w bloku kompilacji warunkowej)!
// main.c
#include
<...>
#include
"main.h"
#include
"wewy.h"
#include
"baza.h"
void
zeruj(
struct
towar *t) {
memset(t, 0,
sizeof
(
struct
towar));
}
int
main() {
struct
towar tow;
int
numer;
// Losowanie numeru towaru:
srand(time(NULL));
numer = rand() % ILET;
// Wypełnienie struktury:
zeruj(&tow);
strcpy(tow.nazwa, baza_towary[ numer ]);
tow.cena = baza_ceny[ numer ];
// Wyświetlenie i koniec:
wyswietl_dane(&tow);
return
0;
}
// main.h
#define
ILEZ 32
struct
towar {
char
nazwa[ILEZ];
float
cena;
};
void
zeruj(
struct
towar *t);
// wewy.c
#include
<stdlib.h>
#include
<stdio.h>
#include
"main.h"
#include
"wewy.h"
void
wczytaj_tekst(
char
*t) {
fgets(t, ILEZ-1, stdin);
}
void
wyswietl_tekst(
char
*t) {
puts(t);
}
void
wczytaj_dane(
struct
towar *t) {
wczytaj_tekst(t->nazwa);
scanf(
"%f"
, &t->cena);
}
void
wyswietl_dane(
struct
towar *t) {
wyswietl_tekst(t->nazwa);
printf(
"CENA = %.2f\n"
, t->cena);
}
// wewy.h
void
wczytaj_dane(
struct
towar *t);
void
wyswietl_dane(
struct
towar *t);
// baza.c
#include
"baza.h"
char
*baza_towary[ILET] = {
"Monitor Yumo 19"
,
"Monitor Yumo 22"
,
"Plyta DVD-R Cedek"
,
"Plyta CD-R Cedek"
,
"Klawiatura Baton-Ergo"
,
"Klawiatura Baton-Economy"
,
"Myszka Gryzon-Ergo"
,
"Dysk HDD Traktor 2TB"
,
"Plyta glowna Zimny-Lut Turbo"
,
"Karta graf. Pixeloza Turbo 1GB"
,
"Karta graf. Pixeloza Silent 2GB"
,
"Pamiec DDR3 Skleroza 2x1GB"
,
"Pamiec DDR3 Skleroza 4GB"
,
"Pendrive Forget-it 16GB"
,
"Obudowa Blaszak Tunafisza BLUE"
,
"Zasilacz Wyjec 300W"
};
float
baza_ceny[ILET] = {
199.99, 299.99, 2, 1, 99.99, 49.99, 19.99, 1000,
301, 999.99, 199.99, 99.99, 199.99, 19.99, 100, 50
};
// baza.h
#define
ILET 16
extern
float
baza_ceny[ILET];
extern
char
*baza_towary[ILET];
W plikach nagłówkowych (*.h) można umieszczać tylko i wyłącznie treść nie
generującą żadnego kodu ani zmiennych (prowadzi to do błędów
kompilacji). Dozwolone są między innymi:
●
definicje typów strukturalnych (struct);
●
definicje nowych typów danych (typedef);
●
stałe symboliczne (#define);
●
definicje zmiennych które mają być widoczne na zewnątrz
poprzedzone słowem extern;
●
prototypy funkcji udostępnionych na zewnątrz;
●
makrodefinicje;
●
funkcje typu inline (C99, C++).
Zawartość plików nagłówkowych
Pliki nagłówkowe i kompilacja warunkowa
#ifdef STALA
Jeżeli STALA istnieje, wiersze poniżej aż do dyrektywy
#endif (lub podobnej) zostaną włączone do programu.
#ifndef STALA Jeżeli STALA nie istnieje, wiersze poniżej aż do
dyrektywy #endif (lub podobnej) zostaną włączone do
programu.
// baza.h
#ifndef
MOJA_XYZ_BAZA_H
#define
MOJA_XYZ_BAZA_H
#define
ILET
16
extern
float
baza_ceny[ILET];
extern
char
*baza_towary[ILET];
#endif
// MOJA_XYZ_BAZA_H
Pliki nagłówkowe i kompilacja warunkowa
gcc -c
main.c
(Powstanie plik main.o)
gcc -c
wewy.c
(Powstanie plik wewy.o)
gcc -c
baza.c
(Powstanie plik baza.o)
gcc
main.o wewy.o baza.o
-o
programik
Kompilacja programu złożonego z wielu plików źródłowych
Kompilacja programu złożonego z wielu plików źródłowych
main.c
wewy.c
baza.c
main.o
wewy.o
baza.o
programik
stdio...
1) kompilacja x3
2) łączenie (konsolidacja, linkowanie)
make
Program „make” to narzędzie wspomagające kompilację i linkowanie
złożonych
programów
napisanych
w
różnych
językach
programowania.
Narzędzie automatycznie (na podstawie czasów modyfikacji plików)
wykonuje tylko niezbędne kroki kompilacji i linkowania prowadzące
do utworzenia nowej wersji pliku wykonywalnego*.
*Sposób kompilacji i budowania programów w kompilatorach wyposażonych w interfejs IDE
(np. Visual Studio, Code::Blocks) wzorowany jest na działaniu narzędzia „make”.
Makefile
Sposób postępowania prowadzący do skompilowania programu
powinien być zapisany pliku tekstowym o nazwie „makefile” lub
„Makefile”, umieszczonym w katalogu razem z plikami źródłowymi*.
Skompilowanie programu, w najprostszym przypadku, polega na
wywołaniu programu make z wiersza poleceń, bez parametrów.
* Plik dla programu make może mieć dowolną nazwę, ale wtedy trzeba ją przekazać
z pomocą dodatkowej opcji „-f”.
cmodular.exe
: main.o wewy.o baza.o
gcc main.o wewy.o baza.o -o cmodular
main.o
: main.c main.h wewy.h baza.h
gcc -c main.c
wewy.o
: wewy.c wewy.h main.h
gcc -c wewy.c
baza.o
: baza.c baza.h
gcc -c baza.c
Makefile (Windows)
UWAGA!
Przed poleceniem do wykonania (drugi wiersz każdej reguły) musi
znaleźć się znak tabulacji (kod ASCII = 8).
Automatyczne tworzenie reguł do pliku makefile
gcc -MM main.c
main.o: main.c main.h wewy.h baza.h
gcc -MM wewy.c
wewy.o: wewy.c main.h wewy.h
gcc -MM baza.c
baza.o: baza.c baza.h