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存储程序介绍 存储程序 ...
随机推荐
- Linux_信号与信号量【转】
转自:http://blog.csdn.net/sty23122555/article/details/51470949 信号: 信号机制是类UNIX系统中的一种重要的进程间通信手段之一.我们经常使用 ...
- java web 资源文件读取
前提:假设web应用test(工程名) webapps下面有一资源文件test.html 规则:在获取资源时一般使用的是相对路径,以符号/开头,而 / 代表什么取决于这个地址给谁使用.服务器使用时,/ ...
- [ 总结 ] Linux系统启动流程
Linux系统启动过程分析: 按下电源 --> BIOS自检 --> 系统引导(lilo/grub) --> 启动内核 --> 初始化系统 --> 用户登录 1. BIO ...
- redis 安装及安装遇到的问题解决
https://blog.csdn.net/jy0902/article/details/19248299 http://q.fireflyclub.org/?/article/24 https:// ...
- qtp录制时间控件不允许用户手动输入的解决办法
qtp录制时间控件不允许用户手动输入的解决办法 [前面的话] 一边学习qtp,一边用自己的项目试着写代码,而遇到一个问题就会让自己卡壳很久,这次也是这样的,在写好了登录代码以后,自己就试着写第一个预订 ...
- hdu多校6
这个场要恶心死我了.. 1001 积分题,不要四舍五入 //#pragma comment(linker, "/stack:200000000") //#pragma GCC op ...
- centos 7 开机启动配置
centos 7 开机启动 1 开机启动配置文件位于/usr/lib/systemd/system/ 2 nginx的配置[Unit]Description=nginx - high performa ...
- ASP.NET MVC4+EF5(Lambda/Linq)读取数据
希望大家记住,这里讲的所有的知识点,不仅仅是了解了就可以了,还要会灵活用,一定要多思考,撑握其中的编程思想. 本文讲的是委托和事件,这两个词可能你早就耳熟能详,但你是否真正撑握了呢? 本系列讲的C#高 ...
- CodeForces 740D Alyona and a tree
倍增,延迟标记. 考虑一个$u$给他的哪几个祖先$v$贡献了$1$.越往上$dis(v,u)$越大,找到最远的一个还满足条件的$v$,$v$到$u$的父亲这条链上的答案都$+1$.延迟标记一下,然后从 ...
- 洛谷P2751[USACO]工序安排
题目传送门 怎么说呢,这个题目我刚开始随便乱搞了几下,交了个暴力代码上去居然还水了49分,数据确实有点弱啊,然后看到洛谷上那位大佬Redbag的题解瞬间就佩服的五体投地,那真的是简洁.易懂又高效.直接 ...