简体   繁体   中英

Confusion about correct design of a program C++

I made a small program that generates primes and lets the user check a number and see if it's a prime or not. Problem is, I'm not sure how to properly design it. This is the program:

#include <iostream>
#include <vector>

typedef unsigned long long bigint;

std::vector<bool> sieve(size_t size)
{   
    std::vector<bool> primelist(size);

    primelist[0] = false;
    primelist[1] = false;

    for (bigint i = 2; i < size; ++i) { primelist[i] = true; }

    for (bigint i = 2; i * i < size; ++i)
    {
        if (primelist[i])
        {
            for (bigint j = i; j * i < size; ++j)
                primelist[i*j] = false;
        }
    }

    return primelist;
}

int main()
{
    bigint range;
    bigint number;
    std::vector<bool> primes;

    std::cout << "Enter range: " << std::endl;
    std::cin >> range;

    primes = sieve(range);

    while (1)
    {
        std::cout << "Enter number to check: " << std::endl;
        std::cin >> number;

        if (primes[number])
            std::cout << "Prime" << std::endl;

        else
            std::cout << "Not prime" << std::endl;
    }

    return 0;
}

The basic flow I want to achieve is: Input range, /handle input/, input number to check, /handle input/

I also want to give the user an option to change the range at any given time, by writing a command like "change range number "

I have a few problems with this:

I want the program to be under control if the user inputs a range bigger than unsigned long long, and if the user basically exceeds any limit(like for example if the range he input was 100 then if he checks for 101) an exception will be caught. I know this needs to be implemented using try/catch/throw, but I have no idea how to do that while keeping the option to change the range and without making my code spaghetti code.

Also, I want the errors to be of enum type(I read that enums are good for exceptions), something like

enum errors
{
    OUT_OF_RANGE = 1,    //Out of the range specified by the user
    INCORRECT_VALUE,    //If user input "one" instead of 1
    RANGE_SIGNED,     //If user inputs a signed value for range
    NUM_LIMITS        //Number exceeds unsigned long long
};

I have no idea how to use exception handling, not to mention using it with enums. How the hell do I keep this program safe and running, while keeping away from spaghetti code?

I am extremely confused. If someone could help me design this program correctly and maintain readability and efficiency, it will really improve my future program designs.

Thanks for reading!

You asked a lot.

You want to validate user input. Users should not be able to enter huge numbers, non-integers, and so on.

I'm going to start off by answering that this is absolutely not a scenario that exceptions should be used for. Exceptions are used to handle exceptional circumstances. These are ones you can't anticipate or really deal with.

A user enters a number that's too big? You can handle that. Tell them that their number is too big, please enter a number between 1 and X.

A user enters the word apple? You can handle that. Tell them that they can only enter integers.

One way of doing this would be to make a ValidateInput function. You can have it return a number (or an enum, they're basically the same thing) to tell you whether there was an error.

In order to do the validation, you will most likely have to receive input as an std::string and then validate it before turning it into a number. Getting input as an unsigned int or similar integral type doesn't really allow you to check for errors.

This adds a bit of work, since you need to manually validate the input manually. There are libraries with functions to help with this, such as boost::lexical_cast , but that's probably too much for you right now.

Below is some very basic psuedo code to illustrate what I mean. It's only meant to give you an idea of what to do, it won't compile or do the work for you. You could extend it further by making a generic function that returns a message based on an error code and so on.

enum error_code {
  SUCCESS,          // No error
  OUT_OF_RANGE,     // Out of the range specified by the user
  INCORRECT_VALUE,  // If user input "one" instead of 1
  RANGE_SIGNED,     // If user inputs a signed value for range
  NUM_LIMITS        // Number exceeds unsigned long long
};

// This function will check if the input is valid.
// If it's not valid, it will return an error code to explain why it's invalid.
error_code ValidateInput(const std::string& input) {
  // Check if input is too large for an unsigned long long
  if (InputIsTooLarge)
    return NUM_LIMITS;
  // Check if input is negative
  if (InputIsNegative)
    return RANGE_SIGNED;
  // Check if input is not an integer
  if (InputIsNotInteger)
    return INCORRECT_VALUE;
  // If we make it here, no problems were found, input is okay.
  return SUCCESS;
}

unsigned long long GetInput() {
  // Get the user's input
  std::string input;
  std::cin >> input;

  // Check if the input is valid
  error_code inputError = ValidateInput(input);

  // If input is not valid, explain the problem to the user.
  if (inputError != SUCCESS) {
    if (inputError == NUM_LIMITS) {
      std::cout << "That number is too big, please enter a number between " 
        "1 and X." << std::endl;
    }
    else if (inputError == RANGE_SIGNED) {
      std::cout << "Please enter a positive number." << std::endl;
    }
    else if (inputError == INCORRECT_VALUE) {
      std::cout << "Please enter an integer." << std::endl;
    }
    else {
      std::cout << "Invalid input, please try again." << std::endl;
    }

    // Ask for input again
    return GetInput();
  }
  // If ValidateInput returned SUCCESS, the input is okay.
  // We can turn it into an integer and return it.
  else {
    return TurnStringIntoBigInt(input);
  }
}

int main() {
  // Get the input from the user
  unsigned long long number = GetInput();

  // Do something with the input
}

I like Dauphic's answer, particularly because it illustrates breaking down the problem into bits and solving them individually. I would, however, do GetInput a bit differently:

unsigned long long GetInput() {
  // Get the user's input
  std::string input;

  error_code inputError;
  // Repeatedly read input until it is valid
  do {
    std::cin >> input;
    inputError = ValidateInput(input);

    if (inputError == NUM_LIMITS) {
      std::cout << "That number is too big, please enter a number between " 
        "1 and X." << std::endl;
    }
    // ...handle all other cases similarly
  } while(inputError != SUCCESS);

  // If ValidateInput returned SUCCESS, the input is okay.
  // We can turn it into an integer and return it.
  return TurnStringIntoBigInt(input);
}

The recursive solution is nice, but has the drawback of, well, being recursive and growing the stack. Probably that's not a big deal in this case, but it is something to watch out for.

As for how to write ValidateInput , basically you're going to be scanning the string for invalid characters and if none are found, testing if the value will fit in your chosen integer type until reading it into a variable with eg >> .

note: this solution has a serious flaw in that it doesn't check the state of std::cin . If the user were to pass EOF, ie press ^D, the program would get stuck in the loop, which is not good behavior.

而不是bool的矢量你最好使用bitset ,你可以使用Eratosthene方法来确定数字是否为素数。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM