用 C 编写“通用”结构打印方法

Dav*_*542 1 c struct

是否可以在 C 中执行类似以下操作来打印不同但相似的struct类型?

#include<stdio.h>

typedef struct Car {
    char*        name;
    unsigned int cost
} Car;

typedef struct Animal {
    char*           name;
    unsigned int    age;
    unsigned int    weight
} Animal;

void print_struct(void *obj) {
    printf("The name is: %s\n", obj->name);
};

int main(void)
{
    Animal *dog = & (Animal) {.name = "Dog", .age = 10, .weight = 200};
    Car *ford   = & (Car) {.name = "Ford", .cost = 50000};

    print_struct(dog);

};
Run Code Online (Sandbox Code Playgroud)

具体来说,print_struct方法:

  • 这可以吗?如果可以的话怎么做?
  • 在 C 中创建非特定类型函数被认为是好还是坏实践?

否则,代码中不会充斥着数十个(在大型项目中甚至数百个?)如下所示的函数:

void print_animal(Animal *animal) {
    printf("The name is: %s\n", animal->name);
};
void print_car(Car *car) {
    printf("The name is: %s\n", car->name);
};
...
print_animal(dog);
print_car(ford);
Run Code Online (Sandbox Code Playgroud)

Cra*_*tey 9

您可以通过多种不同的方式来做到这一点。

通常,为公共信息定义一个“公共”结构,该结构也有一个type字段。


选项1:

这是在打印函数中使用void *指针和 a 的版本:switch

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

typedef struct Common {
    int type;
    const char *name;
} Common;

enum {
    TYPE_CAR,
    TYPE_ANIMAL,
};

typedef struct Car {
    Common comm;
    unsigned int cost;
} Car;

typedef struct Animal {
    Common comm;
    unsigned int age;
    unsigned int weight;
} Animal;

Car *
car_new(const char *name,int cost)
{
    Car *car = malloc(sizeof(*car));

    car->comm.name = name;
    car->comm.type = TYPE_CAR;

    car->cost = cost;

    return car;
}

void
car_print(void *obj)
{
    Car *car = obj;

    printf("The cost is: %d\n",car->cost);
}

Animal *
animal_new(const char *name,int age,int weight)
{
    Animal *animal = malloc(sizeof(*animal));

    animal->comm.name = name;
    animal->comm.type = TYPE_ANIMAL;

    animal->age = age;
    animal->weight = weight;

    return animal;
}

void
animal_print(void *obj)
{
    Animal *animal = obj;

    printf("The age is: %d\n",animal->age);
    printf("The weight is: %d\n",animal->weight);
}

void
print_struct(void *obj)
{
    Common *comm = obj;

    printf("The name is: %s\n", comm->name);

    switch (comm->type) {
    case TYPE_ANIMAL:
        animal_print(obj);
        break;
    case TYPE_CAR:
        car_print(obj);
        break;
    }
}

int
main(void)
{
    Animal *animal = animal_new("Dog",10,200);
    Car *car = car_new("Ford",50000);

    print_struct(animal);
    print_struct(car);

    return 0;
};
Run Code Online (Sandbox Code Playgroud)

选项#2:

传递指针void *并不像它应该的那样安全。

这是在打印函数中使用Common *指针和 a 的版本:switch

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

typedef struct Common {
    int type;
    const char *name;
} Common;

enum {
    TYPE_CAR,
    TYPE_ANIMAL,
};

typedef struct Car {
    Common comm;
    unsigned int cost;
} Car;

typedef struct Animal {
    Common comm;
    unsigned int age;
    unsigned int weight;
} Animal;

Common *
car_new(const char *name,int cost)
{
    Car *car = malloc(sizeof(*car));

    car->comm.name = name;
    car->comm.type = TYPE_CAR;

    car->cost = cost;

    return (Common *) car;
}

void
car_print(Common *obj)
{
    Car *car = (Car *) obj;

    printf("The cost is: %d\n",car->cost);
}

Common *
animal_new(const char *name,int age,int weight)
{
    Animal *animal = malloc(sizeof(*animal));

    animal->comm.name = name;
    animal->comm.type = TYPE_ANIMAL;

    animal->age = age;
    animal->weight = weight;

    return (Common *) animal;
}

void
animal_print(Common *obj)
{
    Animal *animal = (Animal *) obj;

    printf("The age is: %d\n",animal->age);
    printf("The weight is: %d\n",animal->weight);
}

void
print_struct(Common *comm)
{

    printf("The name is: %s\n", comm->name);

    switch (comm->type) {
    case TYPE_ANIMAL:
        animal_print(comm);
        break;
    case TYPE_CAR:
        car_print(comm);
        break;
    }
}

int
main(void)
{
    Common *animal = animal_new("Dog",10,200);
    Common *car = car_new("Ford",50000);

    print_struct(animal);
    print_struct(car);

    return 0;
};
Run Code Online (Sandbox Code Playgroud)

选项#3:

这是使用虚函数回调表的版本:

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

typedef struct Vtable Vtable;

typedef struct Common {
    int type;
    Vtable *vtbl;
    const char *name;
} Common;

enum {
    TYPE_CAR,
    TYPE_ANIMAL,
};

typedef struct Car {
    Common comm;
    unsigned int cost;
} Car;
void car_print(Common *obj);

typedef struct Animal {
    Common comm;
    unsigned int age;
    unsigned int weight;
} Animal;
void animal_print(Common *obj);

typedef struct Vtable {
    void (*vtb_print)(Common *comm);
} Vtable;

void
car_print(Common *obj)
{
    Car *car = (Car *) obj;

    printf("The cost is: %d\n",car->cost);
}

Vtable car_vtbl = {
    .vtb_print = car_print
};

Common *
car_new(const char *name,int cost)
{
    Car *car = malloc(sizeof(*car));

    car->comm.name = name;
    car->comm.type = TYPE_CAR;
    car->comm.vtbl = &car_vtbl;

    car->cost = cost;

    return (Common *) car;
}

Vtable animal_vtbl = {
    .vtb_print = animal_print
};

void
animal_print(Common *obj)
{
    Animal *animal = (Animal *) obj;

    printf("The age is: %d\n",animal->age);
    printf("The weight is: %d\n",animal->weight);
}

Common *
animal_new(const char *name,int age,int weight)
{
    Animal *animal = malloc(sizeof(*animal));

    animal->comm.name = name;
    animal->comm.type = TYPE_ANIMAL;
    animal->comm.vtbl = &animal_vtbl;

    animal->age = age;
    animal->weight = weight;

    return (Common *) animal;
}

void
print_struct(Common *comm)
{

    printf("The name is: %s\n", comm->name);

    comm->vtbl->vtb_print(comm);
}

int
main(void)
{
    Common *animal = animal_new("Dog",10,200);
    Common *car = car_new("Ford",50000);

    print_struct(animal);
    print_struct(car);

    return 0;
};
Run Code Online (Sandbox Code Playgroud)

更新:

哇,这是一个很好的答案,感谢您一直以来的投入。对于我现在的处境来说,这三种方法都有点超出我的能力......

不客气。我注意到你有相当多的python经验。指针(等)是一个有点陌生的概念,可能需要一些时间才能掌握。但是,一旦你这样做了,你就会想知道没有他们你是如何相处的。

但 ...

如果您建议从三种方法中选择一种开始,会是哪一种?

好吧,通过消除过程......

因为选项 #1 使用void *指针,所以我会消除它,因为选项 #2 类似,但具有一定的类型安全性。

我会消除选项#2,因为该switch方法要求泛型/通用函数必须“了解”所有可能的类型(即,我们需要每个通用函数都有一个并且对于每个switch可能的类型它都必须有一个。因此,它的可扩展性或可扩展性不太好。case

这给我们留下了选项#3。

请注意,我们一直在做的事情与继承c++类所做的事情有些相似[尽管采用了更详细的方式]。

这里所说的Common将被称为“基”类。Car并且Animal将是 的“派生”类Common

在这种情况下,c++将[不可见地]将Vtable指针放置为结构的透明/隐藏的第一个元素。它可以通过选择正确的功能来处理所有的魔法。

为什么 aVtable是个好主意的另一个原因是,它可以轻松地向结构添加新函数/功能。

对于这种无定形/异构集合,组织它的一个好方法是使用双向链表。

然后,一旦我们有了一个列表,我们常常希望对其进行排序

因此,我创建了另一个版本来实现简单的双向链表结构。

而且,我添加了一个简单/粗略/缓慢的函数来对列表进行排序。为了能够比较不同类型,我添加了一个Vtable条目来比较列表项。

因此,添加新功能很容易。而且,我们可以很容易地添加新类型。

...小心你的愿望——你可能真的得到它:-)

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

typedef struct Vtable Vtable;

typedef struct Common {
    int type;
    Vtable *vtbl;
    struct Common *prev;
    struct Common *next;
    const char *name;
} Common;

typedef struct List {
    Common *head;
    Common *tail;
    int count;
} List;

enum {
    TYPE_CAR,
    TYPE_ANIMAL,
};

typedef struct Car {
    Common comm;
    unsigned int cost;
} Car;
void car_print(const Common *obj);
int car_compare(const Common *lhs,const Common *rhs);

typedef struct Animal {
    Common comm;
    unsigned int age;
    unsigned int weight;
} Animal;
void animal_print(const Common *obj);
int animal_compare(const Common *lhs,const Common *rhs);

typedef struct Vtable {
    void (*vtb_print)(const Common *comm);
    int (*vtb_compare)(const Common *lhs,const Common *rhs);
} Vtable;

void
car_print(const Common *obj)
{
    Car *car = (Car *) obj;

    printf("The cost is: %d\n",car->cost);
}

int
car_compare(const Common *lhsp,const Common *rhsp)
{
    const Car *lhs = (const Car *) lhsp;
    const Car *rhs = (const Car *) rhsp;
    int cmp;

    cmp = lhs->cost - rhs->cost;

    return cmp;
}

Vtable car_vtbl = {
    .vtb_print = car_print,
    .vtb_compare = car_compare
};

Common *
car_new(const char *name,int cost)
{
    Car *car = malloc(sizeof(*car));
    Common *comm = &car->comm;

    comm->name = name;
    comm->type = TYPE_CAR;
    comm->vtbl = &car_vtbl;

    car->cost = cost;

    return comm;
}

Vtable animal_vtbl = {
    .vtb_print = animal_print,
    .vtb_compare = animal_compare
};

void
animal_print(const Common *obj)
{
    const Animal *animal = (const Animal *) obj;

    printf("The age is: %d\n",animal->age);
    printf("The weight is: %d\n",animal->weight);
}

int
animal_compare(const Common *lhsp,const Common *rhsp)
{
    const Animal *lhs = (const Animal *) lhsp;
    const Animal *rhs = (const Animal *) rhsp;
    int cmp;

    do {
        cmp = lhs->age - rhs->age;
        if (cmp)
            break;

        cmp = lhs->weight - rhs->weight;
        if (cmp)
            break;
    } while (0);

    return cmp;
}

Common *
animal_new(const char *name,int age,int weight)
{
    Animal *animal = malloc(sizeof(*animal));
    Common *comm = &animal->comm;

    comm->name = name;
    comm->type = TYPE_ANIMAL;
    comm->vtbl = &animal_vtbl;

    animal->age = age;
    animal->weight = weight;

    return comm;
}

void
common_print(const Common *comm)
{

    printf("The name is: %s\n", comm->name);

    comm->vtbl->vtb_print(comm);
}

int
common_compare(const Common *lhs,const Common *rhs)
{
    int cmp;

    do {
        cmp = lhs->type - rhs->type;
        if (cmp)
            break;

        cmp = strcmp(lhs->name,rhs->name);
        if (cmp)
            break;

        cmp = lhs->vtbl->vtb_compare(lhs,rhs);
        if (cmp)
            break;
    } while (0);

    return cmp;
}

List *
list_new(void)
{
    List *list = calloc(1,sizeof(*list));

    return list;
}

void
list_add(List *list,Common *comm)
{
    Common *tail;

    tail = list->tail;

    comm->prev = tail;
    comm->next = NULL;

    if (tail == NULL)
        list->head = comm;
    else
        tail->next = comm;

    list->tail = comm;
    list->count += 1;
}

void
list_unlink(List *list,Common *comm)
{
    Common *next;
    Common *prev;

    next = comm->next;
    prev = comm->prev;

    if (list->head == comm)
        list->head = next;

    if (list->tail == comm)
        list->tail = prev;

    if (next != NULL)
        next->prev = prev;
    if (prev != NULL)
        prev->next = next;

    list->count -= 1;

    comm->next = NULL;
    comm->prev = NULL;
}

void
list_sort(List *listr)
{
    List list_ = { 0 };
    List *listl = &list_;
    Common *lhs = NULL;
    Common *rhs;
    Common *min;
    int cmp;

    while (1) {
        rhs = listr->head;
        if (rhs == NULL)
            break;

        min = rhs;
        for (rhs = min->next;  rhs != NULL;  rhs = rhs->next) {
            cmp = common_compare(min,rhs);
            if (cmp > 0)
                min = rhs;
        }

        list_unlink(listr,min);
        list_add(listl,min);
    }

    *listr = *listl;
}

void
list_rand(List *listr)
{
    List list_ = { 0 };
    List *listl = &list_;
    Common *del;
    int delidx;
    int curidx;
    int cmp;

    while (listr->count > 0) {
        delidx = rand() % listr->count;

        curidx = 0;
        for (del = listr->head;  del != NULL;  del = del->next, ++curidx) {
            if (curidx == delidx)
                break;
        }

        list_unlink(listr,del);
        list_add(listl,del);
    }

    *listr = *listl;
}

void
sepline(void)
{

    for (int col = 1;  col <= 40;  ++col)
        fputc('-',stdout);
    fputc('\n',stdout);
}

void
list_print(const List *list,const char *reason)
{
    const Common *comm;
    int sep = 0;

    printf("\n");
    sepline();
    printf("%s\n",reason);
    sepline();

    for (comm = list->head;  comm != NULL;  comm = comm->next) {
        if (sep)
            fputc('\n',stdout);
        common_print(comm);
        sep = 1;
    }
}

int
main(void)
{
    List *list;
    Common *animal;
    Common *car;

    list = list_new();

    animal = animal_new("Dog",10,200);
    list_add(list,animal);
    animal = animal_new("Dog",7,67);
    list_add(list,animal);
    animal = animal_new("Dog",10,67);
    list_add(list,animal);

    animal = animal_new("Cat",10,200);
    list_add(list,animal);
    animal = animal_new("Cat",10,133);
    list_add(list,animal);
    animal = animal_new("Cat",9,200);
    list_add(list,animal);

    animal = animal_new("Dog",10,200);

    car = car_new("Ford",50000);
    list_add(list,car);
    car = car_new("Chevy",26240);
    list_add(list,car);
    car = car_new("Tesla",93000);
    list_add(list,car);
    car = car_new("Chevy",19999);
    list_add(list,car);
    car = car_new("Tesla",62999);
    list_add(list,car);

    list_print(list,"Unsorted");

    list_rand(list);
    list_print(list,"Random");

    list_sort(list);
    list_print(list,"Sorted");

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

这是程序输出:

----------------------------------------
Unsorted
----------------------------------------
The name is: Dog
The age is: 10
The weight is: 200

The name is: Dog
The age is: 7
The weight is: 67

The name is: Dog
The age is: 10
The weight is: 67

The name is: Cat
The age is: 10
The weight is: 200

The name is: Cat
The age is: 10
The weight is: 133

The name is: Cat
The age is: 9
The weight is: 200

The name is: Ford
The cost is: 50000

The name is: Chevy
The cost is: 26240

The name is: Tesla
The cost is: 93000

The name is: Chevy
The cost is: 19999

The name is: Tesla
The cost is: 62999

----------------------------------------
Random
----------------------------------------
The name is: Ford
The cost is: 50000

The name is: Chevy
The cost is: 26240

The name is: Dog
The age is: 10
The weight is: 200

The name is: Cat
The age is: 10
The weight is: 133

The name is: Dog
The age is: 10
The weight is: 67

The name is: Cat
The age is: 10
The weight is: 200

The name is: Cat
The age is: 9
The weight is: 200

The name is: Dog
The age is: 7
The weight is: 67

The name is: Tesla
The cost is: 93000

The name is: Tesla
The cost is: 62999

The name is: Chevy
The cost is: 19999

----------------------------------------
Sorted
----------------------------------------
The name is: Chevy
The cost is: 19999

The name is: Chevy
The cost is: 26240

The name is: Ford
The cost is: 50000

The name is: Tesla
The cost is: 62999

The name is: Tesla
The cost is: 93000

The name is: Cat
The age is: 9
The weight is: 200

The name is: Cat
The age is: 10
The weight is: 133

The name is: Cat
The age is: 10
The weight is: 200

The name is: Dog
The age is: 7
The weight is: 67

The name is: Dog
The age is: 10
The weight is: 67

The name is: Dog
The age is: 10
The weight is: 200
Run Code Online (Sandbox Code Playgroud)