Search

6.x — Chapter 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 (in C++11).

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. For-each 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 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 0 (or in C++11, nullptr) 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 they are pointing to is. This means sizeof() and for-each 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, so make sure to check whether new returned a null pointer.

Make sure to use the array delete (delete[]) when deleting an array. Pointers pointing to deallocated memory are called dangling pointers. Dereferencing a dangling pointer can cause bad things to happen.

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, but this does not mean address-of in this context. References are implicitly 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.

Quiz time

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 a fixed array to store the number of each item the player is carrying (use built-in fixed arrays, not std::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().

Show Solution

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. Dynamically allocate an array 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

Hint: You can modify the selection sort algorithm from lesson 6.4 -- Sorting an array using selection sort to sort your dynamic array. If you put this inside its own function, the array should be passed by address (as a pointer).

Show Solution

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

Hint: Use reference parameters

Show Solution

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 a null terminator. Write a main function that tests the function with the string literal “Hello, world!”.

Hint: Use the ++ operator to advance the pointer to the next character.

Show Solution

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

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

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

Show Solution

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

Show Solution

6c) 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 Solution

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

Hint: Use static_cast if you need to convert an integer into an enumerated type.
Hint: Don’t forget that std::array’s operator[] expects an index of type size_type, and that size_type must be prefixed by the full name of the array type to be accessed.

Show Solution

6e) Write a function named printDeck() that takes the deck as a const reference parameter and prints the values in the deck. Use a for-each loop.

Show Solution

6f) Write a swapCard function that takes two Cards and swaps their values.

Show Solution

6g) Write a function to shuffle the deck of cards called shuffleDeck(). To do this, use a for loop to step through each element of your array. Pick a random number between 1 and 52, and call swapCard with the current card and the card picked at random. Update your main function to shuffle the deck and print out the shuffled deck.

Hint: Review lesson 5.9 -- Random number generation for help with random numbers.
Hint: Don’t forget to call srand() at the top of your main function.
Hint: If you’re using Visual Studio, don’t forget to call rand() once before using rand.

Show Solution

6h) 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

7) 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() that returns true if the player wins, and false if they lose. This function should:
* Accept a shuffled deck of cards as a parameter.
* Initialize a pointer to the first Card named cardPtr. This will be used to deal out cards from the deck (see the hint below).
* Create two integers to hold the player’s and dealer’s total score so far.
* Implement Blackjack as defined above.

Hint: The easiest way to deal cards from the deck is to keep a pointer to the next card in the deck that will be dealt out. Whenever we need to deal a card, we get the value of the current card, and then advance the pointer to point at the next card. This can be done in one operation:

This returns the current card’s value (which can then be added to the player or dealer’s total), and advances cardPtr to the next card.

Also write a main() function that plays a single game of Blackjack.

Show Solution

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

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

7b) 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.16 -- An introduction to std::vector

533 comments to 6.x — Chapter 6 comprehensive quiz

  • Jon

    Hi! Here's my solution for the Blackjack game with aces-as-1-or-11s included. I wanted to get as close as possible to the flow of real blackjack so I added the extra variables playerTotalWithAces and DealerTotalWithAces (probably could have named them more accurately in hindsight) so I could output different, more realistic text throughout the game depending on how many aces were in each hand. My dealer also must hit on Soft 17's, which turned out to be a little tricky to implement but I think I got it! Hopefully it makes sense, I'd love any suggestions/helpful hints, thanks so much!

    • Hi Jon!

      * Line 100, 101: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 43, 44, 223, 320: Initialize your variables with uniform initialization.
      * Line 121, 147, 156, 186, 202, 205, 230, 233, 236, 239, 251, 255, 258, 259, 261, 264, 267, 277, 278, 280, 283, 286, 289, 292, 307, 308, 341, 364: Limit your lines to 80 characters in length for better readability on small displays.
      * Line 119, 325: Don't pass 32767 to @std::cin.ignore. Pass @std::numeric_limits<std::streamsize>::max().
      * Magic numbers: 10, 11, 17, 21, 22, 52
      * @drawPlayerCard and @drawDealerCard are duplicates
      * Line 340ff: Use curly brackets if your statement exceeds 1 line

  • Thao Nguyen

    Hello 🙂 This is my implementation of question 6g. I use Mersenne to generate random number, but I put the die and the mersenne objects in global scope, since I don't want to instantiate these objects every time I call shuffleDeck. I'm wondering if there's a better way to handle this.

    Additional comments are appreciated.

    Thanks a lot!

    • Hi!

      * Line 9: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 2: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 3: Limit your lines to 80 characters in length for better readability on small displays. You can move line 6 above line 3 and use @index_t here.

      > I'm wondering if there's a better way to handle this.
      Lesson 4.3 - Static duration variables

  • Jon

    Hi again! For problem #4, if I define the printString function to take a (char *ptr) argument rather than (const char *ptr) like you have in there, my compiler gives me a warning message "ISO C++11 does not allow conversion from string literal to 'char *'", though the program still seems to work fine.

    Why does adding the const make the warning go away? Thanks!

    • Hi Jon!

      String literals are immutable. If @ptr is a char*, you're allowed to modify the argument. But you're passing a string literal, which you're not allowed to modify, hence the warning/error.
      Make sure you followed lesson 0.10 and 0.11. Your code should not compile.

  • Jon

    Hi, thanks again for putting up with all my questions! For the first quiz question, we should not be using an enum class and static casting in this instance? Just wanted to ask since earlier you mentioned to use enum classes over regular enums from c++11 on.

    Also, in my original solution in the countTotalItems function I used dereferencing a pointer and pointer arithmetic, looks like it just unnecessarily complicates things?

    • Hi Jon!

      If your enum is encapsulated inside a class or namespace, you can use a regular enum. If that's not the case, you should use an enum class. I don't know why Alex used a global enum in this quiz.

      > looks like it just unnecessarily complicates things?
      It does. The subscript operator ([]) for arrays is defined as

      In the end, your code is the same as using the subscript operator. But your code is more complicated without gaining anything from it.
      If you need to sum up a list in the future, you can use @std::accumulate

  • Riven

    Thank you for this wonderful tutorial series!
    I really appreciate every unit!

  • Faruk

    Hi, i have written the Blackjack Game and its been very FUN and my code is very different from the result.Mostly because i used structs to represent the player and the dealer.I would very appreciate it if someone could give me some tips, code improvements and feedback.

    • Hi Faruk!

      * Use enum class instead of enum to prevent name collisions
      * Magic numbers: 11, 17, 51, 52
      * Unnecessary assignments to enumerators. Enumerators get their values automatically and no value should be assigned unless necessary.
      * Line 44: Should be constexpr static
      * Use @std::srand, @std::rand instead of @srand, @rand
      * Initialize your variables with uniform initialization
      * Line 79, 104, 117, 153, 288: Enable compiler warnings and fix them. Ignoring them can cause unexpected or undefined behavior at run-time.
      * Structs should not contain functions. This is what classes are for, they're covered later.
      * Line 136: Code should be scalable. Have a look at @std::fill.
      * Your concept of @nullCard is a waste of memory and, without testing, I assume it causes you code to malfunction in certain scenarios. Instead, keep track of the used cards or use @std::vector.
      * Line 169, 182, 192: @card should be a const reference
      * Line 194: Unnecessary comparison to @nullCard.rank.
      * Line 209: Should be else if
      * Names staring with an underscore (_) are reserved for compiler-internal use. Don't start names with an underscore.
      * Line 279: Inconsistent parentheses
      * Line 317: Magic number with no meaning. Read the documentation of @std::basic_istream::ignore's first parameter.

      Keep on posting your solutions to quizzes to avoid getting into bad habits.

  • Alex A

    Hi All,

    really enjoyed this quiz, admit it took me 2 days and having to have a sneak peak at the solution (hadnt considered using a while function with break and return for the hits) to get it finally working. decided to use Mersenne as advised in an earlier lesson and std::swap for my swapping rather than writing it all out. Nas/Alex any pointers?

    • Hi Alex!

      * Don't seed random number generators more than once.
      * Use enum class instead of enum, unless the enum is declared in a class or namespace.
      * Magic number: 52. You have a constant for it.
      * Line 104, 105, 200: Uniform initialization
      * Line 136: Initialize variables to a specific value unless you want to use their default value. Initialize chars to 0, '\0', '\x00' or any other 0 value.
      * Line 203-209: Wrap your code in curly brackets when it exceeds 1 line.

      Looks fine otherwise. Good job!

  • magaji::hussaini

    Hi alex/nascardriver/others
    As of what I understood, an ace is worth 11 if the players first 2 cards are: a card worth value of 10 and an ace so I assigned "1" to RANK_ACE and do a check to see if it was combined with  with a card of value 10 at the start of the game:

    Is it okay?

    • Check if the rank of shDeck[*] is RANK_ACE, instead of checking the value, to make your code easier to understand.

    • magaji::hussaini

      Oh thanks. Also I've changed the whole idea by altering getCardValue() function so that it return "1" for RANK_ACE and leave the test as it. Better right?
      Because by assigning 1 to RANK_ACE , MAX_RANK also get changed and I used MAX_RANK when making the deck.(test in for loop).

  • Sélim

    When I read exercize 6 and 7, it looks like you did the exact thing you taught us in the "How to design you first program"(https://www.learncpp.com/cpp-tutorial/1-10b-how-to-design-your-first-programs/)  step 4 "Breaking hard problems into easy problems", it's nice to see another concrete example with a card game !

  • magaji::hussaini

    I will first download a blackjack game and be playing while I am revising this chapter bcos I really want to complete this amazing and encouraging quiz...see you soon.

  • hnsdgvefeqg

    I decided to play around with a class.

  • Clapfish

    Just another bit of code sharing here... I figured this was an opportunity to practice multiple-file programming again, so this is my solution across multiple files.

    Credit to Itzik for the switch/case code consolidation in the printCard() and getCardValue() functions (I had to steal that)!

    Any feedback much appreciated, especially with regards to where I've gone wrong in the multiple-file design.

    Cheers!

    ---

    main.cpp:

    blackjack.cpp:

    playingCardManip.cpp:

    validation.cpp:

    RNG.cpp:

    playingCards.h:

    blackjack.h:

    playingCardManip.h:

    validation.h:

    RNG.h:

    ---

    • Hi!

      blackjack.cpp
      * Line 45: Should be else if
      * Line 50: Passing native numeric types by reference is slower than passing them by value
      * @cardPtr violates your naming convention for variables
      * @player_score gets increased in line 66 and right after in line 75. I don't know the game, is this correct?
      * Line 96: Inconsistent use of '\n' and @std::endl
      * I like my constants on the very top of the file/function. It makes it easier to find them if you want to modify their values

      RNG.cpp
      * Line 5: Can be constexpr

      General
      * Magic number: 52

      Nice code otherwise, clap clap 🙂

      For future use, have a look at @std::swap and @std::shuffle

      • Clapfish

        Hello!

        Many thanks as always for your rapid and excellent feedback.

        * Magic number: 52
        - Yup, the last remaining one (I hope). I've replaced it (everywhere) with 'deck_size', declared at the top of playingCards.h as follows:

        Would you agree that's a reasonable solution?

        * Line 50: Passing native numeric types by reference is slower than passing them by value
        - Thanks for the tip, I didn't know that references would actually be slower here! Duly amended.

        * @player_score gets increased in line 66 and right after in line 75. I don't know the game, is this correct?
        - Yep, the player gets dealt two cards to start, so line 75 is the second card (and first of the loop)

        * Line 45: Should be else if
        - Thanks, though this makes me wonder, exactly what the difference is in terms of computation/efficiency? Or is it a consistency thing?

        * I like my constants on the very top of the file/function. It makes it easier to find them if you want to modify their values
        - Good point; I was following the convention of 'declare variables as close as you can to where they're first used'. But I suppose that's more suited to non-const variables?

        • > Would you agree that's a reasonable solution? [deck_size]
          Global variables should generally be declared in a singleton class or namespace. With @deck_size being such an important part of the program, your solution is fine too.

          > computation/efficiency? Or is it a consistency thing?
          Since you're returning in the if-bodies, there's no difference between using if vs else-if. If the success of one if's condition excludes a later condition (eg. if @c is 'h' it cannot possibly be 's' two lines after), you should use else-if. This will make your code easier to read/understand and, if the earlier if's body doesn't change control flow (eg. return, break, goto), increase performance.

          > declare variables as close as you can to where they're first used
          That's done to decrease a variable's lifetime. Do constants have a limited lifetime though? Not really, they're always the same so there's no reason to remove and re-create them (Unless you need the memory for other things, this should never be the case for desktop applications). If you're fine with the increased memory usage you can declare constants 'constexpr static' (or 'static constexpr'). If you do that, the constants have infinite lifetime anyway, so you can move them to the top of the function. Note that this is valid for constexpr variables, not regular const, because those can have different values every time they're initialized.

  • Clapfish

    Hi guys,

    I've not quite finished this monster quiz yet, but I've come across something I thought might be worth mentioning.

    Wherever I've had to access my std::array's elements with variable indices (e.g. deck[index]) my 'index' needs to be specifically declared as an unsigned int, otherwise my compiler complains with the following:

    "error: conversion to 'std::array<Card, 52u>::size_type {aka unsigned int}' from 'int' may change the sign of the result [-Werror=sign-conversion]|"

    I figure this is due to my stringent error/warning settings, and after I figured out what was going on it started to make sense. Since, however, these settings are recommended at the beginning of the tutorial, and assuming I'm right, wouldn't it be better to have this reflected in the quiz solutions for consistency/best practice?

    I'd appreciate any thoughts on this!

    Example code:

    • Alex

      Yes, the quizzes haven't been updated since the recommendation to use the stronger warnings was instituted. I'm in the process of updating everything but it's very slow going.

      Technically, std::array's operator[] takes an object of type std::array::size_type. So the best solution would be:

      Which I find pretty gross, as it's significantly less readable. A little better:

      That's probably the best mix of correctness and readability.

      • Clapfish

        Hi Alex!

        Yes, I can understand that, there's a lot of material to go through! Thanks again for your efforts, it really is such a valuable resource.

        And thank you for the information here; It looks like a good opportunity to put type aliases into practice!

        Another two cents of mine on this chapter in general is that I found it, for the first time, a bit dragging; I think due to the lack of quizzes along the way, which also meant that I felt relatively unequipped for the comprehensive quiz vs previous chapters, and found myself looking back at the articles a lot scratching my head over simple declaration syntax. Perhaps more intermediate quizzes in this chapter would be good in the future.

        Thanks again and all the best!

        • Alex

          Yes, chapter 6 is in desperate need of some restructuring. It's overly long and covers too many topics (arrays and pointers and references!). So first thing to do is break it apart into more digestible chunks. I'm working on a plan for that, but it's slow going.

          • magaji::hussaini

            Hi Alex,
            I am having troubles because I am applying somethings that I learned here in my C learning tutorials but I have to be tracing errors back and correcting them since most of CPP syntax are not valid C syntax(e.g uniform initialization, struct initilization, ..., etc) So I am planning to be using C syntax since most of it are valid in CPP. Please what do you suggest?

      • Another way that can be used universally and without using/typedef, more typing though

        And what I guess is the best solution, iterators and auto

        • Clapfish

          Hi nascardriver!

          I liked the look of your guess at a best solution here, but I got the following error when I tried implementing it:

          error: invalid conversion from 'Card*' to 'std::array<Card, 52u>::size_type {aka unsigned int}' [-fpermissive]|

          Code:

          Any other thoughts?

          • Hey there!

            (Iterators are talked about in chapter 16)
            For now, think of iterators as pointers. @it is a @Card*. @deck::operator[] expects a @size_type, you're giving it a Card*, which doesn't work. Try

            • Clapfish

              Ah it all makes much more sense now! Thanks!

              New code:

              ...which made me wonder about the performance overheads of 'auto' vs what I had before:

              ...if you don't mind me asking yet another question?

              • @auto is expanded at compile-time, the run-time in unaffected.
                The question is if there's a performance difference between using iterators and using @std::array:at. There most likely is, but it's probably implementation specific. If you want to, you can measure the time (lesson 8.16). But don't assume that the results you get apply to every compiler.

  • Itzik

    Hi!
    I'll be glad to read your comments about this solution to the blackjack game.
    thx!