در قسمت یازدهم از آموزش آردوینو به بررسی تبدیل عدد به رشته (استرینگ) و همچنین، تبدیل رشته (استرینگ) به عدد، پرداختیم. در این قسمت قصد داریم درباره ساختاردهی کد با استفاده از بلوکهای تابع در آردوینو و همچنین، برگرداندن بیش از یک مقدار از یک تابع صحبت کنیم.
توابع برای سازماندهی کارهای انجام شده توسط اسکچ به بلوکهای فانکشنال استفاده میشوند. توابع، فانکشن ها را در پکیجهای ورودیهای از پیش تعریف شده (اطلاعاتی که به تابع داده میشود) و خروجیها (اطلاعاتی که توسط تابع ارائه میشود) نگهداری میکنند که این کار استفاده مجدد از کد شما را آسانتر میکند.
قبلاً با دو تابعی که در هر اسکچ آردوینو وجود دارد آشنا شدید؛ یعنی توابع setup و loop. شما میتوانید با تعریف return type (نوع بازگشت) (اطلاعاتی که ارائه میدهد)، نام آن و هر پارامتر اختیاری (مقادیر) که تابع هنگام فراخوانی دریافت میکند، یک تابع ایجاد کنید.
نکته✅
اصطلاحات توابع (functions) و متدها (methods) برای اشاره به بلوکهای well-defined از کد استفاده میکنند.
زبانهای شیگرا مانند C++ که عملکرد را از طریق کلاسها نشان میدهند، از اصطلاح متد استفاده میکنند. آردوینو از ترکیبی از سبکها استفاده میکند (نمونههای اسکچ از سبک C استفاده میکنند و کتابخانهها معمولاً برای نمایش متدهای کلاس C++ نوشته میشوند).
در اینجا یک فانکشن ساده وجود دارد که فقط یک LED چشمک میزند و هیچ پارامتری وجود ندارد و چیزی برنمیگرداند (قبل از تابع، void نشان میدهد که چیزی برگردانده نخواهد شد):
1 2 3 4 5 6 7 8 | // blink an LED once void blink1() { digitalWrite(LED_BUILTIN, HIGH); // turn the LED on delay(500); // wait 500 milliseconds digitalWrite(LED_BUILTIN, LOW); // turn the LED off delay(500); // wait 500 milliseconds } |
ورژن زیر دارای یک پارامتر (یک اینتیجر با نام count) است که تعیین میکند، چند بار LED چشمک میزند:
1 2 3 4 5 6 7 8 9 10 11 12 | // blink an LED the number of times given in the count parameter void blink2(int count) { while(count > 0 ) // repeat until count is no longer greater than zero { digitalWrite(LED_BUILTIN, HIGH); // turn the LED on delay(500); // wait 500 milliseconds digitalWrite(LED_BUILTIN, LOW); // turn the LED off delay(500); // wait 500 milliseconds count = count -1; // decrement count } } |
نکته✅
برنامهنویسان باتجربه متوجه خواهند شد که هر دو تابع را میتوان blink نامید؛ زیرا کامپایلر آنها را بر اساس نوع مقادیر استفاده شده برای پارامتر متمایز میکند. این رفتار اضافهبار تابع (function overloading) نامیده میشود.
تابع blink2() بررسی میکند که آیا مقدار count برابر 0 است یا خیر که اگر نباشد، LED چشمک می زند و سپس مقدار count یک واحد کاهش پیدا می کند. این کار تا زمانی تکرار می شود که متغیر count دیگر از 0 بیش تر نباشد.
توجه⚡
گاهی اوقات، یک پارامتر (parameter) در برخی داکیومنتها، بهعنوان یک ارگومان (argument) ورودی شناخته میشود. در پروژههای عملی، شما میتوانید این دو اصطلاح را یکسان در نظر بگیرید.
در اینجا یک اسکچ نمونه با یک تابع است که یک پارامتر را میگیرد و یک مقدار را برمیگرداند. این پارامتر طول زمان روشن و خاموششدن LED (بر حسب میلیثانیه) را تعیین میکند. تابع تا زمانی که یک دکمه فشار داده شود، به چشمکزدن LED ادامه میدهد و تعداد دفعاتی که LED چشمک میزند از تابع برگردانده میشود.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | /* blink3 sketch Demonstrates calling a function with a parameter and returning a value. The LED flashes when the program starts and stops when a switch connected to digital pin 2 is pressed. The program prints the number of times that the LED flashes. */ const int inputPin = 2; // input pin for the switch void setup() { pinMode(LED_BUILTIN, OUTPUT); pinMode(inputPin, INPUT); digitalWrite(inputPin,HIGH); // use internal pull-up resistor (Recipe 5.2) Serial.begin(9600); } void loop(){ Serial.println("Press and hold the switch to stop blinking"); int count = blink3(250); // blink the LED 250 ms on and 250 ms off Serial.print("The number of times the switch blinked was "); Serial.println(count); while(digitalRead(inputPin) == LOW) { // do nothing until they let go of the button } } // blink an LED using the given delay period // return the number of times the LED flashed int blink3(int period) { int blinkCount = 0; while(digitalRead(inputPin) == HIGH) // repeat until switch is pressed // (it will go low when pressed) { digitalWrite(LED_BUILTIN, HIGH); delay(period); digitalWrite(LED_BUILTIN, LOW); delay(period); blinkCount = blinkCount + 1; // increment the count } // here when inputPin is no longer HIGH (means the switch is pressed) return blinkCount; // this value will be returned } |
نکته✅
اعلان تابع یک پروتوتایپ (نمونه اولیه) است که دارای مشخصاتی ازجمله: نام، انواع مقادیری که ممکن است به تابع ارسال شود و نوع بازگشتی تابع میباشد. فرایند ساخت آردوینو، اعلانها را برای شما در زیر کاور (covers) ایجاد میکند؛ بنابراین شما نیازی به پیروی از الزامات استاندارد C برای اعلان تابع جداگانه ندارید.
کد موجود در اینجا سه شکل فراخوانی تابعی را که با آن روبهرو خواهید شد را نشان میدهد. blink1 هیچ پارامتر و مقدار بازگشتی ندارد. این کد بهصورت زیر است:
1 2 3 4 5 6 7 8 9 | void blink1() { // implementation code goes here... } //blink2 takes a single parameter but does not return a value: void blink2(int count) { // implementation code goes here... } |
blink3 یک پارامتر دارد و یک مقدار را برمی گرداند:
1 2 | int blink3(int period) { int result = 0; // implementation code goes here... return result; // this value will be returned } |
نوع دادهای که قبل از نام تابع قرار میگیرد، نوع برگشتی تابع را نشان میدهد. هنگام نوشتن تابع (نوشتن کدی که تابع و عملکرد آن را تعریف میکند)، نباید در انتهای پرانتز، نقطهویرگول قرار دهید. اما هنگامی که از تابع استفاده میکنید، به یک نقطهویرگول در انتهای خط نیاز دارید که تابع را فراخوانی کند.
اکثر توابعی که با آنها روبهرو میشوید، تغییراتی در این فرمها خواهند داشت.
نوع داده (data type)ای که قبل از نام تابع قرار میگیرد، نوع برگشتی تابع را نشان میدهد. در مورد blink1 و blink2 نوع داده void نشان میدهد که هیچ مقداری برگردانده نمیشود؛ اما در blink3 نوع داده int نشاندهنده آن است یک عدد صحیح برگردانده میشود. هنگام ایجاد توابع، نوع بازگشت را متناسب با عملکردی که تابع انجام میدهد، انتخاب کنید.
نکته✅
توصیه میشود که به توابع خود نامهای مناسب دهید؛ روش رایج انجام این کار این است که کلمات را با حرف بزرگ ابتدای هر کلمه (به جز کلمه اول) ترکیب کنید(به عنوان مثال تابع ()setBlinkerCount قاعده نامگذاری را رعایت میکند). اما بهطورکلی، شما میتوانید از هر روشی که میخواهید، استفاده کنید، اما اگر از یک روش نامگذاری ثابت و یکسان استفاده کنید، به کسانی که کد شما را میخوانند، کمک زیادی خواهید کرد.
تابع blink2 یک پارامتر به نام count دارد (زمانی که تابع فراخوانی میشود، مقدار count بهعنوان پارامتر به تابع منتقل میشود). اما تابع blink3 متفاوت است، زیرا یک پارامتر به نام period دارد.
توابع blink2 ،blink1 و blink3 به ترتیب موارد زیر را انجام میدهند:
- blink1: این تابع LED را روشن و سپس خاموش میکند.
- blink2: این تابع در یک حلقه (لوپ) while تعداد دفعات مشخص شده توسط متغیر count را تکرار میکند. به این صورت که هر بار LED چشمک میزند (روشن و خاموش میشود)، منتظر مدتزمان مشخصی میماند و این عمل را تا تمامشدن تعداد دفعات تکرار میکند.
- blink3: تا زمانی که دکمهای فشار داده نشده باشد، LED چشمک میزند و پس از فشاردادن دکمه، این تابع یعنی blink3 تعداد دفعاتی که LED چشمک زده است را به عنوان نتیجه برمیگرداند.
برگرداندن بیش از یک مقدار از یک تابع
تا حالا در این مطلب، مثالهایی از رایجترین شکل یک تابع (تابعی که فقط یک مقدار یا هیچ مقداری برمیگرداند) ارائه کردیم. اما گاهی لازم است که شما بیش از یک مقدار (دو یا چند مقدار) از یک تابع را برگردانید. بنابراین، در ادامه این قسمت روشهای مختلفی برای این کار توضیح داده میشود. سادهترین روش برای درک، این است که تابع برخی از متغیرهای سراسری (global) را تغییر دهد و در واقع چیزی از تابع را برنگرداند:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | */ int x; // x and y are global variables int y; void setup() { Serial.begin(9600); } void loop(){ x = random(10); // pick some random numbers y = random(10); Serial.print("The value of x and y before swapping are: "); Serial.print(x); Serial.print(","); Serial.println(y); swap(); Serial.print("The value of x and y after swapping are: "); Serial.print(x); Serial.print(","); Serial.println(y);Serial.println(); delay(1000); } // swap the two global values void swap() { int temp; temp = x; x = y; y = temp; } |
تابع swap با استفاده از متغیرهای سراسری (global) دو مقدار را تغییر میدهد. بهطورکلی، متغیرهای global بهراحتی قابلدرک هستند (متغیرهای global مقادیری هستند که در همهجا قابلدسترسی هستند و هر چیزی میتواند آنها را تغییر دهد)، اما برنامهنویسان حرفهای از این متغیر استفاده نمیکنند؛ زیرا بهراحتی میتوان مقدار این متغیر را تغییر داد یا اگر نام یا نوع یک متغیر سراسری در جایی از اسکچ تغییر کند، ممکن است تابع دیگر کار نکند.
روش بهتر و ایمنتر این است که رفرنسها را به مقادیری که میخواهید تغییر دهید منتقل کنید و به تابع اجازه دهید از رفرنسها برای تغییر مقادیر استفاده کند. این کار بهصورت زیر انجام میشود:
1 2 3 4 5 6 | /* functionReferences sketch demonstrates returning more than one value by passing references */ void setup() { Serial.begin(9600); } void loop() { int x = random(10); // pick some random numbers int y = random(10); Serial.print("The value of x and y before swapping are: "); Serial.print(x); Serial.print(","); Serial.println(y); swapRef(x,y); Serial.print("The value of x and y after swapping are: "); Serial.print(x); Serial.print(","); Serial.println(y);Serial.println(); delay(1000); } // swap the two given values void swapRef(int &value1, int &value2) { int temp; temp = value1; value1 = value2; value2 = temp; } |
تابع swapRef مشابه توابع قسمت قبل با پارامترهای یکسان است. اما علامت (&) نشان میدهد که پارامترها رفرنس (reference) هستند. این بدان معناست که تغییر در مقادیر درون تابع، مقدار متغیری را که هنگام فراخوانی تابع داده میشود را نیز تغییر میدهد. سپس کد را با حذف دو علامت در تعریف تابع تغییر دهید.
کد تغییریافته باید به شکل زیر باشد:
1 | void swapRef(int value1, int value2) |
اجرای کد نشان میدهد که مقادیر عوض نشدهاند؛ بلکه تغییرات ایجاد شده، به تابع منتقل شده است و هنگامی که تابع بازگردانده میشود، این تغییرات از بین میروند.
روش دیگر، استفاده از استراکچر (ساختار) C است که میتواند چندین فیلد داشته باشد و به شما این امکان را میدهد که انواع دیتا را ارسال و برگردانید.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | /* struct sketch demonstrates returning more than one value by using a struct */ struct Pair { int a, b; }; void setup() { Serial.begin(9600); } void loop() { int x = random(10); // pick some random numbers int y = random(10); struct Pair mypair = { random(10), random(10) }; Serial.print("The value of x and y before swapping are: "); Serial.print(mypair.a); Serial.print(","); Serial.println(mypair.b); mypair = swap(mypair); Serial.print("The value of x and y after swapping are: "); Serial.print(mypair.a); Serial.print(","); Serial.println(mypair.b); Serial.println(); delay(1000); } // swap the two given values Pair swap(Pair pair) { int temp; temp = pair.a; pair.a = pair.b; pair.b = temp; return pair; } |
تابع swapPair از یک ویژگی زبان C به نام struct (یا استراکچر (structure)) استفاده میکند. یک استراکچر میتواند تعدادی متغیر یا پوینتر داشته باشد.
حجم حافظهای که یک استراکچر اشغال میکند، برابر با حجم عناصر (المنتهای) آن است (در یک آردوینو 8 بیتی، یک Pair حدود 4 بایت حافظه را اشغال میکند، اما در یک برد 32 بیتی، این حجم به 8 بایت افزایش مییابد).
در زبانهای برنامهنویسی، استراکچرها (Structs) و کلاسها (Classes) دو مفهوم متفاوت هستند. اگرچه در برخی جوانب شباهت دارند، اما تفاوتهای مهمی نیز دارند. در زیر به مقایسه این دو پرداختهایم:
استراکچرها (Structs)
- استراکچرها بهعنوان یک نوع داده در زبانهای برنامهنویسی معرفی میشوند.
- آنها میتوانند چندین متغیر داشته باشند.
- کاربرد اصلی استراکچرها برای نگهداری دادههای مرتبط و ساختارمند است.
کلاسها (Classes)
- کلاسها نیز نوعی داده هستند، اما بهعنوان یک مفهوم اصولی در برنامهنویسی شیءگرا معرفی میشوند.
- آنها شامل متغیرها (ویژگیها) و توابع (متدها) هستند.
- کلاسها بهعنوان قالبهایی برای ایجاد شیءها (نمونهها) استفاده میشوند.
درود بر شما
خیلی هم عالی . البته برای برگرداندون چند عدد از توابع روش های دیگه ای هم هست . مثل روشی که عدد ثانیه و دقیقه و ساعت توی کتابخانه ds1307 توی کد ویژن بر میگردونه .
لطفا در مورد استراکچر و کلاس ها هم به صورت کامل آموزش و مثال قرار بدید