Interfejsy programowe w C
Manna5
Załóżmy, że piszemy procedurę sumującą ciąg liczb. Jednak ten ciąg może być podawany na bieżąco z klawiatury, może być zgromadzony w tablicy lub innej strukturze danych, albo też być generowany przez jakiś algorytm. Jak poradzić sobie z takim problemem i napisać jedną, wszechstronną procedurę sumującą? Gdybyśmy programowali w Javie, oczywistą odpowiedzią byłby interfejsy - każde źródło danych byłoby klasą implementującą jeden interfejs z którego korzystalibyśmy podczas sumowania. Język C nie posiada klas ani interfejsów, jednak okazuje się, że zbudowanie takiego mechanizmu wcale nie jest trudne.
Tak naprawdę podczas przetwarzania ciągu liczb wykonujemy trzy operacje: rozpoczęcie przetwarzania, np. poprzez wyzerowanie licznika; odczyt kolejnej danej i sprawdzenie, czy jest jeszcze więcej danych. Zatem możemy stworzyć strukturę zawierającą wskaźniki do procedur odpowiadających tym operacjom.
struct Vector
{
void (*Start) (void*); /* Rozpoczęcie przetwarzania ciągu. */
char (*HasMore) (void*); /* Sprawdzenie, czy to nie koniec. */
int (*Next) (void*); /* Uzyskanie kolejnego elementu. */
};
Warto zauważyć również, że wszystkie te procedury przyjmują jako argument jeden wskaźnik ogólny, typu void
. Ma to takie zastosowanie, że czasami jeden "sterownik" dla danego źródła danych może operować na różnych "egzemplerzach" - choćby w przypadku zwykłej tablicy musi być wiadomo, o jaką tablicę chodzi. Z kolei w przypadku generowania ciągu liczb, takiego jak ciąg Fibonacciego, danymi egzemplarza będą wszelkie dane pomocnicze takie jak ostatnio wygenerowane wyrazy ciągu. Te informacje będziemy mogli zapisać w strukturze i przesyłać wskaźnik do niej. Jeżeli nie potrzebujemy tego wskaźnika, możemy po prostu przekazać NULL.
Procedura sumująca oparta o interfejs wektora liczb koncepcyjnie nie różni się od zwykłego sumowania tablicy, ale zamiast działań na liczniku elementów wywoływane są funkcje odpowiedniego interfejsu. Tę architekturę można rozszerzać "w obie strony" - zarówno dodawać nowe strktury danych do sumowania, jak i wykonywać inne operacje na wektorach liczb, np. wyznaczanie największej wartości.
void SumVec (struct Vector *Vec, void *Inst)
{
int Total;
Vec->Start (Inst);
Total = 0;
while (Vec->HasMore (Inst))
Total += Vec->Next (Inst);
return Total;
}
Ostatnim krokiem będzie napisanie funkcji "opakowujących" nasze źródłó danych i zainicjalizowanie struktur-interfejsów wskaźnikami do nich. Poniżej znajduje się pełny kod programu sumującego zarówno tablicę (wypełnioną liczbami losowymi) jak i serię liczb podaną przez użytkownika.
#include <stdio.h>
#include <stdlib.h>
/* INTERFEJS WEKTORA ------------------------------------------------------- */
struct Vector
{
void (*Start) (void*);
char (*HasMore) (void*);
int (*Next) (void*);
};
void SumVec (struct Vector *Vec, void *Inst)
{
int Total;
Vec->Start (Inst);
Total = 0;
while (Vec->HasMore (Inst))
Total += Vec->Next (Inst);
return Total;
}
/* TABLICA JAKO WEKTOR ----------------------------------------------------- */
struct ArvInstance
{
int *Array, *Begin;
unsigned int RemCount, Count;
};
#define InstA ((struct ArvInstance*) Inst)
void ArrStart (void *Inst)
{
InstA->Array = InstA->Begin;
InstA->RemCount = InstA->Count;
}
char ArrHasMore (void *Inst)
{
return InstA->RemCount > 0;
}
int ArrNext (void *Inst)
{
--(InstA->RemCount);
return *(InstA->Array)++;
}
#undef InstA
const struct Vector ArrayVec = {ArrStart, ArrHasMore, ArrNext};
/* CIĄG WPROWADZANY NA BIEŻĄCO JAKO WEKTOR --------------------------------- */
struct InseqInst
{
int GotValue;
char WasRead;
};
#define InstS ((struct InseqInst*) Inst)
void IsqStart (void *Inst)
{
InstS->WasRead = 0;
puts ("Enter a sequence of numbers, terminated with 0.");
}
void IsqEnsure (void *Inst)
{
int Tmp;
if (! (InstS->WasRead)) {
printf ("? ");
scanf ("%d", &Tmp);
InstS->WasRead = 1;
InstS->GotValue = Tmp;
}
}
char IsqHasMore (void *Inst)
{
IsqEnsure (Inst);
return InstS->GotValue != 0;
}
int IsqNext (void *Inst)
{
IsqEnsure (Inst);
InstS->WasRead = 0;
return InstS->GotValue;
}
#undef InstS
const struct Vector InseqVec = {IsqStart, IsqHasMore, IsqNext};
/* TEST -------------------------------------------------------------------- */
void FillArr (int *Arr, unsigned int Len)
{
while (Len--)
*Arr++ = rand () % 2001 - 1000;
}
#define ARRSIZE 50
void TestArr ()
{
struct ArvInstance ArrI;
int Array[ARRSIZE];
FillArr (Array, ARRSIZE);
ArrI.Begin = Array;
ArrI.Count = ARRSIZE;
printf ("Sum of array: %d.\n", SumVec (&ArrayVec, &ArrI));
}
void TestInseq ()
{
struct InseqInst IsqI;
int Sum;
Sum = SumVec (&InseqVec, &IsqI);
printf ("Sum of entered numbers: %d.\n", Sum);
}
int main ()
{
TestArr ();
TestInseq ();
return 0;
}