Skip to content

Instantly share code, notes, and snippets.

@kspalaiologos
Last active December 4, 2019 08:46
Show Gist options
  • Save kspalaiologos/8f7915abd2011d795baba83a42d207c7 to your computer and use it in GitHub Desktop.
Save kspalaiologos/8f7915abd2011d795baba83a42d207c7 to your computer and use it in GitHub Desktop.
Small OOP tutorial for C.

C OOP tutorial

This small file was made to help beginners with OOP in C. I hope it will be helpful. All code included in this file is released to public domain, while this document itself is released under terms of MIT license. This file contains C99 code, so please adjust your compiler to use it. For gcc pass: -std=c99

Creating new class

To create new class, we have to create new two files first. First will be named 'simple.c' and second will be named 'simple.h'.

simple.h:


/**
 * Write something about class here.
 */

#ifndef __SIMPLE_H_
#define __SIMPLE_H_

struct Simple {
  /* ... */
};
extern const struct SimpleClass {
  /* ... */
} Simple;

#endif

simple.c:


/**
 * Write something about this file here.
 */

#include "simple.h"

const struct SimpleClass Simple = { /*Declare fields here.*/ };

Constructors

To create constructor, we have to create new method inside SimpleClass object. First, let's add field to Simple structure:

Inside simple.h:

struct Simple {
  int a;
  int b;
};

Now, let's add constructor to SimpleClass. Inside simple.h:

extern const struct SimpleClass {
  struct Simple (*ctor)(a, b);
} Simple;

simple.c now contains invalid initializer, so let's fix it, and add constructor. Inside simple.c:

static struct Simple ctor(a, b) {
  return (struct Simple){.a=a, .b=b};
}
const struct SimpleClass Simple={.ctor=&ctor};

Methods

Let's add method to our class. First, add some code to Simple:

Inside simple.h:

struct Simple {
  int a;
  int b;
  int (*add)(struct Simple * this);
};

Now, let's make changes to .c file too!

Inside simple.c:

static add(struct Simple *this) {
  return this->a + this->b;
}
static struct Simple ctor(a, b) {
  return (struct Simple){.a=a, .b=b, .add=&add};
}
const struct SimpleClass Simple={.ctor=&ctor};

Added method is now inside Simple class! Let's test it:

simple_test.c:


#include <stdio.h>
#include "simple.h"

main() {
  struct Simple ins = Simple.ctor(2, 3);
  printf("%d + %d = %d", ins.a, ins.b, ins.add());
}

Code above should print out string 2 + 3 = 5.

Inheritance, polymorphism, overriding base class instance

A base class must be represented as a member variable with the same name and type as the base class itself. A subclass may also override the base class instance method pointers to provide polymorphism. Example of inheritance (and polymorphism):

employee.h:


#ifndef __EMPLOYEE_H_
#define __EMPLOYEE_H_

struct Employee {
  const char *firstname;
  const char *surname;
  const char *(*print)(struct Employee *this, size_t bufsize, char buf[bufsize]);
};

extern const struct EmployeeClass {
  struct Employee (*ctor)(const char *firstname, const char *surname);
} Employee;

#endif

employee.c:


#include "Employee.h"
	
static const char *print(struct Employee *this, size_t bufsize, char buf[bufsize]) {
  snprintf(buf, bufsize, "%s %s",
  this->firstname, this->surname);
  return buf;
}
	
static struct Employee ctor(const char *firstname, const char *surname) {
		return (struct Employee) {
			.firstname=strdup(firstname),
			.surname=strdup(surname),
			.print=&print
		};
}
	
const struct EmployeeClass Employee={.ctor=&ctor};

manager.h:


#include "Employee.h"
	
struct Manager {
  struct Employee Employee;
  int level;
};
	
extern const struct ManagerClass {
  struct Manager (*ctor)(const char *firstname, const char *surname, int level);
} Manager;

manager.c:


#include "Manager.h"
	
static const char * print(struct Employee * base, size_t bufsize, char buf[bufsize]) {
  struct Manager * this = (void *) base - offsetof(struct Manager, Employee);
  snprintf(buf, bufsize, "%s %s (level %d)",
    this -> Employee.firstname,
    this -> Employee.surname,
    this -> level);
  return buf;
}

static struct Manager ctor(const char * firstname, const char * surname, int level) {
  struct Manager ret = {.level = level};
  ret.Employee = Employee.ctor(firstname, surname);
  ret.Employee.print = &print;
  return ret;
}
	
extern const struct ManagerClass Manager={.ctor=&ctor};

test.c:

#include "Manager.h"

main() {
  struct Manager manager=Manager.new("Krzysztof", "Szewczyk", 666);
  struct Employee employee=Employee.new("Linus", "Torvalds");
  struct Employee *polymorph=&manager.Employee;
  char buf[50];
  printf("%s\n", employee.print(&employee, sizeof(buf), buf));
  printf("%s\n", polymorph->print(polymorph, sizeof(buf), buf));
  return 0;
}

The Manager class overrides Employee's print() instance method with the one from Manager: ret.Employee.print=&print;.

Controlling member access

Well, controlling member access is not possible without really dirty hacks. Then, we can use comments to specify, which elements should be accessed, and which shouldn't. There are two ways to do this:

struct x {
  /*public:*/
  int a;
  int b;
  /*protected:*/
  int c;
  int d;
  /*private*/
  int e;
};

or

/**
 * x: public members:
 *  - a
 *  - b
 * x: protected members:
 *  - c
 *  - d
 * x: private members:
 *  - e
 */
struct x {
  int a;
  int b;
  int c;
  int d;
  int e;
};

Abstract methods and classes. Interfaces.

In object oriented languages we can specify an abstract class to guarantee that the class cannot be instanciated. Abstract methods and interfaces can be used to guarantee that subclasses override methods.

In C, just have to make sure any user of the class understands such intensions, for example:

struct EInterface {
};

/*interface*/ struct Element {
};

/*abstract*/ struct Number {
};

struct Stack {
  /*abstract*/ double (*foo)(struct Stack *this);
};

It's recommended to initialize abstract methods to either null or better, stub (printing out some text and terminating program, because of invalid call).

Object creation

It would be nice if we had some kind of global macro for creating objects fast. Let's add it to common header file (maybe the one declaring Object class?):

#define new(type, ...) type.ctor( __VA_ARGS__ )

Use it like this:

struct Simple s = new(Simple, 2, 3);

Namespaces

A namespace defines a common prefix of all identifiers exported by a class and the path of its header and source files. Let's put our first class in namespace:

simple.h:


/**
 * Write something about class here.
 */

#ifndef __SIMPLE_H_
#define __SIMPLE_H_

struct szewczyk_classes_Simple {
  /* ... */
};
extern const struct szewczyk_classes_SimpleClass {
  /* ... */
} szewczyk_classes_Simple;

#endif

simple.c:


/**
 * Write something about this file here.
 */

#include "simple.h"

const struct szewczyk_classes_SimpleClass szewczyk_classes_Simple = { /*Declare fields here.*/ };

Static inclusion

Well, do you really want to type so long names? Maybe an alternative? Yes!

Somewhere inside main.c:

#define STATIC
#include <szewczyk/simple.h>

Now, let's look at szewczyk/simple.h:

/* ... */
#ifdef STATIC
  #undef STATIC
  #define Simple szewczyk_Simple
#endif
/* ... */

Summary

I hope this document was useful and I have explained everything clearly. OOP in C is not simple, that's why I've written this. If You will manage to find any mistakes made by me, please notify me about it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment