Skip to main content

Lecture 4: Arrays

Prerequisites:Lecture 3 Loops (for, while, do-while); basic I/O with scanf and printf

1. Overview

  • Arrays address the fundamental problem of storing and manipulating collections of homogeneous data without declaring a separate variable for each value.
  • Upon completing this chapter, students will be able to declare, initialise, traverse, and manipulate one-dimensional and two-dimensional arrays, work with character arrays and C strings, and implement elementary array algorithms such as searching, reversing, and sorting.
  • This chapter builds directly on the loop constructs introduced in Lecture 3 and lays the groundwork for pointer arithmetic and dynamic memory management in Lecture 7.

2. Learning Outcomes

Upon completing this chapter, students will be able to:

  1. Declare and initialise one-dimensional arrays of various data types, and explain their contiguous memory layout.
  2. Access, update, and traverse array elements using index-based notation and loop constructs.
  3. Read user input into arrays using scanf, getchar, and fgets, and explain the differences among these input methods.
  4. Declare, initialise, and manipulate character arrays (C strings), including manual computation of string length and character-level transformations.
  5. Declare and operate on two-dimensional arrays (matrices), including row-wise and element-wise traversal.
  6. Implement common array algorithms: finding extrema, reversing, counting by predicate, and bubble sort.

3. Key Terms

Term (EN)中文Notes
Array数组A fixed-size, contiguous block of elements of the same type.
Index (subscript)下标/索引Starts from 0 in C, not 1. arr[0] is the first element.
Initialiser list初始化列表Brace-enclosed values, e.g. {1, 2, 3}. Partial initialisation zero-fills the remainder.
Contiguous memory连续内存Array elements are stored adjacently; address of element i = base + i × element size.
Null terminator ('\0')空字符/终止符Marks the end of a C string; a char array of n visible characters requires n + 1 bytes.
C stringC 字符串A char array terminated by '\0'; not a first-class type but a convention.
fgetsReads up to size − 1 characters including spaces; safer than gets (which is deprecated).
Two-dimensional array二维数组An "array of arrays"; stored in row-major order in memory.
Row-major order行优先存储Elements of the same row are contiguous in memory.
Bubble sort冒泡排序An O(n²) comparison-based sorting algorithm that repeatedly swaps adjacent elements.
Sentinel value哨兵值A special value (e.g. −1) used to signal the end of input rather than specifying a fixed count.

4. Core Concepts

4.1 Introduction to Arrays

: An array is a collection of elements of the same data type stored in contiguous memory locations. Rather than declaring individual variables x0, x1, x2, …, one may declare a single array and access each element through an integer index. This makes it straightforward to process large volumes of data using loops. Arrays in C may be one-dimensional (a simple sequence) or multi-dimensional (e.g. a matrix).

The general declaration syntax is:

data_type array_name[size];

where data_type specifies the element type (e.g. int, float, char), array_name is a programmer-chosen identifier, and size is a compile-time constant indicating how many elements the array can hold. Once declared, neither the size nor the type of an array can be changed.

4.2 Declaration and Initialisation

: An array may be declared with or without an explicit initialiser list. When an initialiser list is provided, the compiler can infer the array size automatically if the programmer omits it. If a size is given but fewer initialisers are supplied, the remaining elements are zero-initialised.

Minimal example File: p04_a.c Build: gcc p04_a.c -o p04_a Run: ./p04_a

#include <stdio.h>

int main(void) {
// Explicit size with full initialiser list
int arr1[5] = {2, 4, 6, 8, 10};

// Size inferred from the initialiser list
int arr2[] = {1, 3, 5, 7, 9};

// Partial initialisation: arr3[2]..arr3[4] become 0
int arr3[5] = {10, 20};

printf("arr1: ");
for (int i = 0; i < 5; i++) printf("%d ", arr1[i]);

printf("\narr2: ");
for (int i = 0; i < 5; i++) printf("%d ", arr2[i]);

printf("\narr3: ");
for (int i = 0; i < 5; i++) printf("%d ", arr3[i]);

printf("\n");
return 0;
}

What you should see

arr1: 2 4 6 8 10
arr2: 1 3 5 7 9
arr3: 10 20 0 0 0

Takeaway:The initialiser list {…} is the idiomatic way to set array values at declaration time; omitting the size lets the compiler count for you, reducing the chance of a mismatch.

4.3 Memory Representation

: Array elements occupy contiguous bytes in memory. For an array whose base address is B and whose element type occupies S bytes, the address of element at index i is:

Address(arr[i]) = B + i × S

For example, if int arr[] = {1, 3, 5, 7, 9} is stored starting at address 0x60 and sizeof(int) is 4 bytes, then arr[0] resides at 0x60, arr[1] at 0x64, arr[2] at 0x68, and so on. This formula is the basis of pointer arithmetic, which will be explored in Lecture 7.

A useful idiom for computing the number of elements at compile time is:

int size = sizeof(arr) / sizeof(arr[0]);

This divides the total byte count of the array by the byte count of a single element, yielding the element count without hard-coding the size.

Minimal example File: p04_b.c Build: gcc p04_b.c -o p04_b Run: ./p04_b

#include <stdio.h>

int main(void) {
int arr[] = {1, 3, 5, 7, 9};
int size = sizeof(arr) / sizeof(arr[0]);

printf("Array has %d elements.\n", size);
printf("Total size in bytes: %lu\n", sizeof(arr));
printf("Size of one element : %lu\n", sizeof(arr[0]));

return 0;
}

What you should see

Array has 5 elements.
Total size in bytes: 20
Size of one element : 4

Takeaway:The sizeof idiom is portable and maintenance-friendly; always prefer it over manually counting elements.

4.4 Accessing and Updating Array Elements

: Array elements are accessed by appending the index in square brackets to the array name: arr[i]. In C, indices start at 0, so the first element is arr[0] and the last element of an array of size n is arr[n − 1]. Accessing an index outside the range [0, n − 1] leads to undefined behaviour — the compiler will not warn you by default, and the program may crash or silently corrupt memory.

Elements may be updated either by direct assignment (arr[0] = 9;) or by reading from the keyboard (scanf("%d", &arr[1]);). Note the use of the address-of operator & when passing an element to scanf.

Minimal example File: p04_c.c Build: gcc p04_c.c -o p04_c Run: ./p04_c

#include <stdio.h>

int main(void) {
int arr[5];

printf("Enter 5 integers:\n");
for (int i = 0; i < 5; i++) {
printf("Element %d: ", i + 1);
scanf("%d", &arr[i]);
}

printf("\nYou entered:\n");
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}

// Update the first element
arr[0] = 99;
printf("\nAfter setting arr[0] = 99:\n");
printf("arr[0] = %d\n", arr[0]);

return 0;
}

What you should see:The program prints the five user-supplied values, then confirms that arr[0] has been overwritten to 99.

Takeaway:Always ensure the loop index stays within [0, size − 1]; an off-by-one error is the most common source of array-related bugs.

4.5 Character Arrays and C Strings

: A character array can store a string — a sequence of characters terminated by the null character '\0'. If the string contains n visible characters, the array must have at least n + 1 elements to accommodate the terminator. Two equivalent declaration forms are available:

char name[] = "John"; // string literal; compiler adds '\0'
char name[] = {'J', 'o', 'h', 'n', '\0'}; // explicit character-by-character

When printing, printf("%s", name) outputs characters from the base of the array until it encounters '\0'. One may also print character-by-character using a while loop that checks for the terminator.

Minimal example File: p04_d.c Build: gcc p04_d.c -o p04_d Run: ./p04_d

#include <stdio.h>

int main(void) {
char greeting[] = "Hello";

// Method 1: print the whole string
printf("Method 1: %s\n", greeting);

// Method 2: print character by character
printf("Method 2: ");
int i = 0;
while (greeting[i] != '\0') {
printf("%c", greeting[i]);
i++;
}
printf("\n");

// Show the underlying array size
printf("Array size (including '\\0'): %lu\n", sizeof(greeting));

return 0;
}

What you should see

Method 1: Hello
Method 2: Hello
Array size (including '\0'): 6

Takeaway:Always account for the null terminator when sizing character arrays; forgetting it is the root cause of many string-related bugs.

4.6 String Input Methods

: C offers several mechanisms for reading string input. Each has distinct behaviour regarding whitespace and buffer safety:

FunctionReads spaces?Buffer-safe?Typical use
scanf("%s", str)No — stops at first whitespaceNo built-in limitSingle-word input
getchar() in a loopYesManual bounds check requiredCharacter-by-character input
fgets(str, size, stdin)YesYes — reads at most size − 1 charsRecommended for general string input

scanf("%s", str) is the simplest but reads only until the first whitespace character, making it unsuitable for sentences. getchar() reads one character at a time and can capture spaces, but requires the programmer to manage buffer limits manually. fgets() is the safest choice: it reads up to size − 1 characters (including spaces), then appends '\0'. Note that fgets retains the trailing newline if the buffer is large enough.

Minimal example File: p04_e.c Build: gcc p04_e.c -o p04_e Run: ./p04_e

#include <stdio.h>

int main(void) {
char word[20];
char sentence[100];

/* scanf reads a single word (no spaces) */
printf("Enter a single word: ");
scanf("%s", word);
printf("scanf read: %s\n", word);

/* Consume the leftover newline from scanf */
while (getchar() != '\n');

/* fgets reads the full line including spaces */
printf("Enter a sentence: ");
fgets(sentence, sizeof(sentence), stdin);
printf("fgets read: %s", sentence);

return 0;
}

What you should seescanf captures only the first word; fgets captures the entire line including spaces.

Takeaway:Prefer fgets for any input that may contain spaces; always consume leftover characters in the input buffer when mixing scanf with line-oriented functions.

4.7 Two-Dimensional Arrays

: A two-dimensional array is declared with two size specifiers: data_type name[rows][cols];. Conceptually it represents a table or matrix. In memory, C stores 2D arrays in row-major order: all elements of row 0 come first, followed by all elements of row 1, and so on. Elements are accessed with two indices: arr[row][col].

Initialisation may be done with nested braces for clarity, or with a flat list:

int m[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
int m[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; // equivalent

The #define preprocessor directive is commonly used to name the dimensions, improving readability and maintainability:

#define ROWS 3
#define COLS 4

Minimal example File: p04_f.c Build: gcc p04_f.c -o p04_f Run: ./p04_f

#include <stdio.h>
#define ROWS 3
#define COLS 4

int main(void) {
int matrix[ROWS][COLS] = {
{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{ 9, 10, 11, 12}
};

printf("Matrix contents:\n");
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
printf("%4d", matrix[i][j]);
}
printf("\n");
}

printf("\nElement at row 1, col 2: %d\n", matrix[1][2]);

return 0;
}

What you should see

Matrix contents:
1 2 3 4
5 6 7 8
9 10 11 12

Element at row 1, col 2: 7

Takeaway:Use #define for matrix dimensions and nested loops for traversal; remember that row-major storage means iterating over columns in the inner loop is cache-friendly.

4.8 Bubble Sort

: Bubble sort is a simple comparison-based sorting algorithm. It works by repeatedly stepping through the array, comparing each pair of adjacent elements, and swapping them if they are in the wrong order. After each complete pass, the largest unsorted element "bubbles" to its correct position at the end of the array. The algorithm terminates when a full pass produces no swaps, indicating that the array is sorted.

The algorithm has a worst-case and average-case time complexity of O(n²), making it inefficient for large datasets but valuable as a pedagogical tool for understanding sorting logic and nested loops.

Minimal example File: p04_g.c Build: gcc p04_g.c -o p04_g Run: ./p04_g

#include <stdio.h>

int main(void) {
int arr[] = {9, 5, 3, 8, 2, 7, 6, 1, 4, 10};
int n = sizeof(arr) / sizeof(arr[0]);

/* Bubble sort — ascending order */
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}

printf("Sorted array: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");

return 0;
}

What you should see

Sorted array: 1 2 3 4 5 6 7 8 9 10

Takeaway:Bubble sort illustrates the power of nested loops over arrays; in later courses you will learn more efficient algorithms (e.g. merge sort, quicksort) with O(n log n) complexity.

5. Worked Examples

Example 1:Average Exam Grade

Problem: Write a program that reads 5 student grades (floating-point values in the range 0–100), stores them in an array, computes their average, and prints the result to one decimal place.

Steps

  1. Declare a float array of size 5 and a running total variable initialised to 0.
  2. Use a for loop to read each grade via scanf and accumulate it in the total.
  3. Compute the average by dividing the total by 5 and print the result.

Reference code File: p04_ex1.c Build: gcc p04_ex1.c -o p04_ex1 Run: ./p04_ex1

#include <stdio.h>

int main(void) {
float grades[5];
float total = 0.0f;

for (int i = 0; i < 5; i++) {
printf("Enter the grade of student %d (0-100): ", i + 1);
scanf("%f", &grades[i]);
total += grades[i];
}

float avg = total / 5.0f;
printf("The average of grades is %.1f\n", avg);

return 0;
}

Output

Enter the grade of student 1 (0-100): 85
Enter the grade of student 2 (0-100): 90
Enter the grade of student 3 (0-100): 78
Enter the grade of student 4 (0-100): 92
Enter the grade of student 5 (0-100): 88
The average of grades is 86.6

Why it works: The loop simultaneously reads input and accumulates the total, avoiding a second pass. Dividing by 5.0f (rather than the integer 5) ensures floating-point division, preserving the fractional part of the result.

Example 2:Find Maximum in an Array

Problem: Write a program that asks the user to enter 4 integers, stores them in an array, and then finds and prints the maximum value.

Steps

  1. Declare an int array of size 4 and read the values from the user.
  2. Initialise a variable max to the first element of the array.
  3. Iterate through the remaining elements; whenever an element exceeds max, update max.
  4. Print the result.

Reference code File: p04_ex2.c Build: gcc p04_ex2.c -o p04_ex2 Run: ./p04_ex2

#include <stdio.h>

int main(void) {
int arr[4];

printf("Enter 4 numbers: ");
for (int i = 0; i < 4; i++) {
scanf("%d", &arr[i]);
}

int max = arr[0];
for (int i = 1; i < 4; i++) {
if (arr[i] > max) {
max = arr[i];
}
}

printf("Maximum value = %d\n", max);
return 0;
}

Output

Enter 4 numbers: 4 12 9 23
Maximum value = 23

Why it works: By initialising max to arr[0] rather than to an arbitrary value (e.g. 0), the algorithm works correctly regardless of whether the array contains negative numbers. The single-pass scan has O(n) time complexity.

Example 3:Reverse an Array

Problem: Write a program that reads positive integers from the user (terminated by −1 as a sentinel), stores them in an array, and prints the array before and after reversing.

Steps

  1. Read integers in a loop until −1 is encountered; count the number of elements.
  2. Print the original array.
  3. Swap elements from both ends toward the centre using a single loop.
  4. Print the reversed array.

Reference code File: p04_ex3.c Build: gcc p04_ex3.c -o p04_ex3 Run: ./p04_ex3

#include <stdio.h>

int main(void) {
int arr[100];
int n = 0, val;

printf("Enter elements [-1 to stop]: ");
while (scanf("%d", &val) == 1 && val != -1) {
arr[n++] = val;
}

/* Print original */
printf("Original: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");

/* Reverse in place */
for (int i = 0; i < n / 2; i++) {
int tmp = arr[i];
arr[i] = arr[n - 1 - i];
arr[n - 1 - i] = tmp;
}

/* Print reversed */
printf("Reversed: ");
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
printf("\n");

return 0;
}

Output

Enter elements [-1 to stop]: 23 11 31 28 -1
Original: 23 11 31 28
Reversed: 28 31 11 23

Why it works: The reversal loop runs only ⌊n/2⌋ iterations. At each step it swaps arr[i] with arr[n − 1 − i], meeting in the middle. This is an in-place algorithm requiring only O(1) extra space.

Example 4:Lowercase to Uppercase Conversion

Problem: Write a program that reads a word from the user and converts all lowercase letters to uppercase without using the toupper() library function.

Steps

  1. Read a word into a character array using scanf("%s", str).
  2. Iterate through each character; if it lies in the range 'a' to 'z', subtract 32 (the ASCII offset between lowercase and uppercase letters).
  3. Print the modified string.

Reference code File: p04_ex4.c Build: gcc p04_ex4.c -o p04_ex4 Run: ./p04_ex4

#include <stdio.h>

int main(void) {
char str[100];

printf("Enter a string: ");
scanf("%s", str);

for (int i = 0; str[i] != '\0'; i++) {
if (str[i] >= 'a' && str[i] <= 'z') {
str[i] = str[i] - ('a' - 'A'); /* subtract 32 */
}
}

printf("Uppercase: %s\n", str);
return 0;
}

Output

Enter a string: hello
Uppercase: HELLO

Why it works: In the ASCII table, every lowercase letter is exactly 32 positions above its uppercase counterpart. The expression 'a' - 'A' evaluates to 32, making the code self-documenting and independent of any particular numeric constant. The loop terminates naturally when it reaches the null terminator.

6. Common Mistakes

  1. Off-by-one index error: Accessing arr[n] when valid indices are 0 to n − 1. This leads to undefined behaviour — the program may crash, produce garbage, or appear to work intermittently.
  2. Forgetting the null terminator in character arrays: Declaring char s[5] = "Hello" leaves no room for '\0', causing printf("%s", s) to read past the array boundary. The array must be at least of size 6.
  3. Using sizeof on a pointer instead of an array: When an array is passed to a function, it decays to a pointer. Inside the function, sizeof(arr) / sizeof(arr[0]) yields the pointer size divided by the element size, not the array length. Always pass the size as a separate parameter.
  4. Reading multi-word input with scanf("%s", …): scanf stops at the first whitespace, silently discarding the rest of the line. Use fgets for input that may contain spaces.
  5. Not consuming the trailing newline after scanf: When scanf("%d", …) is followed by fgets or getchar, the newline left in the input buffer by scanf is consumed immediately, giving the appearance of skipped input. Insert while (getchar() != '\n'); between the two calls.
  6. Uninitiised array elements: Declaring int arr[10]; without an initialiser leaves all elements with indeterminate values. If only partial initialisation is needed, use int arr[10] = {0}; to zero-fill the entire array.
  7. Hardcoding array sizes in multiple places: If the size changes, every occurrence must be updated. Prefer #define N 10 or the sizeof idiom for consistency.

7. Practice

7.1 Basic

  • P1: Declare an integer array of size 5, initialise it with {2, 4, 6, 8, 10}, and print all elements using a loop. Acceptance:Output prints each element on a separate line: 2 4 6 8 10.

  • P2: Write a program that reads a word using scanf("%s", str) and prints it back. Acceptance:The entered word is echoed correctly.

  • P3: Write a program that reads a word and computes its length without using strlen(). Input/Output:Input: computer → Output: Length of the string: 8 Acceptance:Count matches the visible character count (excluding '\0').

7.2 Required

  • P4: Write a program that takes 10 numbers as input, stores them in an array, and counts how many are even and how many are odd. Input/Output:Input: 5 8 12 3 7 10 4 6 11 9 → Output: Even count: 5, Odd count: 5 Acceptance:Correct counts for any combination of positive and negative integers.

  • P5: Write a program that reads a sentence (using fgets) and counts the number of vowels (a, e, i, o, u — case-insensitive) and consonants, ignoring punctuation characters (, . - : ; ? !). Input/Output:Input: To learn C for fun, focus on hands-on projects and engaging resources.Vowel count: 20, Consonant count: 36 Acceptance:Spaces and listed punctuation are neither vowels nor consonants.

  • P6: Write a program that reads a 3×3 matrix, calculates the sum of each row, and prints the results. Input/Output

    1 2 3
    5 6 7
    9 10 11

    Output: Row 0 sum: 6, Row 1 sum: 18, Row 2 sum: 30 Acceptance:Correct sums for any integer inputs.

  • P7: Implement bubble sort. Read 10 integers, sort them in ascending order, and print the sorted array. Input/Output:Input: 9 5 3 8 2 7 6 1 4 10 → Output: Sorted array: 1 2 3 4 5 6 7 8 9 10 Acceptance:The output is a non-decreasing sequence; the algorithm must use adjacent swaps.

7.3 Optional

  • P8: Modify the bubble sort program (P7) to add an "early exit" optimisation: if no swaps occur during a pass, the array is already sorted and the algorithm should terminate immediately. Test with an already-sorted input and print the number of passes performed.

  • P9: Write a program that reads a sentence and reverses each word in place whilst preserving the word order. For example, Hello World becomes olleH dlroW.

8. Mini Task

Goal: Build a simple student grade book program that stores up to 30 student names (single word each) and their corresponding integer marks. The program should be able to: (a) accept input until the user types done, (b) print all records, (c) compute and display the class average, (d) find and display the student with the highest mark.

Deliverables

  • README.md:Explain how to compile and run the program; describe sample input and expected output.
  • src/gradebook.c:The complete source file.
  • report.md:A short reflection (200–300 words) covering: which array concepts you applied, any difficulties encountered, and how you resolved them.

9. Further Reading

  1. Kernighan, B. W. & Ritchie, D. M. The C Programming Language (2nd edn): Chapter 1, §1.6 (Arrays); Chapter 5 (Pointers and Arrays).
  2. King, K. N. C Programming: A Modern Approach (2nd edn): Chapter 8 (Arrays) and Chapter 13 (Strings).
  3. ISO/IEC 9899:2018 (C18 Standard): §6.7.9 (Initialisation), §6.5.2.1 (Array subscripting).

10. Appendix

A. ASCII Reference for Case Conversion

CharacterDecimalHex
'A'650x41
'Z'900x5A
'a'970x61
'z'1220x7A

The offset between corresponding upper- and lowercase letters is always 32 (0x20).

B. Summary of Input Functions

FunctionPrototypeReads spaces?Buffer-safe?Notes
scanf("%s", s)int scanf(const char *, …)NoNoStops at first whitespace
getchar()int getchar(void)YesN/A (single char)Returns int; check for EOF
fgets(s, n, stdin)char *fgets(char *, int, FILE *)YesYesRetains trailing '\n' if buffer allows
gets(s)YesNoDeprecated in C11; never use