优秀的编程知识分享平台

网站首页 > 技术文章 正文

C++|输入流对象cin、>>提取和处理无效文本输入

nanyue 2024-07-19 23:59:14 技术文章 8 ℃

Most programs that have a user interface of some kind need to handle user input. In the programs that you have been writing, you have been using std::cin to ask the user to enter text input. Because text input is so free-form (the user can enter anything), it’s very easy for the user to enter input that is not expected.

大多数具有某种用户界面的程序都需要处理用户输入。在您编写的程序中,您一直在使用std::cin要求用户输入文本输入,因为文本输入是如此自由(用户可以输入任何内容),所以用户很容易输入不期望的输入。

As you write programs, you should always consider how users will (unintentionally or otherwise) misuse your programs. A well-written program will anticipate how users will misuse it, and either handle those cases gracefully or prevent them from happening in the first place (if possible). A program that handles error cases well is said to be robust.

在编写程序时,应始终考虑用户(无意或以其他方式)如何滥用程序,一个写得好的程序将预测用户将如何滥用它,并且要么优雅地处理这些情况,要么在第一时间防止它们发生(如果可能的话)。一个能很好地处理错误情况的程序应该是健壮的。

There’ll take a look specifically at ways the user can enter invalid text input via std::cin, and show you some different ways to handle those cases.

这里将具体介绍用户通过std::cin输入无效文本的方法,并向您展示一些处理这些情况的不同方法。

In order to discuss how std::cin and operator>> can fail, it first helps to know a little bit about how they work.

为了讨论std::cin和operator>>如何失败,首先了解一下它们是如何工作的。

When we use operator>> to get user input and put it into a variable, this is called an “extraction”. The >> operator is accordingly called the extraction operator when used in this context.

当我们使用operator>>获取用户输入并将其放入变量中时,这称为“提取”。在这种情况下使用>>运算符时,相应地称为提取运算符。

When the user enters input in response to an extraction operation, that data is placed in a buffer inside of std::cin. A buffer (also called a data buffer) is simply a piece of memory set aside for storing data temporarily while it’s moved from one place to another. In this case, the buffer is used to hold user input while it’s waiting to be extracted to variables.

当用户输入输入以响应提取操作时,该数据将放在std::cin内的缓冲区中。缓冲区(也称为数据缓冲区)只是一块内存,当数据从一个地方移动到另一个地方时,它被临时存储起来。在这种情况下,缓冲区用于在等待提取到变量时保存用户输入。

When the extraction operator is used, the following procedure happens:

使用提取运算符时,将执行以下过程:

① If there is data already in the input buffer, that data is used for extraction.

如果输入缓冲区中已有数据,则该数据将用于提取。

② If the input buffer contains no data, the user is asked to input data for extraction (this is the case most of the time). When the user hits enter, a ‘\n’ character will be placed in the input buffer.

如果输入缓冲区不包含数据,则要求用户输入数据进行提取(大多数情况下都是这样)当用户点击enter时,一个'\n'字符将被放入输入缓冲区。

③ operator>> extracts as much data from the input buffer as it can into the variable (ignoring any leading whitespace characters, such as spaces, tabs, or ‘\n’).

operator>>从输入缓冲区提取尽可能多的数据到变量中(忽略任何前导空白字符,如空格、制表符或'\n')。

④ Any data that can not be extracted is left in the input buffer for the next extraction.

无法提取的任何数据都会留在输入缓冲区中,以便下次提取。

Extraction succeeds if at least one character is extracted from the input buffer. Any unextracted input is left in the input buffer for future extractions. For example:

如果至少从输入缓冲区提取一个字符,则提取成功。任何未提取的输入都保留在输入缓冲区中,以便将来提取,例如:

int x;
std::cin >> x;

If the user enters “5a”, 5 will be extracted, converted to an integer, and assigned to variable x. “a\n” will be left in the input stream for the next extraction.

如果用户输入“5a”,5将被提取,转换为整数,并分配给变量x。在下一次提取时,“a\n”将留在输入流中。

Extraction fails if the input data does not match the type of the variable being extracted to. For example:

如果输入数据与要提取到的变量类型不匹配,则提取失败,例如:

int x;
std::cin >> x;

If the user were to enter ‘b’, extraction would fail because ‘b’ can not be extracted to an integer variable.

如果用户输入“b”,提取将失败,因为“b”无法提取为整数变量。

Validating input(验证输入)

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

检查用户输入是否符合程序期望的过程称为输入验证。

There are below basic ways to do input validation:

输入验证有以下一些基本方法:

1 inline (as the user types)

内联(作为用户类型)

Prevent the user from typing invalid input in the first place.

首先防止用户键入无效输入。

2 Post-entry (after the user types)

输入后(在用户类型之后)

2.1 Let the user enter whatever they want into a string, then validate whether the string is correct, and if so, convert the string to the final variable format.

让用户在字符串中输入他们想要的任何内容,然后验证字符串是否正确,如果正确,则将字符串转换为最终的变量格式。

2.2 Let the user enter whatever they want, let std::cin and operator>> try to extract it, and handle the error cases.

让用户输入他们想要的任何内容,让std::cin和operator>>尝试提取它,并处理错误情况。

Some graphical user interfaces and advanced text interfaces will let you validate input as the user enters it (character by character). Generally speaking, the programmer provides a validation function that accepts the input the user has entered so far, and returns true if the input is valid, and false otherwise. This function is called every time the user presses a key. If the validation function returns true, the key the user just pressed is accepted. If the validation function returns false, the character the user just input is discarded (and not shown on the screen). Using this method, you can ensure that any input the user enters is guaranteed to be valid, because any invalid keystrokes are discovered and discarded immediately. Unfortunately, std::cin does not support this style of validation.

一些图形用户界面和高级文本界面将允许您在用户输入时验证输入(逐个字符)一般来说,程序员提供一个验证函数,它接受用户输入,如果输入有效则返回true,否则返回false。每次用户按键时都会调用此函数如果验证函数返回true,则接受用户刚才按下的键,如果validation函数返回false,则用户刚刚输入的字符将被丢弃(并且不会显示在屏幕上)。使用此方法,可以确保用户输入的任何输入都是有效的,因为会立即发现并丢弃任何无效的击键,不幸的是,std::cin不支持这种类型的验证。

Since strings do not have any restrictions on what characters can be entered, extraction is guaranteed to succeed (though remember that std::cin stops extracting at the first non-leading whitespace character). Once a string is entered, the program can then parse the string to see if it is valid or not. However, parsing strings and converting string input to other types (e.g. numbers) can be challenging, so this is only done in rare cases.

由于字符串对可以输入的字符没有任何限制,因此可以保证提取成功(尽管请记住std::cin在第一个非前导空白字符处停止,一旦输入了字符串,程序就可以分析该字符串,看它是否有效?但是,解析字符串并将字符串输入转换为其他类型(例如数字)可能很有挑战性,因此这只在极少数情况下才完成。

Most often, we let std::cin and the extraction operator do the hard work. Under this method, we let the user enter whatever they want, have std::cin and operator>> try to extract it, and deal with the fallout if it fails. This is the easiest method, and the one we’ll talk more about below.

大多数情况下,我们让std::cin和提取符做比较验证的工作。在这种方法下,我们让用户输入他们想要的任何内容,让std::cin和operator>>尝试提取它,并在失败时处理其后果,这是最简单的方法,下面我们将详细讨论这个方法。

A sample program

示例程序

Consider the following calculator program that has no error handling:

考虑以下没有错误处理的计算器程序:

#include <iostream>
 
double getDouble()
{
 std::cout << "Enter a double value: ";
 double x;
 std::cin >> x;
 return x;
}
 
char getOperator()
{
 std::cout << "Enter one of the following: +, -, *, or /: ";
 char op;
 std::cin >> op;
 return op;
}
 
void printResult(double x, char op, double y)
{
 if (op == '+')
 std::cout << x << " + " << y << " is " << x + y << '\n';
 else if (op == '-')
 std::cout << x << " - " << y << " is " << x - y << '\n';
 else if (op == '*')
 std::cout << x << " * " << y << " is " << x * y << '\n';
 else if (op == '/')
 std::cout << x << " / " << y << " is " << x / y << '\n';
}
 
int main()
{
 double x = getDouble();
 char op = getOperator();
 double y = getDouble();
 
 printResult(x, op, y);
 
 return 0;
}

This simple program asks the user to enter two numbers and a mathematical operator.

这个简单的程序要求用户输入两个数字和一个数学运算符。

Enter a double value: 5

Enter one of the following: +, -, *, or /: *

Enter a double value: 7

5 * 7 is 35

Now, consider where invalid user input might break this program.

现在,请考虑无效的用户输入可能会破坏此程序的位置。

First, we ask the user to enter some numbers. What if they enter something other than a number (e.g. ‘q’)? In this case, extraction will fail.

首先,我们要求用户输入一些数字,如果他们输入的不是数字(例如“q”),怎么办?在这种情况下,提取将失败。

Second, we ask the user to enter one of four possible symbols. What if they enter a character other than one of the symbols we’re expecting? We’ll be able to extract the input, but we don’t currently handle what happens afterward.

其次,我们要求用户输入四个可能的符号之一,如果他们输入的字符不是我们期望的符号呢?我们将能够提取输入,但目前处理不了随后发生的事情。

Third, what if we ask the user to enter a symbol and they enter a string like “*q hello”. Although we can extract the ‘*’ character we need, there’s additional input left in the buffer that could cause problems down the road.

第三,如果我们让用户输入一个符号,然后他们输入一个类似“*q hello”的字符串,会怎么样?虽然我们可以提取所需的“*”字符,但缓冲区中还有其他的输入,可能会导致问题。

Types of invalid text input

无效文本输入的类型

We can generally separate input text errors into four types:

我们通常可以将输入文本错误分为四种类型:

1 Input extraction succeeds but the input is meaningless to the program (e.g. entering ‘k’ as your mathematical operator).

输入提取成功,但输入对程序没有意义(例如,输入“k”作为数学运算符)。

2 Input extraction succeeds but the user enters additional input (e.g. entering ‘*q hello’ as your mathematical operator).

输入提取成功,但用户输入了其他输入(例如,输入“*q hello”作为数学运算符)。

3 Input extraction fails (e.g. trying to enter ‘q’ into a numeric input).

输入提取失败(例如,试图在数字输入中输入“q”)。

4 Input extraction succeeds but the user overflows a numeric value.

输入提取成功,但用户溢出了一个数值。

Thus, to make our programs robust, whenever we ask the user for input, we ideally should determine whether each of the above can possibly occur, and if so, write code to handle those cases.

因此,为了使我们的程序健壮,每当我们要求用户输入时,我们理想地应该确定是否可能发生上述每一种情况,如果可能,则编写代码来处理这些情况。

Let’s dig into each of these cases, and how to handle them using std::cin.

让我们深入研究每一种情况,以及如何使用std::cin处理它们。

Error case 1: Extraction succeeds but input is meaningless

错误情况1:提取成功,但输入无意义

This is the simplest case. Consider the following execution of the above program:

这是最简单的情况,考虑执行上述程序:

Enter a double value: 5

Enter one of the following: +, -, *, or /: k

Enter a double value: 7

In this case, we asked the user to enter one of four symbols, but they entered ‘k’ instead. ‘k’ is a valid character, so std::cin happily extracts it to variable op, and this gets returned to main. But our program wasn’t expecting this to happen, so it doesn’t properly deal with this case (and thus never outputs anything).

在本例中,我们要求用户输入四个符号中的一个,但他们输入了“k”,“k”是一个有效字符,因此std::cin很高兴地将其提取到变量op,并将其返回到main,但是我们的程序没有预料到会发生这种情况,所以它不能正确地处理这种情况(因此永远不会输出任何东西)。

The solution here is simple: do input validation. This usually consists of 3 steps:

这里的解决方案很简单:进行输入验证这通常包括三个步骤:

1) Check whether the user’s input was what you were expecting.

1)检查用户的输入是否符合您的预期。

2) If so, return the value to the caller.

2)如果是,则将值返回给调用方。

3) If not, tell the user something went wrong and have them try again.

3)如果没有,告诉用户出了问题,让他们再试一次。

Here’s an updated getOperator() function that does input validation.

下面是一个更新的getOperator()函数,它执行输入验证。

char getOperator()
{
 while (true) // Loop until user enters a valid input
 {
 std::cout << "Enter one of the following: +, -, *, or /: ";
 char op;
 std::cin >> op;
 
 // Check whether the user entered meaningful input
 if (op == '+' || op == '-' || op == '*' || op == '/') 
 return op; // return it to the caller
 else // otherwise tell the user what went wrong
 std::cout << "Oops, that input is invalid. Please try again.\n";
 } // and try again
}

As you can see, we’re using a while loop to continuously loop until the user provides valid input. If they don’t, we ask them to try again until they either give us valid input, shutdown the program, or destroy their computer.

如您所见,我们使用while循环来连续循环,直到用户提供有效的输入,如果他们没有,我们要求他们再试一次,直到他们给出有效的输入,关闭程序,或者破坏他们的计算。

2 Error case 2: Extraction succeeds but with extraneous input

错误情况2:提取成功,但输入无关

Consider the following execution of the above program:

考虑执行上述程序:

Enter a double value: 5*7

What do you think happens next?

你认为接下来会发生什么?

Enter a double value: 5*7

Enter one of the following: +, -, *, or /: Enter a double value: 5 * 7 is 35

The program prints the right answer, but the formatting is all messed up. Let’s take a closer look at why.

程序打印出正确的答案,但格式都弄乱了,让我们仔细看看原因。

When the user enters “5*7” as input, that input goes into the buffer. Then operator>> extracts the 5 to variable x, leaving “*7\n” in the buffer. Next, the program prints “Enter one of the following: +, -, *, or /:”. However, when the extraction operator was called, it sees “*7\n” waiting in the buffer to be extracted, so it uses that instead of asking the user for more input. Consequently, it extracts the ‘*’ character, leaving “7\n” in the buffer.

当用户输入“5*7”作为输入时,该输入进入缓冲区,然后,运算符>>将5提取到变量x,将“*7\n”保留在缓冲区中。接下来,程序将打印“输入下列值之一:+、-、*或/:”但是,当调用提取运算符时,它看到“*7\n”在缓冲区中等待提取,因此它使用该运算符而不是要求用户输入更多信息。因此,它提取“*”字符,将“7\n”留在缓冲区中。

After asking the user to enter another double value, the “7” in the buffer gets extracted without asking the user. Since the user never had an opportunity to enter additional data and hit enter (causing a newline), the output prompts all get run together on the same line, even though the output is correct.

在要求用户输入另一个双精度值后,在不要求用户的情况下提取缓冲区中的“7”,由于用户从未有机会输入其他数据并按enter键(导致换行),因此即使输出正确,输出提示全部一起在同一行上运行。

Although the above problem works, the execution is messy. It would be better if any extraneous characters entered were simply ignored. Fortunately, that’s easy to do:

尽管上述问题有效,但执行过程却很混乱。如果输入的任何无关字符都被忽略,那就更好了,幸运的是,这很容易做到:

std::cin.ignore(32767, '\n'); 
// clear (up to 32767) characters out of the buffer until a '\n' character is removed

Since the last character the user entered must be a ‘\n’, we can tell std::cin to ignore buffered characters until it finds a newline character (which is removed as well).

由于用户输入的最后一个字符必须是'\n',我们可以告诉std::cin忽略缓冲字符,直到它找到换行字符(也将其删除)。

Let’s update our getDouble() function to ignore any extraneous input:

让我们更新getDouble()函数以忽略任何无关的输入:

double getDouble()
{
 std::cout << "Enter a double value: ";
 double x;
 std::cin >> x;
 std::cin.ignore(32767, '\n'); 
 // clear (up to 32767) characters out of the buffer until a '\n' character is removed
 return x;
}

Now our program will work as expected, even if we enter “5*7” for the first input -- the 5 will be extracted, and the rest of the characters will be removed from the input buffer. Since the input buffer is now empty, the user will be properly asked for input the next time an extraction operation is performed!

现在我们的程序将按预期工作,即使我们输入“5*7”作为第一个输入——5将被提取,其余字符将从输入缓冲区中删除由于输入缓冲区现在是空的,下次执行提取操作时将正确地要求用户输入!

Error case 3: Extraction fails 提取失败

Now consider the following execution of the calculator program:

现在考虑以下计算器程序的执行:

Enter a double value: a

You shouldn’t be surprised that the program doesn’t perform as expected, but how it fails is interesting:

你不必惊讶程序没有按预期执行,但它是如何失败的是有趣的:

Enter a double value: a

Enter one of the following: +, -, *, or /: Enter a double value:

and the program suddenly ends.

程序突然结束了。

This looks pretty similar to the extraneous input case, but it’s a little different. Let’s take a closer look.

这看起来与无关的输入案例非常相似,但有点不同让我们仔细看看。

When the user enters ‘a’, that character is placed in the buffer. Then operator>> tries to extract ‘a’ to variable x, which is of type double. Since ‘a’ can’t be converted to a double, operator>> can’t do the extraction. Two things happen at this point: ‘a’ is left in the buffer, and std::cin goes into “failure mode”.

当用户输入“a”时,该字符将放在缓冲区中,然后运算符>>尝试将“a”提取到变量x,该变量是double类型的,因为“a”不能转换为double,所以operator>>无法执行提取,此时会发生两件事:“a”留在缓冲区中,std::cin进入“故障模式”。

Once in ‘failure mode’, future requests for input extraction will silently fail. Thus in our calculator program, the output prompts still print, but any requests for further extraction are ignored. The program simply runs to the end and then terminates (without printing a result, because we never read in a valid mathematical operation).

一旦进入“失败模式”,以后的输入提取请求将自动失败。因此,在我们的计算器程序中,输出提示仍然是显示的,但是任何进一步提取的请求都将被忽略,程序只是运行到最后,然后终止(不打印结果,因为我们从来没有读过有效的数学运算)。

Fortunately, we can detect whether an extraction has failed and fix it:

幸运的是,我们可以检测提取是否失败并修复它:

if (std::cin.fail()) // has a previous extraction failed?
{
 // yep, so let's handle the failure
 std::cin.clear(); // put us back in 'normal' operation mode
 std::cin.ignore(32767,'\n'); // and remove the bad input
}

That’s it!

就这样!

Let’s integrate that into our getDouble() function:

让我们将其集成到getDouble()函数中:

double getDouble()
{
 while (true) // Loop until user enters a valid input
 {
 std::cout << "Enter a double value: ";
 double x;
 std::cin >> x;
 
 if (std::cin.fail()) // has a previous extraction failed?
 {
 // yep, so let's handle the failure
 std::cin.clear(); // put us back in 'normal' operation mode
 std::cin.ignore(32767,'\n'); // and remove the bad input
 }
 else // else our extraction succeeded
 {
 std::cin.ignore(32767, '\n'); 
 // clear (up to 32767) characters out of the buffer until a '\n' character is removed
 return x; // so return the value we extracted
 }
 }
}

Note: Prior to C++11, a failed extraction would not modify the variable being extracted to. This means that if a variable was uninitialized, it would stay uninitialized in the failed extraction case. However, as of C++11, a failed extraction due to invalid input will cause the variable to be zero-initialized. Zero initialization means the variable is set to 0, 0.0, “”, or whatever value 0 converts to for that type.

注意:在C++ 11之前,失败的提取不会修改 11之前,失败的提取不会修改提取的变量。这意味着,如果变量未初始化,则在提取失败的情况下,它将保持未初始化状态。但是,对于C,将导致变量初始化为零。零初始化意味着变量设置为0、0.0、“”或0转换为该类型的任何值。

Error case 4: Extraction succeeds but the user overflows a numeric value 提取成功,但用户溢出了一个数值

Consider the following simple example:

考虑下面的简单示例:

#include <cstdint>
#include <iostream>
 
int main()
{
 std::int16_t x { 0 }; // x is 16 bits, holds from -32768 to 32767
 std::cout << "Enter a number between -32768 and 32767: ";
 std::cin >> x;
 
 std::int16_t y { 0 }; // y is 16 bits, holds from -32768 to 32767
 std::cout << "Enter another number between -32768 and 32767: ";
 std::cin >> y;
 
 std::cout << "The sum is: " << x + y << '\n';
 return 0;
}

What happens if the user enters a number that is too large (e.g. 40000)?

如果用户输入的数字太大(例如40000),会发生什么情况?

Enter a number between -32768 and 32767: 40000

Enter another number between -32768 and 32767: The sum is: 32767

In the above case, std::cin goes immediately into “failure mode”, but also assigns the closest in-range value to the variable. Consequently, x is left with the assigned value of 32767. Additional inputs are skipped, leaving y with the initialized value of 0. We can handle this kind of error in the same way as a failed extraction.

在上述情况下,std::cin会立即进入“故障模式”,但也会将最接近的范围值赋给变量。因此,x的赋值为32767,将跳过其他输入,使y的初始化值为0。我们可以像处理失败的提取一样处理此类错误。

Note: Prior to C++11, a failed extraction would not modify the variable being extracted to. This means that if a variable was uninitialized, it would stay uninitialized in the failed extraction case. However, as of C++11, an out-of-range failed extraction will cause the variable to be set to the closest in-range value.

注意:在C++ 11之前,失败的提取不会修改提取的变量。这意味着,如果变量未初始化,则在提取失败的情况下,它将保持未初始化状态然而,对于C++ 11,超出范围的失败提取将导致变量设置为最接近的范围值。

Putting it all together

把所有的东西放在一起

Here’s our example calculator with full error checking:

下面是一个带有完整错误检查的计算器示例:

#include <iostream>
 
double getDouble()
{
 while (true) // Loop until user enters a valid input
 {
 std::cout << "Enter a double value: ";
 double x;
 std::cin >> x;
 
 // Check for failed extraction
 if (std::cin.fail()) // has a previous extraction failed?
 {
 // yep, so let's handle the failure
 std::cin.clear(); // put us back in 'normal' operation mode
 std::cin.ignore(32767,'\n'); // and remove the bad input
 std::cout << "Oops, that input is invalid. Please try again.\n";
 }
 else
 {
 std::cin.ignore(32767,'\n'); // remove any extraneous input
 
 // the user can't enter a meaningless double value, 
 // so we don't need to worry about validating that
 return x;
 }
 }
}
 
char getOperator()
{
 while (true) // Loop until user enters a valid input
 {
 std::cout << "Enter one of the following: +, -, *, or /: ";
 char op;
 std::cin >> op;
 
 // Chars can accept any single input character, 
 // so no need to check for an invalid extraction here
 
 std::cin.ignore(32767,'\n'); // remove any extraneous input
 
 // Check whether the user entered meaningful input
 if (op == '+' || op == '-' || op == '*' || op == '/') 
 return op; // return it to the caller
 else // otherwise tell the user what went wrong
 std::cout << "Oops, that input is invalid. Please try again.\n";
 } // and try again
}
 
void printResult(double x, char op, double y)
{
 if (op == '+')
 std::cout << x << " + " << y << " is " << x + y << '\n';
 else if (op == '-')
 std::cout << x << " - " << y << " is " << x - y << '\n';
 else if (op == '*')
 std::cout << x << " * " << y << " is " << x * y << '\n';
 else if (op == '/')
 std::cout << x << " / " << y << " is " << x / y << '\n';
 else // Being robust means handling unexpected parameters as well, 
 // even though getOperator() guarantees op is valid in this particular program
 std::cout << "Something went wrong: printResult() got an invalid operator.";
 
}
 
int main()
{
 double x = getDouble();
 char op = getOperator();
 double y = getDouble();
 
 printResult(x, op, y);
 
 return 0;
}

Conclusion

结论

As you write your programs, consider how users will misuse your program, especially around text input. For each point of text input, consider:

在编写程序时,请考虑用户如何滥用程序,尤其是在文本输入方面。对于文本输入的每个点,请考虑:

Could extraction fail?

提取会失败吗?

Could the user enter more input than expected?

用户能否输入比预期更多的输入?

Could the user enter meaningless input?

用户是否可以输入无意义的输入?

Could the user overflow an input?

用户是否可以使输入溢出?

You can use if statements and boolean logic to test whether input is expected and meaningful.

您可以使用if语句和布尔逻辑来测试输入是否是预期的和有意义的。

The following code will test for and fix failed extractions or overflow:

以下代码将测试并修复失败的提取或溢出:

if (std::cin.fail()) // has a previous extraction failed or overflowed?
{
 // yep, so let's handle the failure
 std::cin.clear(); // put us back in 'normal' operation mode
 std::cin.ignore(32767,'\n'); // and remove the bad input
}

The following will also clear any extraneous input:

下面还将清除任何无关的输入:

std::cin.ignore(32767,'\n'); // and remove the bad input

Finally, use loops to ask the user to re-enter input if the original input was invalid.

最后,如果原始输入无效,使用循环要求用户重新输入输入。


ref:

https://www.learncpp.com/cpp-tutorial/5-10-stdcin-extraction-and-dealing-with-invalid-text-input/

-End-

Tags:

最近发表
标签列表