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.
Reminder: Only seed your random number generator once.

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

701 comments to 6.x — Chapter 6 comprehensive quiz

  • Minh Quan

    Hi Alex and nascardriver! I'm not sure I can understand correctly how to the code working. Now I try write again this code follow  "For statement" instead of "For each loop", but I don't know. Please help me write it again. I'd like to compare 02 kind of "loop" to more understand. Thank you very much!

    --------------------------------------------------------
    void printDeck(const std::array<Card, 52> &deck)
    {
        for (const auto &card : deck)
        {
            printCard(card);
            std::cout << ' ';
        }

        std::cout << '\n';
    }

  • Jörg

    Hi, i've done it like that:

    Is this okay like that?

    • Hi Joerg!

      Not OK:
      * Line 146: You're assigning a value to @playerTotalScore twice in the same statement
      * @getCardValue doesn't always return a value
      * @playBlackjack doesn't always return a value
      Enable and read compiler warnings.

      OK but can be improved:
      * Use enum class to avoid name colissions
      * Use the auto-formatting feature of your IDE to avoid inconsistent formatting
      * Magic numbers
      * @swapCard can be replaced with @std::swap
      * Unnecessary include (to a bad header (Windows.h))
      * Initialize your variables with uniform initialization
      * Line 139: Inconsistent placement of "const"
      * Line 219-224: Enclose multi-line statements in curly brackets

  • And the final blackjack game:

    The @closeGame() function was necessary because this was set as the release version so I could publish it to my desktop and play ad lib.

  • Hi Alex,

    My completed part 6produces a nicely shuffled deck:

    before I move on to part 7 and turn it into a blackjack (Pontoon?) game is there anywhere I should improve it?

    • Hi Nigel!

      * Sort your includes (alphabetically) to make it easier to see what has been included already.
      * Use enum class
      * You're using copy initialization in some places
      * Magic number: 52. There will be more in further quizzes. Declare constants.
      * @swapCard can be replaces with @std::swap, since that's all it's doing.
      * Use @std::srand and @std::rand instead of the global variants
      * Line 133ff: When the statement exceeds 1 line, enclose it in curly brackets

      Rest looks good.

  • The fourth one was easy:

    but if you forget that you're using a pointer in the while statement it still runs but with a stack overflow....

    • Hi Nigel!

      There's nothing wrong with your program, if you wanted to you could combine line 12 and 13.

      > if you forget that you're using a pointer in the while statement it still runs but with a stack overflow
      Because (string != '\0') will yield true until @string has reached @std::numeric_limits<std::uintptr_t>::max(), the next call to ++string causing @string to overflow back to 0. The end of the string is reached far before the highest pointer value is reached. Accessing memory after the string's end causes undefined behavior.

  • for number 3.

  • solution 2

  • Hi Alex,

    This is my version of the 1st problem.  Note that stdafx.h has now been replaced by pch.h in the new versions of Visual Studio and stdafx.h is no longer recognised.  This could cause confusion for anyone new to your lessons who has version 5.18 or above of VS2017, esp as you state several times that stdafx.h should be included if running on Windows....

    • Hi Nigel!

      * Unused includes: cstdlib, cstdio, array, string, algorithm. Those slow down compilation
      * Same name style for types, constants, functions, variables. This will get confusing
      * Use enum class

      Future:
      You can use @std::accumulate to sum up arrays:

      References
      std::accumulate - http://www.cplusplus.com/reference/numeric/accumulate/

  • saka

    Question #4:

    I tried to solve this using a for loop in the printString function but it doesn't seem to like the condition expression being a comparison between a dereferenced pointer and a char literal.

    See below:

    This failed to print anything to the console regardless of what letter I subbed in for '\0'.

  • Eric

    Great tutorial! I got my blackjack game to work. Any suggestions for ways to improve it would be appreciated.

    "learn_cpp_ch6_quiz_7.cpp"

    "play_blackjack.cpp"

    "play_blackjack.h"

    "playing_cards.cpp"

    "playing_cards.h"

    • Hi Eric!

      Here's what I found on the first look, without checking the logic:

      * Edit your posts instead of deleting and re-posting them. The syntax highlighting works after a page refresh.
      * There are still some places that don't use uniform initialization
      * Don't use @exit. If you do, use @std::exit.
      * @playAgain should return a bool
      * Read the documentation of the first parameter of @std::basic_istream::ignore
      * Your game runs recursive, it will stop working once memory is filled up. Use a loop instead.
      * Line 33: Only use strings for user interaction. Don't use them for internal things, they're slow.
      * @getCardSuit and @getCardRank: Missing return statement. Enable compiler warnings and read them. What you did in @getCardValue is better.
      * playing_cards.cpp Line 102, 105: Magic numbers. Use deckToSuffle.size()
      * playing_cards.cpp Line 114: You have a constant for that

  • Minh Quan

    Hi Alex. I want to assign value of rank[] and suit[] to the deck[].rank and the deck[].suit. However, the type of deck is "Card", the type of rank[] and suit[] are std::string. Can you help me build the array deck[]. Thanks you so much sirs.
    --------------------------------------------

    #include <iostream>
    #include <string>
    #include <array>

    enum CardRank
    {
        Rank_2,
        Rank_3,
        Rank_4,
        Rank_5,
        Rank_6,
        Rank_7,
        Rank_8,
        Rank_9,
        Rank_10,
        Rank_Jack,
        Rank_Queen,
        Rank_King,
        Rank_Ace,
        MaxRank
    };

    enum CardSuit
    {
        Suit_Club,
        Suit_Diamonds,
        Suit_Heart,
        Suit_Spades,
        MaxSuit
    };

    struct Card
    {
        CardRank rank;
        CardSuit suit;
    };

    std::string rank [MaxRank]  {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
    std::string suit [MaxSuit] {"C","D","H","S"};

    void printCard (std::string *rank, std::string *suit)
    {
        
        
        for (int i = 0; i < MaxRank; ++i)
        {
            for (int j = 0; j < MaxSuit; ++j)
            {
              
                std::cout << rank[i] << suit[j] << " ";
            }
            std::cout << std::endl;
        }
    }

    void printdeck (std::array <Card,52> &deck)
    {
        int m = 0;
        for (int i = 0; i < MaxRank; ++i)
        {
            for (int j = 0; j < MaxSuit; ++j)
            {
                
                deck [m].rank = rank[i];
                deck [m].suit = suit [i];
                ++m;
            }
        }
        
        for (int i = 0; i < 52; ++i)
        {
            std::cout << deck[i] << " ";
            if (i = 13)
            std::cout << std::endl;
        }
    }

    int main()
    {
        
        std::array <Card,52> deck;
        
        printCard(rank, suit);
        
        printdeck (deck);
      
        
        
        
        return 0;
    }

  • Minh Quan

    Hi Alex, please help me explain difference between:
    deck[card].suit and deck.suit[card] when creat an array of struct elements in 6d.

    • Alex

      deck is a 52-element array of cards, so deck[card] gives you a specific Card object, and .suit accesses the suit member of that Card.
      deck.suit is meaningless, as there is no suit member of the deck array.

  • Minh Quan

    Hi Alex. I make the exercise 6c without using "switch". I make two array to print all cards. How do think this method suitable to perform the next excercise.
    ----------------------------------------------------------------
    #include <iostream>
    #include <string>

    enum CardRank
    {
        Rank_2,
        Rank_3,
        Rank_4,
        Rank_5,
        Rank_6,
        Rank_7,
        Rank_8,
        Rank_9,
        Rank_10,
        Rank_Jack,
        Rank_Queen,
        Rank_King,
        Rank_Ace,
        MaxRank
    };

    enum CardSuit
    {
        Suit_Club,
        Suit_Diamonds,
        Suit_Heart,
        Suit_Spades,
        MaxSuit
    };

    struct Card
    {
        CardRank rank;
        CardSuit suit;
    };
    Card card;

    void printCard (std::string *rank, std::string *suit)
    {
        
        for (int i = 0; i < MaxRank; ++i)
        {
            for (int j = 0; j < MaxSuit; ++j)
            {
                std::cout << rank [i] << suit [j] << " ";
            }
            std::cout << std::endl;
        }
    }

    int main()
    {
        std::string rank [MaxRank]  {"2","3","4","5","6","7","8","9","10","J","Q","K","A"};
        std::string suit [MaxSuit] {"C","D","H","S"};
        printCard (rank, suit);
        
        return 0;
    }

  • gabriel

    in question 2, when getting name input for the students you used std::cin instead of std::getline,
    when i tried the getline the input was automatically set to " " and i couldnt input anything to the name.
    and when i used the std::cin it worked ok. can you please explain why?

    the code ive written so far is:

    • Hi Gabriel!

      * Line 4, 9, 16: Initialize your variables with uniform initialization

      After @std::cin extracted the grade, a line feed ('\n') is still in the input stream.
      @std::getline extracts characters until it finds a line feed, which now is the first character in the stream, so it stops extracting right away.
      You need to cleat the input stream before calling @std::getline by calling

  • Vladyslav

    Hello, I've finished question 7b and I was wondering if I can get any suggestions on how to improve my code. As always, thanks for the flawless tutorials. :)

    • Hi Vladyslav!

      * Enable warning and read them, there are lots, I'm not going to list them here.
      * Line 7: Wrap them in a namespace, you could also get rid of some more magic numbers.
      * Initialize your variables with uniform initialization
      * Use ++prefix unless you need postfix++, same for --
      * Line 112-114: Those variables should be static, as they're never changing.
      * Line 112, 122: Use @std::size, or, preferrably, @deck.size()
      * @random_card should return a reference
      * Line 124: @randCard should be a reference. When it's not, you're allowing duplicate cards.
      * @fix_dealer_aces is a duplicate of @fix_player_aces
      * Line 167, 210, 230: Duplicate comparison of @choice to 's' and 'h'
      * Don't use @system. If you still want to use system (I can't think of a reason why should would ever do so), use @std::system.
      * Sort your includes

      • Vladyslav

        Hey, nascardriver. Thank you a lot for your help. I've fixed all of my errors except for the one that refers to @random_card. Could you, please, explain how can I return a reference in this case? Again, thank you for the help. :)
        I've tried something like this:

        • That's it!

          If you have a look at the documentation of @std::array::operator[], you can see that it returns a reference, so all you have to do is change "Card" to "Card &" to forward that reference.
          Lines 5-7, 14 still use none, copy-, or direct initialization, change them to uniform initialization.
          I'm not so sure about declaring lines 4-7 static anymore, but keep them like that. I might make a difference suggestion in a future post.
          Line 14: @deck.size() returns an @std::size_t (unsigned int), a cast to @int would be nice.

          References
          std::array::operator[] - https://en.cppreference.com/w/cpp/container/array/operator_at

  • Matt

    Hello, I've just finished making the blackjack game (I haven't done 7a or 7b extra credit yet)

    I was wondering if I can get some feedback on my code, whether it be things I've done wrong, tips on improving the efficiency/readability/structure etc.

    A few points:
    It's said that functions should not be too long, and only performs one function - if a function starts to perform too many things, it should be split to multiple functions. I've tried to keep this in mind, would you say I've done it correctly? Or do I have too many functions? In some ways, I feel like it can make the code more 'spaghettified' in so much that the control flow jumps all over the place. But if it makes the code more modular and testable than I guess it's a worthy sacrifice?

    I had to use a reference to a pointer in the dealCards() and the hitOrStand() functions to ensure the pointer location didn't reset once the functions went out of scope, was this the best solution?

    In the dealCards() function, I made all the parameters references, even the integer score variables, so I could permanently alter more than 1 variable, is that OK?

    Anyway, here's my code: