Bagaimana orang menulis kode berorientasi objek dalam C? [Tutup]

500

Apa sajakah cara untuk menulis kode berorientasi objek dalam C? Terutama berkenaan dengan polimorfisme.


Lihat juga ini Stack Overflow pertanyaan Obyek-orientasi dalam C .

Peter Mortensen
sumber
1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Pemrograman Berorientasi Objek dalam C </a> oleh Laurent Deniau
Anda mungkin ingin melihat jawaban atas pertanyaan ini: Teknik / strategi apa yang digunakan orang untuk membangun objek dalam C (bukan C ++)?
Dale Hagglund
25
@Camilo Martin: Saya sengaja meminta bisa tidak harus . Saya sebenarnya tidak tertarik menggunakan OOP di C. Namun, dengan melihat solusi OO di C, saya / kami akan belajar lebih banyak tentang batasan dan / atau fleksibilitas C dan juga tentang cara-cara kreatif untuk menerapkan dan menggunakan polimorfisme.
Dinah
5
OO hanyalah sebuah pola. Periksa di sini, bahkan dapat dilakukan dalam File .bat: dirk.rave.org/chap9.txt (pola apa pun dapat diterapkan ke bahasa pemrograman apa pun jika Anda cukup tertarik, saya pikir). Ini adalah makanan yang baik untuk dipikirkan. Dan mungkin banyak yang bisa dipelajari dari menerapkan pola-pola yang kita terima begitu saja pada bahasa yang tidak memilikinya.
Camilo Martin
6
GTK - 'scuse me, GObject - sebenarnya adalah contoh yang cukup bagus dari OOP (agak) di C. Jadi, untuk menjawab @Camilo, untuk interpoliabilitas C.
new123456

Jawaban:

362

Iya. Bahkan Axel Schreiner menyediakan bukunya "Pemrograman Berorientasi Objek dalam ANSI-C" secara gratis yang mencakup subjek cukup menyeluruh.

mepcotterell
sumber
28
Meskipun konsep dalam buku ini adalah padatan, Anda akan kehilangan keamanan mengetik.
diapir
22
Sebelum apa yang kita kenal sebagai pola desain, adalah pola desain yang dikenal sebagai "orientasi objek"; sama dengan pengumpulan sampah, dan lainnya. Mereka sudah berurat berakar sekarang, kita cenderung lupa, ketika mereka pertama kali dirancang, itu hampir sama dengan apa yang kita pikirkan sebagai pola desain hari ini
Dexygen
11
Anda bisa mendapatkannya langsung dari situs penulis: cs.rit.edu/~ats/books/ooc.pdf makalah lain dari penulis yang sama: cs.rit.edu/~ats/books/index.html
pakman
10
Koleksi yang tepat (Buku + kode sumber contoh) tersedia dari indeks rit.edu ini pemrograman berorientasi objek dengan ANSI-C
David C. Rankin
3
Apakah buku ini diulas bersama? Ada kesalahan ketik pada kalimat pertama paragraf pertama dari halaman pertama.
Dagroom
343

Karena Anda berbicara tentang polimorfisme maka ya, Anda bisa, kami melakukan hal-hal semacam itu bertahun-tahun sebelum C ++ muncul.

Pada dasarnya Anda menggunakan a structuntuk menyimpan data dan daftar pointer fungsi untuk menunjuk ke fungsi yang relevan untuk data tersebut.

Jadi, di kelas komunikasi, Anda akan memiliki panggilan terbuka, baca, tulis, dan tutup yang akan dipertahankan sebagai empat fungsi pointer dalam struktur, di samping data untuk objek, seperti:

typedef struct {
    int (*open)(void *self, char *fspec);
    int (*close)(void *self);
    int (*read)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    int (*write)(void *self, void *buff, size_t max_sz, size_t *p_act_sz);
    // And data goes here.
} tCommClass;

tCommClass commRs232;
commRs232.open = &rs232Open;
: :
commRs232.write = &rs232Write;

tCommClass commTcp;
commTcp.open = &tcpOpen;
: :
commTcp.write = &tcpWrite;

Tentu saja, segmen kode tersebut di atas sebenarnya akan berada dalam "konstruktor" seperti rs232Init().

Ketika Anda 'mewarisi' dari kelas itu, Anda hanya mengubah pointer untuk menunjuk ke fungsi Anda sendiri. Setiap orang yang memanggil fungsi-fungsi itu akan melakukannya melalui pointer fungsi, memberi Anda polimorfisme Anda:

int stat = (commTcp.open)(commTcp, "bigiron.box.com:5000");

Semacam seperti vtable manual.

Anda bahkan bisa memiliki kelas virtual dengan mengatur pointer ke NULL -perilaku akan sedikit berbeda dengan C ++ (core dump pada saat run-time daripada kesalahan pada waktu kompilasi).

Berikut sepotong kode contoh yang menunjukkannya. Pertama struktur kelas tingkat atas:

#include <stdio.h>

// The top-level class.

typedef struct sCommClass {
    int (*open)(struct sCommClass *self, char *fspec);
} tCommClass;

Kemudian kita memiliki fungsi untuk TCP 'subclass':

// Function for the TCP 'class'.

static int tcpOpen (tCommClass *tcp, char *fspec) {
    printf ("Opening TCP: %s\n", fspec);
    return 0;
}
static int tcpInit (tCommClass *tcp) {
    tcp->open = &tcpOpen;
    return 0;
}

Dan HTTP juga:

// Function for the HTTP 'class'.

static int httpOpen (tCommClass *http, char *fspec) {
    printf ("Opening HTTP: %s\n", fspec);
    return 0;
}
static int httpInit (tCommClass *http) {
    http->open = &httpOpen;
    return 0;
}

Dan akhirnya program uji untuk menunjukkannya dalam aksi:

// Test program.

int main (void) {
    int status;
    tCommClass commTcp, commHttp;

    // Same 'base' class but initialised to different sub-classes.

    tcpInit (&commTcp);
    httpInit (&commHttp);

    // Called in exactly the same manner.

    status = (commTcp.open)(&commTcp, "bigiron.box.com:5000");
    status = (commHttp.open)(&commHttp, "http://www.microsoft.com");

    return 0;
}

Ini menghasilkan output:

Opening TCP: bigiron.box.com:5000
Opening HTTP: http://www.microsoft.com

sehingga Anda dapat melihat bahwa fungsi yang berbeda sedang dipanggil, tergantung pada sub-kelas.

paxdiablo
sumber
52
Enkapsulasi cukup mudah, polimorfisme bisa dilakukan - tetapi pewarisan itu sulit
Martin Beckett
5
lwn.net baru-baru ini menerbitkan sebuah artikel berjudul Object Oriented design Patterns di kernel mengenai subjek stucts yang mirip dengan jawaban di atas - yaitu, sebuah struct yang berisi fungsi pointer, atau sebuah pointer ke sebuah struct yang memiliki fungsi-fungsi yang membawa sebuah pointer ke struct dengan data yang kami kerjakan sebagai parameter.
radicalmatt
11
+1 Contoh yang bagus! Meskipun jika seseorang benar-benar ingin turun jalan ini, akan lebih tepat untuk struct "instance" untuk memiliki satu bidang yang menunjuk ke instance "virtual table" mereka, yang berisi semua fungsi virtual untuk tipe itu di satu tempat. Yaitu Anda tCommClassakan diubah namanya menjadi tCommVT, dan tCommClassstruct hanya akan memiliki bidang data dan bidang tunggal yang tCommVT vtmenunjuk ke "satu-satunya" tabel virtual. Membawa semua petunjuk dengan setiap contoh menambahkan overhead yang tidak perlu dan lebih menyerupai bagaimana Anda akan melakukan hal-hal dalam JavaScript daripada C ++, IMHO.
Groo
1
Jadi ini menunjukkan implementasi dari satu antarmuka, tetapi bagaimana dengan mengimplementasikan beberapa antarmuka? Atau pewarisan berganda?
weberc2
Weber, jika Anda ingin semua fungsi C ++, Anda mungkin harus menggunakan C ++. Pertanyaan yang diajukan secara spesifik tentang polimorfisme, kemampuan benda untuk mengambil "bentuk" yang berbeda. Anda tentu saja dapat melakukan antarmuka dan pewarisan berganda di C tetapi ini merupakan pekerjaan ekstra yang adil, dan Anda harus mengelola sendiri kecerdasannya daripada menggunakan C ++ bawaan.
paxdiablo
86

Ruang nama sering dilakukan dengan melakukan:

stack_push(thing *)

dari pada

stack::push(thing *)

Untuk membuat C struct menjadi sesuatu seperti kelas C ++ Anda dapat mengubah:

class stack {
     public:
        stack();
        void push(thing *);
        thing * pop();
        static int this_is_here_as_an_example_only;
     private:
        ...
};

Ke

struct stack {
     struct stack_type * my_type;
     // Put the stuff that you put after private: here
};
struct stack_type {
     void (* construct)(struct stack * this); // This takes uninitialized memory
     struct stack * (* operator_new)(); // This allocates a new struct, passes it to construct, and then returns it
     void (*push)(struct stack * this, thing * t); // Pushing t onto this stack
     thing * (*pop)(struct stack * this); // Pops the top thing off the stack and returns it
     int this_is_here_as_an_example_only;
}Stack = {
    .construct = stack_construct,
    .operator_new = stack_operator_new,
    .push = stack_push,
    .pop = stack_pop
};
// All of these functions are assumed to be defined somewhere else

Dan lakukan:

struct stack * st = Stack.operator_new(); // Make a new stack
if (!st) {
   // Do something about it
} else {
   // You can use the stack
   stack_push(st, thing0); // This is a non-virtual call
   Stack.push(st, thing1); // This is like casting *st to a Stack (which it already is) and doing the push
   st->my_type.push(st, thing2); // This is a virtual call
}

Saya tidak melakukan destruktor atau menghapus, tetapi mengikuti pola yang sama.

this_is_here_as_an_example_only seperti variabel kelas statis - dibagikan di antara semua contoh jenis. Semua metode benar-benar statis, kecuali ada yang mengambil ini *

nategoose
sumber
1
@nategoose - st->my_type->push(st, thing2);bukanst->my_type.push(st, thing2);
Fabricio
@nategoose: ATAU struct stack_type my_type; bukannyastruct stack_type * my_type;
Fabricio
3
Saya suka konsep memiliki struct untuk kelas. Tapi bagaimana dengan Classstruct generik ? Itu akan membuat OO C lebih dinamis daripada C ++. Bagaimana tentang itu? Omong-omong, +1.
Linuxios
54

Saya percaya bahwa selain berguna dalam haknya sendiri, menerapkan OOP dalam C adalah cara terbaik untuk mempelajari OOP dan memahami cara kerja bagian dalamnya. Pengalaman banyak programmer telah menunjukkan bahwa untuk menggunakan teknik secara efisien dan percaya diri, seorang programmer harus memahami bagaimana konsep-konsep yang mendasarinya akhirnya diimplementasikan. Meniru kelas, warisan, dan polimorfisme dalam C mengajarkan hal ini.

Untuk menjawab pertanyaan awal, berikut adalah beberapa sumber yang mengajarkan bagaimana melakukan OOP di C:

Posting blog EmbeddedGurus.com "Pemrograman berbasis objek dalam C" menunjukkan bagaimana mengimplementasikan kelas dan pewarisan tunggal dalam C portabel: http://embeddedgurus.com/state-space/2008/01/object-based-programming-in-c /

Catatan Aplikasi "" C + "- Pemrograman Berorientasi Objek dalam C" menunjukkan bagaimana mengimplementasikan kelas, pewarisan tunggal, dan pengikatan akhir (polimorfisme) dalam C menggunakan macro preprocessor: http://www.state-machine.com/resources/cplus_3. 0_manual.pdf , kode contoh tersedia dari http://www.state-machine.com/resources/cplus_3.0.zip

Miro
sumber
4
Url baru untuk manual C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang
32

Saya sudah melihatnya selesai. Saya tidak akan merekomendasikannya. C ++ awalnya dimulai dengan cara ini sebagai preprosesor yang menghasilkan kode C sebagai langkah perantara.

Pada dasarnya apa yang Anda akhirnya lakukan adalah membuat tabel pengiriman untuk semua metode Anda di mana Anda menyimpan referensi fungsi Anda. Turunnya kelas akan memerlukan menyalin tabel pengiriman ini dan mengganti entri yang ingin Anda timpa, dengan "metode" baru Anda harus memanggil metode asli jika ingin memanggil metode dasar. Akhirnya, Anda akhirnya menulis ulang C ++.

tvanfosson
sumber
5
"Akhirnya, kamu akhirnya menulis ulang C ++," aku bertanya-tanya apakah aku takut itu akan terjadi.
Dinah
39
Atau, Anda mungkin akhirnya menulis ulang Objective C, yang akan menjadi hasil yang jauh lebih menarik.
Kontrak Prof. Falken dilanggar pada
3
Ada rasa kelas-kurang dari OOP, seperti di Javascript , di mana guru berkata: "Kita tidak perlu kelas untuk membuat banyak objek serupa." Tapi saya khawatir ini tidak mudah untuk dicapai dalam C. Tidak (belum) dalam posisi untuk mengatakan, meskipun. (Apakah ada klon () rutin untuk mengkloning sebuah struct?)
Lumi
1
Orang pintar lain, yang harus benar-benar mengimplementasikannya dan membuat implementasi itu cepat (Google, mesin V8) telah melakukan semuanya menambahkan kelas (tersembunyi) ke JavaScript kembali.
cubuspl42
Bukankah glibditulis dalam C dengan cara yang objektif?
kravemir
26

Yakin itu mungkin. Inilah yang dilakukan GObject , kerangka kerja yang mendasari semua GTK + dan GNOME .

Peter Mortensen
sumber
Apa pro / kontra dari pendekatan semacam itu? Yaitu. lebih mudah untuk menulisnya menggunakan C ++.
kravemir
@ kravemir Yah, C ++ tidak cukup portabel seperti C, dan agak sulit untuk menghubungkan C ++ ke kode yang mungkin dikompilasi oleh kompiler C ++ yang berbeda. Tapi ya, lebih mudah untuk menulis kelas dalam C ++, meskipun GObject juga tidak terlalu sulit (dengan asumsi Anda tidak keberatan dengan pelat ketel kecil).
Edwin Buck
17

Sub-pustaka C stdio FILE adalah contoh yang sangat baik tentang cara membuat abstraksi, enkapsulasi, dan modularitas dalam C. yang tidak disadap.

Warisan dan polimorfisme - aspek-aspek lain yang sering dianggap penting untuk OOP - tidak selalu memberikan keuntungan produktivitas yang mereka janjikan dan argumen yang masuk akal telah dibuat bahwa mereka benar-benar dapat menghambat perkembangan dan berpikir tentang domain masalah.

msw
sumber
Bukankah stdio diabstraksi pada lapisan kernel? Jika saya tidak salah, C-library memperlakukan mereka sebagai file karakter / perangkat, dan driver kernel melakukan pekerjaan, ...
kravemir
15

Contoh sepele dengan Hewan dan Anjing: Anda mencerminkan mekanisme vtable C ++ (sebagian besar toh). Anda juga memisahkan alokasi dan instantiasi (Animal_Alloc, Animal_New) sehingga kami tidak memanggil malloc () beberapa kali. Kita juga harus secara eksplisit melewatkan thispointer.

Jika Anda melakukan fungsi non-virtual, itu trival. Anda hanya tidak menambahkannya ke fungsi vtable dan statis tidak memerlukan thispointer. Multiple inheritance umumnya membutuhkan banyak vtables untuk menyelesaikan ambiguitas.

Selain itu, Anda harus dapat menggunakan setjmp / longjmp untuk melakukan penanganan pengecualian.

struct Animal_Vtable{
    typedef void (*Walk_Fun)(struct Animal *a_This);
    typedef struct Animal * (*Dtor_Fun)(struct Animal *a_This);

    Walk_Fun Walk;
    Dtor_Fun Dtor;
};

struct Animal{
    Animal_Vtable vtable;

    char *Name;
};

struct Dog{
    Animal_Vtable vtable;

    char *Name; // Mirror member variables for easy access
    char *Type;
};

void Animal_Walk(struct Animal *a_This){
    printf("Animal (%s) walking\n", a_This->Name);
}

struct Animal* Animal_Dtor(struct Animal *a_This){
    printf("animal::dtor\n");
    return a_This;
}

Animal *Animal_Alloc(){
    return (Animal*)malloc(sizeof(Animal));
}

Animal *Animal_New(Animal *a_Animal){
    a_Animal->vtable.Walk = Animal_Walk;
    a_Animal->vtable.Dtor = Animal_Dtor;
    a_Animal->Name = "Anonymous";
    return a_Animal;
}

void Animal_Free(Animal *a_This){
    a_This->vtable.Dtor(a_This);

    free(a_This);
}

void Dog_Walk(struct Dog *a_This){
    printf("Dog walking %s (%s)\n", a_This->Type, a_This->Name);
}

Dog* Dog_Dtor(struct Dog *a_This){
    // Explicit call to parent destructor
    Animal_Dtor((Animal*)a_This);

    printf("dog::dtor\n");

    return a_This;
}

Dog *Dog_Alloc(){
    return (Dog*)malloc(sizeof(Dog));
}

Dog *Dog_New(Dog *a_Dog){
    // Explict call to parent constructor
    Animal_New((Animal*)a_Dog);

    a_Dog->Type = "Dog type";
    a_Dog->vtable.Walk = (Animal_Vtable::Walk_Fun) Dog_Walk;
    a_Dog->vtable.Dtor = (Animal_Vtable::Dtor_Fun) Dog_Dtor;

    return a_Dog;
}

int main(int argc, char **argv){
    /*
      Base class:

        Animal *a_Animal = Animal_New(Animal_Alloc());
    */
    Animal *a_Animal = (Animal*)Dog_New(Dog_Alloc());

    a_Animal->vtable.Walk(a_Animal);

    Animal_Free(a_Animal);
}

PS. Ini diuji pada kompiler C ++, tetapi harus mudah membuatnya bekerja pada kompiler C.

Jasper Bekkers
sumber
typedefdi dalam structtidak dimungkinkan dalam C.
masoud
13

Lihat GObject . Ini dimaksudkan sebagai OO dalam C dan satu implementasi dari apa yang Anda cari. Jika Anda benar-benar menginginkan OO, gunakan C ++ atau bahasa OOP lainnya. GObject terkadang bisa sangat sulit untuk digunakan jika Anda terbiasa berurusan dengan bahasa OO, tetapi seperti apa pun, Anda akan terbiasa dengan konvensi dan aliran.

NG.
sumber
12

Ini menarik untuk dibaca. Saya sendiri telah memikirkan pertanyaan yang sama, dan manfaat dari memikirkannya adalah ini:

  • Mencoba membayangkan bagaimana menerapkan konsep OOP dalam bahasa non-OOP membantu saya memahami kekuatan bahasa OOp (dalam kasus saya, C ++). Ini membantu memberi saya penilaian yang lebih baik tentang apakah akan menggunakan C atau C ++ untuk jenis aplikasi yang diberikan - di mana manfaat dari satu lebih besar dari yang lain.

  • Dalam menjelajah web saya untuk informasi dan pendapat tentang ini, saya menemukan seorang penulis yang sedang menulis kode untuk prosesor tertanam dan hanya memiliki kompiler C yang tersedia: http://www.eetimes.com/discussion/other/4024626/Object-Oriented -C-Menciptakan-Yayasan-Kelas-Bagian-1

Dalam kasusnya, menganalisis dan mengadaptasi konsep OOP di dataran C adalah pengejaran yang valid. Tampaknya ia terbuka untuk mengorbankan beberapa konsep OOP karena hit overhead kinerja yang dihasilkan dari upaya untuk mengimplementasikannya dalam C.

Pelajaran yang saya ambil adalah, ya itu bisa dilakukan sampai tingkat tertentu, dan ya, ada beberapa alasan bagus untuk mencobanya.

Pada akhirnya, mesin memutar-mutar bit pointer stack, membuat penghitung program melompat-lompat dan menghitung operasi akses memori. Dari sudut pandang efisiensi, semakin sedikit perhitungan yang dilakukan oleh program Anda, semakin baik ... tetapi terkadang kami harus membayar pajak ini hanya agar kami dapat mengatur program kami dengan cara yang membuatnya paling tidak rentan terhadap kesalahan manusia. Kompilator bahasa OOP berusaha untuk mengoptimalkan kedua aspek. Programmer harus lebih berhati-hati mengimplementasikan konsep-konsep ini dalam bahasa seperti C.

RJB
sumber
10

Anda mungkin terbantu untuk melihat dokumentasi Apple untuk kumpulan Core Foundation-nya. Ini adalah API C murni, tetapi banyak tipe yang dijembatani dengan objek Objective-C yang setara.

Anda juga mungkin merasa terbantu untuk melihat desain Objective-C itu sendiri. Ini sedikit berbeda dari C ++ karena sistem objek didefinisikan dalam hal fungsi C, misalnya objc_msg_senduntuk memanggil metode pada objek. Kompiler menerjemahkan sintaks braket kuadrat ke dalam panggilan fungsi tersebut, jadi Anda tidak perlu mengetahuinya, tetapi mempertimbangkan pertanyaan Anda, Anda mungkin menemukan manfaatnya untuk mempelajari cara kerjanya di bawah tenda.

benzado
sumber
10

Ada beberapa teknik yang bisa digunakan. Yang paling penting adalah bagaimana membagi proyek. Kami menggunakan antarmuka dalam proyek kami yang dideklarasikan dalam file .h dan implementasi objek dalam file .c. Bagian yang penting adalah bahwa semua modul yang menyertakan file .h hanya melihat objek sebagai void *, dan file .c adalah satu-satunya modul yang mengetahui internal struktur.

Sesuatu seperti ini untuk kelas yang kita beri nama FOO sebagai contoh:

Dalam file .h

#ifndef FOO_H_
#define FOO_H_

...
 typedef struct FOO_type FOO_type;     /* That's all the rest of the program knows about FOO */

/* Declaration of accessors, functions */
FOO_type *FOO_new(void);
void FOO_free(FOO_type *this);
...
void FOO_dosomething(FOO_type *this, param ...):
char *FOO_getName(FOO_type *this, etc);
#endif

File implementasi C akan seperti itu.

#include <stdlib.h>
...
#include "FOO.h"

struct FOO_type {
    whatever...
};


FOO_type *FOO_new(void)
{
    FOO_type *this = calloc(1, sizeof (FOO_type));

    ...
    FOO_dosomething(this, );
    return this;
}

Jadi saya memberikan pointer secara eksplisit ke objek ke setiap fungsi modul itu. Kompiler C ++ melakukannya secara implisit, dan dalam C kita menuliskannya secara eksplisit.

Saya sangat menggunakan this dalam program saya, untuk memastikan bahwa program saya tidak dikompilasi dalam C ++, dan itu memiliki sifat yang baik berada dalam warna lain di editor menyoroti sintaks saya.

Bidang-bidang FOO_struct dapat dimodifikasi dalam satu modul dan modul lainnya bahkan tidak perlu dikompilasi ulang agar tetap dapat digunakan.

Dengan gaya itu saya sudah menangani sebagian besar keunggulan OOP (enkapsulasi data). Dengan menggunakan pointer fungsi, itu bahkan mudah untuk mengimplementasikan sesuatu seperti warisan, tetapi jujur, itu benar-benar jarang berguna.

Patrick Schlüter
sumber
6
Jika Anda melakukan typedef struct FOO_type FOO_typealih - alih mengetikkan membatalkan di header Anda mendapatkan manfaat tambahan dari pemeriksaan jenis, sementara masih tidak mengekspos struktur Anda.
Scott Wales
8

Anda dapat memalsunya menggunakan pointer fungsi, dan pada kenyataannya, saya pikir secara teori dimungkinkan untuk mengkompilasi program C ++ ke C.

Namun, jarang masuk akal untuk memaksakan paradigma pada suatu bahasa daripada memilih bahasa yang menggunakan paradigma.

Uri
sumber
5
Kompiler C ++ yang pertama melakukan hal itu - ia mengonversi kode C ++ menjadi kode C yang setara (tapi jelek dan tidak dapat dibaca manusia), yang kemudian dikompilasi oleh kompiler C.
Adam Rosenfield
2
EDG, Cfront dan beberapa lainnya masih mampu melakukan ini. Dengan alasan yang sangat bagus: tidak setiap platform memiliki kompiler C ++.
Jasper Bekkers
Untuk beberapa alasan saya berpikir bahwa C-front hanya mendukung ekstensi C ++ tertentu (misalnya, referensi) tetapi tidak penuh emulasi OOP / pengiriman dinamis.
Uri
2
Anda juga dapat melakukan hal yang sama dengan LLVM dan backend C.
Zifre
7

Berorientasi objek C, dapat dilakukan, saya telah melihat jenis kode dalam produksi di Korea, dan itu adalah monster paling mengerikan yang pernah saya lihat selama bertahun-tahun (ini seperti tahun lalu (2007) yang saya lihat kode). Jadi ya itu bisa dilakukan, dan ya orang sudah melakukannya sebelumnya, dan masih melakukannya bahkan di zaman sekarang ini. Tapi saya akan merekomendasikan C ++ atau Objective-C, keduanya bahasa yang lahir dari C, dengan tujuan memberikan orientasi objek dengan paradigma yang berbeda.

Robert Gould
sumber
3
jika Linus melihat komentar Anda. Dia pasti akan tertawa atau mengutuk Anda
Anders Lind
7

Jika Anda yakin bahwa pendekatan OOP lebih unggul untuk masalah yang Anda coba selesaikan, mengapa Anda mencoba menyelesaikannya dengan bahasa non-OOP? Sepertinya Anda menggunakan alat yang salah untuk pekerjaan itu. Gunakan C ++ atau bahasa varian C berorientasi objek lainnya.

Jika Anda bertanya karena Anda mulai membuat kode pada proyek besar yang sudah ada yang ditulis dalam C, maka Anda tidak boleh mencoba memaksakan paradigma OOP Anda sendiri (atau orang lain) ke dalam infrastruktur proyek. Ikuti panduan yang sudah ada dalam proyek. Secara umum, API bersih dan terisolasi perpustakaan dan modul akan pergi jauh ke arah memiliki bersih OOP- ish desain.

Jika, setelah semua ini, Anda benar-benar siap melakukan OOP C, baca ini (PDF).

RarrRarrRarr
sumber
36
Tidak benar-benar menjawab pertanyaan ...
Brian Postow
2
@Brian, tautan ke PDF akan muncul untuk menjawab pertanyaan secara langsung, meskipun saya belum punya waktu untuk memeriksa sendiri.
Mark Ransom
5
Tautan ke PDF tampaknya merupakan keseluruhan buku teks tentang subjek tersebut ... Sebuah bukti yang indah, tetapi tidak sesuai dengan margin ...
Brian Postow
5
ya, jawab pertanyaannya. sangat valid untuk bertanya bagaimana menggunakan bahasa dengan cara tertentu. tidak ada permintaan pendapat tentang bahasa lain ....
Tim Ring
9
@Brian & Tim Ring: Pertanyaan yang diajukan untuk rekomendasi buku tentang suatu topik; Saya memberinya tautan ke sebuah buku yang secara khusus membahas topik ini. Saya juga memberikan pendapat saya tentang mengapa pendekatan terhadap masalah mungkin tidak optimal (yang saya pikir banyak orang di sini tampaknya setuju, berdasarkan suara dan komentar / jawaban lainnya). Apakah Anda punya saran untuk meningkatkan jawaban saya?
RarrRarrRarr
6

Ya kamu bisa. Orang-orang menulis berorientasi objek C sebelum C ++ atau Objective-C muncul. Baik C ++ dan Objective-C, sebagian, berupaya untuk mengambil beberapa konsep OO yang digunakan dalam C dan memformalkannya sebagai bagian dari bahasa.

Berikut adalah program yang sangat sederhana yang menunjukkan bagaimana Anda dapat membuat sesuatu yang terlihat seperti / adalah pemanggilan metode (ada cara yang lebih baik untuk melakukan ini. Ini hanya bukti bahwa bahasa mendukung konsep-konsep):

#include<stdio.h>

struct foobarbaz{
    int one;
    int two;
    int three;
    int (*exampleMethod)(int, int);
};

int addTwoNumbers(int a, int b){
    return a+b;
}

int main()
{
    // Define the function pointer
    int (*pointerToFunction)(int, int) = addTwoNumbers;

    // Let's make sure we can call the pointer
    int test = (*pointerToFunction)(12,12);
    printf ("test: %u \n",  test);

    // Now, define an instance of our struct
    // and add some default values.
    struct foobarbaz fbb;
    fbb.one   = 1;
    fbb.two   = 2;
    fbb.three = 3;

    // Now add a "method"
    fbb.exampleMethod = addTwoNumbers;

    // Try calling the method
    int test2 = fbb.exampleMethod(13,36);
    printf ("test2: %u \n",  test2);

    printf("\nDone\n");
    return 0;
}
Alan Storm
sumber
6

Tentu saja, itu tidak akan secantik menggunakan bahasa dengan dukungan bawaan. Saya bahkan sudah menulis "assembler berorientasi objek".

Darron
sumber
6

Sedikit kode OOC untuk ditambahkan:

#include <stdio.h>

struct Node {
    int somevar;
};

void print() {
    printf("Hello from an object-oriented C method!");
};

struct Tree {
    struct Node * NIL;
    void (*FPprint)(void);
    struct Node *root;
    struct Node NIL_t;
} TreeA = {&TreeA.NIL_t,print};

int main()
{
    struct Tree TreeB;
    TreeB = TreeA;
    TreeB.FPprint();
    return 0;
}
pengguna922475
sumber
5

Saya telah menggali ini selama satu tahun:

Karena sistem GObject sulit digunakan dengan C murni, saya mencoba menulis beberapa makro bagus untuk meringankan gaya OO dengan C.

#include "OOStd.h"

CLASS(Animal) {
    char *name;
    STATIC(Animal);
    vFn talk;
};
static int Animal_load(Animal *THIS,void *name) {
    THIS->name = name;
    return 0;
}
ASM(Animal, Animal_load, NULL, NULL, NULL)

CLASS_EX(Cat,Animal) {
    STATIC_EX(Cat, Animal);
};
static void Meow(Animal *THIS){
    printf("Meow!My name is %s!\n", THIS->name);
}

static int Cat_loadSt(StAnimal *THIS, void *PARAM){
    THIS->talk = (void *)Meow;
    return 0;
}
ASM_EX(Cat,Animal, NULL, NULL, Cat_loadSt, NULL)


CLASS_EX(Dog,Animal){
    STATIC_EX(Dog, Animal);
};

static void Woof(Animal *THIS){
    printf("Woof!My name is %s!\n", THIS->name);
}

static int Dog_loadSt(StAnimal *THIS, void *PARAM) {
    THIS->talk = (void *)Woof;
    return 0;
}
ASM_EX(Dog, Animal, NULL, NULL, Dog_loadSt, NULL)

int main(){
    Animal *animals[4000];
    StAnimal *f;
    int i = 0;
    for (i=0; i<4000; i++)
    {
        if(i%2==0)
            animals[i] = NEW(Dog,"Jack");
        else
            animals[i] = NEW(Cat,"Lily");
    };
    f = ST(animals[0]);
    for(i=0; i<4000; ++i) {
        f->talk(animals[i]);
    }
    for (i=0; i<4000; ++i) {
        DELETE0(animals[i]);
    }
    return 0;
}

Ini situs proyek saya (saya tidak punya cukup waktu untuk menulis en. Doc, namun doc dalam bahasa Cina jauh lebih baik).

OOC-GCC

dameng
sumber
yang KELAS STATIC ASM BARU DELETE ST ... macro di OOC-GCC
Dameng
4

Artikel atau buku mana yang baik untuk menggunakan konsep OOP di C?

Dave Hanson C Interfaces dan Implementasi adalah sangat baik pada enkapsulasi dan penamaan dan sangat baik pada penggunaan fungsi pointer. Dave tidak mencoba mensimulasikan pewarisan.

Norman Ramsey
sumber
4

OOP hanyalah paradigma yang menempatkan data lebih penting daripada kode dalam program. OOP bukan bahasa. Jadi, seperti plain C adalah bahasa yang sederhana, OOP dalam plain C juga sederhana.

anonim
sumber
3
Dikatakan dengan baik, tetapi ini harus menjadi komentar.
pqsk
4

Satu hal yang mungkin ingin Anda lakukan adalah melihat implementasi Xt toolkit untuk X Window . Memang sudah lama di gigi, tetapi banyak struktur yang digunakan dirancang untuk bekerja secara OO dalam C. tradisional. Umumnya ini berarti menambahkan lapisan tipuan tambahan di sana-sini dan merancang struktur untuk saling menempel.

Anda benar-benar dapat melakukan banyak hal di jalan OO yang terletak di C dengan cara ini, meskipun terasa seperti itu beberapa kali, konsep OO tidak muncul sepenuhnya dari pikiran #include<favorite_OO_Guru.h>. Mereka benar-benar merupakan banyak praktik terbaik yang ada saat itu. Bahasa dan sistem OO hanya menyaring dan memperkuat bagian-bagian dari zeitgeist pemrograman saat itu.

Peter Mortensen
sumber
4

Jawaban untuk pertanyaan itu adalah 'Ya, Anda bisa'.

Object-oriented C (OOC) kit adalah untuk mereka yang ingin memprogram dengan cara berorientasi objek, tetapi tetap menggunakan C lama yang baik juga. OOC mengimplementasikan kelas, pewarisan tunggal dan ganda, penanganan pengecualian.

fitur

• Hanya menggunakan makro C dan fungsi, tidak perlu ekstensi bahasa! (ANSI-C)

• Kode sumber yang mudah dibaca untuk aplikasi Anda. Perawatan diambil untuk membuat hal-hal sesederhana mungkin.

• Warisan tunggal kelas

• Warisan berganda dengan antarmuka dan mixin (sejak versi 1.3)

• Menerapkan pengecualian (dalam C murni!)

• Fungsi virtual untuk kelas

• Alat eksternal untuk implementasi kelas yang mudah

Untuk detail lebih lanjut, kunjungi http://ooc-coding.sourceforge.net/ .

Sachin Mhetre
sumber
4

Sepertinya orang mencoba meniru gaya C ++ menggunakan C. Saya kira adalah melakukan pemrograman berorientasi objek C benar-benar melakukan pemrograman berorientasi-struktur. Namun, Anda dapat mencapai hal-hal seperti ikatan yang terlambat, enkapsulasi, dan pewarisan. Untuk warisan Anda secara eksplisit menentukan pointer ke struct dasar di sub struct Anda dan ini jelas merupakan bentuk multiple inheritance. Anda juga harus menentukan apakah Anda

//private_class.h
struct private_class;
extern struct private_class * new_private_class();
extern int ret_a_value(struct private_class *, int a, int b);
extern void delete_private_class(struct private_class *);
void (*late_bind_function)(struct private_class *p);

//private_class.c
struct inherited_class_1;
struct inherited_class_2;

struct private_class {
  int a;
  int b;
  struct inherited_class_1 *p1;
  struct inherited_class_2 *p2;
};

struct inherited_class_1 * new_inherited_class_1();
struct inherited_class_2 * new_inherited_class_2();

struct private_class * new_private_class() {
  struct private_class *p;
  p = (struct private_class*) malloc(sizeof(struct private_class));
  p->a = 0;
  p->b = 0;
  p->p1 = new_inherited_class_1();
  p->p2 = new_inherited_class_2();
  return p;
}

    int ret_a_value(struct private_class *p, int a, int b) {
      return p->a + p->b + a + b;
    }

    void delete_private_class(struct private_class *p) {
      //release any resources
      //call delete methods for inherited classes
      free(p);
    }
    //main.c
    struct private_class *p;
    p = new_private_class();
    late_bind_function = &implementation_function;
    delete_private_class(p);

kompilasi dengan c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Jadi sarannya adalah tetap berpegang pada gaya C murni dan tidak mencoba memaksakan gaya C ++. Juga cara ini cocok untuk cara yang sangat bersih untuk membangun API.

pengguna2074102
sumber
Untuk pewarisan biasanya kelas dasar atau struktur instance tertanam dalam yang diturunkan, tidak dialokasikan secara terpisah dan disebut menggunakan pointer. Dengan cara itu, pangkalan paling atas selalu berada di awal setiap struktur tipe turunannya, sehingga mereka dapat saling bertukar dengan mudah, yang tidak dapat Anda lakukan dengan pointer yang mungkin ada pada offset apa pun.
underscore_d
2

Lihat http://slkpg.byethost7.com/instance.html untuk twist lain pada OOP di C. Ini menekankan contoh data untuk reentrancy menggunakan hanya asli C. Banyak warisan dilakukan secara manual menggunakan pembungkus fungsi. Keamanan jenis dipertahankan. Ini adalah contoh kecil:

typedef struct _peeker
{
    log_t     *log;
    symbols_t *sym;
    scanner_t  scan;            // inherited instance
    peek_t     pk;
    int        trace;

    void    (*push) ( SELF *d, symbol_t *symbol );
    short   (*peek) ( SELF *d, int level );
    short   (*get)  ( SELF *d );
    int     (*get_line_number) ( SELF *d );

} peeker_t, SlkToken;

#define push(self,a)            (*self).push(self, a)
#define peek(self,a)            (*self).peek(self, a)
#define get(self)               (*self).get(self)
#define get_line_number(self)   (*self).get_line_number(self)

INSTANCE_METHOD
int
(get_line_number) ( peeker_t *d )
{
    return  d->scan.line_number;
}

PUBLIC
void
InitializePeeker ( peeker_t  *peeker,
                   int        trace,
                   symbols_t *symbols,
                   log_t     *log,
                   list_t    *list )
{
    InitializeScanner ( &peeker->scan, trace, symbols, log, list );
    peeker->log = log;
    peeker->sym = symbols;
    peeker->pk.current = peeker->pk.buffer;
    peeker->pk.count = 0;
    peeker->trace = trace;

    peeker->get_line_number = get_line_number;
    peeker->push = push;
    peeker->get = get;
    peeker->peek = peek;
}
slkpg
sumber
2

Saya agak terlambat ke pesta, tapi saya ingin berbagi pengalaman saya tentang topik: Saya bekerja dengan hal-hal yang disematkan hari ini, dan satu-satunya kompiler (dapat diandalkan) yang saya miliki adalah C, sehingga saya ingin menerapkan berorientasi objek pendekatan dalam proyek tertanam saya ditulis dalam C.

Sebagian besar solusi yang saya lihat sejauh ini banyak menggunakan typecast, jadi kami kehilangan keamanan jenis: kompiler tidak akan membantu Anda jika Anda melakukan kesalahan. Ini benar-benar tidak dapat diterima.

Persyaratan yang saya miliki:

  • Hindari typecast sebanyak mungkin, jadi kami tidak kehilangan keamanan jenis;
  • Polimorfisme: kita harus dapat menggunakan metode virtual, dan pengguna kelas tidak boleh sadar apakah beberapa metode tertentu adalah virtual atau tidak;
  • Multiple inheritance: Saya tidak sering menggunakannya, tetapi kadang-kadang saya benar-benar ingin beberapa kelas untuk mengimplementasikan beberapa antarmuka (atau untuk memperpanjang beberapa superclasses).

Saya telah menjelaskan pendekatan saya secara rinci dalam artikel ini: Pemrograman berorientasi objek dalam C ; ditambah, ada utilitas untuk autogenerasi kode boilerplate untuk kelas dasar dan turunan.

Dmitry Frank
sumber
2

Saya membangun perpustakaan kecil tempat saya mencobanya dan bagi saya itu berfungsi dengan baik. Jadi saya pikir saya berbagi pengalaman.

https://github.com/thomasfuhringer/oxygen

Warisan tunggal dapat diimplementasikan dengan cukup mudah menggunakan struct dan memperluasnya untuk setiap kelas anak lainnya. Gips sederhana ke struktur induk memungkinkan untuk menggunakan metode induk pada semua keturunan. Selama Anda tahu bahwa sebuah variabel menunjuk ke sebuah struct yang memegang objek semacam ini, Anda selalu dapat melakukan cast ke kelas root dan melakukan introspeksi.

Seperti yang telah disebutkan, metode virtual agak rumit. Tapi itu bisa dilakukan. Untuk menjaga hal-hal sederhana, saya hanya menggunakan array fungsi dalam struktur deskripsi kelas yang setiap kelas anak menyalin dan mengisi kembali slot individu di mana diperlukan.

Berbagai warisan akan agak rumit untuk diterapkan dan disertai dengan dampak kinerja yang signifikan. Jadi saya tinggalkan saja. Saya menganggap itu diinginkan dan berguna dalam beberapa kasus untuk memodelkan keadaan kehidupan nyata dengan bersih, tetapi mungkin 90% dari kasus warisan tunggal mencakup kebutuhan. Dan warisan tunggal itu sederhana dan tidak ada biaya.

Juga saya tidak peduli tentang keamanan tipe. Saya pikir Anda tidak harus bergantung pada kompiler untuk mencegah Anda dari kesalahan pemrograman. Dan itu melindungi Anda hanya dari sebagian kecil kesalahan saja.

Biasanya, dalam lingkungan berorientasi objek Anda juga ingin menerapkan penghitungan referensi untuk mengotomatisasi manajemen memori sejauh mungkin. Jadi saya juga memasukkan jumlah referensi ke kelas root "Object" dan beberapa fungsionalitas untuk merangkum alokasi dan deallokasi memori heap.

Itu semua sangat sederhana dan ramping dan memberi saya esensi OO tanpa memaksa saya untuk berurusan dengan monster yang C ++. Dan saya mempertahankan fleksibilitas untuk tinggal di tanah C, yang antara lain membuatnya lebih mudah untuk mengintegrasikan perpustakaan pihak ketiga.

Thomas F.
sumber
1

Saya mengusulkan untuk menggunakan Objective-C, yang merupakan superset dari C.

Sementara Objective-C berusia 30 tahun, memungkinkan untuk menulis kode yang elegan.

http://en.wikipedia.org/wiki/Objective-C

SteAp
sumber
Dalam hal ini saya akan merekomendasikan C ++ sebagai gantinya karena sebenarnya berorientasi objek ...
yyny
Ini bukan jawaban. Tapi bagaimanapun, @YoYoYonnY: Saya tidak menggunakan Objective-C dan menggunakan C ++, tetapi komentar seperti itu tidak ada gunanya tanpa dasar, dan Anda tidak memberikan apa pun. Mengapa Anda mengklaim Objective-C gagal menjadi "sebenarnya berorientasi objek ..."? Dan mengapa C ++ berhasil ketika Objective-C gagal? Yang lucu adalah bahwa Objective-C, well, secara harfiah memiliki kata Object dalam namanya, sedangkan C ++ memasarkan dirinya sebagai bahasa multi-paradigma, bukan OOP (yaitu bukan terutama OOP, & dalam beberapa pandangan orang yang agak ekstrim bukan OOP sama sekali) ... jadi apakah Anda yakin Anda tidak mendapatkan nama-nama itu secara salah?
underscore_d
0

Ya, tapi saya belum pernah melihat orang yang mencoba menerapkan polimorfisme apa pun dengan C.

Paul Morel
sumber
6
Anda perlu melihat-lihat lebih banyak :) Sebagai contoh, Microsoft Direct X memiliki antarmuka C polimorfik.
AShelly
8
Lihatlah implementasi kernel linux misalnya. Ini adalah praktik yang sangat umum dan banyak digunakan dalam C.
Ilya
3
glib juga polimorfik, atau dapat digunakan dengan cara yang memungkinkan polimorfisme (seperti C ++ Anda harus secara eksplisit mengatakan panggilan mana yang virtual)
Spudd86
1
Polimorfisme tidak begitu langka di C, di sisi lain multiple inheritance adalah.
Johan Bjäreholt