آموزش برنامه نویسی سوئیفت (Swift): تصمیم گیری و حلقه ها — بخش چهارم
در بخش قبلی این سری مطالب آموزش برنامه نویسی سوئیفت به مبحث «عملگر، optional و مقادیر تهی» پرداختیم. در این نوشته قصد داریم به یادگیری دانشی بپردازیم که به هوشمندتر شدن برنامههای ما کمک میکند.
تصمیمگیری
ما هر روزه با موقعیتهای تصمیمگیری مختلفی رو به رو میشویم. هنگامی که صبح از خواب برمیخیزید تصمیم میگیرید که یک قهوه بخورید؛ اما برای خوردن قهوه باید تصمیم بگیرید که یک فنجان قهوه درست میکنید یا نه. ابتدا باید بررسی کنید که آیا قهوه دارید یا نه، سپس باید ببینید که آیا شکر دارید یا نه و سپس باید وجود شیر را بررسی کنید. در نهایت وجود یک فنجان تمیز را بررسی میکنید.
اگر پاسخ هر کدام از سؤالهای فوق منفی باشد، یا باید هیچ کاری نکنیم و یا این که ابتدا موارد مورد نیاز بری تهیه قهوه را فراهم بکنیم. همین نکته در مورد برنامهنویسی نیز صحیح است و میتوانیم با استفاده از گزارههای if تصمیمهایی اتخاذ کنیم. شکل ظاهری گزاره if در زبان سوئیفت به صورت زیر است:
1var hasCoffee: Bool = true
2var madeCup: Bool = false
3if hasCoffee {
4 madeCup = true
5}
6// madeCup is equal to true
در قطعه کد فوق دو مقدار بولی در ابتدا تعریف شدهاند که یکی hasCoffee و دیگری madeCup است. ما میدانیم که در آشپزخانه قهوه داریم؛ اما هنوز قهوه درست نکردهایم و از این رو hasCoffee به صورت درست (true) و madeCup به صورت نادرست (flase) است.
سپس گزاره if واقعی را میبینیم. ما از ساختار if hasCoffee برای بررسی مقدار hasCoffee استفاده میکنیم، اگر وجود قهوه به صورت true ارزیابی شود در این صورت کد موجود درون آکولادها اجرا میشود. اگر hasCoffee به صورت false باشد، از همه کدی که درون آکولاد قرار دارد عبور میکنیم و مقدار madeCup به صورت false خواهد بود.
عرف کدنویسی apple چنین است که باید پیش از مقادیر بولی از یک فعل ربطی استفاده کنیم. بدین ترتیب در ادامه میتوانیم بدون نیاز به مراجعه دوباره به نوع آن نوعش را تشخیص دهیم.
hasCoffee از سنت کدنویسی اپل استفاده میکند؛ اما میتوانیم در موارد نیاز از ساختار isSunny یا didRain نیز استفاده کنیم. برای مثال یک آزمون تست میتواند از userIsAtLeastThirteen استفاده کند. این ساختار نه تنها به ما اعلام میکند که باید انتظار مقدار درست یا نادرست داشته باشیم؛ بلکه حتی کمک میکند بدانیم مقدار مورد نظر چه مقداری میتواند داشته باشد. برخی اوقات سرنخهای زمینهای مانند این به یادآوری متغیرهای دیگری که در زمانهای قبل در برنامه خود تعیین کردهایم کمک میکند.
در ادامه میخواهیم تواناییهای برنامهنویسی خود را افزایش دهیم و امکان تصمیمگیریهای بهتر را ایجاد کنم، زیرا گزارههای if بخش دیگری نیز دارند که امکان اجرای کار خاصی در صورت رد شدن شرط مورد بررسی را فراهم میآورند.
1var hasCoffee = false
2var madeCup: Bool
3if hasCoffee {
4 madeCup = true
5} else {
6 madeCup = false
7}
عبارت Else این امکان را به ما میدهد که در صورت نادرست بودن نتیجه ارزیابی شرط خود کد خاصی را اجرا کنیم. در این حالت hasCoffee به صورت false تعیین شده است و madeCup نیز کلاً مقداردهی نشده است؛ اما در نهایت یک مقدار بولی خواهد داشت و از این رو در ابتدا آن را به صورت یک مقدار بولی بدون مقدار تعریف میکنیم. دقت کنید که اگر تلاش کنید از یک مقدار بولی غیر اختیاری که وهلهسازی نشده است استفاده کنید، کامپایلر اعلام خطا خواهد کرد.
زمانی که وارد گزاره if میشویم، ابتدا دستور if hasCoffee را بررسی میکنیم و چون قهوه نداریم، به بخش else میرویم و madeCup را به صورت false تعیین میکنیم. برخی توسعهدهندگان گزارههای else/if دیگری را نیز برای بررسی حالتهای مختلف اضافه میکنند. بدین ترتیب کد به صورت زیر درمیآید:
1var age: Int = 16
2var ageDescription: String
3if age < 13 {
4 ageDescription = "You are young."
5} else if age <= 18 {
6 ageDescription = "You are a teenager."
7} else {
8 ageDescription = "You are an adult."
9}
10// ageDescription is equal to "You are a teenager."
در مثال فوق ابتدا age را به میزان 16 تعیین میکنیم و ageDescription به صورت نوع string اعلان میشود. گزاره if ابتدا بررسی میکند که آیا if age < 13 است یا نه. از آنجا که سن برابر با 16 است بنابراین گزاره شرطی فوق درست نیست و باید به گزاره else برویم. بنابراین کد else if age <= 18 را داریم که نتیجه این ارزیابی درست است و از این رو مقدار ageDescription به صورت ".You are a teenager" تعیین میشود. از آنجا که یکی از شاخههای گزاره شرطی ما درست بوده است، باید از بررسی حالتهای دیگر اجتناب کنیم و از گزاره if بازگردیم. به بیان دیگر هرگز به گزاره else مراجعه نمیکنیم.
روش دیگر نوشتن کد به صورت زیر است و این نیز ساختار معتبری است، صرفاً خواندن آن کمی دشوارتر از حالت قبلی است:
1var age: Int = 25
2var ageDescription: String
3if age < 13 {
4 ageDescription = "You are young."
5else {
6 if age <= 18 {
7 ageDescription = "You are a teenager."
8 else {
9 ageDescription = "You are an adult."
10 }
11}
12// ageDescription is equal to "You are an adult."
ساختار فوق به نام گزارههای if تودرتو شناخته میشود. در این کد میتوانید ببینید که نخستین بررسی یعنی age < 13 نتیجه false دارد و سپس age <= 18 بررسی میشود که آن نیز نتیجه false دارد و سپس به گزاره else منتقل میشویم که عبارت زیر را تعیین میکند:
1ageDescription = "You are an adult."
در قطعه کد زیر تلاش کنید دریابید متغیر ageDescription چه مقداری میتواند داشته باشد. بدین ترتیب میتوانید میزان یادگیری خود از مقاله قبلی در مورد عملگرها و ترتیب آنها را بیازمایید.
1var likesCoffeeWithSugar = true
2var likesCoffeeWithMilk = false
3var likesCoffeeBlack = !likesCoffeeWithMilk
4var hasCoffee = true
5var hasMilk = true
6var hasSugar = false
7var madeCup: Bool
8
9if (likesCoffeeWithMilk && hasMilk) &&
10 (likesCoffeeWithSugar && hasSugar) && hasCoffee {
11 madeCup = true
12} else if (likesCoffeeWithMilk && hasMilk) ||
13 (likesCoffeeWithSugar && hasSugar) ||
14 likesCoffeeBlack && hasCoffee {
15 madeCup = true
16} else {
17 madeCup = false
18}
در قطعه کد فوق ابتدا باید بررسی کنیم که آیا شخص برای قهوه خود به یک افزودنی نیاز دارد یا نه و اگر چنین است باید مطمئن شویم که آن افزودنی را داریم یا نه. بدین ترتیب از درست کردن قهوه اطمینان مییابیم. بدین ترتیب کد فوق از درست کردن قهوه بدون داشتن ماده اصلی جلوگیری میکند.
دقت کنید که در کد فوق پرانتزها تقدم را از همه عملگرها میگیرند، یعنی قبل از عملگرهای دیگر ارزیابی میشوند. بدین ترتیب عملگر پرانتز نسبت به عملگر (!) اولویت دارد و عملگر (!) نیز نسبت به عملگر (&&) اولویت دارد، در نهایت عملگر (&&) نسبت به عملگر (||) اولویت دارد. با استفاده از این منطق باید بتوانید حدس بزنید که آیا فنجان قهوه آماده خواهد شد یا نه.
گزارههای IF نیز دارای عملگر سهتایی هستند که در مقاله قبلی به اجمال اشاره کردیم؛ اما توضیح آن را به این مقاله موکول نمودیم.
عملگرهای سهتایی شبیه به گزارههای if استفاده میشوند و تا حدودی مانند عملگرهای nil-coalescing هستند. در ادامه مثالی از یک گزاره if نمایش مییابد و سپس روش کوتاه شده انجام همان گزاره if به صورت عملگر سهتایی را ملاحظه میکنید:
1var isSunShining = true
2var description: String = "" // This is an empty string
3if isSunShining {
4 description = "Yay!"
5else {
6 description = "Aww..."
7}
8
9// Same if statement using a ternary operator
10description = isSunShining ? "Yay!" : "Aww..."
در مثال فوق، isSunShining مورد بررسی قرار میگیرد. عملگر (?) بدین معنی است که همه عبارتهای قبل از آن باید مورد بررسی قرار گیرند، عملگر (:) به این معنی است که عبارت سمت چپ باید در صورت درست بودن گزاره مورد بررسی و عبارت سمت راست در صورت نادرست بود گزاره مورد بررسی، استفاده شود. اگر بخواهیم به طور خلاصه آن را بخوانیم به این ترتیب است که مقدار description را به صورت زیر تعیین کن: ابتدا isSunShining را بررسی کن، اگر درست بود از «!Yay» و در غیر این صورت از «...Aww» استفاده کن.
همان طور که میبینید با بهرهگیری از این روش به جای 4 خط کد کافی است یک خط کد بنویسیم. عملگرهای سهتایی جالب هستند چون خوانایی بالایی دارند و این امر موجب بهبود زیادی در کد میشود؛ با این وجود مشکل آنها در زمان استفاده از گزارههای else if است که باید از گزارههای if استفاده کنیم و منتظر بهینهسازی کامپایلر بمانیم.
بهینهسازی کامپایلر در هنگام کامپایل شدن برنامه رخ میدهد و در این فرایند کامپایلر به دنبال چیزهایی میگردد که میتواند مورد استفاده مجدد قرار دهد و به این ترتیب مطمئن میشود که کد کوچکتر شده و کارایی بیشتری در استفاده از متغیرها و منطق برنامه پیدا میکند. به کد زیر توجه کنید:
1var likesCoffeeWithSugar = true
2var likesCoffeeWithMilk = false
3var likesCoffeeBlack = !likesCoffeeWithMilk
4var hasCoffee = true
5var hasMilk = true
6var hasSugar = false
7
8var madeCup = (likesCoffeeWithMilk && hasMilk) &&
9 (likesCoffeeWithSugar && hasSugar) && hasCoffee ? true :
10 (likesCoffeeWithMilk && hasMilk) ||
11 (likesCoffeeWithSugar && hasSugar) ||
12 likesCoffeeBlack && hasCoffee ? true : false
13// Thats really hard to read
14var age = 20
15var description: String
16description = (age < 13) ? "You are young." :
17 (age <= 18)? "You are a teenager." :
18 "You are an adult."
19// A little easier to read
20// Finally how it really shines
21var hasFirstName = true
22var hasLastName = false
23var canLogIn: Bool
24var firstName: String?
25var lastName: String?
26// more variables to follow
27firstName = hasFirstName ? firstNameFromWeb : "Unknown user"
28lastName = hasLastName ? lastNameFromWeb : nil
29canLogIn = (ageFromWeb >= 13)
30
31// 20 more profile information items to check
همان طور که میبینید برخی حالتها هستند که استفاده از گزارههای if/else-if/else بسیار بهتر از عملگر سهتایی است؛ اما مطمئناً مواردی نیز وجود دارند که عملگرهای سهتایی میتوانند کارهای شما را به عنوان یک برنامهنویس، آسانتر سازند. ابزاری وجود دارد که آن را «جدول ارزش» (truth table) مینامیم. هدف آن نمایش نتایجی است که ممکن است در حالتهای مختلف به دست بیاوریم.
در جدول ارزش فوق، وضعیتهای ممکن مختلف برای عملگر AND و عملگر OR را مشاهده میکنید. AND برای بررسی True بودن همه متغیرها استفاده میشود. اگر یک متغیر دیگری داشتیم باید آن را در ستون دیگری از جدول به نام C قرار میدادیم و درستی آن را نیز بررسی میکردیم تا همه حالتهای ممکن بین سه متغیر A، B و C به دست آیند. اگر یکی از این متغیرها False باشند، در این صورت نتیجه False خواهد بود. حتی اگر 100 ستون داشته باشیم و تنها یک از متغیرها False باشد، در این صورت نتیجه False خواهد بود.
در سمت مقابل برای عملگر OR کافی است که یکی از مقادیر True باشد، یعنی اگر 100 ستون داشته باشیم و 99-تای آنها False و تنها یکی True باشد، در این صوت نتیجه True خواهد بود.
همان طور که میبینید این حالت شبیه به دموکراسی یا میانگین وزندار نیست. البته اگر چنین چیزی را بخواهیم میتوانیم منطق خاص خود را به وسیله شمارش تعداد مقادیر True، تعداد مقادیر False و مقایسه بزرگی و کوچکی هر کدام پیادهسازی کنیم. ما این موضوع را در ادامه بررسی خواهیم کرد؛ اما فعلاً به بررسی گزارههای if با عملگرهای منطقی و شیوه استفاده از آنها به صورت ترکیبی میپردازیم.
حلقهها
دو نوع متفاوت از حلقهها وجود دارد که در مورد آنها در ادامه این مقاله صحبت خواهیم کرد. نوع اول حلقههای while و نوع دوم حلقههای for-in هستند.
حلقههای while
حلقههای while به طور خلاصه حلقههایی هستند که «تا وقتی» (while) یک شرط برقرار باشد اجرا میشوند. حلقههای while دو شکل دارند که یکی while و دیگری repeat-while است. هر دوی آنها را میتوانید در کد زیر ملاحظه کنید:
1// The while loop
2var countA = 0
3while countA < 10 {
4 countA += 1
5}
6
7// The repeat-while loop
8var countB = 0
9repeat {
10 countB += 1
11} while countB < 10
تفاوت بین یک حلقه while و حلقه repeat-while در این است که حلقه while شرط حلقه را پیش از اجرای منطق حلقه بررسی میکند؛ اما حلقه repeat-while شرط حلقه را پس از اجرای منطق آن بررسی میکند. در کد زیر میتوانید شرایط استفاده از هر کدام را مشاهده کنید:
1var madeCupCount = 0
2var shouldMakeCoffee = false
3
4repeat {
5 madeCupCount += 1
6} while shouldMakeCoffee
7// madeCupCount = 1
8
9while shouldMakeCoffee {
10 madeCupCount += 1
11}
12// madeCupCount = 0
آیا متوجه میشوید که اشکال کار کجاست؟ ما نمیخواستیم قهوه درست بکنیم؛ اما در حلقه repeat-while در هر حالت یک فنجان قهوه درست میکنیم، زیرا شرط حلقه تا زمانی که منطق آن یک بار اجرا نشده باشد، مورد بررسی قرار نمیگیرد. با این حال حلقه while چنان که انتظار داریم اجرا میشود.
استفاده از حلقه repeat-while در کد فوق مانند آن است که کسی بگوید یک فنجان قهوه به من بده و نوع آن مهم نیست چون قصد نوشیدن آن را ندارم. اما حلقه repeat-while نیز موارد استفاده خاص خود را دارد. برای نمونه به کد زیر نگاه کنید:
1let winningScore = 100
2var playerOneScore = 0
3var playerTwoScore = 0
4
5repeat {
6 playerOneScore += 1
7 playerTwoScore += 2
8} while (playerOneScore < winningScore) ||
9(playerTwoScore < winningScore)
در کد فوق برای ما مهم نیست که امتیازها از winningScore بالاتر برود یا نه. ما صرفاً میخواهیم بدانیم چه کسی امتیازاتش از winningScore بالاتر میرود. در این مثال شخص مورد نظر بازیکن شماره دو است که امتیازش دو برابر است؛ اما این منطق حلقه است که تعیین میکند بازی فعال باشد، در زمان فعال بودن چه اتفاقی بیفتد و چه زمانی بازی خاتمه بیابد.
در زمان نوشتن حلقهها باید مراقب باشیم. چون ممکن است به یک حلقه نامتناهی برسیم یعنی حلقهای که هرگز پایان نمییابد. بدین ترتیب ممکن است رایانه شما برای مدتی از کار بیفتد و برنامه همچنان مشغول محاسبات اجرای حلقه باشد. در کد زیر سناریوی دیگری را ملاحظه میکنید. آیا میتوانید اشکال آن را پیدا کنید؟