Programowanie
w języku C
© 2012
Grzegorz Łukawski & Maciej Lasota
Politechnika Świętokrzyska w Kielcach
Standardowe wejście i wyjście,
preprocesor, stałe,
makrodefinicje, make.
comm
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).
int znak = getc(stdin);
fprintf(stdout, "Tekst");
fgets(str, 32, stdin);
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 #).
#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 );
Przyjęło się, że nazwy stałych symbolicznych zawsze są pisane wielkimi literami.
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”;
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.
Definicja typów z pomocą dyrektywy #define
#define ULONG unsigned long int
#define BYTE char
ULONG licznik = 100;
BYTE dane[16];
#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
#define LICZ 10000
#if LICZ<256
unsigned char licznik = LICZ;
#elif LICZ<65536
unsigned short licznik = LICZ;
#else
unsigned long licznik = LICZ;
#endif
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.
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;
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
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 po
przedzone słowem „inline” (C99, C++).
#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);\
}
Makrodefinicje – zastosowania Makrodefinicje mogą zmieniać wartości zmiennych podanych jako argumenty bez użycia wskaźnika, np.:
#define SwapInts(x,y)
{int z; z=y; y=x; x=z;}
Pozwalają tworzyć nowe wersje już istniejących funkcji, np.: int srednia(int x1, int x2, int x3, int x4) {...}
#define srednia2(a, b)
srednia(a, a, b, b)
#define getchar()
getc(stdin)
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”.
UWAGA!
Tylko w jednym pliku źródłowym programu może znaleźć się jedna funkcja main (więcej jeżeli występu je w bloku kompilacji warunkowej)!
// main.h
#include <...>
#define ILEZ 32
#include "main.h"
struct towar {
#include "wewy.h"
char nazwa[ILEZ];
#include "baza.h"
float cena;
};
void zeruj(struct towar *t) {
memset(t, 0, sizeof(struct towar));
void zeruj(struct towar *t);
}
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;
}
void wczytaj_dane(struct towar *t);
void wyswietl_dane(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)
;
}
// baza.c
#define ILET 16
#include "baza.h"
extern float baza_ceny[ILET];
char *baza_towary[ILET] = {
extern 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
};
Zawartość plików nagłówkowych 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++).
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.
Pliki nagłówkowe i kompilacja warunkowa
// 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
Kompilacja programu złożonego z wielu plików źródłowych 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 main.c
wewy.c
baza.c
1) kompilacja x3
main.o
wewy.o
baza.o
stdio...
2) łączenie (konsolidacja, linkowanie)
programik
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”.
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
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