Search

7.x — Chapter 7 summary and quiz

Quick review

The specific sequence of statements that the CPU executes in a program is called the program’s execution path. A straight-line program takes the same path every time it is run.

Control flow statements (also called Flow control statements) allow the programmer to change the normal path of execution. When a control flow statement causes the program to begin executing some non-sequential instruction sequence, this is called a branch.

A conditional statement is a statement that specifies whether some associated statement(s) should be executed or not.

If statements allow us to execute an associated statement based on whether some condition is true. Else statements execute if the associated condition is false. You can chain together multiple if and else statements.

A dangling else occurs when it is ambiguous which if statement an else statement is connected to. Dangling else statements are matched up with the last unmatched if statement in the same block. Thus, we trivially avoid dangling else statements by ensuring the body of an if statement is placed in a block.

A null statement is a statement that consists of just a semicolon. It does nothing, and is used when the language requires a statement to exist but the programmer does not need the statement to do anything.

Switch statements provide a cleaner and faster method for selecting between a number of matching items. Switch statements only work with integral types. Case labels are used to identify the values for the evaluated condition to match. The statements beneath a default label are executed if no matching case label can be found.

When execution flows from a statement underneath a label into statements underneath a subsequent label, this is called fallthrough. A break statement (or return statement) can be used to prevent fallthrough. The [[fallthrough]] attribute can be used to document intentional fallthrough.

Goto statements allow the program to jump to somewhere else in the code, either forward or backwards. These should generally be avoided, as they can create spaghetti code, which occurs when a program has a path of execution that resembles a bowl of spaghetti.

While loops allow the program to loop as long as a given condition evaluates to true. The condition is evaluated before the loop executes.

An infinite loop is a loop that has a condition that always evaluates to true. These loops will loop forever unless another control flow statement is used to stop them.

A loop variable (also called a counter) is an integer variable used to count how many times a loop has executed. Each execution of a loop is called an iteration.

Do while loops are similar to while loops, but the condition is evaluated after the loop executes instead of before.

For loops are the most used loop, and are ideal when you need to loop a specific number of times. An off-by-one error occurs when the loop iterates one too many or one too few times.

Break statements allow us to break out of a switch, while, do while, or for loop (also range-based for loops, which we haven’t covered yet). Continue statements allow us to move immediately to the next loop iteration.

Halts allow us to terminate our program. Normal termination means the program has exited in an expected way (and the status code will indicate whether it succeeded or not). std::exit() is called at the end of main, or it can be called explicitly to terminate the program. It does some cleanup, but does not cleanup any local variables, or unwind the call stack.

Abnormal termination occurs when the program encountered some kind of unexpected error and had to be shut down. std::abort can be called for an abnormal termination.

Scope creep occurs when a project’s capabilities grow beyond what was originally intended at the start of the project or project phase.

Software verification is the process of testing whether or not the software works as expected in all cases. A unit test is a test designed to test a small portion of the code (typically a function or call) in isolation to ensure a particular behavior occurs as expected. Unit test frameworks can help you organize your unit tests. Integration testing tests the integration of a bunch of units together to ensure they work properly.

Code coverage refers to how much of the source code is executed while testing. Statement coverage refers to the percentage of statements in a program that have been exercised by testing routines. Branch coverage refers to the percentage of branches that have been executed by testing routines. Loop coverage (also called the 0, 1, 2 test) means that if you have a loop, you should ensure it works properly when it iterates 0 times, 1 time, and 2 times.

The happy path is the path of execution that occurs when there are no errors encountered. A sad path is one where an error or failure state occurs. A non-recoverable error (also called a fatal error) is an error that is severe enough that the program can’t continue running. A program that handles error cases well is robust.

A buffer is a piece of memory set aside for storing data temporarily while it is moved from one place to another.

The process of checking whether user input conforms to what the program is expecting is called input validation.

std::cerr is an output stream (like std::cout) designed to be used for error messages.

A precondition is any condition that must always be true prior to the execution of some segment of code. An invariant is a condition that must be true while some component is executing. A postcondition is any condition that must always be true after the execution of some code.

An assertion is an expression that will be true unless there is a bug in the program. In C++, runtime assertions are typically implemented using the assert preprocessor macro. Assertions are usually turned off in non-debug code. A static_assert is an assertion that is evaluated at compile-time.

Assertions should be used to document cases that should be logically impossible. Error handling should be used to handle cases that are possible.

Quiz time

Warning: The quizzes start getting harder from this point forward, but you can do it. Let’s rock these quizzes!

Question #1

In the chapter 4 comprehensive quiz, we wrote a program to simulate a ball falling off of a tower. Because we didn’t have loops yet, the ball could only fall for 5 seconds.

Take the program below and modify it so that the ball falls for as many seconds as needed until it reaches the ground.

In constants.h:

In your main code file:

Show Solution

Question #2

A prime number is a natural number greater than 1 that is evenly divisible (with no remainder) only by 1 and itself. Complete the following program by writing the isPrime() function using a for loop.

Show Solution


8.1 -- Using a language reference
Index
7.17 -- Assert and static_assert

819 comments to 7.x — Chapter 7 summary and quiz

  • nav

  • nav

  • kevin

    Hi sir, Under 3-d)
    Are we making the lookup table "static" to make sure the reference dosen't go out of scope when the function returns ?
    Also in line 3 we have static_cast "Type::max_types" to std::size_t. Is it because we use std::array or because it is a user defined type?
    And again in line 10, we static_cast "type" to std::size_t . Why is that ?

  • Berrie

    This is the most efficient answer I can come up with for question two without using lists to only check for prime divisors. I'm not sure if it can be improved on.

    • Tex

      You can halve the number of iterations by starting the loop at 3 and incrementing by 2 each time, then checking the parity with the last return statement

  • Navigator

    Question 1

    Question 2

  • Jder

    Hey guys, this is what I did for main.cpp from Q1:

    Edit: also changed gravity to an inline constexpr and used uniform initialization (constants.h).

  • A

    for the isPrime, I believe that setting the conditions right in the for loop simplifies the function. No testing for x = 1 or 2, we know they are prime numbers.
    So if  x% is 0 for a number between 2 to x, x is not prime.

  • Tomek

    I know my function is very long

    But I tried to limit the number of cases tested in the for-loop (not testing even numbers)

  • Waldo Lemmer

    Here's my solution for Question #2:

    Three things are required for a number to be prime:
    1. It must be a whole number
    2. It must be larger than 1
    3. It must only be divisible by 1 itself

    1. The function takes an `int` as an argument, so it's always a whole number.

    2. The first `if statement` test this. If the test fails, the function returns `false`.

    3. We know the number is divisible by 1 (because it's an int), and all numbers are divisible by themselves (except 0), so we only need to test 2 - (x - 1). The `for loop` loops through all whole numbers from 2 to x - 1, and checks whether x is divisible by any of those. If x is, it's not prime, so the function returns `false`.

    If all these tests pass, x is prime, so `return true` gets executed.

    I think the flow makes more sense, and it's easier to comprehend. Yours is pretty short, but mine can be shortened by 8 lines if I remove the unneccesary brackets and newlines. That would make it less readable, though, so I'm keeping it like this.

  • Waldo Lemmer

    It's 22:20 right now, and I'm starting to realize why programmers drink so much coffee. It's almost impossible to concentrate, so I'll get back to this lesson tomorrow xD

    Two mistakes I've spotted:

    > Danging else statements are matched up with the last unmatched if statement in the same block.
    "Danging" should be "dangling" lol

    > Goto statement allow the program
    "statement" should be "statements"

  • asd

    Thank you for all the lessons!

    Here's my answers to the questions. Any feedbacks would be appreciated. Thanks for your time!
    Q1:

    Q2:

  • Patrick

    Small change - the ball falling off the tower question is now given in chapter 4, not 2.

  • Brian

  • Anon

  • Andrei

    Behold, my mess of a code

  • yeokaiwei

    I hope this is acceptable.

    Outstanding Issues
    1. Magic Number.Still using a magic number for the random range 1-100. Number of tries was solved using ceil(log2 (N))
    2. Error Handling. Inputs like "123a" still work for usr
    3. Error Handling. playAgain() repeats it depending on the number of characters you input. E.g. Instead of Y or N, you input "12". You get 2 messages asking you if you want to play again.

    • nascardriver

      1 and 100 are only used once in the solution, and in a place with a known meaning (min and max). Say we wanted to also print 1 and 100 and repeated them in a string. Then they'd be magic.

      - `createRandom()` isn't random. Don't re-seed random number generators.
      - Line 60 is always true. Avoid the duplicate comparison by using a `while (true)` loop.
      - Initialize variables with list initialization.
      - math.h is a C header, C++ has cmath which defines its contents in the `std` namespace.
      - ++prefix
      - `ceil(log2(answer))` is magic. Line 70 and 75 should always use the same value, so don't calculate it twice. Calculate it once and store it in a variable.

      By calculating an exposing the number of attempts, you're giving the user information that makes the game easier. If your game says I have 7 attempts, I know the solution must be in the range [65, 100], because it would have given me less attempts if the solution was <=64. I guess your intention was to calculate `log2(max)`, which would make the game always winnable without exposing the solution.

      • yeokaiwei

        I'll have to rewrite this.

        I think I'm a little confused over the concept of Magic Numbers, as I'm interpreting "Magic Numbers" to mean an arbitrary constant in the code.

        There is a hint that says
        "* Avoid magic numbers by defining constants for the minimum/maximum random range and the number of guesses."

        I don't understand the concept that is being conveyed here.

        • nascardriver

          That is confusing indeed. The definition of "magic number" depends on who you ask, I think I have a somewhat odd definition myself.
          `std::uniform_int_distribution` is something every programmer knows the parameters of, so its arguments aren't arbitrary, they're min and max. There's no point in storing every value in a variable before calling a function.

          If you use the same thing more than once but don't name it, it's magic.
          A literal value whose meaning or origin is unknown, is magic. You could argue that the origin of 1 and 100 are unknown, but naming them min and max wouldn't change this.

          Anyway, I've updated the lesson not to say which values constants should be created for. Some people like to print 1 and 100, which caused them to use magic numbers. But because the quiz didn't do this, the hint was confusing.

          • yeokaiwei

            If ceiling(log(n)) is a magic number, I'm unsure of how to solve it.

            I know numbers like 32767 are bad because that is a magic number in error handling. From S4.4b "What's that 32767 magic number in your code?" and 5.10.

            But now, I'm not sure what is the definition of Magic Number used here?

            Is there a definition with examples you could add to www.learncpp.com?

            • nascardriver

              As I said in my previous comment, calculate `ceil(log2(n))` once and store it in a variable. Use that variable in the 2 places you're now doing the calculation at.

              The definition and examples in lesson 4.12 are accurate enough I think. If you're uncertain if something is a magic value, store it in a variable to be sure.

              At this point I'd also like to point out that I am not Alex, which is why the lessons can say something different than I do. Alex is named Alex and has a cat avatar.

  • yeokaiwei

    * Avoid magic numbers by defining constants for the minimum/maximum random range and the number of guesses.

    In the solution, aren't the 1 and 100 magic numbers?

    It's kind of confusing.

    I believe you want us to use log2(N) but it's not mentioned in the solution.

  • Oran

    Hi, thank you for the great tutorials !
    Any way I could improve the code? and do you have any tips on how to stop using if statements as a crutch?

    • nascardriver

      - Avoid recursion (A function calling itself), your program will crash after some iterations. Use loops instead.
      - Don't re-seed random number generators `getRandomNumber` isn't random.
      - Initialize variables instead of assigning to them.
      - Magic number/string: 7/8
      - Line 57 is always true.
      - Don't mix booleans and integers
      - The loop in `main` can be `while (gameFunc());`

  • Justin

    Hi Nascardriver! Here is the solution I came up with for Hi-Lo. I would greatly appreciate critical feedback if you have time!

  • James

    Does it reduce the reusability or readability of the code to give calculateAndPrintHeight() a double return type (the current height) instead of void, in order to avoid calling calculateHeight() again?

    calculateAndPrintHeight() being modified to:

    and allowing me to do something like this:

    or this:

  • Cameron

    This is how i chose to fix the first code. I only altered the calculateAndPrintHeight function. Was this also a good approach?

    • nascardriver

      You decreased reusability by removing the old `calculateAndPrintHeight`. If you don't think you'll ever need to print the current height, that's ok.

  • knight

    Hi, nascardriver, I modified the first answer code likes this:

    It is working, but is it ok for programing?

    • nascardriver

      It's ok but not great, because now you have to repeat the call of `calculateHeight` (And `printHeight` if you want to print the initial height). The more complicated your loop gets the more code you have to repeat. That's not maintainable.

  • Tony

    Hi again. This is my solution for the last question. Could you give me your opinion and what could be done better? Thanks for your time. It's always appreciated!

  • Tony

    Hi again, nascardriver and Alex!

    For Question 1 I came up with a different solution and used a for loop. Do you have any feedback?

    Thanks again :)

    • nascardriver

      You're calculating the height once in line 8 and then again in line 9. You can fix this by having `calculateAndPrintHeight` return whether or not the ball is on the ground.

  • Kacper

    Hello,
    I'm just a fanboy of your tutorials and I would like to know if you could check my code. This is what came up with for the hi-lo game
    hi_lo.h

    hi_lo.cpp

    It was hella fun doing that, hope more quizzes like this will come in the future.
    Have a great day and good luck doing more tutorials!

  • So, after a few hours of fighting with validating y/n input, I finally managed to finish my game, and I'm not gonna lie, I'm proud of myself. And, of course, I would be very thankful for every feedback on my code;)

  • Amir

    help please with showing me the weak points

    main.cpp

    hi-lo.h

    hi-lo.cpp

    • nascardriver

      - Include as few files as possible in headers. "hi-lo.h" doesn't need iostream.
      - `num` should be declared inside of the loop to limit its scope.
      - Use ++prefix unless you need postfix++. postfix++ is slower.
      - Inconsistent formatting. Use an auto-formatter.
      - Line 60: Use your constants.
      - The loop in `playSingle` should be a do-while loop, because the first iteration is unconditional.

  • Luxary

    Making the Hi-Lo game was an absolute blast. Here's what I came up with, I sprinkled some extra stuff in it. If you got any feedback or nitpicking I'd be glad to hear it :) and again, thanks a lot for all of this!

    • nascardriver

      - By having `getYesNo` return a `char`, you're forcing the caller to use magic values ('y' and 'n'). This can be solved by returning a `bool`.
      - If an if's or loop's body exceeds 1 line, use curly braces to make it easier to read.
      - Only use the conditional operator if you're using the return value. Otherwise us an if-statement. If you want to use the conditional operator, limit it to the parts that differ
      [code]
      std::cout << "Your guess is too " << ((user_guess > match_pick) ? "high" : "low") << "\n."; - `doRematch` is unnecessary, you can call `getYesNo` in the loop's condition. - Good constants/enum. Because you're asking for nitpick, I'll happily provide - Non-static global variables - Magic number: 32767 - Weird formatting for operator != (Missing space before operator) - Line 92-98 can be moved into a function. - Invisible space in line 89 "\n \n". Looks good overall :)

  • John Lavik

    Thanks nascardriver for these wonderful tutorials!

    I have a question about validating input.  I have used code from the lesson to validate user input (in HiLo game.)  But a type of error still gets through (and I haven't been able to figure out how to trap it.)  If my user enters "6y" when he meant "67", the extraction succeeds, but only extracts the "6" and assigns that to the int variable.  How can I catch that error and warn the player of the mistake?

    Here is part of my code:

    • nascardriver

      You can use `std::cin.peek()`. Have a look at Tim's solution to this problem https://www.learncpp.com/cpp-tutorial/5-10-stdcin-extraction-and-dealing-with-invalid-text-input/comment-page-3/#comment-465638

Leave a Comment

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