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);
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
Related
My code is following:
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
#include<math.h>
struct stack {
int data;
struct stack* next;
};
struct Student
{
unsigned long int rollnumber;
char name[100];
struct Student *next;
}* head;
// Utility function to initialize stack
void initStack(struct stack** s) { *s = NULL; }
// Utility function to check if stack is empty
int isEmpty(struct stack* s)
{
if (s == NULL)
return 1;
return 0;
}
// Utility function to push an item to stack
void push(struct stack** s, int x, unsigned long int rollnumber, char *name)
{
struct stack* p = (struct stack*)malloc(sizeof(*p));
if (p == NULL) {
fprintf(stderr, "Memory allocation failed.\n");
return;
}
p->data = x;
p->next = *s;
*s = p;
}
// Utility function to remove an item from stack
int pop(struct stack** s)
{
int x;
struct stack* temp;
x = (*s)->data;
temp = *s;
(*s) = (*s)->next;
free(temp);
return x;
}
// Function to find top item
int top(struct stack* s) { return (s->data); }
// Recursive function to insert an item x in sorted way
void sortedInsert(struct stack** s, int x, unsigned long int rollnumber, char *name)
{
// Base case: Either stack is empty or newly inserted
// item is less than top (more than all existing)
if (isEmpty(*s) || x < top(*s)) {
push(s, x, rollnumber, name);
return;
}
else{
// If top is less, remove the top item and recur
int temp = pop(s);
sortedInsert(s, x, rollnumber, name);
// Put back the top item removed earlier
push(s, temp, rollnumber, name);}
}
// Function to sort stack
void sortStack(struct stack** s)
{
// If stack is not empty
char *name;
unsigned long int rollnumber;
if (!isEmpty(*s)) {
// Remove the top item
int x = pop(s);
// Sort remaining stack
sortStack(s);
// Push the top item back in sorted stack
sortedInsert(s, x, rollnumber, name);
}
}
// Utility function to print contents of stack
void printStack(struct stack* s, unsigned long int rollnumber, char* name)
{
struct Student* student = (struct Student *) malloc(sizeof(struct Student));
struct Student* temp = head;
while (s && temp!=NULL) {
printf("%lu %s\n", temp->rollnumber, temp->name);
s = s->next;
temp = temp->next;
}
printf("\n");
}
void insert(unsigned long int rollnumber, char* name)
{
struct Student * student = (struct Student *) malloc(sizeof(struct Student));
student->rollnumber = rollnumber;
strcpy(student->name, name);
student->next = NULL;
if(head==NULL){
// if head is NULL
// set student as the new head
head = student;
}
else{
// if list is not empty
// insert student in beginning of head
student->next = head;
head = student;
}
}
void Delete(unsigned long int rollnumber)
{
struct Student * temp1 = head;
struct Student * temp2 = head;
while(temp1!=NULL){
if(temp1->rollnumber==rollnumber){
printf("Record with roll number %lu Found\n", rollnumber);
if(temp1==temp2){
// this condition will run if
// the record that we need to delete is the first node
// of the linked list
head = head->next;
free(temp1);
}
else{
// temp1 is the node we need to delete
// temp2 is the node previous to temp1
temp2->next = temp1->next;
free(temp1);
}
printf("Record successfully deleted\n");
return;
}
temp2 = temp1;
temp1 = temp1->next;
}
printf("Student with roll number %lu is not found\n", rollnumber);
}
void display()
{
struct Student * temp = head;
while(temp!=NULL){
printf("Roll Number: %lu\n", temp->rollnumber);
printf("Name: %s\n", temp->name);
temp = temp->next;
}
}
int main(void)
{
head = NULL;
int choice;
int fc_,id_,year_, fc_year;
char name[100];
struct student* s;
unsigned long int rollnumber;
struct stack* faculty;
struct stack* year;
struct stack* id;
initStack(&faculty);
initStack(&year);
initStack(&id);
printf("1 - Enter school number\n2 - Display school numbers by ID\n3 - Display school numbers sorted by year\n4 - Display school numbers sorted by the faculty codes\n5 - Delete a record by school number\n6 - Exit");
do
{
printf("\nEnter Choice: ");
scanf("%d", &choice);
switch (choice)
{
case 1:
printf("Enter roll number: ");
scanf("%lu", &rollnumber);
fc_ = rollnumber/pow(10, 6);
id_ = rollnumber%(10*10*10*10);
fc_year = rollnumber/pow(10,4);
year_ = fc_year%(100);
printf("Enter name: ");
scanf("%s", name);
insert(rollnumber, name);
push(&faculty, fc_, rollnumber, name);
push(&year, year_, rollnumber, name);
push(&id, id_, rollnumber, name);
break;
case 2:
printf("Sorted by ID: \n");
sortStack(&id);
printStack(id, rollnumber, name);
break;
case 3:
printf("Sorted by year: \n");
sortStack(&year);
printStack(year, rollnumber, name);
break;
case 4:
printf("Sorted by faculty code: \n");
sortStack(&faculty);
printStack(faculty, rollnumber, name);
break;
case 5:
printf("Enter roll number to delete: ");
scanf("%lu", &rollnumber);
Delete(rollnumber);
break;
case 6:
break;
}
} while (choice != 6);
}
When, for example, the choice is 2, I want to display the student name and whole rollnumber in ascending order. But when I run it that is what I get (C, B, A are the names given by me):
Enter Choice: 2
705102020 C
705102010 B
705102005 A
I am sure that I sorted it correctly, but probably my printStack function is not working properly. How can I reverse this?
It would be better to define a stack struct independantly of the rest of your code:
struct stack {
void *data;
struct stack *next;
};
void stack_init(struct stack **ss)
{
*ss = NULL;
}
bool stack_push(struct stack **ss, void *data)
{
struct stack *node = malloc(sizeof *node);
if (!node) return false;
node->data = data; // Copy address, not data
node->next = *ss;
*ss = node;
return true;
}
bool stack_pop(struct stack **ss, void **data)
{
if (!*ss) return false;
struct stack *rm = *ss;
*ss = (*ss)->next;
if (data) *data = rm->data; // If nothing is provided (i.e. NULL), data will leak if it was allocated dynamically.
free(rm);
return true;
}
bool stack_top(const struct stack *ss, void **data)
{
if (!ss) return false;
*data = ss->data;
return true;
}
void stack_print(const struct stack *ss, void print(const struct stack*))
{
for (const struct stack *sp = ss; sp; sp = sp->next)
print(sp);
}
You can implement your stack sorting function like that:
struct stack *stack_find_min(struct stack *ss, int (*cmp)(const void*, const void*))
{
struct stack *min = ss;
for (struct stack *sp = ss; sp; sp = sp->next)
if (cmp(sp->data, min->data) < 0)
min = sp;
return min;
}
void stack_sort(struct stack *ss, int (*cmp)(const void*, const void*))
{
if (!ss) return;
for (struct stack *sp = ss; sp; sp = sp->next) {
struct stack *min = stack_find_min(sp, cmp);
swap(&sp->data, &min->data);
}
}
swap() swaps two pointers:
void swap(void **p1, void **p2)
{
void *tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
After that, define your student data structure:
struct student {
unsigned long rollnumber;
char name[100];
struct student *next;
};
struct student *student_insert(struct student **head, unsigned long rollnumber, const char *name)
{
struct student *st = malloc(sizeof(*st));
if (!st) return NULL;
st->rollnumber = rollnumber;
strncpy(st->name, name, 99);
st->next = *head;
*head = st;
return st;
}
void student_print(const struct stack *ss)
{
struct student *st = ss->data;
printf("%ld\t%s\n", st->rollnumber, st->name);
}
int student_compare_id(const void *s1, const void *s2)
{
// MUST cast void* into (struct student*) first.
const struct student *student1 = s1;
const struct student *student2 = s2;
unsigned long rollnumber1 = student1->rollnumber;
unsigned long rollnumber2 = student2->rollnumber;
unsigned long id1 = rollnumber1 % 10000;
unsigned long id2 = rollnumber2 % 10000;
return id1 == id2 ? 0 : (id1 < id2 ? -1 : 1);
}
If you want to test the above:
int main(void)
{
struct stack *ss;
stack_init(&ss);
struct student *st = NULL;
student_insert(&st, 100181234, "James");
student_insert(&st, 200195678, "John");
student_insert(&st, 300200324, "Goku");
student_insert(&st, 400214321, "Taylor");
for (struct student *p = st; p; p = p->next)
stack_push(&ss, p);
puts("Unsorted stack:");
stack_print(ss, student_print);
stack_sort(ss, student_compare_id);
puts("\nSorted by ID:");
stack_print(ss, student_print);
// Don't forget to free the memory allocated for students
// and pop all nodes of the stack (ommited here).
}
Output:
Unsorted stack:
100181234 James
200195678 John
300200324 Goku
400214321 Taylor
Sorted by ID:
300200324 Goku
100181234 James
400214321 Taylor
200195678 John
Few more things:
Don't use scanf() to read user input. fgets() is safer.
You can replace pow(10, 4) directly by 10000. Why wasting CPU cycles to calculate constants?
In your insert() function, there is no need to test for *head == NULL. The code in the else statement handles this case.
EDIT: In student_compare_id(), the const void* pointers MUST BE CASTED to const struct student* first before dereferencing. The previous version seemed to work because the rollnumber happened to be the first field in the struct. That was an undefined behavior in all its glory. Fixed that now.
Here is the previous version that NO ONE SHOULD USE:
int student_compare_id(const void *s1, const void *s2)
{
unsigned long rollnumber1 = *(unsigned long*)s1; // WRONG!
unsigned long rollnumber2 = *(unsigned long*)s2; // WRONG!
unsigned long id1 = rollnumber1 % 10000;
unsigned long id2 = rollnumber2 % 10000;
return id1 == id2 ? 0 : (id1 < id2 ? -1 : 1);
}
The primary problem is that there is no connection between your stack(s) and your student list, but the code assumes that there is. You can reorder the stacks however you like, but that does not reorder the list. When you print, the students simply come out in the reverse of their insertion order (because you insert at the front of the list, and then traverse the list from front to back).
You have two main alternatives:
combine the list and all the stacks into one linked list. You can use this both list-style and stack-style if you like. OR
Give the stack nodes pointers to the corresponding list elements, and traverse those when you print a stack to get the students in the order implied by the stack.
Suppose I have two structures in my code like this:
typedef struct Food {
char* name;
int food_id;
int price;
int capacity;
int hall_id;
int day;
int reserved;
int profit;
Food* next;
} Food;
typedef struct Coupon {
int id;
int percentage;
int capacity;
Coupon* next;
} Coupon;
And I want to implement a linked list data structure with them. For example I have a Food* variable which points to food number 1 and then food number 2 in next points to the next food and...
The problem is when I want to write functions for the linked lists, I have to write 2 functions for every job. For example I want to have a function that gets the head of the list and a new element, then add the new element to the list. Because the types of these two linked lists are different, I can't think of a way to write only one function for both. Is there a way to do so?
For example I want to make this function work for all types:
void add_front(Coupon* head, Coupon* new_el){
while (head->next != NULL){
head = head->next;
}
head->next = new_el;
new_el->next = NULL;
}
First, you separate the domain data of each entry from the managing data.
typedef struct {
char* name;
int food_id;
int price;
int capacity;
int hall_id;
int day;
int reserved;
int profit;
} Food;
typedef struct {
int id;
int percentage;
int capacity;
} Coupon;
Then you can use a union with pointers to the domain data in the entry's structure. Each entry will be of the same size.
typedef struct Entry {
struct Entry* next;
union {
Food* food;
Coupon* coupon;
} data;
} Entry;
You could even place the domain data directly in the union, but this will waste memory if only small sized values are stored.
typedef struct Entry {
struct Entry* next;
union {
Food food;
Coupon coupon;
} data;
} Entry;
Now you are able to add new entries of different data with a generic function.
void add_front(Entry* head, Entry* new_el) {
while (head->next != NULL){
head = head->next;
}
head->next = new_el;
new_el->next = NULL;
}
A possible trick is to use the fact that it is legal to convert a pointer to a struct to a pointer to its initial member, and that it is legal to convert from any pointer type to void * and back. So provided next is the first member, a number of functions could be independant of the actual class, if they take void * parameters for any struct for which the first element is a next pointer. Of course, auxiliary function able to handle a real object should be provided...
Here is an example code showing a possible implementation of add_before, add_after, list_remove (remove is defined in stdio.h) and display and showing an example of use with Coupon objects:
#include <stdio.h>
typedef struct Food {
struct Food* next;
char* name;
int food_id;
int price;
int capacity;
int hall_id;
int day;
int reserved;
int profit;
} Food;
typedef struct Coupon {
struct Coupon* next;
int id;
int percentage;
int capacity;
} Coupon;
void* add_before(void* list, void* elem) {
*(void **)elem = list;
return elem;
}
void* add_after(void* list, void* elem) {
if (NULL == list) return elem;
void** last = list;
while (*last != NULL) {
last = *last;
}
*last = elem;
return list;
}
// eltdisplay is a pointer to a function able to display an element
void display(void* list, void (*eltdisplay)(void*, FILE *), FILE *out) {
while (NULL != list) {
eltdisplay(list, out);
if (NULL != *(void **)list) {
fprintf(out, " -> ");
}
list = *(void **)list;
}
fprintf(out, "\n");
}
void* list_remove(void* list, void* elem, int(*comp)(void* elt1, void* elt2)) {
if (list == NULL) return NULL;
void** cur = list, **old = NULL;
while (cur != NULL) {
if (0 == comp(cur, elem)) {
if (old == NULL) return *cur;
*old = *cur;
break;
}
old = cur;
cur = *cur;
}
return list;
}
int couponcomp(void* elt1, void* elt2) {
return ((Coupon*)elt1)->id != ((Coupon*)elt2)->id;
}
void coupondisplay(void* elt, FILE *out) {
Coupon* coupon = elt;
fprintf(out, "%d", coupon->id);
}
int main() {
Coupon data[3] = { {NULL, 1}, {NULL, 2}, {NULL, 3} };
Coupon* list = NULL;
for (int i = 0; i < sizeof(data) / sizeof(*data); i++) {
list = addLast(list, data+i);
}
display(list, coupondisplay, stdout);
Coupon data2 = { NULL, 2 };
list = list_remove(list, &data2, couponcomp);
display(list, coupondisplay, stdout);
return 0;
}
It compiles with no warning and displays as expected:
1 -> 2 -> 3
1 -> 3
You could use a macro :
From one of my personal project
#if !defined(CIRCULAR_DOUBLE_LINKED_LIST_H)
#define CIRCULAR_DOUBLE_LINKED_LIST_H
//T must include ->prev and ->next member
#define DECLARE_NAMED_CIRCULAR_DOUBLE_LINKED_LIST(T, name) \
static inline T* name ## _add_after(T* source, T* item) { \
T* last_next = source->next; \
source->next = item; \
item->prev = source; \
item->next = last_next; \
last_next->prev = item; \
return source; \
} \
static inline T* name ## _add_before(T* source, T* item) {\
T* last_prev = source->prev; \
source->prev = item; \
item->next = source; \
item->prev = last_prev; \
last_prev->next = item; \
return source; \
} \
static inline T* name ## _remove(T* item) { \
T* next = item->next; \
item->prev->next = item->next; \
item->next->prev = item->prev; \
return next == item ? NULL : next; \
}
#define DECLARE_CIRCULAR_DOUBLE_LINKED_LIST(T) DECLARE_NAMED_CIRCULAR_DOUBLE_LINKED_LIST(T, list_ ## T)
#endif // CIRCULAR_DOUBLE_LINKED_LIST_H
typedef struct Food {
Food* next;
Food* prev;
char* name;
int food_id;
int price;
int capacity;
int hall_id;
int day;
int reserved;
int profit;
} Food;
DECLARE_CIRCULAR_DOUBLE_LINKED_LIST(Food)
list_Food_add_after(Food*, Food*);
list_Food_add_before(Food*, Food*);
list_Food_remove(Food*);
I think the best way I ever found to do this in C (i.e., without templates) was:
Make a SinglyLinkedNode class that just has SinglyLinkedNode *next
Write your list functions based on this class -- every list is a list of SinglyLinkedNode
Add SinglyLinkedNode node fields to Food and Coupon, and use that to link them together.
Additionally provide functions or macros to get the containing Food or Coupon pointer from a SinglyLinkedNode pointer, like Coupon *couponFromNode(Node *p);
Note that I would never actually do this for singly-linked lists, because singly-linked list operations are so easy to write that you don't really need list methods. This technique starts to get useful for doubly-linked lists or more complex introspective containers.
I have 3 linked lists all of the same type. I am trying to add cards (with value, color, and suit) to these 2 linked list when newGame() is called, or distribute cards between 2 players.
I have tried to create a function to do so for me. However, I am not sure how to deal with the incompatible pointer type warning when insertCardAtFront() is called.
I'm very confused with pointers sometimes, I'd like to store the state of my game(player1,2's hand of cards) in my game struct.
Any help would be much appreciated.
typedef struct _card {
color color; //enum of RED, BLACK
suit suit; //enum of HEARTS,DIAMONS etc..
value value; //enum of ONE,TWO,THREE etc.
}card;
typedef struct _deck{ //linked list of deck of cards
card *cards;
struct _deck *next;
}deck;
typedef struct _player1Hand{ //linked list of player1's hand
card *card;
struct _player1Hand *next;
}player1Hand;
typedef struct _player2Hand{//linked list of player2's hand
card *card;
struct _player2Hand *next;
}player2Hand;
typedef struct _game{ //game struct to store the state of my game
player1Hand *game_player1Hand;
player2Hand *game_player2Hand;
}*Game;
void insertCardAtFront(struct _deck *deck,card card){
struct _deck *newDeck = malloc(sizeof(struct _deck));
newDeck->cards = malloc(sizeof(card));
newDeck->cards->color = card.color;
newDeck->cards->suit = card.suit;
newDeck->cards->value = card.value;
newDeck->next = deck;
deck = newDeck;
}
Game newGame(int deckSize, value values[], color colors[], suit suits[]){
Game nGame = malloc(sizeof(struct _game));
nGame->game_player1Hand = NULL;
nGame->game_player2Hand = NULL;
for(int i = 0;i < deckSize; i++){
insertCardAtFront(nGame->game_player1Hand,newCard(values[i],colors[i],suits[i]));
i++;
insertCardAtFront(nGame->game_player2Hand,newCard(values[i],colors[i],suits[i]));
}
return nGame;
}
Card newCard(value value, color color, suit suit) {
Card new = calloc(1, sizeof(card));
if (new == NULL) {
err(EXIT_FAILURE, "Could not allocate memory for Card");
}
new->value = value;
new->suit = suit;
new->color = color;
return new;
}
Example main function:
int main (void){
init_deck();
}
static void init_deck(void){
int deck_size = 4;
value values[] = {ONE, TWO, THREE, FOUR};
color colors[] = {RED, BLACK, RED, BLACK};
suit suits[] = {HEARTS, DIAMONDS, CLUBS, SPADES};
Game game = newGame(deck_size, values, colors, suits);
}
You are using _ in your identifiers. Avoid that. While not absolutely prohibited, it is not good practice
insertCardAtFront will take an input of type struct deck, but you are giving an input of type player2Hand
void insertCardAtFront(struct _deck *deck,card card)
....
insertCardAtFront(nGame->game_player1Hand,newCard(values[i],colors[i],suits[i]));
You can have a single type of deck and your game can contain two variables of that type.
typedef struct _deck{ //linked list of deck of cards
card *cards;
struct _deck *next;
}deck;
....
typedef struct _game{ //game struct to store the state of my game
deck *game_player1Hand;
deck *game_player2Hand;
}*Game;
In newcard, you are using calloc but you are defining new as a value of type card. You need a pointer to card to store the return of calloc.
Also, in the current code you are returning a local variable.
The fix for newcard is below.
card* newCard(value value, color color, suit suit) {
card *new;
new = calloc(1, sizeof(card));
if (new == NULL) {
err(EXIT_FAILURE, "Could not allocate memory for Card");
}
new->value = value;
new->suit = suit;
new->color = color;
return new;
}
You need to modify the insertCardAtFront function to take parameters of pointers to deck and pointer to card
void insertCardAtFront(struct _deck *deck,card *card){
I think the main issue has already been addressed at this point.
I would just like to propose a recursive solution for this kind of problem:
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#define N_CARDS 52
enum suits {
HEARTS,
DIAMONDS,
CLUBS,
SPADES
};
typedef struct _card {
char suit; //enum of HEARTS,DIAMONS etc..
char value; //enum of ONE,TWO,THREE etc.
}card_t;
typedef struct _deck{ //linked list of deck of cards
card_t *card;
struct _deck *next;
}deck_t;
typedef struct _playerHand{ //linked list of player's hand
card_t *card;
struct _playerHand *nextCard;
}playerHand_t;
typedef struct _game{ //game struct to store the state of my game
playerHand_t *game_player1Hand;
playerHand_t *game_player2Hand;
}game_t;
const char *suitsStr[4];
void printDeck(deck_t *deck);
void createDeck(deck_t *deck, char suit, char value);
void newGame(playerHand_t *player1, playerHand_t *player2);
void dealCards(playerHand_t *player1, playerHand_t *player2, deck_t *deck, int turn);
void printPlayerCards(playerHand_t* player);
int main() {
suitsStr[0] = "HEARTS";
suitsStr[1] = "DIAMONDS";
suitsStr[2] = "CLUBS";
suitsStr[3] = "SPADES";
game_t game;
game.game_player1Hand = NULL;
game.game_player2Hand = NULL;
newGame(game.game_player1Hand, game.game_player2Hand);
//need to free the allocated memory!
return 0;
}
void newGame(playerHand_t *player1, playerHand_t *player2) {
deck_t *deck = NULL;
deck = malloc(sizeof(deck_t));
deck->card = NULL;
createDeck(deck, HEARTS, 1);
printf("The deck to deal is:\n\r");
printDeck(deck);
printf("\n\r");
player1 = malloc(sizeof(playerHand_t));
player2 = malloc(sizeof(playerHand_t));
player1->card = NULL;
player1->nextCard = NULL;
player2->card = NULL;
player2->nextCard = NULL;
dealCards(player1, player2, deck, 0);
printf("Player 1 cards:\n\r");
printPlayerCards(player1);
printf("\n\r");
printf("Player 2 cards:\n\r");
printPlayerCards(player2);
}
void printPlayerCards(playerHand_t* player) {
printf("%d %s\n", player->card->value, suitsStr[player->card->suit]);
if (player->nextCard != NULL) {
printPlayerCards(player->nextCard);
}
return;
}
void printDeck(deck_t *deck) {
printf("%d %s\n", deck->card->value, suitsStr[deck->card->suit]);
if (deck->next != NULL) {
printDeck(deck->next);
}
return;
}
void createDeck(deck_t *deck, char suit, char value) {
//need to implement here the algorithm to fill the deck (now just fill in order)
card_t card;
card.suit = suit;
card.value = value;
deck->card = malloc(sizeof(card_t));
memcpy(deck->card, &card, sizeof(card_t));
deck->next = NULL;
if (value == 13) {
if (suit == SPADES) {
return;
} else {
deck->next = malloc(sizeof(deck_t));
createDeck(deck->next, ++suit, 1);
}
} else {
deck->next = malloc(sizeof(deck_t));
createDeck(deck->next, suit, ++value);
}
}
void dealCards(playerHand_t *player1, playerHand_t *player2, deck_t *deck, int turn) {
if (turn % 2 == 0) {
player1->card = malloc(sizeof(card_t));
memcpy(player1->card, deck->card, sizeof(card_t));
if (++turn < N_CARDS) {
if (turn != N_CARDS - 1) {
player1->nextCard = malloc(sizeof(playerHand_t));
player1->nextCard->card = NULL;
player1->nextCard->nextCard = NULL;
dealCards(player1->nextCard, player2, deck->next, turn);
} else {
dealCards(player1, player2, deck->next, turn);
}
}
} else {
player2->card = malloc(sizeof(card_t));
memcpy(player2->card, deck->card, sizeof(card_t));
if (++turn < N_CARDS) {
if (turn != N_CARDS - 1) {
player2->nextCard = malloc(sizeof(playerHand_t));
player2->nextCard->card = NULL;
player2->nextCard->nextCard = NULL;
dealCards(player1, player2->nextCard, deck->next, turn);
} else {
dealCards(player1, player2, deck->next, turn);
}
}
}
return;
}
Please note that an extremely simple algorithm has been used to generated the deck, so the cards are generated in order of value (from 1 to 13 that is king) and suit (hearts, diamonds, clubs and spades). So a shuffling algorithm is required, so you could just generate the deck like this and find a shuffling algorithm to apply to the deck.
Also memory here is allocated but not released! The current program suffers of memory leakage, I left a comment where the memory should be freed but didn't do so.
Im doing this program for managing students enrolled in each subject.
The system is composed by a linked list of subjects and a linked list of students. Each subject has a linked list of pointers for its students.
Each student has a list of pointers to their respective subjects.
None of the linked lists have headers.
Im trying to create the function to build the list of pointers to the subjects of each student but i dont understand how can i create it.
The structs i have so far are:
typedef struct node_subject * ListSubjects;
typedef struct node_subject {
char *name;
struct node_ptrStudent *Students;
ListSubjects next;
}NodeSubject;
typedef struct node_student * ListStudents;
typedef struct node_student {
char *name;
int num;
struct node_ptrSubject *Subjects;
ListStudents next;
}NodeStudent;
typedef struct node_ptrSubject * ListPtrSubjects;
typedef struct node_ptrSubject {
ListSubjects subjects;
ListPtrSubjects next;
}ListPtrSubjects;
typedef node_ptrStudent * ListPtrStudents;
typedef struct node_ptrStudent {
ListStudents student;
ListPtrStudents next;
}ListPtrStudents;
void createListPtrSubjects (ListSubjects);
The Linux kernel style says "It’s a mistake to use typedef for structures and pointers." https://www.kernel.org/doc/html/v4.10/process/coding-style.html#typedefs; whether or not one agrees with this, I think you have to many typedef; it's kind of confusing.
If you were programming a relational database, which you kind of are, this would be an associative entity; see https://en.wikipedia.org/wiki/Associative_entity. Also, make sure that your subjects have a primary key.
For example,
#include <stdlib.h> /* EXIT_ realloc */
#include <limits.h> /* INT_MAX */
#include <errno.h> /* errno */
#include <stdio.h> /* perror printf */
#include <assert.h> /* assert */
struct Subject {
int id; /* Primary key. */
const char *name;
struct Takes *takes_head;
};
struct Student {
int id; /* Primary key. */
const char *name;
struct Takes *takes_head;
};
/* (Weak) Associative entity. */
struct Takes {
int subject_id, student_id; /* Compound key. */
struct Takes *next_subject, *next_student;
};
static struct School {
struct Subject *subjects;
int subjects_no;
struct Student *students;
int students_no;
} school;
static struct Subject *lookup_subject(const int id) {
if(id >= school.subjects_no) return 0;
return school.subjects + id;
}
static struct Subject *add_subject(const char *name) {
struct Subject *subjects, *s;
assert(name);
if(school.subjects_no == INT_MAX) { errno = ERANGE; return 0; }
/* fixme: realloc is slow; eg, double capacity. */
if(!(subjects = realloc(school.subjects,
sizeof *school.subjects * (school.subjects_no + 1)))) return 0;
school.subjects = subjects;
s = school.subjects + school.subjects_no;
s->id = school.subjects_no;
s->name = name;
s->takes_head = 0;
school.subjects_no++;
return s;
}
/* May output in a static buffer. */
static const char *subject_to_string(const int id) {
struct Subject *const subject = lookup_subject(id);
static char str[256] = ""; /* Static buffer. */
if(!subject) { sprintf(str, "<subject id %d>", id); return str; }
return subject->name;
}
static struct Student *lookup_student(const int id) {
if(id >= school.students_no) return 0;
return school.students + id;
}
static struct Student *add_student(const char *name) {
struct Student *students, *s;
assert(name);
if(school.students_no == INT_MAX) { errno = ERANGE; return 0; }
if(!(students = realloc(school.students,
sizeof *school.students * (school.students_no + 1)))) return 0;
school.students = students;
s = school.students + school.students_no;
s->id = school.students_no;
s->name = name;
s->takes_head = 0;
school.students_no++;
printf("%s is assigned id %d.\n", s->name, s->id);
return s;
}
/* May output in a static buffer. */
static const char *student_to_string(const int id) {
struct Student *const student = lookup_student(id);
static char str[256] = ""; /* Static buffer. */
if(!student) { sprintf(str, "<student id %d>", id); return str; }
return student->name;
}
static struct Takes *lookup_takes(const int student_id, const int subject_id) {
const struct Student *const student = lookup_student(student_id);
struct Takes *t;
if(!student) return 0;
for(t = student->takes_head; t && t->subject_id != subject_id; t = t->next_student);
return t;
}
static struct Takes *add_takes(const int student_id, const int subject_id) {
struct Subject *const subject = lookup_subject(subject_id);
struct Student *const student = lookup_student(student_id);
struct Takes *t = lookup_takes(student_id, subject_id);
printf("%s enrols in %s.\n", student_to_string(student_id),
subject_to_string(subject_id));
/* Already have it. */
if(t) return t;
/* Or else make a new. */
if(!subject || !student) { errno = EDOM; return 0; }
if(!(t = malloc(sizeof *t))) return 0;
t->subject_id = subject_id;
t->student_id = student_id;
t->next_subject = subject->takes_head, subject->takes_head = t;
t->next_student = student->takes_head, student->takes_head = t;
return t;
}
static void print_subject(const int subject_id) {
struct Subject *const subject = lookup_subject(subject_id);
struct Takes *t;
printf("_Subject: %s._\n", subject_to_string(subject_id));
if(!subject) return;
for(t = subject->takes_head; t; t = t->next_subject)
printf("%s takes %s.\n", student_to_string(t->student_id),
subject_to_string(t->subject_id));
}
static void print_student(const int student_id) {
struct Student *const student = lookup_student(student_id);
struct Takes *t;
printf("_Student: %s._\n", student_to_string(student_id));
if(!student) return;
for(t = student->takes_head; t; t = t->next_student)
printf("%s takes %s.\n", student_to_string(t->student_id),
subject_to_string(t->subject_id));
}
int main(void) {
const char *why = 0;
do { /* Try. */
struct Subject *subject;
struct Student *student;
int herbology_id, defense_id, potions_id;
int hermione_id, ron_id, harry_id;
why = "Herbology";
if(!(subject = add_subject(why))) break;
herbology_id = subject->id;
why = "Defense";
if(!(subject = add_subject(why))) break;
defense_id = subject->id;
why = "Potions";
if(!(subject = add_subject(why))) break;
potions_id = subject->id;
why = "Hermione";
if(!(student = add_student(why))) break;
hermione_id = student->id;
why = "Ron";
if(!(student = add_student(why))) break;
ron_id = student->id;
why = "Harry";
if(!(student = add_student(why))) break;
harry_id = student->id;
why = "enrol";
if(!add_takes(hermione_id, defense_id)
|| !add_takes(hermione_id, potions_id)
|| !add_takes(hermione_id, herbology_id)
|| !add_takes(ron_id, herbology_id)
|| !add_takes(harry_id, potions_id)) break;
print_subject(herbology_id);
print_subject(defense_id);
print_subject(potions_id);
print_student(hermione_id);
why = 0;
} while(0); if(why) perror(why); /* Catch. */
/* fixme: free memory in 'final' block. Remember to free Takes. */
return why ? EXIT_FAILURE : EXIT_SUCCESS;
}
I am trying to do this data stucture in C. Connect another structure to another structure. Like this:
struct room
{
int roomnumber;
struct room * nextRoom;
struct person * personList;
}*top=NULL,*temp=NULL,top1;
struct person
{
int personnumber;
struct person *next;
}*node=NULL,temp1;
Struct room has a pointer to struct person. I am having a hard time on connecting it to another struct. Is this correct?
Here is my function
void insert()
{
int val;
printf("enter value: ");
scanf("%d",&val);
newnode=create_node(val);
if(top->personList==NULL)
{
top->personList=newnode;
}
else
{
node->next=newnode;
node=newnode;
}
}
Insert a person to the room. Room is like created already. create_node() is the one who does the malloc()
Here's a version that doesn't use globals, which makes your functions much more general and reusable:
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
typedef struct person
{
int personNumber;
struct person *next;
} person; /* NOTE: this is a typedef, not a global declaration like you did */
typedef struct room
{
int roomNumber;
person *personList;
person *lastInList;
struct room *nextRoom;
} room; /* NOTE: this is a typedef, not a global declaration like you did */
person *create_person(int val)
{
person *ret = calloc(1, sizeof(*ret));
if (ret)
{
ret->personNumber = val;
ret->next = NULL;
}
return ret;
}
int room_insert(room *r, int val)
{
person *p = create_person(val);
if (NULL == p)
return -1;
if (NULL != r->personList)
r->lastInList->next = p;
else
r->personList = p;
r->lastInList = p;
return 0;
}
int main(int argc, char **argv)
{
room my_room = { 0 }; /* important! initializes pointers to NULL */
room my_other_room = { 0 }; /* important! initializes pointers to NULL */
person *p;
room_insert(&my_room, 5);
room_insert(&my_other_room, -5);
room_insert(&my_room, 10);
room_insert(&my_other_room, -10);
room_insert(&my_room, 15);
room_insert(&my_other_room, -15);
for (p = my_room.personList; p; p = p->next)
printf("%d\n", p->personNumber);
for (p = my_other_room.personList; p; p = p->next)
printf("%d\n", p->personNumber);
return 0;
}