前言
在现代计算中,处理超出标准数据类型范围的大整数问题是一个常见的需求。在 C++ 中,标准数据类型如 int 或 long long 只能表示有限范围的整数,这在处理大数运算时会遇到局限。为了克服这个问题,我们可以实现一个 BigInt 类,用于处理任意大小的整数。在这篇文章中,我们将简要介绍如何设计和实现一个简单的 Number 类,以支持大整数的存储和基本运算。
Number类需要设计什么
Number类为后面的数据的所有基类,它需要实现的是按位存储,按位存储我们可以使用unsigned char
数组进行存储
- 存储的数据
- 设置位/清除位/翻转位
实现Number类
Number的声明
下面的是我们需要实现的内容
class Number
{
public:
Number(size_t BitSize);
Number(const Number& other); // 拷贝构造函数声明
void SetBit(size_t BitIndex);
void ClearBit(size_t BitIndex);
void ToggleBit(size_t BitIndex);
size_t GetBitSize()const;
int GetBit(size_t BitIndex) const;
Number& operator=(const Number& other); // 赋值操作符声明
virtual ~Number();
protected:
size_t BitSize;
unsigned char* Data;
};
这个 Number
类是一个用于表示和操作大整数的类,通过位操作来实现基本的整数操作。以下是类中各个成员的含义和功能:
公有成员函数:
-
Number(size_t BitSize):
- 构造函数,接受一个参数
BitSize
,用于指定要表示的大整数的位数。根据BitSize
分配相应的存储空间。
- 构造函数,接受一个参数
-
Number(const Number& other):
- 拷贝构造函数,用于创建一个新的
Number
对象,并将现有的Number
对象other
的内容复制到新对象中。
- 拷贝构造函数,用于创建一个新的
-
void SetBit(size_t BitIndex):
- 设置特定位置
BitIndex
的位为 1。
- 设置特定位置
-
void ClearBit(size_t BitIndex):
- 清除特定位置
BitIndex
的位,将其设置为 0。
- 清除特定位置
-
void ToggleBit(size_t BitIndex):
- 翻转特定位置
BitIndex
的位。如果该位是 1,则将其变为 0;如果是 0,则变为 1。
- 翻转特定位置
-
size_t GetBitSize() const:
- 返回
Number
对象的位大小,即该大整数的位数。
- 返回
-
int GetBit(size_t BitIndex) const:
- 获取特定位置
BitIndex
的位的值。返回值为 0 或 1。
- 获取特定位置
-
Number& operator=(const Number& other):
- 赋值操作符,用于将
other
对象的内容赋值给当前对象。
- 赋值操作符,用于将
-
virtual ~Number():
- 虚析构函数,用于在对象销毁时释放动态分配的内存。
受保护成员变量:
-
size_t BitSize:
- 表示大整数的位大小,即用多少个位来表示这个整数。
-
unsigned char Data*:
- 指向一个动态分配的字节数组,用于存储大整数的位数据。每个字节包含 8 位数据,通过这个数组可以表示任意大小的整数。
成员函数的用途:
- 构造函数和析构函数:用于创建和销毁
Number
对象,分配和释放存储大整数的内存。 - 拷贝构造函数和赋值操作符:用于确保
Number
对象可以被正确复制和赋值,维护对象的正确性和完整性。 - 位操作函数:用于设置、清除、翻转和获取特定位置的位值,提供对大整数的基本操作功能。
- 辅助函数:例如
GetBitSize
,用于获取整数的位大小,帮助管理和操作大整数。
通过这些成员函数和变量,Number
类可以有效地表示和操作大整数,满足在计算中处理大数的需求。
Number的构造、拷贝构造函数与赋值运算符重载的实现
这三个函数分别是 Number
类的构造函数、拷贝构造函数和赋值操作符。它们的实现确保了 Number
对象能够正确地分配、初始化、复制和赋值。让我们逐一分析每个函数的实现细节。
构造函数:Number::Number(size_t BitSize)
Number::Number(size_t BitSize)
{
this->BitSize = BitSize;
// 计算Data所需的元素个数
size_t NeedGroup = (BitSize + 7) / 8;
// 分配内存
Data = static_cast<unsigned char*>(malloc(NeedGroup));
Invalid = static_cast<unsigned char*>(malloc(NeedGroup));
// 检查内存分配是否成功
if (Data == nullptr || Invalid == nullptr)
{
// 释放已分配的内存
free(Data);
free(Invalid);
throw std::bad_alloc();
}
// 初始化Data和Invalid
std::fill_n(Data, NeedGroup, 0);
std::fill_n(Invalid, NeedGroup, 0xFF); // 假设0xFF表示无效
}
解释
- 初始化
BitSize
: 将传入的BitSize
参数赋值给类成员BitSize
。 - 计算
NeedGroup
: 确定需要多少字节来存储BitSize
位的数据。因为每个字节有 8 位,因此使用(BitSize + 7) / 8
来计算。 - 分配内存: 使用
malloc
为Data
和Invalid
数组分配内存。每个数组的大小都是NeedGroup
。 - 内存分配检查: 如果任何一个数组的内存分配失败,释放已分配的内存,并抛出
std::bad_alloc
异常。 - 初始化数组: 使用
std::fill_n
将Data
初始化为 0,将Invalid
初始化为 0xFF(假设 0xFF 表示无效)。
拷贝构造函数:Number::Number(const Number& other)
Number::Number(const Number& other)
{
BitSize = other.BitSize;
size_t NeedGroup = (BitSize + 7) / 8;
Data = static_cast<unsigned char*>(malloc(NeedGroup));
Invalid = static_cast<unsigned char*>(malloc(NeedGroup));
if (Data == nullptr || Invalid == nullptr)
{
free(Data);
free(Invalid);
throw std::bad_alloc();
}
std::copy(other.Data, other.Data + NeedGroup, Data);
std::copy(other.Invalid, other.Invalid + NeedGroup, Invalid);
}
解释
- 复制
BitSize
: 将other
对象的BitSize
赋值给新对象的BitSize
。 - 计算
NeedGroup
: 同样计算需要的字节数。 - 分配内存: 为
Data
和Invalid
分配内存。 - 内存分配检查: 如果内存分配失败,释放已分配的内存,并抛出
std::bad_alloc
异常。 - 复制数据: 使用
std::copy
将other
对象的Data
和Invalid
内容复制到新对象中。
赋值操作符:Number& Number::operator=(const Number& other)
Number& Number::operator=(const Number& other)
{
if (this == &other)
{
return *this;
}
free(Data);
free(Invalid);
BitSize = other.BitSize;
size_t NeedGroup = (BitSize + 7) / 8;
Data = static_cast<unsigned char*>(malloc(NeedGroup));
Invalid = static_cast<unsigned char*>(malloc(NeedGroup));
if (Data == nullptr || Invalid == nullptr)
{
free(Data);
free(Invalid);
throw std::bad_alloc();
}
std::copy(other.Data, other.Data + NeedGroup, Data);
std::copy(other.Invalid, other.Invalid + NeedGroup, Invalid);
return *this;
}
解释
- 自赋值检查: 如果
this
指针与other
相同,直接返回*this
,避免自赋值。 - 释放旧内存: 释放当前对象的
Data
和Invalid
内存,以便重新分配。 - 复制
BitSize
: 将other
对象的BitSize
赋值给当前对象的BitSize
。 - 计算
NeedGroup
: 同样计算需要的字节数。 - 分配新内存: 为
Data
和Invalid
分配新内存。 - 内存分配检查: 如果内存分配失败,释放已分配的内存,并抛出
std::bad_alloc
异常。 - 复制数据: 使用
std::copy
将other
对象的Data
和Invalid
内容复制到当前对象中。 - 返回当前对象: 返回
*this
,以支持连锁赋值操作。
实现位的操作
void Number::SetBit(size_t BitIndex)
{
// 检查 BitIndex 是否超出范围
if (BitIndex >= BitSize)
{
throw std::out_of_range("Bit index out of range");
}
size_t GroupSize = (BitSize + 7) / 8 - 1;
// 计算字节索引和位位置
size_t operatorgroup = (BitIndex / 8); // 字节索引,从右到左
size_t bitPosition = BitIndex % 8; // 位位置,从右到左
// 设置特定位
Data[GroupSize-operatorgroup] |= (1 << bitPosition);
}
void Number::ClearBit(size_t BitIndex)
{
// 检查 BitIndex 是否超出范围
if (BitIndex >= BitSize)
{
throw std::out_of_range("Bit index out of range");
}
size_t GroupSize = (BitSize + 7) / 8 - 1;
// 计算字节索引和位位置
size_t operatorgroup = (BitIndex / 8); // 字节索引,从右到左
size_t bitPosition = BitIndex % 8; // 位位置,从右到左
// 清除特定位
Data[GroupSize - operatorgroup] &= ~(1 << bitPosition);
}
void Number::ToggleBit(size_t BitIndex)
{
// 检查 BitIndex 是否超出范围
if (BitIndex >= BitSize)
{
throw std::out_of_range("Bit index out of range");
}
size_t GroupSize = (BitSize + 7) / 8 - 1;
// 计算字节索引和位位置
size_t operatorgroup = (BitIndex / 8); // 字节索引,从右到左
size_t bitPosition = BitIndex % 8; // 位位置,从右到左
// 切换特定位
Data[GroupSize - operatorgroup] ^= (1 << bitPosition);
}
由于他们的实现是类似的,我们通过Set来解释:
检查 BitIndex 范围:
首先检查传入的 BitIndex 是否超出对象的位大小 BitSize。如果超出范围,抛出 std::out_of_range 异常。这是为了防止访问非法内存区域。
计算 GroupSize:
计算用于存储位数据的字节数组的大小。通过 (BitSize + 7) / 8 计算出所需的字节数,然后减 1 得到数组的最大索引值 GroupSize。
计算字节索引和位位置:
operatorgroup 计算字节索引,表示 BitIndex 所在的字节位置。通过 BitIndex / 8 得到。
bitPosition 计算位位置,表示 BitIndex 在字节内的具体位置。通过 BitIndex % 8 得到。
设置特定位:
通过 Data[GroupSize - operatorgroup] |= (1 << bitPosition) 设置特定位。这里使用按位或运算符 |= 和左移操作符 <<:
1 << bitPosition 生成一个只有目标位为 1 的掩码。
|= 将该掩码与目标字节的当前值进行按位或操作,设置特定位为 1,而不改变其他位的值。
GroupSize - operatorgroup 是为了从高位到低位设置位,使得 BitIndex 0 对应最高位,BitIndex (BitSize-1) 对应最低位。
GetBit与GetBitSize的实现
size_t Number::GetBitSize()const
{
return BitSize;
}
int Number::GetBit(size_t BitIndex) const
{
if (BitIndex >= BitSize) throw std::out_of_range("BitIndex out of range");
size_t GroupSize = (BitSize + 7) / 8 - 1;
// 计算字节索引和位位置
size_t operatorgroup = (BitIndex / 8); // 字节索引,从右到左
size_t bitPosition = BitIndex % 8; // 位位置,从右到左
return (Data[GroupSize - operatorgroup] & (1 << bitPosition)) != 0;
}
总结
通过实现一个简单的 Number 类,我们能够在 C++ 中处理任意大小的大整数。这个类不仅克服了标准数据类型范围的限制,还为我们提供了处理大整数运算的灵活性。尽管本文仅提供了一个基本实现,但它为更复杂的大数计算打下了基础。未来,我们可以在此基础上扩展功能,支持更多的运算和优化,以满足更复杂的应用需求。通过这种方式,我们可以在 C++ 中实现高效且可靠的大整数处理。