用 C 编写“通用"结构打印方法 [英] Writing a 'generic' struct-print method in C

查看:40
本文介绍了用 C 编写“通用"结构打印方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Is it possible to do something like the following in C, to print a different-but-similar struct type?

#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);

};

具体来说,print_struct 方法:

  • 这是否可行,如果可以,如何做?
  • 在 C 中创建非类型特定函数的做法是好是坏?

否则,代码中是不是会堆满几十个(甚至几百个?在一个大型项目中)如下所示的函数:

Otherwise, wouldn't the code be littered with dozens (maybe even hundreds? in a large project) of functions that look like:

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

推荐答案

有很多不同的方法可以做到这一点.

There are many different ways that you can do this.

通常,定义一个common"用于公共信息的结构,也有一个 type 字段.

Usually, defining a "common" struct for common information that also has a type field.

选项 1:

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

Here's a version that uses void * pointers and a switch in the print function:

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


选项 2:

传递 void * 指针的类型并不安全.

Passing around a void * pointer isn't as type safe as it could be.

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

Here's a version that uses Common * pointers and a switch in the print function:

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


选项 #3:

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

Here's a version that uses a virtual function callback table:

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


更新:

哇,真是太好了,感谢您一直以来的投入.就我现在所处的位置而言,这三种方法都让我有点不知所措...

wow, that's such a great answer thanks for putting in all the time. All three approaches are a bit over my head for where I'm at now...

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

You're welcome. I notice you have a fair bit of python experience. Pointers (et. al.) are a bit of an alien concept that can take some time to master. But, once you do, you'll wonder how you got along without them.

但是……

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

If there was one approach out of the three that you'd suggest to start with, which would it be?

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

Well, by a process of elimination ...

因为选项 #1 使用 void * 指针,我会消除它,因为选项 #2 类似但具有某种类型安全性.

Because option #1 uses void * pointers, I'd eliminate that because option #2 is similar but has some type safety.

我会消除选项 #2,因为 switch 方法要求通用/通用函数必须知道"关于所有可能的类型(即)我们需要每个通用函数来有一个 switch 并且它必须有一个 case 用于 each 可能的类型.因此,它的可扩展性或可伸缩性不是很强.

I'd eliminate option #2 because the switch approach requires that the generic/common functions have to "know" about all the possible types (i.e.) we need each common function to have a switch and it has to have a case for each possible type. So, it's not very extensible or scalable.

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

That leaves us with option #3.

请注意,我们一直在做的事情与 c++继承 类所做的事情有些相似[尽管方式更加冗长].

Note that what we've been doing is somewhat similar to what c++ does for inherited classes [albeit with in a somewhat more verbose manner].

这里所谓的 Common 将被称为基础".班级.CarAnimal 将被派生";Common 的类.

What is called Common here would be termed the "base" class. Car and Animal would be "derived" classes of Common.

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

In such a situation, c++ would [invisibly] place the Vtable pointer as a transparent/hidden first element of the struct. It would handle all the magic with selecting the correct functions.

作为 Vtable 是一个好主意的另一个原因,它可以很容易地向结构中添加新的函数/功能.

As a further reason as to why a Vtable is a good idea, it makes it easy to add new functions/functionality to the structs.

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

For such an amorphous/heterogeneous collection, a good way to organize this is with a doubly linked list.

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

Then, once we have a list, we often wish to sort it.

所以,我创建了另一个版本来实现一个简单的双向链表结构.

So, I've created another version that implements a simple doubly linked list struct.

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

And, I've added a simple/crude/slow function to sort the list. To be able to compare different types, I added a Vtable entry to compare list items.

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

Thus, it's easy to add new functions. And, we can add new types easily enough.

...小心你想要的——你可能真的会得到它:-)

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


这是程序输出:


Here's the program output:

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

这篇关于用 C 编写“通用"结构打印方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆