Search

8.x — Chapter 8 comprehensive quiz

In this chapter, we explored the meat of C++ -- object-oriented programming! This is the most important chapter in the tutorial series.

Quick Summary

Classes allow you to create your own data types that bundle both data and functions that work on that data. Data and functions inside the class are called members. Members of the class are selected by using the . operator (or -> if you’re accessing the member through a pointer).

Access specifiers allow you to specify who can access the members of a class. Public members can be accessed directly by anybody. Private members can only be accessed by other members of the class. We’ll cover protected members later, when we get to inheritance. By default, all members of a class are private and all members of a struct are public.

Encapsulation is the process of making all of your member data private, so it can not be accessed directly. This helps protect your class from misuse.

Constructors are a special type of member function that allow you to initialize objects of your class. A constructor that takes no parameters (or has all default parameters) is called a default constructor. The default constructor is used if no initialization values are provided by the user. You should always provide at least one constructor for your classes.

Member initializer lists allows you to initialize your member variables from within a constructor (rather than assigning the member variables values).

In C++11, non-static member initialization allows you to directly specify default values for member variables when they are declared.

Prior to C++11, constructors should not call other constructors (it will compile, but will not work as you expect). In C++11, constructors are allowed to call other constructors (called delegating constructors, or constructor chaining).

Destructors are another type of special member function that allow your class to clean up after itself. Any kind of deallocation or shutdown routines should be executed from here.

All member functions have a hidden *this pointer that points at the class object being modified. Most of the time you will not need to access this pointer directly. But you can if you need to.

It is good programming style to put your class definitions in a header file of the same name as the class, and define your class functions in a .cpp file of the same name as the class. This also helps avoid circular dependencies.

Member functions can (and should) be made const if they do not modify the state of the class. Const class objects can only call const member functions.

Static member variables are shared among all objects of the class. Although they can be accessed from a class object, they can also be accessed directly via the scope resolution operator.

Similarly, static member functions are member functions that have no *this pointer. They can only access static member variables.

Friend functions are functions that are treated like member functions of the class (and thus can access a class’s private data directly). Friend classes are classes where all members of the class are considered friend functions.

It’s possible to create anonymous class objects for the purpose of evaluation in an expression, or passing or returning a value.

You can also nest types within a class. This is often used with enums related to the class, but can be done with other types (including other classes) if desired.

Quiz time

Question #1


a) Write a class named Point2d. Point2d should contain two member variables of type double: m_x, and m_y, both defaulted to 0.0. Provide a constructor and a print function.

The following program should run:

This should print:

Point2d(0, 0)
Point2d(3, 4)

Show Solution

b) Now add a member function named distanceTo that takes another Point2d as a parameter, and calculates the distance between them. Given two points (x1, y1) and (x2, y2), the distance between them can be calculated as sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)). The sqrt function lives in header cmath.

The following program should run:

This should print:

Point2d(0, 0)
Point2d(3, 4)
Distance between two points: 5

Show Solution

c) Change function distanceTo from a member function to a non-member friend function that takes two Points as parameters. Also rename it “distanceFrom”.

The following program should run:

This should print:

Point2d(0, 0)
Point2d(3, 4)
Distance between two points: 5

Show Solution

Question #2


Write a destructor for this class:

Show Solution

Question #3


Let’s create a random monster generator. This one should be fun.

a) First, let’s create an enumeration of monster types named MonsterType. Include the following monster types: Dragon, Goblin, Ogre, Orc, Skeleton, Troll, Vampire, and Zombie. Add an additional MAX_MONSTER_TYPES enum so we can count how many enumerators there are.

Show Solution

b) Now, let’s create our Monster class. Our Monster will have 4 attributes (member variables): a type (MonsterType), a name (std::string), a roar (std::string), and the number of hit points (int). Create a Monster class that has these 4 member variables.

Show Solution

c) enum MonsterType is specific to Monster, so move the enum inside the class as a public declaration.

Show Solution

d) Create a constructor that allows you to initialize all of the member variables.

The following program should compile:

Show Solution

e) Now we want to be able to print our monster so we can validate it’s correct. To do that, we’re going to need to write a function that converts a MonsterType into a std::string. Write that function (called getTypeString()), as well as a print() member function.

The following program should compile:

and print:

Bones the skeleton has 4 hit points and says *rattle*

Show Solution

f) Now we can create a random monster generator. Let’s consider how our MonsterGenerator class will work. Ideally, we’ll ask it to give us a Monster, and it will create a random one for us. We don’t need more than one MonsterGenerator. This is a good candidate for a static class (one in which all functions are static). Create a static MonsterGenerator class. Create a static function named generateMonster(). This should return a Monster. For now, make it return anonymous Monster(Monster::SKELETON, “Bones”, “*rattle*”, 4);

The following program should compile:

and print:

Bones the skeleton has 4 hit points and says *rattle*

Show Solution

g) Now, MonsterGenerator needs to generate some random attributes. To do that, we’ll need to make use of this handy function:

However, because MonsterGenerator relies directly on this function, let’s put it inside the class, as a static function.

Show Solution

h) Now edit function generateMonster() to generate a random MonsterType (between 0 and Monster::MAX_MONSTER_TYPES-1) and a random hit points (between 1 and 100). This should be fairly straightforward. Once you’ve done that, define two static fixed arrays of size 6 inside the function (named s_names and s_roars) and initialize them with 6 names and 6 sounds of your choice. Pick a random name from these arrays.

The following program should compile:

Show Solution

i) Why did we declare variables s_names and s_roars as static?

Show Solution

Question #4


Okay, time for that game face again. This one is going to be a challenge. Let’s rewrite the Blackjack game we wrote in chapter 6 using classes! Here’s the full code without classes:

Holy moly! Where do we even begin? Don’t worry, we can do this, but we’ll need a strategy here. This Blackjack program is really composed of four parts: the logic that deals with cards, the logic that deals with the deck of cards, the logic that deals with dealing cards from the deck, and the game logic. Our strategy will be to work on each of these pieces individually, testing each part with a small test program as we go. That way, instead of trying to convert the entire program in one go, we can do it in 4 testable parts.

Start by copying the original program into your IDE, and then commenting out everything except the #include lines.

a) Let’s start by making Card a class instead of a struct. The good news is that the Card class is pretty similar to the Monster class from the previous quiz question. First, create private members to hold the rank and suit (name them m_rank and m_suit accordingly). Second, create a public constructor for the Card class so we can initialize Cards. Third, make the class default constructible, either by adding a default constructor or by adding default arguments to the current constructor. Finally, move the printCard() and getCardValue() functions inside the class as public members (remember to make them const!).

A reminder

When using a std::array (or std::vector) where the elements are a class type, your element’s class must have a default constructor so the elements can be initialized to a reasonable default state. If you do not provide one, you’ll get a cryptic error about attempting to reference a deleted function.

The following test program should compile:

Show Solution

b) Okay, now let’s work on a Deck class. The deck needs to hold 52 cards, so use a private std::array member to create a fixed array of 52 cards named m_deck. Second, create a constructor that takes no parameters and initializes m_deck with one of each card (modify the code from the original createDeck() function). Third, move printDeck into the Deck class as a public member. Fourth, move shuffleDeck into the class as a public member.

The trickiest part of this step is initializing the deck using the modified code from the original createDeck() function. The following hint shows how to do that.

Show Hint

The following test program should compile:

Show Solution

c) Now we need a way to keep track of which card is next to be dealt (in the original program, this is what nextCardIndex was for). First, add a member named m_cardIndex to Deck and initialize it to 0. Create a public member function named dealCard(), which should return a const reference to the current card and advance m_cardIndex to the next index. shuffle() should also be updated to reset m_cardIndex (since if you shuffle the deck, you’ll start dealing from the top of the deck again).

The following test program should compile:

Show Solution

d) Next up is the Player. Because playerTurn and dealerTurn are very different from each other, we’ll keep them as non-member functions. Make Player a class and add a drawCard member function that deals the player one card from the deck, increasing the player’s score. We’ll also need a member function to access the Player‘s score. For convenience, add a member function named isBust() that returns true if the player’s score exceeds the maximum (maximumScore). The following code should compile:

Show Solution

e) Almost there! Now, just fix up the remaining program to use the classes you wrote above. Since most of the functions have been moved into the classes, you can jettison them.

Show Solution


9.1 -- Introduction to operator overloading
Index
8.16 -- Timing your code

350 comments to 8.x — Chapter 8 comprehensive quiz

  • Ged

    Question number 1

    Why are using static here? I tried to run the code with and without it and can't see any difference.

    Question number 2

    What is the difference between ( 1.0 / (RAND_MAX + 1.0) )  and ( 1.0 / RAND_MAX )? Forgot it and just wanna make things clear.

    Question number 3

    When we were writing our monster code. I used std::array. Tried to use const, but it wouldn't work. So I wrote it like this. Any tips of how to improve it?

    • nascardriver

      1.
      We only want to calculate `fraction` once. Without `static`, `fraction` might get calculated every time we call `getRandomNumber`. The `static` adds some runtime cost itself, I'm not sure if the `static` is actually beneficial for such a simple calculation.

      2.

      [0, 1] means both 0 and 1 are possible (and everything in between).
      [0, 1) means 0 is possible, but 1 isn't (everything in between is possible).

      If we used [0, 1], we could get

      Oops, we got max + 1, we don't want that.
      What if we remove the +1 from inside the brackets?

      Seems to work, so why don't we do:

      Let's assume min=1, max=2
      How do we generate a 2? (std::rand() * fraction) has to be exactly 1. How can that be exactly 1? `std::rand()` has to be exactly `RAND_MAX`. In all other cases, we get a 1. That's a very predictable RNG.
      The RNG from the lesson doesn't have this problem. The upper half of `std::rand()`'s results generates a 2, the lower half generates a 1.

      3.
      `const` should work. You have to add it to the declaration AND definition.
      Line 18, 19: Magic number: 5. Use `s_names.size() - 1` and `s_roars.size() - 1`.

  • Janko

    I just have a quick question, why aren't we passing (MonsterType type) to getTypeString member function here?

  • Tompouce

    Hello!
    Here's a question. I was writting a function to generate a random integer with the mersene prime twister algorithm for the random monster generator. So I staticaly instantiated the mersene twister engine so that it would onle get seeded once like so:

    BUT, as you can see I thought I could be cheeky and make the uniform_int_distribution static as well, so that it wouldn't be created every time the function is called. But then, eventhough I provided min and max values of 1 and 100 the generated numbers always stayed very low, rarely ever going beyond 8 or 9, and sometimes a 0 comes out even though I gave a minimum value of 1.

    Making the distribution not static fixes everything but I'm curious as to why making it static would cause this problem

    • nascardriver

      The first call to `getRandomInt` sets `min` and `max` of `RNG`. The `min` and `max` of every other call is ignored, because the `RNG` already exists.

  • Avijit Pandey

    Hi! Consider this code:

    As you described in the anonymous object lesson, "return 5+3" creates an anonymous int object and assigns it the value '8' which it then returns back to the caller by value in this case.
    I wanted to ask if it is possible to create a similar anonymous object in the case of the function

    that is directly put in the return value?

    • nascardriver

      The solution is creating the monster in the `return`-statement. You could remove the explicit constructor call by using brace initialization

  • sito

    hello! I've been having problems with quiz 4b. I've organized my classes in to header files with the functions in cpp files. In my deck class i'm trying to call swapCard and getRandomNumber but i get the error that the identifier can not be found. I've checked both in the header and the cpp files to make sure i didn't mess up the declaration of the function. I've been looking at the code for a while now but i can't spot what i'm doing wrong. It's probably something easy that I overlooked but it would be nice if anyone could look through my code and give me some feedback.
    the error is on line 43 in the cpp file.
    header file

    and the cpp file.

    • nascardriver

      Hi!

      `sufleDeck` the the cpp file isn't a member of `Deck`. You forgot the `Deck::` prefix.

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    I try to move the MonsterGenerator class to header file or .cpp file. The error is same: "MonsterGenerator has not been declared". I don't know why. But, I think "Is it because the static keyword 'means' the MonsterGenerator is only seen in that header file or .cpp file?" If it is, so, there is limitation of static class in term of multiple files. But, I'm not sure about this.

    My second question is why don't make s_names and s_roars private? I try to do that and errors come in:

    Why an out of class initialization is still required? s_names and s_roars are static const member variable.

    • `MonsterGenerator` can be moved into separate files. The error message looks like you missed some includes. Without code and a full error message, I can't help.

      > why don't make s_names and s_roars private?
      They're not used outside of `generateMonster`, so there's no reason to make them accessible there.
      `static` member variables that aren't literals have to be initialized outside of the class, as otherwise it's not clear when to initialize them.

      Alternatively, they can be marked `inline`. They will be initialized when they're first used.

      This is easier, but adds overhead to the run time as every time one of the members is accessed, it has to be checked if they have been initialized already.

  • Samira Ferdi

    Hi, Alex and Nascardriver!

    In quiz no.2, my compiler throw errors that I don't understand why:

    • Hi!

      `m_data` is a pointer. When you create a copy of the class, the pointer will be copied.

      The same happens when you assign one `HelloWorld` to another.
      The copy that only copies the pointer, but not the pointed-to data is called _shallow copy_. It's covered later.

      You can explicitly default the copy constructor and copy assignment operator to silence the warnings.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]