前言
这里只是记录自己学习的过程,分享一下自己片鳞半爪的经验。为了方便理解,下面以10进制转成2进制以及2进制转成10进制为例
这里先给出代码
// 由m进制转换成n进制
string conversion(string str, int m, int n) {
int len = str.size(), remainder ;
string ans = "";
for (int i = 0; i < len; ) {
remainder = 0;// remainder是 a/b 的余数,
// 在 a/b 的过程中我们要不断更新商的值,所以要不断更新str[j]
for (int j = i; j < len; j ++) {
int pos = (remainder * m + str[j] - '0') % n;//计算当前余数
str[j] = (remainder * m + str[j] - '0') / n + '0';//更新该位商的值
remainder = pos;//更新余数
}
ans += (remainder + '0');
// 如果 str[i] == 0 说明商在该位上没有值,比如 0001,那值就是 1,跳过去就好了
while (str[i] == '0') i ++;
}
return ans;
}
刚开始学的我对这串代码也是很陌生, 最好的理解就是模拟其运行方式,手动计算一遍。
原理详解
首先我们知道,从10进制转成2进制是一个不断除2取余数的过程,如下图所示
图1
我们不难发现用这种方式计算出来的2进制与实际结果是相反的,也就是说如果我们初始化一个数组存放每一位的值,其结果是实际相反的。那为什么会相反呢,因为我们第一个%2的时候,实际上是决定了二进制上的最低位是多少,当我们/2之后,再%2实际上是决定了二进制第二位上的数是多少,以此类推,所以会出现相反的现象。
其次,我们看看2进制转10进制是如何转化的,
图2
那这种方式有什么局限性呢?其最大的问题是其n会随着位数的增大而增大,也就是当n很大时,2^n次方可能会超过unsigned long long所能表示的最大值。所以我们还能用什么方法转成10进制呢
事实上我们可以跟10进制转成2进制那样,采用除法取余转换,只不过十进制的相邻位上的数相差10,而2进制上的相邻位相差2而已,什么意思呢,我们看下面的例子
图3
不难发现,其结果还是与实际上相反,原理同2进制转10进制一样。
代码详解
首先我们来看看代码主体的两层for循环
for (int i = 0; i < len; ) {
remainder = 0;// remainder是 a/b 的余数
// 在 a/b 的过程中我们要不断更新商的值,所以要不断更新str[j]
for (int j = i; j < len; j ++) {
int pos = (remainder * m + str[j] - '0') % n;//计算当前余数
str[j] = (remainder * m + str[j] - '0') / n + '0';//更新该位商的值
remainder = pos;//更新余数
}
ans += (remainder + '0');
// 如果 str[i] == 0 说明商在该位上没有值,比如 0001,那值就是 1,跳过去就好了
while (str[i] == '0') i ++;
}
以10进制转成2进制为例
通过更新i的方式,我们可以知道外层for循环实际上就是什么时候字符串为"0",什么时候退出。以图1为例的话,外层循环就是执行6次。
内层for循环就是不断除2(更新所在数位的值),%2(更新余数的值),直到计算完成为止。也就是图1所在的6个除法过程的其中一个。最后一次更新余数的值,也就是其整除后模剩下的值,这个值(remainder)就是其2进制在某数位上的值。(也就是图1中图标⬇指向的值)。
最后
上述代码只适合10进制以内的转换,因为11进制到36进制涉及到字母a-z(区别大小写),且字母a-z的ASCII码值跟字符'0'到'9'并不相邻,所以上述代码的逻辑需要稍微更改一下。如这里有一道算法题
首先我们先看看字母与数字间的ASCII值是多少
#include <iostream>
using namespace std;
int main(){
char a='0';
char b='9';
cout<<(int)a<<' '<<(int)b<<endl;//48 57
char c='a';
char d='z';
cout<<(int)c<<' '<<(int)d<<endl;//97 122
char e='A';
char f='Z';
cout<<(int)e<<' '<<(int)f<<endl;//65 90
return 0;
}
ps:为了方便计算,我们可以将输入的字符串统一转换成大写的,再调用conversion函数,输出统一为小写的。如果想修改这种方式,读者可以自己尝试修改~
转换可以直接使用c++的stl
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main(){
string s;
cin>>s;
// 4个形参分别为,前两个分别为指定要转换的字符串范围(这里是起始跟结束位置)
// 第三个为转换后要写回哪里,这里写回字符串的原起始位置,也就是直接在原字符串修改
// 第四个为改成小写,要大写的话就是::toupper
transfrom(str.begin(),str.end(),str.begin(),::tolower);
cout<<s<<endl;
return 0;
}
于是我们的思路就变成遍历字符串str的每一位时,需判断当前位是字母还是数字,然后分别计算其代表的实际值是多少,总体逻辑是不变的,无外乎多添加几个if,else。
// 这串代码的逻辑是大写输入,小写输出
string conversion(string s, int m, int n) {
string ans;
int remainder;// 保存余数
for (int i = 0; i < s.size();) {
remainder = 0;
for (int j = i; j < s.size(); ++j) {
int pos;//暂存remainder的值
int v;// 暂存整除的值
if (s[j] >= 'A' && s[j] <= 'Z') {
pos = (remainder * m + s[j] - 55) % n;// 更新余数的值
v = (remainder * m + s[j] - 55) / n ; // 更新整除的值
} else {
pos = (remainder * m + s[j] - '0') % n;
v = (remainder * m + s[j] - '0') / n ;
}
//判断是否大于10进行替换
if (v < 10)
s[j] = v + '0';
else s[j] = v + 55;
remainder = pos;
}
if (remainder >= 10)
ans += (remainder + 87);
else {
ans += (remainder + '0');
}
while (s[i] == '0')++i;
}
return ans;
}
现在解释一下为什么+-55,和+87的,因为我们知道不管是a还是A都代表的是着10(通过上文可知其ASCII分别是97和65)于是10+87=97,也就对应着其ASCII码值a了,65-55=10,也就对应着A实际表示的值了,z还是Z都是代表35(ASCII码分别是122和90)于是35+87=122,也就对应着其ASCII码值z了,90-55=35,也就对应着Z实际表示的值,总体逻辑是不变的。