解释一下字符串m进制转换成n进制

本文详细介绍了10进制转2进制和2进制转10进制的转换算法,包括原理、代码实现,并讨论了ASCII码在处理非数字字符时的应用。
摘要由CSDN通过智能技术生成

前言

        这里只是记录自己学习的过程,分享一下自己片鳞半爪的经验。为了方便理解,下面以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实际表示的值,总体逻辑是不变的。

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值