Unit 2: Pointers, Reference Variables, Arrays and String Concepts

CSE202 — Object Oriented Programming 3 min read

I. Orientation

C++ provides direct and indirect ways to access data. Arrays and objects store values, pointers store addresses, references provide aliases, and std::string manages variable-length character sequences. Correct use depends on type, object lifetime, ownership, and memory boundaries.

  • Addressing: The address-of operator & obtains an object's address; the dereference operator * accesses the object at a valid stored address.
  • Lifetime: An address is usable only while the object to which it points remains alive.
  • Type safety: A pointer's type determines how dereferencing and pointer arithmetic are interpreted.
  • Ownership: A raw pointer does not state whether it owns dynamically allocated memory; modern C++ uses containers and smart pointers to express ownership.
  • Indexing: For built-in arrays, a[i] is defined as *(a + i).
  • Standard strings: std::string owns and manages its character storage, avoiding most manual C-style string operations.

II. Pointer Types and Operations

A. Void pointer

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

  • Conversion to void*: An object pointer converts implicitly to void*.
  • Conversion back: C++ requires an explicit cast to recover the original pointer type.
  • Restriction: Because void has no size or representation, standard C++ does not permit dereferencing or arithmetic on void*.
  • Typical use: Low-level generic interfaces may transport untyped addresses, although templates are usually safer.
CPP
int value = 25;
void* raw = &value;
int* typed = static_cast<int*>(raw);
std::cout << *typed;                 // 25

Here, raw stores the address of value, while typed restores the information needed to read an int.

B. Pointer arithmetic

Pointer arithmetic moves through elements of an array according to the pointed-to type's size.

  • Increment: If p is an int*, p + 1 points to the next int, not merely the next byte.
  • Valid range: Arithmetic is defined only within the same array object or up to its one-past-the-end position.
  • One-past pointer: It may be used for comparison or loop termination but must not be dereferenced.
  • Subtraction: q - p gives the number of array elements between two pointers and has type std::ptrdiff_t.
  • Equivalence: p[i], *(p + i), and i[p] have the same built-in meaning.
CPP
int data[] = {10, 20, 30};
int* p = data;
std::cout << *(p + 2);               // 30

Here, p + 2 addresses data[2].

C. Pointer to pointer

A pointer to pointer stores the address of another pointer, adding one level of indirection.

  • Declaration: In int** pp, pp points to an int*.
  • Access levels: pp is the pointer's address, *pp is the stored int*, and **pp is the final int.
  • Uses: Common uses include modifying a caller's pointer and representing dynamically allocated pointer tables.
  • Type matching: Each * corresponds to one indirection level.
CPP
int value = 7;
int* p = &value;
int** pp = &p;
**pp = 12;                           // value becomes 12

D. Dangling pointer

A dangling pointer holds an address whose object lifetime has ended.

  • Deletion case: After delete p, the storage formerly designated by p is no longer a live object.
  • Scope case: Returning the address of an automatic local variable produces a dangling pointer when the function returns.
  • Consequence: Dereferencing a dangling pointer causes undefined behavior.
  • Prevention: Use automatic storage, containers, or smart pointers; after deleting through a raw pointer, assign nullptr when the pointer remains accessible.
CPP
int* p = new int(9);
delete p;
p = nullptr;

Setting p to nullptr prevents accidental reuse through that variable, though copied pointers would still dangle.

E. Wild pointer

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

  • Cause: int* p; leaves a local pointer indeterminate when no initializer is supplied.
  • Risk: Reading, dereferencing, or deleting through such a pointer can produce undefined behavior.
  • Difference from dangling: A wild pointer was never given a reliable target; a dangling pointer once referred to a valid object.
  • Prevention: Initialize immediately with a valid address or nullptr.
CPP
int* p = nullptr;

F. Null pointer assignment

A null pointer assignment explicitly records that a pointer currently designates no object.

  • Preferred literal: Since C++11, nullptr is the type-safe null pointer literal.
  • Testing: if (p) is true only when p is non-null; if (p == nullptr) states the test explicitly.
  • Dereference rule: Dereferencing a null pointer is undefined behavior.
  • Advantage over 0 or NULL: nullptr cannot be mistaken for an ordinary integer during overload resolution.
CPP
int* p = nullptr;
if (p != nullptr)
    std::cout << *p;

III. Pointers with Classes and Objects

A. Classes containing pointers

A class containing an owning raw pointer must define how copying, moving, assignment, and destruction manage the resource.

  • Shallow-copy danger: Compiler-generated copying duplicates the address, potentially causing shared modification and double deletion.
  • Deep copy: A copy operation may allocate separate storage and copy the pointed-to value.
  • Rule of five: Resource-owning classes may require a destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator.
  • Preferred design: std::vector, std::string, and std::unique_ptr usually provide safer ownership.
CPP
class Number {
    std::unique_ptr<int> value;
public:
    explicit Number(int n)
        : value(std::make_unique<int>(n)) {}
};

The unique_ptr automatically releases its int when a Number is destroyed.

B. Pointer to objects

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

  • Declaration: Item* p = &obj; makes p point to obj.
  • Member access: p->show() is equivalent to (*p).show().
  • Dynamic object: new Item returns an Item*, but direct use of new should normally be replaced by std::make_unique<Item>().
  • Const form: A const Item* cannot be used to modify the object or call its non-const member functions.
CPP
class Item {
public:
    int price = 100;
};

Item item;
Item* p = &item;
std::cout << p->price;

C. this pointer

Inside a non-static member function, this points to the object for which that function was called.

  • Implicit use: Writing value inside a member function generally means this->value.
  • Name disambiguation: this->value = value; distinguishes the data member from a parameter with the same name.
  • Chaining: Returning *this by reference permits calls such as obj.set(2).display().
  • Const member: In a const member function, this points to a const object.
  • Static exception: Static member functions have no this pointer because they are not called on a specific object.
CPP
class Counter {
    int value;
public:
    Counter& set(int value) {
        this->value = value;
        return *this;
    }
};

D. Array of objects

An array of objects stores multiple class instances contiguously and applies construction and destruction to every element.

  • Declaration: Point points[3]; creates three Point objects.
  • Initialization: Elements may be list-initialized with constructor arguments.
  • Access: points[i].show() accesses the object at index i.
  • Lifetime order: Elements are constructed from first to last and destroyed in reverse order.
  • Safer alternative: std::array<Point, 3> has fixed size, while std::vector<Point> supports dynamic size.
CPP
class Point {
public:
    int x;
    explicit Point(int n) : x(n) {}
};

Point points[] = {Point(2), Point(4), Point(6)};

IV. Standard C++ Strings

A. Defining and assigning Standard C++ string objects

std::string represents an owned, dynamically sized sequence of characters.

  • Header: The class is declared in <string>.
  • Definitions: std::string a; creates an empty string, while std::string b = "C++"; initializes text.
  • Assignment: a = b; copies the characters, so later changes to one string do not alter the other.
  • Concatenation: + creates a combined string, and += appends to an existing string.
  • Input: operator>> reads one whitespace-delimited word; std::getline(std::cin, s) reads an entire line.
CPP
std::string course = "Object";
course += " Oriented Programming";

B. String class member functions

String member functions inspect content, locate characters, compare values, and obtain substrings.

  • Size: s.size() and s.length() return the character count as std::string::size_type.
  • Access: s[i] provides unchecked access; s.at(i) checks bounds and may throw std::out_of_range.
  • State: s.empty() reports whether the size is zero.
  • Search: s.find("text") returns the first position or std::string::npos.
  • Substring: s.substr(pos, count) creates a string from at most count characters starting at pos.
  • Comparison: Relational operators compare strings lexicographically; s.compare(t) returns a negative, zero, or positive result.
  • C interface: s.c_str() returns a null-terminated character pointer valid subject to later string modifications.
CPP
std::string s = "pointer";
std::size_t pos = s.find("int");     // pos is 2
std::string part = s.substr(pos, 3); // "int"

C. String class modifiers

String modifiers change the owned character sequence and may alter its size or storage.

  • Append: append(), +=, and push_back() add characters.
  • Insert: insert(pos, text) places text before position pos.
  • Erase: erase(pos, count) removes characters.
  • Replace: replace(pos, count, text) substitutes a selected range.
  • Removal: pop_back() removes the last character and requires a non-empty string; clear() removes all characters.
  • Capacity changes: resize(n) changes logical length, while reserve(n) requests capacity without adding characters.
  • Iterator invalidation: Modifications that reallocate storage can invalidate pointers, references, and iterators into the string.
CPP
std::string s = "C+";
s.insert(2, "+");                    // "C++"
s.replace(0, 1, "Modern C");         // "Modern C++"

V. Reference Variables

A. Differences between pointer and reference variables

A reference is an alias for an existing object, whereas a pointer is a separate object that stores an address.

  1. Reference variables:

    • Initialization: int& r = x; must bind r when it is declared.
    • Use: Operations on r directly affect x; no dereference syntax is needed.
    • Rebinding: Assigning r = y copies y into x; it does not make r refer to y.
    • Null state: A properly formed reference must bind to an object.
  2. Pointer variables:

    • Initialization: int* p = &x; stores x's address and may later be reassigned.
    • Use: *p accesses the pointed-to object, while p->member accesses an object's member.
    • Null state: A pointer may hold nullptr.
    • Arithmetic: A pointer into an array supports constrained arithmetic; a reference does not.
CPP
int x = 5, y = 8;
int& r = x;
int* p = &x;
r = y;       // x becomes 8
p = &y;      // p now points to y

VI. Multidimensional Arrays

A. Declaration and processing of multidimensional arrays inside main and classes

A multidimensional built-in array is an array whose elements are themselves fixed-size arrays.

  • Inside main: int matrix[2][3] declares two rows, each containing three int elements.
  • Initialization: Nested braces reflect the row structure: {{1, 2, 3}, {4, 5, 6}}.
  • Processing: Nested loops typically traverse rows and then columns.
  • Storage: Built-in multidimensional arrays use row-major order, so each complete row occupies contiguous storage.
  • Function parameters: For void f(int a[][3]), the column count 3 is required to calculate a[i][j].
  • Class member: int data[2][3]; can be declared in a class; modern code may use nested std::array objects for value semantics and size information.
CPP
class Matrix {
    int data[2][3]{};
public:
    void fill() {
        for (std::size_t row = 0; row < 2; ++row)
            for (std::size_t col = 0; col < 3; ++col)
                data[row][col] = static_cast<int>(row + col);
    }
};

Here, row selects one of two inner arrays and col selects one of three integers.

VII. Class Member Addressing

A. Pointer to data member

A pointer to data member identifies a non-static member within objects of a particular class rather than storing an ordinary object address.

  • Declaration: int Record::* member means that member can identify an int data member of Record.
  • Initialization: member = &Record::score; selects the member using its qualified name.
  • Object access: object.*member accesses that member through an object.
  • Pointer access: pointer->*member accesses it through a pointer to an object.
  • Class association: The member pointer must be combined with a suitable object; it does not independently point to one object's storage.
  • Static distinction: A static data member has an ordinary address because it belongs to the class rather than each instance.
CPP
class Record {
public:
    int score = 90;
};

int Record::* member = &Record::score;
Record student;
Record* p = &student;

std::cout << student.*member;        // 90
std::cout << p->*member;             // 90