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

Lesson 5.9 -- 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

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

255 comments to 11.x — Chapter 11 comprehensive quiz

  • Anastasia

    My 3 (I loved this quiz, it was very fun to write)
    I've put all the game logic into a class and made some minor changes that seemed to make sense (to me). I'd very appreciate any feedback, thanks.

    Creature.h

    Creature.cpp

    Player.h

    Player.cpp

    Monster.h

    Monster.cpp

    Game.h

    Game.cpp

    main.cpp

    twister.cpp

    Initially `twister` was in `.h`, but clang kept complaining about multiple definitions despite the header guards, so I had to put it in a `.cpp`. All those 'extern' declarations seem unnecessary, but they made clang totally happy (warning: no previous extern declaration for non-static variable 'twister' [-Wmissing-variable-declarations])

    I feel that `play()` function in `Game.cpp` (lines 12-42) is a bit messy. Maybe I should have separated it in several little functions instead of the nested 'while's? And I don't like the fact that I had to check whether `player.isDead()` in both 'while' :/ Just couldn't figure out how to do it better.

    edit: got rid of unnecessary includes in main()

    • - Parameter names in header. `Creature::Creature` is useless without parameter names.
      - Monster.cpp:15: Add a `Creature` constructor that takes a `Creature::Data`
      - Game.h: Some of these parameters should be `const`.
      - Explicitly initialize variables which use read from before assigning to them.
      - Use curly braces for bodies that exceed 1 line.

      Watch out when using nested types. They can't be forward declared. `Monster::Type` is only accessible to files that include `Monster.h`.

      > multiple definitions despite the header guards
      Header guards only protect against multiple definitions/declarations inside of a single source file. If the header is included by separate source files, it will be compiled multiple times.

      > Maybe I should have separated it in several little functions instead of the nested 'while's?
      Yes, more functions is usually better.

      > I don't like the fact that I had to check whether `player.isDead()` in both 'while'
      The inner loop should be a do-while-loop. The condition is always true when the loop runs for the first time.
      The way you set up your code, it looks like you want a player to be able to play multiple games. Unless you make sure that the player can only die in `Game::play`, you have to check that they're alive before encountering a monster and after every fight. If `isDead` was an expensive call (Many arguments or high cpu-time), you'd store the result in a temporary so you don't have to call it again. But `isDead` is cheap, so there's no problem with calling it multiple tines.

      • Anastasia

        Hi, thank you for taking the time to look at the code.

        > Parameter names in header.
        Yep, I remember you saying I should use them, but it's too much typing for something that essentially not needed there :/

        > Monster.cpp:15: Add a `Creature` constructor that takes a `Creature::Data`
        Thanks, good point, I haven't thought about that.

        > Game.h: Some of these parameters should be `const`
        Only `printResult()` can take a const reference... It's done. Something else I'm missing?

        > Yes, more functions is usually better.
        I changed it to this (the names are not the best, I know, it's just placeholders):

        `fight()` now returns 'true' if someone dies there (monster or player). The output is less verbose in this version(less calling to `printStats()`). But the code seems less readable... Have I made it worse than before?

        Thanks for all the other clarifications and suggestions - everything's noted :)

        • > Something else I'm missing?
          `attacker` in `attack`, unless I'm missing something.

          > Have I made it worse than before?
          I like this better. With proper names, it should be easier to read. A reader should understand what function A does without reading the definitions of all functions that function A calls. But having everything in one function, you're forcing the reader to go through definitions.

  • Sergey

    Hi! Why the author used copy initialization instead of direct or uniform in the line below?

    Thank you)

  • mmp52

    Hello again!

    Why do you return the name by reference, is it also suggested to use return by reference when using std::string?

    Also why do you not define get functions const like :

    Thanks!

    • Non-fundamental types (Everything other than `int`, `double`, `float`, etc.) should always be passed/returned by reference (Unless you need a copy).
      `getName` should be marked as `const`, well spotted!

    • mmp52

      I also have another question,

      On the part 3-e when you are defining your MonsterData struct you have used a constant char ptr instead of the std::string, what was the reason? What kind of difference makes having the pointer const, rather than a usual char ptr?

      • You should only use `const char*` if you're worried about memory and don't have `std::string_view` available. I don't know why Alex used `const char*` here.
        `name` points to a C-style strings, which can't be modified. You can't point a non-const `char*` to a C-style string.

  • Hi Alex!

    This lesson is also still using the old RNG. Maybe you should just link to the lesson in which you defined it.

  • noobmaster

    My solution for the last quiz...
    (I copied the random number generator from Alex's)

    • - Initialize your variables with brace initializers.
      - Limit your lines to 80 characters in length for better readability.
      - If a member function doesn't modify its object, mark the function `const`.
      - Line 129-131: `if (getRandomNumber(0, 1) == 0)`
      - `attackPlayer::m` should be `const`.
      - Avoid abbreviations. Name your variables descriptively.

  • Torraturd

    Hi, I got this for the final question

    • - Initialize your variables with brace initializers.
      - Limit your lines to 80 characters in length for better readability on small displays.
      - Use std::rand instead of its global-namespaced counterpart.
      - Use std::srand instead of its global-namespaced counterpart.
      - Use std::time instead of its global-namespaced counterpart.
      - `std::time` wants a pointer. Pass `nullptr`.
      - `fraction` should be `constexpr`.
      - `isDead`: `return (m_health <= 0);`
      - `Player::Player`: You're not using the arguments.
      - A lot of your functions should be `const`.
      - Inconsistent formatting. Use your editor's auto-formatting feature.
      - `attackMonster` doesn't need to be a friend.
      - `attachPlayer::monster` should be const.
      - Line 149 is always true.

  • A

    Hello! This was my solution for the final quiz question:

    Creature.h:

    Player.h:

    Monster.h:

    Monster.cpp:

    RandomNumber.h:

    main.cpp

    • Hello!

      - Pass non-fundamental types by (const) reference. Copying them is slow.
      - Don't use `exit`. It makes your program's control flow harder to follow.
      - Limit your lines to 80 characters in length for better readability.
      - All your `get*`, `is*` and `has*` functions should be `const`.
      - `canFlee`: `return (getRandomNumber(0, 1) != 0);`
      - "fightMonster" doesn't sound like a function that handles fleeing.
      - Avoid recursion. Use loops instead.
      - Use `std::rand` instead of `rand`.
      - `std::time` wants a pointer, pass `nullptr`.

  • Atas

    Is there an elegant way to use enum class without constantly having to cast the types back and forth? Right now my code looks, well, not pretty:

    I don't want to use typedefs or using aliases since I only need them like once or twice, they makes it look more readable but even more bloated.

  • Vir1oN

    And I think I`ve noticed a typo in 3f) quiz section, game rules` list, 4th line:

    "If the player escapes, they move to the next encounter will no ill effects."

    Maybe there should be "If the player escapes, they move to the next encounter with* no ill effects."

  • Vir1oN

    Hi @Alex and @nascardriver!

    I want to show you my extended quiz solution and ask you for some advice.

    I didn`t used any of the hints you left in the final question, so my code seems rather scruffy, but I have other things to be proud of!
    What I`m talking about is that I added healing mechanics, level-balance mechanics, advanced escape mechanics, rebalanced old monsters and added new ones. So I`d be particularly grateful if you could point out how I could implement these new features better.

    I tried as much as I could to make my code clear for understanding by adding comments, sorry if you`ll find it hard to sort out.

    • - Initialize your variables with brace initializers.
      - Limit your lines to 80 characters in length for better readability on small displays.
      - Don't pass 32767 to @std::cin.ignore. Pass @std::numeric_limits<std::streamsize>::max().
      - Use std::rand instead of its global-namespaced counterpart.
      - Use std::srand instead of its global-namespaced counterpart.
      - Use std::time instead of its global-namespaced counterpart.
      - Pass/return non-fundamental types by (const) reference.
      - Add constants for max hp.
      - `Player::hasWon`, `Creature::isDead`, `Player::getStatus` should be `const`.
      - Inconsistent formatting. Use your editor's auto-formatting feature.
      - Don't use a property that could change to identify monsters (eg. Line 157). You could instead store the monster's type and compare to that.
      - Magic number (string): 1000.
      - Line 195-196: `return ((c == 'f') || (c == 'F'));`.
      - Several places where you should have used `else if`.
      - If you have a limited set of values and you only want to check for equality, use a `switch`.
      - Line 255: Replace with a call to `fight` and a `return`.
      - `appropriateEnemy`: Add a (minimum encounter) level property to `Monster` and compare to that. You could modify `getRandomMonster` to only generate relevant enemies to prevent having to brute-force them in `main`.
      - `std::time` wants a pointer, pass `nullptr`.
      - Move `MarkMet` out of the loop and make it non-static.
      - If your program prints anything, the last character it prints should be a line feed.
      - "heal" sounds like the name of a function that heals unconditionally. Renaming it to "tryHeal", "healWithChance" or similar could make its use more obvious.
      - `fraction` should be `constexpr`.

      Your input options (line 164) are getting quite big, it's time to step up to some data structure that helps you with all the input options. For one, you could transform `c` to lower case, that way you have only half the options. Then you can use an `std::set` to check if the option is valid.

      Note that `std::set::contains` is a C++20 feature. If your compiler doesn't support it yet, you can use `std::set::find`.

      Apart from what I included in my comments, you implemented all new features in a good way. Looks like you had fun, keep it up and you'll because a great programmer!

      • Vir1oN

        Thanks a lot! I’ll definitely find some time to integrate your remarks.
        This particular task has really inspired me and gave me clear understanding that I want to become a programmer (I’ve just graduated from high school). My respects to you and @Alex!

  • Harry

    Hey Alex,
    after completing the tutorial up to this point I think it is time to write a comment.

    First of all I want to thank you for providing this awesome c++ material. I hab to learn c++ at university a few years a go and decided to look for a course for refreshing my knowledge about this programming language. Now, I am here and think I know a lot more than my university teachers told me, especially the reasons why I do things in a certain way.
    So thanks again for your time and effort you put in this site.

    I also have two remarks of which I think they could further improve this (almost perfect) course.

    1. I think it would be great if you could provide a cheat sheet of rules and best practices for good programming in the appendix. I don't expect something fancy, just a list of all the rules and advice you have spread along the tutorial. Of course I could create it on my own, but I think it is useful for everyone to check every now and then if he or she "follows the rules".

    2. I would appreciate if there would be a few more examples with different files. I think after this chapter would be a good spot to create a program withe several headers and different classes as well as outsourced functions and different namespaces. This way user get a better idea which parts to include in separate files and how to fuse them in a working programm. I understand that it would be too much to do so for every question in all of the quizzes but every now and then it would be nice.

    I hope my remarks help you and would be glad if they can help to further improve your already great work.
    So thanks again and keep doing this kind of work.

    At the end I want to put my code from the monster game. I tried to split the programm in several files and appreciate remarks from anyone on how to further improve my progamming skills and style.

    Cheers,
    Harry

    Starting with the class headers:

    Creature.h

    Player.h

    Monster.h