Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'class' behavior in C

Tags:

c

oop

struct

Say I have this:

struct Person {
    char *name;
    char *occupation;
    int years_of_service;
    float salary;
};

and this:

float calculate_salary(struct Person *who){
    float basesalary;

    char *doctor = "Doctor";
    char *janitor = "Janitor";

    int job1 = strcmp(who->occupation, doctor);
    int job2 = strcmp(who->occupation, janitor);

    if(job1 == 0){
        basesalary = 10000.0;
    }
    else if(job2 == 0){
        basesalary = 800.0;
    }

    return basesalary + basesalary*(who->years_of_service*0.1);
}

What's the proper way to calculate the salary of person?

in Python I would do it in the init:

self.salary = self.calculate_salary()

But since C is not OO, I assume I have to first create the person without a salary, and set the salary after. Like this:

struct Person *joe = Person_create("Joe Alex", "Doctor",1);
joe->salary = calculate_salary(joe);

But I would like someone with a better understand of C to tell me if that's the proper way.

As a side note, is the string compassion correct? I find this very weird, should I use switch instead?

like image 423
f.rodrigues Avatar asked Dec 06 '22 23:12

f.rodrigues


2 Answers

What's the proper way to calculate the salary of person?

What you are doing is using a plain struct as object and passing it to the function by pointer. You are already using an object oriented approach in C.

If you are interested in achieving the effect like you showed in python, then add a pointer to function calculate_salary in the structure Person.

struct Person {
    char *name;
    char *occupation;
    int years_of_service;
    float salary;
    float (*fptr)(struct Person *); // fptr is a function pointer
};    

Here is a driver program:

int main(void)
{
    struct Person *joe = malloc(sizeof(struct Person));
    joe->name = "Joe Alex";
    joe->occupation = "Doctor";
    joe->years_of_service = 1;

    joe->fptr = calculate_salary; //Function pointer Assignment
    (*joe).salary = (*joe).fptr(joe);
    printf("%f", joe->salary);
}  

One thing you should note that, the function pointer used in the above struct is mainly used to write callback methods.

like image 173
haccks Avatar answered Dec 08 '22 13:12

haccks


Writing a simple getter in C

The basic purpose of a class in object oriented languages is to provide a set of operations for objects of the same type. Most often the operations are in the form of methods. If we ignore advanced concepts that are apparently not needed for your example, a method is just a function or subroutine that operates on an object of the specific type.

Such methods can be implemented in C just as any other function and that is exactly what your implementation of calculate_salary() is. You may want to rename it to follow the convention that such functions in C are prefixed by the type name, e.g. person_get_salary().

Implementing the class

You shouldn't need a salary field in the struct at all as you will access it using the person_get_salary() function (or method).

struct person {
    char *name;
    char *occupation;
    int years_of_service;
};

float person_get_salary(struct person *person)
{
    ...
}

And then simply use it.

float salary = person_get_salary(person);

The above is the C syntax for the following pseudo code for a method call.

float salary = person.get_salary();

Or for the following properties based pseudo code. Properties look like fields in the syntax but are implemented using getter and setter methods in the background.

float salary = person.salary;

There are still two main ways to work with those structures, as C is very flexible with implementations of various bits of OOP.

Object allocated on the stack

The caller can provide storage for the person. The advantage is that the storage can well be on the stack.

struct person person;

person_init(&person, ...);

float salary = person_get_salary(&person);

...

person_cleanup(&person);

Object allocated on the heap

The implementation provides storage and allocates the object on the heap.

struct person *person = person_new();

float salary = person_get_salary(person);

...

person_free(person);

Implementation notes

You need to implement person_init() or person_new() and person_cleanup() or person_free() accordingly. Note that the main difference is whether the implementation allocates and frees the memory for the object. The strings for name and occupation are typically allocated and freed by the implementation in both cases.

Notes on function pointers

There's another answer talking about function pointers that addresses a different aspect of the question.

As shown in this answer, simple methods can be implemented as C functions that accept a pointer to the object as its first argument. Function pointers are useful for advanced features like polymorphism through virtual methods. But even then the function pointers are stored in a separate structure (often called a virtual method table) that is shared among all objects of the respective type.

There are cases when you may want to store function pointers directly in your objects, but those are typically used for callbacks and not as the trivial getters.

Alternative approach

An alternative way is to actually use the salary field and precompute it at the time of object creation and/or modification. That way the caller would just read the salary field from the structure and the accessor function (or method) wouldn't be necessary.

like image 44
Pavel Šimerda Avatar answered Dec 08 '22 11:12

Pavel Šimerda