问题
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);
};
Specifically, the print_struct
method:
- Is this possible to do, and if so how?
- Is this considered good or bad practice in C to create non-type-specific functions?
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);
回答1:
There are many different ways that you can do this.
Usually, defining a "common" struct for common information that also has a type
field.
Option #1:
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;
};
Option #2:
Passing around a void *
pointer isn't as type safe as it could be.
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;
};
Option #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;
};
UPDATE:
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...
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.
But ...
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 ...
Because option #1 uses void *
pointers, I'd eliminate that because option #2 is similar but has some type safety.
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.
That leaves us with option #3.
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].
What is called Common
here would be termed the "base" class. Car
and Animal
would be "derived" classes of Common
.
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.
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.
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.
... Be careful what you wish for--you may actually get it :-)
#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
来源:https://stackoverflow.com/questions/65621027/writing-a-generic-struct-print-method-in-c