【C++进阶】BigInt之Number类的简单实现


前言

在现代计算中,处理超出标准数据类型范围的大整数问题是一个常见的需求。在 C++ 中,标准数据类型如 int 或 long long 只能表示有限范围的整数,这在处理大数运算时会遇到局限。为了克服这个问题,我们可以实现一个 BigInt 类,用于处理任意大小的整数。在这篇文章中,我们将简要介绍如何设计和实现一个简单的 Number 类,以支持大整数的存储和基本运算。


Number类需要设计什么

Number类为后面的数据的所有基类,它需要实现的是按位存储,按位存储我们可以使用unsigned char数组进行存储

  1. 存储的数据
  2. 设置位/清除位/翻转位

实现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 类是一个用于表示和操作大整数的类,通过位操作来实现基本的整数操作。以下是类中各个成员的含义和功能:

公有成员函数:

  1. Number(size_t BitSize):

    • 构造函数,接受一个参数 BitSize,用于指定要表示的大整数的位数。根据 BitSize 分配相应的存储空间。
  2. Number(const Number& other):

    • 拷贝构造函数,用于创建一个新的 Number 对象,并将现有的 Number 对象 other 的内容复制到新对象中。
  3. void SetBit(size_t BitIndex):

    • 设置特定位置 BitIndex 的位为 1。
  4. void ClearBit(size_t BitIndex):

    • 清除特定位置 BitIndex 的位,将其设置为 0。
  5. void ToggleBit(size_t BitIndex):

    • 翻转特定位置 BitIndex 的位。如果该位是 1,则将其变为 0;如果是 0,则变为 1。
  6. size_t GetBitSize() const:

    • 返回 Number 对象的位大小,即该大整数的位数。
  7. int GetBit(size_t BitIndex) const:

    • 获取特定位置 BitIndex 的位的值。返回值为 0 或 1。
  8. Number& operator=(const Number& other):

    • 赋值操作符,用于将 other 对象的内容赋值给当前对象。
  9. virtual ~Number():

    • 虚析构函数,用于在对象销毁时释放动态分配的内存。

受保护成员变量:

  1. size_t BitSize:

    • 表示大整数的位大小,即用多少个位来表示这个整数。
  2. 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表示无效
}

解释

  1. 初始化 BitSize: 将传入的 BitSize 参数赋值给类成员 BitSize
  2. 计算 NeedGroup: 确定需要多少字节来存储 BitSize 位的数据。因为每个字节有 8 位,因此使用 (BitSize + 7) / 8 来计算。
  3. 分配内存: 使用 mallocDataInvalid 数组分配内存。每个数组的大小都是 NeedGroup
  4. 内存分配检查: 如果任何一个数组的内存分配失败,释放已分配的内存,并抛出 std::bad_alloc 异常。
  5. 初始化数组: 使用 std::fill_nData 初始化为 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);
}

解释

  1. 复制 BitSize: 将 other 对象的 BitSize 赋值给新对象的 BitSize
  2. 计算 NeedGroup: 同样计算需要的字节数。
  3. 分配内存: 为 DataInvalid 分配内存。
  4. 内存分配检查: 如果内存分配失败,释放已分配的内存,并抛出 std::bad_alloc 异常。
  5. 复制数据: 使用 std::copyother 对象的 DataInvalid 内容复制到新对象中。

赋值操作符: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;
}

解释

  1. 自赋值检查: 如果 this 指针与 other 相同,直接返回 *this,避免自赋值。
  2. 释放旧内存: 释放当前对象的 DataInvalid 内存,以便重新分配。
  3. 复制 BitSize: 将 other 对象的 BitSize 赋值给当前对象的 BitSize
  4. 计算 NeedGroup: 同样计算需要的字节数。
  5. 分配新内存: 为 DataInvalid 分配新内存。
  6. 内存分配检查: 如果内存分配失败,释放已分配的内存,并抛出 std::bad_alloc 异常。
  7. 复制数据: 使用 std::copyother 对象的 DataInvalid 内容复制到当前对象中。
  8. 返回当前对象: 返回 *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++ 中实现高效且可靠的大整数处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人才程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值