From 739f6759f5620a012bf3f5b34981c85db3af72e5 Mon Sep 17 00:00:00 2001 From: Aolin Date: Fri, 12 Jan 2024 21:28:07 +0800 Subject: [PATCH] blog: Get an Integer from a String Using stringstream (#60) --- .../2023-03-19-cpp-beginner-guess-number.md | 415 +++++++------- website/blog/2023-09-04-cpp-beginner-cmake.md | 3 +- ...2-cpp-beginner-stringtoint-stringstream.md | 306 ++++++++++ website/docs/cpp/cpp-wiki.md | 99 ++-- website/docs/cpp/guess-number.md | 542 ++++++++++++++++++ website/docs/cpp/stringtoint-stringstream.md | 302 ++++++++++ 6 files changed, 1399 insertions(+), 268 deletions(-) create mode 100644 website/blog/2024-01-12-cpp-beginner-stringtoint-stringstream.md create mode 100644 website/docs/cpp/guess-number.md create mode 100644 website/docs/cpp/stringtoint-stringstream.md diff --git a/website/blog/2023-03-19-cpp-beginner-guess-number.md b/website/blog/2023-03-19-cpp-beginner-guess-number.md index 6a37b4f..47c8fa7 100644 --- a/website/blog/2023-03-19-cpp-beginner-guess-number.md +++ b/website/blog/2023-03-19-cpp-beginner-guess-number.md @@ -85,11 +85,11 @@ C++ 中的输入输出可以通过 [`std::cin`](https://en.cppreference.com/w/cp ```cpp title="io.cpp" #include -int main(){ - int a, b; - std::cout << "Enter two numbers: "; - std::cin >> a >> b; - std::cout << "The numbers you entered are: " << a << " and " << b << '\n'; +int main() { + int a, b; + std::cout << "Enter two numbers: "; + std::cin >> a >> b; + std::cout << "The numbers you entered are: " << a << " and " << b << '\n'; } ``` @@ -137,11 +137,9 @@ int a = 5; ```cpp if (condition1) { // do this if condition1 is true -} -else if (condition2) { +} else if (condition2) { // else this id condition2 is true -} -else { +} else { // otherwise do this } ``` @@ -151,23 +149,17 @@ else { ```cpp title="condition.cpp" #include -int main() -{ - int i = 0; - std::cout << "Enter a number: "; - std::cin >> i; - if (i == 0) - { - std::cout << "zero\n"; - } - else if (i > 0) - { - std::cout << "positive\n"; - } - else - { - std::cout << "negative\n"; - } +int main() { + int i = 0; + std::cout << "Enter a number: "; + std::cin >> i; + if (i == 0) { + std::cout << "zero\n"; + } else if (i > 0) { + std::cout << "positive\n"; + } else { + std::cout << "negative\n"; + } } ``` @@ -198,13 +190,15 @@ C++ 中的循环语句可以通过 `while`、`do while` 和 `for` 关键字来 #include int main() { - int i = 5; - while (i < 10) { - std::cout << i << ' '; - i = i + 1; - } + int i = 5; + while (i < 10) { + std::cout << i << ' '; + i = i + 1; + } } ``` + + ### `do ... while` @@ -214,13 +208,15 @@ int main() { #include int main() { - int i = 10; - do { - std::cout << i << ' '; - i = i + 1; - } while (i < 10); + int i = 10; + do { + std::cout << i << ' '; + i = i + 1; + } while (i < 10); } ``` + + #### `for` @@ -230,11 +226,14 @@ int main() { #include int main() { - for (int i = 5; i < 10; i = i + 1) { - std::cout << i << ' '; - } + for (int i = 5; i < 10; i = i + 1) { + std::cout << i << ' '; + } } ``` + + + ## 实践:猜数字游戏 @@ -270,16 +269,17 @@ int main() { // highlight-next-line #include -int main() -{ - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> distrib(1, 100); - int answer = distrib(gen); +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); - std::cout << "The answer is: " << answer; + std::cout << "The answer is: " << answer; } ``` + + ### 处理用户输入 @@ -289,24 +289,25 @@ int main() #include #include -int main() -{ - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> distrib(1, 100); - int answer = distrib(gen); +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); - std::cout << "The answer is: " << answer << '\n'; + std::cout << "The answer is: " << answer << '\n'; - // highlight-start - int guess_number; - std::cout << "Guess the number: "; - std::cin >> guess_number; - // highlight-end + // highlight-start + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + // highlight-end - std::cout << "The guess_number is: " << guess_number << '\n'; + std::cout << "The guess_number is: " << guess_number << '\n'; } ``` + + ### 判断用户输入的数字是否正确 @@ -316,35 +317,29 @@ int main() #include #include -int main() -{ - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> distrib(1, 100); - int answer = distrib(gen); - - std::cout << "The answer is: " << answer << '\n'; - - int guess_number; - std::cout << "Guess the number: "; - std::cin >> guess_number; - - std::cout << "The guess_number is: " << guess_number << '\n'; - - // highlight-start - if (guess_number == answer) - { - std::cout << "Congratulations!\n"; - } - else if (guess_number < answer) - { - std::cout << "Guess a higher number!\n"; - } - else - { - std::cout << "Guess a lower number!\n"; - } - // highlight-end +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + std::cout << "The guess_number is: " << guess_number << '\n'; + + // highlight-start + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + } else if (guess_number < answer) { + std::cout << "Guess a higher number!\n"; + } else { + std::cout << "Guess a lower number!\n"; + } + // highlight-end } ``` @@ -356,37 +351,30 @@ int main() #include #include -int main() -{ - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> distrib(1, 100); - int answer = distrib(gen); - - std::cout << "The answer is: " << answer << '\n'; - - // highlight-next-line - while (true) - { - int guess_number; - std::cout << "Guess the number: "; - std::cin >> guess_number; - - if (guess_number == answer) - { - std::cout << "Congratulations!\n"; - // highlight-next-line - break; - } - else if (guess_number < answer) - { - std::cout << "Guess a higher number!\n"; - } - else - { - std::cout << "Guess a lower number!\n"; - } +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + // highlight-next-line + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + // highlight-next-line + break; + } else if (guess_number < answer) { + std::cout << "Guess a higher number!\n"; + } else { + std::cout << "Guess a lower number!\n"; } + } } ``` @@ -414,46 +402,41 @@ g++ --std=c++20 guess_number.cpp && ./a.out #include #include -int main() -{ - // highlight-start - int min_val = 1; - int max_val = 100; - // highlight-end - std::random_device rd; - std::mt19937 gen(rd()); - // highlight-next-line - std::uniform_int_distribution<> distrib(min_val, max_val); - int answer = distrib(gen); - - std::cout << "The answer is: " << answer << '\n'; - - while (true) - { - int guess_number; - std::cout << "Guess the number: "; - std::cin >> guess_number; - - if (guess_number == answer) - { - std::cout << "Congratulations!\n"; - break; - } - else if (guess_number < answer) - { - // highlight-start - min_val = guess_number + 1; - std::cout << "Guess the number from " << min_val << " to " << max_val << '\n'; - // highlight-end - } - else - { - // highlight-start - max_val = guess_number - 1; - std::cout << "Guess the number from " << min_val << " to " << max_val << '\n'; - // highlight-end - } +int main() { + // highlight-start + int min_val = 1; + int max_val = 100; + // highlight-end + std::random_device rd; + std::mt19937 gen(rd()); + // highlight-next-line + std::uniform_int_distribution<> distrib(min_val, max_val); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + break; + } else if (guess_number < answer) { + // highlight-start + min_val = guess_number + 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + // highlight-end + } else { + // highlight-start + max_val = guess_number - 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + // highlight-end } + } } ``` @@ -478,41 +461,36 @@ g++ --std=c++20 guess_number.cpp && ./a.out #include #include -int main() -{ - int min_val = 1; - int max_val = 100; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> distrib(min_val, max_val); - int answer = distrib(gen); - - std::cout << "The answer is: " << answer << '\n'; - - while (true) - { - int guess_number; - std::cout << "Guess the number: "; - std::cin >> guess_number; - - if (guess_number == answer) - { - std::cout << "Congratulations!\n"; - break; - } - else if (guess_number < answer) - { - // highlight-next-line - min_val = guess_number < min_val ? min_val : guess_number + 1; - std::cout << "Guess the number from " << min_val << " to " << max_val << '\n'; - } - else - { - // highlight-next-line - max_val = guess_number > max_val ? max_val : guess_number - 1; - std::cout << "Guess the number from " << min_val << " to " << max_val << '\n'; - } +int main() { + int min_val = 1; + int max_val = 100; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(min_val, max_val); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + break; + } else if (guess_number < answer) { + // highlight-next-line + min_val = guess_number < min_val ? min_val : guess_number + 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + } else { + // highlight-next-line + max_val = guess_number > max_val ? max_val : guess_number - 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; } + } } ``` @@ -537,36 +515,31 @@ g++ --std=c++20 guess_number.cpp && ./a.out #include #include -int main() -{ - int min_val = 1; - int max_val = 100; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> distrib(min_val, max_val); - int answer = distrib(gen); - - while (true) - { - int guess_number; - std::cout << "Guess the number: "; - std::cin >> guess_number; - - if (guess_number == answer) - { - std::cout << "Congratulations!\n"; - break; - } - else if (guess_number < answer) - { - min_val = guess_number < min_val ? min_val : guess_number + 1; - std::cout << "Guess the number from " << min_val << " to " << max_val << '\n'; - } - else - { - max_val = guess_number > max_val ? max_val : guess_number - 1; - std::cout << "Guess the number from " << min_val << " to " << max_val << '\n'; - } +int main() { + int min_val = 1; + int max_val = 100; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(min_val, max_val); + int answer = distrib(gen); + + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + break; + } else if (guess_number < answer) { + min_val = guess_number < min_val ? min_val : guess_number + 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + } else { + max_val = guess_number > max_val ? max_val : guess_number - 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; } + } } ``` diff --git a/website/blog/2023-09-04-cpp-beginner-cmake.md b/website/blog/2023-09-04-cpp-beginner-cmake.md index 4a6d3bc..29de2ab 100644 --- a/website/blog/2023-09-04-cpp-beginner-cmake.md +++ b/website/blog/2023-09-04-cpp-beginner-cmake.md @@ -229,8 +229,9 @@ target_link_libraries(hello fmt::fmt) 下面示例代码使用了 `std::cout`、`fmt::print` 和 `fmt::println` 三种方式进行字符串的输出: ```cpp title="cpp_learning/hacking_cpp/hello.cpp" -#include #include + +#include int main() { std::cout << "std::cout: Hello World\n"; fmt::print("fmt::print: Hello!"); diff --git a/website/blog/2024-01-12-cpp-beginner-stringtoint-stringstream.md b/website/blog/2024-01-12-cpp-beginner-stringtoint-stringstream.md new file mode 100644 index 0000000..3b8b871 --- /dev/null +++ b/website/blog/2024-01-12-cpp-beginner-stringtoint-stringstream.md @@ -0,0 +1,306 @@ +--- +slug: 2024-01-12-cpp-beginner-stringtoint-stringstream +title: "C++ Beginner's Guide: Get an Integer from a String Using string stream" +authors: [Oreo] +tags: [C++, CS106L] +--- + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + +During the CS 106L course, I learned a lot of C++ streams. This document describes how to use [`basic_stringstream`](https://en.cppreference.com/w/cpp/io/basic_stringstream) to get an integer from a string. + +:::info quote + +The basic unit of communication between a program and its environment is a _stream_. A stream is a channel between a _source_ and _destination_ which allows the source to push formatted data to the destination. + +—— [CS 106L Course Reader](https://web.stanford.edu/class/cs106l/full_course_reader.pdf) + +::: + + + +## Overview of string stream + +To create a string stream, we need to include the `` header file. Then, we can create a string stream object using the following code: + +```cpp +#include + +int main() { + std::istringstream iss("9.15 pounds."); + std::ostringstream oss("The price of the shirt is "); +} +``` + +For C++ beginners, we usually use `std::cin` to get input from the user and use `std::cout` to output something to the console. Here, `std::cin` and `std::cout` are both streams. The following is an example: + +```cpp +#include + +int main() { + int value; + std::cin >> value; + std::cout << value << std::endl; +} +``` + +In the preceding example, `>>` is the stream **extraction** operator, and `<<` is the stream **insertion** operator. For string streams, we also use `>>` and `<<` to extract and insert data. + +To extract the price and unit from the string stream `iss`, use the following code: + + + + + ```cpp + #include + #include + + int main() { + std::istringstream iss("9.15 pounds."); + std::ostringstream oss("The price of the shirt is "); + double price; + std::string unit; + iss >> price >> unit; + std::cout << oss.str() << price << " " << unit << std::endl; + } + ``` + + + + + + + ```text + The price of the shirt is 9.15 pounds. + ``` + + + + +What is the behavior of `iss >> price >> unit`? We can modify the type of `price` to `int` and see what happens: + + + + + ```cpp + #include + #include + + int main() { + std::istringstream iss("9.15 pounds."); + std::ostringstream oss("The price of the shirt is "); + // highlight-next-line + int price; + std::string unit; + iss >> price >> unit; + std::cout << oss.str() << price << " " << unit << std::endl; + } + ``` + + + + + + + ```text + The price of the shirt is 9 .15 + ``` + + + + +The output shows that the value of `price` is `9`, and the value of `unit` is `.15`. This is because the `>>` operator will stop extracting data when it encounters a whitespace or an invalid character for the type. In this case, the `>>` operator in `iss >> price` stops extracting data at `.`. Then, the `>>` operator in `iss >> unit` extracts `.15` into `unit` and stops extracting data at ` `. + +## Implement `stringToInteger()` without error-checking + +Now, we can use `>>` to extract an integer from a string. Let's implement a function `stringToInteger()` to convert a string to an integer. The code is as follows: + + + + + ```cpp name="stringToInteger.cpp" + #include + #include + + int stringToInteger(const std::string& str) { + std::istringstream iss(str); + int value; + iss >> value; + return value; + } + + int main() { + std::string str = "123"; + int value = stringToInteger(str); + std::cout << "The value is: " << value << std::endl; + } + ``` + + + + + + + ```text + The value is: 123 + ``` + + + + +## Stream state + +What if the string contains invalid characters? For example, the string `"123abc"` contains non-numeric characters. In this case, the `>>` operator stops extracting data at `a`. Then, the value of `value` will be `123`. However, we want to return an error message to the user. To do this, we need to check whether any error occurs during the extraction process. + +A new concept is introduced here: **stream state**. There are four stream states: + +- **good**: no error occurs. The I/O operations are available. +- **eof**: reaching the **end of the stream**. +- **fail**: the input/output operation failed and all future operations frozen, such as the type mismatch. +- **bad**: **irrecoverable** stream error. For example, the file you are reading is deleted suddenly. + +To check the stream state, we can use the `good()`, `eof()`, `fail()`, and `bad()` functions. The following shows some examples: + + + + + ```cpp + #include + #include + #include + + void get_stream_state(std::istringstream &iss) { + if (iss.good()) { + std::cout << "G"; + } + if (iss.eof()) { + std::cout << "E"; + } + if (iss.fail()) { + std::cout << "F"; + } + if (iss.bad()) { + std::cout << "B"; + } + std::cout << std::endl; + } + + int stringToInteger(const std::string &str) { + std::istringstream iss(str); + std::cout << "Before: "; + get_stream_state(iss); + int value; + iss >> value; + std::cout << "After: "; + get_stream_state(iss); + return value; + } + + int main() { + std::vector test_strings{"123", "123abc", "abc123", ""}; + for (const auto &str : test_strings) { + std::cout << "stringToInteger(\"" << str << "\"):\n" + << stringToInteger(str) << std::endl; + } + } + ``` + + + + + + + ```text + stringToInteger("123"): + Before: G + After: E + 123 + stringToInteger("123abc"): + Before: G + After: G + 123 + stringToInteger("abc123"): + Before: G + After: F + 0 # undefined behavior + stringToInteger(""): + Before: G + After: EF + -514166240 # undefined behavior + ``` + + + + +## Implement `stringToInteger()` with error-checking + +Using the stream state, we can implement `stringToInteger()` with error-checking. From the preceding example, to ensure that the string only contains numeric characters, we need to consider the following cases: + +- After the extraction, the stream state is `good` : the string might contain non-numeric characters after the number. +- After the extraction, the stream state is `fail`: the string contains non-numeric characters before the number. + +The following code shows how to implement `stringToInteger()` with error-checking: + +```cpp name="stringToInteger.cpp" +#include +#include + +int stringToInteger(const std::string &str) { + std::istringstream iss(str); + int result; + // highlight-start + char remain; // Record remaining characters after the integer + iss >> result; + if (iss.fail() || iss.bad()) { + throw std::domain_error("The string does not start with an integer."); + } + iss >> remain; + if (!iss.fail()) { + throw std::domain_error( + "The string contains extra characters after the integer."); + } + // highlight-end + return result; +} + +int main() { + std::string str = "123"; + int value = stringToInteger(str); + std::cout << "The value is: " << value << std::endl; +} +``` + + + +In the preceding code, the program first tries to extract an integer from the string. If the stream state is `fail` or `bad`, it throws an error. Otherwise, it tries to extract a character from the remaining string. If the stream state is not `fail`, it means that the string contains extra characters after the integer. Then, the program throws an error. + +Since `if ((iss >> result).fail())` is equivalent to `if (!(iss >> result))`, we can simplify the code as follows: + +```cpp name="stringToInteger.cpp" +#include +#include + +int stringToInteger(const std::string &str) { + std::istringstream iss(str); + int result; + char remain; // Record remaining characters after the integer + // highlight-start + if (!(iss >> result) || iss >> remain) { + throw std::domain_error( + "Failed to get integer from string. Please check the string."); + } + // highlight-end + return result; +} + +int main() { + std::string str = "123"; + int value = stringToInteger(str); + std::cout << "The value is: " << value << std::endl; +} +``` + + diff --git a/website/docs/cpp/cpp-wiki.md b/website/docs/cpp/cpp-wiki.md index a25a37a..8099742 100644 --- a/website/docs/cpp/cpp-wiki.md +++ b/website/docs/cpp/cpp-wiki.md @@ -1,5 +1,5 @@ --- -title: "C++" +title: "C++ wiki" description: "A collection of useful C++ usage and tips." --- @@ -20,7 +20,7 @@ using namespace std; int max = 0; int main() { - cout << max << endl; // error: reference to 'max' is ambiguous + cout << max << endl; // error: reference to 'max' is ambiguous } ``` @@ -34,7 +34,7 @@ To avoid the preceding error, use the following code instead: int max = 0; int main() { - std::cout << max << std::endl; // 0 + std::cout << max << std::endl; // 0 } ``` @@ -54,11 +54,11 @@ The following code examples show the difference between using `std::endl` and no ```cpp #include - + int main() { - for (int i=0; i<5; i++) { - std::cout << i << std::endl; - } + for (int i = 0; i < 5; i++) { + std::cout << i << std::endl; + } } // 0 // 1 @@ -74,11 +74,11 @@ The following code examples show the difference between using `std::endl` and no ```cpp #include - + int main() { - for (int i=0; i<5; i++) { - std::cout << i; // 01234 - } + for (int i = 0; i < 5; i++) { + std::cout << i; // 01234 + } } ``` @@ -106,14 +106,15 @@ The following code examples show the same output with `std::endl` and `\n` in st ```cpp name="std_endl.cpp" - #include #include - + + #include + int main() { - for (int i=0; i<5; i++) { - sleep(1); - std::cout << i << std::endl; - } + for (int i = 0; i < 5; i++) { + sleep(1); + std::cout << i << std::endl; + } } // Sleep 1 second // 0 @@ -131,14 +132,15 @@ The following code examples show the same output with `std::endl` and `\n` in st ```cpp name="new_line_character.cpp" - #include #include - + + #include + int main() { - for (int i=0; i<5; i++) { - sleep(1); - std::cout << i << "\n"; - } + for (int i = 0; i < 5; i++) { + sleep(1); + std::cout << i << "\n"; + } } // Sleep 1 second // 0 @@ -200,35 +202,40 @@ The following example shows the duration for printing 100,000 numbers with `std: ```cpp - #include // for cin, cout - #include // for timers + #include // for timers + #include // for cin, cout int endl_each_time(int n = 10000) { - const auto start_time = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < n; i++) { - std::cout << i << std::endl; - } - const auto end_time = std::chrono::high_resolution_clock::now(); - auto duration_ns = std::chrono::duration_cast(end_time - start_time); - return duration_ns.count(); + const auto start_time = std::chrono::steady_clock::now(); + for (int i = 0; i < n; i++) { + std::cout << i << std::endl; + } + const auto end_time = std::chrono::steady_clock::now(); + auto duration_ns = std::chrono::duration_cast( + end_time - start_time); + return duration_ns.count(); } int new_line_each_time(int n = 10000) { - const auto start_time = std::chrono::high_resolution_clock::now(); - for (int i = 0; i < n; i++) { - std::cout << i << "\n"; - } - const auto end_time = std::chrono::high_resolution_clock::now(); - const auto duration_ns = std::chrono::duration_cast(end_time - start_time); - return duration_ns.count(); + const auto start_time = std::chrono::steady_clock::now(); + for (int i = 0; i < n; i++) { + std::cout << i << "\n"; + } + const auto end_time = std::chrono::steady_clock::now(); + const auto duration_ns = + std::chrono::duration_cast(end_time - + start_time); + return duration_ns.count(); } int main() { - int endl_duration = endl_each_time(); - int new_line_duration = new_line_each_time(); - std::cout << "endl_each_time duration: " << endl_duration << "ns" << std::endl; - std::cout << "new_line_each_time duration: " << new_line_duration << "ns" << std::endl; - return 0; + int endl_duration = endl_each_time(); + int new_line_duration = new_line_each_time(); + std::cout << "endl_each_time duration: " << endl_duration << "ns" + << std::endl; + std::cout << "new_line_each_time duration: " << new_line_duration << "ns" + << std::endl; + return 0; } ``` @@ -238,8 +245,8 @@ The following example shows the duration for printing 100,000 numbers with `std: ```shell ... 9999 - endl_each_time duration: 9717ns - new_line_each_time duration: 8896ns + endl_each_time duration: 15699ns + new_line_each_time duration: 10613ns ``` diff --git a/website/docs/cpp/guess-number.md b/website/docs/cpp/guess-number.md new file mode 100644 index 0000000..ed67be1 --- /dev/null +++ b/website/docs/cpp/guess-number.md @@ -0,0 +1,542 @@ +--- +title: "Building a Guess Number Game" +--- + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + +这篇博客记录了我跟着 [C++ Beginner's Guide](https://hackingcpp.com/cpp/beginners_guide.html) 学习 C++ 的第一天,并通过一个猜数字游戏的实践来加深对 I/O、基本数据类型、控制流等基础知识的理解。 + +## 准备开发环境 + +在 macOS 上,可以直接使用 clang/clang++ 来编译 C++ 代码,也可以使用 gcc/g++ 命令,因为他们指向的均为 clang: + +```bash +# highlight-next-line +> gcc --version +Apple clang version 14.0.0 (clang-1400.0.29.202) +Target: arm64-apple-darwin22.3.0 +Thread model: posix +InstalledDir: /Library/Developer/CommandLineTools/usr/bin +# highlight-next-line +> g++ --version +Apple clang version 14.0.0 (clang-1400.0.29.202) +Target: arm64-apple-darwin22.3.0 +Thread model: posix +InstalledDir: /Library/Developer/CommandLineTools/usr/bin +# highlight-next-line +> clang --version +Apple clang version 14.0.0 (clang-1400.0.29.202) +Target: arm64-apple-darwin22.3.0 +Thread model: posix +InstalledDir: /Library/Developer/CommandLineTools/usr/bin +# highlight-next-line +> clang++ --version +Apple clang version 14.0.0 (clang-1400.0.29.202) +Target: arm64-apple-darwin22.3.0 +Thread model: posix +InstalledDir: /Library/Developer/CommandLineTools/usr/bin +``` + +你也可以使用 [`cpp.sh`](https://cpp.sh) 在线运行 C++ 代码。其它平台的安装方式可以参考 [C++ Development Setup](https://hackingcpp.com/cpp/tools/beginner_dev_setup.html)。 + +## 学习基础知识 + +首先,参考 [Hello World](https://hackingcpp.com/cpp/hello_world.html) 学习如何编写、编译并运行一个简单的 C++ 程序。 + +1. 创建一个名为 `hello.cpp` 的文件,内容如下: + + ```cpp title="hello.cpp" + #include + int main() + { + std::cout << "Hello World!\n"; + } + ``` + +2. 将 `hello.cpp` 编译为可执行文件 `hello` 并运行: + + ```bash + g++ hello.cpp -o hello && ./hello + + # Hello World! + ``` + +第一个简单的 `hello.cpp` 程序主要包含了以下几个部分: + +- `#include `:包含了 C++ 标准库中的 `iostream` 头文件,该头文件包含了 `std::cout` 和 `std::cin` 等用于输入输出的对象。 +- `int main()`:程序的入口函数,所有的 C++ 程序都必须包含一个 `main` 函数。 +- `std::cout << "Hello World!\n";`:输出字符串 `"Hello World!"`,并在末尾添加一个换行符 `\n`。 + +### 输入与输出 + +C++ 中的输入输出可以通过 [`std::cin`](https://en.cppreference.com/w/cpp/io/cin) 和 [`std::cout`](https://en.cppreference.com/w/cpp/io/cout) 对象来实现: + +- `std::cin`:用于从标准输入(通常是键盘)读取数据。 +- `std::cout`:用于向标准输出(通常是屏幕)输出数据。 + +下面是一个使用 `std::cin` 和 `std::cout` 的简单示例: + +```cpp title="io.cpp" +#include + +int main() { + int a, b; + std::cout << "Enter two numbers: "; + std::cin >> a >> b; + std::cout << "The numbers you entered are: " << a << " and " << b << '\n'; +} +``` + +将 `io.cpp` 编译为可执行文件 `a.out`(未指定 `-o` 时的默认值)并运行: + +```bash +g++ --std=c++20 io.cpp && ./a.out + +# Enter two numbers: 2 3 +# The numbers you entered are: 2 and 3 +``` + +在上面的示例程序中: + +- 输入时使用了 `std::cin >> a >> b;` 语句读取两个数值并分别赋值给 `a` 和 `b`。 +- 输出时使用了 `std::cout << "The numbers you entered are: " << a << " and " << b << '\n';` 语句将 `a` 和 `b` 的值输出到屏幕上。 +- `>>` 和 `<<` 为流操作符,用于将数据从流中读取或写入到流中。 + +更多信息可以参考 [Input & Output (Basics)](https://hackingcpp.com/cpp/std/io_basics.html)。 + +### 变量声明与数据类型 + +C++ 中声明变量的语法为: + +```cpp +type variable = value; +``` + +例如,声明一个整型变量 `a` 并赋值为 `5`: + +```cpp +int a = 5; +``` + +更多信息可以参考 [Fundamental Types](https://hackingcpp.com/cpp/lang/fundamental_types.html)。 + +### 控制流 + +关于控制流的详细信息可以参考 [Control Flow (Basics)](https://hackingcpp.com/cpp/lang/control_flow_basics.html)。 + +#### 条件 + +条件语句可以通过 `if`、`else` 和 `else if` 关键字来实现,语法如下: + +```cpp +if (condition1) { + // do this if condition1 is true +} else if (condition2) { + // else this id condition2 is true +} else { + // otherwise do this +} +``` + +下面是判断输入的数字是正数、负数还是零的示例程序: + +```cpp title="condition.cpp" +#include + +int main() { + int i = 0; + std::cout << "Enter a number: "; + std::cin >> i; + if (i == 0) { + std::cout << "zero\n"; + } else if (i > 0) { + std::cout << "positive\n"; + } else { + std::cout << "negative\n"; + } +} +``` + +将 `condition.cpp` 编译为可执行文件并运行: + +```bash +g++ --std=c++20 condition.cpp && ./a.out + +#Enter a number: 0 +# zero + +# Enter a number: 1 +# positive + +# Enter a number: -1 +# negative +``` + +### 循环 + +C++ 中的循环语句可以通过 `while`、`do while` 和 `for` 关键字来实现,使用示例如下: + +### `while` + +下面示例输出结果为 `5 6 7 8 9`: + +```cpp title="while.cpp" +#include + +int main() { + int i = 5; + while (i < 10) { + std::cout << i << ' '; + i = i + 1; + } +} +``` + + + +### `do ... while` + +下面示例输出结果为 `10`: + +```cpp title="do_while.cpp" +#include + +int main() { + int i = 10; + do { + std::cout << i << ' '; + i = i + 1; + } while (i < 10); +} +``` + + + +#### `for` + +下面示例输出结果为 `5 6 7 8 9`: + +```cpp title="for.cpp" +#include + +int main() { + for (int i = 5; i < 10; i = i + 1) { + std::cout << i << ' '; + } +} +``` + + + + +## 实践:猜数字游戏 + +编写一个猜数字游戏,程序随机生成一个 1 到 100 之间的整数,用户输入一个数字,程序判断用户输入的数字是否正确,如果错误则提示用户输入的数字过大或过小,直到用户猜中为止。 + +### 伪代码 + +``` +1. 生成一个 1 到 100 之间的随机数 +2. 用户输入一个数字 guess_number +3. 如果 guess_number == random_number,则提示用户猜中,游戏结束 +4. 如果 guess_number > random_number,则提示用户输入的数字过大,然后跳转到第 2 步 +5. 如果 guess_number < random_number,则提示用户输入的数字过小,然后跳转到第 2 步 +``` + +### 生成随机数 + +首先需要解决的问题是如何生成一个 1 到 100 之间的随机数。可以在 [cppreference.com](https://en.cppreference.com/w/cpp/io/cin) 中搜索 `rand`,找到 [`std::rand`](https://en.cppreference.com/w/cpp/numeric/random/rand) 函数。该函数用于生成一个 `[0, RAND_MAX]` 之间的随机数,并不符合我们的需求。但是,在 [`std::rand`](https://en.cppreference.com/w/cpp/numeric/random/rand) 文档的 See also 章节可以看到: + +:::info quote + +- uniform_int_distribution: produces **integer** values **evenly** distributed **across a range** +- srand: seeds pseudo-random number generator +- RAND_MAX: maximum possible value generated by `std::rand` +- randint: generates a **random integer in the specified range** + +::: + +因此可以使用 [`std::uniform_int_distribution`](https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution) 生成一个 `[min, max]` 之间的随机数。 + +```cpp title="guess_number.cpp" +#include +// highlight-next-line +#include + +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer; +} +``` + + + +### 处理用户输入 + +用户输入的数字可以通过 `std::cin` 来获取: + +```cpp title="guess_number.cpp" +#include +#include + +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + // highlight-start + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + // highlight-end + + std::cout << "The guess_number is: " << guess_number << '\n'; +} +``` + + + +### 判断用户输入的数字是否正确 + +使用 `if` 语句来判断用户输入的数字是否正确: + +```cpp title="guess_number.cpp" +#include +#include + +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + std::cout << "The guess_number is: " << guess_number << '\n'; + + // highlight-start + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + } else if (guess_number < answer) { + std::cout << "Guess a higher number!\n"; + } else { + std::cout << "Guess a lower number!\n"; + } + // highlight-end +} +``` + +### 添加循环 + +使用 `while` 循环在用户没有猜中的情况下一直提示用户输入: + +```cpp title="guess_number.cpp" +#include +#include + +int main() { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(1, 100); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + // highlight-next-line + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + // highlight-next-line + break; + } else if (guess_number < answer) { + std::cout << "Guess a higher number!\n"; + } else { + std::cout << "Guess a lower number!\n"; + } + } +} +``` + +使用示例如下: + +```bash +g++ --std=c++20 guess_number.cpp && ./a.out +# The answer is: 5 +# Guess the number: 2 +# Guess a higher number! +# Guess the number: 4 +# Guess a higher number! +# Guess the number: 5 +# Congratulations! +``` + +### 提供当前猜数字的范围 + +在提示用户输入更大或更小数值时,可以提供更准确的正确数字范围。在程序处理中,需要在 `while` 循环内记录并维护当前的数字范围: + +- 当 `guess_number < answer` 时,`min_val` 应该更新为 `guess_number + 1`,`max_val` 不变。 +- 当 `guess_number > answer` 时,`min_val` 不变,`max_val` 应该更新为 `guess_number - 1`。 + +```cpp title="guess_number.cpp" +#include +#include + +int main() { + // highlight-start + int min_val = 1; + int max_val = 100; + // highlight-end + std::random_device rd; + std::mt19937 gen(rd()); + // highlight-next-line + std::uniform_int_distribution<> distrib(min_val, max_val); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + break; + } else if (guess_number < answer) { + // highlight-start + min_val = guess_number + 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + // highlight-end + } else { + // highlight-start + max_val = guess_number - 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + // highlight-end + } + } +} +``` + +在测试时发现,如果猜测的数字不在当前范围内,程序给出的提示信息不准确。例如,如果当前范围为 `[56, 69]`,而用户输入的数字为 `52`,此时程序提示的范围为 `[53, 69]`,这是不正确的。 + +```bash +g++ --std=c++20 guess_number.cpp && ./a.out +# The answer is: 57 +# Guess the number: 50 +# Guess the number from 51 to 100 +# Guess the number: 70 +# Guess the number from 51 to 69 +# Guess the number: 55 +# Guess the number from 56 to 69 +# Guess the number: 52 +# Guess the number from 53 to 69 +``` + +为了解决这个问题,需要在更改 `min_val` 或 `max_val` 时,判断取值是否在当前范围内: + +```cpp title="guess_number.cpp" +#include +#include + +int main() { + int min_val = 1; + int max_val = 100; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(min_val, max_val); + int answer = distrib(gen); + + std::cout << "The answer is: " << answer << '\n'; + + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + break; + } else if (guess_number < answer) { + // highlight-next-line + min_val = guess_number < min_val ? min_val : guess_number + 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + } else { + // highlight-next-line + max_val = guess_number > max_val ? max_val : guess_number - 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + } + } +} +``` + +修改后的程序输出如下: + +```bash +g++ --std=c++20 guess_number.cpp && ./a.out +# The answer is: 57 +# Guess the number: 50 +# Guess the number from 51 to 100 +# Guess the number: 70 +# Guess the number from 51 to 69 +# Guess the number: 55 +# Guess the number from 56 to 69 +# Guess the number: 52 +# Guess the number from 56 to 69 +``` + +### 整体代码 + +```cpp title="guess_number.cpp" +#include +#include + +int main() { + int min_val = 1; + int max_val = 100; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> distrib(min_val, max_val); + int answer = distrib(gen); + + while (true) { + int guess_number; + std::cout << "Guess the number: "; + std::cin >> guess_number; + + if (guess_number == answer) { + std::cout << "Congratulations!\n"; + break; + } else if (guess_number < answer) { + min_val = guess_number < min_val ? min_val : guess_number + 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + } else { + max_val = guess_number > max_val ? max_val : guess_number - 1; + std::cout << "Guess the number from " << min_val << " to " << max_val + << '\n'; + } + } +} +``` diff --git a/website/docs/cpp/stringtoint-stringstream.md b/website/docs/cpp/stringtoint-stringstream.md new file mode 100644 index 0000000..e35029c --- /dev/null +++ b/website/docs/cpp/stringtoint-stringstream.md @@ -0,0 +1,302 @@ +--- +title: "Get an Integer from a String Using string stream" +description: "During the CS 106L course, I learned a lot of C++ streams. This document describes how to use basic_stringstream to get an integer from a string." +--- + +```mdx-code-block +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +``` + +During the CS 106L course, I learned a lot of C++ streams. This document describes how to use [`basic_stringstream`](https://en.cppreference.com/w/cpp/io/basic_stringstream) to get an integer from a string. + +:::info quote + +The basic unit of communication between a program and its environment is a _stream_. A stream is a channel between a _source_ and _destination_ which allows the source to push formatted data to the destination. + +—— [CS 106L Course Reader](https://web.stanford.edu/class/cs106l/full_course_reader.pdf) + +::: + +## Overview of string stream + +To create a string stream, we need to include the `` header file. Then, we can create a string stream object using the following code: + +```cpp +#include + +int main() { + std::istringstream iss("9.15 pounds."); + std::ostringstream oss("The price of the shirt is "); +} +``` + +For C++ beginners, we usually use `std::cin` to get input from the user and use `std::cout` to output something to the console. Here, `std::cin` and `std::cout` are both streams. The following is an example: + +```cpp +#include + +int main() { + int value; + std::cin >> value; + std::cout << value << std::endl; +} +``` + +In the preceding example, `>>` is the stream **extraction** operator, and `<<` is the stream **insertion** operator. For string streams, we also use `>>` and `<<` to extract and insert data. + +To extract the price and unit from the string stream `iss`, use the following code: + + + + + ```cpp + #include + #include + + int main() { + std::istringstream iss("9.15 pounds."); + std::ostringstream oss("The price of the shirt is "); + double price; + std::string unit; + iss >> price >> unit; + std::cout << oss.str() << price << " " << unit << std::endl; + } + ``` + + + + + + + ```text + The price of the shirt is 9.15 pounds. + ``` + + + + +What is the behavior of `iss >> price >> unit`? We can modify the type of `price` to `int` and see what happens: + + + + + ```cpp + #include + #include + + int main() { + std::istringstream iss("9.15 pounds."); + std::ostringstream oss("The price of the shirt is "); + // highlight-next-line + int price; + std::string unit; + iss >> price >> unit; + std::cout << oss.str() << price << " " << unit << std::endl; + } + ``` + + + + + + + ```text + The price of the shirt is 9 .15 + ``` + + + + +The output shows that the value of `price` is `9`, and the value of `unit` is `.15`. This is because the `>>` operator will stop extracting data when it encounters a whitespace or an invalid character for the type. In this case, the `>>` operator in `iss >> price` stops extracting data at `.`. Then, the `>>` operator in `iss >> unit` extracts `.15` into `unit` and stops extracting data at ` `. + +## Implement `stringToInteger()` without error-checking + +Now, we can use `>>` to extract an integer from a string. Let's implement a function `stringToInteger()` to convert a string to an integer. The code is as follows: + + + + + ```cpp name="stringToInteger.cpp" + #include + #include + + int stringToInteger(const std::string& str) { + std::istringstream iss(str); + int value; + iss >> value; + return value; + } + + int main() { + std::string str = "123"; + int value = stringToInteger(str); + std::cout << "The value is: " << value << std::endl; + } + ``` + + + + + + + ```text + The value is: 123 + ``` + + + + +## Stream state + +What if the string contains invalid characters? For example, the string `"123abc"` contains non-numeric characters. In this case, the `>>` operator stops extracting data at `a`. Then, the value of `value` will be `123`. However, we want to return an error message to the user. To do this, we need to check whether any error occurs during the extraction process. + +A new concept is introduced here: **stream state**. There are four stream states: + +- **good**: no error occurs. The I/O operations are available. +- **eof**: reaching the **end of the stream**. +- **fail**: the input/output operation failed and all future operations frozen, such as the type mismatch. +- **bad**: **irrecoverable** stream error. For example, the file you are reading is deleted suddenly. + +To check the stream state, we can use the `good()`, `eof()`, `fail()`, and `bad()` functions. The following shows some examples: + + + + + ```cpp + #include + #include + #include + + void get_stream_state(std::istringstream &iss) { + if (iss.good()) { + std::cout << "G"; + } + if (iss.eof()) { + std::cout << "E"; + } + if (iss.fail()) { + std::cout << "F"; + } + if (iss.bad()) { + std::cout << "B"; + } + std::cout << std::endl; + } + + int stringToInteger(const std::string &str) { + std::istringstream iss(str); + std::cout << "Before: "; + get_stream_state(iss); + int value; + iss >> value; + std::cout << "After: "; + get_stream_state(iss); + return value; + } + + int main() { + std::vector test_strings{"123", "123abc", "abc123", ""}; + for (const auto &str : test_strings) { + std::cout << "stringToInteger(\"" << str << "\"):\n" + << stringToInteger(str) << std::endl; + } + } + ``` + + + + + + + ```text + stringToInteger("123"): + Before: G + After: E + 123 + stringToInteger("123abc"): + Before: G + After: G + 123 + stringToInteger("abc123"): + Before: G + After: F + 0 # undefined behavior + stringToInteger(""): + Before: G + After: EF + -514166240 # undefined behavior + ``` + + + + +## Implement `stringToInteger()` with error-checking + +Using the stream state, we can implement `stringToInteger()` with error-checking. From the preceding example, to ensure that the string only contains numeric characters, we need to consider the following cases: + +- After the extraction, the stream state is `good` : the string might contain non-numeric characters after the number. +- After the extraction, the stream state is `fail`: the string contains non-numeric characters before the number. + +The following code shows how to implement `stringToInteger()` with error-checking: + +```cpp name="stringToInteger.cpp" +#include +#include + +int stringToInteger(const std::string &str) { + std::istringstream iss(str); + int result; + // highlight-start + char remain; // Record remaining characters after the integer + iss >> result; + if (iss.fail() || iss.bad()) { + throw std::domain_error("The string does not start with an integer."); + } + iss >> remain; + if (!iss.fail()) { + throw std::domain_error( + "The string contains extra characters after the integer."); + } + // highlight-end + return result; +} + +int main() { + std::string str = "123"; + int value = stringToInteger(str); + std::cout << "The value is: " << value << std::endl; +} +``` + + + +In the preceding code, the program first tries to extract an integer from the string. If the stream state is `fail` or `bad`, it throws an error. Otherwise, it tries to extract a character from the remaining string. If the stream state is not `fail`, it means that the string contains extra characters after the integer. Then, the program throws an error. + +Since `if ((iss >> result).fail())` is equivalent to `if (!(iss >> result))`, we can simplify the code as follows: + +```cpp name="stringToInteger.cpp" +#include +#include + +int stringToInteger(const std::string &str) { + std::istringstream iss(str); + int result; + char remain; // Record remaining characters after the integer + // highlight-start + if (!(iss >> result) || iss >> remain) { + throw std::domain_error( + "Failed to get integer from string. Please check the string."); + } + // highlight-end + return result; +} + +int main() { + std::string str = "123"; + int value = stringToInteger(str); + std::cout << "The value is: " << value << std::endl; +} +``` + +