定义一个任意进制的数Number类,继承自string类:
Number类的数据成员:
_radix表示数的基数,
_inum表示数的相应的十进制数
用string存储任意进制的数(可以说是用字符串来存,r进制的数不适合用int来存)
/*
Number类的数据成员:
_radix表示数的基数,
_inum表示数的相应的十进制数
*/
// more: http://msdn.microsoft.com/en-us/library/0heszx3w.aspx
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Number : public string {
public:
Number(string str = "", unsigned int radix = 0, int inum = 0)
: string(str), _radix(radix) ,_inum(inum) {}
void set(unsigned int radix);
void strtoi(void);
void itostr(void);
Number operator+(const Number &);
private:
unsigned int _radix;
int _inum; // 该数的十进制表示
};
void Number::set(unsigned int radix)
{
_radix = radix;
strtoi();
// 字符串一但改变,_inum马上变,与+无关
// 哪个属性更新了,就马上调用相应的方法以随时保持一致
}
void Number::itostr(void) // 十进制到r进制:除k取余法
{
Number &str = *this;
if(_inum == 0) {
str.push_back('0');
}
else {
int balance = 0;
for(int i = 0; _inum > 0; i++) {
balance = _inum % _radix;
// 注意十六进制
str.push_back(balance + (balance < 10 ? '0' : 'A' - 10));
_inum /= _radix;
}
reverse(str.begin(), str.end());
}
}
void Number::strtoi(void) // r进制到十进制:秦九韶算法
{
const Number &str = *this;
if ('0' <= str[0] && str[0] <= (min((int)'9', (int)('0' + _radix - 1))))
_inum = str[0] - '0';
else if ('A' <= str[0] && str[0] <= 'Z') // 注意十六进制,以下待合法性检查
_inum = 10 + str[0] - 'A';
else {
cout << "Input Error!" << endl;
return ;
} // 秦九韶算法中的第一个数
for (unsigned int i = 1; i < str.length(); i++) {
if ('0' <= str[i] && str[i] <= '9')
_inum = (_inum * _radix) + str[i] - '0';
else if ('A' <= str[i] && str[i] <= 'Z')
_inum = (_inum * _radix) + 10 + str[i] - 'A';
else {
cout << "Input Error!" << endl;
return ;
}
}
}
Number Number::operator+(const Number &b)
{
Number sum;
Number &a = *this;
if(a._radix != b._radix) {
cerr << "a与b的基数不相等!" << endl;
exit(-1);
}
sum._radix = a._radix;
sum._inum = a._inum + b._inum;
sum.itostr(); // 紧跟_inum的变化调用
return sum;
// return IToA(AToI(a) + AToI(b), a.radix());
}
int main(void)
{
Number a, b;
int radix;
while (cin >> radix && radix >= 2) {
cin >> a >> b;
// set前必须输入a与b,要初始化(见set函数),这同时也是一个缺陷,待改进
a.set(radix);
b.set(radix); // 这两句最好到构造函数中去(封装起来?),待改进
Number c = a + b;
cout << a << " + " << b << " = " << c << endl << endl;
}
return 0;
}
运行结果如下(依次输入基数,第一个数,第二个数):
为什么说继承方便呢?你是否注意到输入a,b时竟然可以直接cin,cout,在itostr函数中用了push_back等string类的成员函数,及在strtoi函数中也用了[ ]运算符,这些其实都是string类中的成员函数(包括标准输入输出的>>与<<),Number类公有继承了string类,都可以直接拿来用了,挺方便!
这里的Number类中只重载了+,当然可以类比地重载-, *, /,这里我就不多写了。
其实在此之前我写了几个版本,比如没使用_inum作为数的属性,重载+、进制转换函数是作为全局函数的。而现在写的版本基本符合一个封装了。
启示:为什么定义类时往往不把属性设为公有的,其中的原因是为了安全性。而我从这篇博客得到了另一点原因,在继承时也使结构更清晰(猜想),我对C++的string类的内部也不是很了解,看源码没找到string类的数据成员,其实string类也是从其它类继承来的。
附:之前的一个版本
// 进制互转运算,十进制数用int表示,其他进制数用Number表示
// http://msdn.microsoft.com/en-us/library/0heszx3w.aspx
#include <iostream>
#include <string>
#include <cassert>
#include <algorithm>
using namespace std;
class Number : public string {
unsigned int _radix;
public:
Number(string str = "", int r = 0) : string(str), _radix(r) {}
Number operator+(const Number &);
Number operator-(const Number &);
Number operator*(const Number &);
Number operator/(const Number &);
void set(unsigned int r) {
_radix = r;
}
unsigned int radix(void) const {
return _radix;
}
};
Number IToA(int inum, int radix); // 必须在这声明
int AToI(Number str);
Number Number::operator+(const Number &b)
{
const Number &a = *this;
assert(a.radix() == b.radix());
return IToA(AToI(a) + AToI(b), a.radix());
}
Number Number::operator-(const Number &b)
{
const Number &a = *this;
assert(a.radix() == b.radix());
return IToA(AToI(a) - AToI(b), a.radix());
}
Number Number::operator*(const Number &b)
{
const Number &a = *this;
assert(a.radix() == b.radix());
return IToA(AToI(a) * AToI(b), a.radix());
}
Number Number::operator/(const Number &b)
{
const Number &a = *this;
assert(a.radix() == b.radix());
return IToA(AToI(a) / AToI(b), a.radix());
}
Number IToA(int inum, int radix)
{
Number str;
if(inum == 0) {
str.push_back('0');
}
else {
int balance = 0;
for(int i = 0; inum > 0; i++) {
balance = inum % radix;
str.push_back(balance + (balance < 10 ? '0' : 'A' - 10));
inum /= radix;
}
reverse(str.begin(), str.end());
}
return str;
}
int AToI(Number str)
{
int inum = (str[0] < 'A' ? str[0] - '0' : 10 + str[0] - 'A'); // 可加强检查合法性
for (unsigned int i = 1; i < str.length(); i++) {
inum = (inum * str.radix()) + (str[i] < 'A' ? str[i] - '0' : 10 + str[i] - 'A');
}
return inum;
}
/*
// 早期版本
Number operator*(Number a, Number b)
{
assert(a.radix() == b.radix());
return IToA(AToI(a) * AToI(b), a.radix());
}
*/
int main(void)
{
Number a, b;
int radix;
while (cin >> a >> b >> radix) {
assert(radix >= 2);
a.set(radix);
b.set(radix);
cout << a << " + " << b << " = " << a + b << endl
<< a << " - " << b << " = " << a - b << endl
<< a << " * " << b << " = " << a * b << endl
<< a << " / " << b << " = " << a / b << endl << endl;
}
return 0;
}
2012/7/29
其实关于r进制的那些字符(0123456789ABCDEF)用个哈希挺不错(参考自:http://www.cnblogs.com/applebunny/archive/2012/06/21/2557361.html):
const char a[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
避免繁杂的ANSII码计算
2013/4/5
现在看这篇博客,其实设计是不对的,string类和Number类不是继承关系,原来只是为了方便而借用了string类的成员方法。
新的设计参见: