คุณใช้คลาสใน C อย่างไร [ปิด]


139

สมมติว่าฉันต้องใช้ C (ไม่มี C ++ หรือคอมไพเลอร์เชิงวัตถุ) และฉันไม่มีการจัดสรรหน่วยความจำแบบไดนามิกเทคนิคอะไรบ้างที่ฉันสามารถใช้ในการใช้คลาสหรือการประมาณคลาสที่ดี? เป็นความคิดที่ดีหรือไม่ที่จะแยก "class" เป็นไฟล์แยกต่างหาก? สมมติว่าเราสามารถจัดสรรหน่วยความจำล่วงหน้าโดยสมมติว่ามีอินสแตนซ์จำนวนคงที่หรือกำหนดการอ้างอิงไปยังแต่ละวัตถุเป็นค่าคงที่ก่อนเวลารวบรวม อย่าลังเลที่จะตั้งสมมติฐานเกี่ยวกับแนวคิดของ OOP ที่ฉันจะต้องนำไปใช้ (จะแตกต่างกัน) และแนะนำวิธีที่ดีที่สุดสำหรับแต่ละข้อ

ข้อ จำกัด:

  • ฉันต้องใช้ C และไม่ใช่ OOP เพราะฉันกำลังเขียนรหัสสำหรับระบบฝังตัวและคอมไพเลอร์และฐานรหัสที่มีอยู่ก่อนหน้านี้อยู่ใน C
  • ไม่มีการจัดสรรหน่วยความจำแบบไดนามิกเพราะเรามีหน่วยความจำไม่เพียงพอที่จะสันนิษฐานว่าเราจะไม่หมดถ้าเราเริ่มการจัดสรรแบบไดนามิก
  • คอมไพเลอร์ที่เราทำงานด้วยไม่มีปัญหากับพอยน์เตอร์ของฟังก์ชัน

26
คำถามที่บังคับ: คุณต้องเขียนโค้ดเชิงวัตถุหรือไม่? หากคุณทำอะไรก็ตามไม่ว่าจะด้วยเหตุผลใดก็ตาม แต่คุณจะต้องต่อสู้เพื่อต่อสู้ที่ยากเย็นแสนเข็ญ อาจเป็นวิธีที่ดีที่สุดถ้าคุณหลีกเลี่ยงการพยายามเขียนโค้ดเชิงวัตถุใน C เป็นไปได้อย่างแน่นอน - ดูคำตอบที่ยอดเยี่ยมของการผ่อนคลาย - แต่ไม่ใช่ว่า "ง่าย" และถ้าคุณกำลังทำงานกับระบบฝังตัวที่มีหน่วยความจำ จำกัด อาจไม่เป็นไปได้ ฉันอาจจะผิด แต่ฉันไม่ได้พยายามแย้งคุณ แต่เพียงนำเสนอข้อแตกต่างที่อาจไม่ได้นำเสนอ
คริสลัทซ์

1
พูดอย่างเคร่งครัดเราไม่จำเป็นต้อง อย่างไรก็ตามความซับซ้อนของระบบทำให้โค้ดไม่สามารถรักษาได้ ความรู้สึกของฉันคือวิธีที่ดีที่สุดในการลดความซับซ้อนคือการใช้แนวคิด OOP ขอบคุณสำหรับทุกคนที่ตอบกลับภายใน 3 นาที พวกคุณวิกลจริตและรวดเร็ว!
Ben Gartner

8
นี่เป็นเพียงความเห็นที่ต่ำต้อยของฉัน แต่ OOP ไม่ได้ทำให้การบำรุงรักษาโค้ดในทันที มันอาจทำให้ง่ายต่อการจัดการ แต่ไม่จำเป็นต้องบำรุงรักษามากขึ้น คุณสามารถมี "namespaces" ใน C (คำนำหน้า Apache Portable Runtime นำหน้าสัญลักษณ์กลมทั้งหมดด้วยapr_และ GLib นำหน้าด้วยg_เพื่อสร้างเนมสเปซ) และปัจจัยการจัดระเบียบอื่น ๆ โดยไม่มี OOP หากคุณกำลังจะปรับโครงสร้างแอปใหม่อีกครั้งฉันจะลองใช้เวลาสักครู่ในการพิจารณาโครงสร้างขั้นตอนที่บำรุงรักษาได้
คริสลัทซ์

สิ่งนี้ได้ถูกพูดถึงอย่างไม่สิ้นสุดมาก่อน - คุณดูคำตอบก่อนหน้าใด ๆ บ้างไหม?
Larry Watanabe

แหล่งข้อมูลนี้ซึ่งอยู่ในคำตอบที่ถูกลบของฉันอาจช่วยได้: planetpdf.com/codecuts/pdfs/ooc.pdfมันอธิบายวิธีการที่สมบูรณ์แบบสำหรับการทำ OO ใน C.
Ruben Steins

คำตอบ:


86

ขึ้นอยู่กับชุดคุณสมบัติ "เชิงวัตถุ" ที่คุณต้องการ หากคุณต้องการสิ่งต่าง ๆ เช่นการโอเวอร์โหลดและ / หรือวิธีการเสมือนคุณอาจต้องรวมฟังก์ชันพอยน์เตอร์ในโครงสร้าง:

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

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

สิ่งนี้จะช่วยให้คุณสามารถใช้คลาสได้โดย "การสืบทอด" คลาสพื้นฐานและการใช้งานฟังก์ชันที่เหมาะสม:

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

แน่นอนว่าคุณต้องใช้ตัวสร้างซึ่งทำให้แน่ใจว่าตัวชี้ฟังก์ชันได้รับการตั้งค่าอย่างเหมาะสม โดยปกติคุณจะจัดสรรหน่วยความจำแบบไดนามิกสำหรับอินสแตนซ์ แต่คุณสามารถให้ผู้โทรทำเช่นนั้นได้เช่นกัน:

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

หากคุณต้องการตัวสร้างที่แตกต่างกันคุณจะต้อง "ตกแต่ง" ชื่อฟังก์ชั่นคุณไม่สามารถมีมากกว่าหนึ่งrectangle_new()ฟังก์ชั่น:

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

นี่คือตัวอย่างพื้นฐานที่แสดงการใช้งาน:

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

ฉันหวังว่านี่จะช่วยให้คุณมีความคิดอย่างน้อย สำหรับกรอบงานเชิงวัตถุที่ประสบความสำเร็จและอุดมไปด้วยใน C ให้ดูที่ไลบรารีGObjectของglib

นอกจากนี้โปรดทราบว่าไม่มี "คลาส" ที่ชัดเจนในแบบจำลองด้านบนแต่ละวัตถุมีตัวชี้วิธีการของตัวเองซึ่งมีความยืดหยุ่นมากกว่าที่คุณมักจะพบใน C ++ นอกจากนี้ยังมีค่าใช้จ่ายหน่วยความจำ คุณสามารถหลีกหนีจากสิ่งนั้นได้โดยการใส่พอยน์เตอร์ของวิธีการในclassโครงสร้างและคิดค้นวิธีสำหรับแต่ละอินสแตนซ์ของวัตถุเพื่ออ้างอิงคลาส


ไม่ต้องลองเขียน object-oriented C โดยปกติแล้วมันจะดีที่สุดถ้าสร้างฟังก์ชั่นที่รับconst ShapeClass *หรือconst void *เป็นอาร์กิวเมนต์? มันจะดูเหมือนว่าหลังอาจจะดีกว่าบิตบนมรดก แต่ฉันสามารถดูการขัดแย้งทั้งสองวิธี ...
คริสลัทซ์

1
@Chris: ใช่นั่นเป็นคำถามที่ยาก : | GTK + (ซึ่งใช้ GObject) ใช้คลาสที่เหมาะสมเช่น RectangleClass * นี่หมายความว่าคุณจะต้องปลดเปลื้องบ่อยครั้ง แต่พวกมันให้มาโครที่มีประโยชน์ช่วยด้วยดังนั้นคุณจึงสามารถส่ง BASECLASS * p ไปยัง SUBCLASS * ได้โดยใช้เพียง SUBCLASS (p)
คลาย

1
คอมไพเลอร์ของฉันล้มเหลวในรหัสบรรทัดที่สอง: การfloat (*computeArea)(const ShapeClass *shape);บอกว่าShapeClassเป็นประเภทที่ไม่รู้จัก
DanielSank

@DanielSank ที่เกิดจากการขาดการประกาศไปข้างหน้าตามที่กำหนดโดย 'typedef struct` (ไม่แสดงในตัวอย่างที่ให้มา) เนื่องจากการstructอ้างอิงตัวเองมันต้องมีการประกาศก่อนที่จะมีการกำหนด นี้จะอธิบายด้วยตัวอย่างที่นี่ในคำตอบของ Lundin การแก้ไขตัวอย่างเพื่อรวมการประกาศไปข้างหน้าควรแก้ไขปัญหาของคุณ typedef struct ShapeClass ShapeClass; struct ShapeClass { float (*computeArea)(const ShapeClass *shape); };
S. Whittaker

จะเกิดอะไรขึ้นเมื่อ Rectangle มีฟังก์ชั่นที่ไม่ใช่ทุกรูปร่าง ตัวอย่างเช่น get_corners () วงกลมจะไม่ใช้สิ่งนี้ แต่อาจเป็นรูปสี่เหลี่ยมผืนผ้า คุณเข้าถึงฟังก์ชั่นที่ไม่ได้เป็นส่วนหนึ่งของคลาสพาเรนต์ที่คุณสืบทอดมาได้อย่างไร
ตุส

24

ฉันต้องทำมันเหมือนกันเพื่อทำการบ้าน ฉันทำตามวิธีนี้:

  1. กำหนดสมาชิกข้อมูลของคุณใน struct
  2. กำหนดสมาชิกฟังก์ชันของคุณที่ใช้ตัวชี้ไปยัง struct ของคุณเป็นอาร์กิวเมนต์แรก
  3. ทำสิ่งเหล่านี้ในส่วนหัวเดียว & หนึ่งค ส่วนหัวสำหรับการกำหนดนิยาม & ฟังก์ชั่นประกาศ c สำหรับการใช้งาน

ตัวอย่างง่ายๆก็คือ:

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

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

นี่คือสิ่งที่ฉันทำในอดีต แต่ด้วยการเพิ่มขอบเขตการแกล้งทำโดยการวางต้นแบบฟังก์ชันในไฟล์. c หรือ. h ตามต้องการ (ตามที่ฉันกล่าวไว้ในคำตอบ)
Taylor Leese

ฉันชอบสิ่งนี้การประกาศโครงสร้างจัดสรรหน่วยความจำทั้งหมด ด้วยเหตุผลบางอย่างฉันลืมสิ่งนี้จะทำงานได้ดี
Ben Gartner

ฉันคิดว่าคุณจำเป็นต้องมีtypedef struct Queue Queue;ใน
Craig McQueen

3
หรือแค่พิมพ์คำสั่ง struct {/ * members * /} Queue;
บรูคส์โมเสส

#Craig: ขอบคุณสำหรับการเตือนความจำที่อัปเดตแล้ว
ลบ

12

หากคุณต้องการเพียงคลาสเดียวให้ใช้อาร์เรย์ของstructs เป็นข้อมูล "วัตถุ" และส่งต่อพอยน์เตอร์ไปยังฟังก์ชัน "สมาชิก" คุณสามารถใช้typedef struct _whatever Whateverก่อนที่จะประกาศstruct _whateverเพื่อซ่อนการใช้งานจากรหัสลูกค้า ไม่มีความแตกต่างระหว่าง "วัตถุ" และFILEวัตถุไลบรารีมาตรฐาน C

ถ้าคุณต้องการมากกว่าหนึ่งคลาสที่มีการสืบทอดและฟังก์ชันเสมือนมันเป็นเรื่องปกติที่จะมีตัวชี้ไปยังฟังก์ชันในฐานะสมาชิกของ struct หรือตัวชี้ที่ใช้ร่วมกันไปยังตารางของฟังก์ชันเสมือน GObjectห้องสมุดการใช้งานทั้งสองนี้และเคล็ดลับ typedef และมีการใช้กันอย่างแพร่หลาย

นอกจากนี้ยังมีหนังสือเกี่ยวกับเทคนิคในการออนไลน์นี้ใช้ได้ - เขียนโปรแกรมเชิงวัตถุมาตรฐาน ANSI C


1
เย็น! มีคำแนะนำอื่น ๆ สำหรับหนังสือเกี่ยวกับ OOP ใน C หรือไม่ หรือเทคนิคการออกแบบที่ทันสมัยอื่น ๆ ใน C? (หรือระบบฝังตัว?)
Ben Gartner

7

คุณสามารถดู GOBject มันเป็นห้องสมุดระบบปฏิบัติการที่ให้วิธีการทำวัตถุอย่างละเอียด

http://library.gnome.org/devel/gobject/stable/


1
สนใจมาก ใครรู้เกี่ยวกับใบอนุญาตหรือไม่ สำหรับวัตถุประสงค์ในการทำงานของฉันการวางไลบรารี่โอเพนซอร์สลงในโครงการอาจไม่ได้ผลจากมุมมองทางกฎหมาย
Ben Gartner

GTK + และไลบรารีทั้งหมดที่เป็นส่วนหนึ่งของโครงการนั้น (รวมถึง GObject) ได้รับอนุญาตภายใต้ GNU LGPL ซึ่งหมายความว่าคุณสามารถเชื่อมโยงพวกเขาจากซอฟต์แวร์ที่เป็นกรรมสิทธิ์ ฉันไม่รู้ว่ามันจะเป็นไปได้สำหรับงานฝังตัวหรือไม่
คริสลัทซ์

7

การเชื่อมต่อและการใช้งาน C: เทคนิคการสร้างซอฟต์แวร์ที่สามารถใช้ซ้ำได้ David R. Hanson

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

หนังสือเล่มนี้ทำงานได้อย่างยอดเยี่ยมในการครอบคลุมคำถามของคุณ มันอยู่ในซีรีส์ Addison Wesley Professional Computing

กระบวนทัศน์พื้นฐานคืออะไร:

/* for data structure foo */

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

5

ฉันจะให้ตัวอย่างง่ายๆว่า OOP ควรทำอย่างไรใน C. ฉันรู้ว่า thead นี้มาจาก 2009 แต่ต้องการเพิ่มในตอนนี้

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

แนวคิดพื้นฐานเกี่ยวข้องกับการวาง 'คลาสที่สืบทอดมา' ที่ด้านบนของโครงสร้าง วิธีนี้การเข้าถึง 4 ไบต์แรกใน struct ยังเข้าถึง 4 ไบต์แรกใน 'คลาสที่สืบทอดมา' ตอนนี้เมื่อตัวชี้ของ struct ถูกส่งไปยัง 'คลาสที่สืบทอดมา' 'คลาสที่สืบทอดมา' สามารถเข้าถึง 'ค่าที่สืบทอดมา' ได้ในลักษณะเดียวกับที่มันเข้าถึงสมาชิกปกติ

ข้อตกลงนี้และการตั้งชื่อบางอย่างสำหรับฟังก์ชั่นคอนสตรัคเตอร์, destructors, การจัดสรรและการจัดสรรคืน

ในฐานะที่เป็นฟังก์ชั่นเสมือนจริงให้ใช้ตัวชี้ฟังก์ชั่นใน struct อาจมี Class_func (... ); เสื้อคลุมเกินไป สำหรับเทมเพลต (ง่าย) ให้เพิ่มพารามิเตอร์ size_t เพื่อกำหนดขนาดต้องการตัวชี้โมฆะ * หรือต้องการประเภท 'คลาส' ที่มีเพียงฟังก์ชันที่คุณสนใจ (เช่น int GetUUID (Object * self); GetUUID (& p);)


คำเตือน: รหัสทั้งหมดที่เขียนบนสมาร์ทโฟน เพิ่มข้อผิดพลาดที่จำเป็น ตรวจสอบข้อบกพร่อง
yyny

4

ใช้ a structเพื่อจำลองข้อมูลสมาชิกของคลาส ในแง่ของขอบเขตเมธอดคุณสามารถจำลองเมธอดส่วนตัวโดยวางต้นแบบฟังก์ชันส่วนตัวลงในไฟล์. c และฟังก์ชั่นสาธารณะในไฟล์. 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

ในกรณีของคุณประมาณที่ดีของการเรียนอาจจะเป็นที่ADT แต่ก็ยังคงไม่เหมือนเดิม


1
ทุกคนสามารถให้ความแตกต่างสั้น ๆ ระหว่างชนิดข้อมูลนามธรรมและคลาสได้หรือไม่? ฉันเป็นหนึ่งในสองแนวคิดที่เชื่อมโยงกันอย่างใกล้ชิด
Ben Gartner

พวกเขาเกี่ยวข้องกันอย่างแท้จริง คลาสสามารถดูเป็นการใช้งานของ ADT เนื่องจาก (ควร) มันอาจถูกแทนที่ด้วยการใช้งานอื่นที่เป็นไปตามอินเตอร์เฟสเดียวกัน ฉันคิดว่ามันยากที่จะให้ความแตกต่างที่แน่นอนเนื่องจากแนวคิดไม่ชัดเจน
Jørgen Fogh

3

กลยุทธ์ของฉันคือ:

  • กำหนดรหัสทั้งหมดสำหรับชั้นเรียนในไฟล์แยกต่างหาก
  • กำหนดอินเตอร์เฟสทั้งหมดสำหรับคลาสในไฟล์ส่วนหัวแยก
  • ฟังก์ชันสมาชิกทั้งหมดใช้ "ClassHandle" ซึ่งย่อมาจากชื่ออินสแตนซ์ (แทน o.foo (), เรียก foo (oHandle)
  • ตัวสร้างจะถูกแทนที่ด้วยฟังก์ชั่นโมฆะ ClassInit (ClassHandle h, int x, int y, ... ) หรือ ClassHandle ClassInit (int x, int y, ... ) ขึ้นอยู่กับกลยุทธ์การจัดสรรหน่วยความจำ
  • ตัวแปรสมาชิกทั้งหมดจะถูกจัดเก็บในฐานะสมาชิกของโครงสร้างแบบคงที่ในไฟล์คลาสห่อหุ้มไว้ในไฟล์ป้องกันไม่ให้ไฟล์ภายนอกเข้าถึงได้
  • วัตถุถูกเก็บไว้ในอาร์เรย์ของโครงสร้างคงที่ด้านบนพร้อมที่จับที่กำหนดไว้ล่วงหน้า (มองเห็นได้ในส่วนต่อประสาน) หรือขีด จำกัด คงที่ของวัตถุที่สามารถยกตัวอย่างได้
  • หากมีประโยชน์ชั้นเรียนสามารถมีฟังก์ชั่นสาธารณะที่จะวนรอบอาร์เรย์และเรียกใช้ฟังก์ชั่นของวัตถุที่สร้างอินสแตนซ์ทั้งหมด (RunAll () เรียกแต่ละ Run (oHandle)
  • ฟังก์ชัน Deinit (ClassHandle h) ทำให้หน่วยความจำที่จัดสรร (ดัชนีอาร์เรย์) เป็นอิสระในกลยุทธ์การจัดสรรแบบไดนามิก

ไม่มีใครเห็นปัญหาหลุมความผิดพลาดที่อาจเกิดขึ้นหรือผลประโยชน์ / ข้อบกพร่องที่ซ่อนอยู่ในการเปลี่ยนแปลงของวิธีนี้ ถ้าฉันจะสร้างวิธีการออกแบบใหม่ (และฉันคิดว่าฉันต้องเป็น) คุณช่วยชี้ให้ฉันชื่อมันได้หรือไม่?


ตามสไตล์หากคุณมีข้อมูลที่จะเพิ่มในคำถามของคุณคุณควรแก้ไขคำถามของคุณเพื่อรวมข้อมูลนี้
คริสลัทซ์

ดูเหมือนว่าคุณได้ย้ายจาก malloc แบบไดนามิกจาก heap ขนาดใหญ่ไปเป็น ClassInit () เลือกแบบไดนามิกจากกลุ่มขนาดคงที่แทนที่จะทำอะไรเกี่ยวกับสิ่งที่จะเกิดขึ้นเมื่อคุณถามหาวัตถุอื่นและไม่มีทรัพยากรให้ .
Pete Kirkham

ใช่ภาระการจัดการหน่วยความจำถูกเลื่อนไปยังรหัสที่เรียก ClassInit () เพื่อตรวจสอบว่าหมายเลขอ้างอิงที่ส่งคืนนั้นถูกต้อง โดยพื้นฐานแล้วเราได้สร้างฮีปเฉพาะสำหรับคลาส ไม่แน่ใจว่าฉันเห็นวิธีที่จะหลีกเลี่ยงสิ่งนี้หากเราต้องการทำการจัดสรรแบบไดนามิกใด ๆ เว้นแต่ว่าเราจะนำไปใช้เป็นวัตถุประสงค์ทั่วไป ฉันต้องการแยกความเสี่ยงที่สืบทอดในฮีปเป็นคลาสเดียว
Ben Gartner

3

ดูคำตอบนี้และคำตอบนี้

มันเป็นไปได้. มันดูเหมือนความคิดที่ดีเสมอในเวลานั้น แต่หลังจากนั้นมันก็กลายเป็นฝันร้ายในการบำรุงรักษา รหัสของคุณจะเกลื่อนไปด้วยชิ้นส่วนของรหัสผูกทุกอย่างเข้าด้วยกัน โปรแกรมเมอร์ใหม่จะมีปัญหามากมายในการอ่านและทำความเข้าใจรหัสถ้าคุณใช้ตัวชี้ฟังก์ชั่นเนื่องจากมันจะไม่ชัดเจนว่าสิ่งที่เรียกว่าฟังก์ชั่น

การซ่อนข้อมูลด้วยฟังก์ชั่น get / set นั้นใช้งานง่ายใน C แต่หยุดอยู่ตรงนั้น ฉันได้เห็นหลายครั้งในสภาพแวดล้อมแบบฝังตัวและในที่สุดมันก็เป็นปัญหาการบำรุงรักษาเสมอ

เนื่องจากคุณทุกคนพร้อมมีปัญหาการบำรุงรักษาฉันจะคัดท้ายชัดเจน


2

วิธีการของฉันคือการย้ายstructและฟังก์ชั่นที่เกี่ยวข้องขั้นต้นทั้งหมดไปยังไฟล์ต้นฉบับที่แยกต่างหากเพื่อให้สามารถใช้ "แบบพกพา" ได้

ทั้งนี้ขึ้นอยู่กับคอมไพเลอร์ของคุณคุณอาจจะสามารถที่จะรวมถึงฟังก์ชั่นลงในstructแต่ที่เป็นมากขยายคอมไพเลอร์ที่เฉพาะเจาะจงและมีอะไรจะทำอย่างไรกับรุ่นสุดท้ายของฉันมาตรฐานที่ใช้เป็นประจำ :)


2
พอยน์เตอร์ฟังก์ชั่นล้วนดี เรามักจะใช้มันเพื่อแทนที่คำสั่ง switch ขนาดใหญ่ด้วยตารางการค้นหา
Ben Gartner

2

คอมไพเลอร์ c ++ แรกจริง ๆ แล้วเป็นตัวประมวลผลล่วงหน้าซึ่งแปลรหัส C ++ เป็น C

ดังนั้นจึงมีความเป็นไปได้ที่จะมีคลาสเป็นซีคุณอาจลองและสร้างพรีโพรเซสเซอร์ C ++ เก่าขึ้นมาและดูวิธีแก้ปัญหาที่สร้างขึ้น


นั่นจะเป็นcfront; มันพบปัญหาเมื่อมีการเพิ่มข้อยกเว้นใน C ++ - การจัดการข้อยกเว้นนั้นไม่สำคัญ
Jonathan Leffler

2

GTK นั้นสร้างขึ้นทั้งหมดบน C และใช้แนวคิด OOP มากมาย ฉันได้อ่านซอร์สโค้ดของ GTK และมันค่อนข้างน่าประทับใจและอ่านง่ายขึ้น แนวคิดพื้นฐานคือ "คลาส" แต่ละคลาสนั้นเป็นเพียงโครงสร้างและฟังก์ชันคงที่ที่เกี่ยวข้อง ฟังก์ชั่นแบบคงที่ทุกคนยอมรับ "อินสแตนซ์" struct เป็นพารามิเตอร์ทำสิ่งที่ต้องการแล้วและส่งคืนผลลัพธ์หากจำเป็น ตัวอย่างเช่นคุณอาจมีฟังก์ชัน "GetPosition (CircleStruct obj)" ฟังก์ชั่นจะขุดผ่านโครงสร้างแยกหมายเลขตำแหน่งอาจสร้างวัตถุ PositionStruct ใหม่ติด x และ y ใน PositionStruct ใหม่แล้วส่งคืน GTK ยังใช้การสืบทอดด้วยวิธีนี้โดยการฝัง structs ภายใน structs ค่อนข้างฉลาด


1

คุณต้องการวิธีการเสมือนหรือไม่?

ถ้าไม่เช่นนั้นคุณเพียงกำหนดชุดพอยน์เตอร์ของฟังก์ชันใน struct เอง หากคุณกำหนดพอยน์เตอร์ของฟังก์ชั่นทั้งหมดให้กับฟังก์ชั่น C มาตรฐานคุณจะสามารถเรียกใช้ฟังก์ชั่นจาก C ในรูปแบบที่คล้ายกันมากกับวิธีที่คุณใช้ภายใต้ C ++

หากคุณต้องการมีวิธีการเสมือนมันจะซับซ้อนมากขึ้น โดยทั่วไปคุณจะต้องใช้ VTable ของคุณเองสำหรับแต่ละโครงสร้างและกำหนดตัวชี้ฟังก์ชันให้กับ VTable ขึ้นอยู่กับฟังก์ชันที่เรียกว่า จากนั้นคุณจะต้องมีชุดพอยน์เตอร์ของฟังก์ชั่นในโครงสร้างของตัวเองที่จะเรียกตัวชี้ฟังก์ชั่นใน VTable นี่คือสิ่งที่ C ++ ทำ

TBH แม้ว่า ... ถ้าคุณต้องการอันหลังคุณก็น่าจะดีกว่าเพียงแค่หาคอมไพเลอร์ C ++ ที่คุณสามารถใช้และรวบรวมโครงการใหม่ ฉันไม่เคยเข้าใจความลุ่มหลงกับ C ++ ที่ไม่สามารถใช้งานได้ในสมองกลฝังตัว ฉันใช้มันหลายครั้งและใช้งานได้อย่างรวดเร็วและไม่มีปัญหาเรื่องความจำ แน่นอนว่าคุณต้องระวังให้มากขึ้นเกี่ยวกับสิ่งที่คุณทำ แต่มันไม่ซับซ้อนจริงๆ


ฉันได้พูดไปแล้วและจะพูดอีกครั้ง แต่จะพูดอีกครั้ง: คุณไม่ต้องการตัวชี้ฟังก์ชั่นหรือความสามารถในการเรียกใช้ฟังก์ชั่นจากสไตล์ C ++ เพื่อสร้าง OOP ใน C, OOP ส่วนใหญ่เกี่ยวกับการสืบทอดหน้าที่และตัวแปร (เนื้อหา) ซึ่งสามารถทำได้ทั้งใน C โดยไม่มีตัวชี้ฟังก์ชั่นหรือรหัสซ้ำ
yyny

0

C ไม่ใช่ภาษา OOP ตามที่คุณถูกต้องดังนั้นไม่มีวิธีการเขียนคลาสจริง คุณเป็นทางเลือกที่ดีที่สุดคือการดูstructsและฟังก์ชั่นพอยน์เตอร์สิ่งเหล่านี้จะช่วยให้คุณสร้างคลาสโดยประมาณ อย่างไรก็ตามเนื่องจาก C เป็นขั้นตอนคุณอาจต้องการเขียนโค้ด C-like เพิ่มเติม (เช่นโดยไม่ต้องใช้คลาส)

นอกจากนี้หากคุณสามารถใช้ C คุณสามารถใช้ C ++ และเรียนได้


4
ฉันจะไม่ downvote แต่ FYI, ตัวชี้ฟังก์ชั่นหรือความสามารถในการเรียกใช้ฟังก์ชั่นจาก structs (ซึ่งฉันคิดว่าเป็นความตั้งใจของคุณ) มีอะไรจะทำอย่างไรกับ OOP OOP นั้นส่วนใหญ่เกี่ยวกับการสืบทอดฟังก์ชันและตัวแปรซึ่งทั้งสองอย่างนั้นสามารถทำได้ใน C โดยไม่มีตัวชี้ฟังก์ชั่นหรือการทำซ้ำ
yyny
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.