Enums & typedef
Enums & typedef
Enumerations (enum)
An enum defines a set of named integer constants. Compared to bare #define constants, enums are scoped, type-checkable (with compiler warnings), and visible in debuggers:
enum Direction { NORTH, EAST, SOUTH, WEST };By default the first enumerator is 0, and each subsequent one increments by 1. You can assign explicit values:
enum HttpStatus {
HTTP_OK = 200,
HTTP_NOT_FOUND = 404,
HTTP_SERVER_ERROR = 500
};Enums are integers under the hood — you can use them in switch statements, arithmetic, and comparisons. An unnamed enum is legal and is often used purely to define integer constants without the enum tag:
enum { MAX_RETRIES = 3, TIMEOUT_MS = 5000 };typedef — Creating Type Aliases
typedef introduces an alias for an existing type, improving readability and portability:
typedef unsigned long long uint64;
typedef int (*CompareFunc)(const void *, const void *);The C99 idiomatic pattern for structs uses typedef to avoid writing struct everywhere:
typedef struct {
int x;
int y;
} Point;
Point p = {3, 4}; /* instead of: struct Point p = {3, 4}; */For a struct that must refer to itself (linked list node), the tag is needed:
typedef struct Node {
int value;
struct Node *next; /* must use struct Node here, not just Node */
} Node;typedef for Function Pointers
Function pointer syntax in C is notoriously hard to read. typedef is a lifesaver:
/* Without typedef */
void (*callback)(int, const char *);
/* With typedef */
typedef void (*Callback)(int, const char *);
Callback cb;const Correctness
The const qualifier promises not to modify a value. It should be applied consistently to improve safety and enable compiler optimisations:
const int MAX = 100; /* constant integer */
const char *str = "hello"; /* pointer to const char (string unmodifiable) */
char * const ptr = buffer; /* const pointer to char (address unmodifiable) */
const char * const cptr = "world"; /* both pointer and pointee are const */A useful mnemonic: read the type right-to-left: const char *p = "p is a pointer to char that is const".
typedef vs #define for Types
Prefer typedef over #define for type aliases because:
typedefparticipates in the type system; the compiler understands it.typedefrespects scope;#definedoes not.- Pointer typedefs work correctly:
typedef char *String; const String s;makessa const pointer, not a pointer to const char — a subtle but important difference.
Code Examples
#include <stdio.h>
typedef enum {
STATE_IDLE, /* 0 */
STATE_RUNNING, /* 1 */
STATE_PAUSED, /* 2 */
STATE_ERROR, /* 3 */
STATE_COUNT /* sentinel — always keep last */
} AppState;
const char *state_name(AppState s) {
static const char *names[] = {
"IDLE", "RUNNING", "PAUSED", "ERROR"
};
if (s < 0 || s >= STATE_COUNT) return "UNKNOWN";
return names[s];
}
/* State transition table: allowed[current] = allowed next state mask */
static int valid_transition(AppState from, AppState to) {
switch (from) {
case STATE_IDLE: return to == STATE_RUNNING;
case STATE_RUNNING: return to == STATE_PAUSED || to == STATE_ERROR || to == STATE_IDLE;
case STATE_PAUSED: return to == STATE_RUNNING || to == STATE_IDLE;
case STATE_ERROR: return to == STATE_IDLE;
default: return 0;
}
}
int main(void) {
AppState current = STATE_IDLE;
AppState transitions[] = { STATE_RUNNING, STATE_PAUSED, STATE_IDLE, STATE_ERROR };
printf("Starting state: %s\n", state_name(current));
for (int i = 0; i < 4; i++) {
AppState next = transitions[i];
if (valid_transition(current, next)) {
printf(" %s -> %s [OK]\n", state_name(current), state_name(next));
current = next;
} else {
printf(" %s -> %s [INVALID]\n", state_name(current), state_name(next));
}
}
/* Enum used in switch — compiler warns if a case is missing */
printf("\nFinal state: %s\n", state_name(current));
return 0;
}STATE_COUNT as a sentinel gives the number of states automatically — it always equals the count because it follows all real states. The state name lookup array is indexed directly by the enum value (which starts at 0). A compiler with -Wswitch warns if a case is missing in the switch.
#include <stdio.h>
#include <string.h>
#include <math.h>
/* C99 idiom: define the struct and its typedef together */
typedef struct {
double x;
double y;
} Vec2;
/* Self-referential struct requires a tag */
typedef struct ListNode {
int value;
struct ListNode *next; /* must use the tag, not the typedef alias */
} ListNode;
/* Functions taking/returning typedef'd struct by value */
static inline Vec2 vec2_add(Vec2 a, Vec2 b) {
return (Vec2){ a.x + b.x, a.y + b.y };
}
static inline double vec2_length(Vec2 v) {
return sqrt(v.x * v.x + v.y * v.y);
}
/* Compound literal syntax (C99) for temporary struct values */
static inline Vec2 vec2(double x, double y) {
return (Vec2){ x, y };
}
int main(void) {
Vec2 a = vec2(3.0, 4.0);
Vec2 b = { .x = 1.0, .y = 2.0 }; /* designated initialiser */
Vec2 c = vec2_add(a, b);
printf("a = (%.1f, %.1f) |a| = %.3f\n", a.x, a.y, vec2_length(a));
printf("b = (%.1f, %.1f)\n", b.x, b.y);
printf("a+b = (%.1f, %.1f)\n", c.x, c.y);
/* Linked list using the self-referential typedef */
ListNode n3 = {30, NULL};
ListNode n2 = {20, &n3};
ListNode n1 = {10, &n2};
printf("\nList: ");
for (ListNode *p = &n1; p != NULL; p = p->next)
printf("%d ", p->value);
printf("\n");
return 0;
}The typedef lets you write Vec2 instead of struct Vec2 everywhere. Compound literals (Vec2){ x, y } create temporary struct values inline (C99). For the self-referential ListNode, the struct tag is required inside the struct body because the typedef alias isn't yet fully defined at that point.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* typedef makes function pointer types readable */
typedef int (*Comparator)(const void *, const void *);
typedef void (*Transformer)(int *, int); /* modifies array in place */
/* Comparators for qsort */
int cmp_ascending(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}
int cmp_descending(const void *a, const void *b) {
return *(const int *)b - *(const int *)a;
}
/* Transformers */
void double_all(int *arr, int n) {
for (int i = 0; i < n; i++) arr[i] *= 2;
}
void negate_all(int *arr, int n) {
for (int i = 0; i < n; i++) arr[i] = -arr[i];
}
/* Higher-order function: accepts a function pointer */
void sort_and_transform(int *arr, int n, Comparator cmp, Transformer tfm) {
qsort(arr, (size_t)n, sizeof(int), cmp);
if (tfm) tfm(arr, n);
}
void print_arr(const int *arr, int n) {
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");
}
int main(void) {
int data[] = {5, 2, 8, 1, 9, 3};
int n = 6;
int copy[6];
memcpy(copy, data, sizeof(data));
sort_and_transform(copy, n, cmp_ascending, NULL);
printf("Sorted ascending: "); print_arr(copy, n);
memcpy(copy, data, sizeof(data));
sort_and_transform(copy, n, cmp_descending, double_all);
printf("Sorted desc+2x: "); print_arr(copy, n);
/* Array of function pointers */
Transformer ops[] = { double_all, negate_all };
const char *names[] = { "double", "negate" };
memcpy(copy, data, sizeof(data));
for (int i = 0; i < 2; i++) {
ops[i](copy, n);
printf("After %s: ", names[i]); print_arr(copy, n);
}
return 0;
}typedef Comparator and typedef Transformer make the function pointer types as readable as any other type. sort_and_transform takes both a comparator and an optional transformer — a practical higher-order function pattern. The array of function pointers ops[] shows how you can select behavior at runtime.
Quick Quiz
1. What integer value does the third enumerator have in `enum { A, B, C, D };`?
2. Why does a self-referential struct (like a linked list node) require a struct tag even when using typedef?
3. What does `const char *p` mean in C?
4. What is one advantage of using typedef for a function pointer compared to a #define?
Was this lesson helpful?