Zakładając, że muszę używać C (bez C ++ lub kompilatorów obiektowych) i nie mam dynamicznej alokacji pamięci, jakich technik mogę użyć do zaimplementowania klasy lub dobrego przybliżenia klasy? Czy zawsze warto wyodrębnić „klasę” do osobnego pliku? Załóżmy, że możemy wstępnie przydzielić pamięć, przyjmując stałą liczbę wystąpień lub nawet definiując odniesienie do każdego obiektu jako stałą przed kompilacją. Zapraszam do zakładania, którą koncepcję OOP będę musiał wdrożyć (będzie się różnić) i zasugeruj najlepszą metodę dla każdego.
Ograniczenia:
- Muszę używać C, a nie OOP, ponieważ piszę kod dla systemu osadzonego, a kompilator i wcześniej istniejący kod jest w C.
- Nie ma dynamicznej alokacji pamięci, ponieważ nie mamy wystarczającej ilości pamięci, aby rozsądnie założyć, że jej nie zabraknie, jeśli zaczniemy ją dynamicznie alokować.
- Kompilatory, z którymi pracujemy, nie mają problemów ze wskaźnikami funkcji
apr_
a GLib dodaje do nich przedrostek,g_
aby utworzyć przestrzeń nazw) i inne czynniki organizujące bez OOP. Jeśli i tak zamierzasz zrestrukturyzować aplikację, rozważę poświęcenie czasu na wymyślenie łatwiejszej do utrzymania struktury proceduralnej.Odpowiedzi:
To zależy od dokładnego zestawu funkcji „zorientowanych obiektowo”, jaki chcesz mieć. Jeśli potrzebujesz rzeczy takich jak przeciążanie i / lub metody wirtualne, prawdopodobnie będziesz musiał uwzględnić wskaźniki funkcji w strukturach:
Umożliwiłoby to zaimplementowanie klasy poprzez „dziedziczenie” klasy bazowej i zaimplementowanie odpowiedniej funkcji:
Oczywiście wymaga to również zaimplementowania konstruktora, który zapewni prawidłową konfigurację wskaźnika funkcji. Zwykle dynamicznie przydzielałbyś pamięć dla instancji, ale możesz też na to pozwolić wywołującemu:
Jeśli potrzebujesz kilku różnych konstruktorów, będziesz musiał "udekorować" nazwy funkcji, nie możesz mieć więcej niż jednej
rectangle_new()
funkcji:Oto podstawowy przykład pokazujący użycie:
Mam nadzieję, że to przynajmniej daje kilka pomysłów. Aby uzyskać udaną i bogatą strukturę obiektową w języku C, zapoznaj się z biblioteką GObject glib .
Zwróć również uwagę, że powyżej nie jest modelowana żadna wyraźna „klasa”, każdy obiekt ma własne wskaźniki do metod, które są nieco bardziej elastyczne niż typowe dla C ++. Poza tym kosztuje pamięć. Możesz od tego uciec, upychając wskaźniki do metod w
class
strukturze i wymyślić sposób, w jaki każda instancja obiektu będzie odwoływała się do klasy.źródło
const ShapeClass *
lubconst void *
jako argumenty? Wydawałoby się, że ten ostatni może być trochę lepszy w dziedziczeniu, ale widzę argumenty w obie strony ...float (*computeArea)(const ShapeClass *shape);
mówi, żeShapeClass
jest to nieznany typ.struct
same odwołania wymagają deklaracji przed jej zdefiniowaniem. Jest to wyjaśnione na przykładzie tutaj w odpowiedzi na Lundin . Modyfikacja przykładu w celu uwzględnienia deklaracji forward powinna rozwiązać problem;typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };
Raz też musiałem to zrobić jako zadanie domowe. Postępowałem zgodnie z tym podejściem:
Prostym przykładem może być:
źródło
typedef struct Queue Queue;
tam.Jeśli potrzebujesz tylko jednej klasy, użyj tablicy
struct
s jako danych „obiektów” i przekaż wskaźniki do nich do funkcji „składowych”. Możesz użyćtypedef struct _whatever Whatever
przed deklaracją,struct _whatever
aby ukryć implementację przed kodem klienta. Nie ma różnicy między takim „obiektem” a standardowymFILE
obiektem biblioteki C.Jeśli chcesz mieć więcej niż jedną klasę z dziedziczeniem i funkcjami wirtualnymi, często używa się wskaźników do funkcji jako elementów składowych struktury lub wspólnego wskaźnika do tabeli funkcji wirtualnych. Gobject biblioteka korzysta zarówno ten i podstęp typedef i jest powszechnie stosowane.
Jest też książka o technikach to dostępne online - Object Oriented Programming z ANSI C .
źródło
możesz rzucić okiem na GOBject. jest to biblioteka systemu operacyjnego, która daje pełny sposób na wykonanie obiektu.
http://library.gnome.org/devel/gobject/stable/
źródło
C Interfejsy i implementacje: techniki tworzenia oprogramowania wielokrotnego użytku , David R. Hanson
Ta książka świetnie radzi sobie z twoim pytaniem. Znajduje się w serii Addison Wesley Professional Computing.
Podstawowy paradygmat wygląda mniej więcej tak:
źródło
Podam prosty przykład tego, jak OOP powinno być zrobione w C. Zdaję sobie sprawę, że ten plik pochodzi z 2009 roku, ale mimo wszystko chciałbym to dodać.
Podstawowa koncepcja polega na umieszczeniu „klasy dziedziczonej” 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 „klasie dziedziczonej” (zakładając nie szalone optymalizacje). Teraz, gdy wskaźnik struktury jest rzutowany na „dziedziczoną klasę”, „dziedziczona klasa” może uzyskać dostęp do „dziedziczonych wartości” w taki sam sposób, w jaki normalnie uzyskuje dostęp do swoich elementów członkowskich.
Ta i niektóre konwencje nazewnictwa dla konstruktorów, destruktorów, funkcji alokacji i deallocarionów (polecam init, clean, new, free) przyniosą Ci wiele korzyści.
Jeśli chodzi o funkcje wirtualne, użyj wskaźników funkcji w strukturze, prawdopodobnie z Class_func (...); opakowanie też. Jeśli chodzi o (proste) szablony, dodaj parametr size_t, aby określić rozmiar, wymagaj wskaźnika void * lub wymagaj typu 'class' z funkcjami, na których Ci zależy. (np. int GetUUID (Object * self); GetUUID (& p);)
źródło
Użyć
struct
do symulacji członków danych z klasy. Jeśli chodzi o zakres metod, możesz symulować metody prywatne, umieszczając prototypy funkcji prywatnych w pliku .c, a funkcje publiczne w pliku .h.źródło
źródło
W twoim przypadku dobrym przybliżeniem klasy może być ADT . Ale nadal nie będzie to samo.
źródło
Moja strategia to:
Czy ktoś widzi jakieś problemy, dziury, potencjalne pułapki lub ukryte korzyści / wady któregokolwiek z wariantów tego podejścia? Jeśli wymyślam na nowo metodę projektowania (i zakładam, że tak musi być), czy możesz wskazać mi jej nazwę?
źródło
Zobacz także tę odpowiedź i tę
To jest możliwe. Zawsze wydaje się to dobrym pomysłem, ale potem staje się koszmarem konserwacji. Twój kod jest 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 do funkcji, ponieważ nie będzie oczywiste, jakie funkcje się nazywają.
Ukrywanie danych za pomocą funkcji get / set jest łatwe do zaimplementowania w C, ale na tym koniec. Widziałem wiele prób tego w środowisku wbudowanym i ostatecznie zawsze jest to problem z konserwacją.
Ponieważ wszyscy jesteście gotowi, macie problemy z konserwacją, chciałbym omijać.
źródło
Moje podejście polegałoby na przeniesieniu funkcji
struct
i wszystkich związanych z nią głównie funkcji do oddzielnych plików źródłowych, aby można było ich używać „przenośnie”.W zależności od kompilatora, to może być w stanie obejmować funkcje Into the
struct
, ale to bardzo kompilator specyficzne rozszerzenie, a nie ma nic wspólnego z ostatnią wersją standardową I rutynowo stosowanego :)źródło
Pierwszy kompilator C ++ był w rzeczywistości preprocesorem, który przetłumaczył kod C ++ na C.
Tak więc bardzo możliwe jest posiadanie klas w C. Możesz spróbować wykopać stary preprocesor C ++ i zobaczyć, jakie rozwiązania tworzy.
źródło
cfront
; napotkał problemy, gdy wyjątki zostały dodane do C ++ - obsługa wyjątków nie jest trywialna.GTK jest zbudowany w całości na C i wykorzystuje wiele koncepcji OOP. Przeczytałem kod źródłowy GTK i jest on imponujący i zdecydowanie łatwiejszy do odczytania. Podstawowa koncepcja jest taka, że każda „klasa” jest po prostu strukturą i związanymi z nią funkcjami statycznymi. Wszystkie funkcje statyczne akceptują strukturę „instancji” jako parametr, robią wszystko, co trzeba, i zwracają wyniki, jeśli to konieczne. Na przykład możesz mieć funkcję „GetPosition (CircleStruct obj)”. Funkcja po prostu przeszukałaby strukturę, wyodrębniłaby numery pozycji, prawdopodobnie utworzyłaby nowy obiekt PositionStruct, przykleiłaby x i y do nowego PositionStruct i zwróciłaby go. GTK nawet implementuje dziedziczenie w ten sposób, osadzając struktury wewnątrz struktur. całkiem sprytny.
źródło
Chcesz wirtualnych metod?
Jeśli nie, po prostu zdefiniujesz zestaw wskaźników funkcji w samej strukturze. Jeśli przypiszesz wszystkie wskaźniki funkcji do standardowych funkcji C, będziesz mógł wywoływać funkcje z C w bardzo podobnej składni, jak w C ++.
Jeśli chcesz mieć metody wirtualne, sprawa staje się bardziej skomplikowana. Zasadniczo będziesz musiał zaimplementować własną tabelę VTable do każdej struktury i przypisać wskaźniki funkcji do tabeli VTable w zależności od wywoływanej funkcji. Potrzebny byłby wtedy zestaw 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 ++.
Jednak TBH ... jeśli chcesz tego drugiego, prawdopodobnie lepiej będzie 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 nadaje się do użytku we wbudowanym. Używałem go wiele razy i działa szybko i nie ma problemów z pamięcią. Oczywiście, musisz być bardziej ostrożny w tym, co robisz, ale to naprawdę nie jest takie skomplikowane.
źródło
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 do funkcji , które pozwolą ci zbudować przybliżenie klasy. Jednakże, ponieważ C jest językiem proceduralnym, możesz rozważyć napisanie bardziej podobnego do C kodu (tj. Bez próby używania klas).
Ponadto, jeśli możesz użyć C, prawdopodobnie możesz użyć C ++ i uzyskać klasy.
źródło