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 ofdto 20.0)
Basic Code Example
int var = 20;int* ip = &var;cout << var << endl;(Prints 20)cout << &var << endl;(Prints the memory address ofvar)cout << *ip << endl;(Prints 20, the valueippoints 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 ofarr)ptr++;(Increments the pointer to the next element)
Example
cout << *ptr << endl;(Prints 20, becauseptrnow points to the second element)ptr + 1;(Moves the pointer one element forward, but doesn't change the value ofptr)ptr--;(Moves the pointer back to the previous element)Arithmetic can be performed directly in a
coutstatement.
Memory Management
The
newoperator creates dynamic variables accessed via pointers.Example:
string* str_ptr = new string("hello");Dynamically allocated memory must be deallocated using
deleteto prevent memory leaks.delete str_ptr;After deleting a pointer, it becomes a dangling pointer; set it to
nullptrto 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;
}
headis a pointer to the first element of the array.nis the size of the array.The loop iterates through the array, dereferencing the pointer at each position.
Every time
iincrements, the compiler adds four bytes (size ofint) 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
pis 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
whileloop 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
destinationto 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
constor 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
Member initializer list
Default constructors for member variables not in the initializer list
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