Fundamentals of Object-Oriented Programming in C

What is Object-Oriented Programming (OOP)

  • Object: Girlfriend, Boyfriend, Entity (an individual of a class of things)
  • Type: Classification, Category

The four main characteristics of Object-Oriented Programming:

  1. Abstraction: Abstracting things, abstracting properties, abstracting behaviors

    1. Properties: Common characteristics of a class of things, e.g., humans: age, name, ID, gender…
    2. Behaviors: Common behaviors of a class of things, e.g., humans: eating, sleeping, walking (running, jumping), speaking…
  2. Encapsulation

    Combining the abstracted properties and behaviors into a class (structure)

  3. Inheritance

    1. Human -> Man, Woman
    2. Base class (parent class)
    3. Derived class (child class)
    4. The child class inherits from the parent class, thus acquiring all properties and behaviors of the parent class
  4. Polymorphism

    1. Polymorphic behavior can occur between parent and child objects
    2. When a parent class pointer points to a child class object, invoking the parent class’s behavior will invoke the child class’s behavior.
    3. Can respond differently to the same message.

1. Encapsulation

Define a Girl class, properties: age, name, behaviors: speaking, others: outputting its own information

Basic Version:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#include <string.h>
typedef char string[20];
// Forward declaration allows defining pointer variables. Because the struct Girl structure uses Girl to define pointers,
// but at this point, Girl has not been fully defined. So it needs to be declared first.
typedef struct Girl Girl;
typedef struct Girl {
    // Properties (variables)
    int age;
    string name;
    // Behaviors (functions)
    void (*destroy)(Girl* pthis);
    void (*print)(Girl* pthis);
    void (*say)(Girl* pthis);
} Girl;
Girl* createGirl();
Girl* createGirlWithArgs(int age, const string name);
void girl_destroy(Girl* pthis);
void girl_print(Girl* pthis);
void girl_say(Girl* pthis);

int main() {
    Girl* yue = createGirlWithArgs(25, "小月");
// girl_print(yue);
// girl_say(yue);
    yue->print(yue);
    yue->say(yue);
    girl_destroy(yue);
}

void girl_print(Girl* pthis) {
    printf("%d %s\n", pthis->age, pthis->name);
}

void girl_say(Girl* pthis) {
    printf("我是%s,我会说话\n", pthis->name);
}

Girl* createGirl() {
    Girl* girl = calloc(1, sizeof(Girl));
    if (!girl) {
        return NULL;
    }
    girl->destroy = girl_destroy;
    girl->print = girl_print;
    girl->say = girl_say;
    return girl;
}
Girl* createGirlWithArgs(int age, const string name) {
    Girl* girl = createGirl();
    if (!girl) return NULL;
    girl->age = age;
    strcpy(girl->name, name);
    return girl;
}
void girl_destroy(Girl* pthis) {
    free(pthis);
}

Version with hidden this pointer and private data:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#include <string.h>
typedef char string[20];
typedef struct Girl Girl; // Forward declaration allows defining pointer variables

// Class private data;
typedef struct GirlPrivate {
    string addr;
} GirlPrivate;

typedef struct Girl {
    // Properties (variables)
    int age;
    string name;
    GirlPrivate* inner;

    // Behaviors (functions)
    void (*destroy)();
    void (*print)();
    void (*say)();
    int (*getAge)();
    void (*setAge)(int age);
    const char* (*getName)();
    void (*setName)(const char* name);
    const char* (*getAddr)();
    void (*setAddr)(const char* addr);
} Girl;

Girl* pthis = NULL; // Global this pointer, pointing to the current object.
#define G_Cast(obj) (pthis = obj)
Girl* createGirl();
Girl* createGirlWithArgs(int age, const string name, const string addr);
void girl_destroy();
void girl_print();
void girl_say();

int girl_getAge();
void girl_setAge(int age);
const char* girl_getName();
void girl_setName(const char* name);
const char* girl_getAddr();
void girl_setAddr(const char* addr);

int main() {
    // Instantiate object
    Girl* yue = createGirlWithArgs(25, "小月", "北京路");
    yue->print();
    yue->say();

    Girl* mei = createGirlWithArgs(18, "小美", "上海路");
    mei->print();
    mei->say();

    // Since the user does not know about private data, it cannot be accessed, only through functions.
    G_Cast(yue)->setName("小月月");

    G_Cast(yue)->print();
    G_Cast(mei)->print();

    G_Cast(yue)->destroy();
    G_Cast(mei)->destroy();

    return 0;
}

void girl_print() {
    printf("%d %s\n", pthis->age, pthis->name);
}

void girl_say() {
    printf("我是%s,我会说话\n", pthis->name);
}

Girl* createGirl() {
    Girl* girl = (Girl*)calloc(1, sizeof(Girl));
    if (!girl) {
        return NULL;
    }
    girl->inner = (GirlPrivate*)calloc(1, sizeof(GirlPrivate));
    assert(girl->inner != NULL);

    girl->destroy = girl_destroy;
    girl->print = girl_print;
    girl->say = girl_say;

    girl->getAge = girl_getAge;
    girl->setAge = girl_setAge;
    girl->getName = girl_getName;
    girl->setName = girl_setName;
    girl->getAddr = girl_getAddr;
    girl->setAddr = girl_setAddr;

    pthis = girl; // pthis pointer points to this object
    return girl;
}
Girl* createGirlWithArgs(int age, const string name, const string addr) {
    Girl* girl = createGirl();
    if (!girl) return NULL;
    girl->age = age;
    strcpy(girl->name, name);
    strcpy(((GirlPrivate*)girl->inner)->addr, addr);
    return girl;
}
void girl_destroy() {
    free(pthis->inner);
    free(pthis);
    pthis = NULL;
}

Code needs to be organized by the user when used.

2. Inheritance

Taking shapes as an example

#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <malloc.h>
typedef double real;
#define SUPER(obj) (&amp;obj->super) // Get parent class pointer

// Abstract a shape class, abstract base class
typedef struct Shape {
    double (*getArea)();
    void (*print)();
};
double shape_getArea(Shape* pthis);
void shape_print(Shape* pthis);

// Subclass: Rectangle
typedef struct Rect {
    real length;
    real width;
} Rect;
Rect* createRect();
Rect* createRectWithArgs(real len, real w);
double rect_getArea(Rect* pthis);
void rect_print(Rect* pthis);

// Subclass: Cube
typedef struct Cube {
    // Inherits from Rectangle class (parent must be the first member)
    Rect super;
    real height;
} Cube;
Cube* createCube();
Cube* createCubeWithArgs(real len, real w, real h);
double cube_getArea(Cube* pthis);
void cube_print(Cube* pthis);

int main() {
    Rect* rect = createRectWithArgs(5, 6);
    printf("%.21f\n", rect_getArea(rect)); // Directly call subclass function
    // 
    Cube* cb = createCubeWithArgs(6.5, 8, 9);
    printf("%.21f\n", cube_getArea(cb));

    return 0;
}

double shape_getArea(Shape* pthis) {
    // Cannot invoke parent class virtual function.
    assert(!"can't call base class virtual function");
    return 0;
}
void shape_print(Shape* pthis) {
    assert(!"can't call base class virtual function");
}

Rect* createRect() {
    Rect* rect = (Rect*)calloc(1, sizeof(Rect));
    if (!rect) {
        return NULL;
    }
    return rect;
}
Rect* createRectWithArgs(real len, real w) {
    Rect* rect = createRect();
    if (!rect) {
        return NULL;
    }
    rect->length = len;
    rect->width = w;
    return rect;
}
double rect_getArea(Rect* pthis) {
    return pthis->length * pthis->width;
}
void rect_print(Rect* pthis) {
    printf("Rect(%.21f, %.21f)\n", pthis->length, pthis->width);
}

// Subclass (inherits from Rect class)
Cube* createCube() {
    Cube* cube = (Cube*)calloc(1, sizeof(Cube));
    if (!cube) return NULL;
    return cube;
}
Cube* createCubeWithArgs(real len, real w, real h) {
    Cube* cube = createCube();
    if (!cube) return NULL;
    SUPER(cube)->length = len;
    SUPER(cube)->width = w;
    cube->height = h;
    return cube;
}
double cube_getArea(Cube* pthis) {
    return SUPER(pthis)->length * SUPER(pthis)->width * pthis->height;
}
void cube_print(Cube* pthis) {
    printf("Cube(%.21f, %.21f, %.21f)\n", SUPER(pthis)->length, SUPER(pthis)->width, pthis->height);
}

3. Polymorphism

#include <stdio.h>
#include <assert.h>
#include <stdint.h>
#include <malloc.h>

typedef double real;
typedef struct Shape Shape;
// Get parent class pointer
#define SUPER(obj) (&amp;obj->super) 

// Define virtual function table structure
typedef struct VirtualTable {
    double (*getArea)(Shape* pthis);
    void (*print)(Shape* pthis);
} VirtualTable;

// Abstract a shape class, abstract base class
typedef struct Shape {
    VirtualTable* vPtr; // Virtual function pointer
} Shape;

// Subclass: Rectangle
typedef struct Rect {
    Shape super; // Inherits parent class (Shape)
    real length;
    real width;
} Rect;

// Subclass: Cube
typedef struct Cube {
    // Inherits from Rectangle class (parent must be the first member)
    Rect super;
    real height;
} Cube;

void shape_init(Shape);
double shape_getArea(Shape* pthis);
void shape_print(Shape* pthis);

Rect* createRect();
Rect* createRectWithArgs(real len, real w);
double rect_getArea(Rect* pthis);
void rect_print(Rect* pthis);
double _rect_getArea(Rect* pthis);
void _rect_print(Rect* pthis);
// Adapter function: convert Shape* to Rect* and call _rect_getArea
static double _rect_getArea_adapter(Shape* pthis);
// Adapter function: convert Shape* to Rect* and call _rect_print
static void _rect_print_adapter(Shape* pthis);

Cube* createCube();
Cube* createCubeWithArgs(real len, real w, real h);
double cube_getArea(Cube* pthis);
void cube_print(Cube* pthis);
// Adapter function: convert Shape* to Cube*
static double _cube_getArea_adapter(Shape* pthis);
static void _cube_print_adapter(Shape* pthis);

int main() {
    Rect* rect = createRectWithArgs(5, 6);
    printf("%.21f\n", rect_getArea(rect)); // Directly call subclass function
    // 
    Cube* cb = createCubeWithArgs(6.5, 8, 9);
    printf("%.21f\n", cube_getArea(cb));

    // When the parent class pointer points to the child class object, it will call the child class's function instead of the parent class's virtual function table (which is a function pointer structure)
    shape_print((Shape*)rect);

    // Cannot define an object of an abstract class
    // Shape s
    // shape_init(&amp;s);
    // shape_print(&amp;s)
    // But can use an abstract class pointer to point to a subclass object.
    Shape* s = (Shape*)createCubeWithArgs(5, 6, 7);
    shape_print(s);

    // s = createRectWithArgs(5, 9);
    // shape_print(s);

    Rect* r1 = (Rect*)s;
    rect_print(r1);
    return 0;
}

void shape_init(Shape* pthis) {
    double _shape_getArea(Shape* pthis);
    void _shape_print(Shape* pthis);
    static VirtualTable _vTable = {_shape_getArea, _shape_print};
    pthis->vPtr = &amp;_vTable;
}

double shape_getArea(Shape* pthis) {
    // Cannot invoke parent class virtual function.
    // assert(!"can't call base class virtual function");
    return pthis->vPtr->getArea(pthis);
}
void shape_print(Shape* pthis) {
    // assert(!"can't call base class virtual function");
    pthis->vPtr->print(pthis);
}

double _shape_getArea(Shape* pthis) {
    // Cannot invoke parent class virtual function.
    assert(!"can't call base class virtual function");
    return 0;
}
void _shape_print(Shape* pthis) {
    assert(!"can't call base class virtual function");
}

Rect* createRect() {
    Rect* rect = (Rect*)calloc(1, sizeof(Rect));
    if (!rect) {
        return NULL;
    }
    static VirtualTable vTable = {_rect_getArea_adapter, _rect_print_adapter};
    SUPER(rect)->vPtr = &amp;vTable; // Virtual function table points to itself.
    return rect;
}
Rect* createRectWithArgs(real len, real w) {
    Rect* rect = createRect();
    if (!rect) {
        return NULL;
    }
    rect->length = len;
    rect->width = w;
    return rect;
}
// Implementing polymorphism
double rect_getArea(Rect* pthis) {
    return SUPER(pthis)->vPtr->getArea((Shape*)pthis);
}
void rect_print(Rect* pthis) {
    SUPER(pthis)->vPtr->print((Shape*)pthis);
}
// Self-invoking function
double _rect_getArea(Rect* pthis) {
    return pthis->length * pthis->width;
}
void _rect_print(Rect* pthis) {
    printf("Rect(%.21f, %.21f)\n", pthis->length, pthis->width);
}
// Adapter function: convert Shape* to Rect* and call _rect_getArea
static double _rect_getArea_adapter(Shape* pthis) {
    return _rect_getArea((Rect*)pthis); // The cast is safe because pthis actually points to Rect
}

// Adapter function: convert Shape* to Rect* and call _rect_print
static void _rect_print_adapter(Shape* pthis) {
    _rect_print((Rect*)pthis); // The cast is safe
}

// Subclass (inherits from Rect class)
Cube* createCube() {
    Cube* cube = (Cube*)calloc(1, sizeof(Cube));
    if (!cube) return NULL;
    static VirtualTable vTable = {_cube_getArea_adapter, _cube_print_adapter};
    SUPER(cube)->super.vPtr = &amp;vTable; // Virtual function table points to itself.
    return cube;
}
Cube* createCubeWithArgs(real len, real w, real h) {
    Cube* cube = createCube();
    if (!cube) return NULL;
    SUPER(cube)->length = len;
    SUPER(cube)->width = w;
    cube->height = h;
    return cube;
}
double cube_getArea(Cube* pthis) {
    return SUPER(pthis)->length * SUPER(pthis)->width * pthis->height;
}
void cube_print(Cube* pthis) {
    printf("Cube(%.21f, %.21f, %.21f)\n", SUPER(pthis)->length, SUPER(pthis)->width, pthis->height);
}
// Adapter function: convert Shape* to Cube*
static double _cube_getArea_adapter(Shape* pthis) {
    return cube_getArea((Cube*)pthis);
}

static void _cube_print_adapter(Shape* pthis) {
    cube_print((Cube*)pthis);
}

Leave a Comment