查看: 7199|回复: 4

[入门教程] 趣味编程指南(8-1)-自定义函数与分形递归

[复制链接]
本帖最后由 kaka 于 2017-8-18 08:26 编辑

1.jpg
自定义函数与分形递归
2.jpg
这节将会介绍如何在程序中自定义函数,同时用递归函数创作分形图形。
自定义函数 编程中的一个重要概念就是复用。而定义函数,最能体现复用思想。不夸张地说,只有灵活地掌握它,才算真正走进编程的大门。因为你会开始去思考,如何对一些序列化操作进行抽象,以此提高编程效率。
像 P5 中自带的绘图函数 ellipse,rect 其实就是经过设计者的抽象后才产生的,它们的底层由一些更“原始”的代码组成,我们使用的时候无需知道里面有什么,只要清楚函数名以及它对应的功能即可。一旦我们掌握了定义函数的方法,也就能用类似的方式对代码块进行打包,自行定制组件。
[AppleScript] 纯文本查看 复制代码
[/size]
[size=4]基本语法:

        返回值类型 函数名(){
                语句;
        }

“返回值类型”代表的是函数返回值的类型,后面会展开。
而“函数名”代表的就是你给函数取的名称。以后我们可以直接通过名称,来调用这个函数。与变量名的命名规则类似,函数名只能使用字母,数字或是下划线,并且第一个字母必须是字母或是下划线。
两个大括号之间,需要写我们希望函数执行的语句,这个部分也被称为“函数体”
先来看一个最基础的例子
代码示例(8-1):
[AppleScript] 纯文本查看 复制代码
void setup(){
          myFunction();
        }

        void draw(){
          
        }

        void myFunction(){
          println(1);
          println(2);
          println(3);
          println(4);
        }

输出结果:
3.png
在 P5 中定义函数,一般写在 draw 函数之后
这里的“ void ”,代表的就是函数没有返回值,意思是此函数只会执行函数体中的代码,而不会向外返回数据。后面的 “ myFunction ” 代表函数的名称。
函数体中写上你希望执行的代码
调用函数时,只要写上函数名并且后接一个中括号即可。在示例的 setup 函数中,就调用了一次 myFunction 函数。因此它会执行我们定义好的函数内容,依次在控制台上输出数字 1,2,3,4。
上面的例子只是在控制台输出字符数据。接下来可以放在图形的语境中去理解自定义函数。
代码示例(8-2):
[AppleScript] 纯文本查看 复制代码
void setup() {
          size(400,400);
        }

        void draw() {
          background(255);
          myShape();
        }

        void myShape() {
          noStroke();
          fill(0, 0, 255, 70);
          ellipse(width/2, height/2, 100, 100);
          fill(0, 255, 0, 70);
          rect(width/2, height/2, 50, 50);
          rect(width/2 - 50, height/2 - 50, 50, 50);
        }

4.png
上例中的 myShape 函数就是绘制一个复合图形。其中包含一个圆和两个矩形。 myShape 每调用一次,这组元素就会绘制一次。
可以试着在 draw 函数里写上两个 myShape 函数。由于原始图形的色彩是带透明度的,所以两组图形重叠到一起就会变深。
5.png
传入参数 自定义函数很方便。但会发现上面的方式有些局限,无法修改函数中的参数。导致图形元素的位置都是固定的。我们可以学习另外一种写法,往函数中传入东西。
基本语法:
[AppleScript] 纯文本查看 复制代码
返回值类型 函数名(参数类型 参数名){
                语句;
        }

只要在函数名后的中括号中写上参数类型与参数名即可。假如我们希望往 myFunction 函数中传入一个浮点变量。就可以这么写
[AppleScript] 纯文本查看 复制代码
void myFuction(float x){
                语句;
        }

这时我们在函数体内,就可以调用这个参数 x 了。参数的变量名可以任意取,只要遵循变量名的命名规范。当希望传入更多的参数,只要在后面加上逗号,同时写上参数类型,参数名即可。
如:
[AppleScript] 纯文本查看 复制代码
void myFuction(float x,float y){
                语句;
        }

当掌握了这种写法,我们就可以通过传入参数改变图形元件的位置了。
代码示例(8-3):
[AppleScript] 纯文本查看 复制代码
void setup(){
  size(400, 400);
}

void draw(){
  background(255);
  myShape(width/3, height/2);
  myShape(width/3 * 2, height/2);
  myShape(mouseX, mouseY);
  
}

void myShape(float x, float y){
  fill(0, 0, 255, 70);
  ellipse(x, y, 50, 50);
  fill(0, 255, 0, 70);
  rect(x, y, 50, 50);
  fill(0, 255, 0, 70);
  rect(x - 50, y - 50, 50, 50);
}

6.gif
传入多种类型 传入的参数可以不仅仅是 float。像 boolean,int,string 这些数据类型都可以传入。
代码示例(8-4):
[AppleScript] 纯文本查看 复制代码
void setup(){
  size(400, 400);
}

void draw(){
  background(255);
  fill(255);
  myShape(true, mouseX, mouseY);
  myShape(false, width - mouseX, height - mouseY);
}

void myShape(boolean type, float  x, float y){
  fill(0);
  ellipse(x, y, 50, 50);
  
  if(type){
    fill(0, 0, 255);
  }else{
    fill(255, 0, 0);
  }
  
  rect(x, y, 50, 50);
  rect(x - 50, y - 50, 50, 50);
}

7.gif
这个例子就是利用 boolean 值的不同,结合 if 语句来决定图形的颜色。下节我们会学到一种新的数据类型, color。它能存储色彩的数值,若是使用此类型作为传入参数,控制起来就会更灵活。
传出参数 有输入,就有输出。有些时候我们希望函数会返回一些结果。这时我们就可以考虑在函数体中使用 return 关键字。假如我们想创建一些数学公式,例如计算圆的面积,在程序中就可以这么写
代码示例(8-5):
[AppleScript] 纯文本查看 复制代码
void setup(){
          println(circleArea(2));
        }

        void draw(){
          
        }

        float circleArea(float r){
          float area = PI * r * r;
          return area;
        }

输出结果:
8.png
在函数中创建的 area 相当于是一个局部变量,可用来储存计算后的面积。最后通过将它写在 return 关键字后,就能在调用函数时输出 area 中储存的数据
因为输出的数据类型为 float ,所以在定义函数的开头,返回值的类型就必须写 float,以此取代没有返回值时的 void。
函数重载 P5 中的函数是可以允许重载的,重载的意思是。你可以定义多个不同参数的函数,并且使用同一个函数名。在调用的时候,程序会先判断参数的个数,然后再决定相应的函数。
是否记得前面提到的 fill  函数?它允许输入的参数个数可以为 1,3,4。根据参数的不同,函数的作用便会有所区别。
下面的例子会设计一个名为 calculate 的函数,当参数个数为 2,便会将两者相乘(相当于求面积)。当参数个数为 3,则将三者相乘。
代码示例(8-6):
[AppleScript] 纯文本查看 复制代码
void setup() {
          println(calculate(2,3));
          println(calculate(2,3,4));
        }

        void draw() {

        }

        float calculate(float a, float b) {
          float result = a * b;
          return result;
        }

        float calculate(float a, float b, float c) {
          float result = a * b * c;
          return result;
        }

输出结果为:6 , 24
递归与分形 递归 当你掌握了自定义函数的方法。就可以写递归函数了。而利用递归函数,你可以很方便地绘制出分形图形。
递归一词听上去很高深,其实就是指函数调用自身。我们以前肯定有听说过这样一个经典的故事,先用它来举例:
从前有座山,山里有个庙,庙里有个老和尚,给小和尚讲故事。故事讲的是:从前有座山,山里有个庙,庙里有个老和尚,给小和尚讲故事。故事讲的是:从前有座山,山里有个庙……
如果继续讲下去,这个故事永远不会终止。它就像递归的结构。只是在程序中,不能让这个流程永远地执行下去,我们需要设置一个条件,来决定是继续还是跳出。否则就会陷入死循环。
下面是一个基本的递归示例:
代码示例(8-7):
[AppleScript] 纯文本查看 复制代码
void setup() {
          size(400, 400);
        }

        void draw() {
          background(255);
          translate(width/2, height/2);
          recursion(350);
        }

        void recursion(float l) {
          if (l > 30) {
            l = l * 0.92;
            noFill();
            stroke(0);
            rectMode(CENTER);
            rect(0, 0, l, l);
            recursion(l);
          }
        }

9.png
可以看到,从语法结构上,和一般的自定义函数没有多大区别。唯一的不同,就是 recursion 函数中还调用了一次 recursion,同时里面有一个 if 语句作为包裹。
其中的 l ,作为输入参数,它是矩形的边长,同时也是递归函数是否执行的条件。只有当矩形的长度 l 大于 30 时,递归才会进行下去。反过来看,当 l <= 30 时,就是递归的终止条件了。所以你会看到图形的中心是空白的。
仅仅有判断条件是不行的,还需要考虑它能否出现不满足的情况。if 条件语句的第一行代码 l = l * 0.92; 就会在每执行一次函数时,都缩小 l 的数值。接着才把它作为输入参数,传递到下一个 recursion 函数中。如此反复,l 迟早会达到终止条件。
假如我们将乘的 0.92,修改成一个大于 1 的数值,程序是会发生崩溃的。因为 l 值始终在增大,就达不到小于 30 的条件了。但不代表此值小于 1 就完全没有问题了,当你写成 0.999999,程序也还是会崩溃。因为变化越微小,它执行的次数就越多,程序在一帧当中需要绘制的矩形也越多。当这个数量超出了计算机的处理能力,便会崩溃。
递归结构有点像电影《盗梦空间》中的梦中梦。梦境之间是互相嵌套。需要有一个条件来“唤醒”,否则人就永远陷进去了。
分形 现在你可以通过递归函数探索另一个魅力无穷的世界 - 分形。

下次谈分形
10.jpg

转自www.inslab.cn

iooops  版主

发表于 2017-4-9 21:04:55

楼主你的动图是怎么做的 = =
回复 支持 反对

使用道具 举报

iooops  版主

发表于 2017-4-9 21:05:03

我一直很好奇这个……
回复 支持 反对

使用道具 举报

kaka  版主
 楼主|

发表于 2017-4-14 18:00:00

iooops 发表于 2017-4-9 21:05
我一直很好奇这个……

http://mc.dfrobot.com.cn/forum.p ... amp;page=1#pid89676
这里我写了关于动图制作过程
回复 支持 反对

使用道具 举报

iooops  版主

发表于 2017-4-26 09:41:42

kaka 发表于 2017-4-14 18:00
http://mc.dfrobot.com.cn/forum.php?mod=viewthread&tid=24194&page=1#pid89676
这里我写了 ...

好耶~好棒棒!~
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
wifi气象站

硬件清单

btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2019 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail