刷蓝桥杯题目的时候,看到了朋友写的A+B plus的题解,研究一番之后发现代码思路很简洁,适合我这种小菜鸡消化,于是自己重写了一遍,顺便推了个A-B plus,码了个总结。
在C/C++语言中,如果要计算两个整数A和B的和,一般情况下我们都直接让SUM = A + B,但如果A、B、SUM任意一个数超过了最长类型的规模,那程序将会出错。如何解决这样的问题呢?我们都知道,字符串可以根据用户需求存放非常多的字符,所以只需要让数字用字符串的形式存储下来就可以了。但是字符串可不能直接做加减运算,这时候我们就需要写一个模拟列竖式运算的程序。
- (一)原料
我们需要两个字符串数组,用于从用户接收两个加数:
char a[10000] , b[10000]; //加数a、b
所有字符都需要转换成整型数字,才能进行运算:
int ac[10000] , bc[10000] , sum[10000]; //实际参与运算的三个数组
数组开的很大,而程序只需要对有效的数字进行运算,故需要知道有效数字长度:
int al=strlen(a) , bl=strlen(b) , suml; //两个加数、得数的长度
- (二)主函数
只需要输入两个加数,再执行求和函数就可以了:
int main()
{
scanf(“%s%s”,a,b);
add();
return 0;
}
- (三)求和函数——运算
让我们回忆一下小学学过的列竖式求和:
1 2 3
0 5 8
___1___
1 8 1
我们很容易想到如何让程序模拟上述的过程:假如输入a=123,b=58,那首先要让它们的每一位都对齐,也就是将58变成058,这样ac[0]、ac[1]、ac[2]上数字的数位就能分别和bc[0]、bc[1]、bc[2] 上数字的数位对应了。实现方法如下:
先把三个数组的每个元素都初始化为0:(知识点:内存操作函数)
memset(ac,0,sizeof(ac));
memset(bc,0,sizeof(bc));
memset(sum,0,sizeof(sum));
在接收了加数的字符串数组a、b中,从个位开始(逆向)取字符,将其转化为整型数字之后,正向地存入整型数组ac、bc中:(知识点:ASCII码转换)
int i;
for(i=al-1 ; i>=0 ; i--)
ac[al-1-i] = a[i] - ‘0’;
for(i=bl-1 ; i>=0 ; i--)
bc[bl-1-i] = b[i] - ‘0’;
这里的“先逆向取数,再正向存数”仅仅是为了方便写代码而已,因为我们总是习惯从数组的第一位开始向后遍历。
这么一来,我们就成功地将输入的加数列上了“竖式”,也就是说,它们现在已经做好了进行加法运算的准备(如图):
求出两个加数中最大的长度,暂时作为得数sum的长度suml,它不是sum数组的实际长度,它只是为了服务接下来遍历求和与答案输出的一个变量而已:
if(al>bl)
suml=al;
else
suml=bl;
下面就是核心部分——同时遍历两个加数数组进行求和。这部分的重点在于:ac[i]和bc[i]相加后,sum[i]每满10就要向前一位sum[i+1]进1,最后让原位sum[i]保留sum[i]对10取模的结果(也就是保留sum[i]个位数上的数):
for(i=0 ; i<suml ; i++)
{
sum[i] += ac[i] + bc[i];
sum[i+1] += sum[i] / 10;
sum[i] = sum[i] % 10;
}
- (四)求和函数——输出
经过了步骤(三)之后,数组sum[i]里存储的已经是最终答案,但我们在最开始时使用了“先逆向取数,再正向存数”的方法,所以在输出答案的时候,还需要多做一些处理:
不知道你还记不记得上文强调过“得数长度”——suml的值是暂时的,没错,两个数相加之后,得数可能比这两个数的任何一个都长,这是再常见不过的事情,我们还需要检查一下suml是否需要修正,以保证能够正确输出答案。但这其中有一个规律——对于n位数+n位数,得数的位数只可能是n或n+1,不可能是更高的位数,所以我们在程序中只需要判断suml+1位上是否有数即可:
if(sum[suml]!=0) //如果得数确实增加了一位
suml++; //那就让得数长度加1
最后将sum里的得数逆向输出(负负得正):
for(i=suml-1 ; i>=0 ; i--)
printf(“%d”,sum[i]);
总代码如下:
#include<stdio.h>
#include<string.h>
char a[10000],b[10000];
int ac[10000],bc[10000],sum[10000];
int suml,i;
void add(){
int al=strlen(a) , bl=strlen(b);
if(al>bl)
suml=al;
else
suml=bl;
memset(ac,0,sizeof(ac));
memset(bc,0,sizeof(bc));
memset(sum,0,sizeof(sum));
for(i=al-1 ; i>=0 ; i--)
ac[al-1-i] = a[i] - ‘0’;
for(i=bl-1 ; i>=0 ; i--)
bc[bl-1-i] = b[i] - ‘0’;
for(i=0 ; i<suml ; i++){
sum[i] += ac[i] + bc[i];
sum[i+1] += sum[i] / 10;
sum[i] = sum[i] % 10;
}
if(sum[suml]!=0)
suml++;
for(i=suml-1 ; i>=0 ; i--)
printf(“%d”,sum[i]);
}
int main()
{
scanf(“%s%s”,a,b);
add();
return 0;
}
- (五)扩展——大数减法
减法区别于加法的地方在于:1、减法运算可能得到负数;2、加法逢十向前一位进一,而减法不够减向前一位借一。
为了让程序能正确输出负数,我们可以先判断被减数和减数哪个大,让程序固定用大数减小数,如果原式子得数是负数,则输出得数时在最前面增加负号即可:
int PorM=1; //PorM是该数的正负状态,正为1,负为0
for(i=suml-1;i>=0;i--) //从最高位开始遍历数组ac和bc
{
if(bc[i]>ac[i])
//假如在最高位i有bc[i]>ac[i],那么数bc必大于数ac,即减数>被减数
PorM=0; //减数>被减数,那么得数肯定是负数了
break;
}
if(!PorM) {
//如果判断出得数是负数,就把减数与被减数调换,让程序固定用大数减小数
int temp;
PorM=0;
for(i=0;i<len;i++)
{
temp = ac[i];
ac[i] = bc[i];
bc[i] = temp;
}
}
在最后输出得数的时候,视PorM来决定是否输出负号就可以了。
接下来解决向前一位借1的问题,减法函数核心代码如下:
for( i=0 ; i<len ; i++)
{
sum[i] += ac[i] - bc[i];
ac[i+1] += sum[i]>=0 ? 0 : -1; //向下一位借1
sum[i] = sum[i]<0 ? 10+sum[i] : sum[i]; //原位运算后的数字
}
总代码如下:
#include <stdio.h>
#include <string.h>
char a[10000],b[10000];
int ac[10000],bc[10000],sum[10000];
int suml,i;
void add(void) //减法函数
{
int al = strlen(a) , bl = strlen(b);
if(al > bl)
suml = al;
else
suml = bl;
memset(ac,0,sizeof(ac));
memset(bc,0,sizeof(bc));
memset(sum,0,sizeof(sum));
for( i=al-1 ; i>=0 ; i--)
ac[al-1-i] = a[i] - '0';
for(i=bl-1 ; i>=0 ; i--)
bc[bl-1-i] = b[i] - '0';
int PorM=1;
for(i= suml -1;i>=0;i--){
if(bc[i]>ac[i])
PorM=0;
break;
}
if(!PorM){
int temp;
PorM=0;
for(i=0;i< suml;i++){
temp = ac[i];
ac[i] = bc[i];
bc[i] = temp;
}
}
//执行减法
for( i=0 ; i< suml; i++){
sum[i] += ac[i] - bc[i];
ac[i+1] += sum[i]>=0 ? 0 : -1;
sum[i] = sum[i]<0 ? 10+sum[i] : sum[i];
}
while(sum[suml -1]==0&& suml >1)//去除前端多余的0 (减法运算特有)
suml--;
if(PorM)
{
for( i= suml -1 ; i>=0 ; i--)
printf("%d",sum[i]);
}
else
{
printf("-");
for( i= suml -1 ; i>=0 ; i--)
printf("%d",sum[i]);
}
}
int main()
{
scanf("%s%s",a,b);
add();
return 0;
}
这种东西用C写起来也真是麻烦呀。以上如有疏漏或错误,敬请指出。