《回调的原理、实现与应用》一文中已经初步讲了回调的原理和应用,并就一个实际需求用函数的方式实现了回调应用。但回调并不仅限于回调函数,可以有很多不同的现实方式。回调与其说是一种技术,不如说是一种编程思想,我们在各种计算机语言中都能看到它的踪影。我们以《回调的原理、实现与应用》一文中提到的需求为例,看看C++、JavaScript、Java等各种语言下回调的实现方式。
有一Person类,有年龄(age),体重(weight),身高(height)三个属性。现要对Person的一组对象进行排序,但并没有确定根据什么规则来排序,有时需要根据年龄进行排序,有时需要根据身高进行排序,有时可能是根据身高和体重的综合情况来排序,还有可能……
JavaScript的实现
- <script type="text/javascript">
- //定义一个Person类
- function Person(_age, _weight, _height)
- {
- this.age = _age;
- this.weight = _weight;
- this.height = _height;
- }
- //定义Person类的一个方法
- Person.prototype.showInfo = function() {
- document.write("age: " + this.age + " weight: " + this.weight + " heigt:" + this.height);
- };
- //persons为Array数组
- function sortPerson(persons, funcCompare)
- {
- var flag = false;
- //判断persons是否为数组,判断funcCompare是否为回调 函数
- flag = (persons instanceof Array) && (funcCompare instanceof Function);
- if(flag)
- {
- var n = persons.length;
- for(var i = 0; i < n-1; i ++)
- {
- for(var j = 0; j < n-i-1; j ++)
- {
- if(funcCompare(persons[j], persons[j+1]) > 0)
- {
- var temp = persons[j];
- persons[j] = persons[j+1];
- persons[j+1] = temp;
- }
- }
- }
- flag = true;
- }
- return flag;
- }
- //根据年龄比较两个Person对象
- function compareByAge(person1, person2)
- {
- return person1.age - person2.age;
- }
- //根据身高比较两个Person对象
- function compareByHeight(person1, person2)
- {
- return person1.height - person2.height;
- }
- //在页面上显示Person数组的信息
- function showPersonArray(persons)
- {
- var size = persons.length;
- for(i = 0; i < size; i ++)
- {
- persons[i].showInfo();
- document.write("<br>");
- }
- }
- </script>
调用:
- <script type="text/javascript">
- var persons = new Array();
- persons[0] = new Person(2, 54.5, 0.82);
- persons[1] = new Person(31, 74.5, 1.80);
- persons[2] = new Person(54, 44.5, 1.59);
- persons[3] = new Person(23, 62.0, 1.78);
- persons[4] = new Person(16, 45.7, 1.60);
- document.writeln("before sort:<br>");
- showPersonArray(persons);
- sortPerson(persons, compareByAge);
- document.writeln("finished sort by age:<br>");
- showPersonArray(persons);
- sortPerson(persons, compareByHeight);
- document.writeln("finished sort by Height:<br>");
- showPersonArray(persons);
- </script>
当然,你也可以在调用sortPerson的时候定义一个匿名的回调函数。一种更优雅的书写方式如下:
- //根据年龄排序
- sortPerson(persons, function(person1, person2){
- return person1.age - person2.age;
- });
- //根据身高排序
- sortPerson(persons, function(person1, person2){
- return person1.height - person2.height;
- });
JavaScript中Array的sort方法应用的就是这个原理:
arrayObject.sort(sortby)
参数 | 描述 |
sortby | 可选。规定排序顺序。必须是函数。 |
想更多地了解JavaScript中回调的实现和技术要点,请看下一章《理解和使用 JavaScript 中的回调函数》
Java的实现
Java中没有像C++这样的函数指针,所以不能将一个函数通过参数传递给另一个函数。因此Java中没有回调函数,但仍然可以有回调的机制。Java是一种纯面向对象的语言,它的回调可以通过面向对象的特性实现。
依然采用Person的例子,用Java实现回调。如下:
ICompare接口:
- public interface ICompare {
- public int comparable(ICompare obj);
- }
Person类:
- public class Person implements ICompare {
- private int age;
- private float height;
- private float weight;
- public Person(int age, float height, float weight) {
- super();
- this.age = age;
- this.height = height;
- this.weight = weight;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- public float getHeight() {
- return height;
- }
- public void setHeight(float height) {
- this.height = height;
- }
- public float getWeight() {
- return weight;
- }
- public void setWeight(float weight) {
- this.weight = weight;
- }
- @Override
- public int comparable(ICompare obj) {
- Person p = (Person)obj;
- return this.age - p.age;
- }
- }
测试:
- public class CallBackTest {
- public static void Sort(ICompare[] objs) {
- int n = objs.length;
- for(int i = 0; i < n-1; i ++) {
- for(int j = 0; j < n-i-1; j ++) {
- if(objs[j].comparable(objs[j + 1]) > 0)
- {
- ICompare temp = objs[j];
- objs[j] = objs[j + 1];
- objs[j + 1] = temp;
- }
- }
- }
- }
- public static void showArray(Person persons[]){
- for(int i = 0; i < persons.length; i ++) {
- System.out.println("age:" + persons[i].getAge() + " weight:" + persons[i].getWeight() + " height:" + persons[i].getWeight());
- }
- }
- /**
- * @param args
- */
- public static void main(String[] args) {
- Person[] persons = new Person[]{
- new Person(2, 54.5f, 0.82f),
- new Person(31, 74.5f, 1.80f),
- new Person(54, 44.5f, 1.59f),
- new Person(23, 62.0f, 1.78f),
- new Person(16, 45.7f, 1.60f)
- };
- System.out.println("before sort:");
- showArray(persons);
- Sort(persons);
- System.out.println("after sort:");
- showArray(persons);
- }
- }
java.util.Arrays中的Sort就用到了这种技术,从Java的API中可以看出:
public static void sort(Object[] a)
根据元素的自然顺序,对指定对象数组按升序进行排序。数组中的所有元素都必须实现 Comparable 接口。此外,数组中的所有元素都必须是可相互比较的(也就是说,对于数组中的任何 e1 和 e2 元素而言,e1.compareTo(e2) 不得抛出 ClassCastException)。
回调之编程思想
在软件模块调用中,主要有三种机制:同步调用,异步调用和回调。
同步调用是调用方(A)call被调用方(B)的接口,并等待B处理完给出一个回执后A才继续执行,这期间是“阻塞”的。
异步调用是调用方(A)call被调用方(B)的接口后,不等待B执行完,且B在被调用后会直接回执A。
回调是调用方(A)call被调用方(B)时,B也回调(callback)被调用方,双方互调接口,只不过call动作是由A发起的。
同步调用是最简单的,异步调用一般是在回调的基础上实现的。所以了解“回调”的机制是至关重要的。
以“八仙过海”彰显回调之精髓
我们再来看一个例子,用程序模拟“八仙过海,各显神通”。
图片来源:昵图网
- package callback;
- /**
- * 接口 魔法(神通)
- * @author luoweifu
- *
- */
- interface Magic {
- public void doMagic();
- }
- /**
- * 神仙类
- * @author luoweifu
- *
- */
- class Immortal {
- private String name;
- private Magic magic;
- public Immortal(String name) {
- super();
- this.name = name;
- }
- public Immortal(String name, Magic magic) {
- super();
- this.name = name;
- this.magic = magic;
- }
- public void SetMagic(Magic magic) {
- this.magic = magic;
- }
- public String getName() {
- return name;
- }
- public void crossingSea(Magic magic) {
- this.magic = magic;
- System.out.print(name);
- magic.doMagic();
- System.out.println("过海...");
- }
- }
- public class crossingSea {
- /**
- * 八仙过海,各显神通
- * @param args
- */
- public static void main(String[] args) {
- Immortal tieGuaiLi = new Immortal("铁拐李");
- tieGuaiLi.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("将葫芦变成条葫芦大船");
- }
- });
- Immortal zhangGuoLao = new Immortal("张果老");
- zhangGuoLao.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("将毛驴变成一只小黑驴");
- }
- });
- Immortal heXianGu = new Immortal("何仙姑");
- heXianGu.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("将荷叶往海面上一铺,躬身往上一跃,漂浮而去");
- }
- });
- Immortal hanXiangzi = new Immortal("韩湘子");
- hanXiangzi.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("将玉箫往水中一掷,化作一根浮木,站在上面");
- }
- });
- Immortal liDongBin = new Immortal("吕洞宾");
- liDongBin.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("把黄龙宝剑投入海中,宝剑化作一只小船载着他");
- }
- });
- Immortal lanCaiHe = new Immortal("蓝采和");
- lanCaiHe.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("将百花篮变成五彩船悠哉游哉坐上彩船");
- }
- });
- Immortal hanZhongLi = new Immortal("汉钟离");
- hanZhongLi.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("有一把芭蕉扇,他来了一个鲤鱼大翻 身,躺在扇子上悠闲自得地随波飘荡");
- }
- });
- Immortal caoGuoJiu = new Immortal("曹国舅");
- caoGuoJiu.crossingSea(new Magic() {
- public void doMagic() {
- System.out.print("把阴阳玉板在海面上一字儿排开,犹如脚踏竹排迎激流穿巨浪");
- }
- });
- }
- }
结果如下:
铁拐李将葫芦变成条葫芦大船过海...
张果老将毛驴变成一只小黑驴过海...
何仙姑将荷叶往海面上一铺,躬身往上一跃,漂浮而去过海...
韩湘子将玉箫往水中一掷,化作一根浮木,站在上面过海...
吕洞宾把黄龙宝剑投入海中,宝剑化作一只小船载着他过海...
蓝采和将百花篮变成五彩船悠哉游哉坐上彩船过海...
汉钟离有一把芭蕉扇,他来了一个鲤鱼大翻 身,躺在扇子上悠闲自得地随波飘荡过海...
在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于像C++这样的兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。
上面这个例子中Immortal是一个接口,doMagic就是你要实现的回调方法,在调用crossingSea方法时实现的匿名的Magic类是称为回调类,Immortal中的private Magic magic;称为回调对象。传递了这个接口的对象也就传递了这个回调函数的实现,也就是实现了回调。
是不是很有趣?对,就是这么一个有有趣的例子,却淋漓尽致地表现了Java中回调的精髓,你可以反复地体会。上面这个例子在设计模式中也叫观察者模式,大家在别的地方看它的用法就不要再觉得陌生了哦!
恰当地使用回调,有以下的作用:
避免重复代码
在你需要更多的通用功能的地方更好地实现抽象(可处理各种类型的对象和变量)。
增强代码的可维护性
增强代码的可读性
有更多定制的功能
什么是迭代器?
迭代器(Iterator)是按照一定的顺序对一个或多个容器中的元素从前往遍历的一种机制,比如for循环就是一种最简单的迭代器,对一个数组的遍历也是一种的迭代遍历的过程。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。迭代器有时也称为枚举器(Enumerator),其结构图如下:
迭代器结构图
迭代器其实就是维护一个当前的指针,这个指针可以指向当前的元素,可以返回当前所指向的元素,可以移到下一个元素的位置,通过这个指针可以遍历容器的所有元素。迭代器一般至少会有以下几种方法:
First(); //将指针移至第一个位置或获得第一个元素
GetCurrent(); //获得当前所指向的元素
MoveNext(); //移至下一个元素
如何使用迭代器
既然迭代器是封装里面的实现细节,对外提供方便访问容器元素的接口,那我们就先从使用的角度认识迭代器,看看在各种语言下迭代器是如何使用的。
C++中的迭代器:
- void TestIterator()
- {
- vector<int> vec; // 定义一容器
- for(int i = 0; i < 5; i++)
- {
- vec.push_back(i*2); //添加元素
- }
- //用迭代器访问容器中的每个元素
- cout << "iterator vector:" << endl;
- for(vector<int>::iterator itr = vec.begin(); itr != vec.end(); itr ++)
- {
- cout << *itr << " "; //itr是一个指针,指向当前的元素, 所以要解引用获得元素值
- }
- cout << endl;
- map<int, string> student; //创建一个map,对应学号-姓名的键值对
- //添加元素
- student.insert(pair<int, string>(1, "张三"));
- student.insert(pair<int, string>(3, "王五"));
- student.insert(pair<int, string>(2, "李四"));
- //遍历容器中的元素
- cout << "iterator map:" << endl;
- for (map<int, string>::iterator itr = student.begin(); itr != student.end(); itr ++)
- {
- cout << itr->first << "-->" << itr->second << endl;
- }
- }
结果:
iterator vector:
0 2 4 6 8
iterator map:
1-->张三
2-->李四
3-->王五
c++中的容器(如vector、map、list、set等)一般会提供四个迭代器:
iterator:正向迭代,从前往后遍历,可修改元素的值
const_iterator:正向常量迭代,但不能修改元素的值,因为指向的是const的引用
reverse_iterator:反向迭代,从后往前遍历,可修改元素的值
const_reverse_iterator:反向常量迭代,但不能修改元素的值,因为指向的是const的引用
每一种迭代器都提供一对首尾位置的标志begin和end,其关系如下:
迭代器类型 | 开始位置标志 | 末尾位置标志 | 说明 |
iterator | begin() | end() | 正向迭代 |
const_iterator | cbegin() | cend() | 正向常量迭代 |
reverse_iterator | rbegin() | rend() | 反向迭代 |
const_reverse_iterator | crbegin() | crend() | 反向常量迭代 |
对应的示意图如下:
图1:正常的迭代
图2:常量值的迭代
Java中的迭代器:
- public static void testIterator() {
- //创建一个列表
- List<Integer> list = new ArrayList<Integer>();
- list.add(4); //添加元素
- list.add(3);
- list.add(7);
- //返回一个迭代器,并遍历列表中的元素
- Iterator<Integer> iterator = list.iterator();
- while (iterator.hasNext()) {
- Integer value = iterator.next();
- System.out.print(value + " ");
- }
- System.out.println();
- //返回ListIterator迭代器并从后往前遍历列表的元素
- ListIterator<Integer> listIterator = list.listIterator(list.size());
- System.out.println("ListIterator:");
- while (listIterator.hasPrevious()) {
- Integer value = listIterator.previous();
- System.out.print(value + " ");
- }
- System.out.println();
- }
结果:
Iterator begin to end:
4 3 7
ListIterator end to begin:
7 3 4
Java中的List接口及其实现类可以通过iterator()返回Iterator,或通过listIterator()和listIterator(int index) 返回ListIterator。
Iterator和ListIterator都是迭代器,ListIterator继承自Iterator。Iterator只能对列表进行遍历,且只能从前往后遍历,ListIterator可以修改列表,且可以选择往前或往后遍历。关于Iterator和ListIterator更详细的说明请参见官方API:Iterator和ListIterator
JavaScript中的迭代器
JavaScript中表示容器的是Array类型,标准并没有给出对应的迭代器的说明和实现,但我们可以自己实现一个简单的迭代器的功能:
- <script type="text/javascript">
- //创建一个迭代器,传入的必须是Array类型的数据
- function makeIterator(array) {
- var index = 0;
- return {
- hasNext: function () {
- return index < array.length;
- },
- next: function () {
- return this.hasNext ? array[index++] : null;
- },
- current: function () {
- return array[index];
- }
- };
- }
- //创建一个数组并赋值
- var mycars=new Array();
- mycars[0]="Saab";
- mycars[1]="Volvo";
- mycars[2]="BMW";
- //将数组mycars生成一个迭代器,并通过迭代器遍历数据元素
- var iterator = makeIterator(mycars);
- while (iterator.hasNext()) {
- document.write(iterator.next() + '<br>');
- }
- </script>
结果:
Saab
Volvo
BMW
mozilla提供的JavaScript 1.7已经添加了迭代器的功能,但现在只能在Firefox浏览器上才有用。如:
- <script type="application/javascript;version=1.7">
- var lang = { name: 'JavaScript', birthYear: 1995, age: 19 };
- //生成一个迭代器
- var itr = Iterator(lang);
- document.write('key-value:' + '<br>');
- for(var key in itr){
- document.write(key + '<br>');
- }
- //这个迭代器遍历每一个key值
- var itr2 = Iterator(lang, false);
- document.write('key:' + '<br>');
- for(var key in itr2){
- document.write(key + '<br>');
- }
- //这个迭代器遍历每一个索引和值
- var arrs = ['JavaScript', 'Python', 'Haskell'];
- var itr3 = Iterator(arrs, false);
- document.write('index-value:' + '<br>');
- for(let [i, value] in itr3){
- document.write(i + ':' + value + '<br>');
- }
- </script>
结果:
key-value:
name,JavaScript
birthYear,1995
age,19
key:
name,JavaScript
birthYear,1995
age,19
index-value:
0:JavaScript
1:Python
2:Haskell
更多关于JavaScript 1.7的迭代器请参见:Iterators and Generators
迭代器的高级应用
在上面一小节“JavaScript中的迭代器”中,已经对Array数组实现了自己定义的迭代器。以上讲述的迭代器基本都是集合内部的元素具有相同的数据类型,但实际的开发过程中可能会有更复杂的容器结构,假设有如下的需要:
一个公司有多个部门,每个部门有多个人组成,这些人中有开发人员,有测试人员,和与项目相关的其它人员,其结构如下:
现在要遍历这个公司的所有开发人员,遍历这个公司的所有测试人员。
针对这个需求,我们可以创建一个定制化的迭代器来遍历一个公司所有人员,也可以传入员工类型来遍历指定类型的员工,其类的结构图如下:
对应的实现代码如下:
git@code.csdn.net:luoweifu/iteratortest.git
对应的调用代码如下:
- #include "stdafx.h"
- #include <string>
- #include <iostream>
- #include "Person.h"
- #include "Department.h"
- #include "Company.h"
- #include "Enumerator.h"
- int _tmain(int argc, _TCHAR* argv[])
- {
- Company company("Apabi");
- Department* pDepartMent1 = new Department("开发1部");
- Department* pDepartMent2 = new Department("开发2部");
- Department* pDepartMent3 = new Department("内核研发部");
- company.AddDepartment(pDepartMent1);
- company.AddDepartment(pDepartMent2);
- company.AddDepartment(pDepartMent3);
- int empId = 1;
- Person* pPerson11 = new Developer(empId++, "Developer11", "C++", "智慧城市");
- Person* pPerson12 = new Developer(empId++, "Developer12", "Java", "智慧城市");
- Person* pPerson13 = new Developer(empId++, "Developer13", "Java", "智慧城市");
- Person* pPerson14 = new Developer(empId++, "Developer14", "JavaScript", "智慧城市");
- Person* pPerson15 = new Tester(empId++, "Tester15", "LoadRunner");
- Person* pPerson16 = new Tester(empId++, "Tester16", "黑盒测试");
- cout << pPerson16->GetPersonType() << endl;
- pDepartMent1->AddPerson(pPerson11);
- pDepartMent1->AddPerson(pPerson12);
- pDepartMent1->AddPerson(pPerson13);
- pDepartMent1->AddPerson(pPerson14);
- pDepartMent1->AddPerson(pPerson15);
- pDepartMent1->AddPerson(pPerson16);
- Person* pPerson21 = new Developer(empId++, "Developer21", "IOS", "Mobile");
- Person* pPerson22 = new Developer(empId++, "Developer22", "Android", "Mobile");
- Person* pPerson23 = new Tester(empId++, "Tester23", "LoadRunner");
- Person* pPerson24 = new Tester(empId++, "Tester24", "TestIn");
- pDepartMent2->AddPerson(pPerson21);
- pDepartMent2->AddPerson(pPerson22);
- pDepartMent2->AddPerson(pPerson23);
- pDepartMent2->AddPerson(pPerson24);
- Person* pPerson31 = new Developer(empId++, "Developer31", "C++", "CEBX内核");
- Person* pPerson32 = new Developer(empId++, "Developer32", "C++", "CEBX内核");
- Person* pPerson33 = new Developer(empId++, "Developer33", "C++", "CEBX内核");
- Person* pPerson34 = new Developer(empId++, "Developer34", "C++", "CEBX内核");
- Person* pPerson35 = new Tester(empId++, "Tester35", "LoadRunner");
- pDepartMent3->AddPerson(pPerson31);
- pDepartMent3->AddPerson(pPerson32);
- pDepartMent3->AddPerson(pPerson33);
- pDepartMent3->AddPerson(pPerson34);
- pDepartMent3->AddPerson(pPerson35);
- //遍历所有开发者
- cout << "遍历所有开发者:" << endl;
- Enumerator* pEnumerator1 = company.GetEnumerator(PERSON_DEVELOPER);
- while(pEnumerator1->MoveNext())
- {
- Person* pPerson = pEnumerator1->Current();
- if (pPerson)
- {
- pPerson->showInfo();
- }
- }
- delete pEnumerator1;
- //遍历所有测试人员
- cout << "遍历所有测试人员:" << endl;
- Enumerator* pEnumerator2 = company.GetEnumerator(PERSON_TESTER);
- while(pEnumerator2->MoveNext())
- {
- Person* pPerson = pEnumerator2->Current();
- if (pPerson)
- {
- pPerson->showInfo();
- }
- }
- delete pEnumerator2;
- //遍历公司所有员工
- cout << "遍历公司所有员工:" << endl;
- Enumerator* pEnumerator3 = company.GetEnumerator(PERSON_TYPE_NONE);
- while(pEnumerator3->MoveNext())
- {
- Person* pPerson = pEnumerator3->Current();
- if (pPerson)
- {
- pPerson->showInfo();
- }
- }
- delete pEnumerator3;
- return 0;
- }
这样就使得代码简洁易懂易读。
迭代器的应用场景
1.集合的内部结构复杂,不想暴露对象的内部细节,只提供精简的访问方式;
2.需要提供统一的访问接口,从而对不同的集合使用同一的算法。
我之前写过关于递归算法的博文,但作为编程思想系列的文章不得不再对它进行进一步深入的剖析。因为它是一种简单、常用又重要的一种编程思想。
什么叫递归?
举一个通俗的例子:
有一个8俩重的苹果要你切成重量相等的若干份,每一份的重量不能大于1俩。你肯定会想到这样做:
1.第一刀先把一个苹果切成重量均等的2份A1和A2;
2.再把其中的一份A1切成重量均等的两份A11和A12, 把A2切成均等的两份A21和A22;
3.把A11切成均等的两份……
4.直到每一小份都小于等于1俩为止。
以上的例子就是递归一个模型,把一个大的事物化成若干个小的事物,每一次使用的方法都相同。
更为专业的定义:
程序自身调用自身的编程技巧称为递归( recursion)。递归有直接递归和间接递归
•直接递归:函数在执行过程中调用本身。
•间接递归:函数在执行过程中调用其它函数再经过这些函数调用本身。
递归有四个特性:
1.必须有可最终达到的终止条件,否则程序将陷入无穷循环;
2.子问题在规模上比原问题小,或更接近终止条件;
3.子问题可通过再次递归调用求解或因满足终止条件而直接求解;
4.子问题的解应能组合为整个问题的解。
上面的例子中也满足以上的四点性质:
(1).终止条件是每一份的重量不能大于1俩;(2).每一次切的大小都比上一次小;(3).每一次切的方式都相同,所以子问题可递归调用;(4).最终切成的每一小份也就是要求的解。
对上面例子的实现:
- public static void sliceApple(float weight, int times){
- if (weight <= 1) {
- //System.out.println("weight:" + weight);
- } else {
- float w = weight / 2;
- System.out.println("第" + times + "次等分的重量为:" + w + " " + w);
- times += 1;
- sliceApple(w, times);
- sliceApple(w, times);
- }
- }
- public static void main(String args[]) {
- sliceApple(8, 1);
- }
结果:
第1次等分的重量为:4.0 4.0
第2次等分的重量为:2.0 2.0
第3次等分的重量为:1.0 1.0
第3次等分的重量为:1.0 1.0
第2次等分的重量为:2.0 2.0
第3次等分的重量为:1.0 1.0
第3次等分的重量为:1.0 1.0
递归能做什么?
将大问题分解成小问题,将复杂的问题简单化;
使程序更易于理解,增强可读性;
你会怎样使用递归?
分析问题,看看问题是否属于递归模型,能否用递归模型解决。一个问题能否用递归模型就看它是否满足递归的四个特性。
递归在调用的时候要保存调用点的信息,因此会有调用开销。在对效率有较高要求的时候,如果能用循环解决问题最好不要用递归,因为循环没有调用开销,效率会更高