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:
-
Abstraction: Abstracting things, abstracting properties, abstracting behaviors
- Properties: Common characteristics of a class of things, e.g., humans: age, name, ID, gender…
- Behaviors: Common behaviors of a class of things, e.g., humans: eating, sleeping, walking (running, jumping), speaking…
-
Encapsulation
Combining the abstracted properties and behaviors into a class (structure)
-
Inheritance
- Human -> Man, Woman
- Base class (parent class)
- Derived class (child class)
- The child class inherits from the parent class, thus acquiring all properties and behaviors of the parent class
-
Polymorphism
- Polymorphic behavior can occur between parent and child objects
- When a parent class pointer points to a child class object, invoking the parent class’s behavior will invoke the child class’s behavior.
- 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) (&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) (&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(&s);
// shape_print(&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 = &_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 = &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 = &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);
}