C++ Review - Pointers, Structs, Classes, and Memory Management

Pointers

  • Pointers store the memory address of a variable.

  • The & operator retrieves the address of a variable.

  • The * operator dereferences a pointer to access the value at that address.

  • Example:

    • double d = 10.0;

    • double* dp = &d;

    • *dp = 20.0; (This changes the value of d to 20.0)

Basic Code Example

  • int var = 20;

  • int* ip = &var;

  • cout << var << endl; (Prints 20)

  • cout << &var << endl; (Prints the memory address of var)

  • cout << *ip << endl; (Prints 20, the value ip points to)

Pointer Arithmetic

  • Arrays are treated as pointers to their first element.

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

  • int* ptr = arr; (ptr points to the first element of arr)

  • ptr++; (Increments the pointer to the next element)

Example
  • cout << *ptr << endl; (Prints 20, because ptr now points to the second element)

  • ptr + 1; (Moves the pointer one element forward, but doesn't change the value of ptr)

  • ptr--; (Moves the pointer back to the previous element)

  • Arithmetic can be performed directly in a cout statement.

Memory Management

  • The new operator creates dynamic variables accessed via pointers.

  • Example: string* str_ptr = new string("hello");

  • Dynamically allocated memory must be deallocated using delete to prevent memory leaks.

  • delete str_ptr;

  • After deleting a pointer, it becomes a dangling pointer; set it to nullptr to avoid errors.

  • It is acceptable to allocate an empty string without needing to deallocate since it takes no space.

Dynamic Arrays

  • Dynamic arrays can have their size determined at runtime.

  • Example: int* dyn_arr = new int[size];

  • Use delete[] to deallocate dynamic arrays: delete[] dyn_arr;

Memory Allocation (Heap vs. Stack)

  • Stack: Stores local variables; memory is managed automatically.

  • Heap: Stores dynamic variables (created with new); memory must be managed manually.

  • Local variables in a function (foo) are deallocated when the function exits.

  • Dynamic variables persist even after the function that created them exits.

Memory Leaks

  • Occur when dynamically allocated memory is not freed using delete.

  • This blocks off memory from being accessed or written to, leading to inefficient use of resources.

  • Deletion is crucial because the compiler doesn't automatically deallocate memory on the heap.

Correct Memory Management Example

  • delete[] arr; (Deallocates the dynamic array)

  • delete str; (Deallocates the string)

  • After these operations, all memory is returned, avoiding memory leaks.

  • Memory leaks don't typically crash the program but are bad practice.

Practice Questions

Question 1: Summing Array Elements Using Pointers
  • Write a function that sums the elements of an integer array using only pointers to traverse the array.

Solution
int sum(int* head, int n) {
    int total = 0;
    for (int i = 0; i < n; i++) {
        total += *(head + i);
    }
    return total;
}
  • head is a pointer to the first element of the array.

  • n is the size of the array.

  • The loop iterates through the array, dereferencing the pointer at each position.

  • Every time i increments, the compiler adds four bytes (size of int) to the address.

  • Example Usage: sum(arr, 4);

Question 2: Reversing a C-string
  • Implement a function to reverse a C-string (null-terminated character array) using pointers.

Solution
void reverse(const char* str) {
    const char* p = str;
    while (*p != '\0') {
        p++;
    }
    p--;
    while (p >= str) {
        cout << *p;
        p--;
    }
    cout << endl;
}
  • The pointer p is advanced to the end of the C-string (null terminator).

  • The pointer is then moved back one position to point to the last character.

  • The while loop prints each character in reverse order until the start of the string is reached.

  • Adding "const" means that the value that the pointer is pointing to will not be edited.

Question 3: Concatenating Two C-strings
  • Implement a function to concatenate two C-strings (source to the end of destination).

Solution
char* concatenate(char* destination, const char* source) {
    char* d = destination;
    while (*d != '\0') {
        d++;
    }

    const char* s = source;
    while (*s != '\0') {
        *d = *s;
        d++;
        s++;
    }
    *d = '\0'; // Ensure null termination
    return destination;
}
  • The function iterates through the destination to find the end of the string and then copies the source string to the end of the destination.

Structs

  • Structs are collections of data treated as a single unit.

  • Used to organize related data.

  • Can contain variables of different data types.

  • Member variables are accessed using the dot operator (.).

  • Example:

struct Person {
    string name;
    int age;
    double money;
};

Person person1;
person1.name = "Alice";
person1.age = 30;
  • Assignment operator (=) is defined by default for structs.

  • Structs are a good way to keep your code organized and readable.

  • Member variables with primitive types (int, double, bool) are left uninitialized.

  • Classes will be constructed with their default constructors.

  • Always remember the semicolon after the struct declaration.

Classes

  • Similar to structs, but members are private by default.

  • At the core of object-oriented programming.

  • An instance of a class is called an object.

  • Strings are objects from the string class.

  • Example:

class Person {
private:
    string name;
    int age;
    double money;
public:
    double doubleMoney(); // Declared, to be defined
};

double Person::doubleMoney() { // Defined using scope resolution operator
    return money * 2;
}
  • When inside a member function, the dot operator is not needed to access members.

Access Specifiers

  • private: Members can only be accessed from within the class.

  • public: Members can be accessed from anywhere.

  • Example:

class Person {
private:
    int age;
public:
    string name;
};

Person bobby;
bobby.name = "Bobby"; // Valid
bobby.age = 5; // Invalid (age is private)

Encapsulation

  • Bundling the data and methods that operate on that data within a class.

  • Making member variables private and providing public accessor (getter) and mutator (setter) functions.

  • Allows controlled access to the object's internal state.

  • Getter functions retrieve the value of a member variable.

  • Setter functions modify the value of a member variable, often with validation.

  • Example:

class Person {
private:
    int age;
    string name;
public:
    int getAge() { return age; }
    void setAge(int newAge) {
        if (newAge >= 0) {
            age = newAge;
        }
    }
    string getName() { return name; }
    void setName(string newName) {
        if (newName.length() > 0) {
            name = newName;
        }
    }
};

Class vs. Struct

  • By convention, structs are used for simpler collections of data, while classes are used for more complex objects.

Constructors

  • Member functions with the same name as the class.

  • No return type.

  • Automatically perform initialization when an object is declared.

  • Can be defined inside or outside the class.

  • Can be overloaded with different numbers and types of parameters.

  • Cannot be called with the dot operator.

  • A default constructor is one with no arguments.

  • The compiler generates an empty one by default.

Basic Syntax

class Person {
private:
    string name;
    int age;
public:
    Person() { // Default constructor
        age = 0;
        name = "";
    }

    Person(string n, int a) : name(n), age(a) {} // Another constructor
};

Person p1; // Calls default constructor
Person p2("John", 25); // Calls the constructor with parameters

Initializer Lists

  • Alternate syntax for initializing member variables in a constructor.

  • Required to initialize const or reference type member variables.

  • Preferred for class member variables.

  • Otherwise, the default constructor for that class will be called.

  • Required for classes with no default constructor.

  • Example:

class Example {
private:
    const int value;
public:
    Example(int v) : value(v) {} // Initializer list
};

Order of Construction

  1. Member initializer list

  2. Default constructors for member variables not in the initializer list

  3. Constructor body

  • Default constructors of class type members are either initialized with the initializer list or a default constructor is required

Practice Questions

Question 1
class Cat {
public:
    Cat(string name) { cout << "I am cat: " << name <<  endl;}
};

class Person {
public:
    Person(int age) {
        cout << "I am " << age << "years old" << endl;
    }
    Cat m_cat;
};
int main() {
    Person p(21);
}

This code doesn't compile because there is no default constructor for Cat.

Solution

class Cat {
public:
Cat(string name) {cout << "I am cat:" << name << endl;}
};

class Person {
public:
Person(int age): mcat("Alfred") { cout << "I am " << age << "years old" << endl; } Cat mcat;
};
int main() {
Person p(21);
}

Output

I am cat: Alfred
I am 21 years old

Question 2

What is the output

class Cat {
public:
    Cat(string name) { cout << "I am cat: " << name <<  endl;}
};

class Person {
public:
    Person(int age) {
        cout << "I am " << age << "years old" << endl;
        m_cat = new Cat("Alfred");
        cout << "Second" << endl;
    }
    Cat* m_cat;
};
int main() {
    Person p(21);
    Cat* cat2 = new Cat("Bob");
}

In this case the output is:
I am 21 years old
I am cat: Alfred
Second
I am cat: Bob
Because now Person has a number variable that will be a pointer to Cat, this means at run time, the cat pointer is initialized once new Cat is called

Destructors

  • The destructors is used when an object is deleted.

  • The destructor will free memory of allocated variables to avoid memory leaks.

  • Example: To use delete on allocated cat, the destructs needs to have that implementation such that
    Class Person {
    ~Person() {
    Delete pointer
    }
    }*
    Destructs get dynamically allocated

Practice Question

Class Cat {
Public:
Cat(string name) { cout << “I am cat: “ << name << endl;}
~Cat() { cout << “Bye” << endl} //destructor
};

Class Person {
Public:
Person(int age) {
cout << “I am: “ << age << “years old” << endl;}
mcat = new Cat (“Alfred”); cout << << “Second” << endl; ~Person () { //destructor Delete mcat;
cout << “Destructing” << endl;}
}
};
Int main () {
Person p(21);
Cat* cat2 = new Cat (“Bob”);

}

Output

I am 21 years old
I am cat: Alfred
second
I am cat: bob
Destructing
In this Case the Structors were both called.

Arrow Operator

This is used when you have a POINTER and you want to access a number variable and or a number function.
Example
Int main () { Person* p= NEw Person (21);
}
P-> set age
Or
(Star p).set age

  • This pointer**
    Is inside with every class and it’s basically pointer to the object that’s occurring for being the memeber function. (Not required).
    This is used when you want to pass the current object.
    Examples
    Void set age (Int age ) {
    //the arrow operator shows that it’s pointing to the number variable = Age.
    This -> age =age ;

Function Overloading
You can make many functions for someone and name as long as the parameters are changing or adding parameters