鱼C论坛

 找回密码
 立即注册
查看: 1724|回复: 2

[已解决]想用c++实现大数加法,但不知道到底出现了什么问题

[复制链接]
发表于 2022-7-18 15:50:02 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 GaoKui 于 2022-7-18 17:14 编辑

感觉问题出在赋值号上?数据较小时最后两次打印的结果不同,一个是正确的,一个是错误的
数据较大时则是会直接出错,不知道为什么

  1. #include <iostream>
  2. #include <string>

  3. using namespace std;

  4. class BigInt
  5. {
  6. public:
  7.         BigInt(void);
  8.         void getData(void);
  9.         void printData(void);
  10.         BigInt operator+(BigInt &n); // 必须传引用,若传值,参数传入函数后会调用构造函数把原本的数据初始化
  11.        
  12. private:
  13.         string data; // 大数
  14.         int size; // 数字的位数
  15. };

  16. BigInt::BigInt(void)
  17. {
  18.         data = ""; // 空字符串
  19.         size = 0;
  20. }

  21. void BigInt::getData(void)
  22. {
  23.         string tmp;
  24.         int i;
  25.        
  26.         getline(cin, tmp); // 先读入数据
  27.         i = tmp.size(); // i为字符串长度
  28.        
  29.         while (i > 0)
  30.         {
  31.                 data[size++] = tmp[--i]; // data中的字符串反着放
  32.         }
  33. }

  34. void BigInt::printData(void)
  35. {
  36.         for (int i = size - 1; i >= 0; i--)
  37.         {
  38.                 cout << data[i];
  39.         }
  40.         cout << '\n';
  41. }

  42. BigInt BigInt::operator+(BigInt &n)
  43. {
  44.         BigInt result;
  45.         int i, up, tmp;
  46.         bool flag;
  47.        
  48.         int max = this->size > n.size ? this->size : n.size; // 找到较大的位数
  49.         flag = this->size > n.size ? true : false; // 若是 this指针指向的对象位数大为 true,否则为 false
  50.        
  51.         if (flag) // 把两个数的位数变一致,位数较小的将前面补上 0
  52.         {
  53.                 for (i = n.size; i < max; i++)
  54.                 {
  55.                         n.data[i] = '0';
  56.                 }
  57.         }
  58.         else
  59.         {
  60.                 for (i = this->size; i < max; i++)
  61.                 {
  62.                         this->data[i] = '0';
  63.                 }
  64.         }
  65.        
  66.         for (i = 0, up = 0; i < max; i++) // 模拟竖式加法
  67.         {
  68.                 tmp = this->data[i] - '0' + n.data[i] - '0' + up; // 先将两个位对应的和算出,并加上上一位的进位
  69.                
  70.                 result.size++; // 结果长度 +1
  71.                 result.data[i] = tmp % 10 + '0'; // 新的一位是之前的数对 10取余(由于是字符还要加上 '0')
  72.                 up = tmp / 10; // 进位是之前的数除以 10
  73.         }
  74.        
  75.         if (up != 0) // 全部结束还有进位,就将进位放到下一位
  76.         {
  77.                 result.size++;
  78.                 result.data[i] = up + '0';
  79.         }
  80.        
  81.         // result.printData(); /* ---------------------此处若不注释,打印结果是正确的--------------------- */
  82.        
  83.         return result;
  84. }

  85. int main(void)
  86. {
  87.         BigInt a, b, c; // 创建对象
  88.        
  89.         a.getData(); // 输入数据
  90.         b.getData();
  91.        
  92.         c = a + b;
  93.        
  94.         (a + b).printData(); /* ---------------------此处结果正确--------------------- */
  95.         c.printData(); /* ---------------------此处结果错误--------------------- */
  96.        
  97.         return 0;
  98. }
复制代码
最佳答案
2022-7-18 20:43:42
问题不在赋值而在对 string 的使用:您对 string 的下标访问是越界的。在容器中,有这样两个大小的概念,分别是容量和大小。其中容量是容器当前最多能够容纳的元素个数,而大小是当前已经实际存储的元素个数。您使用下标访问 data 的方式直接对底层进行了访问,这一步骤是没有越界检查的,而由于默认构造的空(大小为 0 ) string 的容量很可能不为 0 ,因此访问此处的内存是合法的,因而当数据较小时出现诡异的结果,而较大时由于越界而直接报错。
因为直接访问了底层,向 string 添加字符的操作未经过容器的正规接口,容器并不知道自己存储了有效的数据,因此第 99 行赋值时调用 string 的赋值操作符时不会复制这些数据,则在容器的视角看来赋值操作实际进行的是“把一个空字符串复制到 c 中的 data ”,显然这会造成无法从 c 中读出正确的结果。而由于尽管您修改容器内部存储空间的操作对容器不可见,但是您从中读取的操作同样是自行进行的,因此对于 a + b 得到的结果直接读取是能够得到正确结果的。
总结起来,您的问题在于对 string 等标准库容器的理解和使用,可能还需要进一步学习。如果不自信,在初学阶段可以考虑不要使用容器的 [] 运算符而是使用 at 方法,这会引入边界检查从而帮助发现一些问题。另外,个人认为在重载 + 操作符时修改操作数的行为(您的代码中补零的操作)是欠妥的,直观上 + 操作符不应该对两个操作数造成任何(外部可见的)修改。(当然您的实现方式实际上对外部不可见,但是这种实现方式是错误的,因此可能还是需要重新考虑补零这一部分。)
感觉自己没说明白,献丑在您的基础上稍作修改了一下,显然并不是最好的写法,您可以视情况参考。
  1.     #include <iostream>
  2.     #include <string>
  3.     #include <algorithm>

  4.     using namespace std;

  5.     class BigInt
  6.     {
  7.     public:
  8.             BigInt();
  9.             void getData();
  10.             void printData()const;
  11.             BigInt operator+(const BigInt &n)const; // 必须传引用,若传值,参数传入函数后会调用构造函数把原本的数据初始化
  12.            
  13.     private:
  14.             string data; // 大数
  15.             static int addWithCarry(int value, int& carry); // 处理带进位加法,减少代码重复
  16.     };

  17.     BigInt::BigInt() = default;

  18.     void BigInt::getData()
  19.     {
  20.             getline(cin, data);
  21.             reverse(data.begin(), data.end());  // 反转字符串
  22.     }

  23.     void BigInt::printData()const
  24.     {
  25.         for(int i = data.size() - 1; i >= 0; --i) cout << data[i];
  26.         cout << '\n';
  27.     }

  28.     BigInt BigInt::operator+(const BigInt &n)const
  29.     {
  30.             BigInt result;
  31.             int carry = 0;
  32.             unsigned int i = 0;
  33.             while(i < data.size() && i < n.data.size()){
  34.                 result.data.push_back(addWithCarry(data[i] - '0' + n.data[i] - '0', carry) + '0');
  35.                 ++i;
  36.             }
  37.             while(i < data.size()){
  38.                 result.data.push_back(addWithCarry(data[i] - '0', carry) + '0');
  39.                 ++i;
  40.             }
  41.             while(i < n.data.size()){
  42.                 result.data.push_back(addWithCarry(n.data[i] - '0', carry) + '0');
  43.                 ++i;
  44.             }
  45.             if(carry != 0) result.data.push_back('1');
  46.             return result;
  47.     }
  48.     int BigInt::addWithCarry(int value, int& carry){
  49.         carry = (value += carry) >= 10 ? 1 : 0;
  50.         return carry ? value - 10 : value;
  51.     }

  52.     int main()
  53.     {
  54.             BigInt a, b, c; // 创建对象
  55.            
  56.             a.getData(); // 输入数据
  57.             b.getData();
  58.             c = a + b;
  59.            
  60.             (a + b).printData();
  61.             c.printData();
  62.            
  63.             return 0;
  64.     }
复制代码

(一开始没想到问题会出在这里,把我给整不自信了,去翻了半天 C++ 标准,还以为记错了默认赋值运算符的行为……)
小甲鱼最新课程 -> https://ilovefishc.com
回复

使用道具 举报

发表于 2022-7-18 20:43:42 | 显示全部楼层    本楼为最佳答案   
问题不在赋值而在对 string 的使用:您对 string 的下标访问是越界的。在容器中,有这样两个大小的概念,分别是容量和大小。其中容量是容器当前最多能够容纳的元素个数,而大小是当前已经实际存储的元素个数。您使用下标访问 data 的方式直接对底层进行了访问,这一步骤是没有越界检查的,而由于默认构造的空(大小为 0 ) string 的容量很可能不为 0 ,因此访问此处的内存是合法的,因而当数据较小时出现诡异的结果,而较大时由于越界而直接报错。
因为直接访问了底层,向 string 添加字符的操作未经过容器的正规接口,容器并不知道自己存储了有效的数据,因此第 99 行赋值时调用 string 的赋值操作符时不会复制这些数据,则在容器的视角看来赋值操作实际进行的是“把一个空字符串复制到 c 中的 data ”,显然这会造成无法从 c 中读出正确的结果。而由于尽管您修改容器内部存储空间的操作对容器不可见,但是您从中读取的操作同样是自行进行的,因此对于 a + b 得到的结果直接读取是能够得到正确结果的。
总结起来,您的问题在于对 string 等标准库容器的理解和使用,可能还需要进一步学习。如果不自信,在初学阶段可以考虑不要使用容器的 [] 运算符而是使用 at 方法,这会引入边界检查从而帮助发现一些问题。另外,个人认为在重载 + 操作符时修改操作数的行为(您的代码中补零的操作)是欠妥的,直观上 + 操作符不应该对两个操作数造成任何(外部可见的)修改。(当然您的实现方式实际上对外部不可见,但是这种实现方式是错误的,因此可能还是需要重新考虑补零这一部分。)
感觉自己没说明白,献丑在您的基础上稍作修改了一下,显然并不是最好的写法,您可以视情况参考。
  1.     #include <iostream>
  2.     #include <string>
  3.     #include <algorithm>

  4.     using namespace std;

  5.     class BigInt
  6.     {
  7.     public:
  8.             BigInt();
  9.             void getData();
  10.             void printData()const;
  11.             BigInt operator+(const BigInt &n)const; // 必须传引用,若传值,参数传入函数后会调用构造函数把原本的数据初始化
  12.            
  13.     private:
  14.             string data; // 大数
  15.             static int addWithCarry(int value, int& carry); // 处理带进位加法,减少代码重复
  16.     };

  17.     BigInt::BigInt() = default;

  18.     void BigInt::getData()
  19.     {
  20.             getline(cin, data);
  21.             reverse(data.begin(), data.end());  // 反转字符串
  22.     }

  23.     void BigInt::printData()const
  24.     {
  25.         for(int i = data.size() - 1; i >= 0; --i) cout << data[i];
  26.         cout << '\n';
  27.     }

  28.     BigInt BigInt::operator+(const BigInt &n)const
  29.     {
  30.             BigInt result;
  31.             int carry = 0;
  32.             unsigned int i = 0;
  33.             while(i < data.size() && i < n.data.size()){
  34.                 result.data.push_back(addWithCarry(data[i] - '0' + n.data[i] - '0', carry) + '0');
  35.                 ++i;
  36.             }
  37.             while(i < data.size()){
  38.                 result.data.push_back(addWithCarry(data[i] - '0', carry) + '0');
  39.                 ++i;
  40.             }
  41.             while(i < n.data.size()){
  42.                 result.data.push_back(addWithCarry(n.data[i] - '0', carry) + '0');
  43.                 ++i;
  44.             }
  45.             if(carry != 0) result.data.push_back('1');
  46.             return result;
  47.     }
  48.     int BigInt::addWithCarry(int value, int& carry){
  49.         carry = (value += carry) >= 10 ? 1 : 0;
  50.         return carry ? value - 10 : value;
  51.     }

  52.     int main()
  53.     {
  54.             BigInt a, b, c; // 创建对象
  55.            
  56.             a.getData(); // 输入数据
  57.             b.getData();
  58.             c = a + b;
  59.            
  60.             (a + b).printData();
  61.             c.printData();
  62.            
  63.             return 0;
  64.     }
复制代码

(一开始没想到问题会出在这里,把我给整不自信了,去翻了半天 C++ 标准,还以为记错了默认赋值运算符的行为……)
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

 楼主| 发表于 2022-7-18 23:18:03 | 显示全部楼层
dolly_yos2 发表于 2022-7-18 20:43
问题不在赋值而在对 string 的使用:您对 string 的下标访问是越界的。在容器中,有这样两个大小的概念,分 ...

十分感谢!虽然有一小部分还是没看懂,应该是还没完全掌握相关的知识。会继续学习的!
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-4-24 01:13

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表