Tuyên bố từ chối trách nhiệm: Tôi là một người mới hoàn thành với C, nhưng tôi đã chơi với nó cố gắng bắt chước một số tính năng của các lớp học. Ok, Tôi biết rằng nếu tôi muốn đi theo cách đó tôi nên học C++, nhưng hãy xem xét sau đây một thí nghiệm nhỏ.Spicing C với các lớp học
Schreiner, trong sách Lập trình hướng đối tượng với ANSI-C gợi ý cách sử dụng con trỏ để có các đối tượng hướng đối tượng trong C. Tôi phải thừa nhận rằng tôi chỉ lướt qua cuốn sách, nhưng tôi không thích cách tiếp cận của anh ấy quá nhiều. Về cơ bản, ông sử dụng con trỏ chức năng để sắp xếp mà
func(foo);
thực sự dẫn đến gọi
foo.methods->func();
nơi foo.methods
là một cấu trúc chứa các con trỏ tới các chức năng. Điều tôi không thích trong phương pháp này là người ta phải có chức năng toàn cầu foo
; cảm giác của tôi là điều này sẽ sớm dẫn đến lộn xộn: suy nghĩ hai đối tượng foo
và bar
, cả hai đều có phương thức func
nhưng với số lượng tham số khác nhau.
Vì vậy, tôi đã cố gắng để có được một cái gì đó phù hợp hơn với khẩu vị của tôi. Lần thử đầu tiên là những điều sau (tôi bỏ qua các khai báo vì lợi ích ngắn gọn)
#include <stdio.h>
//Instances of this struct will be my objects
struct foo {
//Properties
int bar;
//Methods
void (* print)(struct foo self);
void (* printSum)(struct foo self, int delta);
};
//Here is the actual implementation of the methods
static void printFoo(struct foo self) {
printf("This is bar: %d\n", self.bar);
}
static void printSumFoo(struct foo self, int delta) {
printf("This is bar plus delta: %d\n", self.bar + delta);
}
//This is a sort of constructor
struct foo Foo(int bar) {
struct foo foo = {
.bar = bar,
.print = &printFoo,
.printSum = &printSumFoo
};
return foo;
}
//Finally, this is how one calls the methods
void
main(void) {
struct foo foo = Foo(14);
foo.print(foo); // This is bar: 14
foo.printSum(foo, 2); // This is bar plus delta: 16
}
Đây là một công việc không thuận tiện. Tuy nhiên, điều tôi không thích là bạn phải thêm chính đối tượng đó làm đối số đầu tiên. Với một số công việc tiền xử lý, tôi có thể làm tốt hơn một chút:
#include <stdio.h>
#define __(stuff) stuff.method(* stuff.object)
//Instances of this struct will be my objects
struct foo {
//Properties
int bar;
//Methods
//Note: these are now struct themselves
//and they contain a pointer the object...
struct {
void (* method)(struct foo self);
struct foo * object;
} print;
};
//Here is the actual implementation of the methods
static void printFoo(struct foo self) {
printf("This is bar: %d\n", self.bar);
}
//This is a sort of constructor
struct foo Foo(int bar) {
struct foo foo = {
.bar = bar,
//...hence initialization is a little bit different
.print = {
.method = &printFoo,
.object = &foo
}
};
return foo;
}
//Finally, this is how one calls the methods
void
main(void) {
struct foo foo = Foo(14);
//This is long and unconvenient...
foo.print.method(* foo.print.object); // This is bar: 14
//...but it can be shortened by the preprocessor
__(foo.print); // This is bar: 14
}
Điều này càng xa càng tốt. Vấn đề ở đây là nó sẽ không làm việc cho các phương thức với các đối số, như các macro tiền xử lý không thể lấy một số lượng các đối số biến. Tất nhiên người ta có thể xác định các macro _0
, _1
và cứ như vậy theo số lượng đối số (cho đến khi một người bị mệt mỏi), nhưng đây không phải là cách tiếp cận tốt.
Có cách nào để cải thiện điều này và để C sử dụng cú pháp hướng đối tượng hơn không?
Tôi nên thêm thực sự Schreiner làm nhiều hơn những gì tôi đã nói trong cuốn sách của mình, nhưng tôi nghĩ rằng việc xây dựng cơ bản không thay đổi.
Tôi sẽ sử dụng một cách tiếp cận vtable cho các chức năng, mà là tương tự như phương pháp thứ hai của bạn, ngoại trừ 'print' sẽ là một con trỏ. – leppie
"cả hai có một phương thức func" ... func là tên của một trường con trỏ tới hàm trong một cấu trúc: không có lý do hàm toàn cầu mà nó trỏ đến phải được gọi đơn giản là "func". Tiền tố/postfix nó với một cái gì đó lớp cụ thể và bạn đã giải quyết vấn đề đó. –
Macro tiền xử lý có thể lấy số lượng đối số thay đổi, tính từ 12 năm trước. –