C++/Classes and Inheritance

From Wikiversity
< C++
Jump to navigation Jump to search

Classes are the building blocks of programs built using the object-oriented methodology. Such programs consist of independent, self-managing modules and their interactions. An object is an instance of such module, and a class is its definition.

Abstract example[edit | edit source]

To illustrate classes and objects, we will use a dog.

Dog:

----

char name
int gender
int age
int size
bool healthy

A dog has several variables: name, the gender of the dog, the age of the dog, its size, and whether it is healthy or not. These variables are called properties. This definition would go in a class. An instance of a dog, say, a dog named Lucy, would be an object. So would a dog named Ruffy. You can have multiple instances of a class, just like you can have multiple dogs. Properties are like "inner variables" of each object made of type Dog.

The example in terms of C++[edit | edit source]

struct Dog[edit | edit source]

#include <string>

struct Dog
{
    std::string name;
    char gender;
    int age;
    int size;
    bool healthy;
};

We have just defined what a dog is. Let us make a couple of dogs...

int main()
{
   Dog lucy;
   Dog ruffy;
   lucy.gender = 'F';
   lucy.age = 3;
   ruffy.gender = 2;
   ruffy.age = 5;

   return 0;
}

In this short example, we have created two objects of the type Dog. One is lucy, and the other is ruffy. We have set lucy's gender to 1, and ruffy's gender to 2. Also, we've set lucy's age to 3, and ruffy's to 5. However, having such information publicly accessible can be dangerous. A bug in your program, for example, could cause the unintended change of the properties of one of the dogs! You wouldn't want a bug in your software to cause lucy to be sick, would you?

class Dog[edit | edit source]

Structs are containers whose properties are always public. In order to gain control over what is public and what isn't, we will have to use a class. Let us see how the Dog class would look like in a class instead of a struct:

#include <string>

class Dog
{
private:
    std::string name;
    int gender;
    int age;
    int size;
    bool healthy;
};

Now our dogs precious properties aren't public for everyone to view and change. However, this raises a little problem. We can't change them either. Only other dogs can see and change them. In order to be able to access a Dog's private properties, we would have to make a function to do so. This brings us to our next chapter.

Dogs with Methods[edit | edit source]

Classes can have functions in them. These functions are called methods. Methods can access all, and even private, properties and methods (yes, methods can be private) of its class. Let us make some methods to get our dog's information...

#include <string>

class Dog
{
private:
    std::string name;
    char gender;
    int age;
    int size;
    bool healthy;

public:
    std::string getName()  const { return name;   } 
    int   getGender() const { return gender; }
    int   getAge()  const  { return age; }
    int   getSize()  const  { return size; }
    bool  isHealthy() const { return healthy; }
    void  setHealthy(bool dhealthy) { healthy = dhealthy; }
    void  setName(const std::string& dname)      { name = dname; }
};

#include <iostream>

int main()
{
    Dog lucy;

    std::cout << "lucy's name   is " << lucy.getName()   << std::endl;
    std::cout << "lucy's gender is " << lucy.getGender() << std::endl;
    std::cout << "lucy's age    is " << lucy.getAge()    << std::endl;
    std::cout << "lucy's size   is " << lucy.getSize()   << std::endl;
    if(lucy.isHealthy())
        std::cout << "lucy is healthy"       << std::endl;
    else
        std::cout << "lucy isn't healthy :(" << std::endl;

    std::cout << "Now I'm changing lucy abit..." << std::endl;

    lucy.setHealthy(!(lucy.isHealthy()));
    lucy.setName("lUCY");

    std::cout << "lucy's name   is " << lucy.getName()   << std::endl;
    std::cout << "lucy's gender is " << lucy.getGender() << std::endl;
    std::cout << "lucy's age    is " << lucy.getAge()    << std::endl;
    std::cout << "lucy's size   is " << lucy.getSize()   << std::endl;
    if(lucy.isHealthy())
        std::cout << "lucy is healthy"       << std::endl;
    else
        std::cout << "lucy isn't healthy :(" << std::endl;

    return 0;
}

However, if we run this program, we might have a little problem. We have never initialized lucy's properties...

Constructors[edit | edit source]

In order to initialize lucy's properties, we could write a method that would do it for us, and call it every time we make a dog. However, C++ already provides us such a method. A constructor is a method which is called every time a new object is created. To use this nice little thing provided to us by C++, we will have to write a method which returns no value, and has the same name as the class. Here's Dog written using a constructor:

#include <string>

class Dog
{
private:
    std::string name;
    char gender;
    int age;
    int size;
    bool healthy;

public:
    std::string getName() const  { return name;   }
    char   getGender() const { return gender; }
    int   getAge() const   { return age; }
    int   getSize() const   { return size; }
    bool  isHealthy() { return healthy; }
    void  setHealthy(bool dhealthy) { healthy = dhealthy; }
    void  setName(const std::string& dname)      { name = dname; }

    Dog() :
        name("Lucy"), gender('f'), age(3), size(4), healthy(true)
    {}
};

From now on, every dog we instantiate will have the name "Lucy", be of gender 1, aged 3, sized 4 and healthy. Well, what if you want some diversity? No problem. Constructors with parameters!

    Dog(const std::string& dname, int dgender, int dage, int dsize, bool dhealthy) :
        name(dname), gender(dgender), age(dage), size(dsize), healthy(dhealthy)
    {}

If you have both constructors in your Dog class, you can now either create a dog as always, which will have the default values (named "Lucy" etc etc), or you could use RAII to make your customized dog as follows:

#include <iostream>

int main()
{
    Dog scruffy("Scruffy", 'm', 8, 6, false);
    
    std::cout << "scruffy's name   is " << scruffy.getName()   << std::endl;
    std::cout << "scruffy's gender is " << scruffy.getGender() << std::endl;
    std::cout << "scruffy's age    is " << scruffy.getAge()    << std::endl;
    std::cout << "scruffy's size   is " << scruffy.getSize()   << std::endl;
    if(scruffy.isHealthy())
        std::cout << "scruffy is healthy"       << std::endl;
    else
        std::cout << "scruffy isn't healthy :(" << std::endl;

    return 0;
}

Inheritance[edit | edit source]

The inheritance mechanism is a core component of the Object Oriented Programming paradigm. Inheritance means that a class "inherits" another class's definition and type. In the previous chapter, we've covered the "HAS-A" relationship. A dog HAS-A name. In this chapter, we will cover the IS-A relationship. A dog IS-A pet.

Abstract example[edit | edit source]

A pet is a wonderful concept, don't you think? It's an abstract concept, but we will not cover abstraction here. There are many kinds of pets (dogs, cats, horses...), and they all have some things in common.

Pet:

----

char* name;

----

char* getName();
void voice();
void interact(Pet* other);
void annoy();
Pet(char* dname);

In terms of C++[edit | edit source]

Implementation of Pet[edit | edit source]

As we already know classes, it shouldn't be a problem to implement Pet in C++.

#include <iostream>

class Pet
{
private:
    char* name;

public:
    char*        getName()            { return name; }
    virtual void voice()              { std::cout << name << ": DEFAULT VOICE" << std::endl; }
    void         interact(Pet* other) { voice(); other->voice(); }
    void         annoy()             { std::cout << name << " annoys you." << std::endl; }
    // I've omitted the constructor for now
};

Notice anything different? Yep, the virtual keyword. This keyword tags a method as overridable. Inheriting classes can override inherited methods. I'll explain it in a moment.

Inheritance[edit | edit source]

OK. We've got a Pet with a "DEFAULT VOICE". However, a dog barks. Well, we could make a dog class, however note that interact() receives a pointer to a Pet. As Pet and Dog are two different classes, pets will be unable to interact with dogs. That's not what we want to do, so we need to somehow make Dog a Pet.
One word: Inheritance.
Remember how I told you that it'd inherit its type? This is what I have meant. Inheritance is done using the ':' operator, as such:

class Dog : public Pet {};

Overriding[edit | edit source]

Now we can make Dogs, and they would all have unique names, they could voice, interact with other pets and annoy their lovely owner. However, there's yet one problem. They still have the annoying "DEFAULT VOICE". Well now that we inherit from Pet, we can override its virtual methods. voice() is a virtual method, so why not do that now? Overriding looks very similar to declaring a new method, so watch out.

class Dog : public Pet
{
public:
    void voice() { std::cout << getName() << ": Woof!" << std::endl; }
};

Constructors and inheritance[edit | edit source]

Now our Dog class functions as intended. However, there's yet one complication...
We haven't got a constructor yet.

class Pet
{
    // ...
public:
    // ...
    Pet(char* dname)
    {
        name = dname;
    }
};
Constructor calling constructors[edit | edit source]

Now that Pet knows how to initialize itself, Dog needs to too. Dog can not directly modify its name, so it has to make Pet do it for him. If Pet would have had a constructor that took no arguments, there wouldn't have been a problem and the process would have been automatic. However, Pet has no "default constructor" (a constructor that takes no arguments), and Dog is in a little difficult situation. I'll save you the trouble. This is how it's done:

class Dog
{
    // ...
public:
    // ...
    Dog(char* dname) : Pet(dname)
    {
    }
};

Here we have Dog's constructor getting a desired name, and passing it to Pet's constructor to do the work.

Inheritance in a program[edit | edit source]

int main()
{
    Pet pet("Michael");
    Dog dog("Wooflemeister");

    dog.voice();
    pet.annoy();
    pet.interact(&dog);
    dog.annoy();
    
    return 0;
}

Output:

Wooflemeister: Woof!
Michael annooys you.
Michael: DEFAULT VOICE
Wooflemeister: Woof!
Wooflemeister annoys you.

Exercise[edit | edit source]

  • Design and implement a system of vehicles.
A family car has a color, a maximum speed and a sunroof. The sunroof can be opened and closed.
A cab's color is always yellow and it has a maximum speed.
A bicycle has a color, a maximum speed and pedals that can be spun.
A truck has a color, a maximum speed, and it can carry a cab or a family car, but not a bicycle.
A road can contain any of the above.
A policeman has a hat, and can block a road or any vehicle.
Tip: You can chain inheritance, and create trees.

Where To Go Next[edit | edit source]

Topics in C++
Beginners Data Structures Advanced
Part of the School of Computer Science