Skip to content

Arduino - 函数

Arduino - 使用函数编写模块化代码

Section titled “Arduino - 使用函数编写模块化代码”

随着您的程序规模增长,将所有代码保留在setup()和loop()中会变得混乱且难以管理。函数是将程序构建成逻辑的、可重用代码块的主要工具,每个代码块旨在执行特定任务。

  • 组织性:函数将复杂问题分解为更小、更易于管理的部分(例如,readSensors()、updateDisplay()、sendData())。
  • 可重用性(DRY原则):代表“不要重复自己”(Don’t Repeat Yourself)。如果您需要多次执行相同的操作,您只需在函数中编写一次代码,然后在需要时调用它即可。
  • 可读性:命名良好的函数使您的代码具有自文档性。blinkLed(3, 500);比一段for、digitalWrite和delay调用代码要清晰得多。
  • 调试:将代码分离到函数中,可以更容易地测试和调试程序的各个部分,而不会影响其余部分。
  • 模块化:函数允许您创建代码库,这些代码库可以轻松共享并在其他项目中重用。

每个Arduino草图都已使用两个特殊的、必需的函数:setup(),它在程序开始时运行一次;以及loop(),它持续运行。

C++中的函数定义有四个主要部分:

returnType functionName(parameterType parameterName1, ...)
{
// 函数体:要执行的代码
return returnValue; // (仅当returnType不是'void'时)
}
  1. 返回类型:函数执行完毕后返回的值的数据类型(例如int、float、bool)。如果函数不返回任何内容,则使用void。
  2. 函数名称:一个描述性的名称,遵循与变量名相同的规则。
  3. 参数(或实参):作为函数输入的变量列表。这是您向函数传递数据的方式。如果函数不接受任何输入,则括号为空:functionName()。
  4. 函数体:花括号{}内的代码块,在函数被调用时执行。

C++编译器从上到下读取您的代码。它必须在您尝试调用函数之前就知道该函数。您可以通过两种方式实现这一点:

  1. 先定义后使用:只需将整个函数定义在所有调用它的代码之上(例如,在setup()和loop()之上)。这很简单,但可能使文件顶部显得杂乱。
  2. 先原型后定义(推荐):在草图顶部声明一个原型。原型就像函数的签名,没有函数体。这告诉编译器,“一个具有此名称、返回类型和参数的函数存在于某处。”然后,您可以将完整的函数定义放置在任何其他位置,通常在loop()之下,以便更好地组织。

这个完整的草图演示了一个void函数和一个返回值的函数,使用了推荐的原型方法。

// ============ 函数原型 ============
// 一个接受两个参数并执行操作(闪烁)的函数。
// 它不返回值,因此其返回类型是'void'。
void blinkLed(int pin, int blinkCount);
// 一个执行计算并返回结果的函数。
// 它返回一个整数,因此其返回类型是'int'。
int celsiusToFahrenheit(int celsius);
// ============ 全局变量 ============
const int BUILTIN_LED_PIN = LED_BUILTIN;
// ================== Setup ===================
void setup() {
Serial.begin(9600);
pinMode(BUILTIN_LED_PIN, OUTPUT);
Serial.println("--- Function Demo ---");
// 调用'blinkLed'函数以指示setup完成。
Serial.println("Blinking LED 3 times...");
blinkLed(BUILTIN_LED_PIN, 3);
// 调用'celsiusToFahrenheit'函数并存储结果。
int tempC = 25;
int tempF = celsiusToFahrenheit(tempC);
Serial.print(tempC);
Serial.print(" degrees Celsius is ");
Serial.print(tempF);
Serial.println(" degrees Fahrenheit.");
}
// ================== Loop ====================
void loop() {
// 主循环保持整洁。所有逻辑都在函数中。
// 这使得程序的意图清晰。
}
// ========== 函数定义 ===========
/**
* @brief 使指定LED闪烁特定次数。
* @param pin LED的引脚号。
* @param blinkCount LED闪烁的次数。
*/
void blinkLed(int pin, int blinkCount) {
for (int i = 0; i < blinkCount; i++) {
digitalWrite(pin, HIGH);
delay(250);
digitalWrite(pin, LOW);
delay(250);
}
}
/**
* @brief 将温度从摄氏度转换为华氏度。
* @param celsius 摄氏度温度。
* @return 华氏度温度(整数形式)。
*/
int celsiusToFahrenheit(int celsius) {
// 在转换回整数之前,使用浮点数进行计算以确保精度
return (celsius * 9.0 / 5.0) + 32;
}
  • 单一职责原则:一个函数应该只做一件事,并把它做好。如果您的函数名为readSensorAndPrintValue,请考虑将其拆分为int readSensor()和void printValue(int value)。
  • 描述性命名:根据函数执行的操作(calculateAverage、checkForButtonPress)或返回的值(isButtonPressed)来命名函数。
  • 保持函数简短:更短的函数更容易理解、测试和调试。
  • 按值传递与按引用传递:默认情况下,参数是按值传递的,这意味着函数会获得变量的副本。在函数内部修改它不会影响原始变量。要修改原始变量,您可以通过在参数类型后添加一个与号(&)来按引用传递(例如,void myFunction(int& myVar))。请谨慎使用此方法。