25、Processing编程:void函数与返回值函数的使用

Processing编程:void函数与返回值函数的使用

1. draw()函数的图形示例

首先,我们尝试使用 draw() 函数进行重复绘图。操作步骤如下:
1. 选择 File > New 开始一个新程序,并将其保存为 Circles
2. 使用 setup() 函数设置画布大小为150像素宽、200像素高,并在画布上随机位置绘制一个直径为30像素的圆,代码如下:

void setup()
{
    size(150, 200);
    ellipse(random(width), random(height), 30, 30);
}

多次运行该程序,每次运行时,圆都会在画布的随机位置绘制。

接下来,我们向程序中添加 draw() 函数:

void setup()
{
    size(150, 200);
    ellipse( random(width), random(height), 30, 30);
}
void draw()
{
}

运行程序时,Processing会自动调用 setup() 函数一次,执行完 setup() 函数中的所有语句后,会自动调用 draw() 函数。当 draw() 函数中的所有语句执行完毕后,Processing会再次调用 draw() 函数,这种重复自动调用会一直持续,直到我们停止程序。

由于当前 draw() 函数没有包含任何语句,我们将 ellipse() 函数的调用从 setup() 函数移到 draw() 函数中:

void setup()
{
    size(150, 200);
}
void draw()
{
    ellipse(random(width), random(height), 30, 30);
}

再次运行程序,Processing会不断在随机位置绘制圆,直到我们停止程序。

当使用 draw() 函数时,我们还可以创建响应鼠标指针在画布上移动的程序。Processing提供了两个 int 类型的内置变量 mouseX mouseY ,每次调用 draw() 函数时,它们会存储鼠标指针尖端在画布上的当前像素位置:
- mouseX :存储鼠标指针尖端当前所在的画布像素列位置。
- mouseY :存储鼠标指针尖端当前所在的画布像素行位置。

我们可以修改程序中 ellipse() 函数的调用,使用这两个变量来指定每次调用 draw() 函数时绘制圆的位置:

void setup()
{
    size(150, 200);
}
void draw()
{
    ellipse(mouseX, mouseY, 30, 30);
}

运行该程序,无论我们将鼠标指针移动到画布的哪个位置,都会在该位置绘制圆。保存 Circles 程序,后续还会用到。

2. 使用draw()函数为气球动画

我们尝试在绘制气球的程序中使用 draw() 函数以及 mouseX mouseY 变量。操作步骤如下:
1. 返回 BalloonDraw 程序,选择 File > Save As ,将程序另存为 BalloonMove
2. 由于Processing的内置变量 mouseX mouseY 都是 int 类型,它们可以作为参数传递给 balloon() 函数。我们在程序中插入 draw() 函数,并将 balloon() 函数的调用放在 draw() 函数的主体中:

void setup()
{
    size(200, 200);
}
void draw()
{
    balloon(mouseX, mouseY, 30);
}
void balloon(int column, int row, int diameter)
{
    int lineLength = 50;
    line(column, row, column, row + lineLength);
    fill(255, 0, 0);
    ellipse(column, row, diameter, diameter);
}

运行该程序,每次执行 draw() 函数时,都会调用 balloon() 函数,因此每次 draw() 函数重复执行时,都会在鼠标指针的当前位置绘制一个气球。

接着,我们在 draw() 函数中插入对 background() 函数的调用:

void draw()
{
    background(200, 200, 200);
    balloon(mouseX, mouseY, 30);
}

每次 draw() 函数重复执行时,会先调用 background() 函数,生成一个空白的灰色画布,然后调用 balloon() 函数,在鼠标指针的当前位置再次绘制一个气球,这样就会产生我们用鼠标在画布上移动单个气球的错觉。

如果对程序进行如下修改:

void setup()
{
    size(200, 200);
    cursor(HAND);
}
void draw()
{
    background(200, 200, 200);
    balloon(mouseX, mouseY, 30);
}
void balloon(int column, int row, int diameter)
{
    int lineLength = 50;
    line(column, row, column, row - lineLength);
    fill(255, 0, 0);
    ellipse(column, row - lineLength, diameter, diameter);
}

看起来就好像一只手在绳子末端拿着气球。

使用重复的 draw() 函数产生的效果有时很难提前预测,因此在Processing中使用 draw() 函数编程通常需要一些尝试和错误,但这种不可预测性也增加了使用 draw() 函数的乐趣。保存并关闭 BalloonMove 程序。

3. Processing中与鼠标和键盘相关的特殊void函数

我们已经了解了Processing的两个特殊 void 函数: setup() draw() 。如果在程序中定义了 setup() 函数,当我们启动程序时,Processing会自动调用该函数一次;如果还定义了 draw() 函数,在 setup() 函数执行完毕后,Processing会自动重复调用 draw() 函数。

如果定义了 draw() 函数,我们还可以选择定义Processing中与鼠标和键盘使用相关的其他特殊 void() 函数。 draw() 函数的重复执行(即使其主体中没有语句)会使Processing“监听”鼠标和键盘事件的发生,例如移动鼠标、按下鼠标按钮或按下键盘上的键。

例如,如果定义了 void mousePressed() 函数,每当我们将鼠标指针放在画布上并按下鼠标按钮时,Processing会调用该函数一次,并执行该函数主体中的任何语句。之后,Processing会恢复对 draw() 函数的重复调用。我们回到 Circles 程序,插入包含对 background() 函数调用的 mousePressed() 函数定义:

void setup()
{
    size(150, 200);
}
void draw()
{
    ellipse(mouseX, mouseY, 30, 30);
}
void mousePressed()
{
    background(200, 200, 200);
}

运行该程序,和之前一样,无论我们将鼠标指针移动到画布的哪个位置,都会在该位置绘制圆。但是,每当我们按下鼠标按钮时,会调用 mousePressed() 函数,执行其中对 background() 函数的调用,将画布上的所有像素重置为 background() 函数调用中指定的颜色。之后, mousePressed() 函数执行完毕,执行流程返回到 draw() 函数,Processing继续重复调用 draw() 函数,再次开始在鼠标指针的当前位置重复绘制圆。

Processing还包含一个类似的 void 函数 keyPressed() 。顾名思义,每当我们在画布窗口被选为活动窗口时按下键盘上的键,Processing会调用 keyPressed() 函数一次,执行该函数主体中的语句,然后执行流程返回到 draw() 函数,Processing继续重复调用 draw() 函数。

例如,我们修改程序以使用 keyPressed() 函数:

void setup()
{
    size(150, 200);
}
void draw()
{
    ellipse(mouseX, mouseY, 30, 30);
}
void keyPressed()
{
    background(200, 200, 200);
}

这样,当我们按下键盘上的键时,会使用 background() 函数将画布清空。保存并关闭 Circles 程序。

4. 创建返回值的函数

在Processing中,我们可以创建自己的返回值函数,这与创建 void 函数有相似之处,但也有一些关键区别。

4.1 非void函数的概念

我们可以用一个类比来理解非 void 函数。假设每年家长都会要求青少年“了解本赛季踢足球需要花费多少钱”,这实际上转化为一系列具体的指令,如“了解所需足球鞋的成本”“了解所需护腿板的成本”等,最后要求青少年汇报总费用。与“准备上学”的指令不同,这个指令要求带回一项信息,即本赛季踢足球的总费用。

在Processing中,非 void 函数类似于这个类比,它执行一系列命名的语句,并带回由指令描述的结果信息。我们在Processing中使用过许多返回值的内置函数,例如 sqrt() 函数,它计算我们作为参数提供的任何值的平方根,并将结果作为 float 值返回给我们。

需要注意的是,当我们调用非 void 函数时,需要立即使用返回的值。例如,如果我们编写语句 sqrt(9.0); ,该函数调用返回的平方根会丢失。一个好的方法是将返回的值赋给一个变量,例如:

float root;
root = sqrt(9.0);
println("Square root: " + root);

这样,函数返回的结果值就存储在变量中,我们可以进一步使用它。

4.2 创建自己的返回值函数

我们以模拟掷骰子的程序为例。操作步骤如下:
1. 选择 File > New 创建一个新程序,将其保存为 RollingFunction
2. 我们需要在Processing的活动模式下工作,以创建返回值的函数。首先定义 setup() 函数:

void setup()
{
    int die;
    die = int( random(1, 7) );
    println(die);
}

运行该程序,我们会在控制台看到一个1到6之间的整数。

如果我们想模拟使用五个骰子的游戏中的掷骰子过程,一种方法是在程序中使用五个类似的语句,但每次模拟掷骰子都编写嵌套的 int() random() 函数调用有些繁琐。我们可以创建一个函数来执行这个模拟。

定义函数时,函数头需要三个信息:
1. 返回值的类型 :在模拟掷骰子的函数中,我们需要返回一个1到6之间的随机整数,所以返回值的类型是 int ,函数头以 int 开头。
2. 函数的名称 :我们使用 roll 作为函数的名称。
3. 函数的参数列表 :该函数目前不需要任何参数,所以我们只写一对空括号。

函数头完成后,我们定义 roll() 函数的主体:

int roll()
{
    int result;
    result = int( random(1, 7) );
    return result;
}

在函数主体中,我们声明一个局部变量 result 来存储1到6之间的随机整数值,并将其作为每次调用 roll() 函数的结果返回。需要注意的是,由于 roll() 函数的返回类型是 int ,我们必须使用 return 语句返回一个 int 类型的值。

我们将 roll() 函数应用到程序中:

void setup()
{
    int die;
    die = roll();
    println(die);
}
int roll()
{
    int result;
    result = int( random(1, 7) );
    return result;
}

运行该程序,我们会在控制台看到一个1到6之间的值。

下面是程序执行流程的mermaid流程图:

graph TD;
    A[开始运行程序] --> B[调用setup()函数];
    B --> C[声明die变量];
    C --> D[调用roll()函数];
    D --> E[进入roll()函数];
    E --> F[声明result变量];
    F --> G[为result变量赋值随机整数];
    G --> H[执行return result语句];
    H --> I[返回setup()函数,将result值赋给die];
    I --> J[调用println(die)输出结果];
5. 练习

以下是一些相关的练习,帮助我们巩固所学知识:
1. 输入以下程序:

void setup()
{
}
void smiley()
{
    println( ":-)" );
}

修改该程序,使 setup() 函数调用 smiley() 函数,运行程序应输出 :-)
2. 修改上一个程序,多次调用 smiley() 函数,输出:

:-)
:-)
:-)
:-)
:-)
  1. 修改上一个程序,使用 for 语句调用 smiley() 函数,产生相同的输出。
  2. 编写一个名为 qwerty() void 函数,显示QWERTY键盘上每行的字母。让 setup() 函数在控制台显示 Keyboard Letters: ,然后调用 qwerty() 函数,运行程序应产生以下控制台输出:
Keyboard Letters:
QWERTYUIOP
ASDFGHJKL
ZXCVBNM
  1. 编写一个名为 days() void 函数,在控制台显示一周的天数。让 setup() 函数在控制台显示 Days of the week: ,然后调用 days() 函数,运行程序应产生以下控制台输出:
Days of the week:
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
  1. 在Processing中, month() 函数返回月份数, day() 函数返回日期, year() 函数返回年份。编写一个名为 today() void 函数,在控制台显示当前的月份、日期和年份。让 setup() 函数在控制台显示 TODAY: ,然后调用 today() 函数,运行程序应产生类似以下的控制台输出:
TODAY:
Month: 5
Day: 28
Year: 2017
  1. 编写一个名为 hashtags() void 函数,在控制台的一行显示一个井号,但之后不换行。让 setup() 函数在控制台显示 hashtags: ,然后调用 hashtags() 函数,运行程序应产生以下控制台输出:
hashtags:
#
  1. 修改上一个程序,使 hashtags() 函数有一个名为 quantity int 参数变量,指定函数应显示的井号数量。在 hashtags() 函数中插入 for 语句来实现这一点。例如,调用 hashtags(5) 应产生以下输出:
hashtags:
#####

调用 hashtags(10) 应产生以下输出:

hashtags:
##########
  1. 编写一个名为 square() void 函数,在200像素×200像素的画布的左上角绘制一个边长为50像素的正方形。从 setup() 函数调用该函数。
  2. 修改上一个程序,使 square() 函数有一个 int 参数变量 side ,用于指定正方形的边长。例如,调用 square(100) 应绘制相应边长的正方形。
  3. 修改上一个程序,使 square() 函数还有一个 int 参数变量 column 和一个 int 参数变量 row ,用于指定正方形左上角的列和行。例如,调用 square(100, 25, 50) 应绘制相应位置和边长的正方形。
  4. 修改上一个程序,在 setup() 函数中插入 for 语句,调用 square() 函数五次,使用20作为边长,并使用随机值作为列和行号。
  5. 编写一个名为 yoyo() void 函数,在200像素×300像素的画布的顶部中心到画布中心绘制一条线,并在画布中心绘制一个直径为50像素的圆。从 setup() 函数调用该函数。
  6. 修改上一个程序,使 yoyo() 函数有一个名为 row int 参数变量,指定悠悠球的垂直位置。例如,调用 yoyo(220) 应将悠悠球放置在相应的垂直位置。
  7. 修改上一个程序:
    • setup() 函数中声明一个名为 randomRow int 变量。
    • randomRow 分配一个从0到画布高度的随机整数。
    • 使用 randomRow 作为调用 yoyo() 函数的参数。运行该程序,每次悠悠球会放置在不同的垂直位置。

Processing编程:void函数与返回值函数的使用

6. 练习解答思路与示例代码
6.1 练习1
void setup()
{
    smiley();
}
void smiley()
{
    println( ":-)" );
}

在这个程序中,我们在 setup() 函数里调用了 smiley() 函数,这样运行程序时就会输出 :-)

6.2 练习2
void setup()
{
    smiley();
    smiley();
    smiley();
    smiley();
    smiley();
}
void smiley()
{
    println( ":-)" );
}

通过多次调用 smiley() 函数,实现了输出多行 :-) 的效果。

6.3 练习3
void setup()
{
    for(int i = 0; i < 5; i++) {
        smiley();
    }
}
void smiley()
{
    println( ":-)" );
}

使用 for 循环来控制 smiley() 函数的调用次数,达到与练习2相同的输出效果。

6.4 练习4
void setup()
{
    println("Keyboard Letters:");
    qwerty();
}
void qwerty()
{
    println("QWERTYUIOP");
    println("ASDFGHJKL");
    println("ZXCVBNM");
}

qwerty() 函数按行输出QWERTY键盘上的字母, setup() 函数负责显示提示信息并调用 qwerty() 函数。

6.5 练习5
void setup()
{
    println("Days of the week:");
    days();
}
void days()
{
    println("Sunday");
    println("Monday");
    println("Tuesday");
    println("Wednesday");
    println("Thursday");
    println("Friday");
    println("Saturday");
}

days() 函数依次输出一周的天数, setup() 函数显示提示信息并调用 days() 函数。

6.6 练习6
void setup()
{
    println("TODAY:");
    today();
}
void today()
{
    println("Month: " + month());
    println("Day: " + day());
    println("Year: " + year());
}

today() 函数利用Processing的 month() day() year() 函数获取当前日期信息并输出, setup() 函数显示提示信息并调用 today() 函数。

6.7 练习7
void setup()
{
    println("hashtags:");
    hashtags();
}
void hashtags()
{
    print("#");
}

hashtags() 函数使用 print() 函数输出井号且不换行, setup() 函数显示提示信息并调用 hashtags() 函数。

6.8 练习8
void setup()
{
    println("hashtags:");
    hashtags(5);
}
void hashtags(int quantity)
{
    for(int i = 0; i < quantity; i++) {
        print("#");
    }
}

hashtags() 函数接收一个整数参数 quantity ,使用 for 循环根据该参数输出相应数量的井号。

6.9 练习9
void setup()
{
    size(200, 200);
    square();
}
void square()
{
    rect(0, 0, 50, 50);
}

square() 函数使用 rect() 函数在画布左上角绘制边长为50像素的正方形, setup() 函数设置画布大小并调用 square() 函数。

6.10 练习10
void setup()
{
    size(200, 200);
    square(100);
}
void square(int side)
{
    rect(0, 0, side, side);
}

square() 函数接收一个整数参数 side ,用于指定正方形的边长, setup() 函数设置画布大小并调用 square() 函数。

6.11 练习11
void setup()
{
    size(200, 200);
    square(100, 25, 50);
}
void square(int side, int column, int row)
{
    rect(column, row, side, side);
}

square() 函数接收三个整数参数,分别指定正方形的边长、左上角的列和行, setup() 函数设置画布大小并调用 square() 函数。

6.12 练习12
void setup()
{
    size(200, 200);
    for(int i = 0; i < 5; i++) {
        int col = (int)random(200);
        int row = (int)random(200);
        square(20, col, row);
    }
}
void square(int side, int column, int row)
{
    rect(column, row, side, side);
}

setup() 函数中使用 for 循环调用 square() 函数五次,每次使用随机的列和行号以及固定的边长20。

6.13 练习13
void setup()
{
    size(200, 300);
    yoyo();
}
void yoyo()
{
    line(100, 0, 100, 150);
    ellipse(100, 150, 50, 50);
}

yoyo() 函数在画布顶部中心到中心绘制一条线,并在中心绘制一个直径为50像素的圆, setup() 函数设置画布大小并调用 yoyo() 函数。

6.14 练习14
void setup()
{
    size(200, 300);
    yoyo(220);
}
void yoyo(int row)
{
    line(100, 0, 100, row);
    ellipse(100, row, 50, 50);
}

yoyo() 函数接收一个整数参数 row ,用于指定悠悠球的垂直位置, setup() 函数设置画布大小并调用 yoyo() 函数。

6.15 练习15
void setup()
{
    size(200, 300);
    int randomRow = (int)random(height);
    yoyo(randomRow);
}
void yoyo(int row)
{
    line(100, 0, 100, row);
    ellipse(100, row, 50, 50);
}

setup() 函数中声明一个随机整数变量 randomRow ,其值在0到画布高度之间,然后将该变量作为参数调用 yoyo() 函数,使得每次运行程序悠悠球的垂直位置不同。

7. 总结

通过以上内容,我们学习了在Processing中创建 void 函数和返回值函数的方法。 void 函数可以用于执行一系列操作,而返回值函数则可以带回执行结果供我们进一步使用。同时,我们还了解了Processing中与鼠标和键盘相关的特殊 void 函数,如 mousePressed() keyPressed() ,它们可以让程序对用户的输入做出响应。通过练习,我们巩固了所学的知识,提高了编程能力。

以下是一个总结表格:
| 函数类型 | 特点 | 示例 |
| ---- | ---- | ---- |
| void 函数 | 不返回值,用于执行一系列操作 | setup() draw() smiley() 等 |
| 返回值函数 | 执行操作并返回结果 | roll() sqrt() 等 |
| 特殊 void 函数(鼠标和键盘相关) | 响应鼠标和键盘事件 | mousePressed() keyPressed() |

希望这些知识和练习能帮助你更好地掌握Processing编程,开启更多有趣的创意编程之旅!

graph LR
    A[开始学习] --> B[掌握void函数]
    B --> C[了解返回值函数]
    C --> D[学习特殊void函数]
    D --> E[通过练习巩固知识]
    E --> F[提升编程能力]
<think>好的,用户想了解如何在OpenCV中使用cv::Mat作为函数参数和返回值。首先,我需要回忆一下cv::Mat的基本特性。cv::Mat是OpenCV中用于存储图像和其他多维数组的数据结构,它使用引用计数机制来管理内存,这样在传递时不会发生深拷贝,提高了效率。 接下来,用户需要具体的用法示例,包括参数传递和返回。作为参数时,应该考虑传递方式:值传递、引用传递或指针传递。通常推荐使用const引用以避免不必要的拷贝,尤其是在只读情况下。如果函数需要修改Mat,则使用引用传递。返回的时候,可以直接返回cv::Mat对象,因为OpenCV的Mat类已经处理好了引用计数,不会导致数据重复拷贝。 然后,我需要确保示例代码的正确性。例如,函数参数使用const cv::Mat&,返回值直接返回cv::Mat。还要注意可能的错误,比如浅拷贝问题,但Mat的引用计数机制会自动处理,只要正确使用就不会有问题。 另外,用户可能关心性能问题,所以需要解释为什么使用引用传递更高效。同时,可能需要提到返回局部Mat对象是安全的,因为引用计数会保持数据有效直到所有引用都被释放。 最后,根据用户提供的引用资料,引用[1]介绍了cv::Mat的基本结构,引用[2]涉及Vec模板类,可能在处理特定元素类型时有用,但当前问题主要关注参数传递,所以可能不需要深入。引用[3]的checkVector方法可能和参数校验有关,但这里暂不涉及。 总结步骤:解释传参方式,给出示例代码,说明注意事项,并解答可能的性能疑问。确保代码符合OpenCV的最佳实践,并引用相关资料支持。</think>### cv::Mat作为函数参数返回值的用法详解 #### 一、参数传递方式 1. **常量引用传递(推荐)** ```cpp void processImage(const cv::Mat& input) { // 只读操作(如计算直方图) cv::Mat gray; cv::cvtColor(input, gray, cv::COLOR_BGR2GRAY); } ``` - **优势**:避免数据拷贝,适合只读场景[^1] 2. **引用传递(需修改原数据)** ```cpp void blurImage(cv::Mat& input) { // 直接修改原图 cv::GaussianBlur(input, input, cv::Size(5,5), 0); } ``` 3. **指针传递(C风格兼容)** ```cpp void resizeImage(cv::Mat* input) { cv::resize(*input, *input, cv::Size(640,480)); } ``` #### 二、返回值使用示例 ```cpp cv::Mat createMask(int width, int height) { cv::Mat mask = cv::Mat::zeros(height, width, CV_8UC1); cv::circle(mask, cv::Point(300,200), 100, cv::Scalar(255), -1); return mask; // 返回时自动触发引用计数机制 } ``` #### 三、完整调用示例 ```cpp cv::Mat imageProcessing(const cv::Mat& src) { cv::Mat dst; cv::Canny(src, dst, 50, 150); // 边缘检测 return dst; } int main() { cv::Mat input = cv::imread("test.jpg"); cv::Mat output = imageProcessing(input); cv::imshow("Result", output); return 0; } ``` #### 四、注意事项 1. **浅拷贝特性**:默认赋值操作共享数据 ```cpp cv::Mat a = input.clone(); // 深拷贝需要显式调用 ``` 2. **类型安全**:使用`CV_Assert()`校验矩阵类型 ```cpp void checkType(const cv::Mat& mat) { CV_Assert(mat.type() == CV_8UC3); } ``` 3. **返回值优化**:编译器会自动应用返回值优化(RVO),无需担心拷贝开销
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值