C++ was created to add strong Object-Oriented Programming support for C.
Key differences from Python:
__init__
for the constructorthis
instead of self
void
for the return type of a method when there is no return value;
after the brace }
at the end of a class public
and private
for data hiding, or encapsulation
class Product:
def __init__(self, name, price, nutrition):
self.name = name
self.price = price
self.nutrition = nutrition
self.inventory = 0
def increase_inventory(self, amount):
self.inventory += amount
def reduce_inventory(self, amount):
self.inventory -= amount
def get_label(self):
return "Foxolate Shop: " + self.name
def get_inventory_report(self):
if self.inventory == 0:
return "There are no bars!"
return f"There are {self.inventory} bars."
class Product {
std::string name;
double price;
std::string nutrition;
int inventory;
Product(std::string name, double price, std::string nutrition) {
this->name = name;
this->price = price;
this->nutrition = nutrition;
this->inventory = 0;
}
void increase_inventory(int amount) {
this->inventory += amount;
}
void reduce_inventory(int amount) {
this->inventory -= amount;
}
std::string get_label() {
return "Foxolate Shop: " + this->name;
}
std::string get_inventory_report() {
if (this->inventory == 0) {
return "There are no bars!";
}
return "There are " + std::to_string(this->inventory) + " bars.";
}
};
class Product {
std::string name;
double price;
std::string nutrition;
int inventory;
Product(std::string name, double price, std::string nutrition) {
this->name = name;
this->price = price;
this->nutrition = nutrition;
this->inventory = 0;
}
void increase_inventory(int amount) {
this->inventory += amount;
}
void reduce_inventory(int amount) {
this->inventory -= amount;
}
std::string get_label() {
return "Foxolate Shop: " + this->name;
}
std::string get_inventory_report() {
if (this->inventory == 0) {
return "There are no bars!";
}
return "There are " + std::to_string(this->inventory) + " bars.";
}
};
We'll explain the ->
instead of the .
later. Hint: this
is a pointer, self
is a reference.
Note the std::to_string
function that converts an integer into a C++ string.
A new library format
was just added to the C++ standard library with C++20. Using it, you could change the last line in get_inventory_reportp
to:
return std::format("There are {} bars.", this->inventory);
C++ enforces encapsulation, which means restricting direct access to some of an object's components. This is a good software engineering principle.
Recall that C++ defaults everything in the class to be private. You must explicitly use the keyword public
.
Many people use both public
and private
for clarity.
:
after private
and public
class Product {
private: // This is optional
std::string name;
double price;
std::string nutrition;
int inventory;
public:
Product(std::string name, double price, std::string nutrition) {
this->name = name;
this->price = price;
this->nutrition = nutrition;
this->inventory = 0;
}
void increase_inventory(int amount) {
this->inventory += amount;
}
void reduce_inventory(int amount) {
this->inventory -= amount;
}
std::string get_label() {
return "Foxolate Shop: " + this->name;
}
std::string get_inventory_report() {
if (this->inventory == 0) {
return "There are no bars!";
}
return "There are " + std::to_string(this->inventory) + " bars.";
}
};
Python
pina_bar = Product("Piña Chocolotta", 7.99, "200 calories")
pina_bar.increase_inventory(2)
C++ (2 ways)
Product pina_bar("Piña Chocolotta", 7.99, "200 calories");
pina_bar.increase_inventory(2);
Product* pina_bar = new Product("Piña Chocolotta", 7.99, "200 calories");
pina_bar->increase_inventory(2);
C++ forces the programmer to think about where variables are stored in memory.
new
C++ (2 ways)
Product pina_bar("Piña Chocolotta", 7.99, "200 calories");
pina_bar.increase_inventory(2);
Product* pina_bar = new Product("Piña Chocolotta", 7.99, "200 calories");
pina_bar->increase_inventory(2);
The second C++ code snippet has Product*
which indicates a pointer to memory that contains a variable of type Product
. Calling new
always returns a pointer.
⚠️ An object allocated on the stack will be lost once it goes out of scope (when the program reaches the next closing brace }
).
⚠️ An object allocated on the heap must be deallocated, using the keyword delete
otherwise you will have a memory leak ⚡ (unrecoverable memory). Memory leaks affect efficiency and cause problems the longer a program runs.
delete pina_bar; // once we are done using it, or the program is closing
Pointers are variables that store a memory address. That address is the memory location to an actual variable with some value. Thus a pointer points to a variable.
int x = 13;
int* x_ptr = &x; // The '&' gets the memory address
*x_ptr = 5; // The '*'dereferences the pointer and accesses the memory
printf("x is %d\n", x); // "x is 5"
printf("x_ptr is 0x%x\n", x_ptr); // "x_ptr is 0xf993b534"
printf("*x_ptr is %d\n", *x_ptr); // "*x_ptr is 5"
The value of the pointer, or memory address may change each time the program is run.
This is confusing at first, but recall that when we looked at Python lists in the environment, the name points to the actual list. Python hides pointers so the programmer doesn't have to worry about the. C++ does not.
Python List:
pair = [1, 2]
pair
is a pointer! 😖
Python hides the memory from the programmer, so even though there is a pointer, as the programmers we just think of pair
as a variable. It is called a reference.
When calling a method of an object via a pointer, you need to first dereference the pointer with *
and next access the method with .
, but since the access operator has higher precedence you must put the dereference operation in parentheses 😕
Product* pina_bar = new Product("Piña Chocolotta", 7.99, "200 calories");
(*pina_bar).increase_inventory(2);
To simplify the above notation, C++ uses ->
which does both the dereference and access 😆
Product* pina_bar = new Product("Piña Chocolotta", 7.99, "200 calories");
pina_bar->increase_inventory(2);
class Animal:
species_name = "Animal"
scientific_name = "Animalia"
play_multiplier = 2
interact_increment = 1
def __init__(self, name, age=0):
self.name = name
self.age = age
self.calories_eaten = 0
self.happiness = 0
def play(self, num_hours):
self.happiness += (num_hours * self.play_multiplier)
print("WHEEE PLAY TIME!")
class Animal {
std::string name;
int age;
int calories_eaten;
int happiness;
public:
std::string species_name = "Animal";
std::string scientific_name = "Animalia";
int play_multiplier = 2;
int interact_increment = 1;
Animal(std::string name, int age=0) {
this->name = name;
this->age = age;
this->calories_eaten = 0;
this->happiness = 0;
}
void play(int num_hours) {
this->happiness += (num_hours * this->play_multiplier);
printf("WHEEE PLAY TIME!\n");
}
};
class Rabbit(Animal):
species_name = "European rabbit"
scientific_name = "Oryctolagus cuniculus"
calories_needed = 200
play_multiplier = 8
interact_increment = 4
num_in_litter = 12
class Rabbit : public Animal {
public:
int calories_needed = 200;
int num_in_litter = 12;
std::string species_name = "European rabbit";
std::string scientific_name = "Oryctolagus cuniculus";
int play_multiplier = 8;
int interact_increment = 4;
Rabbit(std::string name, int age=0) : Animal(name, age) {}
};
Note that we must explicitly define a constructor for the subclass, Rabbit
, and that it must call the superclass constructor.
It is bad practice to have class data public. This is one of the major complaints against Python.
Common practice is to use getter and setter methods that allow the class to manage the data at a private level, but still provide a way to read from and/or write to the data.
Here is the Rabbit class using getters and setters.
class Rabbit : public Animal {
int calories_needed = 200;
// ... other data members (private)
protected:
std::string species_name = "European rabbit";
// ... other inherited data members (protected)
public:
Rabbit(std::string name, int age=0) : Animal(name, age) {}
int get_calories_needed() { return calories_needed; }
// ...
std::string get_species_name() { return species_name; }
// ...
void set_calories_needed(int calories_needed) {
if (calories_needed < 1) {
throw std::invalid_argument("calories_needed must be greater than 1");
}
this->calories_needed = calories_needed;
}
};
Using getter and setter methods adds a lot more code to write, but it facilitates encapsulation and allows for validation (like in the setter above).
This are very important when building large software systems.
The protected
keyword is used to indicate data that can be access by the class and any of its subclasses, but no publicly.
// Old
class Animal {
std::string name;
int age;
int calories_eaten;
int happiness;
public:
std::string species_name = "Animal";
std::string scientific_name = "Animalia";
int play_multiplier = 2;
int interact_increment = 1;
Animal(std::string name, int age=0) {
this->name = name;
this->age = age;
this->calories_eaten = 0;
this->happiness = 0;
}
};
// New
class Animal {
std::string name;
int age;
int calories_eaten;
int happiness;
protected:
std::string species_name = "Animal";
std::string scientific_name = "Animalia";
int play_multiplier = 2;
int interact_increment = 1;
public:
Animal(std::string name, int age=0) {
this->name = name;
this->age = age;
this->calories_eaten = 0;
this->happiness = 0;
}
};
Getter and setter methods for protected class data should be in that class.
class Animal {
std::string name;
int age;
int calories_eaten;
int happiness;
protected:
std::string species_name = "Animal";
std::string scientific_name = "Animalia";
int play_multiplier = 2;
int interact_increment = 1;
public:
Animal(std::string name, int age=0) {
this->name = name;
this->age = age;
this->calories_eaten = 0;
this->happiness = 0;
}
std::string get_species_name() { return species_name; }
std::string get_scientific_name() { return scientific_name; }
// No setters for `species_name` or `scientific_name`
// since they shouldn't change
};
Not all data members need getters and setters. In this example, we may want subclasses to have access to
play_multiplier
and interact_increment
, but not provide a way for outside code
to read from or write to those fields.
When using inheritance and vectors, you must use pointers.
std::vector animals;
animals.push_back(new Rabbit("Bugs"));
animals.push_back(new Elephant("Ella"));
animals.push_back(new Lion("Leo"));
Don't forget to deallocate any allocated memory 👀
for (Animal* animal : animals) {
delete animal;
}
animals.clear();
Python uses both pass by value and pass by reference by default — Python does the first for primative types and the second for references.
When you pass arguments, or parameters, to a function in C++, the default is to use pass by value.
Essentially, Python does what a programmer would want by default, but with C++ the programmer must explicity say when to pass by reference 🙄
Pass by value works as expected for primitive types (int
, bool
, etc.) and for arrays and pointers.
Since pass by value always makes a copy of the variable to pass to the function, passing an object by value is usually not what you want.
Passing object by reference solves both of these problems.
#include <cstdio>
#include <vector>
void zeroize(std::vector<int> values) {
for (int i = 0; i < values.size(); i++) {
values[i] = 0;
}
}
int main() {
std::vector<int> values;
values.push_back(8);
values.push_back(13);
values.push_back(15);
zeroize(values);
// will NOT print 0, but instead will print 15
printf("Last should be zero, is: %d\n", values[values.size()-1]);
return 0;
}
The vector values
is copied and then passed to the function zeroize
.
The zeroize
function does modified the copied vector to only contain three zeros.
The zeroize
function does NOT modify the vector values
defined in main
.
#include <cstdio>
#include <vector>
void zeroize(std::vector<int>& values) {
for (int i = 0; i < values.size(); i++) {
values[i] = 0;
}
}
int main() {
std::vector<int> values;
values.push_back(8);
values.push_back(13);
values.push_back(15);
zeroize(values);
// will print 0
printf("Last should be zero, is: %d\n", values[values.size()-1]);
return 0;
}
Note the use of &
after the type of the parameter to indicate pass by reference.
The zeroize
function now modifies the vector values
defined in main
.
The zeroize
function parameter name doesn't matter. It is the &
that matters.
This ends your crash course on C++ 💥