BigInt的实现——C++编程风格读书笔记
C++编程风格这本书前面一些章节都觉得很简明易懂,但是读到效率这一章是才充分认识到读别人的代码还是很痛苦的一件事。书中给出的需要改进的初始类如下:
class BigInt
{
private:
char* digits;
unsigned ndigits;
BigInt(char *d,unsigned n)
{
digits = d;
ndigits = n;
}
friend class DigitStream;
public:
BigInt(const char*);
BigInt(unsigned n = );
BigInt(const BigInt&);
void operator=(const BigInt&);
BigInt operator+(const BigInt&) const;
void print(FILE* f = stdout) const;
~BigInt() {delete [] digits;}
}; class DigitStream
{
private:
char* dp;
unsigned nd;
public:
DigitStream(const BigInt& n)
{
dp = n.digits;
nd = n.ndigits;
}
unsigned operator++()
{
if(nd == )
return ;
else
{
nd--;
return *dp++;
}
}
}; void BigInt::print(FILE* f) const
{
for(int i = ndigits - ;i >= ;i --)
fprintf(f,"%c",digits[i]+'');
}
void BigInt::operator=(const BigInt& n)
{
if (this == &n) return;
delete [] digits;
unsigned i = n.ndigits;
digits = new char[ndigits = i];
char* p = digits;
char* q = n.digits;
while(i--) *p++ = *q++;
}
BigInt BigInt::operator+(const BigInt& n) const
{
unsigned maxDigits = (ndigits > n.ndigits ? ndigits : n.ndigits) + ;
char* sumPtr = new char[maxDigits];
BigInt sum(sumPtr,maxDigits);
DigitStream a(*this);
DigitStream b(n);
unsigned i = maxDigits;
unsigned carry = ;
while (i --)
{
*sumPtr = (++a) + (++b) + carry;
if(*sumPtr >= )
{
carry = ;
*sumPtr -= ;
}
else carry = ;
sumPtr++;
}
return sum;
} BigInt::BigInt(unsigned n)
{
char d[*sizeof(unsigned)+];
char *dp = d;
ndigits = ;
do
{
*dp++ = n % ;
n /= ;
ndigits++;
} while(n > );
digits = new char[ndigits];
for(register int i = ;i < ndigits;i++)
digits[i] = d[i];
} BigInt::BigInt(const BigInt& n)
{
unsigned i = n.ndigits;
digits = new char[ndigits = i];
char* p = digits;
char* q = n.digits;
while(i--) *p++ = *q++;
} BigInt::BigInt(const char* digitString)
{
unsigned n = strlen(digitString);
if(n != )
{
digits = new char[ndigits=n];
char* p = digits;
const char* q = &digitString[n];
while(n--) *p++ = *--q - '';
}
else
{
digits = new char[ndigits=];
digits[] = ;
}
}
因为这一章讨论的是效率问题,所以我们将统计代码的运行时间,查看代码是否存在内存泄露的代码都加入程序中。因为用的机器比书上的机器快,所以把循环增加到了10000次,耗时4.7秒。
void test(){
clock_t start = clock();
BigInt b = ;
for(int i=; i <= ; ++i){
b = b + ;
}
b.print();
clock_t end = clock();
cout << endl << static_cast<double>(end - start)/CLOCKS_PER_SEC << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
test();
_CrtDumpMemoryLeaks();
cin.get();
return ;
}
觉得原始代码晦涩的同志们肯定一眼就能发现DigitStream真是很多余的东西,影响我们对代码的理解,将DigitStream去掉,得到代码
class BigInt
{
private:
char* digits;
unsigned ndigits;
BigInt(char *d,unsigned n)
{
digits = d;
ndigits = n;
}
char fetch(int i) const {
return i < ndigits ? digits[i] : ;
}
public:
BigInt(const char*);
BigInt(unsigned n = );
BigInt(const BigInt&);
void operator=(const BigInt&);
BigInt operator+(const BigInt&) const;
void print(FILE* f = stdout) const;
~BigInt() {delete [] digits;}
}; void BigInt::print(FILE* f) const
{
for(int i = ndigits - ;i >= ;i --)
fprintf(f,"%c",digits[i]+'');
}
void BigInt::operator=(const BigInt& n)
{
if (this == &n) return;
delete [] digits;
unsigned i = n.ndigits;
digits = new char[ndigits = i];
char* p = digits;
char* q = n.digits;
while(i--) *p++ = *q++;
}
BigInt BigInt::operator+(const BigInt& n) const
{
unsigned maxDigits = (ndigits > n.ndigits ? ndigits : n.ndigits) + ;
char* sumPtr = new char[maxDigits];
BigInt sum(sumPtr,maxDigits);
unsigned carry = ;
for(int i=; i<maxDigits; ++i)
{
*sumPtr = fetch(i) + n.fetch(i) + carry;
if(*sumPtr >= )
{
carry = ;
*sumPtr -= ;
}
else carry = ;
sumPtr++;
}
return sum;
} BigInt::BigInt(unsigned n)
{
char d[*sizeof(unsigned)+];
char *dp = d;
ndigits = ;
do
{
*dp++ = n % ;
n /= ;
ndigits++;
} while(n > );
digits = new char[ndigits];
for(register int i = ;i < ndigits;i++)
digits[i] = d[i];
} BigInt::BigInt(const BigInt& n)
{
unsigned i = n.ndigits;
digits = new char[ndigits = i];
char* p = digits;
char* q = n.digits;
while(i--) *p++ = *q++;
} BigInt::BigInt(const char* digitString)
{
unsigned n = strlen(digitString);
if(n != )
{
digits = new char[ndigits=n];
char* p = digits;
const char* q = &digitString[n];
while(n--) *p++ = *--q - '';
}
else
{
digits = new char[ndigits=];
digits[] = ;
}
}
运行,速度略有加快,耗时4.4秒,但无明显改进。
我们注意到之前的输出中前面都有那么多的0,这些0实际上都是木有必要的。其根源在于我们做加法运算的时候不管有没有进位,都默认进了一位,也就是每做一次加法运算,数位的长度都至少加一位,这在速度和空间上都是一种浪费。最简单的解决方法是每次加完以后都检测一下最高位,这样将加法改进如下:
BigInt BigInt::operator+(const BigInt& n) const
{
unsigned maxDigits = (ndigits > n.ndigits ? ndigits : n.ndigits) + ;
char* sumPtr = new char[maxDigits];
BigInt sum(sumPtr,maxDigits);
unsigned carry = ;
for(int i=; i<maxDigits; ++i)
{
*sumPtr = fetch(i) + n.fetch(i) + carry;
if(*sumPtr >= )
{
carry = ;
*sumPtr -= ;
}
else carry = ;
sumPtr++;
}
if(sum.digits[maxDigits-]==){
--sum.ndigits;
}
return sum;
}
这一次的改进可以说是质的飞跃,耗时一下子缩短为0.069秒。输出的结果里,那些讨厌的0也消失了。
为了做进一步的改进,我们把循环改为一百万,运行耗时6.2秒
再次分析程序,发现在赋值时,如果位数相同,不需要重新分配空间,根据这个思路,将赋值函数修改为:
void BigInt::operator=(const BigInt& n)
{
if (this == &n) return;
unsigned i = n.ndigits;
if(ndigits != i){
delete [] digits;
digits = new char[i];
}
ndigits = i;
char* p = digits;
char* q = n.digits;
while(i--) *p++ = *q++;
}
运行,时间缩短为5.1秒
再次分析b = b + 1这个过程,先做加法运算,再做拷贝构造,赋值运算,其中加法运算需要分配空间,构造对象和逐个计算赋值,拷贝构造需要分配空间和逐个赋值,赋值运算只在需要的时候重新分配空间(次数可忽略),但是会逐个赋值。
一共分配空间两次,逐个赋值3次,逐个计算一次。
我们将这个加法过程进行优化,先构造对象再做+=运算和赋值,其中构造对象需要分配空间和逐个赋值,+=运算需要逐个计算(只在需要时分配空间,可忽略),assign在需要的时候分配空间(次数可忽略)和逐个赋值
一共分配空间一次,逐个赋值2次,逐个计算一次,优化后的代码如下:
class BigInt
{ private:
char* digits;
unsigned ndigits;
unsigned size;
BigInt (const BigInt&, const BigInt&);
char fetch (unsigned i) const { return i < ndigits ? digits[i] : ; }
public:
friend BigInt operator+(const BigInt&,const BigInt&);
BigInt (const char*);
BigInt (unsigned n = );
BigInt (const BigInt&);
BigInt& operator= (const BigInt&);
BigInt& operator+= (const BigInt&);
void print (FILE* f = stdout) const;
~BigInt() {delete [] digits;}
}; inline BigInt operator+(const BigInt& left, const BigInt& right)
{
return BigInt(left,right);
}
BigInt& BigInt::operator+=(const BigInt& rhs)
{
unsigned max = +(rhs.ndigits > ndigits ? rhs.ndigits : ndigits);
if(size < max)
{
char *d = new char[size = max];
for(unsigned i = ;i < ndigits;++i)
d[i] = digits[i];
delete [] digits;
digits = d;
}
while(ndigits < max)
digits[ndigits++] = ;
for(unsigned i = ;i < ndigits;++i)
{
digits[i] += rhs.fetch(i);
if(digits[i]>=)
{
digits[i] -= ;
digits[i+] += ;
}
}
if(digits[ndigits - ] ==)
--ndigits;
return *this;
}
void BigInt::print (FILE* f) const
{
for (int i = ndigits - ; i >= ; i --)
fprintf (f, "%c", digits[i] + '');
} BigInt& BigInt::operator= (const BigInt& rhs)
{
if (this == &rhs) return *this;
ndigits = rhs.ndigits;
if (ndigits > size)
{
delete [] digits;
digits = new char[size = ndigits];
}
for(unsigned i = ;i < ndigits;++i)
digits[i] = rhs.digits[i];
return *this;
} BigInt::BigInt(const BigInt& left, const BigInt& right)
{
size = + (left.ndigits > right.ndigits ? left.ndigits : right.ndigits);
digits = new char[size];
ndigits = left.ndigits;
for (unsigned i = ;i < ndigits;++i)
digits[i] = left.digits[i];
*this += right;
}
BigInt::BigInt (unsigned u)
{
char v = u;
for (ndigits = ;(v/=) > ;++ndigits)
;
digits = new char[size = ndigits];
for(unsigned i = ;i < ndigits;++ i)
{
digits[i] = u % ;
u /=;
}
} BigInt::BigInt (const BigInt& copyFrom)
{
size = ndigits = copyFrom.ndigits;
digits = new char[size];
for(unsigned i = ;i < ndigits;++i)
digits[i] = copyFrom.digits[i];
} BigInt::BigInt (const char* digitString)
{
if(digitString[] == '/0')
digitString = "";
size = ndigits = strlen(digitString);
digits = new char[size];
for(unsigned i = ;i < ndigits;++i)
digits[i] = digitString[ndigits - - i] - '';
}
运行,时间缩短为2.6秒
最后,优化客户代码,用b += 1;来代替b = b + 1;时间再次缩短为1.4秒
BigInt的实现——C++编程风格读书笔记的更多相关文章
- 《C#高级编程》读书笔记
<C#高级编程>读书笔记 C#类型的取值范围 名称 CTS类型 说明 范围 sbyte System.SByte 8位有符号的整数 -128~127(−27−27~27−127−1) sh ...
- 《Windows核心编程》读书笔记 上
[C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...
- JAVA编程思想读书笔记(五)--多线程
接上篇JAVA编程思想读书笔记(四)--对象的克隆 No1: daemon Thread(守护线程) 参考http://blog.csdn.net/pony_maggie/article/detail ...
- JAVA编程思想读书笔记(四)--对象的克隆
接上篇JAVA编程思想读书笔记(三)--RTTI No1: 类的克隆 public class MyObject implements Cloneable { int i; public MyObje ...
- JAVA编程思想读书笔记(三)--RTTI
接上篇JAVA编程思想读书笔记(二) 第十一章 运行期类型判定 No1: 对于作为程序一部分的每个类,它们都有一个Class对象.换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当的说, ...
- JAVA编程思想读书笔记(二)--容器
接上篇JAVA编程思想读书笔记(一) 第八章.对象的容纳 No1: java提供了四种类型的集合类:Vector(矢量).BitSet(位集).Stack(堆栈).Hashtable(散列表) No2 ...
- 《[MySQL技术内幕:SQL编程》读书笔记
<[MySQL技术内幕:SQL编程>读书笔记 2019年3月31日23:12:11 严禁转载!!! <MySQL技术内幕:SQL编程>这本书是我比较喜欢的一位国内作者姜承尧, ...
- Java编程思想读书笔记
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- 《MySQL 存储过程编程》-读书笔记
本书结构: 第一部分:存储编程基础 第1章:存储过程程序基础 第2章:MySQL存储编程指南 第3章:语言基础 第4章:语句块 第5章:在存储程序中使用SQL 第一章:MySQL存储程序介绍 存储程序 ...
随机推荐
- ARM中断向量表与响应流程【转】
转自:http://blog.csdn.net/honour2sword/article/details/40213417 一首先中断向量表定义在哪里?如何加载? 二 中断向量表与中断服务程序 三处理 ...
- KVM虚拟机建立快照
部分转载: http://blog.csdn.net/gg296231363/article/details/6899533 windows虚拟机默认镜像格式为raw,快照默认格式为qcow2.win ...
- 大原則 研讀 spec 與 code 的 心得
最近在研究 stm32f429i-disc0 的 device tree source code, 並且 參造 Devicetree Specification Release 0.1, 在 dts ...
- monkey测试===关于monkey测试的介绍
https://www.cnblogs.com/lauren1003/p/6193277.html
- 12-4 NSString
原文:http://rypress.com/tutorials/objective-c/data-types/nsstring NSString 在本教程的内容可能我们已经看到过很多次了,NSStri ...
- grep常见操作整理(更新)
提取邮箱和URL [root@test88 ~]# cat url_email.txt root@gmail.com,http://blog.peter.com,peter@qq.com [root@ ...
- .NET直接编译成本地代码:.NET Native架构简介
原文地址:http://blog.csdn.net/atfield/article/details/23449089
- windows系统安装mysql压缩zip版
1.下载 打开官网:https://www.mysql.com 进入DOWNLOADS--->Community--->MySQL Community Server,选择系统对应的版本点击 ...
- 字符串aaaa......bbbb....ccc...dddddd用正则替换为abcd
public static void main(String[] args) { String s = "aaaa......bbbb....ccc...dddddd"; Stri ...
- 访问Github慢的解决办法
http://blog.csdn.net/sunsteam/article/details/63253933 http://tool.chinaz.com/dns 151.101.196.249 g ...