توابع و محدودهٔ متغیرها
نوشتن تابع در C++ آردوینو؛ پارامتر، مقدار بازگشتی و void، محدوده و عمر متغیرهای محلی و سراسری، پیشتعریف (Prototype) و بازنویسی تمیز یک چشمکزن با توابع کمکی.
آنچه در این درس میآموزید
- تابع با پارامتر و مقدار بازگشتی تعریف کنید و آن را صدا بزنید
- تفاوت تابع بازگشتدهنده و تابع void را توضیح دهید
- محدوده (Scope) و عمر متغیر محلی و سراسری را تشخیص دهید
- کد تکراری را به توابع کمکی مثل blinkPattern و readAverage منتقل کنید
فهرست مطالب
کد چراغ راهنمای درس قبل همه در loop() بود؛ اگر یک فاز دیگر اضافه میکردید، تابع اصلی شلوغتر میشد. ابزار مهار این شلوغی «تابع» (Function) است: بلوکی نامدار از کد که یک بار مینویسید و هر جا لازم شد صدا میزنید. توابع کد را کوتاه و خوانا میکنند و اجازه میدهند یک قطعهٔ امتحانشده را بدون بازنویسی در برنامههای دیگر هم بردارید. در این درس تعریف تابع، پارامتر و مقدار بازگشتی، و مفهوم مهم «محدوده» (Scope) را یاد میگیرید و یک چشمکزن شلوغ را با توابع بازنویسی میکنید.
چرا تابع؟
دو انگیزه اصلی دارید:
- حذف تکرار — همین حالا در
loop()شما احتمالاً چند جفتdigitalWriteوdelayکپی شده است. هر تکرار، یک جا برای خطا و یک دردسر در اصلاح است؛ کد را عوض کنید باید همهٔ نسخهها را بگردید. - چیدمان ذهنی — تابع با نام گویا، برنامه را به فهرستی از کارها تبدیل میکند:
blinkPattern(3, 200)یعنی «سه بار با فاصلهٔ ۲۰۰ میلیثانیه بزن»؛ نیازی به خواندن جزئیات نیست.
هر برنامهٔ آردوینو از قبل با دو تابع آشنا هستید: setup() و loop() هر دو تابعاند؛ شما از این درس به بعد تابع خودتان را میسازید.
تعریف تابع: پارامتر، بازگشت و void
یک تابع از چهار بخش ساخته میشود: نوع بازگشت، نام، پارامترها و بدنه:
int addTwo(int a, int b) { // Return type, name, parameters
int sum = a + b; // Local variable, lives only here
return sum; // Send the result back to the caller
}- نوع بازگشت — چیزی که تابع برمیگرداند؛ این تابع یک عدد صحیح میدهد، پس
int. اگر تابع چیزی برنگرداند، نوعشvoidاست؛setup()وloop()همیناند. - پارامتر (Parameter) — متغیرهای ورودی تابع؛
aوbفقط داخل همین تابع وجود دارند. هنگام صدا زدن، به آنها «آرگومان» (Argument) میدهید:int result = addTwo(3, 4); - return — اجرای تابع را تمام میکند و مقدار را به محل صدا زدن میفرستد. در تابع
voidهم میتوانید یکreturn;تنها بنویسید تا زودتر خارج شوید. - پیشتعریف (Prototype) — اعلامِ «چنین تابعی وجود دارد» پیش از استفاده:
int addTwo(int a, int b);. در زبان C++ تابع باید قبل از محل صدا زدن دیده شود، اما IDE آردوینو بهطور خودکار پیشتعریفها را میسازد؛ پس ترتیب تعریف توابع در فایل.inoمعمولاً مهم نیست و میتوانید تابع را بعد ازloop()هم بنویسید.
محدوده و عمر متغیرها
جایی که متغیر تعریف میشود، تعیین میکند چه کسی آن را میبیند و چه زمانی زنده است:
- متغیر محلی (Local Variable) — داخل تابع یا بلوک تعریف میشود؛ فقط در همانجا معتبر است و با پایان تابع از حافظه پاک میشود. هر بار صدا زدن تابع، نسخهٔ تازهای از آن ساخته میشود.
- متغیر سراسری (Global Variable) — بیرون از همهٔ توابع، بالای فایل تعریف میشود؛ همهٔ توابع آن را میبینند و از روشنشدن برد تا خاموشی زنده است. شمارهٔ پینها و وضعیتهایی که چند تابع باید بدانند سراسریاند.
قاعدهٔ عملی: تا میتوانید محلی بنویسید. متغیر سراسری از هر تابعی قابلتغییر است، پس وقتی مقدارش عجیب شد، باید همهٔ برنامه را بگردید تا مقصر را پیدا کنید. «برخورد نام» (Naming Collision) هم همینجاست: اگر متغیر محلی همنام یک متغیر سراسری بسازید، داخل آن تابع نامِ محلی برنده است و سراسری پنهان میشود؛ کامپایلر خطا نمیگیرد و گیجی از نوع خاموش است.
بازنویسی: از کد تکراری به توابع
این چشمکزن را ببینید؛ همان الگوی دو بار کپیشده:
const int LED_PIN = 8;
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
// Three quick blinks, copy-pasted by hand
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
delay(1000);
}همین برنامه با یک تابع کمکی:
const int LED_PIN = 8;
// Blink the LED on pin 8, "times" times, each phase "ms" milliseconds
void blinkPattern(int times, int ms) {
for (int i = 0; i < times; i++) {
digitalWrite(LED_PIN, HIGH);
delay(ms);
digitalWrite(LED_PIN, LOW);
delay(ms);
}
}
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
blinkPattern(3, 200); // Three quick blinks
delay(1000);
}هجده خط تکراری به یک حلقه در تابع و یک خط صدا زدن تبدیل شد. اگر فردا خواستید الگو را عوض کنید، فقط تابع را تغییر میدهید؛ و همین تابع را در هر برنامهٔ دیگری هم میتوانید کپی کنید.
دو تابع کمکی کاربردی
دو الگوی پرتکرار که ارزش داشتن در جعبهابزار را دارند:
// Pulse a pin once: on for "onMs", off for "offMs"
void pulse(int pin, int onMs, int offMs) {
digitalWrite(pin, HIGH);
delay(onMs);
digitalWrite(pin, LOW);
delay(offMs);
}
// Read an analog pin "samples" times and return the average
int readAverage(int pin, int samples) {
int sum = 0; // Local: this sum belongs to this call only
for (int i = 0; i < samples; i++) {
sum += analogRead(pin);
delay(5);
}
return sum / samples;
}تابع pulse یک فلاش میسازد و blinkPattern از قبل میتوانست با آن نوشته شود؛ ترکیب توابع کوچک، ساخت توابع بزرگتر را ارزان میکند. تابع readAverage هم خواندن سنسور آنالوگ را پایدار میکند: بهجای یک نمونهٔ شلوغ، میانگین چند نمونه برمیگرداند — و چون sum محلی است، هر صدا زدن از صفر شروع میشود و اثری از صدا زدن قبلی نمیماند.
تلههای رایج
- تغییر مخرب متغیر سراسری — اگر چند تابع مقدار یک متغیر سراسری را عوض کنند، ردیابی باگ سخت میشود. بهجای آن، داده را با پارامتر بدهید و نتیجه را با return بگیرید؛ جریان داده در کدتان آشکار میماند.
- متغیر محلی بیمقدار اولیه — متغیر محلی مقدار اولیهٔ خودکار ندارد و محتوایش زبالهٔ حافظه است؛ همیشه در تعریف، مقدار بدهید:
int sum = 0; - توابع خیلی بزرگ — اگر تابعتان بیش از یک صفحه شد، احتمالاً چند کار میکند؛ به چند تابع کوچک با نامهای گویا بشکنید.
اشتباهات رایج و عیبیابی
| خطا یا نشانه | علت رایج | راهحل |
|---|---|---|
'blinkPattern' was not declared in this scope |
غلط املایی در نام هنگام صدا زدن، یا فراموشکردن تعریف تابع | نام را با تعریف تابع حرفبهحرف مقایسه کنید؛ تعریف را بالای فایل یا بیرون از setup و loop بگذارید |
too few arguments to function |
صدا زدن تابع بدون همهٔ آرگومانها | تعداد و ترتیب آرگومانها را با پارامترهای تعریف تطبیق دهید |
return-statement with no value |
return; در تابعی که نوع بازگشت دارد، یا برعکس |
تابع بازگشتدار حتماً مقدار برگرداند؛ تابع void مقدار برنگرداند |
| متغیر داخل تابع همیشه صفر یا مقدار عجیب دارد | متغیر محلی بدون مقدار اولیه، یا پنهانشدن سراسری زیر محلی همنام | در تعریف مقدار بدهید و از نام همسان با متغیرهای سراسری پرهیز کنید |
| مقدار سراسری بیدلیل عوض میشود | چند تابع همان متغیر را تغییر میدهند | تغییرها را فقط در یک تابع متمرکز کنید یا داده را با پارامتر و return جابهجا کنید |
تمرینها
تمرین
تمرین ۱ — چشمکزن مورس با توابع
تابعی به نام flash بنویسید که یک فلاش کوتاه (۱۰۰ میلیثانیه روشن، ۱۰۰ میلیثانیه خاموش) روی پینی که با پارامتر میگیرد بزند؛ سپس با صدا زدن چندبارهٔ همین تابع، الگوی «کوتاه کوتاه کوتاه» روی پین ۸ بسازید و میان دو الگو یک ثانیه مکث بگذارید.
نمایش راهحل
تابع flash پین را با پارامتر میگیرد و pulse درس با زمانهای ثابت کوتاه میشود؛ تکرار الگو را هم یک حلقهٔ for در loop() انجام میدهد:
const int LED_PIN = 8;
// One short flash on the given pin
void flash(int pin) {
digitalWrite(pin, HIGH);
delay(100);
digitalWrite(pin, LOW);
delay(100);
}
void setup() {
pinMode(LED_PIN, OUTPUT);
}
void loop() {
for (int i = 0; i < 3; i++) { // Three short flashes
flash(LED_PIN);
}
delay(1000); // Pause between patterns
}چون flash پین را با پارامتر میگیرد، همین تابع را برای هر LED دیگری هم میتوانید صدا بزنید؛ مثلاً flash(9) بدون هیچ تغییر دیگری کار میکند.
:::
خلاصه
- تابع بلوکی نامدار از کد است؛ نوع بازگشت، نام، پارامترها و بدنه دارد و با نامش صدا زده میشود.
- تابعی که مقدار برمیگرداند نوعش همان مقدار است؛ تابع بیبازگشت
voidاست. - متغیر محلی فقط در تابع خودش زنده است و با هر صدا زدن تازه میشود؛ متغیر سراسری همهجا دیده میشود — پس کم و محتاط بسازید.
- IDE آردوینو پیشتعریف (Prototype) توابع را خودکار میسازد و ترتیب تعریف در فایل
.inoرا آزاد میکند. - کد تکراری را به توابع کوچک با نام گویا منتقل کنید؛ همین الگو پایهٔ کتابخانهسازی است.
توابع کد را تمیز کردند؛ اما برنامهها هنوز فقط حرف میزنند و گوش نمیدهند. در درس بعدی نخستین ورودی واقعی را میخوانید: یک دکمهٔ فشاری که LED را کنترل میکند.