چهار Composite Type در زبان Go

در زبان گولنگ برای نگهداری مقادیر پیچیده در مموری ۴ نوع دیتاتایپ داریم که از نوع Composite تایپ هستن، به این صورت که هر کدوم از اینا مجموعه‌ای از مقادیر Basic تایپ رو میتونن در خودشون به نحو‌ مختلف نگهداری کنن. بریم که باهاشون آشنا بشیم:


آپدیت:‌ گویا یه اشتباهی کردم در مورد Map و Slice که گفته بودم با Reference فرستاده میشن که دوست عزیزمون اشاره کرد اشتباهه و تغییراتی در مقاله دادم. ممنون از مهدی عزیز.

نکته: در زبان Go شمارش از ۰ شروع می‌شود. مثلا خانه‌های آرایه از ایندکس ۰ شروع می‌شوند:


دسته اول:‌ Array

یک آرایه به دنباله‌ای از المان‌هایی اشاره داره که دارای یک نوع یکسان هستن و اندازه آرایه کاملا مشخص بوده به طوری که این مقدار در زمان کامپایل باید ثابت باشد.

نحوه تعریف آرایه میتواند به صورت یکی از اشکال زیر باشد:

تعریف متغیر و استفاده در طول برنامه
تعریف متغیر و استفاده در طول برنامه


مقدار دهی در زمان تعریف متغیر
مقدار دهی در زمان تعریف متغیر


مقدار دهی در زمان تعریف به صورت ضمنی
مقدار دهی در زمان تعریف به صورت ضمنی


نکته: جهت راحتی کار میشه به جای عدد لیترال از ... استفاده کرد با این قابلیت در زمان کامپایل سایز آرایه توسط مقادیر Initialize شده حساب می‌شود:

به جای ۳ میتوان ... استفاده کرد
به جای ۳ میتوان ... استفاده کرد


نکته‌ای که باید بهش اشاره کرد اینه که مقدار دادن به خونه‌ای از آرایه که وجود نداره باعث Panic میشه. مثلا کد زیر با خطا مواجه میشه:

چون خونه چهارم (همون ایندکس سه) وجود نداره با خطا مواجه میشیم.
چون خونه چهارم (همون ایندکس سه) وجود نداره با خطا مواجه میشیم.


برای خوندن از آرایه میشه به شکل زیر اقدام کرد:


نکته مهم دیگه اینه که آرایه ها فقط با نوع و سایز یکسان قابل مقایسه هستن و در غیر اینصورت برنامه امکان اینو نداره که کامپایل بشه. مثلا این دو تا آرایه چون عملا از نوع متفاوتی هستن (یکی سه خونه جا داره و دیگری چهار خونه) به همین دلیل قابل مقایسه با == نیستن

var x [3]int
var y [4]int


آرایه به صورت پیش‌فرض By Value به فانکشن‌ها و متدها فرستاده می‌شود و تغییری در آرایه به صورت پیش‌فرض انجام نمی‌شود:

خروجی:

Before Mutation:  [Go PHP .NET]
After Mutation:  [Go PHP .NET]


دسته دوم:‌ Slice

نکته اولیه و مهم در مورد Sliceها این است که یه شکل متفاوتی از آرایه هستن که از لحاظ کارکرد با آرایه تفاوت‌هایی دارن. برای مثال سایز یک آرایه مشخص است و در طول زمان اجرای برنامه قابل تغییر نیست اما سایز Slice می‌تواند در طول برنامه متغیر باشه. (همین قضیه باعث میشه که در مورد پرفورمنس بیشتر دقت کنیم).

تعریف و  سپس مقدار‌دهی در طول برنامه
تعریف و سپس مقدار‌دهی در طول برنامه


مقدار‌دهی در زمان تعریف
مقدار‌دهی در زمان تعریف


مقدار‌دهی به صورت ضمنی
مقدار‌دهی به صورت ضمنی


تعریف با کمک فانکشن make
تعریف با کمک فانکشن make


یک Slice در حقیقت متشکل از سه جز زیر می‌باشد:

Pointer | Len | Capacity

پوینتر (Pointer) اشاره داره به نقطه شروع اسلایس در مموری

اندازه (Len) اشاره داره به تعداد آیتم‌های داخل اسلایس (که انتخاب شدن)

ظرفیت (Cap) اشاره داره به تعداد کل آیتم‌های اسلایس (ظرفیتی که تعیین شده)

متغیر quarter2 از اسلایس months خانه‌های ۴ تا ۶ را انتخاب می‌کند.
متغیر quarter2 از اسلایس months خانه‌های ۴ تا ۶ را انتخاب می‌کند.


نکته:‌در خواندن اسلایس [n:m] یعنی از خانه n تا سر خانه m (خانه m حساب نمی‌شود)

نکته مهمی که باید بهش توجه داشت این است که با هر بار Append شدن به Slice اگر از تعداد Cap فراتر بریم رانر گو در پشت صحنه یه آرایه جدیدی میسازه با سایزی دو برابر Cap قبلی. پس باید توجه داشته باشیم این عمل ممکنه روی پرفورمنس تاثیر بدی بزاره اگر بدونیم که تعداد خونه‌ها دقیقا (یا حداقل حدودا)‌ چقدر هست و در زمان تعریف Slice اونا رو تعریف کنیم به مشکل نمی‌خوریم.

ظرفیت ۲۰ خونه تعریف شده
ظرفیت ۲۰ خونه تعریف شده


در طول اجرای برنامه Cap تغییری نمی‌کنه
در طول اجرای برنامه Cap تغییری نمی‌کنه


ولی اگر تعداد Cap رو دقیقا یا حدودی ندونیم و از صفر شروع کنیم و این مقدار زیاد باشه هر بار دو برابر میشه:

ظرفیتی تعریف نشده
ظرفیتی تعریف نشده


در طول اجرای برنامه ۶ بار ظرفیت افزایش پیدا کرده (یعنی هر بار آرایه جدید و Allocation/De-allocation)
در طول اجرای برنامه ۶ بار ظرفیت افزایش پیدا کرده (یعنی هر بار آرایه جدید و Allocation/De-allocation)


اسلایس‌ به صورت پیش‌فرض By value به فانکشن‌ها و متدها فرستاده می‌شود ولی تغییر بر روی اسلایس به صورت پیش‌فرض انجام می‌شه به دلیل اینکه مقدار اصلی و هم کپی شده Pointer که دارن به یک backing-array اشاره میکنه.

خروجی:

Before Mutation:  [Ross Monica Rachel Phoebe Chandler Joey]
After Mutation:  [ROSS MONICA RACHEL PHOEBE CHANDLER JOEY]


دسته سوم: Map

ساختار Hash Table در اکثر زبان‌ها وجود داره و یکی از کاربردی‌ترین ساختار داده‌هاست. یک مجموعه نامرتب از Key/Value که میشه داخلش دیتا گذاشت، ازش دیتا خوند، حذف کرد یا تغییرات داد.

انواع تعریف‌ها:

تعریف با استفاده از فانکشن make
تعریف با استفاده از فانکشن make


تعریف و مقداردهی اولیه
تعریف و مقداردهی اولیه


مقدار‌دهی به صورت ضمنی
مقدار‌دهی به صورت ضمنی


نحوه خوندن یک مقدار از Map:

fmt.Println(scores["sina"]) // 12.5

نحوه حذف و اضافه به Map:

scores := map[string]float32{
   "sina":   12.5,
   "romina": 20,
}

scores["reza"] = 19
delete(scores, "romina")

fmt.Println(scores) // Output: map[reza:19 sina:12.5]


مپ‌ به صورت پیش‌فرض By Value به فانکشن‌ها و متدها فرستاده می‌شود ولی تغییر بر روی مپ به صورت پیش‌فرض انجام می‌شود به دلیل اینکه مقدار اصلی و هم کپی شده Pointer که دارن به یک backing-array اشاره میکنه.

خروجی:

Before Mutation: map[reza:19 romina:20 sina:12.5]
After Mutation: map[reza:20 romina:20 sina:13.5]


دسته چهارم: Struct

یک struct به مجموعه‌ای از فیلدهای مرتب با هم که یک Unit را تشکیل می‌دهند اشاره دارد. به هر عضو از این Unit یک Field گفته می‌شود

نحوه تعریف:

تعریف و مقداردهی در طول برنامه
تعریف و مقداردهی در طول برنامه


مقداردهی در زمان تعریف
مقداردهی در زمان تعریف


تعریف به صورت ضمنی
تعریف به صورت ضمنی


می‌توان Structهای Nested (تو در تو) تعریف کرد. برای مثال:

استراکت Address داخل Person استفاده شده است.
استراکت Address داخل Person استفاده شده است.


نحوه استفاده از Nested Structs
نحوه استفاده از Nested Structs


جهت اطلاعات بیشتر از استراکت‌های Nested میتونید مقاله آقای حسینی‌راد در مورد ارث‌بری در گولنگ رو مطالعه کنید.

نکته:‌ امکان اینکه یک Struct در خودش به صورت Nest استفاده شود وجود ندارد ولی پوینتر آن می‌تواند Nest شود. مثلا:

یک نمونه از Nested Structs به صورت Pointer
یک نمونه از Nested Structs به صورت Pointer


همونطور که می‌بینید این دو نقطه با هم برابرند
همونطور که می‌بینید این دو نقطه با هم برابرند

نکات مهم:

  • در تعریف Literal Type Structها ترتیب مهم می‌باشد. (ترتیب به صورت چیزی که برای استراکت تعریف شده)
  • عمدتا استفاده از Literal Type Structها در Scope بزرگتر از پکیج توصیه نمی‌شود. (خوانایی کمی دارد)
  • هر Field از Struct شامل قوانین کلی Visibility در گولنگ می‌شوند.


استراکت به صورت پیش‌فرض By Value به فانکشن‌ها و متدها فرستاده می‌شود و تغییری در استراکت به صورت پیش‌فرض انجام نمی‌شود:


خروجی:

Before Mutation:  {1 2}
After Mutation:  {1 2}



نکات خیلی مهمی در مورد این چهار Composite Type وجود داره که فرصت نشده اینجا عنوان بشه. پیشنهاد می‌کنم حتما کتاب The Go Programming Language رو بخونید تا اطلاعات بیشتری کسب کنید