To brush up my C I decided to do some OOP programming in C. I have never tested this before, but I was curious to try it since seeing an example of it a while ago. I decided to implement a simple hash table. My first approach was to give each instance of the object its own methods, i.e. pointers to functions, but this felt meaningless because they could not be used for more than shared state (without giving the methods a self reference). Instead I decided that each "object" should only hold the private state and use normal functions.
Here follows a usage of the "class", main.c:
#include <stdio.h>
#include "hashtable.h"
int main(int argc, char *argv[]) {
HashTable ht = newHashTable(65535);
HashTable_put(ht, "1", "two");
HashTable_put(ht, "2", "three");
HashTable_put(ht, "3", "five");
HashTable_put(ht, "4", "seven");
HashTable_put(ht, "5", "eleven");
HashTable_put(ht, "6", "thirteen");
HashTable_put(ht, "7", "seventeen");
HashTable_remove(ht, "1");
HashTable_remove(ht, "2");
HashTable_remove(ht, "6");
HashTable_remove(ht, "7");
HashTable_put(ht, "3", "not allowed");
printf("%s\n", HashTable_get(ht, "1"));
printf("%s\n", HashTable_get(ht, "2"));
printf("%s\n", HashTable_get(ht, "3"));
printf("%s\n", HashTable_get(ht, "4"));
printf("%s\n", HashTable_get(ht, "5"));
printf("%s\n", HashTable_get(ht, "6"));
printf("%s\n", HashTable_get(ht, "7"));
printf("size: %d\n", HashTable_size(ht));
freeHashTable(&ht);
return 0;
}
And the header hashtable.h:
#ifndef HASHTABLE_H
#define HASHTABLE_H
#include <stdbool.h>
typedef struct hashtable *HashTable;
// Create, destory
HashTable newHashTable(int);
void freeHashTable(HashTable *);
// Public methods
int HashTable_buckets(HashTable);
const char *HashTable_get(HashTable, char *);
bool HashTable_put(HashTable, char *, char *);
bool HashTable_remove(HashTable, char *);
int HashTable_size(HashTable);
#endif
And lastly the actual implementation, i.e. hashtable.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hashtable.h"
struct entry {
char *key;
char *value;
struct entry *next;
};
struct hashtable {
int buckets;
int size;
struct entry **table;
};
typedef struct entry *Entry;
static unsigned long djb2_hash(char *);
static void free_bucket(Entry *);
static void free_entry(Entry *);
//-----------------------------------------------------------------------------
// Create, destory
//-----------------------------------------------------------------------------
HashTable newHashTable(int buckets) {
HashTable ht = malloc(sizeof(struct hashtable));
ht->table = malloc(buckets * sizeof(struct entry *));
for (int i = 0; i < buckets; i++) {
ht->table[i] = NULL;
}
ht->buckets = buckets;
ht->size = 0;
return ht;
}
void freeHashTable(HashTable *thisPtr) {
for (int i = 0; i < (*thisPtr)->buckets; i++) {
free_bucket(&(*thisPtr)->table[i]);
}
free((*thisPtr)->table);
(*thisPtr)->table = NULL;
free(*thisPtr);
*thisPtr = NULL;
}
//-----------------------------------------------------------------------------
// Public methods
//-----------------------------------------------------------------------------
int HashTable_buckets(HashTable this) {
return this->buckets;
}
const char *HashTable_get(HashTable this, char *key) {
Entry e;
int b = djb2_hash(key) % this->buckets;
for (e = this->table[b]; e != NULL; e = e->next) {
if (strcmp(e->key, key) == 0) {
return e->value;
}
}
return NULL;
}
bool HashTable_put(HashTable this, char *key, char *value) {
Entry *ePtr;
int b = djb2_hash(key) % this->buckets;
for (ePtr = &this->table[b]; *ePtr != NULL; ePtr = &(*ePtr)->next) {
if (strcmp((*ePtr)->key, key) == 0) {
return false;
}
}
*ePtr = malloc(sizeof(struct entry));
(*ePtr)->key = strdup(key);
(*ePtr)->value = strdup(value);
(*ePtr)->next = NULL;
this->size++;
return true;
}
bool HashTable_remove(HashTable this, char *key) {
Entry *ePtr, next;
int b = djb2_hash(key) % this->buckets;
for (ePtr = &this->table[b]; *ePtr != NULL; ePtr = &(*ePtr)->next) {
if (strcmp((*ePtr)->key, key) == 0) {
next = (*ePtr)->next;
free_entry(ePtr);
*ePtr = next;
this->size--;
return true;
}
}
return false;
}
int HashTable_size(HashTable this) {
return this->size;
}
//-----------------------------------------------------------------------------
// Private methods
//-----------------------------------------------------------------------------
static unsigned long djb2_hash(char *str) {
unsigned long hash = 5381;
unsigned char c;
while ((c = *str++) != '\0') {
hash = ((hash << 5) + hash) + c; // hash * 33 + c
}
return hash;
}
static void free_bucket(Entry *ePtr) {
if (*ePtr == NULL) {
return;
}
free_bucket(&(*ePtr)->next);
free_entry(ePtr);
}
static void free_entry(Entry *ePtr) {
free((*ePtr)->key);
free((*ePtr)->value);
(*ePtr)->key = NULL;
(*ePtr)->value = NULL;
free(*ePtr);
*ePtr = NULL;
}
Each hash table bucket, that is the content of table
, is a pointer to its first entry (or NULL), and if more entries in a bucket they are chained as a linked list with a pointer in each entry to the next entry.
As you can see I tried to encapsulate the data field as much as possible. It would be very nice to get feedback on anything. I struggled some to get the freeing right, have only used malloc one time before, I have mostly done simple embedded programming in C before. I also put in effort to make the functions as concise as possible. Is this a good OOP implementation with C? Are there any other issues? If you want to run the code its available here with a compilation script. I'm aware of that it would be better if the the number of buckets was dynamic, so you could leave that feedback out.