Search

6.x — Chapter P.6 comprehensive quiz

Words of encouragement

Congratulations on reaching the end of the longest chapter in the tutorials! Unless you have previous programming experience, this chapter was probably the most challenging one so far. If you made it this far, you’re doing great!

The good news is that the next chapter is easy in comparison. And in the chapter beyond that, we reach the heart of the tutorials: Object-oriented programming!

Chapter summary

Arrays allow us to store and access many variables of the same type through a single identifier. Array elements can be accessed using the subscript operator ([]). Be careful not to index an array out of the array’s range. Arrays can be initialized using an initializer list or uniform initialization.

Fixed arrays must have a length that is set at compile time. Fixed arrays will usually decay into a pointer when evaluated or passed to a function.

Loops can be used to iterate through an array. Beware of off-by-one errors, so you don’t iterate off the end of your array. Range-based for-loops are useful when the array hasn’t decayed into a pointer.

Arrays can be made multidimensional by using multiple indices.

Arrays can be used to do C-style strings. You should generally avoid these and use std::string_view and std::string instead.

Pointers are variables that store the memory address of (point at) another variable. The address-of operator (&) can be used to get the address of a variable. The dereference operator (*) can be used to get the value that a pointer points at.

A null pointer is a pointer that is not pointing at anything. Pointers can be made null by initializing or assigning the value nullptr (before in C++11, 0) to them. Avoid the NULL macro. Dereferencing a null pointer can cause bad things to happen. Deleting a null pointer is okay (it doesn’t do anything).

A pointer to an array doesn’t know how large the array it is pointing to is. This means sizeof() and range-based for-loops won’t work.

The new and delete operators can be used to dynamically allocate memory for a pointer variable or array. Although it’s unlikely to happen, operator new can fail if the operating system runs out of memory. If you’re writing software for a memory-limited system, make sure to check if new was successful.

Make sure to use the array delete (delete[]) when deleting an array. Pointers pointing to deallocated memory are called dangling pointers. Using the wrong delete, or dereferencing a dangling pointer causes undefined behavior.

Failing to delete dynamically allocated memory can result in memory leaks when the last pointer to that memory goes out of scope.

Normal variables are allocated from limited memory called the stack. Dynamically allocated variables are allocated from a general pool of memory called the heap.

A pointer to a const value treats the value it is pointing to as const.

A const pointer is a pointer whose value can not be changed after initialization.

A reference is an alias to another variable. References are declared using an ampersand (&const -- they must be initialized with a value, and a new value can not be assigned to them. References can be used to prevent copies from being made when passing data to or from a function.

The member selection operator (->) can be used to select a member from a pointer to a struct. It combines both a dereference and normal member access (.).

Void pointers are pointers that can point to any type of data. They can not be dereferenced directly. You can use static_cast to convert them back to their original pointer type. It’s up to you to remember what type they originally were.

Pointers to pointers allow us to create a pointer that points to another pointer.

std::array provides all of the functionality of C++ built-in arrays (and more) in a form that won’t decay into a pointer. These should generally be preferred over built-in fixed arrays.

std::vector provides dynamic array functionality that handles its own memory management and remember their size. These should generally be favored over built-in dynamic arrays.

Thanks to iterators, we don’t have to know how a container is implemented to loop through its elements.

The algorithms library helps us to save a lot of time by providing many off-the-shelf functions. In combination with iterators (and later lambdas), the algorithms library is an important part of C++.

Quiz time

To make the quizzes a little easier, we have to introduce a couple of new algorithms.

std::reduce applies a function, by default the + operator, to all elements in a list, resulting in a single value. When we use the + operator, the result is the sum of all elements in the list. Note that there’s also std::accumulate. std::accumulate cannot be parallelized, because it applies the function left-to-right. std::reduce segments the list, which means that the function is applied in an unknown order, allowing the operation to be parallelized. If we want to sum up a list, we don’t care about the order and we use std::reduce.

Author's note

std::reduce is currently not fully implemented in all major standard libraries. If it doesn’t work for you, fall back to std::accumulate.

std::shuffle takes a list and randomly re-orders its elements.

Possible output

10
10
2 1 4 3

Question #1


Pretend you’re writing a game where the player can hold 3 types of items: health potions, torches, and arrows. Create an enum to identify the different types of items, and an std::array to store the number of each item the player is carrying (The enumerators are used as indexes of the array). The player should start with 2 health potions, 5 torches, and 10 arrows. Write a function called countTotalItems() that returns how many items the player has in total. Have your main() function print the output of countTotalItems() as well as the number of torches.

Show Solution

Question #2


Write the following program: Create a struct that holds a student’s first name and grade (on a scale of 0-100). Ask the user how many students they want to enter. Create a std::vector to hold all of the students. Then prompt the user for each name and grade. Once the user has entered all the names and grade pairs, sort the list by grade (highest first). Then print all the names and grades in sorted order.

For the following input:

Joe
82
Terry
73
Ralph
4
Alex
94
Mark
88

The output should look like this:

Alex got a grade of 94
Mark got a grade of 88
Joe got a grade of 82
Terry got a grade of 73
Ralph got a grade of 4

You can assume that names don’t contain spaces and that that input extraction doesn’t fail.

Show Solution

Question #3


Write your own function to swap the value of two integer variables. Write a main() function to test it.

Show Hint

Show Solution

Question #4


Write a function to print a C-style string character by character. Use a pointer to step through each character of the string and print that character. Stop when you hit the null terminator. Write a main function that tests the function with the string literal “Hello, world!”.

Show Hint

Show Solution

Question #5


What’s wrong with each of these snippets, and how would you fix it?

a)

Show Solution

b)

Show Solution

c)

Show Solution

d)

Show Solution

e)

Show Solution

Question #6


Let’s pretend we’re writing a card game.

a) A deck of cards has 52 unique cards (13 card ranks of 4 suits). Create enumerations for the card ranks (2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, King, Ace) and suits (clubs, diamonds, hearts, spades). Those enumerators will not be used to index arrays.

Show Solution

b) Each card will be represented by a struct named Card that contains a rank and a suit. Create the struct.

Show Solution

c) Create a printCard() function that takes a const Card reference as a parameter and prints the card rank and value as a 2-letter code (e.g. the jack of spades would print as JS).

Show Hint

Show Solution

d) A deck of cards has 52 cards. Create an array (using std::array) to represent the deck of cards, and initialize it with one of each card. Do this in a function named createDeck and call createDeck from main. createDeck should return the deck to main.

Hint: Use static_cast if you need to convert an integer into an enumerated type.

Show Solution

e) Write a function named printDeck() that takes the deck as a const reference parameter and prints the cards in the deck. Use a range-based for-loop. When you can printDeck with the deck you generated in the previous task, the output should be

2C 3C 4C 5C 6C 7C 8C 9C TC JC QC KC AC 2D 3D 4D 5D 6D 7D 8D 9D TD JD QD KD AD 2H 3H 4H 5H 6H 7H 8H 9H TH JH QH KH AH 2S 3S 4S 5S 6S 7S 8S 9S TS JS QS KS AS

If you used different characters, that’s fine too.

Show Solution

f) Write a function named shuffleDeck to shuffle the deck of cards using std::shuffle. Update your main function to shuffle the deck and print out the shuffled deck.

Reminder: Only seed your random number generator once.

Show Solution

g) Write a function named getCardValue() that returns the value of a Card (e.g. a 2 is worth 2, a ten, jack, queen, or king is worth 10. Assume an Ace is worth 11).

Show Solution

Question #7


a) Alright, challenge time! Let’s write a simplified version of Blackjack. If you’re not already familiar with Blackjack, the Wikipedia article for Blackjack has a summary.

Here are the rules for our version of Blackjack:

  • The dealer gets one card to start (in real life, the dealer gets two, but one is face down so it doesn’t matter at this point).
  • The player gets two cards to start.
  • The player goes first.
  • A player can repeatedly “hit” or “stand”.
  • If the player “stands”, their turn is over, and their score is calculated based on the cards they have been dealt.
  • If the player “hits”, they get another card and the value of that card is added to their total score.
  • An ace normally counts as a 1 or an 11 (whichever is better for the total score). For simplicity, we’ll count it as an 11 here.
  • If the player goes over a score of 21, they bust and lose immediately.
  • The dealer goes after the player.
  • The dealer repeatedly draws until they reach a score of 17 or more, at which point they stand.
  • If the dealer goes over a score of 21, they bust and the player wins immediately.
  • Otherwise, if the player has a higher score than the dealer, the player wins. Otherwise, the player loses (we’ll consider ties as dealer wins for simplicity).

In our simplified version of Blackjack, we’re not going to keep track of which specific cards the player and the dealer have been dealt. We’ll only track the sum of the values of the cards they have been dealt for the player and dealer. This keeps things simpler.

Start with the code you wrote in quiz #6. Create a function named playBlackjack(). This function should:

  • Accept a shuffled deck of cards as a parameter.
  • Implement Blackjack as defined above.
  • Returns true if the player won, and false if they lost.

Also write a main() function to play a single game of Blackjack.

Show Solution

b) Extra credit: Critical thinking time: Describe how you could modify the above program to handle the case where aces can be equal to 1 or 11.

It’s important to note that we’re only keeping track of the sum of the cards, not which specific cards the user has.

Show Solution

c) In actual blackjack, if the player and dealer have the same score (and the player has not gone bust), the result is a tie and neither wins. Describe how you’d modify the above program to account for this.

Show Solution


7.1 -- Function parameters and arguments
Index
6.18 -- Introduction to standard library algorithms

755 comments to 6.x — Chapter P.6 comprehensive quiz

  • Suyash

    Wow... This was a wonderful exercise... Thank you for presenting this problem to us... I didn't know that 'gambling' could be this much fun... Anyways, coming back to business...

    I have decided to split the program into three sections (due to maintenance & readability reasons)... The following list will provide a brief summary for each of these sections:

      1. deck.h & deck.cpp - Contains all the code related to question 6... There are a couple of changes but
         nothing major is different from the one I had written to answer question 6.

      2. blackjack.h & blackjack.cpp - Contains the declaration & definition of playBlackjack function as well    
         as the (declaration &) implementation of the associated helper functions. This section also contains
         the code changes that were asked, in part b) & c) of the question 7.

      3. main.cpp - Only contains the code required to play a single game of Blackjack.

    Few extra notes:

      1. The playBlackjack function contains all the complete implementation of the game... And, I have made
         sure to abstract/refactor the reusable portions of the implementation into their own separate
         functions. This function mainly contains a lot of cout statements.
         I could have decided to move some of them to other helper functions (like simulatePlayerHand or
         simulateDealerHand) but after some reflection, I felt that the helper functions were really well
         encapsulated and reusable in their current form.

      2. I have decided to add a zero-initialization value to the enum classes (of Rank & Suit) to allow me the
         ability to create a default Card(struct created in question 6) & aid me in simulating a removal of a
         card from the deck. Since the deck is implemented as a fixed size array of 52 cards, I felt like I
         needed to make this change to differentiate between used & available cards.
        
         On second thought, I could have also added a static counter which could have always pointed to the
         next available card, but I was content with this decision (even though I might have sacrificed some
         performance to run the loops). But, I felt that the trade-off was worth it.

      3. All the functions, symbolic constants, enum classes & struct declared in their respective header files
         are placed in their own separate namespace to prevent name collision. I have also ensured that the
         header files only include other header files that are needed for the purpose of declarations.
         Also, I have decided to use the name 'Deck' instead of 'deck' as the namespace name in order to avoid
         confusion with the parameter/variables name 'deck' used in many functions (as well as other code).

      4. Tested the code to ensure that all the branches of the if/else-if/else, switch & loops can be reached
         by the code. Also made sure that I don't intentionally use any magic numbers (or for that matter,
         hardcoded values) in my code. Also, I tried to ensure that I have used 'constexpr' wherever I was
        using a compile-time constant.

    Like always, feel free to point to any errors in my code... And, my compiler is C++17 capable...

    And, can you answer one silly question of mine, do you guys send an email to our accounts when you (or someone else replies) to a post? If so, I would be greatly relieved because I always worry that you might have replied to one of my posts but due to lack of an email response, I might miss it...

    1. deck.h

    2. deck.cpp

    3. blackjack.h

    4. blackjack.cpp

    5. main.cpp

    • nascardriver

      You receive an email notification whenever someone replies to a comment of yours.

      You went through great effort to make sure your code is of good quality, nice!

      What you're doing with namespaces is what classes are there for. You'll learn about classes later, and you'll update blackjack to use classes in a later quiz.
      Using a namespace for `Deck` isn't wrong, it can exist on its own and doesn't depend on anything else in your program.
      By capitalizing "Deck", you introduced an inconsistency (lower case vs upper case namespace name). If you want to capitalize "Deck", you capitalize all your namespace names.

      To your comments:
      - You shouldn't repeat enumerators in comments, they're in the enum already. Same for `struct` members and parameters. Describe what the enumerator/member/parameter means (If it's not obvious), but don't repeat the type or value in the comment.
      - The -1 `UNAVAILABLE` enumerator is rather self-explanatory to a programmer, it doesn't need a comment. If you want to comment this, a better place would be in a documentation or readme. Otherwise you'll have to repeat the comment above every enum you write.
      - In the header, write what's relevant to a user of your code. They don't care how you shuffle a deck or how you generate random numbers. If you want to document that, do so in the source file.

      To your code:
      - You don't need to handle `UNAVAILABLE` in a `switch`, the default case will catch it. Both, the `UNAVAILABLE` and default case, mean that you have a bug in your code, there's no need for extra handling.
      - You're returning an error code from `getCardValue`, but when you use `getCardValue`, you're not checking for the error code. The error will go unnoticed. As of now, you don't know how to properly handle errors, so a `std::cout` or `std::cerr` in `getCardValue` is enough.
      - blackjack.cpp:29: If there is an unavailable card, you have a bug in your code. You should verify the deck after it's been generated, maybe after it's been modified, but not during normal operation.
      - blackjack.cpp:36: This is magic. You want to know if the card is an ace (card.rank == Rank::ACE), not if the value of the card is the value you'd give to an ace.
      - blackjack.cpp:70: Magic strings ('h', 's'), you have constants. Great use of constants otherwise!
      - blackjack.cpp:10: Duplicate name.
      - `calculateGameResult`'s name doesn't sound like the function prints anything, but that's its main job.
      - You've repeated "DEALER:" and "SYSTEM:" very often. You don't yet know how to write custom output streams. You could've added a print function that takes an enum indicating who's saying something and what they're saying. Then, if you want to rename someone, there's only one place you have to update.
      - blackjackcpp:187: `else if`
      - Use a `switch`-statement for limited sets of values (`main`, `Result`).

      Those are primarily suggestions, not issues, you did an awesome job and I can see you had fun :)

  • chai

    Because I am not experienced with pointers and pointers knowledge is still useful I have decided to try a pointer and fixed array version. Any comments are welcome. I am still mixed up sometime but intellisense really helps. Thanks.

    • nascardriver

      - Inconsistent formatting. Use your editor's auto-formatting feature.
      - Magic numbers: 11, 52
      - Use single quotation marks for characters ('x' vs "x"). The allows optimized functions to be used.
      - You're not using `rankIndex` outside of the loop, declare it in line 148.
      - Line 179: `deckIndex->rank`
      - Line 344, 345: `person->loScore += cardValue`
      - Print line feeds at the ends of lines. This helps with flushing and using foreign code.
      - If you don't modify a pointer/reference parameter, mark it as `const`.
      - Missing `#include <string>`

      To your ace handling:
      You're making the assumption that the maximum score is 21 (Your handling doesn't work when the maximum is over 21). That's ok, blackjack is popular and that 21 likely never changes. But if you make that assumption, aces can be handled a lot easier with a single `bool` that indicates whether or not the player holds an ace (A player can only hold 1 ace with score 11, because 2 would mean their score is too high (22)).

      You're using C-style things for practice, that's ok. In reality, you should be using standard functions/types as much as possible. They make your program more reliable, your code easier to understand and easier to maintain.

  • chai

    This is my implementation of ace being 1 or 11. Any comments and suggestions please. Seems to work fine . It is big and I cannot refactor further. Thanks.

    • nascardriver

      `updateScores` seems to play an important role in your ace handling. Without it, I can't tell if it's correct or if it could be improved.

      So far:
      - Your function is too long, you already noticed that. Split it into several functions.
      - Line 39, 63: These conditions are seemingly unrelated. They shouldn't be handles by an `else if`.
      - `hit`'s initial value is unused, initialize it with empty curly braces.

  • Charan

    I declared all the enumerations and structs in a header file. However when I forward declared the function prototypes in this file, the following error is shown.
    error: 'array' in namespace 'std' does not name a template type.
    However when I removed the forward declarations from the header file and included it in main.cpp,everything worked fine. Can you tell what the error is?

    • Charan

      The problem is solved. It arose because of the error I made in the definition of the function. I did not write const in the parameters.

    • nascardriver

      card.h is using `std::array`, but you're not including . Your code will only work with a specific include order, which is bad. card.h should include .

  • cnoob

    Hi! Ive also written a solution for the Blackjack game. The aces are case specific, but the ties are not implemented. Im sure there is still a lot of room for improvement. It would be nice to hear your suggestions. (I know that its quite long, but unfortunately i could not make it shorter)

    • nascardriver

      - Line 10-18: Identifiers starting in an underscore are reserved. Using them causes undefined behavior.
      - `Card.rank` and `Card.suit` should be of type `Ranks` and `Suits`. This caused you to have magic numbers.
      - Line 45-71: You can do that a lot shorter by adding 2 to the rank.
      - Use single quotation marks for characters ('J' vs "J").
      - Seed random number generators only once. `shuffleDeck` is not random.
      - `playBlackJack` and `main` are too long and contain repetitions. They should be split into several functions.
      - `gameCtrl` and `newGame` should be a `char`.
      - Line 228-231: `return (player > dealer&& player <= 21);`. - Line 252: Initialize with brace initialization. You can use `auto`. - Line 184-192: Wrap bodies in braces if they exceed 1 line. If you don't understand something I suggested, please ask. Try splitting `playBlackJack` into several function for practice to remove the duplicated code. You can add another struct `Player` that contains the score and ace count of the player and dealer.

      • cnoob

        O.K., Ive tried to improve the code, but Ive got a couple of questions:
        -When I try to brace initialize line 252, I get the error message: "error C2064: term does not evaluate to a function taking 1 arguments"
        -What do you mean by "- Line 45-71: You can do that a lot shorter by adding 2 to the rank."?

        • nascardriver

          I meant line 259, sorry! 252 is an assignment, you can't use brace initialization there.

          Looking at the code, you don't need `deckRef` at all. You can use `deck` everywhere you used `deckRef`.

          • cnoob

            You are absolutely right, but it should be RANK_10, because it starts at RANK_2. Ive implemented that, and I really did not need deckRef in the end. The error message at the brace initialization was only because of my bad syntax. Ive split the functions and improved the code. Even though some parts needed a couple of extra lines here and there, I think it is much more comprehensible now.

            • nascardriver

              That's a lot better!
              Make sure you enable compiler warnings. Your code produces undefined behavior.
              A couple more issues that you don't have to fix now, but should keep in mind:
              - Magic numbers are bad. You have enums, use them.
              - Duplicate code is bad. Write functions (Line 179, 183, 187).
              - Global variables are bad. `mersenne` should be `static` in `shuffleDeck`.

              PS: Syntax highlighting works after refreshing the page, you don't have to delete and re-post.

              • cnoob

                Yes, I have just noticed: the undefined behavior was because of a missing return value, that I simply forgot about in the new card function. Ive fixed that. In the end I have also corrected the rest, since it was not so much work anymore. Thanks a lot!

  • Ryan

    What can I improve in this playBlackjack() function. I tried including all possible outcomes and it looks very messy with all the if-else statements. I included the additional parts of the quiz.
    The additional parameter in getCardValue(1, 2); just increments the value if the card is an Ace.

    • nascardriver

      - Initialize variable with brace initialization (Line 13).
      - Use `std::exit`, not `exit.
      - Line 40+ and 50+, 58+ and 66+: Duplicate code, write functions.
      - Line 30, 31: Extraction failed, so there's something bad in the input stream. You cleared the error flag, but didn't remove the bad input (Lesson C.5.10).
      - `dealerScore` and `playerScore` can be initialized.
      - Line 48 always compares true.

      You code looks messy because you wrote everything in one function.

  • BakaTensi

    There is my code for exercise 6 below. I have a few specific questions about it:

    1. Does it make sense to declare a symbolic constant for the size of the deck (since it is not likely to change and I don't want to use the magic number 52 all the time)?

    2. Is it an acceptable solution to declare that typedef for Deck?

    3. For the random number generation:
         a, How bad a solution is it to have getRandomDeckIndex() return a size_type directly?
         b, I feel like my solution is a mess for the random number generation + shuffling. Is that the case, and how can I improve on them if I want to stay with Mersenne Twister?

    4.In the main function, is it okay to use a for-each loop that way to initialize the deck?

    Any other observations are appriciated!