20211018080620 - Objects in C

We can use C for Object-Oriented Programming (OOP). Although C does not support OOP natively, we can implement the missing features ourselves to support it.

# Classes

We count with the keyword `struct` in the C language to create custom data types (a.k.a. structures) containing other datatypes as members.

In OOP, classes are also custom data types with two types of members:

- attributes (or data members, or member variables), and
- methods (or member functions).

When using C's structures, we can implement attributes as variables and methods as function pointers.

## The `Dog` class

Consider that we want to create a `Dog` class that has the following members:

- `name`: a string containing the name of the dog.
- `play()`: a function that prints on the screen that the dog is playing.
- `bark()`: a function that prints on the screen that the dog is barking.

```
.----------------.
| Dog            |
|----------------|
| + name:char*   |
|----------------|
| + play()       |
| + bark()       |
'----------------'
```

We can declare such a class as shown in Listing 1.

```c

struct Dog {
    char * name;
    void (*play)(struct Dog * self);
    void (*bark)(struct Dog * self);
};

```

**Listing 1**. 

Notice that the member functions have a reference to a Dog structure pointer "self". Member functions need this pointer to know over which instance they are acting on. A function without such a pointer, would not be able to access the rest of the members of the class.

## Method implementations

Now, let's implement the two methods: play and bark.


```c

void play(struct Dog * self) {
  printf(
    "%s, the dog, is playing.\n",
    self->name);
}

void bark(struct Dog * self) {
  printf(
    "%s ways 'woof!'.\n",
    self->name);
}

```

**Listing 2**.

## `Dog` objects

To use our `Dog` class in an application, we will need to create objects out of it. And to do so, we must provide the following set of functions:

- a **creator**: provides a pointer to a memory block big enough to store the object information.
- an **initializer**: initializes the memory given by the creator function with the initial attribute values and sets the method pointers to the implementations.
- a **destroyer**: frees the memory block that our object is using.

```c

struct Dog * createDog(){
  struct Dog * newDog;
  newDog = (struct Dog *)malloc(
    sizeof(struct Dog));
  memset(
    newDog,
    0,
    sizeof(struct Dog));
  return newDog;
}

void initDog(
  struct Dog * dog,
  char * name
) {
  unsigned int nameLength;
  nameLength = strlen(name);
  dog->name = (char*)malloc(
    nameLength + 1);
  strncpy(
    dog->name,
    name,
    nameLength);
  dog->play=play;
  dog->bark=bark;
}

void destroyDog(
  struct Dog** dog
) {
  if ((*dog)->name) {
    free((*dog)->name);
  }
  free(*dog);
  *dog = NULL;
}

```

**Listing 3**.

**Other creation options**. Object creation functions may use more allocation options than just calling malloc. In embedded systems, often static allocation will be a better option. In such a case, we could declare an array of  ̀Dog` structures and mark them as busy or free as we call `createDog()` and `destroyDog()` respectively.

## Example application

Now we can use our Dog class to create objects.

```c

int main() {
  struct Dog * astro = createDog();
  initDog(astro, "Astro");
  astro->play(astro);
  astro->bark(astro);
  destroyDog(&astro);
  return 0;
}

```

**Listing 4**.

## Full code

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

struct Dog {
    char * name;
    void (*play)(struct Dog * self);
    void (*bark)(struct Dog * self);
};

void play(struct Dog * self) {
  printf(
    "%s, the dog, is playing.\n",
    self->name);
}

void bark(struct Dog * self) {
  printf(
    "%s ways 'woof!'.\n",
    self->name);
}

struct Dog * createDog(){
  struct Dog * newDog;
  newDog = (struct Dog *)malloc(
    sizeof(struct Dog));
  memset(
    newDog,
    0,
    sizeof(struct Dog));
  return newDog;
}

void initDog(
  struct Dog * dog,
  char * name
) {
  unsigned int nameLength;
  nameLength = strlen(name);
  dog->name = (char*)malloc(
    nameLength + 1);
  strncpy(
    dog->name,
    name,
    nameLength);
  dog->play=play;
  dog->bark=bark;
}

void destroyDog(
  struct Dog** dog
) {
  if ((*dog)->name) {
    free((*dog)->name);
  }
  free(*dog);
  *dog = NULL;
}

int main() {
  struct Dog * astro = createDog();
  initDog(astro, "Astro");
  astro->play(astro);
  astro->bark(astro);
  destroyDog(&astro);
  return 0;
}
```

**Listing 5**.

## Standard output

```
Astro, the dog, is playing.
Astro ways 'woof!'.
```

**Listing 6**.

Comentarios

Entradas más populares de este blog

10 palabras valen más que una imagen - 20240526223027

20220214085408 - El reto de hacer amistades nuevas en la vida adulta

20220208192042 - Los modelos abstractos: sólo una cara del diamante