Jak zaimplementować klasę w C?

Zakładając, że muszę używać C (bez kompilatorów C++ lub obiektowych) i nie mam dynamicznej alokacji pamięci, jakie są niektóre techniki, których mogę użyć, aby zaimplementować klasę lub dobre przybliżenie klasy? Czy zawsze dobrym pomysłem jest wyizolowanie "klasy" do osobnego pliku? Załóżmy, że możemy prealokować pamięć, zakładając stałą liczbę instancji lub nawet definiując odniesienie do każdego obiektu jako stałą przed czasem kompilacji. Zapraszam do składania założeń co do koncepcji OOP I będzie musiał wdrożyć (będzie się różnić) i zaproponować najlepszą metodę dla każdego.

Ograniczenia:

  • muszę używać C, a nie OOP ponieważ piszę kod do system wbudowany, A kompilator i wcześniej istniejąca baza kodu znajduje się w C.
  • nie ma dynamicznej alokacji pamięci bo mamy za mało pamięci rozsądnie zakładać, że nie zabraknie nam jeśli zaczniemy dynamicznie przydzielać to.
  • Kompilatory, z którymi pracujemy, nie mają problemów z funkcją pointers
Author: Ben Gartner, 2009-09-10

19 answers

To zależy od dokładnego "zorientowanego obiektowo" zestawu funkcji, który chcesz mieć. Jeśli potrzebujesz rzeczy takich jak przeciążenie i / lub metody wirtualne, prawdopodobnie musisz dołączyć Wskaźniki funkcji w strukturach:

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

To pozwoli Ci zaimplementować klasę, "dziedzicząc" klasę bazową i implementując odpowiednią funkcję:

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

To oczywiście wymaga również implementacji konstruktora, który upewnia się, że wskaźnik funkcji jest prawidłowo ustawiony. Normalnie dynamicznie przydziel pamięć dla instancji, ale możesz pozwolić wywoływaczowi zrobić to również:

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

Jeśli chcesz mieć kilka różnych konstruktorów, musisz "ozdobić" nazwy funkcji, nie możesz mieć więcej niż jednej rectangle_new() funkcji:

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}
Oto podstawowy przykład pokazujący użycie:
int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lengths(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}
Mam nadzieję, że to da ci jakieś pomysły. Aby uzyskać pomyślny i bogaty obiektowy framework w języku C, zajrzyj do biblioteki GObject glib.

Zauważ również, że nie ma wyraźnego "klasa" jest modelowana powyżej, każdy obiekt ma swoje własne wskaźniki metody, które są nieco bardziej elastyczne niż zwykle można znaleźć w C++. Ponadto kosztuje pamięć. Można od tego uciec, umieszczając wskaźniki metody w strukturze class i wymyślając sposób odwoływania się do klasy dla każdej instancji obiektu.

 70
Author: unwind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-10 17:30:20

Ja też musiałem to zrobić, żeby odrobić lekcje. Zastosowałem takie podejście:

  1. Zdefiniuj swoje dane w struct.
  2. Zdefiniuj swoje funkcje, które weź wskaźnik do swojej struktury jako pierwszy argument.
  3. Zrób to w jednym nagłówku i jednym c. Nagłówek dla definicji struktury & deklaracje funkcji, c dla wdrożenia.

Prostym przykładem może być:

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
/// 
 22
Author: erelender,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-16 19:21:50

Jeśli chcesz tylko jedną klasę, użyj tablicy struct s jako danych " obiektów "i przekaż im wskaźniki do funkcji" member". Możesz użyć typedef struct _whatever Whatever przed zadeklarowaniem struct _whatever, aby ukryć implementację przed kodem klienta. Nie ma różnicy między takim "obiektem" a obiektem standardowej biblioteki C FILE.

Jeśli chcesz mieć więcej niż jedną klasę z dziedziczeniem i funkcjami wirtualnymi, to często używa się wskaźników do funkcji jako członków struktury lub współdzielonego wskaźnika do tabeli funkcji wirtualnych. Biblioteka GObject używa zarówno tego, jak i sztuczki typedef i jest szeroko stosowana.

Istnieje również książka na temat technik do tego dostępna online- Programowanie obiektowe z ANSI C .

 11
Author: Pete Kirkham,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 08:04:49

Możesz spojrzeć na GOBject. jest to biblioteka systemu operacyjnego, która daje szczegółowy sposób na wykonanie obiektu.

Http://library.gnome.org/devel/gobject/stable/

 7
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 07:59:06

interfejsy i implementacje C: techniki tworzenia oprogramowania Wielokrotnego Użytku, David R. Hanson

Http://www.informit.com/store/product.aspx?isbn=0201498413

Ta książka doskonale odpowiada na twoje pytanie. Należy do serii Addison Wesley Professional Computing.

Podstawowy paradygmat jest taki:

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);
 7
Author: Mark Harrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-12 23:08:34

Miro Samek opracował obiektowy framework C dla swojej maszyny stanowej: http://sourceforge.net/projects/qpc / . napisał też o tym książkę: http://www.state-machine.com/psicc2/.

 6
Author: Frank Grimm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 11:53:44

Użyj struct do symulacji danych klasy. Pod względem zakresu metod można symulować metody prywatne, umieszczając w nich prototypy funkcji private .pliku c oraz funkcji public {[3] } wplik H.

 4
Author: Taylor Leese,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 07:56:04

Podam prosty przykład, jak OOP powinno się zrobić w C. zdaję sobie sprawę, że thead jest z 2009 roku, ale i tak chciałbym to dodać.

/// Object.h
typedef struct Object {
    uuid_t uuid;
} Object;

int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);

/// Person.h
typedef struct Person {
    Object obj;
    char *name;
} Person;

int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);

/// Object.c
#include "object.h"

int Object_init(Object *self)
{
    self->uuid = uuid_new();

    return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
    return self->uuid;
}
int Object_clean(Object *self)
{
    uuid_free(self->uuid);

    return 0;
}

/// Person.c
#include "person.h"

int Person_init(Person *self, char *name)
{
    Object_init(&self->obj); // Or just Object_init(&self);
    self->name = strdup(name);

    return 0;
}
int Person_greet(Person *self)
{
    printf("Hello, %s", self->name);

    return 0;
}
int Person_clean(Person *self)
{
    free(self->name);
    Object_clean(self);

    return 0;
}

/// main.c
int main(void)
{
    Person p;

    Person_init(&p, "John");
    Person_greet(&p);
    Object_get_uuid(&p); // Inherited function
    Person_clean(&p);

    return 0;
}

Podstawowa koncepcja zakłada umieszczenie 'dziedziczonej klasy' na szczycie struktury. W ten sposób, dostęp do pierwszych 4 bajtów w strukturze uzyskuje również dostęp do pierwszych 4 bajtów w 'dziedziczonej klasie' (jako Nie-szalone optymalizacje). Teraz, gdy wskaźnik struktury jest przypisany do 'dziedziczonej klasy', 'dziedziczona klasa' może uzyskać dostęp do 'dziedziczone wartości' w taki sam sposób, w jaki normalnie uzyskałby dostęp do swoich członków.

To i niektóre konwencje nazewnicze dla konstruktorów, destruktorów, alokacji i funkcji deallocarion (polecam init, clean, new, free) zapewnią ci długą drogę.

Jeśli chodzi o funkcje wirtualne, użyj wskaźników funkcji w strukturze, ewentualnie z Class_func(...); wrapper też. Jeśli chodzi o (proste) szablony, Dodaj parametr size_t, aby określić rozmiar, wymagaj wskaźnika void* lub wymagaj typu "class" z tylko funkcjonalność, na której ci zależy. (np. int GetUUID( Object * self); GetUUID (&p);)

 4
Author: YoYoYonnY,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-21 00:15:18

W Twoim przypadku dobrym przybliżeniem klasy może być an ADT . Ale to nie będzie to samo.

 3
Author: Artem Barger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 07:54:41

Moja strategia to:

  • Zdefiniuj cały kod dla klasy w oddzielnym pliku
  • Zdefiniuj wszystkie interfejsy dla klasy w oddzielnym pliku nagłówkowym
  • wszystkie funkcje Członkowskie przyjmują "ClassHandle", które oznacza nazwę instancji (zamiast o. foo(), wywołaj foo (oHandle)
  • konstruktor jest zastąpiony funkcją Void ClassInit (ClassHandle h, int x, int y,...) Lub ClassHandle ClassInit (int x, int y,...) w zależności od strategii alokacji pamięci
  • Wszystkie zmienne członkowskie są przechowywane jako element statycznej struktury w pliku klasy, zamykając ją w pliku, uniemożliwiając zewnętrznym plikom dostęp do niej
  • obiekty są przechowywane w tablicy statycznej struktury powyżej, z predefiniowanymi uchwytami (widocznymi w interfejsie) lub ustalonym limitem obiektów, które można utworzyć instancję
  • jeśli jest to użyteczne, klasa może zawierać publiczne funkcje, które będą pętlować przez tablicę i wywoływać funkcje wszystkich instancji obiektów (RunAll() wywołuje każdy z nich Run (oHandle)
  • funkcja Deinit(ClassHandle h) zwalnia przydzieloną pamięć (indeks tablicy) w strategii dynamicznej alokacji

Czy ktoś widzi jakieś problemy, dziury, potencjalne pułapki lub ukryte korzyści / wady obu wariacji tego podejścia? Jeśli wymyślam na nowo metodę projektowania (i zakładam, że muszę nią być), czy możesz wskazać mi jej nazwę?

 3
Author: Ben Gartner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 08:01:47

Jest bardzo obszerna książka na ten temat, która może być warta sprawdzenia:

Programowanie obiektowe w ANSI-C

 3
Author: Ruben Steins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 08:53:16

Zobacz też Ta odpowiedź i Ta

To możliwe. To zawsze wydaje się dobrym pomysłem w tym czasie, ale potem staje się koszmarem konserwacji. Twój kod staje się zaśmiecony kawałkami kodu wiążącymi wszystko razem. Nowy programista będzie miał wiele problemów z czytaniem i zrozumieniem kodu, jeśli użyjesz wskaźników funkcji, ponieważ nie będzie oczywiste, jakie funkcje są nazywane.

Ukrywanie danych za pomocą funkcji get/set jest łatwe do zaimplementowania w C, ale zatrzymaj się. Widziałem wiele prób tego w środowisku wbudowanym i w końcu zawsze jest to problem konserwacyjny.

Ponieważ wszyscy gotowi mają problemy z konserwacją, będę omijać.

 3
Author: Gerhard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:14
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>

/**
 * Define Shape class
 */
typedef struct Shape Shape;
struct Shape {
    /**
     * Variables header...
     */
    double width, height;

    /**
     * Functions header...
     */
    double (*area)(Shape *shape);
};

/**
 * Functions
 */
double calc(Shape *shape) {
        return shape->width * shape->height;
}

/**
 * Constructor
 */
Shape _Shape() {
    Shape s;

    s.width = 1;
    s.height = 1;

    s.area = calc;

    return s;
}

/********************************************/

int main() {
    Shape s1 = _Shape();
    s1.width = 5.35;
    s1.height = 12.5462;

    printf("Hello World\n\n");

    printf("User.width = %f\n", s1.width);
    printf("User.height = %f\n", s1.height);
    printf("User.area = %f\n\n", s1.area(&s1));

    printf("Made with \xe2\x99\xa5 \n");

    return 0;
};
 3
Author: Pierozi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-23 16:54:02

Może być Ta książka Online może pomóc ci rozwiązać twój problem.

 3
Author: Jaydeep Dhrangdhariya,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-23 08:07:10

Moim podejściem byłoby przeniesienie structi wszystkich przede wszystkim związanych z funkcji do osobnego pliku(ów) źródłowego (ów) tak, że może być używany "przenośnie".

W zależności od kompilatora, Może BYĆ w stanie włączyć funkcje do struct, ale jest to bardzo specyficzne dla kompilatora rozszerzenie i nie ma nic wspólnego z ostatnią wersją standardu, którego rutynowo używałem:)

 2
Author: warren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 07:52:48

Pierwszy kompilator c++ był w rzeczywistości preprocesorem, który przetłumaczył kod C++ na C.

Więc bardzo możliwe są zajęcia w C. Możesz spróbować wykopać Stary preprocesor C++ i zobaczyć, jakie rozwiązania tworzy.

 2
Author: Toad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-10 07:57:33

GTK jest zbudowany w całości na C i wykorzystuje wiele koncepcji OOP. Przeczytałem kod źródłowy GTK i jest dość imponujący i zdecydowanie łatwiejszy do odczytania. Podstawową koncepcją jest to, że każda "klasa" jest po prostu strukturą i powiązanymi funkcjami statycznymi. Wszystkie funkcje statyczne akceptują strukturę" instance " jako parametr, wykonują wszystko, co jest potrzebne i zwracają wyniki, jeśli jest to konieczne. Na przykład możesz mieć funkcję " GetPosition (CircleStruct obj)". Funkcja wystarczy przekopać struktura, wyodrębnij numery pozycji, prawdopodobnie zbuduj nowy obiekt PositionStruct, przyklej X i y do nowej struktury pozycyjnej i zwróć ją. GTK implementuje nawet dziedziczenie w ten sposób, osadzając struktury wewnątrz struktur. całkiem sprytne.

 2
Author: rocketsarefast,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-31 00:44:18

Chcesz wirtualnych metod?

Jeśli nie, to po prostu definiujesz zbiór wskaźników funkcji w samej strukturze. Jeśli przypisasz wszystkie wskaźniki funkcji do standardowych funkcji C, będziesz mógł wywoływać funkcje z języka C w bardzo podobnej składni jak w C++.

Jeśli chcesz mieć metody wirtualne, staje się to bardziej skomplikowane. Zasadniczo będziesz musiał zaimplementować własną Vtable do każdej struktury i przypisać Wskaźniki funkcji do VTable w zależności od tego, który funkcja jest wywoływana. Następnie będziesz potrzebował zestawu wskaźników funkcji w samej strukturze, które z kolei wywołują wskaźnik funkcji w tabeli VTable. To jest zasadniczo to, co robi C++.

TBH.. jeśli chcesz tego ostatniego, to prawdopodobnie lepiej jest po prostu znaleźć kompilator C++, którego możesz użyć i ponownie skompilować projekt. Nigdy nie rozumiałem obsesji na punkcie tego, że C++ nie jest użyteczny w embedded. Używałem go wiele razy i działa szybko i nie ma problemów z pamięcią. Jasne. musisz być trochę bardziej ostrożny o tym, co robisz, ale to naprawdę nie jest skomplikowane.

 1
Author: Goz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-10-18 06:16:06

C nie jest językiem OOP, jak słusznie zauważyłeś, więc nie ma wbudowanego sposobu na napisanie prawdziwej klasy. Najlepiej jest spojrzeć na struktury i Wskaźniki funkcji , które pozwolą Ci zbudować przybliżenie klasy. Jednakże, ponieważ C jest proceduralne, możesz rozważyć napisanie kodu bardziej przypominającego C (tj. bez używania klas).

Ponadto, jeśli możesz używać C, możesz używać C++ i pobierać klasy.

 0
Author: Benjamin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-26 13:30:46