کاتلین یاد بگیریم! (قسمت ۳)

سلام مجدد و چطورین! اول یک عذرخواهی لازمه بکنم چون این دفعه فاصله بیشتری از قولی که داده بودم بین آموزش قسمت قبل و این قسمت افتاد. دارم یک کارهایی براش میکنم که دیگه اتفاق نیفته و قولش را اینجا به شما میدم. ممنون و ببخشید که توی فرایند یادگیریتون اختلال ایجاد شد.

هفته پیش و در قسمت ۲ کاتلین راجع به «نوع‌های پایه» صحبت کردیمو لازمه که قبل از این بخش اون قسمت را بخونین که دونستنش شدیداً لازمه برای اینکه ادامه بدید این قسمت را.

در ادامه مبحث «نوع‌های پایه» میریم سراغ سایر نوع‌ها از جمله انواع درست/غلط، رشته‌ها و آرایه‌ها.

 دزدی از یوتیوب!
دزدی از یوتیوب!

انواع درست/غلط(Boolean)

انواع درست/غلط یا Boolean ها در زبان کاتلین بسیار مشابه زبان برنامه‌نویسی جاوا است. در این زبان نیز مقدار صحیح با true و مقدار غلط با false نمایش داده می‌شود.

var myTrueBoolean: Boolean = true; //  تعریف یک متغیر از نوع «درست/غلط» با مقدار درست 
var myFalseBoolean = false; // متغیر از طریق مقدار متوجه میشود که نوع آن «درست/غلط» است.

اپراتورهای «یا» (||)، «و» (&&) و «نفی یا معکوس» (!) اپراتورهایی هستند که میتوان از آن‌ها برای کار روی انواع درست/غلط استفاده کرد.

val x = 1;
val w = 4;
val z = 6;
val n = x < z && z > w; //n is true

بعدا در مورد boolean ها بسیار صحبت خواهیم کرد. خصوصا وقتی برسیم به بخش «کنترل گردش» یا Flow Control که تمامی کنترل‌ها با استفاده از همین نوع صورت میگیره.

رشته‌ها (Strings)

رشته‌ها یک نوع پایه ای برای نمایش دنباله‌ای از کاراکترها است. در کاتلین رشته ها به دودسته تقسیم می شوند:

  • رشته‌های با قابلیت دریافت اپراتور \ یا Escaped String که با «"..."» (double quotes) در کامپایلر مشخص می‌شوند.
  • رشته‌های خام حاوی هر نوع رشته یا Raw strings که با «"""..."""» (double triple quotes) در کامپایلر مشخص می‌شوند.

یک escaped string:

val myString = "This is a String"

علاوه بر این اگر بخواهید از کاراکترهای معنا دار مثل n\ برای Enter استفاده کنید هم میتونید از همین قاعده استفاده کنید:

val escapeString = "This is a string with new line \n"

خوب نکته بعد اینه که المان‌های داخل رشته‌ها «کاراکترها» هستند که با استفاده از اندیس‌ها قابل دسترسی هستند. مثلاً:

val str = "abcd";
println(str[1]); //را چاپ می‌کند b مقدار  

همچنین عمل اتصال یا concatenation دو رشته به هم را میتونید با استفاده از علامت + انجام بدید. توجه داشته باشید مادامی که اولین گزاره در عبارت شما از نوع «رشته» باشد می توانید عملیات اتصال را روی بقیه انواع پایه‌ای نیز انجام بدهید. مثل مثال زیر:

val s = "abc" + 1
println(s + "def") // "abc1def" چاپ می شود

نکته بسیار مهم در بحث رشته‌ها این هست که در کاتلین رشته‌ها غیرقابل تغییر یا immutable هستند. یعنی انتظار داریم این کد اجرا نشه:

var str = "Hello "
str = str + "World!"
println(str) // prints "Hello world"!

اما همونطور که میبینیم اجرا شد! خوب اگر رشته‌ها غیر قابل تغییر هستند چرا این کد اجرا میشه؟

خوب ماجرا از این قراره که در واقع نوع رشته غیرقابل تغییر هست و چیزی که داره تغییر میکنه در واقع اشاره متغیر به بخش‌های مختلف حافظه است. به عبارت دیگر وقتی در خط اول ما رشته "Hello" را به str نسبت میدیم کاری که واقعا داریم میکنیم اینه که مقدار "Hello" را در جایی از حافظه مینویسیم و آدرس اون مکان را داخل str میگذاریم. در نتیجه هربار str را بخونیم در واقع محتوای حافظه ای را میخونیم که str به اون اشاره میکنه.

حالا وقتی ما عمل اتصال بین "Hello" و world را انجام میدیم و مجددا اون را داخل str میریزیم، کاری که کردیم اینه که در حافظه رشته "Hello world" را ایجاد کردیم و حالا آدرس این مکان حافظه را داخل str قرار دادیم و این درحالیه که مکان حافظه‌ای که داخلش "Hello" وجود داشت بدون تغییر همچنان وجود داره و تنها از دسترس ما خارج شده.

پس میتونیم با قطعیت بگیم که رشته‌ها قابل تغییر نیستند. و سوال بعدی که میتونه تمرین شما باشه اینه که چه به سر این همه رشته رها شده یا abandoned میاد؟ از منظر مصرف حافظه چه اتفاقی میفته؟

تصویر زیر هم این مفهوم را نمایش میده که بسیار گویاست.

عکس متعلق به اینجا
عکس متعلق به اینجا

رشته‌های خام

در صورتی که بخواهید رشته خود را در چندین خط بنویسید اونوقت لازمه که از """...""" استفاده کنید. به مثال زیر توجه کنید:

val multipleStringLines = """
        This is first line
        This is second line
        This is third line """

دقت داشته باشید که نمیتونید از "" برای تعریف چنین رشته‌ای استفاده کنید. امتحان کنید تا مطمئن بشید.

و اما یک قابلیت بسیار جذاب قابلیت جاگذاری متغیر در شته یا در اصطلاح string interpolation یا string templates هست. مثال زیر را ببینید:

val accountBalance = 200
val bankMessage = "Your account balance is $accountBalance" 
// Your account balance is 200

در مثال بالا یک متغیر به نام accountBalance با مقدار اولیه ۲۰۰ ساخته شد و سپس با استفاده از کاراکتر $ کامپایلر متوجه می‌شود که باید مقدار این متغیر را جایگرین کنه و در نتیجه خروجی همونطور که در بالا میبینیم Your account balance is 200 خواهد بود. طبیعتاً اگر متغیر استفاده شده تعریف نشده باشد و یا به درستی استفاده نشود کد کامپایل نخواهد شد.

برای نمایش کاراکتر $ در رشته escaped می‌توانید از $\ استفاده کنید.

یک قابلیت بسیار جذاب که کاتلین ازش بهره‌مند شده {}$ هست. با استفاده از این گزاره در رشته‌ها شما خیلی کارها میتونید بکنید از جمله استفاده از توابع در رشته‌ها و یا قراردادن کاراکترهای خاص در رشته. مثال زیر را با دقت ببینید:

val name = "Chike"
val message = "The first letter in my name is ${name.first()}" 
println(message) // The first letter in my name is C

در کد بالا {()name.first}$به کامپایلر اعلام می‌کند که حرف اول متغیر nameرا چاپ کند که مقدار C خواهد بود. و به عنوان آخرین نکته جذاب در مورد رشته‌ها شما حتی می‌توانید در داخل رشته‌ها منطق برنامه را نیز وارد کنید. مثال زیر را ببینید:

val age = 40
val anotherMessage = "You are ${if (age > 60) "old" else "young"}" 
println(anotherMessage) // You are young

در کد بالا بخش {"if(age>60) "old" else "young}$ در واقع بر اساس مقدار age تصمیم میگیرد که چه متنی در ادامه You are چاپ شود. در اینجا نیز چون مقدار age از مقدار ۶۰ کوچکتر است رشته young در ادامه You are چاپ شده و خروجی You are young را می‌دهد.

val price = "${'$'}9.99"
println(price) // prints $9.99

و حتی استفاده در رشته‌های خامبرای نمایش کاراکترهای خاص که امکان استفاده از \ برای نمایش کاراکترهای اینچنینی وجود ندارد:

val price = """
${'\n'}9.99
"""

آرایه‌ها (Arrays)

قبل از اینکه بریم سراغ اینکه چجوری میشه در کاتلین آرایه تعریف کرد بنظرم بهتره یک تعریف کلاسیک از آرایه بگیم چون بعدا خواهید دید که این نوع داده‌ای یکی از پرمصرف‌ترین انواع داده در برنامه‌نویسی هست:

درعلوم کامپیوتر «ساختمان داده آرایه» یا به عبارت ساده‌تر «آرایه» یک ساختمان داده متشکل از مجموعه‌ای از المان‌ها(مقدار یا متغیر) است که هر یک با استفاده از حداقل یک شاخص یا اندیس قابل شناسایی هستند. [...] ساده‌ترین نوع آرایه، آرایه خطی است که در اصطلاح به آن آرایه تک بعدی نیز گفته می‌شود. (Wikipedia)

خوب با این توضیح هممون متوجه شدم که به زبان ساده آرایه یک لیست از اقلام اطلاعاتی هست که با استفاده از یک اندیس قابل بازیابی و استفاده هستند. برگردیم به کاتلین و ببینیم که آرایه در این زبان چجوری تعریف میشه:

arrayOf()

در کاتلین دو روش اصلی برای تعریف آرایه وجود داره. استفاده از تابع کمکی ()arrayOfو یا استفاده از سازنده* ()Array است. در ادامه با استفاده از مثال هر یک از این روش‌ها را توضیح میدیم:

val myArray = arrayOf(4, 5, 7, 3)

در کد بالا یک آرایه به استفاده از تابع کمکی ؟ شامل ۴ نوع صحیح ساختیم. اما بر اساس تعریف ما باید بتونیم با استفاده از یک اندیس به هر کدام از المان‌های آرایه دسترسی پیدا کنیم. برای این منظور میتونیم از کد زیر استفاده کنیم:

myArray[2] // 7

خوب چندتا نکته در مورد آرایه ها وجود داره:

اول اینکه شمار ایندکس از ۰ شروع میشه. مثل اغلب زبان‌های برنامه نویسی. یعنی اندیس ۰ میشه اولین المان، اندیس ۱ دومین المان و به همین ترتیب تا انتها.

تصویر از اینجا!
تصویر از اینجا!

نکته دوم اینکه شما میتونید آرایه‌ای از انواع گوناگون داشته باشید:

val myArray = arrayOf(4, 5, 7, 3, "Chike", false)

همونطور که در این آرایه میبینید اعداد صحیح، رشته و بولین در کنار هم در یک لیست آمدند که قابلیت بسیار جذابیه و کاربردی. اما اگر بخواهید میتونید با استفاده از نوع خاصی از تعریف آرایه استفاده از یک نوع داده‌ای خاص را اجباری کنیم. برای مثال:

val myArray3 = arrayOf<Int>(4, 5, 7, 3, "Chike", false) // کامپایل نمی شود
val myArray4 = arrayOf<Int>(1, 2, 3, 4) // کامپایل می شود 

val myArray5 = intArrayOf(4, 5, 7, 3, "Chike", false)  // کامپایل نمی شود
val myArray6 = intArrayOf(1, 2, 3, 4) // کامپایل می شود 

val myArray7: IntArray = intArrayOf(4, 5, 7, 3, "Chike", false)  // کامپایل نمی شود
val myArray8: IntArray = intArrayOf(1, 2, 3, 4) // کامپایل می شود 

علاوه بر مواردی که گفته شد توابع دیگری نیز برای ساخت آرایه‌های با نوع خاص وجود دارند که در زیر نام آنها آماده است:

charArrayOf()
booleanArrayOf()
longArrayOf()
doubleArrayOf()
shortArrayOf()
byteArrayOf()

اما شاید براتون جالب باشه که بدونید پشت صحنه ساختن آرایه چجوریه. در واقع با صدا کردن هر کدام از این توابع ما یک آرایه از نوع‌های اولیه پایه آن‌ها یا Basic Primitive Type میسازیم. به عبارت دیگر تابع ()intArrayOf به داده پایه‌ای جاوا از نوع صحیح []int کامپایل می شود و در نتیجه تنها می‌توان در آن المان‌های از نوع صحیح ریخت. این موضوع برای بقیه این توابع کمکی هم صادق است. یعنی اگر بخواهیم با جاوا مقایسه کنیم میتونیم مثال زیر را در نظر بگیریم:

double[] myList = {34.1, 10.2, 5.8}; // تعریف آرایه در زبان جاوا
val myList = doubleArrayOf(34.1, 10.2, 5.8) //تعریف آرایه از نوع دابل در کاتلین
// این دو تعریف معادل هم در زبان جاوا و کاتلین هستند

آرایه‌ها در برنامه‌نویسی جزو ساختمان‌داده‌های بسیار عالی و کاربردی هستند. حتما خوب ازشون تمرین حل کنید و سعی کنید خودتون را باهاش به چالش بکشید.

سازنده* ()Array

برخلاف تابع ()arrayOf برای ساخت آرایه که یک پرانتز میذاشتیم و مقادیر خودمون را داخلش میریختیم، استفاده از ()Array نیازمند یک «اندازه - Size» و یک «تابع بی‌نام - lambda function یا anonymous function » است. ما بعدا در مورد «توابع بی‌نام» صحبت خواهیم کرد ولی در حال حاضر بهش به چشم یک تابع یک خطی بی‌نام نگاه بکنید که میتونه هرجایی از برنامه ظاهر بشه. مثال زیر کم گویاتر میکنه:

val numbersArray = Array(5, { i -> i * 2 })

در کد بالا ما عدد ۵ را به عنوان اندازه آرایه در پارامتر اول قرار دادیم. به این معنا که اندازه آرایه ما ۵ المان خواهد بود. پارامتر دوم ()Array یک تابع بی‌نام است که اندیس آرایه را یکی یکی دریافت کرده و یک مقدار در آن اندیس بر اساس فرمول i * 2 قرار می دهد. بنابراین در مثال بالا ما آرایه‌ای ساختیم که حاوی اعداد ۰ و ۲ و ۴ و ۶ و ۸ است. یادآوری میکنم که اندیس از ۰ شروع میشه.

بذارید یک مثال دیگه بزنیم یکم بیشتر جا بیفته:

val asc = Array(5, { i -> (i * i).toString() })

در مثال بالا مجددا یک آرایه ساخته شده با اندازه ۵ که مقادیر آن با استفاده از فرمول بی‌نام i->i*i ایجاد شده. به عبارت دیگه هربار اندیس گرفته میشه در خودش ضرب میشه و مقدارش به عنوان المان آرایه داخل آرایه قرار داده میشه. پس خروجی ما خواهد شد یک آرایه متشکل از ۰ و ۱ و ۴ و ۹ و ۱۶. اما این تابع بی‌نام یک تابع دیگه را هم در خودش داره و اون تابع ()toString هست.

اگر خاطرتون باشه در قسمت ۲ آموزش ما راجع به این توابع برای گسترده‌سازی نوع‌ها صحبت کردیم. مثلا گفتیم برای اینکه یک عدد صحیح را داخل یک متغیر از نوع Long قرار بدیم لازمه که از تابع ()toLong استفاده کنیم. اینجا هم همین اتفاق میفته. به عبارت دیگه هر المانی که تولید میشه - یادمون هست که همه نوع‌های داده‌ای شی هستند - تابع ()toString روش اجرا میشه و اون المان را تبدیل به رشته میکنه. در نتیجه خروجی ما برای آرایه asc مقادیر زیر خواهد بود:

// Array <String> ["0", "1", "4", "9", "16"]

خوب این هم از انواع داده‌ای پایه کاتلین. و اما چند تا نکته بسیار لازم و حیاتی:

۱. وقتی میخواین کد بزنین - که البته احتمالا خودتون متوجه شدید - لازمه که کدتون را داخل تابع main بنویسید. اینجوری:

fun main(args: Array<String>) {
    val i = 10
    println("i = $i") // prints "i = 10"
}

من در قسمت قبل فراموش کردم این را بگم که کد را باید داخل این تابع اجرا کنید.

۲. تمام حیات برنامه‌نویسی ما با همین نوع‌ها خواهد گذشت. یک عالمه چیز یاد میگیریم که وقتی ریز بشیم به هر گذاره اون میبینیم که داریم از یکی از این نوع‌ها و تبدیل‌ها و عملگرها استفاده میکنیم. پس بی اندازه حیاتیه که برای این دو قسمت وقت بگذارید. تا میتونین مثال حل کنید و منابع دیگه را مطالعه کنید. ما اینجا داریم آشنا میشیم با این زبان و نکات کلیدی که من به ذهنم میرسه را میگم ولی این اصلا معنیش این نیست که با خوندن این مطالب شما یک برنامه‌نویس حرفه‌ای میشید. تنها چیزی که شما را حرفه‌ای می‌کنه مطالعه، مطالعه، مطالعه، تمرین، تمرین، تمرین! همین و بس.

۳. اگر سوالی براتون پیش میاد حتما اینجا بپرسین - من سعی میکنم در حد توان و دانشم پاسخگو باشم - و یا با دوستانتون راجع بهش صحبت کنید و حتی اگر لازمه بازهم مطالعه کنید و مسئله حل کنید.

همین. میبینمتون :)

* در مورد سازنده‌ها یا constructor ها وقتی وارد بحث شی‌گرایی بشیم صحبت میکنیم. برای درکش در این مقطع اینقدر کافیه بدونید که سازند‌ه‌ها کارشون مقداردهی اولیه یک شی از یک کلاس است.