Project 7 — Class BigInt

139次阅读
没有评论

这是一个仅支持非负整数的十进制大整数类 BigInt。本文用通俗语言解释每个构造函数与运算符的作用、何时使用,以及典型示例与注意事项。


快速开始

  • 代码结构:
    • BigInt.h:类声明与字段
    • BigInt.cpp:具体实现
    • main.cpp:示例与测试
    • Makefile:构建脚本
  • 构建与运行:
make
./bigint_demo

运行后若显示 All tests passed.,说明功能正常。


字段与内部表示

  • bool isZero:是否表示数字 0(便于快速判断)。
  • size_t len:当前数字的位数(例如 12345 的位数是 5)。
  • size_t size:内部字符缓冲区的容量(为动态扩容服务)。
  • char* s:存储每一位数字的数组,最高位在 s[0],每个位置是字符 '0'..'9'

构造函数详解

  • BigInt(size_t value)

    • 作用:用普通无符号整数初始化一个大整数。
    • 场景:数值在机器整型范围内(如 0、12345)时最简便;常用于循环累加、简单测试。
    • 示例:BigInt a(static_cast size_t (0)); BigInt b(12345);
    • 注意:如果写 BigInt(0) 且同时存在 BigInt(const char*) 构造,某些编译器可能把 0 误解为空指针产生歧义;使用 static_cast size_t (0) 更稳妥。
  • BigInt(const char* str)

    • 作用:用十进制字符串创建大整数,支持任意多位,允许前导零。
    • 场景:需要表示超过机器整型范围的超大数,或从文本/输入中读取数字。
    • 示例:BigInt c("00012345"); BigInt d("9999999999999999999999999999");
    • 规范化:会自动去掉前导零,保证内部没有多余的零("000123" 会变成 "123")。
    • 无效输入:如果包含非数字字符或是空串,按 处理以免程序崩溃。
  • BigInt(const BigInt& other)

    • 作用:拷贝已有的 BigInt,得到一个内容相同、独立存储的新对象。
    • 场景:函数传参、返回值、需要保留快照等。
    • 示例:BigInt e(b); // e 与 b 数值相同,但互不影响

赋值运算符

  • BigInt& operator=(const BigInt& other)
    • 作用:用另一个 BigInt 覆盖当前对象的值(深拷贝)。
    • 场景:复用已有变量存结果,或在计算链中更新同一对象。
    • 示例:BigInt x(1); BigInt y("999"); x = y; // x 变成 999
    • 注意:支持自赋值(x = x;),内部已做安全处理。

加法运算符

  • BigInt operator+(const BigInt& rhs) const

    • 作用:返回两数之和,不修改原对象(就像 a + b)。
    • 场景:需要保持输入不变时,用该形式更清晰。
    • 示例:BigInt z = BigInt("999") + BigInt("1"); // z = 1000
    • 实现要点:竖式加法,从最低位向最高位处理,正确处理进位。
  • BigInt& operator+=(const BigInt& rhs)

    • 作用:把 rhs 加到当前对象上,并返回当前对象引用(就地更新)。
    • 场景:累加场景(如循环求和)。
    • 示例:BigInt sum(0); for (size_t i = 0; i < 1000; ++i) sum += BigInt(i);
    • 等价:a += b; 基本等价于 a = a + b;,但可能更易读。

乘法运算符

  • BigInt operator*(const BigInt& rhs) const

    • 作用:返回乘积,不修改原对象。
    • 场景:计算两大数的乘积。
    • 示例:BigInt p = BigInt("1234") * BigInt("5678"); // p = 7006652
    • 实现要点:朴素竖式乘法,时间复杂度约为 O(n⋅m),其中 n、m 为位数。
    • 特殊:只支持非负数,遇到 0 会直接返回 0。
  • BigInt& operator*=(const BigInt& rhs)

    • 作用:把当前对象乘以 rhs 并更新自身。
    • 场景:链式运算或迭代更新(如多次相乘)。
    • 示例:BigInt t(1); t *= BigInt("123"); // t = 123
    • 等价:a *= b; 基本等价于 a = a * b;

比较运算符

  • bool operator==(const BigInt& rhs) const

    • 作用:判断两数是否完全相等(位数与每位都相同)。
    • 示例:BigInt("00123") == BigInt("123") // true,因为会规范化
  • bool operator!=(const BigInt& rhs) const

    • 作用:与 == 相反。
  • bool operator<(const BigInt& rhs) const

    • 作用:按数值大小比较(先比位数,再比每位字符)。
    • 示例:BigInt("99") < BigInt("100") // true
  • bool operator<=(const BigInt& rhs) const

    • 作用:小于或等于。
  • bool operator>(const BigInt& rhs) const

    • 作用:大于。
  • bool operator>=(const BigInt& rhs) const

    • 作用:大于或等于。

输出运算符

  • std::ostream& operator<<(std::ostream& os, const BigInt& x)
    • 作用:把 BigInt 直接打印为十进制字符串。
    • 场景:调试输出、记录日志、展示结果。
    • 示例:std::cout << BigInt("00100") << "\n"; // 打印 100

示例

#include "BigInt.h"
#include 
<iostream>
using namespace std;

int main() {
        BigInt a(static_cast
<size_t>(0));   // 0
        BigInt b(12345);                    // 12345
        BigInt c("00012345");              // 12345(会去掉前导零)

        BigInt s = b + c;                   // 24690
        s += BigInt("10");                 // 24700

        BigInt p = BigInt("1234") * BigInt("5678"); // 7006652

        cout << s << "\n";
        cout << p << "\n";
}

设计理念与边界约束

  • 只支持非负整数;不处理负号和小数。
  • 输入有误(包含非数字或空串)时按 0 处理,避免异常。
  • 规范化保证没有前导零,方便比较与输出一致。
  • 算法简洁易懂,适合教学与中等规模数据;如需更高性能可考虑更高级算法(不在本项目范围内)。

测试与验证

  • 运行示例:
make && ./bigint_demo
  • 包含的测试覆盖:构造(含前导零与超大数)、比较、加法与+=、乘法与*=、与 0/1 的乘加、以及大数相乘场景。全部通过将输出 All tests passed.

常见疑问(FAQ)

  • 为什么要写 BigInt(static_cast size_t (0))
    • 因为存在 BigInt(const char*) 构造,BigInt(0) 在个别编译环境中可能与空指针构造产生歧义。显式转换能避免这个问题。
  • 可以打印或读取负数吗?
    • 当前版本不支持负数;若课程后续需要,可扩展存储符号位并调整算术与比较逻辑。
  • 为什么最高位在 s[0]
    • 为与课程字段要求和直观的十进制字符串表示保持一致,简化规范化与比较逻辑。

维护建议

  • 新增操作(如减法、除法)时继续使用规范化策略,保持内部表示一致。
  • 若切换存储结构(如低位在前或改用 std::vector char ),请同步修改所有算术、比较与输出代码。

源码

本项目仅用于交流学习。请按课程与学校规定提交作业。

BigInt.h

#ifndef BIGINT_H
#define BIGINT_H

#include 
<cstddef>
#include 
<iostream>
using namespace std;

// BigInt: stores non-negative integers in base-10 as a char buffer of digits
// Fields required by assignment:
// - bool isZero
// - size_t len   : number of digits currently used (without leading zeros)
// - size_t size  : capacity of the buffer
// - char* s      : dynamically allocated buffer of digits (most significant first)
class BigInt {
public:
    // Constructors
    BigInt(size_t value = 0);                 // BigInt(size_t)
    BigInt(const char* str);                       // BigInt(const char*)
    BigInt(const BigInt& other);                   // BigInt(const BigInt&)

    // Destructor
    ~BigInt();

    // Assignment
    BigInt& operator=(const BigInt& other);

    // Arithmetic
    BigInt operator+(const BigInt& rhs) const;
    BigInt& operator+=(const BigInt& rhs);
    BigInt operator*(const BigInt& rhs) const;
    BigInt& operator*=(const BigInt& rhs);

    // Comparisons
    bool operator==(const BigInt& rhs) const;
    bool operator!=(const BigInt& rhs) const;
    bool operator<(const BigInt& rhs) const;
    bool operator<=(const BigInt& rhs) const;
    bool operator>(const BigInt& rhs) const;
    bool operator>=(const BigInt& rhs) const;

    // Output
    friend ostream& operator<<(ostream& os, const BigInt& x);

private:
    bool isZero;
    size_t len;
    size_t size;
    char* s; // digits stored most significant first, each '0'..'9'

    // Helpers
    void init_with_zero();
    void ensure_capacity(size_t cap);
    void normalize(); // remove leading zeros and set isZero appropriately
};

#endif // BIGINT_H

BigInt.cpp

#include "BigInt.h"
#include 
<cstring>
#include 
<algorithm>
using namespace std;

static bool is_valid_digits(const char* str) {
    if (!str || *str == '\0') return false;
    // allow leading zeros, but must be digits only
    for (const char* p = str; *p; ++p) {
        if (*p < '0' || *p > '9') return false;
    }
    return true;
}

void BigInt::init_with_zero() {
    isZero = true;
    len = 1;
    size = 1;
    s = new char[size];
    s[0] = '0';
}

void BigInt::ensure_capacity(size_t cap) {
    if (cap <= size) return;
    size_t newCap = max(cap, size * 2);
    char* ns = new char[newCap];
    memcpy(ns, s, len);
    delete[] s;
    s = ns;
    size = newCap;
}

void BigInt::normalize() {
    // remove leading zeros from s (MSD at s[0])
    size_t i = 0;
    while (i < len && s[i] == '0') ++i;
    if (i == len) {
        // all zeros -> represent as single '0'
        isZero = true;
        len = 1;
        s[0] = '0';
        return;
    }
    if (i > 0) {
        // shift left by i positions
        size_t newLen = len - i;
        for (size_t j = 0; j < newLen; ++j) s[j] = s[j + i];
        len = newLen;
    }
    isZero = false;
}

BigInt::BigInt(size_t value) {
    if (value == 0) {
        init_with_zero();
        return;
    }
    isZero = false;
    // compute number of digits
    size_t v = value;
    size_t digits = 0;
    while (v) { v /= 10; ++digits; }
    len = digits;
    size = max
<size_t>(digits, 1);
    s = new char[size];
    // fill from end
    size_t idx = len;
    v = value;
    while (v) {
        s[--idx] = char('0' + (v % 10));
        v /= 10;
    }
}

BigInt::BigInt(const char* str) {
    if (!is_valid_digits(str)) {
        // treat invalid input as zero
        init_with_zero();
        return;
    }
    len = strlen(str);
    size = max
<size_t>(len, 1);
    s = new char[size];
    memcpy(s, str, len);
    normalize();
}

BigInt::BigInt(const BigInt& other) : isZero(other.isZero), len(other.len), size(other.size) {
    s = new char[size];
    memcpy(s, other.s, len);
}

BigInt::~BigInt() {
    delete[] s;
}

BigInt& BigInt::operator=(const BigInt& other) {
    if (this == &other) return *this;
    isZero = other.isZero;
    len = other.len;
    if (other.size > size) {
        delete[] s;
        size = other.size;
        s = new char[size];
    }
    memcpy(s, other.s, len);
    return *this;
}

BigInt BigInt::operator+(const BigInt& rhs) const {
    if (isZero) return rhs;
    if (rhs.isZero) return *this;
    // add from LSD: since MSD at s[0], we iterate from back
    size_t i = len;
    size_t j = rhs.len;
    size_t maxLen = max(len, rhs.len);
    BigInt result(static_cast
<size_t>(0));
    result.ensure_capacity(maxLen + 1);
        result.len = maxLen + 1; // possible carry
    result.isZero = false;
    // temp fill zeros
    for (size_t k = 0; k < result.len; ++k) result.s[k] = '0';

    int carry = 0;
    size_t ri = result.len;
    while (i > 0 || j > 0 || carry) {
        int a = (i > 0) ? (s[--i] - '0') : 0;
        int b = (j > 0) ? (rhs.s[--j] - '0') : 0;
        int sum = a + b + carry;
        carry = sum / 10;
        int digit = sum % 10;
        result.s[--ri] = char('0' + digit);
    }
    // if any leading (unused) positions remain, keep '0' there
    // normalize to remove leading zeros
    result.normalize();
    return result;
}

BigInt& BigInt::operator+=(const BigInt& rhs) {
    *this = *this + rhs;
    return *this;
}

BigInt BigInt::operator*(const BigInt& rhs) const {
    if (isZero || rhs.isZero) {
           return BigInt(static_cast
<size_t>(0));
    }
    // result length at most len + rhs.len
        BigInt result(static_cast
<size_t>(0));
    result.ensure_capacity(len + rhs.len);
    result.len = len + rhs.len;
    result.isZero = false;
    for (size_t i = 0; i < result.len; ++i) result.s[i] = '0';

    // multiply from LSD
    // indices: our LSD at s[len-1], rhs LSD at rhs.s[rhs.len-1]
    for (size_t i = 0; i < len; ++i) {
        int carry = 0;
        int a = s[len - 1 - i] - '0';
        size_t pos = result.len - 1 - i; // starting position in result
        for (size_t j = 0; j < rhs.len; ++j) {
            int b = rhs.s[rhs.len - 1 - j] - '0';
            int cur = (result.s[pos - j] - '0') + a * b + carry;
            carry = cur / 10;
            result.s[pos - j] = char('0' + (cur % 10));
        }
        // place remaining carry
        size_t k = pos - rhs.len;
        while (carry > 0) {
            int cur = (result.s[k] - '0') + carry;
            carry = cur / 10;
            result.s[k] = char('0' + (cur % 10));
            if (k == 0) break; else --k;
        }
    }
    result.normalize();
    return result;
}

BigInt& BigInt::operator*=(const BigInt& rhs) {
    *this = *this * rhs;
    return *this;
}

bool BigInt::operator==(const BigInt& rhs) const {
    if (isZero && rhs.isZero) return true;
    if (len != rhs.len) return false;
    for (size_t i = 0; i < len; ++i) {
        if (s[i] != rhs.s[i]) return false;
    }
    return true;
}

bool BigInt::operator!=(const BigInt& rhs) const { return !(*this == rhs); }

bool BigInt::operator<(const BigInt& rhs) const {
    if (isZero && rhs.isZero) return false;
    if (isZero && !rhs.isZero) return true;
    if (!isZero && rhs.isZero) return false;
    if (len != rhs.len) return len < rhs.len;
    for (size_t i = 0; i < len; ++i) {
        if (s[i] != rhs.s[i]) return s[i] < rhs.s[i];
    }
    return false; // equal
}

bool BigInt::operator<=(const BigInt& rhs) const { return (*this < rhs) || (*this == rhs); }
bool BigInt::operator>(const BigInt& rhs) const { return rhs < *this; }
bool BigInt::operator>=(const BigInt& rhs) const { return !(*this < rhs); }

ostream& operator<<(ostream& os, const BigInt& x) {
    os.write(x.s, static_cast
<streamsize>(x.len));
    return os;
}

main.cpp

#include "BigInt.h"
#include 
<iostream>
#include 
<vector>
// #include 
<cassert>
using namespace std;

int main() {

    cout << "1. Equality & Comparison" << endl << "----------" << endl;

    // Constructors
    BigInt a(static_cast
<size_t>(0));
    BigInt b(12345);
    BigInt c("00012345");
    BigInt d("9999999999999999999999999999");
    BigInt e(b); // copy

    cout << "a=0" << "\n";
    cout << "b=12345" << "\n";
    cout << "c=00012345" << "\n";
    cout << "d=9999999999999999999999999999" << "\n";
    cout << "e=b" << "\n";

    // Equality
    cout << "a==0:" << (a == BigInt(static_cast<size_t>(0))) << endl;
    cout << "b==c:" << (b == c) << endl;
    cout << "b==e:" << (b == e) << endl;
    cout << "b!=a:" << (b != a) << endl;

    // Comparisons
    cout << "a<b:" << (a < b) << endl;
    cout << "b>=c:" << (b >= c) << endl;
    cout << "d>b:" << (d > b) << endl;
    cout << "b<c:" << (b < c) << endl;
    cout << "b<=c:" << (b <= c) << endl;

    // Addition
    cout << "--------------------" << endl << "2. Addition" << endl << "----------" << endl;
    BigInt x("999");
    BigInt y("1");
    BigInt z = x + y; // 1000
    cout << "x=999,y=1"<<endl;
    cout << "z=x+y=" << z << "\n";

    BigInt aa("12345678901234567890");
    BigInt bb("98765432109876543210");
    cout << "aa=12345678901234567890,bb=98765432109876543210" << endl;
    BigInt cc = aa + bb; // 111111111011111111100
    cout << "aa+bb=" << cc << "\n";

    // +=
    BigInt m(static_cast
<size_t>(0));
    cout << "m=0" << endl;
    m += BigInt("5");
    cout << "m+=5:" << m <<endl;
    m += BigInt("95");
    cout << "m+=95:" << m <<endl;

    // Multiplication
    cout << "--------------------" << endl << "3. Multiplication" << endl << "----------" << endl;
    BigInt p1("1234");
    BigInt p2("5678");
    BigInt p = p1 * p2; // 7006652
    cout << "p1=1234,p2=5678" <<endl;
    cout << "p1*p2:" << p << "\n";

    // *= with zero and one
    BigInt t(static_cast
<size_t>(0));
    t *= BigInt("98765");
    cout << "t=0" << endl;
    cout << "t*=98765:" << t << endl;
    BigInt u("1");
    u *= BigInt("12345678901234567890");
    cout << "u=1" << endl;
    cout << "u*=12345678901234567890:" << u <<endl;

    cout << "--------------------" << endl << "All End.\n";
    return 0;
}

Makefile

CXX := g++
CXXFLAGS := -std=c++17 -O2 -Wall -Wextra -pedantic

TARGET := bigint_demo
SRCS := BigInt.cpp main.cpp
OBJS := $(SRCS:.cpp=.o)

all: $(TARGET)

$(TARGET): $(OBJS)
    $(CXX) $(CXXFLAGS) -o $@ $^

%.o: %.cpp BigInt.h
    $(CXX) $(CXXFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: all clean

程序下载

点击此处下载

正文完
 0
评论(没有评论)

YanQS's Blog