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. Linux汇编教程01: 基本知识

    在我们开始学习Linux汇编之前,需要简单的了解一下计算机的体系结构.我们不需要特别深入的了解,理解了一些基本概念对与我们理解程序会很有帮助.现在计算机的结构体系都是采用冯诺依曼体系结构的基础上发展过 ...

  2. python 学习笔记 sqlalchemy

    数据库表是一个二维表,包含多行多列.把一个表的内容用Python的数据结构表示出来的话,可以用一个list表示多行,list的每一个元素是tuple,表示一行记录,比如,包含id和name的user表 ...

  3. JS面试题第一弹

    1.javascript的typeof返回哪些数据类型  alert(typeof [1, 2]); //object     alert(typeof 'leipeng'); //string   ...

  4. mac系统安装mysql

    1.下载 打开官网:https://www.mysql.com 进入DOWNLOADS--->Community--->MySQL Community Server--->DOWNL ...

  5. 《java并发编程实战》读书笔记7--线程池的使用

    第8章 线程池的使用 8.1 在任务与执行策略之间的隐性耦合 虽然Executor框架为制定和修改执行策略都提供了相当大的灵活性,但并非所有的任务都适用所有的执行策略.有些类型的任务需要明确地指明执行 ...

  6. Android学习之Android studio篇-Android Studio快捷键总结(mac)

    原文:http://blog.csdn.net/hudfang/article/details/52117065 符号代表键盘按键:⌘(command).⌥(option).⇧(shift).⇪(ca ...

  7. AC日记——贪婪大陆 洛谷 P2184

    贪婪大陆 思路: 树状数组: 跪烂.. 代码: #include <bits/stdc++.h> using namespace std; #define maxn 100005 int ...

  8. HTTP Status 500 - Handler processing failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11GraphicsEnvironment

    解决方案:修改catalina.sh 文件加上-Djava.awt.headless=true JAVA_OPTS="$JAVA_OPTS $JSSE_OPTS -Djava.awt.hea ...

  9. CF 某套题 O :Grid (简单BFS)

    题意: 从左上角跳到右下角最少需要多少步,跳的规则为:可以向四个方向的任意一个方向跳当前格子中的步数,若跳不到右下角输出IMPOSSIBLE. 题解: BFS搜索,注意判断边界,标记. 代码: #in ...

  10. oracle常用函数整理

    oracle常用函数整理    1.绝对值.取余.判断数值正负函数    绝对值:ABS(n)        示例: SELECT ABS(100),ABS(-100),ABS('100') FROM ...