C++ 整数拆位(提取个十百千位)完全技术讲义
在编程入门与算法题(如数位和、回文数、水仙花数、大数处理)中,拆位(Digit Extraction) 是最基础但极其关键的核心技能。
目录
一、拆位的核心数学原理
拆位的本质是利用十进制的 整数除法(/) 和 取模运算(%):
x % 10(取个位):获取当前数字的最末尾一位(个位)。x / 10(降位/剔除末尾):在整数除法中会舍弃小数部分,相当于将整个数向右移一位(删掉当前的个位数)。
以提取 1234 的每一位为例:
1234 % 10 = 4 ---> 拿到【个位 4】
1234 / 10 = 123 ---> 降位,去掉 4,剩下 123
123 % 10 = 3 ---> 拿到【十位 3】
123 / 10 = 12 ---> 降位,去掉 3,剩下 12
12 % 10 = 2 ---> 拿到【百位 2】
12 / 10 = 1 ---> 降位,去掉 2,剩下 1
1 % 10 = 1 ---> 拿到【千位 1】
1 / 10 = 0 ---> 降位,结果为 0,提取结束!
二、固定位数拆解
若已知数字的位数区间(例如明确是三位数或四位数),可以直接通过数学公式单行提取:
1. 三位数提取示例(以 $n = 358$ 为例)
#include <iostream>
int main() {
int n = 358;
int ge = n % 10; // 358 % 10 = 8
int shi = (n / 10) % 10; // 358 / 10 = 35, 35 % 10 = 5
int bai = n / 100; // 358 / 100 = 3 (因为是最高位,无需再 % 10)
std::cout << "百位: " << bai << "\n"
<< "十位: " << shi << "\n"
<< "个位: " << ge << std::endl;
return 0;
}
2. 四位数提取公式
int ge = n % 10;
int shi = (n / 10) % 10;
int bai = (n / 100) % 10;
int qian = n / 1000;
通用第 $k$ 位公式(设个位为第 0 位,十位为第 1 位……): $$\text{Digit}_k = \left( \frac{n}{10^k} \right) \pmod{10}$$
三、通用任意长度拆解
当数字长度未知(如输入的 int 或 long long 不定长),通常配合循环操作。
1. 逆序提取(从个位到最高位)
这是最自然的拆位方式,最右边的位最先被提取出来:
#include <iostream>
#include <vector>
int main() {
int num = 987654;
int temp = num;
std::cout << "逆序拆位: ";
while (temp > 0) {
int digit = temp % 10; // 1. 提取末尾位
std::cout << digit << " ";
temp /= 10; // 2. 剥离末尾位
}
// 输出: 4 5 6 7 8 9
std::cout << std::endl;
return 0;
}
2. 正序提取(从最高位到个位)
方法 A:利用 std::vector 逆转或头插
#include <iostream>
#include <vector>
#include <algorithm>
std::vector<int> getDigits(int n) {
std::vector<int> digits;
if (n == 0) return {0};
while (n > 0) {
digits.push_back(n % 10);
n /= 10;
}
// 当前 digits 是 [个, 十, 百...],翻转后即为 [最高位 ... 个位]
std::reverse(digits.begin(), digits.end());
return digits;
}
int main() {
auto digits = getDigits(12345);
for (int d : digits) {
std::cout << d << " "; // 输出: 1 2 3 4 5
}
std::cout << std::endl;
return 0;
}
方法 B:纯数学正序提取(通过计算最高权值)
#include <iostream>
void printDigitsForward(int n) {
if (n == 0) {
std::cout << 0 << std::endl;
return;
}
// 1. 寻找最高位对应的 10 的幂(例如 1234 -> 1000)
int power = 1;
while (n / power >= 10) {
power *= 10;
}
// 2. 从高向低逐位剥离
while (power > 0) {
int digit = n / power; // 取得最高位
std::cout << digit << " ";
n %= power; // 删去最高位
power /= 10; // 权值降低一位
}
std::cout << std::endl;
}
int main() {
printDigitsForward(12345); // 输出: 1 2 3 4 5
return 0;
}
四、负数与 0 的边界处理
在 C++11 标准中,对于负数整除与求模:
* 截断规则:向零取整(Truncate toward zero)。
* 负数取模结果可能为负数:例如 -123 % 10 在 C++ 中为 -3,-123 / 10 为 -12。
1. 0 的处理(推荐 do-while)
如果使用普通的 while (n > 0),当输入 0 时循环体会一次都不执行。使用 do-while 结构可以完美解决:
#include <iostream>
#include <cmath>
void solveAnyInteger(int n) {
// 转换为正数处理(注意:INT_MIN 需考虑溢出,通常使用 long long 或单独特判)
long long val = std::abs(static_cast<long long>(n));
do {
int digit = val % 10;
std::cout << digit << " ";
val /= 10;
} while (val > 0);
std::cout << std::endl;
}
int main() {
solveAnyInteger(0); // 正常输出: 0
solveAnyInteger(-520); // 正常输出: 0 2 5
return 0;
}
五、字符/字符串法拆位
当输入的数字非常大(超过 long long 的范围,例如 100 位的大整数),无法使用数值整除,此时应以字符串形式读取并拆位。
原理:字符 ASCII 码相减
字符 '0' 到 '9' 的 ASCII 码是连续的,只要用当前字符减去 '0',即可直接转换为对应的数字:
$$\text{数值} = \text{字符} - \text{'0'}$$
#include <iostream>
#include <string>
int main() {
// 输入一个超出 64 位整数范围的超长数字
std::string bigNum = "987654321987654321987654321";
std::cout << "正序遍历大整数的每一位: ";
for (char ch : bigNum) {
int digit = ch - '0'; // 关键转换: '9' - '0' = 9
std::cout << digit << " ";
}
std::cout << std::endl;
return 0;
}
六、四大经典实战应用
1. 计算数位和(Sum of Digits)
输入一个正整数,求其各位数字之和。
#include <iostream>
int getDigitSum(int n) {
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
int main() {
std::cout << "12345 的数位和为: " << getDigitSum(12345) << std::endl; // 输出: 15
return 0;
}
2. 整数反转(Number Reversal)
输入
1234,输出4321(LeetCode 经典题目原型)。
#include <iostream>
int reverseNumber(int n) {
int rev = 0;
while (n > 0) {
// 核心:上一轮的结果乘 10 + 当前个位数
rev = rev * 10 + (n % 10);
n /= 10;
}
return rev;
}
int main() {
std::cout << "12345 反转后为: " << reverseNumber(12345) << std::endl; // 输出: 54321
return 0;
}
3. 水仙花数(Narcissistic Number)
一个三位数,其各位数字立方和等于该数本身(如 $153 = 1^3 + 5^3 + 3^3$)。
#include <iostream>
int main() {
std::cout << "所有的三位水仙花数: ";
for (int i = 100; i <= 999; ++i) {
int a = i / 100; // 百位
int b = (i / 10) % 10; // 十位
int c = i % 10; // 个位
if (a * a * a + b * b * b + c * c * c == i) {
std::cout << i << " "; // 输出: 153 370 371 407
}
}
std::cout << std::endl;
return 0;
}
4. 回文数判定(Palindrome Number)
判断一个数正着读和反着读是否相同(如
12321)。
#include <iostream>
bool isPalindrome(int x) {
if (x < 0) return false; // 负数不是回文数
if (x % 10 == 0 && x != 0) return false; // 末尾为0但本身不为0的一定不是
int reversedHalf = 0;
// 只反转后半部分,避免整个反转导致溢出
while (x > reversedHalf) {
reversedHalf = reversedHalf * 10 + x % 10;
x /= 10;
}
// 偶数长度: x == reversedHalf (例如 1221 -> x=12, rev=12)
// 奇数长度: x == reversedHalf / 10 (例如 12321 -> x=12, rev=123)
return (x == reversedHalf) || (x == reversedHalf / 10);
}
int main() {
std::cout << std::boolalpha;
std::cout << "12321 是回文数吗: " << isPalindrome(12321) << std::endl; // true
std::cout << "12345 是回文数吗: " << isPalindrome(12345) << std::endl; // false
return 0;
}
七、速查公式总结
| 操作 | 公式 / 代码实现 | 说明 |
|---|---|---|
| 取个位 | n % 10 |
获取最右侧的一位数 |
| 去个位 | n / 10 |
整体向右平移一位 |
| 取十位 | (n / 10) % 10 |
先去个位,再取末尾 |
| 取百位 | (n / 100) % 10 |
先去个、十位,再取末尾 |
| 取第 $k$ 位 | (n / pow(10, k)) % 10 |
$k$ 从 0 开始(0 代表个位) |
| 字符转数字 | ch - '0' |
针对单字符 '0'~'9' |
| 反转累加构造 | res = res * 10 + digit |
逆序重构整数的标准逻辑 |
—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com