Search

11.x — Chapter 11 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

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).

1a)

Show Solution

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

Show Solution

1c)

Show Solution

1d)

Show Solution

1e)

Show Solution

2a) 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

2b) 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

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.

3a) 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

3b) 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

3c) 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

3d) 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 two things. First, we need an array that contains an element for each monster Type. Each array element will contain a struct that contains all of the predefined attribute values for that Type of Monster.

Step 1: Create a struct type inside Monster named MonsterData. This struct should have a member for each attribute (name, symbol, health, damage, and gold).
Step 2: Declare an array of that struct as a static member of the class named monsterData (declare this like a normal array member, and then add the word static before it).
Step 3: Add the following code outside of the class. This is the definition for our lookup table:

Now we can index this array to lookup any values we need! For example, to get a Dragon's gold, we can access monsterData[DRAGON].gold.

Use this lookup table to implement your constructor:

The following program should compile:

and print:

A orc (o) was created.

Show Solution

3e) Finally, add a static function to Monster named getRandomMonster(). This function should pick a random number between 0 and 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).

You can use the following code to pick a random number:

The following main function should run:

The results of this program should be randomized.

Show Solution

3f) 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 will 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.
The orc hit you for 2 damage.
(R)un or (F)ight: f
You hit the orc for 2 damage.
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:

  1. The main() function should handle game setup (creating the Player) and the main game loop.
  2. 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.
  3. attackMonster() handles the player attacking the monster, including leveling up.
  4. attackPlayer() handles the monster attacking the player.

Show Solution

12.1 -- Pointers and references to the base class of derived objects
Index
11.7 -- Multiple inheritance

204 comments to 11.x — Chapter 11 comprehensive quiz

  • Luis

    My Player vs Monster code:

    • * Initialize your variables with brace initializers.
      * Line 8, 9, 22, 58, 69, 70, 71, 87, 92, 140, 142, 148, 154, 157: Limit your lines to 80 characters in length for better readability on small displays.
      * Line 3, 10, 71, 142, 143: Use std::rand instead of its global-namespaced counterpart.
      * Line 3, 142: Use std::srand instead of its global-namespaced counterpart.
      * Line 4, 142: Use std::time instead of its global-namespaced counterpart.
      * Line 25-29, 45: Should be const.
      * Line 8, 69: Should be constexpr.
      * Use ++prefix unless you need postfix++.
      * Line 85: @m should be const.
      * Avoid abbreviations.
      * @std::time wants pointer as its argument. Pass @nullptr.

      • Luis

        Thanks! My first post but my C++ is still lack of little details like those, but I'm learning hard in a daily basis for migrating to C++ a big software C project I made in the past.

        Regards!

  • Anthony

    Hi,

    Breaking this down, am I correct in saying that, elision aside, the @Monster(Type) constructor is called to create an anonymous object on the right hand side, and then @Monster m is copy initialised using the copy constructor with the anonymous object?

    If I were to do this instead:

    Here, monster m is direct initialised?

    • > elision aside
      You can't do that anymore. Copy elision is mandatory since C++17.

      Your explanation doesn't fit the code, because @getRandomMonster returns a @Monster, not a @Type.

      Lets go through some examples.

      Line 4 calls @Monster(Type). The value returned by @Monster(Type) is a prvalue (Because it's an expression that initializes an object).

      has to apply copy elision, so no copy is created.
      In line 7, the return value of @getRandomMonster is a prvalue (Returned values that are not references are prvalues). Copy elision applies.
      There's only 1 constructor call.

      Assuming you're using a pre C++17 version with copy elision disabled for whatever reason

      • Anthony

        That was super helpful! Thanks so much.

        Actually, I had written the getRandomMonster() function like this:

        Sorry to have been confusing. But your answer helps me despite me not providing the full picture :)

  • Louis Cloete

    Typo in Q3a): in the output line, the last string should be : ".\n", not ".\n." (notice the extra '.' after the newline char).

  • Louis Cloete

    A question about my implementation of Q2: Here are my Apple and GrannySmith classes:

    apple.h:

    grannySmith.h:

    grannySmith.cpp:

    Now my question is: which is the better way, and why, between my answer and Alex's solution?

    • Alex

      It looks like the only thing you're doing differently is getting apple's name from the Apple class, whereas I've hardcoded the word "apple" into Granny Smith.

      If you were to change the name of an Apple, your Granny Smith would update automatically and mine wouldn't, which makes your solution a little more durable. It looks like you're abusing a getter to do this though.

  • Spirit

  • alpine

    used mersenne twister instead of the provided random gen.

    • * Initialize your variables with brace initializers.
      * Line 16, 65, 66, 74, 75, 76, 80, 85, 90, 99, 134, 140: Limit your lines to 80 characters in length for better readability on small displays.
      * Use your editor's auto-formatting feature.
      * Don't use @std::system.
      * Seed random number generators only once.
      * @std::random_device might produce deterministic results.
      * Use descriptive names.
      * Line 19+: Some of these functions should be const.
      * Built-in data types (int) should be passed by copy, others (std::string) by reference.
      * Line 93 should be moved into the if's body.

  • lucieon

    Hello,

    In quiz 3 inside main(), you've written:

    But so far in this course, I've learnt that the default copy assignment constructor is called like:

    and this is also working on my system.
    So are there more than one default copy assignment constructors which are overloaded?

    • Alex

      > Monster m = Monster{Monster::getRandomMonster()};

      Since Monster::getRandomMonster() already returns a monster, copy-constructing another Monster just so you can copy-initialize m is redundant. Your compiler will probably optimize out the copy-construction, but you should remove it from your code -- or even better, use uniform initialization:

      • lucieon

        Oh my bad.
        I thought getRandomMonster() was returning a Monster::Type.
        Thanks!

        • saltwaterterrapin

          I'm pretty sure that getRandomMonster() does return a Monster::Type the way it's described (I read it that way, at least). This means that the copy constructor is not called:

          becomes

          like you thought, but this calls the Monster(Type type) constructor, not the copy constructor, which has the form Monster(Monster& monster). Keep in mind that the copy constructor isn't a special magic function, but merely a constructor of the form Class(Class& class). It's impossible to overload because the copy constructor is a version of the constructor with a specific set of function parameters.

  • Jon

    Hello, here's my main.cpp from the monster battle game. I laid out my logic slightly differently than the solution you gave, but the game seems to be working properly.

    See any "gotchas" or have any suggestions for me? Thanks for everything, this was a fun one!

    • Hi Jon!

      * Line 3: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 84: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 36, 80: Initialize your variables with uniform initialization.
      * Line 86, 87: Limit your lines to 80 characters in length for better readability on small displays.
      * Line 4, 76, 77: Use std::rand instead of it's global-namespaced counterpart
      * Line 76: Use std::srand instead of it's global-namespaced counterpart
      * Line 76: Use std::time instead of it's global-namespaced counterpart

      * @attackMonster, @attackPlayer: @player and @monster respectively should be const.
      * Line 47, 49, 54: Double comparison of @c
      * Line 76: @std::time expects a pointer as its argument. Pass @nullptr.

      • Jon

        * @attackMonster, @attackPlayer: @player and @monster respectively should be const.

        Question about this line - what about them should be const? I thought that since we're modifying objects with them that they wouldn't be const but I may be a little confused now...

        Thanks very much!

        • Sorry about @attackMonster, I didn't see that it can level up the player.
          @attackPlayer doesn't modify the monster

  • gekyume onfroy

    My solution
    My solution
    My solution
    My solution
    My solution

    Project1.cpp

    CreatureClass.h

    • Snippet 1
      * Line 10, 12, 19: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 80: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 25, 62, 77: Initialize your variables with uniform initialization.
      * Line 10, 56, 75, 93: Limit your lines to 80 characters in length for better readability on small displays.
      * Use @std::rand, @std::srand, @std::time
      * @std::time wants a pointer, pass @nullptr
      * Line 42, 65: Should be else-if
      * Inconsistent formatting
      * Don't use @std::exit or @std::_Exit
      * Don't use goto
      * @fightMonster: Missing return statement
      * Poor naming
      * @<Windows.h>: Unnecessary include that destroys cross-platform compatibility.

      Snippet 2
      * Line 123, 125: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 128: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 5, 10, 11, 12, 13, 14, 106, 107, 108, 109, 110, 143: Initialize your variables with uniform initialization.
      * Line 16, 116, 117, 121, 123, 131, 145, 146, 149, 184, 193: Limit your lines to 80 characters in length for better readability on small displays.
      * Line 21: Should return by const reference
      * Line 46: Parameter should be a const reference
      * Line 21-37: Functions should be const
      * @isDead should be a 1-liner
      * @hasWon: Missing return statement

  • Jahseh

    My solution

  • Spixmaster

    I think that there is a full stop too much at the end.

  • Faruk

    Hi I made the Monster Combat Game.It was very fun and it took me about 2 day.My code is very different for the solutions mostly because i used a Entity class that is a base class of the Player and the Enemy, and i also added a Game object with a game loop and states(Menu state, Game state and Quit).Any tips and advice.

    Here's the code:

    Entity.hpp

    Entity.cpp

    Enemy.hpp

    Enemy.cpp

    Player.hpp

    Player.cpp

    Game.hpp

    Game.cpp

    RandomNumberGeneratro.cpp

    Includes.hpp

    Main.cpp