Lecture 27 - Nov 14, 2023

Summary

In this lecture, we introduce a new pillar of object-oriented programming: inheritance. Inheritance is a process in which a class acquires all properties and behavior of the parent class.

Last lecture

Complexity analysis.

Today

Inheritance.

Inheritance

Inheritance is a process in which a class acquired all properties and behaviour of the parent class. This is one of the pillar of object-oriented programming.

This allows programmers to extend/improve existing classes without modifying the code of these classes.

Example

In Person.h:

#include <iostream>

using namespace std;

class Person {
 private:
  string name;
  int age;

 public:
  Person() {
    name = "";
    age = 0;
  }
  Person(string n, int a) {
    name = n;
    age = a;
  }
  void setName(string n) { name = n; }
  void print() {
    cout << "Name: " << name << endl;
    cout << "Age: " << age << endl;
  }
};

After we implemented the Person class, we want another class that is more specific to aStudent.

The Student class has data members of name, age, ID. We want the print() member function to print all these data members. We still want setName() to set the name of the student.

Options:

  1. Write the Student class from scratch. Issue: Re-write code.
  2. Copy and paste the Person class and change it. Issue: You need to understand all the details of Person.
  3. Inherit the Person class.
  • Benefits: Re-use code and you need to understand what Person does (not the details)
  • Disadvantage: Double-sided weapon. All updates to Person will affect Student.

Base class is Person.

Derived class is Student.

In Student.h:

#include <iostream>

using namespace std;

                // inherit from Person class
class Student : public Person {
 private:
  // string name and int age are inherited
  // age and name are innaccessible  
  int ID;

  // we don't inherit constructors of Person

 public:
  Student() { ID = 0; }
  
  // setName is inherited and accessible

  void setNameID(string n, int d = 0) {
    // name = n // no, name is not accessible
    Person::setName(n);
    ID = d;
  }

  // method overriding
  // cancel/overwrite the print function inherited from Person
  // we write our own
  void print() {
    // I can't access age and name
    Person::print();
    cout << "ID: " << ID << endl;
  }
};

An overriding method, e.g. print, is not the same as an overloaded method. Overloaded function has different arguments and/or return type.

Student is inherited from Person. All data and function members, except for overridden print function.

Student cannot access private members of Person.

In main.cpp:

#include "Person.h"
#include "Student.h"

using namespace std;

int main() {
  Person p("Joe", 23); // constructor of Person
  Student s;  // 2 constructors are called
  // 1st is Person() default constructor
  // 2nd is Student() default constructor

  p.setName("Joseph");
  p.print();  // from Person

  s.setNameID("Ryan");  // setNameID is inherited from Person
  s.print();  // from Student
              // we invoke print on Student object, hence we call print of
              // Student, not of person

  s.setNameID("Marina", 125); // from Student
  s.print(); // from Student

  return 0;
}

The type of s is Student and Person. The type of p is Person.

Inheritance versus membership

Inheritance

Inheritance represents an “is-a” relationship.

Example: Student is a Person, as Student inherits from Person.

class Person {
  ...

};

class Student : public Person {
  ...

};

Student s;
s.SetName();

// s is Student
// s is Person

Membership

Membership represents a “has-a” relationship.

Example: Student has a member Person p.

class Student {
  private:
    Person p;
};

Student s;

// s is Student
// s is NOT person