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

225 comments to 11.x — Chapter 11 comprehensive quiz

  • 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

    Functions:

    randomNumberGenerator.h

    gameFunctions.h

    and the cpp-files:

    Creature.cpp

    Player.cpp

    Monster.cpp

    randomNumberGenerator.cpp

    gameFunctions.cpp

    last but not least the main.cpp

    Thanks to everyone who actually reads this.

    • Hi Harry!

      I like your improvement ideas. The catch with multiple files is that makes sharing the code harder. If someone gets and error and doesn't post it along with their code, I can just c&p their code and compile it myself. Multiple files slow this process down quite a bunch. This doesn't reason not using multiple files. They're a fundamental skill of programming in C++, and as you'll see in my hints, there still are things that can go wrong and would go unnoticed if every quiz was done in 1 file.

      - Initialize your variables with brace initializers.
      - `Creature::isDead`, `Player::hasWon` should be `const`.
      - Pass/return non-fundamental types by reference.
      - Headers should include as few other headers as possible. The larger the header, the longer the compilation. randomNumberGenerator.h doesn't need any includes, gameFunctions.h only needs forward declarations of `Monster` and `Player`, but also no includes.
      - Use `std::srand`, `std::time` and `std::rand`. `std::time` wants a pointer, pass `nullptr`.
      - Limit your lines to 80 characters in length for better readability.
      - Use single quotation marks for characters ('\n' instead of "\n").
      - Inconsistent formatting, use your editor's auto-format feature.
      - gameFunctions.cpp:30 and gameFunctions.cpp:35: Duplicate comparison of `input` to 'F' and 'f'.
      - Line 41+: Wrap conditional statements in curly braces if they exceed 1 line.
      - gameFunctions.cpp:25: You have `isDead`.

      These are minor mistakes, you did pretty good overall!

      • Harry

        Hi nascardriver!
        Amazing how fast you respond and check the code of all the users of this tutorial. Thank you very much!
        I get that it is more work to check the code if it is split in different files and can't see a way around it. Maybe a simpler example with less functions/classes or less content within the functions. This way it would be more about using multiple files an less about the content so it might be easier to check.
        Nevertheless I hope I could contribute to the improvement of this site.

        Regarding your remarks I still have some questions:

        - Headers should include as few other headers as possible. The larger the header, the longer the compilation. randomNumberGenerator.h doesn't need any includes, gameFunctions.h only needs forward declarations of `Monster` and `Player`, but also no includes.
        --> So the includes should be in the cpp file not in the header? Should the classes be also within the cpp file? I know it would be stated twice if I include Player.h and Monster.h also in gameFunctions.cpp but this way a user knows everthing that is needed without checking every header.

        - gameFunctions.cpp:30 and gameFunctions.cpp:35: Duplicate comparison of `input` to 'F' and 'f'.
        --> Maybe I am blind but how can I avoid that? First I check for correct input and ask the player until he provides one (while-loop) and the the program reacts according to the input (if-else). If I change the order (if f… else if r … else incorrect input) I can only imagine going back by a  “goto” which I try to avoid.

        And last but not least, I couldn't find the part you were relating to with the following two remarks:
        - Initialize your variables with brace initializers.
        - Pass/return non-fundamental types by reference.
        --> Can you give me an example where in my code I should do this?

        Sorry for bothering you again but thank you very much for your advice!

        • > So the includes should be in the cpp file not in the header?
          Here's a decision tree (An example is further down)

          > Should the classes be also within the cpp file?
          Declare classes in the header. Include the header in the source file. Define the class in the source file. Your current code is correct in this aspect.

          > if I include Player.h and Monster.h also in gameFunctions.cpp
          You should do that. You could leave the includes in gameFunctions.h as well, since every time you include gameFunctinos.h in a file, that file will most likely need Player.h and Monster.h as well. But if you have a file that needs the functions in gameFunctions.h, but doesn't need the full declaration of Player.h or Monster.h, you're slowing down compilation and are increasing the risk of running into circular includes at some point.

          Here's how I'd structure the includes according to the decision tree from earlier
          Creature.h

          Player.h

          Monster.h

          randomNumberGenerator.h

          gameFunctions.h

          Everything from here on is (1) -> Yes -> Include unless otherwise stated.

          Creature.cpp

          Player.cpp

          Monster.cpp

          randomNumberGenerator.cpp

          gameFunctions.cpp

          main.cpp

          I hope I didn't miss anything.

          > Duplicate comparison

          You could move run and fight into their own functions and call them directly from within the switch. Then you could move the remaining code into its own function and replace the breaks with returns, eliminating the need for `bInputIsValid`. Then `fightMonster` is just the outer loop that calls 1 function.

          > Brace initializers
          Player.h:9
          Creature.cpp:4
          Player.cpp:5
          Monster.cpp:13
          Monster.cpp:20
          Monster.cpp:21
          This should get you the idea, you'll find the rest on your own.

          > Pass/return non-fundamental types by reference
          `Creature::Creature(const std::string &, ...`
          `Player::Player(const std::string & ....`
          `getRandomMonster` is correct, because you want a copy of the monster, not the original.
          If I missed something, now you know what to look for.

          > Sorry for bothering you again but thank you very much for your advice!
          You're welcome!

          • Harry

            Thank you, I think I got it. But it is still hard to decide whether files that include A need the full declarations of B most of the time.
            The things about brace initializers and pass by reference is clear, i just missed the constructors when checking (an unfortunately Alex did not use them in his solution either).

            About the duplicate comparison:
            I haven't thought about adding new boolean variables. I still wonder if adding new variables an do comparisons with them is really preferable over checking an existing variable twice. Isn't it the same regarding computational effort and worse regarding memory?

            One last thing: Are you "officially" part of this site? So do I also support you when supporting this site? I ask because I couln't find any information about you under about/contact but you are giving so much help to all the users.

            • > Isn't it the same regarding computational effort and worse regarding memory?
              No and no. For fundamental types, the performance difference is negligible, but since this is an easy change, there's no reason not to do it.
              Unless you're creating a huge amount of variables, there's no reason to worry about memory usage concerning fundamental types with automatic storage duration (eg. non-static bool inside a function). The memory for these variables exists from when your program starts until it ends. It's reused and doesn't need to be reserved or freed.
              In addition, it's an an extra source of human error. If you decide to use a different character to fight but forget to update all comparisons, your program won't do what you want it to do.

              > Are you "officially" part of this site?
              No, I'm a user like any other. I'm using the WordPress API to check for new comments, that's how I can reply so fast (sometimes :P).
              I'm doing this to stay in touch with C++ while I don't have any active projects and often enough someone posts something that I wouldn't even have thought about.
              C++ is a nice language and I don't like seeing poor code, which is pretty much everywhere, including academic institutes. I'm trying to avoid people from getting bad habits and don't want them to become programmers who write code that's just good enough to get the job done. At some point almost every programmer will work with someone else, and I don't like explaining why undefined behavior should be avoided while something useful could be done instead.

  • Paulo Filipe

    Ok,@Alex, your game is impossible to beat! So, here's my version of Quizz 3 with some additions:

    - More monsters,
    - Healing on level up,
    - Monsters are rolled accordingly to level, so you don't fight a final boss on Level 1,
    - Chance of 2/3rds to flee of a monster.

    I took some time to balance the values to still make it hard to finish the game.

    So far, I won about 1/5 of the runs and to win, you need some strategy.

    Here goes the code:

    I hope it doesn't have a lot of mistakes.

    P.S. This exercise was a lot of fun and interesting to make! Loved doing this!

  • Harley Urquhart

    i was really happy with my result it turned out slightly different but works perfectly

    i use clion and have a large screen i know you will ask me to limit line length lol

    • - Initialize your variables with brace initializers.
      - Limit your lines to 80 characters in length for better readability on small displays. I'm pretty sure CLion has an option to display a vertical line and a certain character count. A little more white space won't hurt your code either.
      - 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`.
      - Print a line feed as the very last character to prevent mushed lines.
      - `fraction` should be `constexpr`.
      - Pass/return non built-in types by const reference.
      - Mixed use of '\n' and `std::endl`. Don't do this unless you have a reason to.
      - Line 99: This loop can only run once. The loop should start earlier.
      - `attackMonster::player` should be const.
      - `attackplayer::moster` should be const.
      - `attackplayer`: Inconsistent capitalization.
      - Line 129: Cannot be true. If extraction failed, `ch` is 0.
      - Line 123, 124: Use documentation comments for documentations.

      Those allow you to generate a documentation of your code later on.
      - Line 140, 144: Should not be in the if-statement.
      - Line 138: This should happen in `main`. If you want to modify an argument, make it a pointer so it's obvious at the call-site that the argument may be modified.
      - Line 39: No need for a conditional operator. `operator<=` returns a bool.

      A lot of comments, but you seem to be getting along nicely overall :)

  • std::rand()

    I don't understand why the interactions between player and monster are outside of the classes. If you make that as an association, it would be a good reminder about object relationships. I made them as associations.

    • Alex

      I chose to put them outside the Player and Monster class because those interactions are part of the game logic / simulation, not intrinsic to the objects themselves. Helps keep the classes themselves from getting too weighed down.

  • Red Lightning

    I don't understand why getRandomNumber() is not inside Monster(). I know letting the user access a function that is not related to monsters would not make sense, but what if you made it private/protected? We could even make another class to generate random numbers as a dependency.

    • A class should only contain members that are related to that class, no matter the visibility. A random number generator has nothing to do with monsters, so it's not part of `Monster`.
      `getRandomMonster` _is_ related to monsters, so it's part of the class.

  • 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?