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++编程风格读书笔记的更多相关文章

  1. 《C#高级编程》读书笔记

    <C#高级编程>读书笔记 C#类型的取值范围 名称 CTS类型 说明 范围 sbyte System.SByte 8位有符号的整数 -128~127(−27−27~27−127−1) sh ...

  2. 《Windows核心编程》读书笔记 上

    [C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对 ...

  3. JAVA编程思想读书笔记(五)--多线程

    接上篇JAVA编程思想读书笔记(四)--对象的克隆 No1: daemon Thread(守护线程) 参考http://blog.csdn.net/pony_maggie/article/detail ...

  4. JAVA编程思想读书笔记(四)--对象的克隆

    接上篇JAVA编程思想读书笔记(三)--RTTI No1: 类的克隆 public class MyObject implements Cloneable { int i; public MyObje ...

  5. JAVA编程思想读书笔记(三)--RTTI

    接上篇JAVA编程思想读书笔记(二) 第十一章 运行期类型判定 No1: 对于作为程序一部分的每个类,它们都有一个Class对象.换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当的说, ...

  6. JAVA编程思想读书笔记(二)--容器

    接上篇JAVA编程思想读书笔记(一) 第八章.对象的容纳 No1: java提供了四种类型的集合类:Vector(矢量).BitSet(位集).Stack(堆栈).Hashtable(散列表) No2 ...

  7. 《[MySQL技术内幕:SQL编程》读书笔记

    <[MySQL技术内幕:SQL编程>读书笔记 2019年3月31日23:12:11 严禁转载!!! <MySQL技术内幕:SQL编程>这本书是我比较喜欢的一位国内作者姜承尧, ...

  8. Java编程思想读书笔记

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  9. 《MySQL 存储过程编程》-读书笔记

    本书结构: 第一部分:存储编程基础 第1章:存储过程程序基础 第2章:MySQL存储编程指南 第3章:语言基础 第4章:语句块 第5章:在存储程序中使用SQL 第一章:MySQL存储程序介绍 存储程序 ...

随机推荐

  1. SpringCloud Fegin超时重试源码

    springCloud中最重要的就是微服务之间的调用,因为网络延迟或者调用超时会直接导致程序异常,因此超时的配置及处理就至关重要. 在开发过程中被调用的微服务打断点发现会又多次重试的情况,测试环境有的 ...

  2. Kaggle机器学习之模型集成(stacking)

    Stacking是用新的模型(次学习器)去学习怎么组合那些基学习器,它的思想源自于Stacked Generalization这篇论文.如果把Bagging看作是多个基分类器的线性组合,那么Stack ...

  3. static class 和 non static class 的区别

    static class non static class 1.用static修饰的是内部类,此时这个 内部类变为静态内部类:对测试有用: 2.内部静态类不需要有指向外部类的引用: 3.静态类只能访问 ...

  4. 通过file中的字段查询MySQL内容

    # -*- coding: utf-8 -*- import MySQLdb with open(r"./user.txt", "r") as f: f.rea ...

  5. 辨别苹果数据线真伪 苹果计算器 Dashboard 知识

    辨别苹果数据线真伪 苹果计算器 Dashboard 知识  苹果数据线真伪的最简单的辨别: 线质柔软 用数据线连接适配器(苹果自带的适配器)充电 连接手机 如果该手机数据线是假的, 在手机上会提示”该 ...

  6. iframe弹出框js ie6下存在bug

    ie6的iframe在第一次加载的显示不出来,显示空白,但是很奇怪,刷新就可以正常显示了,一开始以为这只是IE6下iframe加载的bug,但是最后结果发现这是ie6下javascript延迟加载出现 ...

  7. Mybatis学习 PageHelper分页插件

    1.Maven依赖,注意使用PageHelper时的版本必须与Mybatis版本对应 1 <!-- 添加Mybatis依赖 --> 2 <dependency> 3 <g ...

  8. Leetcode 之Largest Rectangle in Histogram(40)

    又是一道构思巧妙的题,暴力求解复杂度太高,通过构造一个递增栈来解决:如果当前元素小于栈顶元素,则说明栈内已经构成一个 递增栈,则分别计算以每个元素为最低值的面积:反之,则入栈. int largest ...

  9. Request对象与Response对象

    1.Request对象 Request对象是来获取请求消息的,是由服务器(Tomcat)创建的. Request对象继承体系结构: ServletRequest        --    接口     ...

  10. layui文件单文件和多文件的上传、预览以及删除和修改

    活不多说,直接上代码 单文件上传 1.HTML <blockquote class="layui-elem-quote layui-quote-nm" style=" ...