Unit 2: Handling Pointers, Arrays and String

CAP455 — Object Oriented Programming Using C++ 11 min read

I. Orientation — Memory, Addresses, Collections, and Text

C++ provides low-level memory access through pointers, structured data collections through arrays, and safe text processing through std::string. These mechanisms differ in abstraction but depend on correct object lifetime, type compatibility, bounds management, and ownership.

  • Object and address: An object occupies storage, has a type, and usually has an address obtainable with &; for int x = 5;, &x is the address of x.
  • Indirection: The unary * operator accesses the object at a valid pointer’s stored address; if int* p = &x;, then *p denotes x.
  • Lifetime rule: A pointer or reference may be used only while its target object remains alive.
  • Array convention: Array elements occupy contiguous memory and use zero-based indexing; an array of size n has valid indices 0 through n - 1.
  • String convention: std::string manages a dynamic character sequence and should normally be preferred to manually managed C-style character arrays.
  • Safety conditions: Dereferencing null, wild, or dangling pointers and accessing outside array bounds cause undefined behavior.

II. Pointer Fundamentals — Address-Based Access

Pointers store memory addresses and permit indirect access, traversal, dynamic allocation, and interaction with generic memory.

A. Pointer vs reference variables

A pointer stores an address and can be redirected, whereas a reference is an alias that must be initialized when declared.

  • Declaration: int* p declares a pointer to int; int& r declares a reference to int.
  • Initialization: A pointer may hold nullptr, but a reference must bind to an existing object.
  • Access: A pointer requires *p for the target value; a reference is used like the original variable.
  • Reassignment: Assigning another address redirects a pointer, while assigning through a reference changes its bound object.
  • Comparison:
    1. Pointer: Supports arithmetic, null state, and redirection.
    2. Reference: Offers simpler syntax and is commonly used for non-null function parameters.
CPP
int x = 10, y = 20;
int* p = &x;  // p stores x's address
int& r = x;   // r aliases x
p = &y;       // p now points to y
r = y;        // assigns 20 to x; r still aliases x

B. Void pointer

A void pointer, written void*, can hold the address of any object type but cannot be dereferenced directly.

  • Generic storage: Converting int* or double* to void* preserves the address but hides the pointed-to type.
  • Required conversion: The pointer must be converted back to the correct type before dereferencing.
  • Restriction: Standard C++ does not permit arithmetic on void* because the pointed-to element size is unknown.
  • Risk: Converting to the wrong type and dereferencing can produce undefined behavior.
CPP
int value = 25;
void* vp = &value;
int* ip = static_cast<int*>(vp);
int result = *ip;  // result is 25

C. Pointer arithmetic

Pointer arithmetic moves in units of the pointed-to type and is valid only within an array or one position beyond it.

  • Increment: If p points to a[0], then p + 1 points to a[1], advancing by sizeof(int) bytes for an int*.
  • Subtraction: For pointers into the same array, q - p gives the number of elements between them.
  • Index equivalence: p[i] is defined as *(p + i).
  • Boundary: A one-past-the-end pointer may be formed and compared, but it must not be dereferenced.
CPP
int a[3] = {10, 20, 30};
int* p = a;
int second = *(p + 1);  // 20

Here, a is the array, p points to its first element, and second stores the next element.

D. Pointer to pointer

A pointer to pointer stores the address of another pointer and introduces two levels of indirection.

  • Declaration: int** pp means that pp points to an int*.
  • Dereferencing: *pp yields the inner pointer, while **pp yields the final integer.
  • Use cases: It supports modification of a caller’s pointer and representation of dynamically allocated tables.
  • Type consistency: Each * removes exactly one pointer level.
CPP
int x = 7;
int* p = &x;
int** pp = &p;
**pp = 9;  // changes x to 9

E. Dangling pointer

A dangling pointer retains an address after the target object’s lifetime has ended.

  • Deletion case: After delete p, the allocated object no longer exists, even though p may still contain its former address.
  • Scope case: Returning the address of a local automatic variable creates a dangling pointer when the function ends.
  • Consequence: Dereferencing a dangling pointer causes undefined behavior.
  • Prevention: Reset non-owning remnants to nullptr and prefer RAII facilities such as std::unique_ptr.
CPP
int* p = new int(5);
delete p;
p = nullptr;  // prevents accidental use of the old address

F. Wild pointer

A wild pointer is an uninitialized pointer whose stored bit pattern does not intentionally identify a valid object.

  • Cause: int* p; creates an uninitialized local pointer when no initializer is supplied.
  • Danger: Reading or dereferencing its indeterminate value causes undefined behavior.
  • Prevention: Initialize immediately with a valid address or nullptr.
  • Distinction: A wild pointer was never properly initialized; a dangling pointer once referred to a live object.
CPP
int* p = nullptr;  // defined null state, not a wild pointer

G. Null pointer assignment

Null pointer assignment explicitly records that a pointer currently points to no object.

  • Modern literal: nullptr, introduced in C++11, has type std::nullptr_t and converts safely to pointer types.
  • Testing: if (p != nullptr) or simply if (p) checks whether a pointer is non-null.
  • Dereferencing rule: *p and p->member are invalid when p is null.
  • Preference: Use nullptr rather than 0 or NULL because it avoids integer-overload ambiguity.
CPP
int* p = nullptr;
if (p) {
    int value = *p;
}

III. Pointers and Classes — Object-Oriented Indirection

Pointers participate in object state, object access, member selection, and communication between an object and its member functions.

A. Pointers as class members

A pointer data member stores an address as part of each object and may represent ownership or a non-owning relationship.

  • Initialization: Pointer members should be initialized in a constructor’s member-initializer list.
  • Ownership: A raw owning pointer requires correct destruction, copying, and assignment—the Rule of Three or Rule of Five.
  • Shallow-copy danger: Default copying duplicates only the address, potentially causing shared mutation or double deletion.
  • Preferred design: Use std::unique_ptr<T> for exclusive ownership and ordinary pointers for clearly documented non-owning links.
CPP
class Box {
    int* value;
public:
    explicit Box(int v) : value(new int(v)) {}
    ~Box() { delete value; }
    Box(const Box&) = delete;
    Box& operator=(const Box&) = delete;
};

Here, value owns one dynamically allocated int, and copying is prohibited.

B. Pointer to objects

A pointer to an object stores the object’s address and accesses members through the arrow operator.

  • Syntax: If Student* p points to a Student, then p->show() is equivalent to (*p).show().
  • Automatic object: Student s; Student* p = &s; does not require delete.
  • Dynamic object: An object created by new Student must be released by delete, unless managed by a smart pointer.
  • Polymorphism: A base-class pointer can refer to a derived object; virtual functions then enable runtime dispatch.
CPP
class Item {
public:
    int price = 50;
};

Item object;
Item* p = &object;
int cost = p->price;  // 50

C. This pointer

The this pointer is an implicit pointer to the object for which a non-static member function was invoked.

  • Type: In a member of class C, this behaves as C* const; in a const member, it behaves as const C* const.
  • Disambiguation: this->value = value; distinguishes a data member from a parameter with the same name.
  • Chaining: Returning *this by reference enables expressions such as a.set(1).set(2).
  • Restriction: Static member functions have no this pointer because they are not called for a particular object.
CPP
class Number {
    int value;
public:
    Number& set(int value) {
        this->value = value;
        return *this;
    }
};

D. Pointer to data member

A pointer to data member identifies a member within a class rather than storing a complete ordinary memory address.

  • Declaration: int Record::* pm = &Record::score; declares pm as a pointer to an int member of Record.
  • Object access: Use object.*pm with an object.
  • Pointer access: Use pointer->*pm with a pointer to an object.
  • Purpose: It allows member selection to be passed as data while preserving class type information.
CPP
struct Record { int score; };

int Record::* pm = &Record::score;
Record r{80};
Record* pr = &r;
int a = r.*pm;    // 80
int b = pr->*pm;  // 80

IV. Arrays — Fixed-Size Contiguous Collections

An array stores a fixed number of same-type elements contiguously, permitting indexed processing and predictable memory layout.

A. Array declaration and processing of multidimensional arrays inside main and inside class

A multidimensional array is an array whose elements are themselves arrays, commonly used to represent matrices.

  • Inside main: int m[2][3] defines two rows and three columns, giving six integers.
  • Initialization: int m[2][3] = {{1,2,3},{4,5,6}}; initializes rows explicitly.
  • Processing: Nested loops use m[i][j], where i is the row index and j is the column index.
  • Inside a class: A built-in array can be a private data member and processed through member functions.
  • Storage order: C++ stores multidimensional built-in arrays in row-major order.
CPP
class Matrix {
    int data[2][3]{};
public:
    void setAll(int value) {
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 3; ++j)
                data[i][j] = value;
    }
};

Here, data is the matrix, i selects a row, j selects a column, and value is assigned to every element.

B. Array of objects

An array of objects contains multiple class instances, with each element constructed and accessed independently.

  • Declaration: Student group[3]; creates three Student objects using the default constructor.
  • Initialization: Point points[2]{{1,2}, {3,4}}; initializes two objects when matching constructors exist.
  • Access: group[i].show() selects element i and calls its member function.
  • Lifetime: Constructors run from the first element to the last; destructors run in reverse order.
  • Dynamic alternative: std::vector<T> is preferable when the number of objects is determined at runtime.
CPP
class Point {
public:
    int x, y;
    Point(int x, int y) : x(x), y(y) {}
};

Point points[2] = {{1, 2}, {3, 4}};

V. C++ Strings — Managed Character Sequences

The standard string abstraction manages storage automatically and supplies operations for construction, assignment, searching, comparison, and modification.

A. Standard C++ string class

The std::string class represents a mutable sequence of char values and is declared in the <string> header.

  • Namespace: Its full name is std::string.
  • Size management: Storage expands or contracts automatically; size() and length() return the character count.
  • Indexing: s[i] performs unchecked access, while s.at(i) checks bounds and may throw std::out_of_range.
  • Comparison: Operators such as ==, <, and > compare string contents lexicographically.
  • Input: std::cin >> s reads one whitespace-delimited word; std::getline(std::cin, s) reads a full line.
CPP
#include <string>
std::string text = "C++";
std::size_t n = text.size();  // n is 3

B. Defining and assigning string objects

String objects can be default-constructed, initialized from literals or other strings, and reassigned after creation.

  • Empty definition: std::string s; creates an empty string.
  • Direct initialization: std::string s("Object"); copies characters from a string literal.
  • Copy initialization: std::string b = a; creates b with the same contents as a.
  • Assignment: s = "Pointer"; replaces the previous contents.
  • Concatenation: a + b creates a combined string, while a += b appends to a.
CPP
std::string first = "Object";
std::string second;
second = first;
second += " Oriented";  // "Object Oriented"

C. Modifiers of string class

String modifiers change a string’s contents, length, or arrangement while maintaining managed storage.

  • append() and +=: Add characters at the end, as in s.append("++").
  • push_back() and pop_back(): Add or remove one final character; pop_back() requires a non-empty string.
  • insert(): Places content at a specified index, as in s.insert(1, "X").
  • erase(): Removes characters; s.erase(pos, count) starts at pos and removes at most count characters.
  • replace(): Replaces a selected range with new text.
  • clear() and resize(): clear() makes the string empty, while resize(n, ch) changes its length and may fill new positions with ch.
  • Iterator modifiers: Overloads of insert() and erase() can operate at iterator positions, supporting algorithm-oriented code.
CPP
std::string s = "cat";
s.append("alog");       // "catalog"
s.insert(3, "");        // remains "catalog"
s.replace(0, 3, "dia"); // "dialog"
s.erase(3, 2);          // "diag"

Here, numeric arguments are zero-based positions and character counts; modifiers may invalidate pointers, references, and iterators into the string when storage is reallocated.