C Programming Basics: Part 6 – Arrays and Strings
Welcome to the sixth part of our C programming tutorial series! In previous articles, we’ve covered the fundamentals of C, variables, operators, control flow, and functions. Now, let’s explore arrays and strings, which allow you to work with collections of related data.
Understanding Arrays in C
An array is a collection of elements of the same data type, stored in contiguous memory locations. Think of an array as a row of storage boxes, where each box can hold one value, and all boxes are numbered sequentially.
flowchart TD A[Declare Array] --> B[Initialize Array] B --> C[Access Elements] C --> D[Direct Access] C --> E[Loop Through Array] C --> F[Add/Modify Elements] C --> G[Find/Search Elements] E --> E1[For Loop] E --> E2[While Loop] G --> G1[Linear Search] G --> G2[Binary Search for Sorted Arrays] F --> H[Modify Single Element] F --> I[Modify Multiple Elements] J[Array Manipulation] --> J1[Sort Array] J --> J2[Reverse Array] J --> J3[Merge Arrays] J --> J4[Filter Array] K[Advanced Operations] --> K1[Pass Arrays to Functions] K --> K2[Return Arrays from Functions] K --> K3[Dynamic Memory Allocation] classDef basic fill:#e1f5fe,stroke:#29b6f6,stroke-width:2px classDef operations fill:#e8f5e9,stroke:#66bb6a,stroke-width:2px classDef advanced fill:#fff3e0,stroke:#ffa726,stroke-width:2px class A,B,C basic class D,E,F,G,E1,E2,G1,G2,H,I operations class J,J1,J2,J3,J4,K,K1,K2,K3 advanced
Array Declaration and Initialization
Here’s how to declare an array:
data_type array_name[array_size];
For example, to declare an array of 5 integers:
int numbers[5];
You can initialize an array at declaration time:
int numbers[5] = {10, 20, 30, 40, 50};
If you provide all initial values, the array size is optional:
int numbers[] = {10, 20, 30, 40, 50}; // Size 5 is determined automatically
You can also initialize specific elements:
int numbers[5] = {0}; // Initializes all elements to 0
int marks[10] = {1, 2}; // First two elements are 1 and 2, rest are 0
Accessing Array Elements
Array elements are accessed using their index, which starts from 0:
int numbers[5] = {10, 20, 30, 40, 50};
printf("%d\n", numbers[0]); // Outputs 10 (first element)
printf("%d\n", numbers[4]); // Outputs 50 (last element)
Here’s a visual representation of an array in memory:
graph LR subgraph "Array numbers[5]" A[10<br>index 0] --- B[20<br>index 1] --- C[30<br>index 2] --- D[40<br>index 3] --- E[50<br>index 4] end
Modifying Array Elements
You can change array elements by assigning new values:
int numbers[5] = {10, 20, 30, 40, 50};
numbers[2] = 35; // Change the third element to 35
Arrays and Loops
Arrays are often processed using loops:
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// Print all elements using a loop
for (int i = 0; i < 5; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
}
// Calculate the sum of array elements
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += numbers[i];
}
printf("Sum: %d\n", sum);
return 0;
}
Array Size and Memory
The total memory allocated for an array equals the size of one element multiplied by the number of elements. You can use the sizeof
operator to determine this:
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
printf("Size of int: %lu bytes\n", sizeof(int));
printf("Size of array: %lu bytes\n", sizeof(numbers));
printf("Number of elements: %lu\n", sizeof(numbers) / sizeof(int));
return 0;
}
Passing Arrays to Functions
When passing an array to a function, you’re actually passing a pointer to the first element. This means the function can modify the original array:
#include <stdio.h>
// Function prototype - both are equivalent
void printArray(int arr[], int size);
// void printArray(int *arr, int size);
// Function to double each element of an array
void doubleElements(int arr[], int size);
int main() {
int numbers[5] = {1, 2, 3, 4, 5};
printf("Original array:\n");
printArray(numbers, 5);
doubleElements(numbers, 5);
printf("Modified array:\n");
printArray(numbers, 5);
return 0;
}
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void doubleElements(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2; // Modify the original array
}
}
It’s important to pass the array size as a parameter because C doesn’t track array sizes internally.
Returning Arrays from Functions
C doesn’t allow you to directly return an array from a function. Instead, you can:
- Pass an existing array to be modified
- Return a pointer to a static array
- Dynamically allocate an array and return its pointer
Here’s an approach using a static array:
#include <stdio.h>
// Function returning a pointer to a static array
int* getEvenNumbers(int n) {
static int evens[10]; // Static array persists after function returns
for (int i = 0; i < n && i < 10; i++) {
evens[i] = (i + 1) * 2;
}
return evens;
}
int main() {
int* evenNumbers = getEvenNumbers(5);
printf("First 5 even numbers:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", evenNumbers[i]);
}
printf("\n");
return 0;
}
Note: Static arrays have limitations in size and scope, and more complex programs typically use dynamic memory allocation instead.
Multidimensional Arrays
C supports multidimensional arrays – arrays of arrays. The most common are 2D arrays, which you can think of as tables or matrices.
Declaring and Initializing 2D Arrays
// Declaration
data_type array_name[rows][columns];
// Example: 3x4 array of integers
int matrix[3][4];
// Initialization
int matrix[3][4] = {
{1, 2, 3, 4}, // Row 0
{5, 6, 7, 8}, // Row 1
{9, 10, 11, 12} // Row 2
};
Here’s a visual representation of a 2D array:
Accessing 2D Array Elements
Elements are accessed using row and column indices:
int value = matrix[1][2]; // Gets 7 (row 1, column 2)
matrix[0][3] = 44; // Sets the element in row 0, column 3 to 44
Processing 2D Arrays with Nested Loops
To process all elements in a 2D array, we typically use nested loops:
#include <stdio.h>
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Print all elements
for (int i = 0; i < 3; i++) { // Outer loop for rows
for (int j = 0; j < 4; j++) { // Inner loop for columns
printf("%3d ", matrix[i][j]);
}
printf("\n"); // New line after each row
}
return 0;
}
Passing 2D Arrays to Functions
When passing a 2D array to a function, you must specify the column size:
void processMatrix(int mat[][4], int rows); // Column size (4) is required
// Usage:
processMatrix(matrix, 3);
Here’s a complete example:
#include <stdio.h>
// Function prototype
void printMatrix(int mat[][4], int rows);
void doubleMatrix(int mat[][4], int rows);
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printf("Original matrix:\n");
printMatrix(matrix, 3);
doubleMatrix(matrix, 3);
printf("\nModified matrix:\n");
printMatrix(matrix, 3);
return 0;
}
void printMatrix(int mat[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%3d ", mat[i][j]);
}
printf("\n");
}
}
void doubleMatrix(int mat[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
mat[i][j] *= 2;
}
}
}
Higher-Dimensional Arrays
C also supports arrays with more than two dimensions:
// 3D array (2 layers, 3 rows, 4 columns)
int cube[2][3][4] = {
{ // First layer
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{ // Second layer
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};
// Access element (layer 1, row 2, column 3)
int value = cube[1][2][3]; // Gets 24
Strings in C
In C, strings are arrays of characters terminated by a null character (\0
). This null terminator marks the end of the string and is automatically added when you use string literals.
Declaring and Initializing Strings
There are several ways to declare strings:
// String literal
char greeting[] = "Hello"; // Compiler adds the null terminator
// Equivalent to:
char greeting[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// With explicit size (must be large enough for characters + null terminator)
char message[10] = "Hello"; // Remaining positions are filled with '\0'
The string "Hello"
is stored in memory as:
String Input and Output
You can use various functions to read and write strings:
#include <stdio.h>
int main() {
char name[50];
printf("Enter your name: ");
// Method 1: Using scanf (stops at whitespace)
// scanf("%s", name); // No & operator needed for strings
// Method 2: Using fgets (better for full lines with spaces)
fgets(name, 50, stdin);
printf("Hello, %s!\n", name);
return 0;
}
Note: When using fgets
, the newline character from pressing Enter is included in the string. You may want to remove it:
#include <stdio.h>
#include <string.h>
int main() {
char name[50];
printf("Enter your name: ");
fgets(name, 50, stdin);
// Remove the newline character
name[strcspn(name, "\n")] = '\0';
printf("Hello, %s!\n", name);
return 0;
}
String Library Functions
The <string.h>
header provides many useful functions for string manipulation:
1. String Length – strlen()
#include <stdio.h>
#include <string.h>
int main() {
char text[] = "Hello, World!";
int length = strlen(text); // Returns 13
printf("Length of '%s': %d\n", text, length);
return 0;
}
2. String Copy – strcpy()
and strncpy()
#include <stdio.h>
#include <string.h>
int main() {
char source[] = "Hello";
char destination[10];
// Copy the string
strcpy(destination, source);
printf("Source: %s\n", source);
printf("Destination: %s\n", destination);
// Safer version with size limit
char limited[4];
strncpy(limited, source, sizeof(limited) - 1);
limited[sizeof(limited) - 1] = '\0'; // Ensure null-termination
printf("Limited: %s\n", limited);
return 0;
}
3. String Concatenation – strcat()
and strncat()
#include <stdio.h>
#include <string.h>
int main() {
char first[20] = "Hello, ";
char last[] = "World!";
// Concatenate strings
strcat(first, last);
printf("Concatenated: %s\n", first);
return 0;
}
4. String Comparison – strcmp()
and strncmp()
#include <stdio.h>
#include <string.h>
int main() {
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";
int result1 = strcmp(str1, str2); // Negative (a comes before b)
int result2 = strcmp(str1, str3); // Zero (identical strings)
int result3 = strcmp(str2, str1); // Positive (b comes after a)
printf("strcmp(apple, banana): %d\n", result1);
printf("strcmp(apple, apple): %d\n", result2);
printf("strcmp(banana, apple): %d\n", result3);
return 0;
}
5. String Searching – strchr()
and strstr()
#include <stdio.h>
#include <string.h>
int main() {
char text[] = "The quick brown fox jumps over the lazy dog";
// Find first occurrence of a character
char *result1 = strchr(text, 'q');
if (result1) {
printf("Found 'q' at position: %ld\n", result1 - text);
}
// Find a substring
char *result2 = strstr(text, "fox");
if (result2) {
printf("Found 'fox' at position: %ld\n", result2 - text);
}
return 0;
}
Common String Operations
Let’s look at some common string operations:
Converting Between Characters and Integers
Each character in C is represented by an ASCII value:
#include <stdio.h>
int main() {
char ch = 'A';
int ascii = (int)ch; // Get ASCII value of 'A' (65)
printf("Character: %c, ASCII: %d\n", ch, ascii);
// Converting between uppercase and lowercase
char lower = 'a';
char upper = lower - 32; // 'A' (ASCII 65) is 32 less than 'a' (ASCII 97)
printf("Lowercase: %c, Uppercase: %c\n", lower, upper);
return 0;
}
Checking Character Types
The <ctype.h>
header provides functions for checking character types:
#include <stdio.h>
#include <ctype.h>
int main() {
char ch = 'A';
if (isalpha(ch)) {
printf("%c is an alphabetic character\n", ch);
}
if (isupper(ch)) {
printf("%c is uppercase\n", ch);
}
if (isdigit('5')) {
printf("5 is a digit\n");
}
if (isspace(' ')) {
printf("Space is a whitespace character\n");
}
// Converting case
printf("Lowercase of %c is %c\n", ch, tolower(ch));
printf("Uppercase of %c is %c\n", 'b', toupper('b'));
return 0;
}
Parsing Integers from Strings
#include <stdio.h>
#include <stdlib.h>
int main() {
char num_str[] = "12345";
int num = atoi(num_str); // Convert string to integer
printf("String: %s, Integer: %d\n", num_str, num);
printf("Integer + 5: %d\n", num + 5);
return 0;
}
Arrays vs Strings in C
Feature | Arrays | Strings |
---|---|---|
Definition | Collection of elements of any data type | Array of characters with null terminator (\0 ) |
Declaration | int numbers[5]; |
char name[20]; |
Initialization | int nums[] = {1, 2, 3, 4, 5}; |
char name[] = "John"; char name[] = {'J', 'o', 'h', 'n', '\0'}; |
Termination | No special terminator | Null character (\0 ) required |
Length | Determined at compile time or by memory allocation | Determined by position of null terminator, use strlen() |
Library Support | No dedicated library | Extensive via <string.h> |
Memory Model | Contiguous memory location for elements | Contiguous memory location for characters |
Direct Assignment | Not possible after declaration | Not possible after declaration, use strcpy() |
Comparison | Element by element comparison | Use strcmp() or strncmp() |
Input/Output | No direct I/O, element-by-element | Direct with %s format specifier, gets() , fgets() |
Size Calculation | sizeof(array) / sizeof(array[0]) |
strlen(str) (excludes null terminator) |
Practical Example: String Analysis
Let’s create a program that analyzes a string, counting various character types:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
void analyzeString(const char *str);
int main() {
char text[100];
printf("Enter a string to analyze: ");
fgets(text, sizeof(text), stdin);
// Remove newline if present
text[strcspn(text, "\n")] = '\0';
analyzeString(text);
return 0;
}
void analyzeString(const char *str) {
int length = strlen(str);
int letters = 0, digits = 0, spaces = 0, other = 0;
int uppercase = 0, lowercase = 0;
for (int i = 0; i < length; i++) {
if (isalpha(str[i])) {
letters++;
if (isupper(str[i])) {
uppercase++;
} else {
lowercase++;
}
} else if (isdigit(str[i])) {
digits++;
} else if (isspace(str[i])) {
spaces++;
} else {
other++;
}
}
printf("\nString Analysis Results\n");
printf("======================\n");
printf("Total length: %d\n", length);
printf("Letters: %d (%.1f%%)\n", letters, 100.0 * letters / length);
printf(" - Uppercase: %d\n", uppercase);
printf(" - Lowercase: %d\n", lowercase);
printf("Digits: %d (%.1f%%)\n", digits, 100.0 * digits / length);
printf("Spaces: %d (%.1f%%)\n", spaces, 100.0 * spaces / length);
printf("Other characters: %d (%.1f%%)\n", other, 100.0 * other / length);
}
String Analyzer
Array of Strings
To store multiple strings, you can use an array of strings, which is essentially a 2D array of characters:
#include <stdio.h>
#include <string.h>
int main() {
// Array of 5 strings, each with maximum length of 20
char names[5][20] = {
"Alice",
"Bob",
"Charlie",
"David",
"Eve"
};
// Print all names
printf("Names:\n");
for (int i = 0; i < 5; i++) {
printf("%d. %s\n", i + 1, names[i]);
}
// Modify a name
strcpy(names[2], "Chris");
// Print the modified array
printf("\nAfter modification:\n");
for (int i = 0; i < 5; i++) {
printf("%d. %s\n", i + 1, names[i]);
}
// Find the longest name
int longest_index = 0;
for (int i = 1; i < 5; i++) {
if (strlen(names[i]) > strlen(names[longest_index])) {
longest_index = i;
}
}
printf("\nLongest name: %s (%lu characters)\n",
names[longest_index], strlen(names[longest_index]));
return 0;
}
Names
# | Name |
---|
Modify a Name
Find Longest Name
Common Array and String Pitfalls
Working with arrays and strings in C has several potential pitfalls:
1. Array Bounds Violations
C doesn't check if you access elements outside the array's bounds:
int numbers[5] = {1, 2, 3, 4, 5};
numbers[10] = 100; // Undefined behavior! Beyond array bounds
This can lead to unpredictable behavior or crashes. Always ensure index values are within bounds.
Array Bounds Visualization
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
numbers[0] = 100; // Set value at index
printf("Value: %d\n", numbers[0]);
return 0;
}
2. Missing Null Terminator in Strings
Strings must end with a null character ('\0'
):
char name[4] = {'J', 'o', 'h', 'n'}; // Missing '\0'
printf("%s", name); // Undefined behavior! May print garbage characters
Correct version:
char name[5] = {'J', 'o', 'h', 'n', '\0'};
// or
char name[5] = "John";
3. Buffer Overflow in String Functions
Many standard string functions don't check buffer sizes:
char small[5];
strcpy(small, "This is too long"); // Buffer overflow!
Use safer functions or check lengths:
char small[5];
strncpy(small, "Long", sizeof(small) - 1);
small[sizeof(small) - 1] = '\0'; // Ensure null-termination
4. Forgetting That Arrays Are Zero-Indexed
The first element is at index 0, not 1:
int scores[5] = {90, 85, 95, 80, 88};
int first = scores[0]; // First element (90)
int last = scores[4]; // Last element (88) - not scores[5]!
5. Not Accounting for the Null Terminator When Allocating Strings
Always allocate one more byte than the string's visible length:
// For a 5-character string "Hello", allocate at least 6 bytes
char greeting[6] = "Hello";
Advanced Topics
Dynamic Arrays (Using Pointers and Memory Allocation)
For arrays whose size is determined at runtime:
#include <stdio.h>
#include <stdlib.h>
int main() {
int size;
printf("Enter the number of elements: ");
scanf("%d", &size);
// Allocate memory for the array
int *dynamic_array = (int*)malloc(size * sizeof(int));
if (dynamic_array == NULL) {
printf("Memory allocation failed\n");
return 1;
}
// Use the array
for (int i = 0; i < size; i++) {
dynamic_array[i] = i * 10;
}
// Print the array
for (int i = 0; i < size; i++) {
printf("%d ", dynamic_array[i]);
}
printf("\n");
// Free the allocated memory when done
free(dynamic_array);
return 0;
}
String Tokenization
Breaking a string into tokens (separated by delimiters):
#include <stdio.h>
#include <string.h>
int main() {
char sentence[] = "This is a sample sentence.";
const char delimiters[] = " ,.";
// Get the first token
char *token = strtok(sentence, delimiters);
// Walk through other tokens
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, delimiters);
}
return 0;
}
String Tokenization Visualization
#include <string.h>
int main() {
char sentence[] = "This is a sample sentence.";
const char delimiters[] = " ,.";
// Get the first token
char *token = strtok(sentence, delimiters);
// Walk through other tokens
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok(NULL, delimiters);
}
return 0;
}
Tokens:
Conclusion
Arrays and strings are fundamental data structures in C programming. They allow you to work with collections of data efficiently, making them essential for almost any non-trivial program. Understanding how to manipulate arrays and strings properly will make you a much more effective C programmer.
In this article, we've covered:
- Array declaration and initialization
- Accessing and modifying array elements
- Passing arrays to functions
- Multidimensional arrays
- String basics and null termination
- String input/output and manipulation
- Common string library functions
- Array of strings
- Common pitfalls and best practices
- Advanced techniques
In the next part of our series, we'll explore structures and unions in C, which allow you to create custom data types for complex information.
Practice Exercises
- Write a program that finds the minimum and maximum values in an array of integers.
- Create a program that checks if a string is a palindrome (reads the same forwards and backwards).
- Implement a function that reverses the elements of an array.
- Write a program that counts the occurrences of each word in a sentence.
- Create a function that removes all whitespace from a string.
- Implement your own version of the
strlen()
function without using any standard library functions. - Write a program that takes a sentence as input and prints each word on a new line in reverse order.
Happy coding!