Home | Hand Evaluation in Bridge | C Programming | Keyboard Layout |
Classes in CThis document describes the simplest possible coding style for making classes in C. It will describe constructors, instance variables, instance methods, class variables, class methods, inheritance, polymorphism, namespaces with aliasing and put it all together in an example project.
1. C ClassesA class consists of an instance type and a class object:
An instance type is a
A class object is a global
A class named "Complex" should name the instance type Complex.h: struct Complex { ... }; extern const struct ComplexClass { ... } Complex; Complex.c: #include "Complex.h" const struct ComplexClass Complex={...}; 2. ConstructorsInstances must be initialized by constructors when declared, and the constructors must be class methods. The constructor should preferably return an instance type, but may also return a pointer to an instance type.
The Complex class gets two instance variables Complex.h: struct Complex { double re, im; }; extern const struct ComplexClass { struct Complex (*new)(double real, double imag); } Complex; Complex.c: #include "Complex.h" static struct Complex new(double real, double imag) { return (struct Complex){.re=real, .im=imag}; } const struct ComplexClass Complex={.new=&new}; Complex_test.c: ... struct Complex c=Complex.new(3., -4.); 3. Methods
Instance methods must be
declared as instance type
members pointing to the wanted function prototype, and that
pointer must be set by the constructor. Typically, the method pointer is
set to a
To be able to access the instance's data, instance methods must receive a
pointer to the instance as its
first argument. This argument is typically named
When we add an instance method Complex.h: struct Complex { double re, im; double (*abs)(struct Complex *this); }; extern const struct ComplexClass { struct Complex (*new)(double real, double imag); } Complex; Complex.c: #include "Complex.h" static double abs(struct Complex *this) { return sqrt(this->re*this->re+this->im*this->im); } static struct Complex new(double real, double imag) { return (struct Complex){.re=real, .im=imag, .abs=&abs}; } const struct ComplexClass Complex={.new=&new}; Complex_test.c: ... struct Complex c=Complex.new(3., -4.); printf("%g\n", c.abs(&c)); // Prints 5 Class methods must be initialized in the same way as instance methods, but have no restriction on the prototype. 4. InheritanceA base class must be represented as a member variable with the same name and type as the base class itself. A subclass may override the base class instance method pointers to provide polymorphism. The subclass must override with an identically prototyped function and set the base class' method pointer in the constructor after the baseclass' constructor has been called.
Whenever an overriden instance method is called, we are
guaranteed that it was called by an instance of the baseclass. Since the
instance method receives a pointer to the base class as its first
argument, we may get the subclass using the The following files shows a simple example of inheritance and polymorphism: struct Employee { const char *first_name; const char *family_name; const char *(*print)(struct Employee *this, size_t bufsize,char buf[bufsize]); }; extern const struct EmployeeClass { struct Employee (*new)(const char *first_name, const char *family_name); } Employee; #include "Employee.h" struct Manager { struct Employee Employee; int level; }; extern const struct ManagerClass { struct Manager (*new)(const char *first_name, const char *family_name, int level); } Manager; #include "Manager.h" int main(void) { struct Manager manager=Manager.new("Håkon", "Hallingstad", 3); struct Employee employee=Employee.new("Håkon", "Hallingstad"); 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 line from Manager.c: ret.Employee.print=&print; Which makes inheritance.c print: Name: Håkon Hallingstad Name: Håkon Hallingstad, level 3 5. Controlling Access to MembersIn object oriented languages each member has an access attribute and the compiler will enforce that access attribute. With Classes in C we should use comments to specify the access attributes. For instance we use the following notation: struct Complex { ... // protected: ... // private: ... }; 6. Abstract Classes, Abstract Methods and InterfacesIn 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. With Classes in C just have to make sure any user of the class understands such intensions, for instance: struct ElementInterface { ... }; /*interface*/ struct Element { ... }; /*abstract*/ struct Complex { ... }; struct Stack { /*abstract*/ double (*foo)(struct Stack *this); };
Abstract instance method pointers should be initialized to
7. NamespacesA namespace defines a common prefix of all identifiers exported by a class and the path of its header- and implementation- files.
For instance a #ifndef ORG_PVV_HAKONHAL_UTILS_COMPLEX_H #define ORG_PVV_HAKONHAL_UTILS_COMPLEX_H struct org_pvv_hakonhal_utils_Complex { ... }; extern struct org_pvv_hakonhal_utils_ComplexClass { ... } org_pvv_hakonhal_utils_Complex; #endif
When we are going to use the class we may alias the identifiers to make them more managable, by
using the #include "org/pvv/hakonhal/utils/Complex.h" #define Complex org_pvv_hakonhal_utils_Complex ... struct Complex c=Complex.new(); 8. An Example ProjectIn this example project we will create and test a bounds-checking stack implementation by extending a simpler stack implementation. The project will illustrate everything about C Classes including constructors, methods, inheritance, namespaces and aliases. For compiling this project you should read C Project Building. The Libray Project
We imagine the simple stack project has been downloaded from the net
and the header file may be referenced as
#ifndef ORG_SOMEWHERE_SOMEONE_STACK_H #define ORG_SOMEWHERE_SOMEONE_STACK_H struct org_somewhere_someone_Stack_ElementI { }; #define ORG_SOMEWHERE_SOMEONE_STACK_SIZE 100 struct org_somewhere_someone_Stack { void (*push) (struct org_somewhere_someone_Stack *this, struct org_somewhere_someone_Stack_ElementI *element); struct org_somewhere_someone_Stack_ElementI *(*pop) (struct org_somewhere_someone_Stack *this); // protected: int count; struct org_somewhere_someone_Stack_ElementI * data[ORG_SOMEWHERE_SOMEONE_STACK_SIZE]; }; extern struct org_somewhere_someone_StackClass { struct org_somewhere_someone_Stack (*new)(void); } org_somewhere_someone_Stack; #endif
We choose a unique org_pvv_hakonhal_utils namespace and creates the
The only thing our class will do in addition to
#ifndef ORG_PVV_HAKONHAL_UTILS_BSTACK_H #define ORG_PVV_HAKONHAL_UTILS_BSTACK_H #include "org/somewhere/someone/Stack.h" struct org_pvv_hakonhal_utils_BStack { struct org_somewhere_someone_Stack org_somewhere_someone_Stack; }; extern const struct org_pvv_hakonhal_utils_BStackClass { struct org_pvv_hakonhal_utils_BStack (*new)(void); } org_pvv_hakonhal_utils_BStack; #endif The implementation file is somewhat more complex. 1 #include "org/pvv/hakonhal/utils/BStack.h" 2 #define BStack org_pvv_hakonhal_utils_BStack 3 #define BStackClass org_pvv_hakonhal_utils_BStackClass 4 #include "org/somewhere/someone/Stack.h" 5 #define ElementI org_somewhere_someone_Stack_ElementI 6 #define Stack org_somewhere_someone_Stack 7 #define STACK_SIZE ORG_SOMEWHERE_SOMEONE_STACK_SIZE 8 #include <stdio.h> 9 #include <stdlib.h> 10 static void (*base_push)(struct Stack *this, struct ElementI *element); 11 static void push(struct Stack *base, struct ElementI *element) 12 { 13 if (base->count>=STACK_SIZE) { 14 fprintf(stderr, "%s", "Stack overflow!\n"); 15 exit(1); 16 } 17 18 base_push(base, element); 19 } 20 static struct ElementI *(*base_pop)(struct Stack *this); 21 static struct ElementI *pop(struct Stack *base) 22 { 23 if (base->count<=0) { 24 fprintf(stderr, "%s", "Stack underflow!\n"); 25 exit(1); 26 } 27 return base_pop(base); 28 } 29 static struct BStack new(void) 30 { 31 struct BStack ret; 32 ret.Stack=Stack.new(); 33 base_push=ret.Stack.push; 34 ret.Stack.push=&push; 35 base_pop=ret.Stack.pop; 36 ret.Stack.pop=&pop; 37 return ret; 38 } 39 const struct BStackClass BStack={.new=&new};
Let us look at the implementation of the Testing the Library Project
The implementation of the 1 #include "org/pvv/hakonhal/utils/BStack.h" 2 #define BStack org_pvv_hakonhal_utils_BStack 3 #include "org/somewhere/someone/Stack.h" 4 #define ElementI org_somewhere_someone_Stack_ElementI 5 #define Stack org_somewhere_someone_Stack 6 #include <stddef.h> 7 #include <stdio.h> 8 struct Integer { 9 struct ElementI ElementI; 10 int value; 11 void (*print)(struct Integer *this, const char *id); 12 }; 13 static void print(struct Integer *this, const char *id) 14 { 15 printf("%s: %d\n", id, this->value); 16 } 17 static struct Integer new(int value) 18 { 19 return (struct Integer){ 20 .ElementI={}, 21 .value=value, 22 .print=&print, 23 }; 24 } 25 static const struct { 26 struct Integer (*new)(int value); 27 } Integer={.new=&new}; 28 int main(void) 29 { 30 struct BStack stack=BStack.new(); 31 struct Integer i=Integer.new(10), j=Integer.new(20); 32 struct Integer *ptr; 33 stack.Stack.push(&stack.Stack, &i.ElementI); 34 stack.Stack.push(&stack.Stack, &j.ElementI); 35 ptr=(void *)stack.Stack.pop(&stack.Stack)-offsetof(struct Integer, ElementI); 36 ptr->print(ptr, "j"); 37 ptr=((void *)stack.Stack.pop(&stack.Stack))-offsetof(struct Integer, ElementI); 38 ptr->print(ptr, "i"); 39 printf("%s\n", "Will now try to pop an empty stack"); 40 stack.Stack.pop(&stack.Stack); 41 return 0; 42 }
We define an Since we are using aliasing, statements such as that on line 30 actually reads: struct org_pvv_hakonhal_utils_BStack stack=org_pvv_hakonhal_utils_BStack.new();
Since the
When we retreive the elements on the stack, we need the slightly
awkward syntax in 35 and 37. Conceptually, we receive a pointer to the
The j: 20 i: 10 Will now try to pop an empty stack Stack underflow detected! |
Home | Hand Evaluation in Bridge | C Programming | Keyboard Layout |
Håkon Hallingstad |