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 .
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 .
Câu trả lời:
Đú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.
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.
tCommClass
sẽ được đổi tên thành tCommVT
và một tCommClass
cấu trúc sẽ chỉ có các trường dữ liệu và một tCommVT vt
trườ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.
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 *
st->my_type->push(st, thing2);
thay vìst->my_type.push(st, thing2);
struct stack_type my_type;
thay vìstruct stack_type * my_type;
Class
cấ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.
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
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 ++.
glib
được viết bằng C theo cách khách quan?
Chắc chắn điều đó là có thể. Đây là những gì GObject , khuôn khổ mà tất cả GTK + và Gnome dựa trên, thực hiện.
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ý 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 đề.
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.
typedef
bên trong a struct
là không thể có trong C.
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.
Đ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.
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_send
gọ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.
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.
typedef struct FOO_type FOO_type
thay 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.
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.
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.
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 nó (PDF).
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;
}
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;
}
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).
Có một ví dụ về thừa kế sử dụng C trong talk 1996 Jim Larson trao tặng tại Mục 312 Lập trình vào giờ nghỉ trưa Hội thảo ở đây: cao và Low-Level C .
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.
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.
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/ .
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.
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;
}
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ó:
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.
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.
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.
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.