文章目录
- 一、实验目的、内容
- 二、实验程序设计及结构
- 1.需求分析
- 类
- 变量
- 函数
- 2.设计结构或流程图
- 三、设计过程
- 四、测试分析
- 第一组
- 第二组
- 实验中出现的bug及解决方案
- 五、设计的特点和结果
一、实验目的、内容
大数是超过整数表示范围的整数,针对正整数运算,定义一个大数类,并编写两个大数类对象的加法和减法函数。
二、实验程序设计及结构
1.需求分析
类
bigu
,内含p
(unsigned char*
)、n
(size_t
)数据成员及构造函数、析构函数等成员函数。
变量
bigu
类型数据a
和b
,用于测试数据。
函数
主函数、运算符重载函数及bigu
类的成员函数。
2.设计结构或流程图
- 进行
a
和b
的输入。(调用构造函数及istream& operator>>(istream& c, bigu& x)
)- 从输入的第一个数字字符开始,定义
unsigned short
临时数组(看作256进制数)。 - 每输入一个数字字符,每个数组元素乘10。
- 遍历临时数组,将超过255的元素执行进位操作;若数组长度不够,重新定义数组并拷贝。
- 不断输入直到遇到非数字字符为止。
- 将临时数组的数据拷贝进
unsigned char
数组并存储。
- 从输入的第一个数字字符开始,定义
- 进行
a
和b
的加法并输出。(调用bigu operator+(const bigu& x, const bigu& y)
和ostream& operator<<(ostream& c, const bigu& x)
)- 加法运算
- 根据整数运算进位的性质,和的元素个数为
max(x.n,y.n)
或max(x.n,y.n)+1
,故定义max(x.n,y.n)+1
长度的临时数组。 - 按
x.n
和y.n
的大小分类讨论。 - 将临时数组初始化为
x
和y
各位元素之和。 - 遍历临时数组,对值超过255的执行进位操作。
- 将临时数组的数据拷贝进
unsigned char
数组并存储。
- 根据整数运算进位的性质,和的元素个数为
- 输出
- 定义临时数组。
- 从最高位开始,将超过9的数据进行十进制展开。
- 每次加新数之前将原来的所有元素乘256并对超过9的数进行进位。
- 输出并释放临时数组。
- 加法运算
- 比较
a
与b
的大小,作差后输出。(调用bool operator>(const bigu& x, const bigu& y)
、bigu operator-(const bigu& x, const bigu& y)
和ostream& operator<<(ostream& c, const bigu& x)
)- 大于运算
- 若两者元素个数不同,直接返回结果。
- 逐位比较,直到两者不相等为止。
- 减法运算(与加法类似)
- 大于运算
三、设计过程
#include <iostream>
using namespace std;
class bigu
{
public:
unsigned char *p; // 指向正整数数组
size_t n; // 数组长度
bigu() { *(p = new unsigned char[n = 1]) = '\0'; } // 默认构造函数:初始化为0
// 带参数构造(无符号整型)
bigu(unsigned long long x)
{
unsigned char a[8], *q(a), *t(q);
*q = x;
while (x >>= 8)
*++q = x; // 逐字节读取
*(p = new unsigned char[n = ++q - t]) = *t;
while (++t < q)
*++p = *t;
++(p -= n);
}
// 为了防止浅拷贝的问题,重写拷贝构造函数
bigu(const bigu &x)
{
delete[] p; // 防止内存泄漏
const unsigned char *q(x.p);
*(p = new unsigned char[n = x.n]) = *q;
while (--n)
*++p = *++q;
++(p -= n = x.n);
}
~bigu() { delete[] p; } // 析构函数
// 为了加快效率、避免拷贝的有参构造函数
bigu(unsigned char *a, size_t b)
{
p = a;
n = b;
}
};
// 重载关系运算符
bool operator>(const bigu &x, const bigu &y)
{
if (x.n > y.n)
return true;
if (y.n > x.n)
return false;
const unsigned char *p(x.p + x.n), *q(y.p + y.n);
do
if (*--p > *--q)
return true;
else if (*p < *q)
return false;
while (p > x.p);
return false;
}
// 重载加法运算符
bigu operator+(const bigu &x, const bigu &y)
{
const unsigned char *p(x.p), *q(y.p); // 读取数据
// 考虑新数组大小将会是x,y中较大的那个或是较大的加1
// 故分类讨论
if (y.n > x.n)
{
size_t n(x.n);
unsigned short *a(new unsigned short[y.n + 1]), *t(a + y.n); // 定义临时数组
// 向临时数组输入数据
*a = *p + *q;
while (--n)
*++a = *++p + *++q;
while (++a < t)
*a = *++q;
// 处理首位
if (*(a -= (n = y.n)--) > 255)
{
*(a + 1) += *a / 256;
*a %= 256;
}
// 处理头尾之间
while (--n)
if (*++a > 255)
{
*(a + 1) += *a / 256;
*a %= 256;
}
// 处理最高位
if (*++a > 255) // 超过了unsigned char表示的范围
{
// 拷贝、释放并返回
unsigned char *u(new unsigned char[y.n + 1] + (n = y.n));
*u = *a / 256;
*a %= 256;
do
*--u = *--t;
while (--n);
delete[] t;
return bigu(u, y.n + 1);
}
// 未超过unsigned char表示的范围
unsigned char *u(new unsigned char[n = y.n] + y.n);
*--u = *a;
while (--n)
*--u = *--a;
delete[] a;
return bigu(u, y.n);
}
// 此时x的位数大于等于y的位数
// 操作过程同理
if (x.n == 1)
return bigu(*p + *q);
size_t n(y.n);
unsigned short *a(new unsigned short[x.n + 1]), *t(a + x.n);
*a = *p + *q;
while (--n)
*++a = *++p + *++q;
while (++a < t)
*a = *++p;
if (*(a -= (n = x.n)--) > 255)
{
*(a + 1) += *a / 256;
*a %= 256;
}
while (--n)
if (*++a > 255)
{
*(a + 1) += *a / 256;
*a %= 256;
}
if (*++a > 255)
{
unsigned char *u(new unsigned char[x.n + 1] + (n = x.n));
*u = *a / 256;
*a %= 256;
do
*--u = *--t;
while (--n);
delete[] t;
return bigu(u, x.n + 1);
}
unsigned char *u(new unsigned char[n = x.n] + x.n);
*--u = *a;
while (--n)
*--u = *--a;
delete[] a;
return bigu(u, x.n);
}
// 重载减法运算符
// 默认x>=y
bigu operator-(const bigu &x, const bigu &y)
{
if (x.n == 1) // 由于y<=x,只需要1字节内存空间
{
unsigned char *a(new unsigned char[1]);
*a = *x.p - *y.p;
return bigu(a, 1);
}
const unsigned char *p(x.p), *q(y.p); // 读取
short *a(new short[x.n]), *t(a), *r(a + x.n - 1); // 定义临时数组
*t = *p - *q;
size_t n(y.n);
while (--n)
*++t = short(*++p) - short(*++q); // 由于p,q为无符号指针,需要进行强制类型转换
while (++t <= r)
*t = *++p;
if (*(t = a) < 0)
{
--*(t + 1);
*t += 256;
} // 对首位进行退位操作
while (++t < r)
if (*t < 0)
{
*(t + 1) += *t / 256 - 1;
(*t %= 256) += 256;
} // 对其余各位进行退位操作
++t;
while (!*--t)
; // 找到首个大于0的高位
// 拷贝数据进新的合适大小的数组
unsigned char *u(new unsigned char[n = ++t - a] + n);
do
*--u = *--t;
while (t > a);
delete[] a; // 防止内存泄漏
return bigu(u, n);
}
// 重载右移运算符,用以输入
istream &operator>>(istream &c, bigu &x)
{
delete[] x.p; // 释放原来的数组,防止内存泄漏
char s; // 由于不知道大数的上限,采用逐字节读取方式
do
c.get(s);
while (s == ' ' || s == '\n' || s == '\t'); // 排除空格、水平制表符和换行
if (s < '0' || s > '9') // 如果输入的不是数字,按0处理
{
*(x.p = new unsigned char[x.n = 1]) = 0;
return c;
}
unsigned short *a(new unsigned short[x.n = 10]), *b(a), *p; // 定义临时数组,用范围比unsigned char稍大的类型进行临时存储,a指向最低位,b指向目前的最高位
*a = s - '0'; // 存储第一位
c.get(s);
while (s >= '0' && s <= '9')
{
(*(p = a) *= 10) += s - '0'; // 加上新数并乘10(位权)
while (++p <= b)
*p *= 10; // 每一位乘10
if (*(p = a) > 255) // 最低位超过了unsigned char的范围
if (p == b) // 只有1位的情况
{
*++b = *p / 256; // 改变尾指针
*p %= 256;
goto F; // 直接进行下一位的读取
}
else
{
*(p + 1) += *p / 256;
*p %= 256;
}
else if (p == b)
goto F; // 只有1位而且不需要处理
while (++p < b) // 处理头尾之间的数据
if (*p > 255)
{
*(p + 1) += *p / 256;
*p %= 256;
}
if (*p > 255) // 最高位需要进位
if (++b - a == x.n) // 事先的上限不够用,重新申请更大的存储空间并拷贝
{
unsigned short *u(a), *v(new unsigned short[x.n += 10]);
*(p = v) = *a;
while (++a < b)
*++p = *a;
delete[] u;
a = v;
*(b = p + 1) = *p / 256;
*p %= 256;
}
else // 够用,直接处理
{
*b = *p / 256;
*p %= 256;
}
F:
c.get(s);
}
// 将处理结果拷贝进unsigned char数组并释放临时数组
unsigned char *q(x.p = new unsigned char[x.n = ++b - (p = a)]);
*q = *p;
while (++p < b)
*++q = *p;
delete[] a;
return c;
}
// 重载左移运算符,用以输出
ostream &operator<<(ostream &c, const bigu &x)
{
size_t m(10); // 先假定一个上限
unsigned short *t(new unsigned short[m]), *a(t), *b(t); // 用范围比unsigned char稍大的类型进行临时存储,a指向最低位,b指向目前的最高位
const unsigned char *q(x.p + x.n); // 用于读取数据的指针
if ((*t = *--q) > 9)
do
{
*++b = *t / 10;
*t %= 10;
} while (*++t > 9); // 如果最高位大于等于10
while (--q >= x.p)
{
(*(t = a) *= 256) += *q; // 新位加上原来的乘上位权(256)
while (++t <= b)
*t *= 256; // 各高位乘位权(256)
t = a;
while (t != b) // 处理头尾之间的位,使它们都小于10
{
*(t + 1) += *t / 10;
*t %= 10;
++t;
}
if (*t > 9) // 如果最高位需要处理
{
if (false)
F:
{ // 预先的上限不够用,重新申请更大的存储空间
unsigned short *u(t = new unsigned short[m += 30]), *v(a);
*u = *a;
while (++a < b)
*++u = *a;
delete[] v;
a = t;
t = b = u;
}
// 处理最高位
do
{
if (++b - a == m)
goto F;
*b = *t / 10;
*t++ %= 10;
} while (*b > 9);
}
}
c << *b; // 输出最高位
while (--b >= a)
c << *b; // 输出其余各位
delete[] a; // 释放临时数组
return c;
}
// 主函数
// 用以测试数据
int main()
{
bigu a, b;
cout << "请输入大数a:\n";
cin >> a;
cout << "请输入大数b:\n";
cin >> b;
cout << "a+b=" << a + b << endl;
if (a > b)
cout << "a-b=" << a - b << endl;
else
cout << "b-a=" << b - a << endl;
system("pause");
return 0;
}
四、测试分析
第一组
第二组
实验中出现的bug及解决方案
实验中出现的bug:忽略了大数的进位、退位问题。
解决方案:定义更大的临时数据数组并逐位处理。
五、设计的特点和结果
设计采用的数据结构为数组。为了表示大数,把unsigned char
数组看作256进制的数,而不是十进制数,节约了内存空间,并利用动态内存分配技术,解决了大数问题。在实验过程中,我原本是打算逐个扩充数组(每次数组长度加1),后来发现运行的效率很低,就采用了和标准库类型vector
类似的内存分配方法,每次多扩充29位,解决了效率低的问题。