导语:本系列讲述了编程语言当中,有关于代码复用所设计的一些机制和概念,包括变量、函数、类、库和包的概念和作用,属个人见解,欢迎指正。本文不谈及某个具体的编程语言,旨将其中一些共通的概念进行讲解,以便给于像我这种半路出家的人一种思考的维度。
1.代码块的封装和复用
例1:取三个数的最大值
a=1,b=2,c=3,max=0
#条件判断,三个数互相比较大小
if(a>b and a>c) max=a
if(b>a and b>c) max=b
if(c>a and c>b) max=c
print(max)
上例中,用了三行代码来判断三个数的大小,来获取最大值。这三行代码就是取三个数最大值的一个算法,也就是说任意给三个数,我都可以用这三行代码来取出最大值。比如,下例中,给出d,e,f三个数
d=4,e=5,f=6,max=0
#条件判断,三个数互相比较大小
if(d>e and d>f) max=d
if(e>d and e>f) max=e
if(f>d and f>e) max=f
print(max)
直接复制例1的三行代码,然后把对应的变量进行替换,即可完成任意给定三个数取最大值的算法。
这么操作没毛病,但是,好像哪里有些不妥?下回再遇到取三个数的最大值,还得再复制一遍三行代码,再把对应的变量进行替换。复制代码总不会出错吧,替换变量就说不定了,稍微走神,就可能替换错了,而且替换变量的操作次数的代价也不小。那有什么办法优化呢?
1.1代码封装->定义函数
我们可以把那三行代码进行封装,封装成一个函数。封装的意思,可以理解为,在三行代码外边加一层包装封起来,形成一个代码块:函数。
例2:
function max(x,y,z)
{
max=0
#条件判断,三个数互相比较大小
if(x>y and x>z) max=x
if(y>x and y>z) max=y
if(z>x and z>y) max=z
return max
}
然后再对任意给定的三个数取最大值,可以这么操作
a=1,b=2,c=3,max=0
max=max(a,b,c) =>等价于 1->a->x,2->b->y,3->c->z
复制{if(x>y and x>z)...}
print(max)
用max函数把三行代码进行了封装,哪个地方需要那三行代码,就直接调用max函数,相当于把那三行代码通过max函数复制过去,实际的效果跟例1是等价的。max函数替代了那三行代码,可以理解为max函数是个变量,保存的是三行代码,那个地方用到了,就相当于把三行代码复制过去。
然后,通过函数传参的方式,把需要比较的三个数,传递给函数,而不用再做修改相应变量的操作。数据通过实参传递给形参之后,数据会被赋值到相应的函数内部变量。
实参:可以理解为,需要传入函数的变量和数据,是需要被函数进行处理的真实数据或者真实参数。即上例中,max(a,b,c)–>1,2,3三个数,通过变量a,b,c传入max函数。
形参:可以理解为,函数自己内部使用到的变量,这些变量只在函数内部有效,即作用域(起作用的范围)只限于函数体内部。这些变量,接收传入的实参的数据(1,2,3),然后在函数内部用到这些变量的地方,把数据调用到相应的位置。这些变量在函数定义的时候,本身是不保存任何数据的,相当于给之后要传入的数据占个位置,只有等到函数被调用,真实数据被传入之后,这些变量才代表了那些数据。在函数定义的时候只作为形式上的变量或参数而存在。
实际的数据传递过程如下:
d=4,e=5,f=6,max=0
#条件判断,三个数互相比较大小
if(d>e and d>f) max=d =>if(4>5 and 4>6) max=4
if(e>d and e>f) max=e =>if(5>4 and 5>6) max=5
if(f>d and f>e) max=f =>if(6>4 and 6>5) max=6
max=max(d,e,f) =>max(4,5,6)#通过实参def传入数据
function max(x,x,y) =>x:4,y:5,z:6#数据传给形参xyz
{
max=0 #x,y,z所在的位置,填入相应数据
if(x>y and x>z) max=x =>if(4>5 and 4>6) max=4
if(y>x and y>z) max=y =>if(5>4 and 5>6) max=5
if(z>x and z>y) max=z =>if(6>4 and 6>5) max=6
return max
}
可以看到,两种方式是等价的。以上可得,使用函数的好处有:
- 不用复制代码,直接调用函数即可,两者是等价的
- 有了实参和形参,复制代码之后替换变量的操作也不需要了
需要用到取三个数最大值代码的地方,直接调用函数max,传入数据,返回的输出结果即是三个数的最大值。
可以把max函数当做一个黑盒,里边的代码不需要知道,只需知晓这个黑盒的作用就是:输入三个数,输出最大值。别人调用这个函数的时候,只需要一行代码即可:max=max(参数1,参数2,参数3)。
这个黑盒的作用,就是封装的意义,无须理会函数内部的代码,只需关注输入和输出。函数内部有三行还是三百行代码都可以,我却不需要关心,我只知道输入三个数,它能给我输出一个正确的最大值,而且屡试不爽。
这会带来一个直接的好处:
- 如果需要对取最大值的算法进行优化,扩展到可以对不限定个数的数据进行取最大值,只需要在函数内部进行修改操作,并做兼容处理,在任何调用了函数的地方,都不需要进行修改。即:只修改一次函数内部代码,即可实现:已经调用到函数取三个数最大值的代码,和即将调用函数取任意多个数的最大值的代码,输出结果都是正确的。
封装之后的代码块,构成一个函数,调用函数时只需关心输入和输出;当函数内部代码改变,只要输入和输出的关系不变,这个函数依然有效。
1.2代码复用->调用函数
函数要起到代码复用的作用,首先要进行代码块的封装,然后在需要使用到相同代码的地方,以调用函数的方式进行代码复用。
即:某段代码,需要被重复用到多次,为减少复制代码和替换变量等操作,将其封装成函数,通过调用函数,实现复制代码和替换变量的等价操作。同时,这段代码,只进行了一次编写,就可以通过调用函数来重复使用,也实现了代码复用的作用。
所以,高效的代码复用,需要进行代码封装。
那哪些代码需要进行封装?需要重复使用的相同功能的代码,或者,为了降低代码的复杂度,对某些实现基本功能的代码进行封装,进行模块化操作,定义功能性描述的函数名,增加可读性。
也就是,为了提高编写效率和阅读起来方便,把重复的部分和理解起来比较复杂的部分,进行封装,定义函数,用函数来替代那些代码。一来,代码重复的地方,用调用函数来替代,可以减少代码数量;二来,定义了函数,给函数取一个功能性描述的函数名,使复杂的代码理解起来比较容易,增加代码的可读性;三来,需要对重复代码进行修改时,从修改每一处重复的代码,变成只修改一次代码,提高可维护性。
下面是封装、调用和修改代码的完整过程:
例3:
#未封装前的代码:
a=1,b=2,c=3,max1=0
if(a>b and a>c) max1=a
if(b>a and b>c) max1=b
if(c>a and c>b) max1=c
print(max1)
d=4,e=5,f=6,max2=0
if(d>e and d>f) max2=d
if(e>d and e>f) max2=e
if(f>d and f>e) max2=f
print(max2)
g=7,h=8,i=9,max3=0
if(g>h and g>i) max3=g
if(h>g and g>i) max3=h
if(i>g and i>h) max3=i
print(max3)
提取重复的功能相同的代码进行封装:
重复的共同部分代码形式:
if(a>b and a>c) max1=a
if(b>a and b>c) max1=b
if(c>a and c>b) max1=c
在外头套一层包装,封装成函数:
function max()
{
if(a>b and a>c) max1=a
if(b>a and b>c) max1=b
if(c>a and c>b) max1=c
}
增加函数的形参,替代代码中的变量,并完善函数代码和格式
function max(x,y,z)
{
max=0
#条件判断,三个数互相比较大小
if(x>y and x>z) max=x
if(y>x and y>z) max=y
if(z>x and z>y) max=z
return max
}
以调用函数的形式使用重复的代码,原来代码可以变为:
a=1,b=2,c=3,max1=0
max1=max(a,b,c)
print(max1)
d=4,e=5,f=6,max2=0
max2=max(d,e,f)
print(max2)
g=7,h=8,i=9,max3=0
max3=max(g,h,i)
print(max3)
如果需要对重复的代码进行修改,只需把函数的代码修改即可,max函数变为如下:
function max(x,y,z)
{
max=x
if(x<y)
if(y<z) max=z
max=y
else if (x<z) max=z
return max
}
输入同样的数据,函数的输出不变,函数依然有效,省去了重复修改代码的操作代价。对于函数调用来说,不需要关心函数的内部实现过程,只需要关心输入和输出的关系。
之后调用函数的时候,等价的代码为:
max(a,b,c)
#等价于下边的代码
{
max=a
if(a<b)
if(b<c) max=c
max=b
else if(a<c) max=c
return max
}
调用的地方无须修改,实际的实现代码自动更新为修改后的代码。
总结:
重复的代码块实现相同的功能,为了减少操作上的时间耗费,将其封装成函数,通过函数的调用实现代码的重复使用,可降低代码的复杂度,变得简洁,增加可读性,提高可维护性。代码块复用的关键在于封装,封装哪些代码和如何封装,直接影响代码复用的效果。函数要实现代码复用的前提,是进行代码的封装,封装是为了复用,复用需要先进行封装。
使用编程语言内建的函数,因为函数的定义已经被包含了进来,可以直接使用即可。但是使用第三方函数库时,需要先导入第三方的函数库,也就是把第三方函数的定义进行导入,然后才可以正常调用。以上的过程,即是:封装在前,调用在后,调用函数前,需有函数的定义即代码块的封装。