Przejdź do treści

Projekty

Przy tworzeniu rozbudowanych aplikacji pojawia się potrzeba podziału projektu na osobne pliki, tak aby zachować porządek i czytelność. Standardowy podział polega na zgrupowaniu funkcjonalności i wydzieleniu ich w postaci osobnych plików. W ten sposób tworzymy niejako własne biblioteki, czy też zależności projektu. W wyniku takiego podziału otrzymujemy dwa pliki dla każdej zależności:

  • plik nagłówkowy z rozszerzeniem .h,
  • plik z implementacją z rozszerzeniem .c.

W pliku nagłówkowym zawieramy zależności, struktury, a także deklaracje funkcji. W pliku z implementacją natomiast definiujemy funkcje z pliku nagłówkowego, czyli tworzymy ich implementację. Taki podział pozwala między innymi na łatwą podmianę implementacji bez modyfikacji interfejsu biblioteki.

Przyjrzyjmy się dokładniej opisanemu konceptowi na poniższym przykładzie.

Przykład

Jako przykład rozważymy implementację prostej biblioteki pozwalającej na wykonywanie podstawowych operacji na dwuwymiarowych punktach. Doprecyzowując zaimplementujemy jedynie strukturę do reprezentacji punktu o współrzędnych rzeczywistych, a także funkcję obliczającą odległość między dwoma punktami.

point.h - plik nagłówkowy

#ifndef POINT_H
#define POINT_H

#include <math.h>

typedef struct Point {
    float x;
    float y;
} Point;

float distance(const Point *a, const Point *b);

#endif

Zacznijmy od wyjaśnienia makra #ifndef POINT_H, które znajduje się na początku pliku nagłówkowego. Jego znaczenie jest bardzo istotne, gdy pracujemy z wieloplikowymi projektami. Dzięki zastosowaniu takiego makra unikniemy problemów związanych z wielokrotnym dołączaniem zależności.

Poniżej definiujemy zależności. W tym przypadku skorzystamy z biblioteki math.h, ponieważ przyda nam się funkcja obliczająca wartość pierwiastka kwadratowego. Następnie tworzymy naszą strukturę, a także deklarujemy funkcję obliczającą odległość pomiędzy dwoma punktami na płaszczyźnie.

W celu oszczędzania pamięci nasza funkcja przyjmuje wskaźniki na punkty, zamiast ich kopii.

point.c - plik z implementacją

#include "point.h"

float distance(const Point *a, const Point *b) {
    return sqrt((a->x - b->x) * (a->x - b->x) + (a->y - b->y) * (a->y - b->y));
}

Na początku, za pomocą instrukcji include dołączamy plik nagłówkowy point.h.

main.c

#include <stdio.h>
#include "point.h"

int main(void) {
    Point a = {0.0, 0.0};
    Point b = {0.0, 5.0};

    float dist = distance(&a, &b);

    printf("Distance: %.2f\n", dist);

    return 0;
}

Kompilacja

gcc -o point point.c main.c

Makefile

Przydatne makra

  • $@ - lewa strona znaku dwukropka, tzn. nazwa polecenia
  • $^ - prawa strona znaku dwukropka, tzn. zależności polecenia
  • $< - pierwszy element z listy zależności

Przykład

CC=gcc
CFLAGS=-Wall
DEPS = point.h
OBJS = point.o main.o 

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

main: $(OBJS)
    $(CC) -o $@ $^ $(CFLAGS)

main-debug: $(OBJS)
    $(CC) -o $@ -O0 $^ $(CFLAGS)

all: main

clean:
    rm -f $(OBJS) main main-debug