Làm thế nào để bạn thực hiện một lớp trong C? [đóng cửa]


139

Giả sử tôi phải sử dụng C (không có C ++ hoặc trình biên dịch hướng đối tượng) và tôi không có phân bổ bộ nhớ động, một số kỹ thuật tôi có thể sử dụng để triển khai một lớp hoặc xấp xỉ tốt một lớp là gì? Có phải luôn luôn là một ý tưởng tốt để tách "lớp" thành một tệp riêng biệt? Giả sử rằng chúng ta có thể phân bổ bộ nhớ bằng cách giả sử một số lượng phiên bản cố định hoặc thậm chí xác định tham chiếu cho mỗi đối tượng là một hằng số trước khi biên dịch thời gian. Vui lòng đưa ra các giả định về khái niệm OOP nào tôi sẽ cần thực hiện (nó sẽ thay đổi) và đề xuất phương pháp tốt nhất cho mỗi khái niệm.

Những hạn chế:

  • Tôi phải sử dụng C chứ không phải OOP vì tôi đang viết mã cho một hệ thống nhúng, và trình biên dịch và cơ sở mã có sẵn có trong C.
  • Không có phân bổ bộ nhớ động vì chúng tôi không có đủ bộ nhớ để cho rằng chúng tôi sẽ không hết nếu chúng tôi bắt đầu phân bổ động.
  • Trình biên dịch chúng tôi làm việc không có vấn đề với con trỏ hàm

26
Câu hỏi bắt buộc: Bạn có phải viết mã hướng đối tượng không? Nếu bạn làm vì bất cứ lý do gì, điều đó tốt, nhưng bạn sẽ chiến đấu trong một trận chiến khá khó khăn. Có lẽ là tốt nhất nếu bạn tránh cố gắng viết mã hướng đối tượng vào C. Điều đó chắc chắn là có thể - hãy xem câu trả lời tuyệt vời của relax - nhưng nó không chính xác "dễ dàng" và nếu bạn đang làm việc trên một hệ thống nhúng với bộ nhớ hạn chế, thì nó có thể không khả thi Tôi có thể sai, mặc dù - tôi không cố gắng tranh luận với bạn về điều đó, chỉ đưa ra một số điểm phản biện có thể không được trình bày.
Chris Lutz

1
Nói đúng ra, chúng tôi không phải làm thế. Tuy nhiên, sự phức tạp của hệ thống đã làm cho mã không thể nhầm lẫn. Cảm giác của tôi là cách tốt nhất để giảm sự phức tạp là thực hiện một số khái niệm OOP. Cảm ơn tất cả những người trả lời trong vòng 3 phút. Các bạn thật điên rồ và nhanh chóng!
Ben Gartner

8
Đây chỉ là ý kiến ​​khiêm tốn của tôi, nhưng OOP không tạo ra mã có thể duy trì ngay lập tức. Nó có thể làm cho nó dễ quản lý hơn, nhưng không nhất thiết phải dễ bảo trì hơn. Bạn có thể có "không gian tên" trong C (Tiền tố di động Apache có tất cả các ký hiệu toàn cục apr_và GLib tiền tố chúng g_để tạo một không gian tên) và các yếu tố tổ chức khác mà không có OOP. Nếu bạn sắp tái cấu trúc ứng dụng, tôi sẽ xem xét dành thời gian cố gắng đưa ra cấu trúc thủ tục dễ bảo trì hơn.
Chris Lutz

điều này đã được thảo luận không ngừng trước đây - bạn có xem xét bất kỳ câu trả lời nào trước đó không?
Larry Watanabe

Nguồn này, trong một câu trả lời đã bị xóa của tôi, cũng có thể giúp ích: hành tinhpdf.com / codecuts / pdfs / ooc.pdf Nó mô tả một cách tiếp cận hoàn chỉnh để thực hiện OO trong C.
Ruben Steins

Câu trả lời:


86

Điều đó phụ thuộc vào bộ tính năng "hướng đối tượng" chính xác mà bạn muốn có. Nếu bạn cần những thứ như nạp chồng và / hoặc phương thức ảo, có lẽ bạn cần bao gồm các con trỏ hàm trong các cấu trúc:

typedef struct {
  float (*computeArea)(const ShapeClass *shape);
} ShapeClass;

float shape_computeArea(const ShapeClass *shape)
{
  return shape->computeArea(shape);
}

Điều này sẽ cho phép bạn triển khai một lớp, bằng cách "kế thừa" lớp cơ sở và thực hiện một chức năng phù hợp:

typedef struct {
  ShapeClass shape;
  float width, height;
} RectangleClass;

static float rectangle_computeArea(const ShapeClass *shape)
{
  const RectangleClass *rect = (const RectangleClass *) shape;
  return rect->width * rect->height;
}

Điều này tất nhiên đòi hỏi bạn cũng phải thực hiện một hàm tạo, điều đó đảm bảo con trỏ hàm được thiết lập đúng. Thông thường, bạn sẽ tự động phân bổ bộ nhớ cho ví dụ, nhưng bạn cũng có thể để người gọi làm điều đó:

void rectangle_new(RectangleClass *rect)
{
  rect->width = rect->height = 0.f;
  rect->shape.computeArea = rectangle_computeArea;
}

Nếu bạn muốn một số hàm tạo khác nhau, bạn sẽ phải "trang trí" các tên hàm, bạn không thể có nhiều hơn một rectangle_new()hàm:

void rectangle_new_with_lengths(RectangleClass *rect, float width, float height)
{
  rectangle_new(rect);
  rect->width = width;
  rect->height = height;
}

Đây là một ví dụ cơ bản cho thấy việc sử dụng:

int main(void)
{
  RectangleClass r1;

  rectangle_new_with_lengths(&r1, 4.f, 5.f);
  printf("rectangle r1's area is %f units square\n", shape_computeArea(&r1));
  return 0;
}

Tôi hy vọng điều này cung cấp cho bạn một số ý tưởng, ít nhất. Để có khung hướng đối tượng thành công và phong phú trong C, hãy xem thư viện GObject của glib .

Cũng lưu ý rằng không có "lớp" rõ ràng nào được mô hình hóa ở trên, mỗi đối tượng có con trỏ phương thức riêng linh hoạt hơn một chút so với bạn thường thấy trong C ++. Ngoài ra, nó có giá bộ nhớ. Bạn có thể thoát khỏi điều đó bằng cách nhồi các con trỏ phương thức vào một classcấu trúc và phát minh ra một cách để mỗi thể hiện đối tượng tham chiếu một lớp.


Không phải cố gắng viết C hướng đối tượng, thông thường tốt nhất là tạo các hàm lấy const ShapeClass *hoặc const void *làm đối số? Có vẻ như cái sau có thể đẹp hơn một chút về thừa kế, nhưng tôi có thể thấy các đối số theo cả hai cách ...
Chris Lutz

1
@Chris: Vâng, đó là một câu hỏi khó. : | GTK + (sử dụng GObject) sử dụng lớp thích hợp, tức là hình chữ nhật *. Điều này có nghĩa là bạn thường phải thực hiện các phôi, nhưng chúng cung cấp các macro tiện dụng giúp ích cho việc đó, vì vậy bạn luôn có thể chuyển BASECLASS * p sang SUBCLASS * chỉ bằng SUBCLASS (p).
thư giãn

1
Trình biên dịch của tôi thất bại trên dòng mã thứ hai: float (*computeArea)(const ShapeClass *shape);nói rằng đó ShapeClasslà một loại không xác định.
DanielSank

@DanielSank đó là do thiếu khai báo chuyển tiếp theo yêu cầu của 'typedef struct` (không được hiển thị trong ví dụ đã cho). Bởi vì các structtham chiếu chính nó, nó yêu cầu một khai báo trước khi nó được xác định. Điều này được giải thích với một ví dụ ở đây trong câu trả lời của Lundin . Sửa đổi ví dụ để bao gồm khai báo chuyển tiếp sẽ giải quyết vấn đề của bạn; typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };
S. Whittaker

Điều gì xảy ra khi Hình chữ nhật có một chức năng mà không phải tất cả các Hình dạng đều làm. Ví dụ: get_corners (). Một vòng tròn sẽ không thực hiện điều này nhưng một hình chữ nhật có thể. Làm thế nào để bạn truy cập vào một chức năng không phải là một phần của lớp cha mà bạn được thừa hưởng?
Otus

24

Tôi đã phải làm điều đó một lần quá cho một bài tập về nhà. Tôi đã làm theo phương pháp này:

  1. Xác định thành viên dữ liệu của bạn trong một cấu trúc.
  2. Xác định các thành viên hàm của bạn lấy một con trỏ tới struct của bạn làm đối số đầu tiên.
  3. Làm những điều này trong một tiêu đề và một c. Tiêu đề cho định nghĩa cấu trúc & khai báo hàm, c để thực hiện.

Một ví dụ đơn giản sẽ là thế này:

/// Queue.h
struct Queue
{
    /// members
}
typedef struct Queue Queue;

void push(Queue* q, int element);
void pop(Queue* q);
// etc.
/// 

Đây là những gì tôi đã làm trong quá khứ, nhưng với việc bổ sung phạm vi giả mạo bằng cách đặt các nguyên mẫu hàm trong tệp .c hoặc .h khi cần (như tôi đã đề cập trong câu trả lời của mình).
Taylor Leese

Tôi thích điều này, khai báo struct phân bổ tất cả bộ nhớ. Vì một số lý do tôi quên điều này sẽ hoạt động tốt.
Ben Gartner

Tôi nghĩ rằng bạn cần một typedef struct Queue Queue;trong đó.
Craig McQueen

3
Hoặc chỉ typedef struct {/ * thành viên * /} Hàng đợi;
Brooks Moses

#Craig: Cảm ơn đã nhắc nhở, cập nhật.
erelender

12

Nếu bạn chỉ muốn một lớp, hãy sử dụng một mảng structs làm dữ liệu "đối tượng" và chuyển con trỏ tới chúng cho các hàm "thành viên". Bạn có thể sử dụng typedef struct _whatever Whatevertrước khi khai báo struct _whateverđể ẩn việc thực hiện khỏi mã máy khách. Không có sự khác biệt giữa một "đối tượng" như vậy và FILEđối tượng thư viện chuẩn C.

Nếu bạn muốn có nhiều hơn một lớp có các hàm thừa kế và các hàm ảo, thì thông thường sẽ có các con trỏ tới các hàm là các thành viên của cấu trúc hoặc một con trỏ được chia sẻ với một bảng các hàm ảo. các GObject thư viện sử dụng cả này và lừa typedef, và được sử dụng rộng rãi.

Ngoài ra còn có một cuốn sách về kỹ thuật này có sẵn trực tuyến - Object Oriented Programming with ANSI C .


1
Mát mẻ! Bất kỳ đề xuất nào khác cho sách về OOP trong C? Hoặc bất kỳ kỹ thuật thiết kế hiện đại khác trong C? (hoặc hệ thống nhúng?)
Ben Gartner

7

bạn có thể xem GOBject. đó là một thư viện hệ điều hành cung cấp cho bạn một cách dài dòng để làm một đối tượng.

http: // l Library.gnome.org/devel/gobject/ sóng /


1
Rất thích thú. Bất cứ ai cũng biết về việc cấp phép? Đối với mục đích của tôi tại nơi làm việc, việc thả một thư viện nguồn mở vào một dự án có thể sẽ không hoạt động theo quan điểm pháp lý.
Ben Gartner

GTK + và tất cả các thư viện là một phần của dự án đó (bao gồm GObject), được cấp phép theo GNU LGPL, có nghĩa là bạn có thể liên kết với chúng từ phần mềm độc quyền. Tuy nhiên, tôi không biết điều đó có khả thi đối với công việc nhúng hay không.
Chris Lutz

7

C Giao diện và triển khai: Kỹ thuật tạo phần mềm tái sử dụng , David R. Hanson

http://www.informit.com/store/product.aspx?vdn=0201498413

Cuốn sách này làm một công việc tuyệt vời bao gồm câu hỏi của bạn. Nó nằm trong loạt máy tính chuyên nghiệp Addison Wesley.

Mô hình cơ bản là như thế này:

/* for data structure foo */

FOO *myfoo;
myfoo = foo_create(...);
foo_something(myfoo, ...);
myfoo = foo_append(myfoo, ...);
foo_delete(myfoo);

5

Tôi sẽ đưa ra một ví dụ đơn giản về cách OOP nên được thực hiện trong C. Tôi nhận ra rằng đây là từ năm 2009 nhưng dù sao cũng muốn thêm điều này.

/// Object.h
typedef struct Object {
    uuid_t uuid;
} Object;

int Object_init(Object *self);
uuid_t Object_get_uuid(Object *self);
int Object_clean(Object *self);

/// Person.h
typedef struct Person {
    Object obj;
    char *name;
} Person;

int Person_init(Person *self, char *name);
int Person_greet(Person *self);
int Person_clean(Person *self);

/// Object.c
#include "object.h"

int Object_init(Object *self)
{
    self->uuid = uuid_new();

    return 0;
}
uuid_t Object_get_uuid(Object *self)
{ // Don't actually create getters in C...
    return self->uuid;
}
int Object_clean(Object *self)
{
    uuid_free(self->uuid);

    return 0;
}

/// Person.c
#include "person.h"

int Person_init(Person *self, char *name)
{
    Object_init(&self->obj); // Or just Object_init(&self);
    self->name = strdup(name);

    return 0;
}
int Person_greet(Person *self)
{
    printf("Hello, %s", self->name);

    return 0;
}
int Person_clean(Person *self)
{
    free(self->name);
    Object_clean(self);

    return 0;
}

/// main.c
int main(void)
{
    Person p;

    Person_init(&p, "John");
    Person_greet(&p);
    Object_get_uuid(&p); // Inherited function
    Person_clean(&p);

    return 0;
}

Khái niệm cơ bản liên quan đến việc đặt 'lớp kế thừa' ở đầu cấu trúc. Bằng cách này, truy cập 4 byte đầu tiên trong cấu trúc cũng truy cập 4 byte đầu tiên trong 'lớp kế thừa' (Đưa ra các tối ưu hóa không điên rồ). Bây giờ, khi con trỏ của cấu trúc được truyền tới 'lớp được kế thừa', 'lớp được kế thừa' có thể truy cập vào 'các giá trị được kế thừa' giống như cách nó sẽ truy cập các thành viên của nó một cách bình thường.

Điều này và một số quy ước đặt tên cho các hàm tạo, hàm hủy, phân bổ và hàm deallocarion (tôi khuyên bạn nên init, sạch, mới, miễn phí) sẽ giúp bạn đi một chặng đường dài.

Đối với các hàm ảo, sử dụng các con trỏ hàm trong cấu trúc, có thể với Class_func (...); bao bọc quá. Đối với các mẫu (đơn giản), hãy thêm tham số size_t để xác định kích thước, yêu cầu con trỏ void * hoặc yêu cầu loại 'lớp' chỉ với chức năng bạn quan tâm. (ví dụ: int GetUUID (đối tượng * tự); GetUUID (& p);)


Disclaimer: Tất cả các mã được viết trên điện thoại thông minh. Thêm kiểm tra lỗi khi cần thiết. Kiểm tra lỗi.
yyny

4

Sử dụng a structđể mô phỏng các thành viên dữ liệu của một lớp. Về phạm vi phương thức, bạn có thể mô phỏng các phương thức riêng tư bằng cách đặt các nguyên mẫu hàm riêng trong tệp .c và các hàm công khai trong tệp .h.


4
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <uchar.h>

/**
 * Define Shape class
 */
typedef struct Shape Shape;
struct Shape {
    /**
     * Variables header...
     */
    double width, height;

    /**
     * Functions header...
     */
    double (*area)(Shape *shape);
};

/**
 * Functions
 */
double calc(Shape *shape) {
        return shape->width * shape->height;
}

/**
 * Constructor
 */
Shape _Shape() {
    Shape s;

    s.width = 1;
    s.height = 1;

    s.area = calc;

    return s;
}

/********************************************/

int main() {
    Shape s1 = _Shape();
    s1.width = 5.35;
    s1.height = 12.5462;

    printf("Hello World\n\n");

    printf("User.width = %f\n", s1.width);
    printf("User.height = %f\n", s1.height);
    printf("User.area = %f\n\n", s1.area(&s1));

    printf("Made with \xe2\x99\xa5 \n");

    return 0;
};

3

Trong trường hợp của bạn, xấp xỉ tốt của lớp có thể là một ADT . Nhưng nó vẫn không giống nhau.


1
Bất cứ ai cũng có thể đưa ra một sự khác biệt ngắn gọn giữa một loại dữ liệu trừu tượng và một lớp? Tôi luôn luôn có hai khái niệm liên kết chặt chẽ.
Ben Gartner

Họ thực sự có liên quan chặt chẽ. Một lớp có thể được xem như là một triển khai của một ADT, vì (được cho là) ​​nó có thể được thay thế bằng một triển khai khác thỏa mãn cùng một giao diện. Tôi nghĩ rằng thật khó để đưa ra một sự khác biệt chính xác, vì các khái niệm không được xác định rõ ràng.
Jørgen Fogh

3

Chiến lược của tôi là:

  • Xác định tất cả mã cho lớp trong một tệp riêng
  • Xác định tất cả các giao diện cho lớp trong một tệp tiêu đề riêng
  • Tất cả các hàm thành viên có một "ClassHandle", viết tắt của tên dụ (thay vì o.foo (), gọi foo (oHandle)
  • Hàm tạo được thay thế bằng một hàm void ClassInit (ClassHandle h, int x, int y, ...) HOẶC ClassHandle ClassInit (int x, int y, ...) tùy thuộc vào chiến lược cấp phát bộ nhớ
  • Tất cả các biến thành viên được lưu trữ như một thành viên của cấu trúc tĩnh trong tệp lớp, đóng gói nó trong tệp, ngăn các tệp bên ngoài truy cập vào nó
  • Các đối tượng được lưu trữ trong một mảng của cấu trúc tĩnh ở trên, với các tay cầm được xác định trước (hiển thị trong giao diện) hoặc giới hạn cố định của các đối tượng có thể được khởi tạo
  • Nếu hữu ích, lớp có thể chứa các hàm công khai sẽ lặp qua mảng và gọi các hàm của tất cả các đối tượng được khởi tạo (RunAll () gọi mỗi Run (oHandle)
  • Hàm Deinit (ClassHandle h) giải phóng bộ nhớ được phân bổ (chỉ mục mảng) trong chiến lược phân bổ động

Có ai nhìn thấy bất kỳ vấn đề, lỗ hổng, cạm bẫy tiềm năng hoặc lợi ích / nhược điểm tiềm ẩn đối với sự thay đổi của phương pháp này không? Nếu tôi đang phát minh lại một phương pháp thiết kế (và tôi cho rằng tôi phải như vậy), bạn có thể chỉ cho tôi tên của nó không?


Về vấn đề phong cách, nếu bạn có thông tin để thêm vào câu hỏi của mình, bạn nên chỉnh sửa câu hỏi của mình để đưa thông tin này vào.
Chris Lutz

Có vẻ như bạn đã chuyển từ malloc phân bổ động từ một đống lớn sang ClassInit () chọn động từ nhóm kích thước cố định, thay vì thực sự làm bất cứ điều gì sẽ xảy ra khi bạn yêu cầu một đối tượng khác và không có tài nguyên để cung cấp .
Pete Kirkham

Có, gánh nặng quản lý bộ nhớ được chuyển sang mã gọi ClassInit () để kiểm tra xem xử lý trả về có hợp lệ không. Về cơ bản, chúng tôi đã tạo ra đống riêng của mình cho lớp. Không chắc chắn tôi thấy một cách để tránh điều này nếu chúng tôi muốn thực hiện bất kỳ phân bổ động nào, trừ khi chúng tôi thực hiện một đống mục đích chung. Tôi muốn tách biệt rủi ro kế thừa trong một đống thành một lớp.
Ben Gartner

3

Cũng xem câu trả lời này này cái này

Điều đó là có thể. Nó luôn luôn có vẻ là một ý tưởng tốt vào thời điểm đó nhưng sau đó nó trở thành một cơn ác mộng bảo trì. Mã của bạn trở nên ngổn ngang với những đoạn mã buộc mọi thứ lại với nhau. Một lập trình viên mới sẽ gặp rất nhiều vấn đề khi đọc và hiểu mã nếu bạn sử dụng các con trỏ hàm vì sẽ không rõ ràng các hàm được gọi là gì.

Việc ẩn dữ liệu với các hàm get / set rất dễ thực hiện trong C nhưng dừng ở đó. Tôi đã thấy nhiều lần thử điều này trong môi trường nhúng và cuối cùng nó luôn là một vấn đề bảo trì.

Vì tất cả các bạn đã sẵn sàng có vấn đề bảo trì, tôi sẽ chỉ đạo rõ ràng.


2

Cách tiếp cận của tôi sẽ là di chuyển structvà tất cả các chức năng chủ yếu liên quan đến một tệp nguồn riêng biệt để nó có thể được sử dụng "có thể di chuyển ".

Tùy thuộc vào trình biên dịch của bạn, bạn thể bao gồm các hàm vào struct, nhưng đó là một phần mở rộng rất dành riêng cho trình biên dịch và không liên quan gì đến phiên bản cuối cùng của tiêu chuẩn tôi thường sử dụng :)


2
Chức năng con trỏ đều tốt. Chúng tôi có xu hướng sử dụng chúng để thay thế các câu lệnh chuyển đổi lớn bằng bảng tra cứu.
Ben Gartner

2

Trình biên dịch c ++ đầu tiên thực sự là một bộ tiền xử lý đã dịch mã C ++ sang C.

Vì vậy, rất có thể có các lớp trong C. Bạn có thể thử và đào một bộ tiền xử lý C ++ cũ và xem loại giải pháp nào nó tạo ra.


Đó sẽ là cfront; nó gặp vấn đề khi các ngoại lệ được thêm vào C ++ - xử lý các ngoại lệ không phải là nhỏ.
Jonathan Leffler

2

GTK được xây dựng hoàn toàn trên C và nó sử dụng nhiều khái niệm OOP. Tôi đã đọc qua mã nguồn của GTK và nó khá ấn tượng, và chắc chắn dễ đọc hơn. Khái niệm cơ bản là mỗi "lớp" chỉ đơn giản là một cấu trúc và các hàm tĩnh liên quan. Các hàm tĩnh đều chấp nhận cấu trúc "thể hiện" như một tham số, làm bất cứ điều gì sau đó cần và trả về kết quả nếu cần. Ví dụ: bạn có thể có chức năng "GetPocation (CircleSturation obj)". Hàm chỉ đơn giản là đào qua cấu trúc, trích xuất các số vị trí, có thể xây dựng một đối tượng PositionSturation mới, gắn x và y vào PositionSturation mới và trả về nó. GTK thậm chí còn thực hiện kế thừa theo cách này bằng cách nhúng các cấu trúc bên trong các cấu trúc. khá thông minh.


1

Bạn có muốn các phương thức ảo?

Nếu không thì bạn chỉ cần định nghĩa một tập hợp các con trỏ hàm trong chính cấu trúc. Nếu bạn gán tất cả các con trỏ hàm cho các hàm C tiêu chuẩn thì bạn sẽ có thể gọi các hàm từ C theo cú pháp rất giống với cách bạn làm trong C ++.

Nếu bạn muốn có các phương thức ảo, nó trở nên phức tạp hơn. Về cơ bản, bạn sẽ cần triển khai VTable của riêng mình cho từng cấu trúc và gán các con trỏ hàm cho VTable tùy thuộc vào chức năng nào được gọi. Sau đó, bạn sẽ cần một tập hợp các con trỏ hàm trong chính cấu trúc mà lần lượt gọi con trỏ hàm trong VTable. Đây là, về cơ bản, những gì C ++ làm.

TBH mặc dù ... nếu bạn muốn cái sau thì có lẽ tốt hơn hết là bạn chỉ cần tìm một trình biên dịch C ++ mà bạn có thể sử dụng và biên dịch lại dự án. Tôi chưa bao giờ hiểu nỗi ám ảnh về việc C ++ không thể sử dụng được nhúng. Tôi đã sử dụng nó nhiều lần và nó hoạt động rất nhanh và không gặp vấn đề về bộ nhớ. Chắc chắn bạn phải cẩn thận hơn một chút về những gì bạn làm nhưng nó thực sự không quá phức tạp.


Tôi đã nói rồi và sẽ nói lại, nhưng sẽ nói lại: Bạn không cần con trỏ hàm hoặc khả năng gọi hàm từ cấu trúc kiểu C ++ để tạo OOP trong C, OOP chủ yếu là về kế thừa chức năng và biến (nội dung) cả hai đều có thể đạt được trong C mà không cần con trỏ hàm hoặc mã trùng lặp.
yyny

0

C không phải là ngôn ngữ OOP, như bạn chỉ ra một cách đúng đắn, vì vậy không có cách tích hợp nào để viết một lớp thực sự. Đặt cược tốt nhất của bạn là xem xét các cấu trúccác con trỏ hàm , những thứ này sẽ cho phép bạn xây dựng một xấp xỉ của một lớp. Tuy nhiên, vì C là thủ tục, bạn có thể muốn xem xét viết mã giống C hơn (nghĩa là không cố gắng sử dụng các lớp).

Ngoài ra, nếu bạn có thể sử dụng C, bạn có thể sử dụng C ++ và nhận các lớp.


4
Tôi sẽ không downvote, nhưng FYI, các con trỏ hàm hoặc khả năng gọi các hàm từ các cấu trúc (mà tôi cho là ý định của bạn) không liên quan gì đến OOP. OOP chủ yếu là về sự kế thừa chức năng và các biến, cả hai đều có thể đạt được trong C mà không có con trỏ hàm hoặc trùng lặp.
yyny
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.