Tuesday 28 May 2024

Learn C++ by Example: chapter 3

I promised to share some details about my latest book "Learn C++ by Example", and gave an overview of the first two chapters last time.


You can buy my book directly here: http://mng.bz/AdAQ - or just go look at the table of contents. You can also buy it from Amazon: https://amzn.to/4dMJ0aG

Let's look at chapter 3. It's simply called "Input of numbers and strings", but as with each chapter, covers more besides. Since the first chapter displayed numbers, it seems sensible to think about inputs, and this will give us a way to build a small number guessing game.

The chapter starts with a hard coded number, returned by a function so we can swap it to something more interesting later:

unsigned some_const_number()
{
    return 42;
}

Now, using the iostream header, we can input a number from cin using the stream extraction operator>>:

unsigned number;
std::cin >> number;

That's enough to build a simple, but boring number guessing game. 

We ought to deal with invalid input, including negative numbers. The book shows how to write a small function, checking a result is valid and returns the number as an optional int. The optional type was introduced in C++17, and maybe holds a value. This allows us to loop, giving the player however many goes they want, treating empty or invalid input as quitting.

Guessing a fixed number isn't much fun, so we also learn how to make a random number using C++11's random library. We need an engine and a distribution. This may be unfamiliar is you are used to libraries that you just call to get a number. However, it's a powerful, giving you flexibility. 

The engine provides numbers in a range, and people often use a Mersenne Twister, such as std::mt19937. We can seed the engine, and random_device is a good way to do this - it returns a number based on the state of the system so will tend to be very unpredictable. We pass the engine to the distribution's operator() to get the next random number. The distribution smears, stretches or distributes the numbers produces by the engine so that the output is distributed as required. For example, for a number in a range, with each possible number being equally likely, we can use the uniform_int_distribution picking anything from 1 to 100 inclusive:

int some_random_number()
{
    std::random_device rd;
    std::mt19937 engine(rd());
    std::uniform_int_distribution<int> dist(1, 100);
    return dist(engine);
}

If you want more details, I talked about this at Meeting C++ in 20203 (and other conferences). Later on, we try prime numbers for extra practice.

At this point though, we can build a better game, but it might be frustrating. If we pass a function to the game, detecting if a number is too big or small we can provide clues. We use lambdas to do this, for example given a number and a guess:

auto message = [](int number, int guess) {
    return std::format("Your guess was too {}\n",
        (guess < number ? "small" : "big"));
};

The lambda is an anonymous function, with [] potentially populated with variables to capture, optionally inputs in (), and the body in {}. Yes, there's a std::format in there too! The previous chapter introduced these. Practice is good.

We also explore std::function, noting a slight inefficiency if we copy a lambda to a std::function. We also  consider testing, which can be a challenge when code uses randomness, and we discover using constexpr and static_assert on the way.

Starting with a very simple idea - write a guess the number game gives the opportunity to learn a lot about C++. Give it a go. Some questions for you:
  • What sort of clues would you provide?
  • Could you change it easily to guess one digit at a time?
  • Or report which are correct? 

No comments:

Post a Comment