简体   繁体   English

使 C 中的两个对象更加面向对象

[英]Making two objects in C more object-oriented

First, this is merely an academic question.首先,这只是一个学术问题。 I know C is not the job for doing OOP programming, but this is more of a learning exercise for a beginner learning what's possible and what's not (or perhaps, what might be possible but is not a good idea).我知道 C 不是做 OOP 编程的工作,但这更像是一个学习练习,让初学者了解什么是可能的,什么不是(或者也许,什么是可能的,但不是一个好主意)。

Let's take the following as a starting place, where I have two different objects but I want to give each of them the same two methods: create and print .让我们以以下为起点,我有两个不同的对象,但我想为它们中的每一个提供相同的两种方法: createprint I've omitted any error checking, freeing, etc. just to simplify matters:为了简化问题,我省略了任何错误检查、释放等:

#include <stdio.h>
#include <stdlib.h>

struct Person {
    char* name;
    int age;
};
struct Car {
    char* make;
    char* model;
    int year;
};
struct Person* create_person(void)
{
    struct Person *new = malloc(sizeof (struct Person));
    return new;
}
void print_person(struct Person* person)
{
    printf("<Person: %s (%d)>\n", person->name, person->age);
}
struct Car* create_car(void)
{
    struct Car *new = malloc(sizeof (struct Car));
    return new;
}
void print_car(struct Car* car)
{
    printf("<Car: %s - %s (%d)>\n", car->make, car->model, car->year);
}
int main(void)
{
    struct Car *car = create_car();
    *car = (struct Car) {.make="Chevy", .model="Eldorado", .year=2015};
    print_car(car);

    struct Person *person = create_person();
    *person = (struct Person) {.name="Tom", .age=30};
    print_person(person);
}

I would think that the first part would be to group the 'methods' into the struct itself.我认为第一部分是将“方法”分组到结构本身中。 So then we would have:那么我们将有:

#include <stdio.h>
#include <stdlib.h>

struct Person {
    char* name;
    int age;
    void (*print)(struct Person*);
};
struct Car {
    char* make;
    char* model;
    int year;
    void (*print)(struct Car*);
};
void print_car(struct Car* car);
void print_person(struct Person* person);
struct Person* create_person(void)
{
    struct Person *new = malloc(sizeof (struct Person));
    return new;
}
void print_person(struct Person* person)
{
    printf("<Person: %s (%d)>\n", person->name, person->age);
}
struct Car* create_car(void)
{
    struct Car *new = malloc(sizeof (struct Car));
    return new;
}
void print_car(struct Car* car)
{
    printf("<Car: %s - %s (%d)>\n", car->make, car->model, car->year);
}
int main(void)
{
    struct Car *car = create_car();
    *car = (struct Car) {.make="Chevy", .model="Eldorado", .year=2015, .print=print_car};
    car->print(car);

    struct Person *person = create_person();
    *person = (struct Person) {.name="Tom", .age=30, .print=print_person};
    person->print(person);
}

What would be the next step in making it "more OOP like"?让它“更像 OOP”的下一步是什么? Perhaps using preprocessor glue and generics?也许使用预处理器胶水和 generics? What would be an example of the most OOP-like that the two objects could become?这两个对象可能成为最像 OOP 的例子是什么? Again, I know this isn't what C is meant for, but it's more a learning experience.同样,我知道这不是 C 的用途,但它更多的是一种学习体验。

You could use approach applied by the Linux kernel.您可以使用 Linux kernel 应用的方法。 The implementation of OOP is based using a composition for inheritance, and embedding interfaces into new classes. OOP 的实现基于使用 inheritance 的组合,并将接口嵌入到新类中。

The macro container_of lets easily alternate between a pointer to class and a pointer to one of its members.container_of可以轻松地在指向 class 的指针和指向其成员之一的指针之间切换。 Usually an embedded object will be an interface of the class.通常嵌入式 object 将是 class 的接口。 To find more details about container_of macro see my answer to other but related question.要查找有关container_of宏的更多详细信息,请参阅我对其他但相关问题的回答。

https://stackoverflow.com/a/66429587/4989451 https://stackoverflow.com/a/66429587/4989451

This methodology was used to create a huge complex object oriented software, ie Linux kernel.该方法用于创建一个庞大的复杂 object 导向软件,即 Linux kernel。

In the examples from the question, the Car and Person classes use an printing interface that we can call struct Printable .在问题的示例中, Car 和 Person 类使用我们可以称为struct Printable的打印接口。 I strongly suggest to produce a fully initialized objects in create_... functions.我强烈建议在create_...函数中生成完全初始化的对象。 Let it make a copy of all strings.让它复制所有字符串。 Moreover, you should add destroy_... methods to release resources allocated by create_... .此外,您应该添加destroy_...方法来释放create_...分配的资源。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

#define container_of(ptr, type, member) \
  (type*)(void*)((char*)ptr - offsetof(type, member))

struct Printable {
    void (*print)(struct Printable*);
};

struct Person {
    char* name;
    int age;
    struct Printable printable;
};

void print_person(struct Printable *printable) {
    struct Person *p = container_of(printable, struct Person, printable);
    printf("<Person: %s (%d)>\n", p->name, p->age);
}

struct Person *create_person(char *name, int age) {
    struct Person *p = malloc(sizeof *p);
    p->name = strdup(name);
    p->age = age;
    p->printable.print = print_person;
    return p;
}

struct Car {
    char* make;
    char* model;
    int year;
    struct Printable printable;
};

void print_car(struct Printable *printable) {
    struct Car *c = container_of(printable, struct Car, printable);
    printf("<Car: %s - %s (%d)>\n", c->make, c->model, c->year);
}

struct Car *create_car(char *make, char *model, int year) {
    struct Car *c = malloc(sizeof *c);
    c->make = strdup(make);
    c->model = strdup(model);
    c->year = year;
    c->printable.print = print_car;
    return c;
}

void print(struct Printable *printable) {
    printable->print(printable);
}

int main() {
    struct Car *car = create_car("Chevy", "Eldorado", 2015);
    struct Person *person = create_person("Tom", 30);
    print(&car->printable);
    print(&person->printable);
    return 0;
}

produces output:生产 output:

<Car: Chevy - Eldorado (2015)>
<Person: Tom (30)>

Note that function print() takes a pointer to the Printable interface.请注意,function print()需要一个指向Printable接口的指针。 The function does not need to know anything about the original class. function不需要知道原厂class的任何事情。 No preprocessor is used.没有使用预处理器。 All casts are done on "library" side, not on clients side.所有演员都是在“图书馆”方面完成的,而不是在客户端。 The library initializes the Printable interface, therefore it cannot be misused.该库初始化 Printable 接口,因此它不能被滥用。

You can easily add other base classes or interfaces like ie RefCounted to solve memory management.您可以轻松添加其他基类或接口,例如 RefCounted 来解决 memory 管理。 The i-face would contain a pointer to destructor and refcount itself. i-face 将包含指向析构函数和引用计数本身的指针。 Other examples are intrusive linked lists or binary trees.其他示例是侵入式链表或二叉树。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM