Arduino - 函数
Arduino - 使用函数编写模块化代码
Section titled “Arduino - 使用函数编写模块化代码”随着您的程序规模增长,将所有代码保留在setup()和loop()中会变得混乱且难以管理。函数是将程序构建成逻辑的、可重用代码块的主要工具,每个代码块旨在执行特定任务。
为什么要使用函数?
Section titled “为什么要使用函数?”- 组织性:函数将复杂问题分解为更小、更易于管理的部分(例如,
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'时)}- 返回类型:函数执行完毕后返回的值的数据类型(例如
int、float、bool)。如果函数不返回任何内容,则使用void。 - 函数名称:一个描述性的名称,遵循与变量名相同的规则。
- 参数(或实参):作为函数输入的变量列表。这是您向函数传递数据的方式。如果函数不接受任何输入,则括号为空:
functionName()。 - 函数体:花括号
{}内的代码块,在函数被调用时执行。
声明和定义函数
Section titled “声明和定义函数”C++编译器从上到下读取您的代码。它必须在您尝试调用函数之前就知道该函数。您可以通过两种方式实现这一点:
- 先定义后使用:只需将整个函数定义在所有调用它的代码之上(例如,在
setup()和loop()之上)。这很简单,但可能使文件顶部显得杂乱。 - 先原型后定义(推荐):在草图顶部声明一个原型。原型就像函数的签名,没有函数体。这告诉编译器,“一个具有此名称、返回类型和参数的函数存在于某处。”然后,您可以将完整的函数定义放置在任何其他位置,通常在
loop()之下,以便更好地组织。
示例:使用函数原型
Section titled “示例:使用函数原型”这个完整的草图演示了一个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;}编写函数的最佳实践
Section titled “编写函数的最佳实践”- 单一职责原则:一个函数应该只做一件事,并把它做好。如果您的函数名为
readSensorAndPrintValue,请考虑将其拆分为int readSensor()和void printValue(int value)。 - 描述性命名:根据函数执行的操作(
calculateAverage、checkForButtonPress)或返回的值(isButtonPressed)来命名函数。 - 保持函数简短:更短的函数更容易理解、测试和调试。
- 按值传递与按引用传递:默认情况下,参数是按值传递的,这意味着函数会获得变量的副本。在函数内部修改它不会影响原始变量。要修改原始变量,您可以通过在参数类型后添加一个与号(
&)来按引用传递(例如,void myFunction(int& myVar))。请谨慎使用此方法。