读入一个正整数。以十进制形式打印整数,每次一位数字。使用打印整数的函数执行此操作,每行一位数字。

例如:13456

1
3
4
5
6

我尝试用 for 循环来做到这一点,但我不知道如何阻止它,因为 i 值一直在变化。

我尝试将整个事情 和 /10 起来,但我不知道如何实现它。

这是我目前所写的内容:

#include <iostream>
#include <vector>

using namespace std;

int oneatatime(int number){

    //vector<int> contents; //for how much the contents of the for loop has to go through
    vector<int> forremainder; 
    vector<int> fordivide;

    int divide = number / 10; //the divided number using / operation
    int remainder = number % 10; //the remainder using % operation
    int newdigit = 0;
    int digitsum = 0;
    int newnum = 0;

    for(int i = 0; i < 5; i++){ //how would i replace the old one with the new one? // contents.size() == 1 how do we make it the same as how many numbers in the number

        fordivide[i] = divide;
        int n = fordivide[i] = divide;
        //remainder = number % 10;
        //fordivide.push_back(divide);
        //n.push_back(remainder); // will put it in the array of 

        //fordivide.push_back(divide);

        //forremainder.push_back(remainder);
        fordivide.push_back(n);

    }

    for(int i = 0; i < 5; i++){
        cout << fordivide[i] << endl;
        //cout << forremainder[i] << endl;
    }

    /*do{
        divide
    }while(newnum == 1)*/
}

int main(){
    int number = 12346;

    oneatatime(number);
}

我不知道该怎么办,请帮忙。

9

  • 4
    战术提示:如果您正在读取一个数字,但又不打算将其用作数字(例如对其进行数学运算),则最好读取字符串。对于字符串,所有数字都已分开,您只需一个循环来迭代并打印其中的所有字符即可。


    – 

  • @user4581301 我认为这项练习的重点是弄清楚如何用数字方式来完成它。


    – 

  • 2
    重要提示:除一次数字对你没有任何好处。在循环中,你需要使用模数运算符%来分离一个数字。int digit = number % 10;一旦你得到那个数字,你就除以这个数字来删除这个数字。number = number / 10;重复,直到你用完数字。这会给你一个倒着的数字,所以你必须想办法把它倒过来。递归真的很管用。


    – 

  • 2
    带有std::to_string或递归的


    – 

  • 2
    @Barmar 或者意识到找到一个合适的代表会让事情变得简单


    – 


最佳答案
4

void printDigits(unsigned int num) {
  long reversedNum = 0;
  while (num > 0) {
    reversedNum = reversedNum * 10 + num % 10;
    num /= 10;
  }
  while (reversedNum > 0) {
    std::cout << reversedNum % 10 << std::endl;
    reversedNum /= 10;
  }
}

printDigits 函数从最高位到最低位打印无符号整数 num 的数字。它首先将数字反转到新变量 reversedNum 中。

该函数将 reversedNum 初始化为 0。然后它进入一个循环,直到 num 变为 0。在每次迭代中,它使用 % 10 提取 num 的最后一位数字,通过将 reversedNum 乘以 10 并添加提取的数字将其附加到 reversedNum,并通过将 num 除以 10 删除最后一位数字。循环之后,reversedNum 包含 num 的反转版本。然后,该函数进入另一个循环,从右到左打印 reversedNum 的数字。在每次迭代中,它使用 % 10 打印 reversedNum 的最后一位数字,并通过将 reversedNum 除以 10 删除它。注意:虽然该函数首先反转数字,但并非严格要求必须这样做才能按顺序打印数字。循环可以直接打印 num 的数字,而无需反转它。

4

  • 它首先将数字反转为一个新变量 reversedNum — 如果反转后的数字无法放入 中,会发生什么unsigned int?假设无符号整数为 32 位。如果原始数字为 1234567899,会发生什么?– 反转后的数字为 9987654321,超出了 32 位整数可以容纳的范围


    – 


  • 是的,当对一个整数进行反转的时候,就会出现溢出,将 reversedNum 定义为 long 可以避免这种现象。


    – 

  • 1
    @IceRiver——旁注:long至少需要与 一样大int; 它不需要更大。 因此使用long可能无法避免这个问题。 在桌面系统上它更大,但这不是您可以绝对依赖的东西。


    – 

  • 对此要谨慎。如果你将 1000 反转为数字,则会得到 1,而不是 0001,并且不会提供正确的输出。示例:


    – 


好的代码是自我解释的(我在这里添加的评论大多是教育性的,而不是关于程序在做什么)。学习用清晰的名称编写小函数。像这样

#include <iostream>
#include <string>
#include <algorithm> // for reverse

//using namespace std; <-- don't use this

// learn to use namespaces
// and to never put code of your own in the global namespace
namespace number_output
{
    // helper and details stuff
    namespace details
    {

        // don't be afraid to make small functions
        // usually if you feel you have to write
        // comments for a few lines of code
        // you know you have to make a function
        char last_digit_of_value_as_char(unsigned int value)
        {
            char last_digit = (value % 10); 
            return (last_digit + '0');
        }

        std::string make_backward_string(unsigned int value)
        {
            std::string output;

            do
            {
                char c = last_digit_of_value_as_char(value);
                output.push_back(c);
                value /= 10;
            } while (value > 0);

            return output;
        }

    }

    std::string make_string(unsigned int value)
    {
        std::string backward_output = details::make_backward_string(value);

        // using std::reverse makes your code more readable
        // it shows what you want to do (not how it is done)
        // this is abstraction at work :)
        std::reverse(backward_output.begin(), backward_output.end());
        return backward_output;
    }

} // namespace number_output

int main()
{
    std::cout << number_output::make_string(0) << "\n"; // test a corner case
    std::cout << number_output::make_string(12345) << "\n";
}

2

  • 😉


    – 

  • @Jarod 对于生产代码,当然:)


    – 


为了好玩,我们用一些递归来解决这个问题。是的,递归通常被视为“不好”,但永远int不会容纳足够的数字让这个简单的程序溢出堆栈,而这有一个好处,那就是实现起来非常简单。

#include <iostream>

void print_digits(int num) {
    if (num == 0) return;

    print_digits(num / 10);

    std::cout << (num % 10) << std::endl;
}

int main() {
    print_digits(123456);
}

输出:

1
2
3
4
5
6

这是有效的,因为打印之前的递归使得最后一次递归调用的输出首先出现。

如果您需要以相同的顺序保存向量中的数字,则可以采用相同的方法。

void save_digits(int num, std::vector<int>& vec) {
    if (num == 0) return;

    save_digits(num / 10, vec);

    vec.push_back(num % 10);
}

正如评论中所述,这不适用于传递它0,但在任何其他情况下,我们都不希望前导零。克服这个问题的一种方法是传递一个标志,指示我们是否处于初始调用中。

void save_digits(int num, std::vector<int>& vec, bool init_call=true) {
    if (num == 0 && !init_call) return;

    save_digits(num / 10, vec, false);

    vec.push_back(num % 10);
}

3

  • print_digits(0);什么都不打印…(我在主评论中添加了带有递归的演示链接。)


    – 

  • 已在编辑中解决。谢谢。


    – 

  • 太复杂了 🙂if (num >= 10 /* or num / 10 != 0 */) {recursion(num / 10); } job(num % 10);


    – 


这个答案是针对初学者的。它避免使用标准库中最基本的类型以外的所有类型。这就是为什么std::vector选择而不是 的原因std::stack

这篇回答很长,因为它简要讨论了隐式类型转换整型文字伪代码等主题,这些主题在解决方案的开发过程中出现,而原帖作者才刚刚开始了解这些主题。当我还是他这个水平的学生时,我很想读到这样的回答。

不幸的是,这意味着高级程序员和专业人士可能会决定:TL;DR。没关系。我明白了。您可以在此答案的末尾看到完成的程序。

消除警告

当我编译原始问题中提供的代码时,收到此警告:

警告 C4715:’oneatatime’:并非所有控制路径都返回值

一般来说,你应该把警告信息当做错误,然后修改程序,让它编译时不出现警告。在这里,这很容易。

  • 解决方案1:将函数的返回类型改为oneatatimevoid这是我采用的解决方案。
  • 解决方案 2:返回任意值,例如 0。

避免using namespace std;

接下来,我删除了这一行using namespace std;。专业程序员会避免使用 using 指令,学生程序员也应该避免使用。遗憾的是,许多教科书都采取了偷懒的方法,教初学者使用它。

可以将标准库中的名称加上前缀。除了少数情况,通常与所谓的参数相关查找std::有关,这是专业人士所做的。

对于这个程序,我std::从标准库中添加了每个名称。

避免std::endl

这个输出操纵器做两件事。

  1. 将换行符写入输出流。
  2. 刷新与流关联的输出缓冲区。

出于效率原因,流 I/O 通常采用缓冲方式。不是一次将一个字符写入流,而是将字符收集到缓冲区中。稍后,将缓冲区作为块发送到流。通常,这会自动发生,可能是因为缓冲区已满,或者可能是因为缓冲区与已收到读取请求的输入流绑定。刷新缓冲区是将其内容写入流的过程。

使用std::cout,就没有必要(并且效率低下)强制刷新缓冲区。因此,您应该避免std::endl。相反,只需直接输出换行符即可。这就是我在这个程序中所做的。所有出现的endl都已替换为'\n'

std::cout << '\n';  // Avoid `std::endl` by outputting the newline character.

使用类型unsigned long long

这个答案假设作业要求学生使用商和余数运算来剥离整数的数字。我们在 OP 的代码中看到了这一点:

// From the OP:
int divide = number / 10;     //the divided number using / operation
int remainder = number % 10;  //the remainder using % operation

问题规范指定整数始终为正数。因此,使用无符号整数类型来存储它们是有意义的。这样,您就无需检查负数。之所以unsigned long long选择该类型,是因为它支持最广泛的值范围。

在 OP 中,所有出现的 typeint都已更改为 type unsigned long long

商和余数

给定一个整数number,您可以使用余数运算符提取最右边的数字%

// Extract rightmost digit.
unsigned long long number = 13456ull;
auto const rightmost_digit = number % 10ull;

提取最右边的数字后,可以使用整数除法将其从原始数字中删除:

// Discard the rightmost digit of `number`.
number /= 10ull;

当变量中只剩下一位数字时number,除以 10 的结果为 0。因此,测试 0 就是确定没有更多数字可提取的方法。

文字后缀ull意味着unsigned long long

在上面的代码中,后缀ull表示类型的文字值unsigned long long。由于提升隐式转换初始化器和表达式中使用的操作数类型的一组复杂规则,事实证明ull可以省略。

// The integer literal 13456 will be given type `int`. So will the literal 10.
// In the code below, both are implicitly converted to type `unsigned long long`.
unsigned long long number = 13456;
auto const rightmost_digit = number % 10;

由于它们足够小,可以存储在 类型的对象中int,因此整数文字13456 和 10(上文)将被赋予 类型int。如果整数文字的值太大而无法存储在 中,int则将根据需要为其分配更宽的类型,例如longlong long

使用复制初始化不同算术类型的变量(例如13456时,它将被隐式转换为该类型。同样,当在混合模式表达式中使用时,例如,编译器将隐式提升和/或转换一个或两个操作数为适合计算的通用类型。intnumber10number % 10

在您掌握整数提升隐式类型转换的规则之前,我的建议是避免使用混合模式表达式,即混合不同类型的操作数。这就是为什么我在 OP 中使用文字后缀的原因ull,尽管如果省略后缀,程序可以正常工作ull

即使在今天,我仍然经常避免使用混合模式,这样我就不必分析我原本要求编译器进行的隐式类型转换。

循环提取数字

OP 中的程序使用以下 for 语句来提取数字。这是不正确的,因为它假设正在处理的数字中有 5 位数字。

for (int i = 0; i < 5; i++) {
    // ...
}

正确的循环测试是否number为 0。

问题规范保证number以非零值开始,因此它为 0 的唯一方式是其所有数字都已被提取。因此,我们希望继续循环,只要number != 0ull

void oneatatime(unsigned long long number)
{
    // Precondition: number > 0
    std::vector<unsigned long long> digits;
    while (number != 0ull)
    {
        // Extract rightmost digit, and save in vector `digits`.
        // Discard rightmost digit.
    }

    // ...
}

因为问题规范承诺number不是 0,所以上述循环“符合规范”。但是,很容易同时满足规范并扩展解决方案以处理 0。这可以通过将测试移到循环底部来实现,即使用do-while循环。

void oneatatime(unsigned long long number)
{
    std::vector<unsigned long long> digits;
    do {
        // Extract rightmost digit, and save in vector `digits`.
        // Discard rightmost digit.
    } while (number != 0ull);

    // ...
}

您可能已经学过伪代码。这是大多数教科书都过度强调的一个主题。它们要求学生用伪代码写出整个函数,然后在将其翻译成 C++ 源代码时一行一行地删除它。

在现实世界中,专业人士会将伪代码与真实代码混合在一起,经常将伪代码写成注释。这就是我上面所做的。

这两行是伪代码:

// Extract rightmost digit, and save in vector `digits`.
// Discard rightmost digit.

这样做的好处是你不会浪费时间写那些必须删除的东西。

  • “伪代码”注释通常会留在完成的程序中。
  • 否则,它们可以转换为函数调用。

如果您知道您将要调用一个函数,则可以跳过注释步骤。在这种情况下,“伪代码”可以直接写成函数调用。对于每个这样的调用,添加一个存根函数(稍后将对其进行编码)来处理工作。

我认为对于这个任务来说这有点小题大做,但如果我要使用函数的话我可能会这样做:

void oneatatime(unsigned long long number)
{
    std::vector<unsigned long long> digits;
    do {
        // I think of these function calls as "psuedocode."
        // I also think of them as "finished code."
        extract_and_save_rightmost_digit(number, digits);
        discard_rightmost_digit(number);
    } while (number != 0ull);

    // ...
}

再次注意,使用函数调用代替伪代码意味着程序员不必浪费时间编写稍后必须删除的内容。无论您是在注释中还是以函数调用的形式编写伪代码,您编写的都是完成的代码,这些代码通常在最终程序中保持不变。

综合起来

我们知道如何提取和丢弃数字。我们知道循环是如何工作的。将它们结合起来只是一小步。当以下循环完成时,变量中的数字number将被提取并存储在向量中digits

void oneatatime(unsigned long long number)
{
    std::vector<unsigned long long> digits;
    do {
        // Extract rightmost digit, and save in vector `digits`.
        auto const rightmost_digit = number % 10ull;
        digits.push_back(rightmost_digit);

        // Discard rightmost digit.
        number /= 10ull;
    } while (number != 0ull);

    // Output vector in reverse order.
    // ...
}

最后一项任务是输出向量。上述循环将数字以相反的顺序放入向量中。数字的最右边的数字首先存储在向量的最前面。数字的最左边的数字最后存储在向量的后面。因此,我们必须以相反的顺序输出向量。

有几种方法可以实现这一点。一种简单的方法是将向量视为LIFO 堆栈,并从向量的后面弹出元素。(LIFO表示后进先出。)

// Output vector in reverse order.
while (!digits.empty()) {
    std::cout << digits.back() << '\n';
    digits.pop_back();
}

或者,您可以使用下标。通常,下标使用 type std::size_t,您可以通过包含 header 来获得它<cstddef>

(下标中也定义了一种类型std::vector,但对于这个初学者级别的答案,我们将忽略它。此外,在我见过的每个实现中,它都是类型别名std::size_t。)

// Output vector in reverse order.
for (std::size_t i = digits.size(); i > 0u; --i)
    std::cout << digits[i - 1u] << '\n';

可以使用常见的 C++ 习惯用法(倒计时循环)更简洁地编写下标循环。

以下循环依赖于这样一个事实:在编译器期望布尔值的地方使用整数表达式会导致整数被隐式转换为bool。在这种情况下,0 表示false,任何非零值表示true

循环也依赖于后缀减法。在最后一次迭代之前,当i等于 1 时,表达式的i--值也为 1。因此,循环体被执行。然而,在进入循环之前,会发生后缀减法,从而使i循环内部等于 0。下一次i--测试时,它将具有值 0,循环将结束。

如果由于某种原因,向量的大小为 0(这里不可能发生),那么i将被初始化为 0。表达式i--也将具有值 0,并且循环主体将不会被执行。

// Output vector in reverse order.
for (auto i = digits.size(); i--;)  // must use postfix decrement!
    std::cout << digits[i] << '\n';

最后,您可以使用反向迭代器

// Output vector in reverse order.
for (auto it{ digits.crbegin() }; it != digits.crend(); ++it)
    std::cout << *it << '\n';

完整的程序

// main.cpp
#include <iostream>
#include <vector>

void oneatatime(unsigned long long number)
{
    std::vector<unsigned long long> digits;
    do {
        // Extract rightmost digit, and save in vector `digits`.
        auto const rightmost_digit = number % 10ull;
        digits.push_back(rightmost_digit);

        // Discard rightmost digit.
        number /= 10ull;
    } while (number != 0ull);

    // Output vector in reverse order.
    while (!digits.empty()) {
        std::cout << digits.back() << '\n';
        digits.pop_back();
    }
}

int main() {
    unsigned long long number = 13456ull;
    oneatatime(number);
}
// end file: main.cpp

输出

根据需要,数字一次输出一个:

1
3
4
5
6

只是为了好玩

这是 function 的四行版本oneatatime。它使用std::to_string来自 header 的内容<string>。这可能是在生产代码中使用的内容。

void oneatatime(unsigned long long const number) {
    auto const s{ std::to_string(number) };
    for (auto const& c : s)
        std::cout << c << '\n';
}

2

  • 是的,我考虑过这一点。我可以使用 do-while 循环来处理 0。但是请注意,原贴的第一句话是“读入一个正整数”。因此,我将参数为正数视为函数的先决条件,需要由调用者强制执行(如果有的话)。此外,我的回答在两处指出参数保证为正数。无论如何,我可能应该使用 do-while。希望初学者仍然会喜欢它。


    – 


  • @Jarod42 查看更新后的答案。我对其进行了编辑,以包括do-while


    –