Classes in C with struct: solved modular design exercise

Classes in C with struct: solved exercise step by step

If you are searching for classes in C with struct, this guide shows a practical object-like pattern in plain C: struct + functions + separated files.

The goal is to simulate encapsulation and clean responsibilities without native OOP features.

Problem statement

Design a Cuenta module that:

  1. defines an entity with owner name and balance,
  2. exposes functions to create, deposit, withdraw, and print state,
  3. separates interface (cuenta.h) and implementation (cuenta.c),
  4. uses main.c to test the full workflow.

C solution

cuenta.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#ifndef CUENTA_H
#define CUENTA_H

typedef struct {
    char titular[64];
    double saldo;
} Cuenta;

Cuenta cuenta_crear(const char *titular, double saldo_inicial);
int cuenta_depositar(Cuenta *c, double importe);
int cuenta_retirar(Cuenta *c, double importe);
void cuenta_imprimir(const Cuenta *c);

#endif

cuenta.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include "cuenta.h"
#include <stdio.h>
#include <string.h>

Cuenta cuenta_crear(const char *titular, double saldo_inicial) {
    Cuenta c;
    strncpy(c.titular, titular, sizeof(c.titular) - 1);
    c.titular[sizeof(c.titular) - 1] = '\0';
    c.saldo = (saldo_inicial >= 0.0) ? saldo_inicial : 0.0;
    return c;
}

int cuenta_depositar(Cuenta *c, double importe) {
    if (!c || importe <= 0.0) return 0;
    c->saldo += importe;
    return 1;
}

int cuenta_retirar(Cuenta *c, double importe) {
    if (!c || importe <= 0.0 || importe > c->saldo) return 0;
    c->saldo -= importe;
    return 1;
}

void cuenta_imprimir(const Cuenta *c) {
    if (!c) return;
    printf("Owner: %s | Balance: %.2f\n", c->titular, c->saldo);
}

main.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include "cuenta.h"
#include <stdio.h>

int main(void) {
    Cuenta c = cuenta_crear("Rodrigo", 1000.0);
    cuenta_imprimir(&c);

    cuenta_depositar(&c, 250.0);
    cuenta_retirar(&c, 400.0);
    cuenta_imprimir(&c);

    if (!cuenta_retirar(&c, 2000.0)) {
        printf("Withdrawal rejected due to insufficient funds.\n");
    }

    return 0;
}

Build:

1
gcc -Wall -Wextra -std=c11 main.c cuenta.c -o cuenta_demo

Expected output

1
2
3
Owner: Rodrigo | Balance: 1000.00
Owner: Rodrigo | Balance: 850.00
Withdrawal rejected due to insufficient funds.

Common mistakes

  • Putting all business logic in main.c and losing modularity.
  • Skipping pointer and amount validation.
  • Copying strings without bounds checks.
  • Assuming this is full OOP: C still has no native inheritance or methods.

Practical use

This pattern helps you:

  • design maintainable C modules,
  • separate public API from implementation details,
  • build cleaner foundations for larger projects.

It is a practical way to get object-like structure while staying in idiomatic C.

Guided practice and next step

If you want a complete path with progressive difficulty:

FAQ

Can you build real object-oriented design in C?

Not natively. You can simulate parts of it with struct, function APIs, and pointers, but there is no built-in inheritance or method system.

Why separate .h and .c in this exercise?

Because it improves maintainability, reuse, and clarity of the module public contract.

Is this pattern useful for interviews or only large projects?

Both. In interviews it shows structured thinking, and in production it prevents monolithic code.