Simplifying OOP For CS112 Part 3: Understanding Inheritance.
September 30th, 2024
September 30th, 2024
Object oriented programming in C++ is based on 3 core principles. These are: encapsulation, inheritance, and polymorphism. Inheritance is the ability for one class to borrow all attributes and methods from another class, and add its own. An example of this is is a child inheriting features from its parents, while having its own characteristics. Inheritance helps us manage our code well by reducing code duplication and improving modularity. This article builds off what was covered in the understanding encapsulation article. Be sure to check that out before proceeding.
In inheritance, the derived class (child) borrows attributes/methods from a base class (parent). An important thing to note is that while the child gets data from the parent, the parent is unaware of any children. This means base classes cannot access attributes/methods from derived classes. The following is the syntax for inheritance:
class Derived: public Base {
// attribute and methods
};
Let’s implement a Dog
class that inherits from an Animal
class.
class Animal {
public:
string name
// A default constructor is required
Animal() {}
void greet() {
cout << "Hi, I'm" << name << endl;
}
};
class Dog: public Animal {
public:
string breed;
void bark() {
cout << "Woof! Woof!" << endl
}
};
The Dog
class can now be used in main()
as follows:
Dog *dogPtr = new Dog;
dogPtr->name = "Rufus";
dogPtr->breed = "Husky";
dogPtr->greet(); // Output: Hi, I'm Rufus
dogPtr->bark(); // Output: Woof! Woof!
delete dogPtr;
Activity question: create a program that has a Person
class. This class will have a name
and age
attribute. Create a Student
class that inherits from this class. The derived class should have a printDetails()
method that prints the persons details. Using a Student
pointer, set values for the attributes and print the students details in main()
. Solution.
In inheritance, not all methods are passed down. Constructors and destructors are left out. But there are cases when you need to call the base constructor in the derived constructor. This can be done either by using the scope resolution (::)
operator, or the constructor initializer list.
class Animal {
public:
Animal() {
cout << "Animal constructor" << endl;
}
~Animal() {
cout << "Animal destructor" << endl;
}
};
class Dog: public Animal {
public:
Dog()
:Animal() // Either use constructor initializer list
{
Animal::Animal(); // Or use scope resolution (::) operator
cout << "Dog constructor" << endl;
}
~Dog() {
cout << "Dog destructor" << endl;
}
};
The output in main()
would be as follows:
Dog *dogPtr = new Dog;
// Output: Animal constructor
// Output: Dog constructor
delete dogPtr;
// Output: Dog destructor
// Output: Animal destructor
Note how the Animal
constructor is called first but the destructor is called last. And on the other hand, the Dog
constructor is called later but the destructor is called earlier.
Activity question: modify the program made in the base and derived classes section. Add a studentId
attribute to the Student
class. In addition to this, create default and parameterized constructors for both classes. Use the parameterized constructors to set values for the attributes, and make sure to call the base constructor in the derived constructor. Solution.
Until now, the examples used the public:
access specifier. Class attributes and methods can also be marked as protected:
and private:
. These are used to specify which part of the program has access to a classes data.
Public: anyone can access the attribute/method.
Protected: the class itself and its children can access the attribute/method.
Private: only the class itself can access the attribute/method.
class Animal {
private:
string breed;
protected:
int age;
public:
string name;
void printDetails() {
cout << "Breed: " << breed << endl; // Allowed
cout << "Age: " << age << endl; // Allowed
cout << "Name: " << name << endl; // Allowed
}
};
class Dog: public Animal {
public:
void printDetails() {
cout << "Breed: " << breed << endl; // Not allowed
cout << "Age: " << age << endl; // Allowed
cout << "Name: " << name << endl; // Allowed
}
};
The only Animal
attribute that can be accessed in main()
isname
.
Animal *animalPtr = new Animal;
cout << animalPtr->breed; // Not allowed
cout << animalPtr->age; // Not allowed
cout << animalPtr->name; // Allowed
delete animalPtr;
Luckily, getters and setters exist for this sole purpose. For each each attribute, it is recommended to create a getter and setter method.
class Animal {
private:
string breed;
protected:
int age;
public:
string name;
string getBreed() {
return breed;
}
string getAge() {
return age;
}
};
class Dog: public Animal {
public:
void printDetails() {
cout << "Breed: " << getBreed() << endl; // Allowed
cout << "Age: " << age << endl; // Allowed
cout << "Name: " << name << endl; // Allowed
}
};
The updated usage in main()
would be as follows:
Animal *animalPtr = new Animal;
cout << animalPtr->getBreed(); // Allowed
cout << animalPtr->getAge(); // Allowed
cout << animalPtr->name; // Allowed
delete animalPtr;
Activity question: modify the program made in the access specifiers section. Add a secret
attribute to the Person
class. Use access specifiers to make the secret private, the age protected, and the name public. Create a getSecret()
method in the Person
class and modify the printDetails()
method to make use of it. Solution.
This guide went over what inheritance is and the various ways its internals can be implemented. Understanding inheritance will help in grasping the last OOP principle, as it is only achievable using classes with a inheritance relationship. I highly recommend attempting the activity questions before moving forward with the next parts. With that being said, I wish you best of luck for your object oriented programming journey!