Làm thế nào một người sẽ viết mã hướng đối tượng trong C? [đóng cửa]


500

Một số cách để viết mã hướng đối tượng trong C là gì? Đặc biệt là liên quan đến đa hình.


Xem thêm Stack Overflow câu hỏi này đối tượng định hướng trong C .


1
<a href=" ldeniau.web.cern.ch/ldeniau/html/oopc.html"> Lập trình hướng đối tượng trong C </a> của Laurent Deniau


25
@Camilo Martin: Tôi cố tình hỏi có thể không nên . Tôi không thực sự quan tâm đến việc sử dụng OOP trong C. Tuy nhiên, bằng cách xem các giải pháp OO trong C, tôi / chúng tôi đứng để tìm hiểu thêm về các giới hạn và / hoặc tính linh hoạt của C và cả về các cách sáng tạo để thực hiện và sử dụng đa hình.
Dinah

5
OO chỉ là một mô hình. Kiểm tra ở đây, nó thậm chí có thể được thực hiện trong .bat Tệp: dirk.rave.org/chap9.txt (bất kỳ mẫu nào cũng có thể được áp dụng cho bất kỳ ngôn ngữ lập trình nào nếu bạn đủ quan tâm, tôi nghĩ vậy). Đây là thực phẩm tốt cho suy nghĩ, mặc dù. Và có lẽ nhiều điều có thể học được từ việc áp dụng các mô hình như vậy mà chúng ta đã được cấp cho các ngôn ngữ không có chúng.
Camilo Martin

6
GTK - 'lừa tôi, GObject - thực sự là một ví dụ khá hay về OOP (sorta) trong C. Vì vậy, để trả lời @Camilo, cho phép nội suy C.
new123456

Câu trả lời:


362

Đúng. Trên thực tế, Axel Schreiner cung cấp miễn phí cuốn sách "Lập trình hướng đối tượng trong ANSI-C", bao quát chủ đề khá kỹ lưỡng.


28
Mặc dù các khái niệm trong cuốn sách này là chất rắn, bạn sẽ mất an toàn loại.
diapir

22
Trước những gì chúng ta gọi là mẫu thiết kế, là mẫu thiết kế được gọi là "hướng đối tượng"; tương tự với bộ sưu tập rác, và khác như vậy. Bây giờ chúng đã ăn sâu đến mức, chúng ta có xu hướng quên đi, khi chúng lần đầu tiên được nghĩ ra, nó cũng giống như những gì chúng ta nghĩ về các mẫu thiết kế ngày nay
Dexygen

11
Bạn có thể lấy nó trực tiếp từ trang của tác giả: cs.rit.edu/~ats/books/ooc.pdf các giấy tờ khác từ cùng tác giả: cs.rit.edu/~ats/books/index.html
pakman

10
Bộ sưu tập thích hợp (Sách + ví dụ mã nguồn) có sẵn từ chỉ mục rit.edu Lập trình hướng đối tượng này với ANSI-C
David C. Rankin

3
Là cuốn sách này được đánh giá ngang hàng? Có một lỗi đánh máy trong câu đầu tiên của đoạn đầu tiên của trang đầu tiên.
Dagrooms

343

Vì bạn đang nói về đa hình nên có, bạn có thể, chúng tôi đã thực hiện loại công cụ đó nhiều năm trước khi C ++ ra đời.

Về cơ bản, bạn sử dụng a structđể giữ cả dữ liệu và danh sách các con trỏ hàm để trỏ đến các hàm có liên quan cho dữ liệu đó.

Vì vậy, trong một lớp truyền thông, bạn sẽ có một cuộc gọi mở, đọc, viết và đóng sẽ được duy trì như bốn con trỏ hàm trong cấu trúc, cùng với dữ liệu cho một đối tượng, đại loại như:

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;

Tất nhiên, các phân đoạn mã ở trên thực sự sẽ nằm trong một "hàm tạo" như rs232Init().

Khi bạn 'kế thừa' từ lớp đó, bạn chỉ cần thay đổi các con trỏ để trỏ đến các chức năng của riêng bạn. Mọi người gọi các hàm đó sẽ thực hiện điều đó thông qua các con trỏ hàm, cung cấp cho bạn tính đa hình của bạn:

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

Sắp xếp giống như một vtable thủ công.

Bạn thậm chí có thể có các lớp ảo bằng cách đặt con trỏ thành NULL - hành vi sẽ hơi khác so với C ++ (kết xuất lõi trong thời gian chạy thay vì lỗi khi biên dịch).

Đây là một đoạn mã mẫu chứng minh điều đó. Đầu tiên cấu trúc lớp cấp cao nhất:

#include <stdio.h>

// The top-level class.

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

Sau đó, chúng ta có các chức năng cho 'lớp con' TCP:

// 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;
}

Và HTTP cũng vậy:

// 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;
}

Và cuối cùng là một chương trình thử nghiệm để hiển thị nó trong hành động:

// 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;
}

Điều này tạo ra đầu ra:

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

vì vậy bạn có thể thấy rằng các hàm khác nhau đang được gọi, tùy thuộc vào lớp con.


52
Đóng gói là khá dễ dàng, đa hình là có thể thực hiện được - nhưng sự kế thừa là khó khăn
Martin Beckett

5
lwn.net gần đây đã xuất bản một bài viết có tiêu đề Các mẫu thiết kế hướng đối tượng trong nhân về chủ đề của các quy tắc tương tự như câu trả lời ở trên - đó là một cấu trúc chứa các con trỏ hàm hoặc một con trỏ tới một cấu trúc có các hàm đưa con trỏ tới cấu trúc với dữ liệu chúng ta đang làm việc với tư cách là một tham số.
triệt để

11
+1 ví dụ hay! Mặc dù nếu bất kỳ ai thực sự muốn đi theo con đường này, sẽ phù hợp hơn với các cấu trúc "thể hiện" để có một trường duy nhất trỏ đến thể hiện "bảng ảo" của chúng, chứa tất cả các hàm ảo cho loại đó tại một nơi. Tức là bạn tCommClasssẽ được đổi tên thành tCommVTvà một tCommClasscấu trúc sẽ chỉ có các trường dữ liệu và một tCommVT vttrường duy nhất trỏ đến bảng ảo "một và duy nhất". Mang theo tất cả các con trỏ xung quanh với mỗi phiên bản sẽ thêm chi phí không cần thiết và giống với cách bạn sẽ làm công cụ trong JavaScript hơn C ++, IMHO.
Groo

1
Vì vậy, điều này cho thấy việc thực hiện một giao diện duy nhất, nhưng whatabout thực hiện nhiều giao diện? Hoặc thừa kế nhiều?
weberc2

Weber, nếu bạn muốn tất cả các chức năng của C ++, có lẽ bạn nên sử dụng C ++. Câu hỏi được hỏi cụ thể về tính đa hình, khả năng của các đối tượng có một "hình thức" khác nhau. Bạn chắc chắn có thể thực hiện các giao diện và nhiều kế thừa trong C nhưng đó là một công việc bổ sung và bạn phải tự mình quản lý thông minh hơn là sử dụng công cụ tích hợp C ++.
paxdiablo

86

Không gian tên thường được thực hiện bằng cách làm:

stack_push(thing *)

thay vì

stack::push(thing *)

Để tạo cấu trúc C thành một thứ giống như lớp C ++, bạn có thể biến:

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

Vào

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

Và làm:

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
}

Tôi đã không làm hàm hủy hoặc xóa, nhưng nó theo cùng một mẫu.

this_is_here_as_an_example_only giống như một biến lớp tĩnh - được chia sẻ giữa tất cả các thể hiện của một loại. Tất cả các phương thức đều thực sự tĩnh, ngoại trừ một số phương thức này *


1
@nargetoose - st->my_type->push(st, thing2);thay vìst->my_type.push(st, thing2);
Fabricio

@nargetoose: HOẶC struct stack_type my_type; thay vìstruct stack_type * my_type;
Fabricio

3
Tôi thích khái niệm có một cấu trúc cho lớp. Nhưng làm thế nào về một Classcấu trúc chung ? Điều đó sẽ làm cho OO C hơn năng động hơn so với C ++. Thế còn cái đó? Nhân tiện, +1.
Linuxios

54

Tôi tin rằng bên cạnh việc hữu ích theo cách riêng của mình, thực hiện OOP trong C là một cách tuyệt vời để học OOP và hiểu hoạt động bên trong của nó. Kinh nghiệm của nhiều lập trình viên đã chỉ ra rằng để sử dụng một kỹ thuật hiệu quả và tự tin, một lập trình viên phải hiểu làm thế nào các khái niệm cơ bản được thực hiện cuối cùng. Các lớp mô phỏng, kế thừa và đa hình trong C chỉ dạy điều này.

Để trả lời câu hỏi ban đầu, đây là một số tài nguyên hướng dẫn cách thực hiện OOP trong C:

Bài đăng trên blog của EmbeddedGurus.com "Lập trình dựa trên đối tượng trong C" cho thấy cách triển khai các lớp và kế thừa đơn trong C di động: http://embeddedgurus.com/state-space/2008/01/object-basing-programming-in-c /

Lưu ý về ứng dụng "" C + "- Lập trình hướng đối tượng trong C" cho thấy cách triển khai các lớp, kế thừa đơn và liên kết muộn (đa hình) trong C bằng cách sử dụng macro tiền xử lý: http://www.state-machine.com/resource/cplus_3. 0_manual.pdf , mã ví dụ có sẵn từ http://www.state-machine.com/resource/cplus_3.0.zip


4
Url mới cho hướng dẫn sử dụng C +: state-machine.com/doc/cplus_3.0_manual.pdf
Liang

32

Tôi đã thấy nó được thực hiện. Tôi sẽ không đề nghị nó. C ++ ban đầu bắt đầu theo cách này như một bộ tiền xử lý tạo ra mã C như một bước trung gian.

Về cơ bản những gì bạn kết thúc là tạo một bảng công văn cho tất cả các phương thức của bạn nơi bạn lưu trữ các tham chiếu hàm của bạn. Việc tạo ra một lớp sẽ đòi hỏi phải sao chép bảng công văn này và thay thế các mục mà bạn muốn ghi đè, với "phương thức" mới của bạn phải gọi phương thức ban đầu nếu nó muốn gọi phương thức cơ sở. Cuối cùng, bạn kết thúc việc viết lại C ++.


5
"Cuối cùng, bạn kết thúc việc viết lại C ++" Tôi tự hỏi nếu / sợ đó là trường hợp.
Dinah

39
Hoặc, bạn có thể sẽ viết lại Mục tiêu C, đó sẽ là một kết quả hấp dẫn hơn nhiều.
Hợp đồng của Giáo sư Falken vi phạm

3
Có hương vị không có lớp của OOP, chẳng hạn như trong Javascript , nơi đạo sư nói: "Chúng tôi không cần các lớp để tạo ra nhiều đối tượng tương tự." Nhưng tôi sợ rằng điều này không dễ để đạt được trong C. Tuy nhiên, không phải ở một vị trí để nói. (Có thói quen nhân bản () để sao chép một cấu trúc không?)
Lumi

1
Một kẻ thông minh khác, người đã thực sự phải thực hiện điều đó và làm cho việc triển khai đó nhanh chóng (Google, động cơ V8) đã thực hiện mọi thứ để thêm các lớp (ẩn) vào JavaScript.
cubuspl42

Không glibđược viết bằng C theo cách khách quan?
kravemir

26

Chắc chắn điều đó là có thể. Đây là những gì GObject , khuôn khổ mà tất cả GTK +Gnome dựa trên, thực hiện.


Ưu / nhược điểm của phương pháp này là gì? I E. Nó dễ dàng hơn nhiều để viết nó bằng C ++.
kravemir

@kravemir Chà, C ++ không hoàn toàn di động như C và khó hơn một chút để liên kết C ++ với mã có thể được biên dịch bởi trình biên dịch C ++ khác. Nhưng vâng, việc viết các lớp trong C ++ sẽ dễ dàng hơn, mặc dù GObject cũng không thực sự khó khăn (giả sử bạn không bận tâm một chút nồi hơi).
Edwin Buck

17

Thư viện con C stdio FILE là một ví dụ tuyệt vời về cách tạo ra sự trừu tượng hóa, đóng gói và mô đun hóa trong C. không bị biến đổi C.

Kế thừa và đa hình - các khía cạnh khác thường được coi là thiết yếu đối với OOP - không nhất thiết cung cấp mức tăng năng suất mà họ hứa và các lẽ hợp lý đã được đưa ra rằng chúng thực sự có thể cản trở sự phát triển và suy nghĩ về miền vấn đề.


Không phải stdio được trừu tượng hóa trên lớp kernel? Nếu tôi không nhầm, thư viện C coi chúng là tệp / thiết bị ký tự và trình điều khiển kernel thực hiện công việc, ...
kravemir

15

Ví dụ tầm thường với Động vật và Chó: Bạn phản chiếu cơ chế vtable của C ++ (dù sao đi nữa). Bạn cũng tách biệt phân bổ và khởi tạo (Animal_Alloc, Animal_New) để chúng tôi không gọi malloc () nhiều lần. Chúng ta cũng phải vượt qua một cách rõ ràngthis con trỏ .

Nếu bạn đã làm các chức năng không ảo, đó là trival. Bạn chỉ không thêm chúng vào các hàm vtable và tĩnh không yêu cầuthis con trỏ. Nhiều kế thừa thường yêu cầu nhiều vtables để giải quyết sự mơ hồ.

Ngoài ra, bạn sẽ có thể sử dụng setjmp / longjmp để xử lý ngoại lệ.

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);
}

Tái bút Điều này đã được thử nghiệm trên trình biên dịch C ++, nhưng nó sẽ dễ dàng làm cho nó hoạt động trên trình biên dịch C.


typedefbên trong a structlà không thể có trong C.
masoud

13

Kiểm tra GObject . Nó có nghĩa là OO trong C và là một triển khai những gì bạn đang tìm kiếm. Nếu bạn thực sự muốn OO, hãy sử dụng C ++ hoặc một số ngôn ngữ OOP khác. GObject có thể rất khó để làm việc đôi khi nếu bạn quen xử lý các ngôn ngữ OO, nhưng cũng giống như mọi thứ, bạn sẽ quen với các quy ước và dòng chảy.


12

Điều này đã được thú vị để đọc. Bản thân tôi đã suy nghĩ về câu hỏi tương tự và lợi ích của việc suy nghĩ về nó là:

  • Cố gắng tưởng tượng cách triển khai các khái niệm OOP bằng ngôn ngữ không phải OOP giúp tôi hiểu được các điểm mạnh của ngôn ngữ OOp (trong trường hợp của tôi, C ++). Điều này giúp tôi đánh giá tốt hơn về việc nên sử dụng C hoặc C ++ cho một loại ứng dụng nhất định - trong đó lợi ích của loại này vượt trội so với loại kia.

  • Trong quá trình duyệt web để biết thông tin và ý kiến ​​về vấn đề này, tôi đã tìm thấy một tác giả viết mã cho bộ xử lý nhúng và chỉ có trình biên dịch C có sẵn: http://www.eetimes.com/discussion/other/4024626/Object-Orients -C-Creation-Foundation-Classes-Part-1

Trong trường hợp của ông, phân tích và điều chỉnh các khái niệm OOP ở đồng bằng C là một sự theo đuổi hợp lệ. Có vẻ như anh ta đã sẵn sàng hy sinh một số khái niệm OOP do cú đánh trên đầu hiệu suất do cố gắng thực hiện chúng trong C.

Bài học tôi đã học là, vâng, nó có thể được thực hiện ở một mức độ nhất định, và vâng, có một số lý do tốt để thử nó.

Cuối cùng, máy đang xoay các bit con trỏ ngăn xếp, làm cho bộ đếm chương trình nhảy xung quanh và tính toán các hoạt động truy cập bộ nhớ. Từ quan điểm hiệu quả, chương trình của bạn càng ít tính toán thì càng tốt ... nhưng đôi khi chúng tôi phải trả thuế này một cách đơn giản để chúng tôi có thể tổ chức chương trình của mình theo cách dễ bị lỗi nhất của con người. Trình biên dịch ngôn ngữ OOP cố gắng tối ưu hóa cả hai khía cạnh. Lập trình viên phải cẩn thận hơn nhiều khi thực hiện các khái niệm này bằng một ngôn ngữ như C.


10

Bạn có thể thấy hữu ích khi xem tài liệu của Apple về bộ API Core Foundation của mình. Nó là một API C thuần túy, nhưng nhiều loại được kết nối với các đối tượng Objective-C.

Bạn cũng có thể thấy hữu ích khi xem xét thiết kế của chính Objective-C. Nó khác một chút so với C ++ ở chỗ hệ thống đối tượng được định nghĩa theo các hàm C, ví dụ như objc_msg_sendgọi một phương thức trên một đối tượng. Trình biên dịch dịch cú pháp dấu ngoặc vuông thành các lệnh gọi hàm đó, vì vậy bạn không cần phải biết nó, nhưng xem xét câu hỏi của bạn, bạn có thể thấy nó hữu ích để tìm hiểu cách nó hoạt động dưới mui xe.


10

Có một số kỹ thuật có thể được sử dụng. Điều quan trọng nhất là làm thế nào để phân chia dự án. Chúng tôi sử dụng một giao diện trong dự án của chúng tôi được khai báo trong tệp .h và việc thực hiện đối tượng trong tệp .c. Phần quan trọng là tất cả các mô-đun bao gồm tệp .h chỉ xem một đối tượng là mộtvoid * và tệp .c là mô-đun duy nhất biết các phần bên trong của cấu trúc.

Một cái gì đó như thế này cho một lớp, chúng tôi đặt tên FOO là một ví dụ:

Trong tệp .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

Các tập tin thực hiện C sẽ là một cái gì đó như thế.

#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;
}

Vì vậy, tôi đưa con trỏ rõ ràng cho một đối tượng cho mọi chức năng của mô-đun đó. Một trình biên dịch C ++ thực hiện nó một cách ngầm định, và trong C, chúng tôi viết nó ra một cách rõ ràng.

Tôi thực sự sử dụng this trong các chương trình của mình, để đảm bảo rằng chương trình của tôi không biên dịch trong C ++ và nó có đặc tính tốt là nằm trong một màu khác trong trình soạn thảo tô sáng cú pháp của tôi.

Các trường của FOO_struct có thể được sửa đổi trong một mô-đun và một mô-đun khác thậm chí không cần phải biên dịch lại để vẫn có thể sử dụng được.

Với phong cách đó, tôi đã xử lý một phần lớn các lợi thế của OOP (đóng gói dữ liệu). Bằng cách sử dụng các con trỏ hàm, thậm chí dễ dàng thực hiện một cái gì đó như thừa kế, nhưng thành thật mà nói, nó thực sự chỉ hiếm khi hữu ích.


6
Nếu bạn làm typedef struct FOO_type FOO_typethay vì một typedef để bỏ trống trong tiêu đề, bạn sẽ nhận được lợi ích bổ sung của việc kiểm tra loại, trong khi vẫn không làm lộ cấu trúc của bạn.
Scott Wales

8

Bạn có thể giả mạo nó bằng cách sử dụng các hàm con trỏ hàm và trên thực tế, tôi nghĩ rằng về mặt lý thuyết có thể biên dịch các chương trình C ++ thành C.

Tuy nhiên, hiếm khi có ý nghĩa để buộc một mô hình trên một ngôn ngữ hơn là chọn một ngôn ngữ sử dụng một mô hình.


5
Trình biên dịch C ++ đầu tiên đã thực hiện chính xác điều đó - nó đã chuyển đổi mã C ++ thành mã C tương đương (nhưng xấu và không thể đọc được), sau đó được biên dịch bởi trình biên dịch C.
Adam Rosenfield

2
EDG, Cfront và một số người khác vẫn có khả năng làm điều này. Với một lý do rất chính đáng: không phải mọi nền tảng đều có trình biên dịch C ++.
Jasper Bekkers

Vì một số lý do, tôi nghĩ rằng C-front chỉ hỗ trợ một số phần mở rộng C ++ nhất định (ví dụ: tài liệu tham khảo) chứ không phải mô phỏng OOP / công văn động đầy đủ.
Uri

2
Bạn cũng có thể làm điều tương tự với LLVM và C phụ trợ.
Zifre

7

Hướng đối tượng C, có thể được thực hiện, tôi đã thấy loại mã đó trong sản xuất tại Hàn Quốc và đó là con quái vật khủng khiếp nhất tôi từng thấy trong nhiều năm (giống như năm ngoái (2007) mà tôi đã thấy mã). Vì vậy, có nó có thể được thực hiện, và có người đã làm nó trước đây, và vẫn làm nó ngay cả trong thời đại ngày nay. Nhưng tôi muốn giới thiệu C ++ hoặc Objective-C, cả hai đều là ngôn ngữ được sinh ra từ C, với mục đích cung cấp hướng đối tượng với các mô hình khác nhau.


3
nếu Linus thấy bình luận của bạn. Anh ấy chắc chắn sẽ cười hoặc nguyền rủa bạn
Anders Lind

7

Nếu bạn tin rằng một cách tiếp cận OOP là tốt hơn cho vấn đề bạn đang cố gắng giải quyết, tại sao bạn lại cố gắng giải quyết nó bằng ngôn ngữ không phải OOP? Có vẻ như bạn đang sử dụng công cụ sai cho công việc. Sử dụng C ++ hoặc một số ngôn ngữ biến thể C hướng đối tượng khác.

Nếu bạn đang hỏi bởi vì bạn đang bắt đầu viết mã cho một dự án lớn đã có sẵn bằng C, thì bạn không nên cố gắng áp dụng mô hình OOP của riêng bạn (hoặc của bất kỳ ai khác) vào cơ sở hạ tầng của dự án. Thực hiện theo các hướng dẫn đã có trong dự án. Nói chung, các API sạch và các thư viện và mô-đun bị cô lập sẽ đi một chặng đường dài hướng tới việc có một thiết kế OOP- ish sạch .

Nếu, sau tất cả những điều này, bạn thực sự bắt đầu thực hiện OOP C, hãy đọc (PDF).


36
Không thực sự trả lời câu hỏi ...
Brian Postow

2
@Brian, liên kết đến PDF sẽ xuất hiện để trả lời câu hỏi trực tiếp, mặc dù tôi không có thời gian để tự kiểm tra.
Đánh dấu tiền chuộc

5
Liên kết đến PDF dường như là toàn bộ sách giáo khoa về chủ đề này ... Một bằng chứng đẹp, nhưng nó không phù hợp với lề ...
Brian Postow

5
vâng, trả lời câu hỏi hoàn toàn hợp lệ khi hỏi cách sử dụng ngôn ngữ theo một cách cụ thể. không có yêu cầu cho ý kiến ​​về các ngôn ngữ khác ....
Tim Ring

9
@Brian & Tim Ring: Câu hỏi yêu cầu giới thiệu sách về một chủ đề; Tôi đã cho anh ta một liên kết đến một cuốn sách đề cập cụ thể đến chủ đề này. Tôi cũng đưa ra ý kiến ​​của mình về lý do tại sao cách tiếp cận vấn đề có thể không tối ưu (mà tôi nghĩ nhiều người ở đây dường như đồng ý, dựa trên phiếu bầu và các nhận xét / câu trả lời khác). Bạn có bất cứ đề nghị để cải thiện câu trả lời của tôi?
RarrRarrRarr

6

Vâng, bạn có thể. Mọi người đã viết C hướng đối tượng trước C ++ hoặc Objective-C xuất hiện. Cả C ++ và Objective-C, trong các phần, đều cố gắng lấy một số khái niệm OO được sử dụng trong C và chính thức hóa chúng như một phần của ngôn ngữ.

Đây là một chương trình thực sự đơn giản chỉ ra cách bạn có thể tạo ra thứ gì đó trông giống như / là một cuộc gọi phương thức (có nhiều cách tốt hơn để làm điều này. Đây chỉ là bằng chứng ngôn ngữ hỗ trợ các khái niệm):

#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;
}

6

Tất nhiên, nó sẽ không đẹp bằng việc sử dụng một ngôn ngữ có hỗ trợ tích hợp. Tôi thậm chí đã viết "trình biên dịch hướng đối tượng".


6

Một ít mã OOC để thêm:

#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;
}

5

Tôi đã đào cái này được một năm:

Vì hệ thống GObject khó sử dụng với C thuần túy, tôi đã cố gắng viết một số macro đẹp để giảm bớt kiểu OO bằng 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;
}

Đây là trang web dự án của tôi (tôi không có đủ thời gian để viết en. Doc, tuy nhiên tài liệu bằng tiếng Trung tốt hơn nhiều).

OOC-GCC


các lớp tĩnh ASM MỚI DELETE ST ... là macro trong OOC-GCC
dameng


4

Những bài báo hoặc sách nào là tốt để sử dụng các khái niệm OOP trong C?

Giao diện và triển khai C của Dave Hanson là tuyệt vời về đóng gói và đặt tên và rất tốt trong việc sử dụng các con trỏ hàm. Dave không cố gắng mô phỏng sự kế thừa.


4

OOP chỉ là một mô hình đặt dữ liệu quan trọng hơn mã trong các chương trình. OOP không phải là một ngôn ngữ. Vì vậy, giống như plain C là một ngôn ngữ đơn giản, OOP trong plain C cũng đơn giản.


3
Nói tốt, nhưng điều này nên được bình luận.
pqsk

4

Một điều bạn có thể muốn làm là xem xét việc triển khai bộ công cụ Xt cho X Window . Chắc chắn rằng nó sẽ mọc dài trong răng, nhưng nhiều cấu trúc được sử dụng được thiết kế để hoạt động theo kiểu OO theo truyền thống C. Nói chung, điều này có nghĩa là thêm một lớp bổ sung ở đây và ở đó và thiết kế các cấu trúc để đặt lên nhau.

Bạn thực sự có thể làm rất nhiều theo cách của OO nằm trong C theo cách này, mặc dù đôi khi cảm thấy như vậy, các khái niệm OO không hình thành hoàn toàn từ tâm trí #include<favorite_OO_Guru.h>. Họ thực sự cấu thành nhiều thực tiễn tốt nhất được thiết lập thời đó. Các ngôn ngữ và hệ thống OO chỉ chưng cất và khuếch đại các phần của zeitgeist lập trình trong ngày.


4

Câu trả lời cho câu hỏi là 'Có, bạn có thể'.

Bộ C hướng đối tượng (OOC) dành cho những người muốn lập trình theo hướng đối tượng, nhưng cũng dính vào C cũ tốt. OOC thực hiện các lớp, kế thừa đơn và đa, xử lý ngoại lệ.

Đặc trưng

• Chỉ sử dụng các macro và chức năng C, không yêu cầu mở rộng ngôn ngữ! (AN-C)

• Mã nguồn dễ đọc cho ứng dụng của bạn. Chăm sóc đã được thực hiện để làm cho mọi thứ đơn giản nhất có thể.

• Kế thừa đơn lớp

• Nhiều kế thừa bởi các giao diện và mixin (kể từ phiên bản 1.3)

• Thực hiện các ngoại lệ (bằng C thuần túy!)

• Hàm ảo cho các lớp

• Công cụ bên ngoài để thực hiện lớp dễ dàng

Để biết thêm chi tiết, hãy truy cập http://ooc-coding.sourceforge.net/ .


4

Có vẻ như mọi người đang cố gắng mô phỏng phong cách C ++ bằng C. Tôi nghĩ rằng việc lập trình hướng đối tượng C thực sự là lập trình hướng cấu trúc. Tuy nhiên, bạn có thể đạt được những thứ như ràng buộc muộn, đóng gói và kế thừa. Đối với thừa kế, bạn xác định rõ ràng một con trỏ tới các cấu trúc cơ sở trong cấu trúc con của bạn và đây rõ ràng là một hình thức của nhiều kế thừa. Bạn cũng cần xác định xem

//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);

biên dịch với c_compiler main.c inherited_class_1.obj inherited_class_2.obj private_class.obj.

Vì vậy, lời khuyên là hãy gắn bó với một phong cách C thuần túy và không cố gắng ép buộc vào một phong cách C ++. Ngoài ra, cách này cho vay một cách rất rõ ràng để xây dựng API.


Đối với kế thừa thông thường, lớp cơ sở hoặc cấu trúc thể hiện được nhúng trong lớp dẫn xuất, không được phân bổ riêng và được gọi bằng con trỏ. Bằng cách đó, cơ sở trên cùng luôn luôn là khởi đầu của bất kỳ cấu trúc loại dẫn xuất nào của nó, vì vậy chúng có thể được truyền sang nhau một cách dễ dàng, điều mà bạn không thể làm với các con trỏ có thể ở bất kỳ điểm bù nào.
gạch dưới

2

Xem http://slkpg.byethost7.com/instance.html để biết thêm một sự thay đổi khác đối với OOP trong C. Nó nhấn mạnh dữ liệu cá thể cho reentrancy chỉ sử dụng nguồn gốc C. Nhiều kế thừa được thực hiện thủ công bằng cách sử dụng trình bao bọc hàm. Loại an toàn được duy trì. Đây là một mẫu nhỏ:

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;
}

2

Tôi đến bữa tiệc hơi muộn, nhưng tôi muốn chia sẻ kinh nghiệm của mình về chủ đề này: Tôi làm việc với các công cụ nhúng ngày nay và trình biên dịch (đáng tin cậy) duy nhất tôi có là C, vì vậy tôi muốn áp dụng hướng đối tượng cách tiếp cận trong các dự án nhúng của tôi được viết bằng C.

Hầu hết các giải pháp tôi từng thấy cho đến nay đều sử dụng các kiểu chữ rất nhiều, vì vậy chúng tôi mất an toàn kiểu: trình biên dịch sẽ không giúp bạn nếu bạn mắc lỗi. Điều này là hoàn toàn không thể chấp nhận.

Yêu cầu mà tôi có:

  • Tránh các dự báo càng nhiều càng tốt, vì vậy chúng tôi không mất an toàn kiểu;
  • Đa hình: chúng ta sẽ có thể sử dụng các phương thức ảo và người dùng của lớp không nên biết liệu một số phương thức cụ thể có ảo hay không;
  • Nhiều kế thừa: Tôi không sử dụng nó thường xuyên, nhưng đôi khi tôi thực sự muốn một số lớp thực hiện nhiều giao diện (hoặc mở rộng nhiều siêu lớp).

Tôi đã giải thích cách tiếp cận của tôi một cách chi tiết trong bài viết này: Lập trình hướng đối tượng trong C ; cộng với, có một tiện ích để tự động tạo mã soạn sẵn cho các lớp cơ sở và các lớp dẫn xuất.


2

Tôi đã xây dựng một thư viện nhỏ nơi tôi đã thử nó và với tôi nó hoạt động thực sự độc đáo. Vì vậy, tôi nghĩ rằng tôi chia sẻ kinh nghiệm.

https://github.com/thomasfuhringer / oxygen

Kế thừa đơn có thể được thực hiện khá dễ dàng bằng cách sử dụng một cấu trúc và mở rộng nó cho mọi lớp con khác. Một cấu trúc đơn giản cho cấu trúc cha làm cho nó có thể sử dụng các phương thức cha trên tất cả các hậu duệ. Miễn là bạn biết rằng một biến chỉ đến một cấu trúc chứa loại đối tượng này, bạn luôn có thể chuyển sang lớp gốc và thực hiện nội quan.

Như đã đề cập, các phương thức ảo có phần phức tạp hơn. Nhưng họ có thể làm được. Để giữ cho mọi thứ đơn giản, tôi chỉ cần sử dụng một mảng các hàm trong cấu trúc mô tả lớp mà mỗi lớp con sao chép và sao chép các vị trí riêng lẻ khi cần.

Nhiều kế thừa sẽ khá phức tạp để thực hiện và đi kèm với một tác động hiệu suất đáng kể. Vì vậy, tôi rời khỏi nó. Tôi thực sự coi nó là mong muốn và hữu ích trong một vài trường hợp để mô hình hóa hoàn cảnh thực tế trong cuộc sống, nhưng có lẽ 90% trường hợp thừa kế đơn lẻ đáp ứng nhu cầu. Và thừa kế duy nhất là đơn giản và chi phí không có gì.

Ngoài ra tôi không quan tâm đến loại an toàn. Tôi nghĩ bạn không nên phụ thuộc vào trình biên dịch để ngăn bạn khỏi lỗi lập trình. Và nó chỉ bảo vệ bạn khỏi một phần lỗi nhỏ.

Thông thường, trong một môi trường hướng đối tượng, bạn cũng muốn thực hiện đếm tham chiếu để tự động hóa quản lý bộ nhớ trong phạm vi có thể. Vì vậy, tôi cũng đặt một số tham chiếu vào lớp gốc đối tượng của đối tượng và một số chức năng để đóng gói phân bổ và phân bổ bộ nhớ heap.

Tất cả đều rất đơn giản và gọn gàng và mang lại cho tôi những yếu tố cần thiết của OO mà không buộc tôi phải đối phó với con quái vật đó là C ++. Và tôi giữ được sự linh hoạt khi ở trong đất C, trong số những thứ khác giúp việc tích hợp thư viện của bên thứ ba dễ dàng hơn.


1

Tôi đề xuất sử dụng Objective-C, đó là một superset của C.

Trong khi Objective-C đã 30 tuổi, nó cho phép viết mã thanh lịch.

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


Trong trường hợp đó, tôi muốn giới thiệu C ++ thay vì hướng đối tượng thực sự của nó ...
yyny

Đây không phải là một câu trả lời. Nhưng dù sao đi nữa, @YoYoYonnY: Tôi không sử dụng Objective-C và sử dụng C ++, nhưng những bình luận như thế không có ích gì mà không có cơ sở, và bạn đã không cung cấp. Tại sao bạn tuyên bố Objective-C lại thiếu "thực sự hướng đối tượng ..."? Và tại sao C ++ thành công khi Objective-C thất bại? Điều buồn cười là Objective-C, theo nghĩa đen có từ Object trong tên của nó, trong khi C ++ tiếp thị bản thân như một ngôn ngữ đa mô hình, không phải là một ngôn ngữ OOP (không phải chủ yếu là OOP, và trong một số quan điểm dân gian cực đoan không phải là OOP chút nào) ... vậy bạn có chắc là bạn đã không đặt sai tên đó không?
gạch dưới

0

Có, nhưng tôi chưa bao giờ thấy ai cố gắng thực hiện bất kỳ loại đa hình nào với C.


6
Bạn cần nhìn xung quanh nhiều hơn :) Chẳng hạn, Direct X của Microsoft có giao diện C đa hình.
AShelly

8
Nhìn vào thực thi kernel linux chẳng hạn. Nó là thực tế rất phổ biến và được sử dụng rộng rãi trong C.
Ilya

3
còn glib là đa hình, hoặc có thể được sử dụng theo cách cho phép đa hình (giống như C ++, bạn phải nói rõ ràng cuộc gọi nào là ảo)
Spudd86

1
Đa hình không phải là hiếm ở C, mặt khác, đa thừa kế là.
Johan Bjäreholt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.