مقدمه#
همیشه یاد گرفتن یه زبون جدید چالشهای خاصی داره. یادم میاد زمانی که شروع کرده بودم شی گرایی پایتون رو یاد میگرفتم و با جاوا مقایسش میکردم، همه چیزش خیلی عجیب بود. الان هم که دارم go رو یاد میگیرم یه چیزاییش عجیبه ولی میدونم زمان بگذره همه اینها عادی و بدیهی میشه.
در این مطلب هم میخوام در مورد تابع append بنویسم.
تصور اشتباه از این تابع#
همونطور که میدونید این تابع یک slice به همراه دادههای جدیدی که قراره به اسلایس اضافه بشن رو ورودی میگیره و بعد یک اسلایس جدید بر میگردونه.
ولی دقیقا تصور اشتباه من همین جا بود: اسلایس جدید!
من همیشه سعی میکنم توابع و کلاسهایی که در هر زبونی استفاده میکنم رو خوب بشناسم. بدونم اون پشتش چه اتفاقی داره میافته؛ صرفا یه مصرفکننده نباشم. به خاطر همین سعی کردم اتفاقی که پشت این تابع در حال رخ دادن هست رو با زبون c++ تصور کنم. میگفتم خب اگر قرار بود این رو توی c++ پیادهسازی کنم، باید اول یه حافظه جدید با یه سایز اضافهتر میساختم (new)، بعد تمام المنتهای آرایه قبلی رو کپی میکردم و در آخر حافظه قبلی رو delete میکردم.
ولی اشتباه من همین جا بود که فکر میکردم capacity همزمان با length یه دونه یه دونه زیاد میشه. ولی در واقع capacity جلوتر حرکت میکنه و احتمالا 1.5 برابر میشه. خب در چنین حالتی سر آدم که درد نمیکنه که اگر capacity کافی برای اضافه کردن المنت جدید وجود داره بیاد کلا یه آرایه جدید بسازه و همهی اون المنتهای قبلی رو هم کپی کنه.
کد#
مثلا کد زیر همین اتفاق رو خیلی جالب داره نشون میده:
package main
import "fmt"
func main() {
s := make([]int, 0, 5)
s = append(s, []int{1, 2, 3, 4}...)
a := append(s, 5)
fmt.Println(a)
b := append(s, 6)
fmt.Println(b)
fmt.Println(a)
}
خروجی:
[1 2 3 4 5]
[1 2 3 4 6]
[1 2 3 4 6]
اگر همین s سایزش 4 بود به جای 5 خروجی این میشد:
[1 2 3 4 5]
[1 2 3 4 6]
[1 2 3 4 5]
پس همیشه از اسلایس قبلی کپی گرفته نمیشه و یه اسلایس جدید ساخته بشه.