Search

17.x — Chapter 17 comprehensive quiz

Summary

Inheritance allows us to model an is-a relationship between two objects. The object being inherited from is called the parent class, base class, or superclass. The object doing the inheriting is called the child class, derived class, or subclass.

When a derived class inherits from a base class, the derived class acquires all of the members of the base class.

When a derived class is constructed, the base portion of the class is constructed first, and then the derived portion is constructed. In more detail:

  1. Memory for the derived class is set aside (enough for both the base and derived portions).
  2. The appropriate derived class constructor is called.
  3. The base class object is constructed first using the appropriate base class constructor. If no base class constructor is specified, the default constructor will be used.
  4. The initialization list of the derived class initializes members of the derived class.
  5. The body of the derived class constructor executes.
  6. Control is returned to the caller.

Destruction happens in the opposite order, from most-derived to most-base class.

C++ has 3 access specifiers: public, private, and protected. The protected access specifier allows the class the member belongs to, friends, and derived classes to access the protected member, but not the public.

Classes can inherit from another class publicly, privately, or protectedly. Classes almost always inherit publicly.

Here’s a table of all of the access specifier and inheritance types combinations:

Access specifier in base class Access specifier when inherited publicly Access specifier when inherited privately Access specifier when inherited protectedly
Public Public Private Protected
Private Inaccessible Inaccessible Inaccessible
Protected Protected Private Protected

Derived classes can add new functions, change the way functions that exist in the base class work in the derived class, change an inherited member’s access level, or hide functionality.

Multiple inheritance enables a derived class to inherit members from more than one parent. You should avoid multiple inheritance as much as possible.

Quiz time

Question #1


For each of the following programs, determine what they output, or if they would not compile, indicate why. This exercise is meant to be done by inspection, so do not compile these (otherwise the answers are trivial).

a)

Show Solution

b) Hint: Local variables are destroyed in the opposite order of definition.

Show Solution

c)

Show Solution

d)

Show Solution

e)

Show Solution

Question #2


a) Write an Apple class and a Banana class that are derived from a common Fruit class. Fruit should have two members: a name, and a color.

The following program should run:

And produce the result:

My apple is red.
My banana is yellow.

Show Solution

b) Add a new class to the previous program called GrannySmith that inherits from Apple.

The following program should run:

And produce the result:

My apple is red.
My banana is yellow.
My granny smith apple is green.

Show Solution

Question #3


Challenge time! The following quiz question is more difficult and lengthy. We're going to write a simple game where you fight monsters. The goal of the game is to collect as much gold as you can before you die or get to level 20.

Our program is going to consist of 3 classes: A Creature class, a Player class, and a Monster class. Player and Monster both inherit from Creature.

a) First create the Creature class. Creatures have 5 attributes: A name (std::string), a symbol (a char), an amount of health (int), the amount of damage they do per attack (int), and the amount of gold they are carrying (int). Implement these as class members. Write a full set of getters (a get function for each member). Add three other functions: void reduceHealth(int) reduces the Creature's health by an integer amount. bool isDead() returns true when the Creature's health is 0 or less. void addGold(int) adds gold to the Creature.

The following program should run:

And produce the result:

The orc has 3 health and is carrying 15 gold.

Show Solution

b) Now we're going to create the Player class. The Player class inherits from Creature. Player has one additional member, the player's level, which starts at 1. The player has a custom name (entered by the user), uses symbol '@', has 10 health, does 1 damage to start, and has no gold. Write a function called levelUp() that increases the player's level and damage by 1. Also write a getter for the level member. Finally, write a function called hasWon() that returns true if the player has reached level 20.

Write a new main() function that asks the user for their name and produces the output as follows:

Enter your name: Alex
Welcome, Alex.
You have 10 health and are carrying 0 gold.

Show Solution

c) Next up is the Monster class. Monster also inherits from Creature. Monsters have no non-inherited member variables.

First, write an empty Monster class inheriting from Creature, and then add an enum inside the Monster class named Type that contains enumerators for the 3 monsters that we'll have in this game: DRAGON, ORC, and SLIME (you'll also want a max_types enumerator, as that will come in handy in a bit).

Show Solution

d) Each Monster type will have a different name, symbol, starting health, gold, and damage. Here is a table of stats for each monster Type:

Type Name Symbol Health Damage Gold
dragon dragon D 20 4 100
orc orc o 4 2 25
slime slime s 1 1 10

Next step is to write a Monster constructor, so we can create monsters. The Monster constructor should take a Type enum as a parameter, and then create a Monster with the appropriate stats for that kind of monster.

There are a number of different ways to implement this (some better, some worse). However in this case, because all of our monster attributes are predefined (not random), we'll use a lookup table. A lookup table is an array that holds all of the predefined attributes. We can use the lookup table to look up the attributes for a given monster as needed.

So how do we implement this lookup table? It's not hard. We just need an array that contains an element for each monster Type. Each array element will contain a Creature that contains all of the predefined attribute values for that Type of Monster. We place this array inside of a static member function of Monster so that we can get a default Creature for a given Monster::Type.

The definition of the lookup table is as follows:

Now we can call this function to lookup any values we need! For example, to get a Dragon's gold, we can call getDefaultCreature(Type::dragon).getGold().

Use this function and delegating constructors to implement your Monster constructor.

The following program should compile:

and print:

A orc (o) was created.

Show Solution

e) Finally, add a static function to Monster named getRandomMonster(). This function should pick a random number from 0 to max_types-1 and return a monster (by value) with that Type (you'll need to static_cast the int to a Type to pass it to the Monster constructor).

Lesson 9.5 -- Random number generation contains code you can use to pick a random number.

The following main function should run:

The results of this program should be randomized.

Show Solution

f) We're finally set to write our game logic!

Here are the rules for the game:

The player encounters one randomly generated monster at a time.
For each monster, the player has two choices: (R)un or (F)ight.
If the player decides to Run, they have a 50% chance of escaping.
If the player escapes, they move to the next encounter with no ill effects.
If the player does not escape, the monster gets a free attack, and the player chooses their next action.
If the player chooses to fight, the player attacks first. The monster's health is reduced by the player's damage.
If the monster dies, the player takes any gold the monster is carrying. The player also levels up, increasing their level and damage by 1.
If the monster does not die, the monster attacks the player back. The player's health is reduced by the monster's damage.
The game ends when the player has died (loss) or reached level 20 (win)
If the player dies, the game should tell the player what level they were and how much gold they had.
If the player wins, the game should tell the player they won, and how much gold they had
Here's a sample game session:

Enter your name: Alex
Welcome, Alex
You have encountered a slime (s).
(R)un or (F)ight: f
You hit the slime for 1 damage.
You killed the slime.
You are now level 2.
You found 10 gold.
You have encountered a dragon (D).
(R)un or (F)ight: r
You failed to flee.
The dragon hit you for 4 damage.
(R)un or (F)ight: r
You successfully fled.
You have encountered a orc (o).
(R)un or (F)ight: f
You hit the orc for 2 damage.
The orc hit you for 2 damage.
(R)un or (F)ight: f
You hit the orc for 2 damage.
You killed the orc.
You are now level 3.
You found 25 gold.
You have encountered a dragon (D).
(R)un or (F)ight: r
You failed to flee.
The dragon hit you for 4 damage.
You died at level 3 and with 35 gold.
Too bad you can't take it with you!
Hint: Create 4 functions:

The main() function should handle game setup (creating the Player) and the main game loop.
fightMonster() handles the fight between the Player and a single Monster, including asking the player what they want to do, handling the run or fight cases.
attackMonster() handles the player attacking the monster, including leveling up.
attackPlayer() handles the monster attacking the player.

Show Solution

g) Extra credit:
Reader Tom didn't sharpen his sword enough to defeat the mighty dragon. Help him by implementing the following potions in different sizes:

Type Effect (Small) Effect (Medium) Effect (Large)
Health +2 Health +2 Health +5 Health
Strength +1 Damage +1 Damage +1 Damage
Poison -1 Health -1 Health -1 Health

Feel free to get creative and add more potions or change their effects!

The player has a 30% chance of finding a potion after every won fight and has the choice between drinking or not drinking it. If the player doesn't drink the potion, it disappears. The player doesn't know what type of potion was found until the player drinks it, at which point the type and size of the potion is revealed and the effect is applied.

In the following example, the player found a poison potion and died from drinking it (Poison was much more damaging in this example)

You have encountered a slime (s).
(R)un or (F)ight: f
You hit the slime for 1 damage.
You killed the slime.
You are now level 2.
You found 10 gold.
You found a mythical potion! Do you want to drink it? [y/n]: y
You drank a Medium potion of Poison
You died at level 2 and with 10 gold.
Too bad you can't take it with you!

Show Hint

Show Solution


18.1 -- Pointers and references to the base class of derived objects
Index
17.9 -- Multiple inheritance

460 comments to 17.x — Chapter 17 comprehensive quiz

  • AutomaticP

    I find using templated functions for random number generation in this context to be a cleaner, fully static approach

  • farikun aziz

    covers almost all material regarding inheritance, was confused but in the end understood the flow of the program program

    #include <iostream>
    #include <array>
    #include <ctime>
    #include <random>

    int get_random_numbers(int min, int max){
       std::mt19937_64 twister {static_cast<std::mt19937_64::result_type>(std::time(nullptr))};
       std::uniform_int_distribution<int> random_number(min, max);
       return random_number(twister);
    }

    class Creature{
    protected:
       std::string m_name;
       std::string m_symbol;
       int m_health;
       int m_demage;
       int m_gold;
    public:
       Creature(std::string_view name, std::string_view symbol, int health, int demage, int gold) : m_name{name}, m_symbol{symbol}, m_health{health}, m_demage{demage}, m_gold{gold}{}
       Creature() = default;

       //getter
       std::string_view get_name()const{return m_name;}
       std::string_view get_symbol(){
          if(m_name == "dragon"){
             m_symbol += "\U0001F409";
         }else if(m_name == "orc"){
             m_symbol += "\U0001F47A";
         }else{
             m_symbol += "\U0001F5FF";
         }
          return m_symbol;
       }
       int get_health()const{return m_health;}
       int get_demage()const{return m_demage;}
       int get_gold()const{return m_gold;}
       //getter

       //additional methods
       void reduce_health(int value){
          this->m_health -= value;
       }
       bool is_dead(){
          return this->m_health <= 0;
       }
       void add_gold(int value){
          this->m_gold += value;
       }
       //additional methods
    };

    class Potion{
    protected:
       enum class PotionType{
          HEALT_SMALL ,
          HEALT_MEDIUM,  
          HEALT_LARGE,
          STRENGTH_SMALL,  
          STRENGTH_MEDIUM,  
          STRENGTH_LARGE,  
          POISON_SMALL,
          POISON_MEDIUM,
          POISON_LARGE,
          max_size    
       }m_pt;

    public:
       Potion(int healt, int damage, PotionType pt) : m_health_potion(healt), m_damage_potion(damage), m_pt(pt){}

       static Potion random_potion(){
          int acak = get_random_numbers(0, static_cast<int>(PotionType::max_size) - 1);
          return potion_data(static_cast<Potion::PotionType>(get_random_numbers(0, static_cast<int>(Potion::PotionType::max_size) - 1)));
       }

       const char* potion_size(){
          const char* result;
          switch(m_pt){
             case PotionType::HEALT_SMALL:
             case PotionType::STRENGTH_SMALL:
             case PotionType::POISON_SMALL:
             result = "Small";
             break;

             case PotionType::HEALT_MEDIUM:
             case PotionType::STRENGTH_MEDIUM:
             case PotionType::POISON_MEDIUM:
             result = "Medium";
             break;

             case PotionType::HEALT_LARGE:
             case PotionType::STRENGTH_LARGE:
             case PotionType::POISON_LARGE:
             result = "Large";
             break;
          }
           return result;
       }

       const char* potion_name(){
          const char* result;
          switch(m_pt){
             case PotionType::HEALT_SMALL:
             case PotionType::HEALT_MEDIUM:
             case PotionType::HEALT_LARGE:
             result = "Healt";
             break;

             case PotionType::STRENGTH_SMALL:
             case PotionType::STRENGTH_MEDIUM:
             case PotionType::STRENGTH_LARGE:
             result = "Strength";
             break;

             case PotionType::POISON_SMALL:
             case PotionType::POISON_MEDIUM:
             case PotionType::POISON_LARGE:
             result = "Poison";
             break;
          }
          return result;
       }

       int get_health_potion()const{return m_health_potion;}
       int get_damage_potion()const{return m_damage_potion;}

    private:
       int m_health_potion;
       int m_damage_potion;

       static Potion& potion_data(const PotionType p){
          static std::array<Potion, static_cast<int>(PotionType::max_size)>data
          {
             {
                {2,0,PotionType::HEALT_SMALL},
                {2,0,PotionType::HEALT_MEDIUM},
                {5,0,PotionType::HEALT_LARGE},
                {0,1,PotionType::STRENGTH_SMALL},
                {0,2,PotionType::STRENGTH_MEDIUM},
                {0,3,PotionType::STRENGTH_LARGE},
                {-1,0,PotionType::POISON_SMALL},
                {-2,0,PotionType::POISON_MEDIUM},
                {-5,0,PotionType::POISON_LARGE}
             }
          };
          return data.at(static_cast<int>(p));
       }
    };

    class Player : public Creature{
    private:
       int m_player_level {1};
    public:
       Player(std::string_view name) : Creature{name, "@", 10, 1, 0}{}

       void level_up(){
          ++m_player_level;
          ++m_demage;
       }
       const int& get_player_level()const{return m_player_level;}
       bool has_won(){return get_player_level() >= 20;}

       //another class
       void drink_potion(Potion& p){
          m_health += p.get_health_potion();
          m_demage += p.get_damage_potion();
       }
       //another class
    };

    class Monster : public Creature{
    public:
       enum class Type{DRAGON,ORC,SLIME,max_types};

       Monster(Type type):Creature{get_default_creature(type)}{}

        static Monster get_random_monster(){
          int acak = get_random_numbers(0, static_cast<int>(Type::max_types) - 1);
          return Monster {static_cast<Type>(acak)};
       }
    private:
       static const Creature& get_default_creature(Type t){
          static std::array<Creature, static_cast<std::size_t>(Type::max_types)> monster_stat{
             {
                {"dragon","",20,4,100},
                {"orc","",4,2,25},
                {"slime","",1,1,10}
             }
          };
          return monster_stat.at(static_cast<std::size_t>(t));
       }
    };

    Player get_player_name(){
       std::string player_name;
       std::cout<<"Enter your name: ";
       std::cin>>player_name;
       std::cout<<"Welcome,"<<player_name<<std::endl;
       return Player {player_name};
    }

    //bertanggung jawab atas serangan user ke monster
    void attack_monster(Player &p, Monster& m){
       std::cout<<"You hit the "<<m.get_name()<<" for "<<p.get_demage()<<" damage"<<std::endl;
       m.reduce_health(p.get_demage());

       if(m.is_dead()){
          std::cout<<'\n';
          std::cout<<"===================="<<std::endl;
          p.level_up();
          std::cout<<"You killed the "<<m.get_name()<<std::endl;
          std::cout<<"You are now level "<<p.get_player_level()<<std::endl;
          std::cout<<"You found "<<m.get_gold()<<" gold"<<std::endl;
          p.add_gold(m.get_gold());

          char drink_or_not;
          std::cout<<"You found a mythical potion! Do you want to drink it? [y/n]: ";
          std::cin>>drink_or_not;
          if(drink_or_not == 'y' || drink_or_not == 'Y'){
             Potion pt {Potion::random_potion()};
             std::cout<<"You drank a "<<pt.potion_size()<<" potion of "<<pt.potion_name()<<std::endl;
                p.drink_potion(pt);
             return;
          }else{
             std::cout<<"It's ok \U0001F44C"<<std::endl;
             return;
          }
          std::cout<<"===================="<<std::endl;
       }
    }

    //bertanggung jawab atas serangan monster ke user
    void attack_player(Player &p, Monster &m){
       std::cout<<"The "<<m.get_name()<<" hit you for "<<m.get_demage()<<" damage"<<std::endl;
       p.reduce_health(m.get_demage());
    }

    //fungsi ini akan terus dijalankan selama user belum mati/menang
    void keep_fighting(Player& p, Monster& m){
       m = Monster::get_random_monster();
       std::cout<<"\nYou have encountered a "<<m.get_name()<<'('<<m.get_symbol()<<")"<<std::endl;

       std::cout<<"\n(R)un or (F)ight ";
       char r_or_f;
       std::cin>>r_or_f;

       if(r_or_f == 'F' || r_or_f == 'f'){
          //kita serang monster
          attack_monster(p,m);
          //jika ternyata monster tidak mati
          if(!m.is_dead())
             attack_player(p, m);
          return;
       }else if(r_or_f == 'R' || r_or_f == 'r'){
          //harus diatas 50 agar bisa selamat, ceritanya 50%
          if(get_random_numbers(1,100) >= 50){
             std::cout<<"You succesfully fled"<<std::endl;
             return;
          }else{
             std::cout<<"You failed to fled"<<std::endl;
             attack_player(p, m);
          }
       }
    }

    int main(int argc, char const **argv){
      Player player {get_player_name()};
      Monster monster {Monster::get_random_monster()};

      while(!player.has_won() && !player.is_dead()){
       keep_fighting(player, monster);
      }
      
      if (player.is_dead()){
        std::cout << "\nYou died at level " << player.get_player_level() << " and with " << player.get_gold() << " gold.\n";
        std::cout << "Too bad you can't take it with you!\n";
      }else{
        std::cout << "\nYou won the game with " << player.get_gold() << " gold!\n";
      }

       return 0;
    }

  • Christofle

    This was a fun one, alex & nascardriver!
    Stayed up for hours, it was worth it.
    Hopefully I used all the best practices

    • nascardriver

      Hi!

      Try calling `getRand()` in rapid succession, eg. in a loop.
      ALL_CAPS names are used for macros. If a macro has the same name as one of your entities, there's nothing you can do about it, the macro wins.
      Passing fundamental types by reference is slower than passing them by value. Enums are fundamental types (They're based on integers).
      You don't need to `break` after a `return`, a `return` stops everything.
      If a parameter is a non-const reference, the function is not usable with `const` objects. Most objects should be `const`. Try calling `drinkPotion()` with a const Potion.

      A function that does not return but has a return type causes undefined behavior. Your compiler must have warned you.

      You'll get better warnings, errors and more predictable code if you use list-initialization everywhere.

      Good job! Nice to hear you had fun :)

      • Christofle

        Thanks for pointing those out! :)
        I didn't realize

        was just calling it with 10% chance 3 times.. lmao
        I will keep those practices in mind next time, thanks for everything man!

  • hi again i find solution after a time of thinking ,problem was that attackPlayer and attackMonster cant access Creature objects(or Inheritance from Creature objects ) variables
    ,so i add these function (attackPlayer and attackMonster )as a friend .
    but i find another stranger thing i hope u can explaining to me.
    question is :why i cant call empty function (with non-parameter ) in fightMonster, why we have to use parameter in inner function from
    ,mother function.
    check my code line:77-78

    • nascardriver

      This is happening because of argument-dependent lookup (ADL). ADL is most present in operator uses

      There is no `operator+()` in the global namespace and neither in `std::string`. The `operator+()` comes from `std::operator+()`, but we don't need to write `std::` every time we use `operator+`. It's obvious from the types which `operator+` we want to use. When the compiler sees a function call and doesn't immediately see the function that is being called, it will continue to search the the class/namespace of the first argument.
      The compiler doesn't find `operator+()` in the global namespace, so it continues the search in `std::string` and `std`, where it find the function.

      The compiler sees `empty1(player1)`, doesn't find `empty1` in the global namespace, so it searches for it in the class of the first argument, `Player`.

    • qwerty

      man, use some whitespaces lol

  • hi nascardriverand and Alex i hope u have a good day .
    can u pleaseeee help me  im really depressed i cant find problem with my code from Q3-f
    problem is with line 78 and 80 as i can tall, is say('attackmonster,attackplayer': identifier not found-C3861)
    it should work but i idk why its not working i hope u can help me.

    • nascardriver

      Hi!

      You can only use functions that have been declared before the line you're trying to use them in. If you can only define the function later, you can use a forward declaration. Classes don't have this restriction, all member functions can access all other member functions.

  • Alireza Nikpay

    Great game.
    Thanks.
    Also we can use

    and the switch in drinkPotion will get easier i guess:

  • Sebastien

    Hi,
    For #3-f), I tried to insert attackMonster (resp. attackPlayer) function in Player (resp. Monster) class. However I run into a circular issue since I cannot use Monster type as parameter type in attackMonster if the class Monster has not been declared before Player class, and vice versa.
    May I please know if there is an elegant way to solve this? I tried to forward declare Monster class, without success.
    Thank you!

    • Alex

      Two ways that I can think of:
      1) Put the Monster class in monster.h and #include that into your player.h so that Monster has been defined before the Player.
      2) Make attackMonster a non-member function so neither Player nor Monster depend on each other.

      I like #2 better as it reduces the coupling of the two classes.

  • michael oska

    hello
    in the lookup table why we didn t create array of structure instead of array of creature??
    we can define struct and put the info"name,symbol,...." in it.
    then when we call the delegating constructors like
    Monster(getMonsterInfo(type).name, getMonsterInfo(type).symbol, ........
    i guess it will be faster than calling creature constructor 3 times inside the array
    sorry i m still a begginer ,i m missing something??

    • nascardriver

      Your new `struct` will duplicate `Creature`, duplicate information is not maintainable.
      Construction of that `struct` won't be any faster than that of `Creature`, unless you omit information.
      The lookup table is constructed only once, because it is `static`, performance is of no concern.

  • Aphamino

    Hi!

    This monster game was fun. I'm only having problems getting my random number generator to work. I'm using the mersenne twister inside a non-member function; here's my code for it:

    When I create a for-loop in my main and call getRandomNumber(1, 10) many times, it works like a charm giving numbers within that range. But then when I use that inside my game logic, it generates values within range [0, 2]. No matter what kind of range I give, it only gives numbers 0, 1, or 2. I'm absolutely clueless what's wrong.

    I'd appreciate help. :)

    • Aphamino

      I finally kind of understood the problem. When I call the function getRandomNumber(int min, int max) the first time, it instantiates the parameters of the range variable with the min and max arguments, and since it's static, those values are not going to change on consecutive calls to the function. However, when I removed the static keywords from the lines 6 and 7, it still didn't behave as intended. (It got really strange.)

      So, I ended up writing my code in a way that I created a static randomNumberGenerator for each and every different range for which I needed a random number. That way I got things to work.

      • Hi Aphamino,
        Have you considered only making the Mersenne Twister static? If you do it that way you can reuse the same getRandomNumber() function and pass in your desired min, max each time. Let the distribution be dynamic. This way if you want to change how you generate random numbers in the future you only have one function to worry about, opposed to one for every possible range.

        Alternatively, if you really want the distribution to be static, you could define it one level higher in the function that calls getRandomNumber() and then pass it in instead of min, max.

        I think the first option is better, because doing it the second way makes the caller more dependent on how getRandomNumber() works internally, but it's an option.

        • Aphamino

          Hi Liberty.
          Your suggestion makes so much sense. I don't know why it didn't occur to me to leave only the mersenne variable static. That's the obvious solution. Thank you!

  • chai

    Hi, I was wondering if the static in the std::array is so that the array is only initialized once. Missing this keyword generated no compiler error but I get abort debug message.

    thanks.

    • nascardriver

      Without the `static`, `getDefaultCreature()` would return a reference to a local variable that dies at the end of the function.

  • J34NP3T3R

    can i get more clarification on Question #3

  • J34NP3T3R

    for class Player

    is it not advisable to use default parameter values so that instances of Player class can also be initiated with values like in Creature class ?

  • J34NP3T3R

    in answer to question #3 i got confused a bit.

    some part where i thought a const must be used it wasn't
    and some part where i thought its not necessary, it was.