Arrays, Structs & Unions — 10 Examples
0%
Arrays · Structs · Unions  ·  10 Examples

Arrays, Structs & Unions —
Together in C

Ten programs that combine all three — arrays of structs, structs with union fields, nested layouts, student records, inventory systems, and more. Each teaches a different way the three constructs work together.

1
Array Basics
2
Struct Basics
3
Array of Structs
4
Nested Structs
5
Struct + Union
6
Passing to Fn
7
Student Records
8
Inventory
9
2D Array Struct
10
Full App
1
📐 Array Basics — Declare, Initialise, Iterate
1-D array of ints — indexing, loops, finding min/max/sum
Basics
An array is a contiguous block of memory holding elements of the same type. Elements are accessed by zero-based index: arr[0] is the first, arr[n-1] is the last. The array name itself is a pointer to the first element. Here we declare an int array, initialise it with a brace list, and compute the sum, minimum, and maximum in a single pass — the most common array pattern in any C program.
ex1_array_basics.c
C
#include <stdio.h>

int main() {
    int marks[] = { 85, 92, 78, 95, 60, 88, 74 };
    int n = sizeof(marks) / sizeof(marks[0]);   /* 7 */

    int sum = 0, mn = marks[0], mx = marks[0];

    for (int i = 0; i < n; i++) {
        sum += marks[i];
        if (marks[i] < mn) mn = marks[i];
        if (marks[i] > mx) mx = marks[i];
    }

    printf("Marks  : ");
    for (int i = 0; i < n; i++)
        printf("%d ", marks[i]);
    printf("\n");

    printf("Count  : %d\n",     n);
    printf("Sum    : %d\n",     sum);
    printf("Average: %.2f\n",  (double)sum / n);
    printf("Min    : %d\n",     mn);
    printf("Max    : %d\n",     mx);

    /* sizeof trick — always use it, never hard-code n */
    printf("\nsizeof(marks) = %zu bytes\n", sizeof(marks));
    printf("sizeof(int)   = %zu bytes\n", sizeof(int));
    return 0;
}
output
Marks  : 85 92 78 95 60 88 74
Count  : 7
Sum    : 572
Average: 81.71
Min    : 60
Max    : 95

sizeof(marks) = 28 bytes
sizeof(int)   = 4 bytes
memory layout — 7 ints, contiguous, zero-indexed
marks[0..6]
85
92
78
95
60
88
74
← 7 × 4 = 28 bytes
index
[0]
[1]
[2]
[3]
[4]
[5]
[6]
← base address + i×4
sizeof trick: int n = sizeof(arr) / sizeof(arr[0]) always gives the correct element count — even if you add or remove values from the initialiser list. Never hard-code n = 7; let the compiler count for you.
example 2
2
🧱 Struct Basics — Group Related Fields
Define a struct, create instances, access members with the dot operator
Struct
A struct bundles variables of different types under one name. Unlike an array — which holds elements of the same type — a struct lets you group a name, an age, a GPA, and a boolean together as a single logical unit. Members are accessed with the dot operator (.) for a variable, or the arrow operator (->) for a pointer. Here we build a simple Student struct and display its fields.
ex2_struct_basics.c
C
#include <stdio.h>
#include <string.h>

typedef struct {
    char  name[30];
    int   roll;
    float gpa;
    int   active;      /* 1 = enrolled, 0 = alumni */
} Student;

void printStudent(const Student *s) {
    printf("Roll %-4d | %-20s | GPA %.2f | %s\n",
           s->roll, s->name, s->gpa,
           s->active ? "Enrolled" : "Alumni");
}

int main() {
    /* Initialise with brace list */
    Student s1 = { "Ananya Sharma", 101, 8.9f, 1 };

    /* Initialise by field name (designated initialiser) */
    Student s2 = { .name = "Rohan Verma", .roll = 102,
                   .gpa = 7.6f, .active = 1 };

    /* Assign fields one by one */
    Student s3;
    strcpy(s3.name, "Priya Singh");
    s3.roll   = 103;
    s3.gpa    = 9.1f;
    s3.active = 0;

    printf("%-8s %-20s %s   %s\n",
           "Roll", "Name", "GPA", "Status");
    printf("%s\n", "------------------------------------------------------");
    printStudent(&s1);
    printStudent(&s2);
    printStudent(&s3);

    printf("\nsizeof(Student) = %zu bytes\n", sizeof(Student));
    return 0;
}
output
Roll     Name                 GPA   Status
------------------------------------------------------
Roll 101  | Ananya Sharma        | GPA 8.90 | Enrolled
Roll 102  | Rohan Verma          | GPA 7.60 | Enrolled
Roll 103  | Priya Singh          | GPA 9.10 | Alumni

sizeof(Student) = 40 bytes
Three ways to initialise a struct: brace list (order matters), designated initialisers (C99, order-independent, self-documenting), or field-by-field assignment. Prefer designated initialisers for structs with more than 3 fields — they're immune to field reordering bugs.
example 3
3
📋 Array of Structs — Classlist
Put multiple Student structs in an array — sort, search, and display
Array of Structs
The most common real-world pattern: an array of structs. Each element is a complete struct; you access individual fields with arr[i].field. Here we store five students, find the topper (highest GPA), count enrolled vs alumni, and print a formatted table — exactly the kind of record-processing loop that appears in every database, game engine, or server.
ex3_array_of_structs.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    int   roll;
    float gpa;
    int   active;
} Student;

int main() {
    Student cls[] = {
        { "Ananya",  101, 8.9f, 1 },
        { "Rohan",   102, 7.6f, 1 },
        { "Priya",   103, 9.1f, 0 },
        { "Karan",   104, 8.3f, 1 },
        { "Sunita",  105, 6.8f, 0 },
    };
    int n = sizeof(cls) / sizeof(cls[0]);

    printf("%-6s %-12s %5s  %s\n", "Roll", "Name", "GPA", "Status");
    printf("%s\n", "--------------------------------------");

    int topIdx = 0, enrolled = 0;
    for (int i = 0; i < n; i++) {
        printf("%-6d %-12s %5.2f  %s\n",
               cls[i].roll, cls[i].name, cls[i].gpa,
               cls[i].active ? "Enrolled" : "Alumni");
        if (cls[i].gpa > cls[topIdx].gpa) topIdx = i;
        if (cls[i].active) enrolled++;
    }

    printf("%s\n", "--------------------------------------");
    printf("Topper   : %s (GPA %.2f)\n", cls[topIdx].name, cls[topIdx].gpa);
    printf("Enrolled : %d / %d\n", enrolled, n);
    return 0;
}
output
Roll   Name          GPA   Status
--------------------------------------
101    Ananya        8.90  Enrolled
102    Rohan         7.60  Enrolled
103    Priya         9.10  Alumni
104    Karan         8.30  Enrolled
105    Sunita        6.80  Alumni
--------------------------------------
Topper   : Priya (GPA 9.10)
Enrolled : 3 / 5
Access pattern: cls[i].gpa — array index first, then dot-member. This scales to thousands of records. The same loop pattern (find max, count matching) is used in every data-processing task: leaderboards, inventory reports, log analysis, and more.
example 4
4
🏠 Nested Structs — Address Inside Student
A struct whose field is itself another struct — two levels of nesting
Nested Struct
Structs can contain other structs as fields — nested structs. An Address struct (city, state, pin) is embedded directly inside Student. You navigate with chained dots: s.addr.city. This keeps related data logically grouped and avoids a flat list of twenty loosely related fields. It is also how C libraries represent complex entities — a sockaddr contains a nested in_addr, for example.
ex4_nested_structs.c
C
#include <stdio.h>
#include <string.h>

typedef struct {
    char city[20];
    char state[20];
    int  pin;
} Address;

typedef struct {
    char    name[25];
    int     roll;
    Address addr;       /* nested struct */
    float   gpa;
} Student;

void printStudent(const Student *s) {
    printf("Name : %s  (Roll %d)\n", s->name, s->roll);
    printf("GPA  : %.2f\n", s->gpa);
    printf("Addr : %s, %s — %d\n\n",
           s->addr.city, s->addr.state, s->addr.pin);
}

int main() {
    Student s1 = {
        .name = "Ananya Sharma",
        .roll = 101,
        .gpa  = 8.9f,
        .addr = { .city = "Haridwar", .state = "Uttarakhand", .pin = 249401 }
    };

    Student s2;
    strcpy(s2.name, "Rohan Verma");
    s2.roll = 102; s2.gpa = 7.6f;
    strcpy(s2.addr.city,  "Dehradun");    /* chained dot access */
    strcpy(s2.addr.state, "Uttarakhand");
    s2.addr.pin = 248001;

    printf("--- Student Records ---\n\n");
    printStudent(&s1);
    printStudent(&s2);

    printf("sizeof(Address) = %zu\n", sizeof(Address));
    printf("sizeof(Student) = %zu\n", sizeof(Student));
    return 0;
}
output
--- Student Records ---

Name : Ananya Sharma  (Roll 101)
GPA  : 8.90
Addr : Haridwar, Uttarakhand — 249401

Name : Rohan Verma  (Roll 102)
GPA  : 7.60
Addr : Dehradun, Uttarakhand — 248001

sizeof(Address) = 44
sizeof(Student) = 76
Chained dot access: s.addr.city — as many levels deep as needed. With pointers: p->addr.city (arrow for the outermost pointer, dot for everything nested inside). This pattern is used in OS structs like struct stat, struct tm, and all network address types.
example 5
5
🔀 Struct + Union — Sensor Reading
A sensor can be temperature, humidity, or pressure — one struct covers all three
Struct + Union
This is where all three concepts lock together. A Sensor struct has a type tag (an enum), a name (array of char), and a union holding the actual reading. Temperature is a float. Humidity is a float percentage. Pressure is a double in Pascals. The union means we never waste memory on unused fields — each sensor object is exactly as big as its largest possible reading.
ex5_struct_union_sensor.c
C
#include <stdio.h>

typedef enum { TEMP, HUMIDITY, PRESSURE } SensorType;

typedef struct {
    char       label[16];
    SensorType type;
    union {
        float  celsius;      /* for TEMP */
        float  percent;      /* for HUMIDITY */
        double pascals;      /* for PRESSURE */
    } reading;
} Sensor;

void printSensor(const Sensor *s) {
    printf("%-14s", s->label);
    switch (s->type) {
        case TEMP:     printf("Temperature : %.1f °C\n",   s->reading.celsius);  break;
        case HUMIDITY: printf("Humidity    : %.1f %%\n",   s->reading.percent);  break;
        case PRESSURE: printf("Pressure    : %.0f Pa\n",  s->reading.pascals); break;
    }
}

int main() {
    Sensor sensors[] = {
        { "Roof",    TEMP,     { .celsius  = 38.5f    } },
        { "Lab",     HUMIDITY, { .percent  = 64.2f    } },
        { "Basement",PRESSURE, { .pascals  = 101325.0 } },
        { "Outdoor", TEMP,     { .celsius  = 42.0f    } },
        { "Kitchen", HUMIDITY, { .percent  = 75.0f    } },
    };
    int n = sizeof(sensors) / sizeof(sensors[0]);

    printf("--- Sensor Dashboard ---\n");
    for (int i = 0; i < n; i++)
        printSensor(&sensors[i]);

    printf("\nsizeof(Sensor) = %zu bytes\n", sizeof(Sensor));
    return 0;
}
output
--- Sensor Dashboard ---
Roof          Temperature : 38.5 °C
Lab           Humidity    : 64.2 %
Basement      Pressure    : 101325 Pa
Outdoor       Temperature : 42.0 °C
Kitchen       Humidity    : 75.0 %

sizeof(Sensor) = 32 bytes
Sensor memory layout — tag + label array + union share one struct
label[16]
char[16]
← 16 bytes
type (enum)
int
← 4 bytes
reading (union)
double (8B)
← largest member wins
Always match the tag to the union member you read. If type == TEMP, read reading.celsius. Reading reading.pascals when the tag says TEMP gives garbage — the raw bytes are reinterpreted as a double. The tag is your contract.
example 6
6
📬 Passing Structs to Functions
Pass by value vs pass by pointer — see the difference with a swap example
Functions
Structs can be passed to functions by value (a full copy is made — changes don't affect the original) or by pointer (only the address is passed — changes affect the original). For large structs, always prefer pointer: it avoids copying dozens of bytes on every call. Here we demonstrate both — a display function takes a const pointer (read-only), and a swap function takes two pointers to exchange two struct variables.
ex6_struct_functions.c
C
#include <stdio.h>

typedef struct {
    char  name[20];
    int   score;
} Player;

/* Pass by const pointer — no copy, read-only */
void display(const Player *p) {
    printf("  %-15s %d\n", p->name, p->score);
}

/* Pass by value — gets its own copy, original unchanged */
void addBonus(Player p, int bonus) {
    p.score += bonus;    /* only modifies the local copy */
    printf("  Inside addBonus: %s -> %d\n", p.name, p.score);
}

/* Pass by pointer — modifies the original */
void swap(Player *a, Player *b) {
    Player tmp = *a;
    *a = *b;
    *b = tmp;
}

int main() {
    Player p1 = { "Karan", 850 };
    Player p2 = { "Sunita", 920 };

    printf("Before:\n");
    display(&p1); display(&p2);

    printf("\naddBonus (pass by value):\n");
    addBonus(p1, 100);
    printf("  After call: p1.score still = %d\n", p1.score);

    printf("\nswap (pass by pointer):\n");
    swap(&p1, &p2);
    printf("After swap:\n");
    display(&p1); display(&p2);
    return 0;
}
output
Before:
  Karan           850
  Sunita          920

addBonus (pass by value):
  Inside addBonus: Karan -> 950
  After call: p1.score still = 850

swap (pass by pointer):
After swap:
  Sunita          920
  Karan           850
Rule of thumb: read-only access → const Struct *p. Modifying the struct → Struct *p. Returning a new struct from a function → return by value (compilers optimise this with RVO). Never pass large structs by value in a loop — you copy the entire struct on every iteration.
example 7
7
🎓 Student Records System — Sort by GPA
Array of structs + bubble sort + linear search — a complete mini DBMS
Mini App
Everything so far combined into a mini student records system. We store ten students in an array of structs, sort them by GPA using bubble sort (swapping entire struct objects), search by roll number using linear search, and print a grade letter for each. This is the archetype for small file-less databases in C — the same logic powers embedded systems, exam results processors, and small admin tools.
ex7_student_records.c
C
#include <stdio.h>
#include <string.h>

typedef struct {
    int   roll;
    char  name[20];
    float gpa;
} Student;

char grade(float g) {
    if (g >= 9.0f) return 'A';
    if (g >= 8.0f) return 'B';
    if (g >= 7.0f) return 'C';
    if (g >= 6.0f) return 'D';
    return 'F';
}

/* Bubble sort — descending GPA */
void sortByGPA(Student *arr, int n) {
    for (int i = 0; i < n-1; i++)
        for (int j = 0; j < n-i-1; j++)
            if (arr[j].gpa < arr[j+1].gpa) {
                Student tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
}

/* Linear search by roll */
Student* findByRoll(Student *arr, int n, int roll) {
    for (int i = 0; i < n; i++)
        if (arr[i].roll == roll) return &arr[i];
    return NULL;
}

int main() {
    Student cls[] = {
        {101,"Ananya",8.9f},{102,"Rohan",7.6f},
        {103,"Priya",9.4f},{104,"Karan",6.2f},
        {105,"Sunita",8.1f},{106,"Amit",5.7f},
        {107,"Divya",9.0f},{108,"Vijay",7.3f},
        {109,"Meena",8.5f},{110,"Arjun",6.8f},
    };
    int n = sizeof(cls)/sizeof(cls[0]);

    sortByGPA(cls, n);

    printf("Rank %-6s %-12s  GPA  Grade\n","Roll","Name");
    printf("%s\n","------------------------------------------");
    for (int i = 0; i < n; i++)
        printf(" %2d  %-6d %-12s  %.1f    %c\n",
               i+1, cls[i].roll, cls[i].name,
               cls[i].gpa, grade(cls[i].gpa));

    printf("\nSearch roll 106: ");
    Student *found = findByRoll(cls, n, 106);
    if (found)
        printf("%s GPA %.1f Grade %c\n",
               found->name, found->gpa, grade(found->gpa));
    return 0;
}
output
Rank Roll   Name          GPA  Grade
------------------------------------------
  1  103    Priya         9.4    A
  2  107    Divya         9.0    A
  3  101    Ananya        8.9    B
  4  109    Meena         8.5    B
  5  105    Sunita        8.1    B
  6  102    Rohan         7.6    C
  7  108    Vijay         7.3    C
  8  110    Arjun         6.8    D
  9  104    Karan         6.2    D
 10  106    Amit          5.7    F

Search roll 106: Amit GPA 5.7 Grade F
Swapping structs is just tmp = a; a = b; b = tmp; The compiler copies the entire struct in each assignment. For large structs (>100 bytes), swap indices or use an array of pointers to structs instead — you then swap only the 8-byte pointer, not the whole struct.
example 8
8
🛒 Inventory System — Struct + Union + Array
Products can be weighed, counted, or sold by volume — one array covers all
Real World
A practical inventory: products come in three sale types — by weight (kg), by count (units), or by volume (litres). Each needs different data. A tagged union inside the Product struct holds exactly the right field for each type. An array of Product covers all three types in one list. A single totalValue() function computes stock value for any product regardless of type.
ex8_inventory.c
C
#include <stdio.h>

typedef enum { BY_WEIGHT, BY_COUNT, BY_VOLUME } SaleType;

typedef struct {
    char     name[20];
    float    pricePerUnit;   /* per kg, per unit, per litre */
    SaleType saleType;
    union {
        float kg;           /* BY_WEIGHT */
        int   units;        /* BY_COUNT  */
        float litres;       /* BY_VOLUME */
    } stock;
} Product;

float totalValue(const Product *p) {
    switch (p->saleType) {
        case BY_WEIGHT: return p->pricePerUnit * p->stock.kg;
        case BY_COUNT:  return p->pricePerUnit * p->stock.units;
        case BY_VOLUME: return p->pricePerUnit * p->stock.litres;
    }
    return 0;
}

void printProduct(const Product *p) {
    const char *units[] = { "kg", "units", "litres" };
    float qty;
    switch(p->saleType){
        case BY_WEIGHT: qty=p->stock.kg;     break;
        case BY_COUNT:  qty=p->stock.units;  break;
        case BY_VOLUME: qty=p->stock.litres; break;
        default: qty=0;
    }
    printf("%-14s Rs%6.2f/%-6s  stock:%6.1f  value: Rs%.2f\n",
           p->name, p->pricePerUnit, units[p->saleType],
           qty, totalValue(p));
}

int main() {
    Product inv[] = {
        { "Rice",        55.0f,  BY_WEIGHT, { .kg     = 200.5f } },
        { "Pen",         12.0f,  BY_COUNT,  { .units  = 500    } },
        { "Cooking Oil", 130.0f, BY_VOLUME, { .litres = 80.0f  } },
        { "Sugar",       44.0f,  BY_WEIGHT, { .kg     = 150.0f } },
        { "Notebook",    60.0f,  BY_COUNT,  { .units  = 300    } },
    };
    int n = sizeof(inv)/sizeof(inv[0]);

    printf("%-14s %-18s %-12s %s\n","Product","Price/Unit","Stock","Value");
    printf("%s\n","--------------------------------------------------------------");

    float grandTotal = 0;
    for (int i = 0; i < n; i++) {
        printProduct(&inv[i]);
        grandTotal += totalValue(&inv[i]);
    }
    printf("%s\n","--------------------------------------------------------------");
    printf("%-44s  TOTAL: Rs%.2f\n", "", grandTotal);
    return 0;
}
output
Product        Price/Unit          Stock        Value
--------------------------------------------------------------
Rice           Rs 55.00/kg      stock: 200.5  value: Rs11027.50
Pen            Rs 12.00/units   stock: 500.0  value: Rs6000.00
Cooking Oil    Rs130.00/litres  stock:  80.0  value: Rs10400.00
Sugar          Rs 44.00/kg      stock: 150.0  value: Rs6600.00
Notebook       Rs 60.00/units   stock: 300.0  value: Rs18000.00
--------------------------------------------------------------
                                          TOTAL: Rs52027.50
The union saves memory here because each product only ever stores one quantity type. Without a union, the struct would need float kg, int units, float litres — all three fields, all the time — wasting 8 bytes per product. With a union, only the largest field is allocated, and the right one is always in use.
example 9
9
🗺️ 2D Array of Structs — Seating Chart
A 2-D grid of seat structs — row/col access, search by name, count empty seats
2D Arrays
A 2-D array of structs — each element is a Seat struct; the whole grid represents a cinema hall. Access a seat with hall[row][col].field. The grid makes it natural to print a visual layout, search across all seats for a name, and count vacant seats — the exact same operations a real booking system performs.
ex9_seating_chart.c
C
#include <stdio.h>
#include <string.h>

#define ROWS 4
#define COLS 6

typedef struct {
    int  booked;
    char guest[15];
} Seat;

void book(Seat hall[ROWS][COLS], int r, int c, const char *name) {
    if (!hall[r][c].booked) {
        hall[r][c].booked = 1;
        strncpy(hall[r][c].guest, name, 14);
    } else {
        printf("R%dC%d already booked!\n", r, c);
    }
}

void printHall(Seat hall[ROWS][COLS]) {
    printf("    ");
    for (int c = 0; c < COLS; c++) printf(" C%d  ", c+1);
    printf("\n");
    for (int r = 0; r < ROWS; r++) {
        printf("R%d  ", r+1);
        for (int c = 0; c < COLS; c++)
            printf("[%s] ", hall[r][c].booked ? "XX" : "  ");
        printf("\n");
    }
}

int main() {
    Seat hall[ROWS][COLS];
    /* zero-init all seats */
    for (int r = 0; r < ROWS; r++)
        for (int c = 0; c < COLS; c++)
            hall[r][c].booked = 0;

    book(hall, 0, 0, "Ananya");
    book(hall, 0, 1, "Rohan");
    book(hall, 1, 3, "Priya");
    book(hall, 2, 5, "Karan");
    book(hall, 3, 2, "Sunita");
    book(hall, 0, 0, "Duplicate");   /* conflict */

    printf("--- Seating Chart ---\n");
    printHall(hall);

    int empty = 0;
    for (int r = 0; r < ROWS; r++)
        for (int c = 0; c < COLS; c++)
            if (!hall[r][c].booked) empty++;
    printf("\nBooked: %d  |  Empty: %d  |  Total: %d\n",
           ROWS*COLS - empty, empty, ROWS*COLS);

    printf("\n--- Guest list ---\n");
    for (int r = 0; r < ROWS; r++)
        for (int c = 0; c < COLS; c++)
            if (hall[r][c].booked)
                printf("  R%d-C%d : %s\n", r+1, c+1, hall[r][c].guest);
    return 0;
}
output
R0C0 already booked!
--- Seating Chart ---
     C1   C2   C3   C4   C5   C6
R1  [XX] [XX] [  ] [  ] [  ] [  ]
R2  [  ] [  ] [  ] [XX] [  ] [  ]
R3  [  ] [  ] [  ] [  ] [  ] [XX]
R4  [  ] [  ] [XX] [  ] [  ] [  ]

Booked: 5  |  Empty: 19  |  Total: 24

--- Guest list ---
  R1-C1 : Ananya
  R1-C2 : Rohan
  R2-C4 : Priya
  R3-C6 : Karan
  R4-C3 : Sunita
2-D array access pattern: hall[row][col].field — two indices, then the dot. In memory it is still a flat contiguous block — element [r][c] lives at offset (r * COLS + c) * sizeof(Seat). Pass a 2-D array to a function as Seat arr[][COLS] — the column count must be known at compile time.
example 10
10
🏗️ Full App — Hospital Patient Registry
Arrays + nested structs + tagged union + sort + search — one complete C program
Complete App
Every concept from Examples 1–9 in one real application. A hospital patient registry stores patients with nested struct contact info, a tagged union for diagnosis data (inpatient needs a bed number and ward; outpatient needs a clinic slot string), an array to hold all records, and functions to sort by name and search by ID. The design is extensible: add a new patient type by touching exactly four places.
ex10_hospital_registry.c
C
#include <stdio.h>
#include <string.h>

typedef enum { INPATIENT, OUTPATIENT } PatientType;

typedef struct {
    char phone[12];
    char city[20];
} Contact;    /* nested struct */

typedef struct {
    int         id;
    char        name[25];
    int         age;
    Contact     contact;           /* nested struct */
    PatientType type;
    union {                        /* tagged union */
        struct {
            int  bedNo;
            char ward[15];
        } inp;
        struct {
            char slot[20];
        } outp;
    } care;
} Patient;

void printPatient(const Patient *p) {
    printf("ID:%04d  %-20s  Age:%3d  Ph:%-12s  City:%-12s\n",
           p->id, p->name, p->age,
           p->contact.phone, p->contact.city);
    if (p->type == INPATIENT)
        printf("         [INPATIENT]  Bed:%d  Ward:%s\n",
               p->care.inp.bedNo, p->care.inp.ward);
    else
        printf("         [OUTPATIENT] Slot:%s\n", p->care.outp.slot);
}

/* Sort by name (bubble sort) */
void sortByName(Patient *arr, int n) {
    for (int i = 0; i < n-1; i++)
        for (int j = 0; j < n-i-1; j++)
            if (strcmp(arr[j].name, arr[j+1].name) > 0) {
                Patient t = arr[j]; arr[j] = arr[j+1]; arr[j+1] = t;
            }
}

/* Search by ID */
Patient* findByID(Patient *arr, int n, int id) {
    for (int i = 0; i < n; i++)
        if (arr[i].id == id) return &arr[i];
    return NULL;
}

int main() {
    Patient reg[] = {
        { 1003, "Meena Gupta",    45,
          { "9876543210", "Haridwar" },
          INPATIENT,  { .inp  = { 12, "Cardiology" } } },

        { 1001, "Arjun Rawat",    32,
          { "9123456789", "Dehradun"  },
          OUTPATIENT, { .outp = { "Mon 10:00am"  } } },

        { 1004, "Sunita Devi",    60,
          { "9988776655", "Rishikesh" },
          INPATIENT,  { .inp  = { 7,  "Ortho"      } } },

        { 1002, "Aditya Kumar",   28,
          { "9001122334", "Roorkee"   },
          OUTPATIENT, { .outp = { "Wed 3:30pm"   } } },

        { 1005, "Priya Negi",     37,
          { "9765432100", "Mussoorie" },
          INPATIENT,  { .inp  = { 22, "Neurology" } } },
    };
    int n = sizeof(reg) / sizeof(reg[0]);

    printf("=== Hospital Registry (sorted by name) ===\n\n");
    sortByName(reg, n);
    for (int i = 0; i < n; i++) { printPatient(&reg[i]); printf("\n"); }

    printf("--- Search ID 1004 ---\n");
    Patient *f = findByID(reg, n, 1004);
    if (f) printPatient(f);

    int inp=0, outp=0;
    for (int i=0; i<n; i++)
        (reg[i].type==INPATIENT ? inp : outp)++;
    printf("\nInpatients: %d  Outpatients: %d\n", inp, outp);
    return 0;
}
output
=== Hospital Registry (sorted by name) ===

ID:1002  Aditya Kumar          Age: 28  Ph:9001122334    City:Roorkee
         [OUTPATIENT] Slot:Wed 3:30pm

ID:1001  Arjun Rawat           Age: 32  Ph:9123456789    City:Dehradun
         [OUTPATIENT] Slot:Mon 10:00am

ID:1003  Meena Gupta           Age: 45  Ph:9876543210    City:Haridwar
         [INPATIENT]  Bed:12  Ward:Cardiology

ID:1005  Priya Negi            Age: 37  Ph:9765432100    City:Mussoorie
         [INPATIENT]  Bed:22  Ward:Neurology

ID:1004  Sunita Devi           Age: 60  Ph:9988776655    City:Rishikesh
         [INPATIENT]  Bed:7   Ward:Ortho

--- Search ID 1004 ---
ID:1004  Sunita Devi           Age: 60  Ph:9988776655    City:Rishikesh
         [INPATIENT]  Bed:7   Ward:Ortho

Inpatients: 3  Outpatients: 2
All 5 patterns in one program: Contact as nested struct · PatientType enum as tag · union care with two inner structs · Patient reg[] as array of structs · sortByName and findByID as the canonical data-processing functions. Add a new patient type (e.g. EMERGENCY) by adding one enum value, one struct in the union, and one branch in printPatient — nothing else changes.
checklist
  • Ex 1 — sizeof(arr) / sizeof(arr[0]) gives the element count. Arrays are zero-indexed, contiguous, same type.
  • Ex 2 — Struct groups different types. Dot operator for variables, arrow for pointers. Three init styles: brace list, designated, field-by-field.
  • Ex 3 — Array of structs: arr[i].field. One loop can sort, search, count, and aggregate over all records.
  • Ex 4 — Nested struct: s.outer.inner chained dots. With pointer: p->outer.inner. sizeof(outer) includes sizeof(inner).
  • Ex 5 — Struct + union: tag enum tells you which union member is valid. Read only the member matching the tag.
  • Ex 6 — Pass struct by const * for reading (no copy). Pass by * to modify. Pass by value for a safe local copy.
  • Ex 7 — Bubble sort swaps entire struct objects. For big structs, swap pointers or indices instead to avoid copying.
  • Ex 8 — Tagged union inside struct for inventory variants. totalValue() dispatches on tag — one function handles all product types.
  • Ex 9 — 2-D array of structs: arr[row][col].field. Pass to functions as Seat arr[][COLS] — column count required.
  • Ex 10 — Full app: nested struct + enum tag + tagged union + array + sort + search. Add a new variant in 4 places only.