Chapter 05

Arrays & Strings

Working with collections of data and text manipulation in C++

What You Will Learn

  • How to declare and use C-style arrays
  • Multi-dimensional arrays for matrices and grids
  • C-style strings (char arrays)
  • Modern std::string and its powerful methods
  • String manipulation techniques
  • String streams for parsing and formatting
  • std::string_view for efficient string handling
  • std::array for safe, fixed-size arrays
  • Preview of std::vector

Arrays Introduction

An array is a fixed-size collection of elements of the same type, stored in contiguous memory.

Declaring Arrays

// Syntax: type name[size];
int numbers[5];           // Uninitialized (contains garbage)
int scores[5] = {0};      // All elements initialized to 0
int values[5] = {1, 2, 3, 4, 5};  // Explicit initialization

// Size can be omitted if initialized
int arr[] = {10, 20, 30};  // Size is 3

// Partial initialization (remaining elements are 0)
int data[10] = {1, 2, 3};  // data[3] to data[9] are 0

Accessing Elements

int arr[5] = {10, 20, 30, 40, 50};

// Access with index (0-based)
std::cout << arr[0];  // 10 (first element)
std::cout << arr[4];  // 50 (last element)

// Modify elements
arr[2] = 100;  // arr is now {10, 20, 100, 40, 50}

// WARNING: No bounds checking!
// arr[5] = 60;  // Undefined behavior! Out of bounds

Array Size

int arr[] = {1, 2, 3, 4, 5};

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

// With std::size (C++17)
#include <iterator>
int size2 = std::size(arr);  // 5

// Note: This doesn't work with pointers
void printSize(int* arr) {
    // sizeof(arr) returns pointer size, not array size!
}

Array Operations

Iterating Over Arrays

int arr[] = {1, 2, 3, 4, 5};
int size = sizeof(arr) / sizeof(arr[0]);

// Traditional for loop
for (int i = 0; i < size; i++) {
    std::cout << arr[i] << " ";
}

// Range-based for loop (C++11)
for (int x : arr) {
    std::cout << x << " ";
}

// Modify with reference
for (int& x : arr) {
    x *= 2;  // Doubles each element
}

Finding Elements

int arr[] = {10, 20, 30, 40, 50};
int size = 5;
int target = 30;

// Linear search
int index = -1;
for (int i = 0; i < size; i++) {
    if (arr[i] == target) {
        index = i;
        break;
    }
}
// index = 2

// Using std::find
#include <algorithm>
auto it = std::find(std::begin(arr), std::end(arr), target);
if (it != std::end(arr)) {
    std::cout << "Found at index: " << (it - std::begin(arr));
}

Sorting Arrays

#include <algorithm>

int arr[] = {5, 2, 8, 1, 9};

// Sort ascending
std::sort(std::begin(arr), std::end(arr));
// arr = {1, 2, 5, 8, 9}

// Sort descending
std::sort(std::begin(arr), std::end(arr), std::greater<int>());
// arr = {9, 8, 5, 2, 1}

// Custom comparison
std::sort(std::begin(arr), std::end(arr), [](int a, int b) {
    return a > b;  // Descending
});

Copying Arrays

int source[] = {1, 2, 3, 4, 5};
int dest[5];

// Method 1: Loop
for (int i = 0; i < 5; i++) {
    dest[i] = source[i];
}

// Method 2: std::copy
#include <algorithm>
std::copy(std::begin(source), std::end(source), std::begin(dest));

// Method 3: memcpy (for POD types)
#include <cstring>
std::memcpy(dest, source, sizeof(source));

Filling Arrays

int arr[10];

// Fill with a value
std::fill(std::begin(arr), std::end(arr), 42);
// arr = {42, 42, 42, ...}

// Fill with memset (bytes only, works for 0 or -1)
std::memset(arr, 0, sizeof(arr));
// arr = {0, 0, 0, ...}

Multi-Dimensional Arrays

Arrays can have multiple dimensions for representing matrices, grids, and tables.

2D Arrays

// Syntax: type name[rows][cols];
int matrix[3][4];  // 3 rows, 4 columns

// Initialization
int grid[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Access elements
std::cout << grid[0][0];  // 1 (top-left)
std::cout << grid[2][2];  // 9 (bottom-right)
grid[1][1] = 100;         // Modify center element

Iterating 2D Arrays

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

// Nested loops
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 4; j++) {
        std::cout << matrix[i][j] << " ";
    }
    std::cout << "\n";
}

// Range-based (C++11)
for (const auto& row : matrix) {
    for (int val : row) {
        std::cout << val << " ";
    }
    std::cout << "\n";
}

3D Arrays

// 3D array: depth x rows x cols
int cube[2][3][4];

// Access
cube[0][1][2] = 42;

// Useful for:
// - RGB images (height x width x 3)
// - 3D game worlds
// - Time series of matrices

Passing 2D Arrays to Functions

// Must specify all dimensions except first
void print2D(int arr[][4], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 4; j++) {
            std::cout << arr[i][j] << " ";
        }
        std::cout << "\n";
    }
}

// With templates (more flexible)
template <size_t R, size_t C>
void print2D(int (&arr)[R][C]) {
    for (size_t i = 0; i < R; i++) {
        for (size_t j = 0; j < C; j++) {
            std::cout << arr[i][j] << " ";
        }
        std::cout << "\n";
    }
}

Matrix Operations

// Matrix addition
void addMatrices(int a[][3], int b[][3], int result[][3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            result[i][j] = a[i][j] + b[i][j];
        }
    }
}

// Matrix multiplication
void multiplyMatrices(int a[][3], int b[][3], int result[][3], 
                       int r1, int c1, int c2) {
    for (int i = 0; i < r1; i++) {
        for (int j = 0; j < c2; j++) {
            result[i][j] = 0;
            for (int k = 0; k < c1; k++) {
                result[i][j] += a[i][k] * b[k][j];
            }
        }
    }
}

C-Style Strings

C-style strings are character arrays terminated with a null character '\0'.

Declaring C-Strings

// String literal (null-terminated automatically)
char greeting[] = "Hello";  // Size is 6 (includes '\0')

// Character array
char name[20] = "World";    // Extra space for longer names

// As char pointer (read-only string literal)
const char* message = "Hello, World!";

C-String Functions (<cstring>)

#include <cstring>

char str1[50] = "Hello";
char str2[] = " World";

// strlen - get length (excluding '\0')
size_t len = strlen(str1);  // 5

// strcpy - copy string
char dest[50];
strcpy(dest, str1);  // dest = "Hello"

// strcat - concatenate
strcat(str1, str2);  // str1 = "Hello World"

// strcmp - compare strings
int result = strcmp("abc", "abd");  // < 0 (a < b)

// strncpy, strncat - safer versions with length limit
strncpy(dest, str1, 10);
strncat(dest, str2, 10);

Reading C-Strings

char name[100];

// Using cin (stops at whitespace)
std::cin >> name;  // "John Doe" only reads "John"

// Using getline
std::cin.getline(name, 100);  // Reads entire line

// Gets deprecated - use fgets or getline
// gets(name);  // Dangerous! No bounds checking

C-String Dangers

  • Buffer overflow if string exceeds array size
  • No automatic memory management
  • Missing null terminator causes undefined behavior
  • Prefer std::string in modern C++

std::string

std::string is the modern, safe way to work with strings in C++.

Creating Strings

#include <string>

std::string s1;                    // Empty string
std::string s2 = "Hello";          // From string literal
std::string s3("World");           // Constructor syntax
std::string s4(5, 'a');            // "aaaaa" (repeat character)
std::string s5 = s2;               // Copy
std::string s6 = s2 + " " + s3;    // Concatenation

// From C-string
const char* cstr = "Hello";
std::string s7 = cstr;
std::string s8(cstr);

Basic Operations

std::string s = "Hello, World!";

// Length
size_t len = s.length();   // 13
size_t len2 = s.size();    // Same as length()

// Empty check
bool empty = s.empty();    // false

// Access characters
char first = s[0];         // 'H'
char last = s.back();      // '!'
char safe = s.at(0);       // 'H' (with bounds checking)

// Modify
s[0] = 'h';                // "hello, World!"
s.front() = 'H';           // "Hello, World!"

// Concatenation
std::string a = "Hello";
a += " World";             // "Hello World"
a.append("!");             // "Hello World!"

// Clear
s.clear();                 // s is now ""

Comparing Strings

std::string a = "apple";
std::string b = "banana";

// Comparison operators
bool eq = (a == b);        // false
bool lt = (a < b);         // true (lexicographic)
bool ne = (a != b);        // true

// compare() method
int cmp = a.compare(b);    // < 0 because a < b

// Case-insensitive comparison (no built-in, use algorithm)
#include <algorithm>
#include <cctype>

bool equalsIgnoreCase(const std::string& a, const std::string& b) {
    return a.size() == b.size() &&
           std::equal(a.begin(), a.end(), b.begin(),
               [](char c1, char c2) {
                   return std::tolower(c1) == std::tolower(c2);
               });
}

Input/Output

std::string name;

// Read word (stops at whitespace)
std::cin >> name;

// Read entire line
std::getline(std::cin, name);

// Read with custom delimiter
std::getline(std::cin, name, ',');

// After cin, clear newline before getline
std::cin >> age;
std::cin.ignore();          // Skip newline
std::getline(std::cin, address);

// Output
std::cout << name << "\n";

String Methods

Searching

std::string s = "Hello, World!";

// Find substring (returns position or npos if not found)
size_t pos = s.find("World");     // 7
size_t pos2 = s.find("xyz");      // std::string::npos

if (s.find("Hello") != std::string::npos) {
    std::cout << "Found!\n";
}

// Find from position
size_t pos3 = s.find("o", 5);     // 8 (second 'o')

// Find last occurrence
size_t pos4 = s.rfind("o");       // 8

// Find any character from set
size_t pos5 = s.find_first_of("aeiou");  // 1 ('e')
size_t pos6 = s.find_last_of("aeiou");   // 8 ('o')

// Find character not in set
size_t pos7 = s.find_first_not_of(" ");  // First non-space

Substrings

std::string s = "Hello, World!";

// substr(pos, len) - extract substring
std::string sub = s.substr(7, 5);     // "World"
std::string sub2 = s.substr(7);       // "World!" (to end)

// Get prefix/suffix
std::string prefix = s.substr(0, 5);  // "Hello"

Modifying Strings

std::string s = "Hello, World!";

// Replace
s.replace(7, 5, "C++");           // "Hello, C++!"
s.replace(0, 5, "Hi");            // "Hi, C++!"

// Insert
s.insert(3, "there ");            // "Hi,there  C++!"

// Erase
s.erase(3, 6);                    // "Hi, C++!"
s.erase(0, 4);                    // "C++!"

// Pop back
s.pop_back();                     // "C++"

// Resize
s.resize(10, '!');                // "C++!!!!!!!"
s.resize(3);                      // "C++"

Useful Transformations

#include <algorithm>
#include <cctype>

std::string s = "Hello, World!";

// To uppercase
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
// "HELLO, WORLD!"

// To lowercase
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
// "hello, world!"

// Reverse
std::reverse(s.begin(), s.end());
// "!dlrow ,olleh"

// Trim whitespace (no built-in)
std::string trim(const std::string& s) {
    size_t start = s.find_first_not_of(" \t\n\r");
    size_t end = s.find_last_not_of(" \t\n\r");
    if (start == std::string::npos) return "";
    return s.substr(start, end - start + 1);
}

Splitting Strings

// Split by delimiter
std::vector<std::string> split(const std::string& s, char delim) {
    std::vector<std::string> tokens;
    std::stringstream ss(s);
    std::string token;
    while (std::getline(ss, token, delim)) {
        tokens.push_back(token);
    }
    return tokens;
}

auto parts = split("apple,banana,cherry", ',');
// {"apple", "banana", "cherry"}

// Using find and substr
std::vector<std::string> split2(const std::string& s, 
                                 const std::string& delim) {
    std::vector<std::string> tokens;
    size_t start = 0, end;
    while ((end = s.find(delim, start)) != std::string::npos) {
        tokens.push_back(s.substr(start, end - start));
        start = end + delim.length();
    }
    tokens.push_back(s.substr(start));
    return tokens;
}

Conversion

// String to number
std::string numStr = "42";
int num = std::stoi(numStr);           // 42
long numL = std::stol(numStr);         // 42L
double numD = std::stod("3.14");       // 3.14
float numF = std::stof("2.5");         // 2.5f

// Number to string
std::string s1 = std::to_string(42);       // "42"
std::string s2 = std::to_string(3.14159);  // "3.141590"

// C-string conversion
std::string str = "Hello";
const char* cstr = str.c_str();        // Get C-string
const char* data = str.data();         // Same in C++11+

String Streams

std::stringstream allows treating strings like streams for parsing and formatting.

#include <sstream>

// Parsing with stringstream
std::string data = "42 3.14 Hello";
std::stringstream ss(data);

int num;
double pi;
std::string word;
ss >> num >> pi >> word;
// num = 42, pi = 3.14, word = "Hello"

// Building strings
std::stringstream builder;
builder << "Value: " << 42 << ", Pi: " << std::fixed 
        << std::setprecision(2) << 3.14159;
std::string result = builder.str();
// "Value: 42, Pi: 3.14"

// Clear and reuse
builder.str("");        // Clear contents
builder.clear();        // Clear error flags

Input/Output String Streams

// Input only
std::istringstream iss("10 20 30");
int a, b, c;
iss >> a >> b >> c;

// Output only
std::ostringstream oss;
oss << "Hello" << " " << "World";
std::string result = oss.str();

Parsing CSV

std::string csv = "John,25,NYC";
std::stringstream ss(csv);
std::string name, city;
int age;

std::getline(ss, name, ',');
ss >> age;
ss.ignore();  // Skip comma
std::getline(ss, city, ',');

// name = "John", age = 25, city = "NYC"

std::string_view (C++17)

std::string_view is a non-owning reference to a string. It avoids copying.

#include <string_view>

void print(std::string_view sv) {
    std::cout << sv << "\n";
}

// Works with different string types without copying
print("Hello");                    // String literal
print(std::string("World"));       // std::string
print(std::string_view("Hi"));     // Another string_view

// Creating string_view
std::string str = "Hello, World!";
std::string_view sv1 = str;                 // View of entire string
std::string_view sv2(str.data(), 5);        // "Hello"
std::string_view sv3 = str.substr(0, 5);    // Also "Hello"

// Useful methods (similar to string)
sv1.size();
sv1.substr(0, 5);
sv1.find("World");
sv1[0];
sv1.remove_prefix(7);  // Now views "World!"
sv1.remove_suffix(1);  // Now views "World"

string_view Pitfalls

  • The underlying string must outlive the view
  • Don't return string_view to local strings
  • No null termination guaranteed
// Dangerous!
std::string_view bad() {
    std::string local = "Hello";
    return local;  // local destroyed, view dangles!
}

std::array (C++11)

std::array is a fixed-size container that wraps C-style arrays with added safety.

#include <array>

// Declaration
std::array<int, 5> arr = {1, 2, 3, 4, 5};

// Access
arr[0];           // No bounds checking
arr.at(0);        // With bounds checking (throws if out of range)
arr.front();      // First element
arr.back();       // Last element
arr.data();       // Pointer to underlying array

// Size (always known)
arr.size();       // 5
arr.empty();      // false
arr.max_size();   // 5

// Iteration
for (int x : arr) {
    std::cout << x << " ";
}

// Fill
arr.fill(0);      // All elements become 0

// Swap
std::array<int, 5> arr2 = {10, 20, 30, 40, 50};
arr.swap(arr2);

// Comparison
if (arr == arr2) { /* ... */ }
if (arr < arr2) { /* ... */ }  // Lexicographic

Benefits Over C-Arrays

  • Knows its own size (.size())
  • Bounds checking with .at()
  • Can be passed by value/reference without decay
  • Works with STL algorithms
  • Supports comparison operators
// C-array decays to pointer when passed
void func(int arr[]);  // Actually takes int*

// std::array doesn't decay
void func(std::array<int, 5> arr);  // Receives copy
void func(const std::array<int, 5>& arr);  // Reference

std::vector Preview

std::vector is a dynamic array that can grow and shrink. It's covered in depth in the STL chapter.

#include <vector>

// Dynamic size
std::vector<int> v;              // Empty
std::vector<int> v2 = {1, 2, 3}; // With elements
std::vector<int> v3(10);         // 10 zeros
std::vector<int> v4(10, 5);      // 10 fives

// Add elements
v.push_back(42);                  // Add to end
v.emplace_back(100);              // Construct in place

// Access
v[0];
v.at(0);
v.front();
v.back();

// Size operations
v.size();
v.capacity();                     // Allocated space
v.resize(20);                     // Change size
v.reserve(100);                   // Pre-allocate

// Remove
v.pop_back();                     // Remove last
v.clear();                        // Remove all
v.erase(v.begin());               // Remove first

Arrays Comparison

Feature C-Array std::array std::vector
Size Fixed, compile-time Fixed, compile-time Dynamic
Bounds Check No Yes (.at()) Yes (.at())
Memory Stack Stack Heap
Knows Size No (decays) Yes Yes
Can Resize No No Yes
STL Compatible Limited Full Full
When to Use C compatibility Fixed size, stack Dynamic needs

Practice Questions

  1. Write a function to find the maximum element in an array.
  2. Implement a function to reverse an array in place.
  3. Create a 2D array representing a tic-tac-toe board and display it.
  4. Write a program to transpose a matrix (swap rows and columns).
  5. Implement a function to count vowels in a string.
  6. Write a palindrome checker for strings.
  7. Create a function to split a string by a delimiter.
  8. Implement a simple word count program.
  9. Write a program to remove duplicate characters from a string.
  10. Convert a sentence to title case (first letter of each word uppercase).
  11. Implement string compression ("aabbbcc" → "a2b3c2").
  12. Create a program to find all anagrams of a word from a list.