《高效编程十八式》(1/13)复数运算:类与函数

来源:互联网 发布:淘宝pos机突然不能买了 编辑:程序博客网 时间:2024/06/05 04:57
 

复数运算:类与函数

王伟冰

有时候我们会在程序中用到复数运算,我们一般会先定义一个复数类:

class complex{

public:

    double x,y;

};

假如我们需要把两个复数相加,比如说有三个complex型变量abc,要把ab相加的和赋给c。我们可以这样做:

c.x=a.x+b.x;

c.y=a.y+b.y;

假如我们每次做复数加法都要写这么两行,那显然太麻烦,不妨写一个函数:

void add(const complex &a,const complex &b, complex &c){

c.x=a.x+b.x;

c.y=a.y+b.y;

    }

这样每次做加法只要写add(a,b,c)就可以了,所以,经常用到的代码段写成一个函数,可以简化代码。(简洁原则1(请注意红色的定语,如果不是经常用到,就没必要写成函数,写成函数反而会制造麻烦。后面还会有这种红色的定语,就不一一解释了。)

const修饰的函数参数表示这个函数不会修改此参数的值。

也可以把add写成complex类的一个成员函数:

class complex{

public:

    double x,y;

void add(const complex &a,const complex &b){

x=a.x+b.x;

y=a.y+b.y;

    }

};

这样就可以用c.add(a,b)来做加法了,对比add函数的两种实现,你可以发现,后一种更简洁一些,因为不用再传complex &c参数,而且c.xc.y也可以直接写成xy。所以把函数定义为与之密切相关的类的成员函数,可以简化函数实现的代码。(简洁原则2

 

无论是add(a,b,c)还是c.add(a,b),都已经够简洁了,但是从清晰原则的角度来看,它们还不合格,因为你无法一眼看出是哪两个加起来等于第三个。如果是c=add(a,b),是不是比较直观呢?

complex add(const complex &a,const complex &b){

    complex c;

c.x=a.x+b.x;

c.y=a.y+b.y;

return c;

    }

甚至可以用运算符重载:

complex operator +(const complex &a,const complex &b){

   ……//同上

这样就可以用c=a+b来表示复数加法了,这是最简洁也最清晰的一种形式。所以,一个函数的函数名、参数个数和类型、返回值类型应该能够自然地体现出这个函数所要实现的操作。(清晰原则1

 

上面的方法似乎已经非常完美了,但是其实还有问题:性能问题。在add函数里,我们定义了一个局部变量c,然后把c作为返回值,实际上返回的是c的一个副本,这种复制导致了不必要的开销。尽管复制一个复数所需的时间微不足道,但是推而广之,假如是做矩阵加法呢?矩阵C=add(A,B),如果C100*100的矩阵,那么就要把C10000元素都进行复制,这时间可就不能忽视了。其实,我们在传入参数ab时,用的是引用传参(complex &a,complex &b),而不是按值传参(complex a,complex b),就是为了避免在传参数过程中多余的复制运算。所以要尽量减少对变量的复制。(快速原则1

如何解决返回时多余的复制操作?这是C++独有的一个缺陷,JavaC#并不存在这个问题。在JavaC#里,返回的只是引用,并不需要复制。C++也可以返回引用啊,比如这样:

complex& add(const complex &a,const complex &b){

   ……//同上

但是,这样做并不能如愿,因为返回的是对局部变量c的引用,局部变量在函数返回的时候就会自动销毁,也就是说,返回的是对一个不复存在的变量的引用!这样带来的后果,是不可预测的。所以,要保证每个指针或引用不指向实际并不存在的变量。(安全原则1(引用的实质就是指针)

局部变量是在栈中创建的,所以函数一返回就自动销毁,那可不可以在堆中创建变量c?比如这样:

complex& add(const complex &a,const complex &b){

    complex* pc=new complex();//在堆中创建变量c

pc->x=a.x+b.x;

pc->y=a.y+b.y;

return *pc;

    }

实际上,JavaC#就是这样做的。但是C++这样做会有一个严重问题:你在堆中创建了变量,什么时候回收?如果不回收,则会浪费内存(或者叫内存泄漏),如果要回收,则每次用完这个返回值之后都要自己手动回收,比如说:

complex& c=add(a,b);

……//c进行其它运算

delete &c;//手动回收

这简直就是麻烦。JavaC#以及其它很多高级语言都有自动垃圾回收机制,所以不需要手动回收,但是C++却只能这样。总之,在堆中创建的变量,要保证能回收。(安全原则2

 

现有C++很难解决这个问题,不过,即将发布的C++新标准,加入了“右值引用”的新特性,可以完美地解决这个问题。网上有很多资料,具体我就不介绍了。

在现有的情况下,只能根据实际需要进行折衷设计,一般牺牲快速原则用c=a+b 

 

也许你会觉得我无聊,不就是一个复数加法吗,几行代码就完事,用得着讨论这么多吗?其实我只是想借用这个最简单例子来阐述一些普适性的原则。当你运用这些原则解决了复数加法的问题之后,你会发现同样的方法可以用来实现复数减法、乘法、除法,甚至矩阵的加减乘除,集合的交、并、差,……很多很多。

原创粉丝点击