un homme normal :)
پوینترها در زبان گو
منبع :)
پوینترها یک ابزار اولیه اما خیلی قدرتمند هستند که معمولا به عنوان یک چیز سخت و مزخرف ازش یاد میشه موقع برنامه نویسی !
پوینترها ساخته شدند برای تسریع و کمک به جهت جابجایی دیتا در کدها بدون اینکه نیاز باشه از یک متغیر کپی گرفته یا ساخته بشه، خوب که چی بشه دوست من !؟
جوابش سادست => مصرف کمتر مموری :)
در برخی زبان ها این concept کلا دوری شده ازش، بعضی ها هم تقریبی سعی کردن مدیریتش کنند اما در زبان گو پوینترها به معنای واقعی یک ابزار خوب برای بهبود performance ازش استفاده میشه.
خوب بریم سراغ این دوستمون پوینتر
یک پوینتر متغیری هستش که اشاره میکنه به محل خاصی از مموری که مقداری در اونجا ذخیره شده، بنابراین شما یک متغیری را ایجاد کرده و مقداری در این قرار میدهید و خیلی ساده به جای اینکه مقدار واقعی اونو بخواین بخونین از آدرسش می تونین استفاده کنین.
به کد زیر توجه کنید
package main
import (
"fmt"
)
func main ( ) {
myString := "a string value"
fmt.Println(myString)
myInteger := 1
fmt.Println(myInteger)
}
ما دوتا متغیر ساختیم و پرینتش کردین ! هیچ کار خاصی و عجیبی هم انجام ندادیم هنوز
اگر ما یک متغیری را به یک تابعی پاس بدیم، زبان میاد یک کپی از اونا میگیره و در داخل تایع ازش استفاده میکنه مثل این:
func myFunction(s string){
fmt.Println(s)
}func main() {
myOtherString := "another message here"
myFunction(myOtherString)
}
وقتیکه تابع myFunction مقدار رشته ای s دریافت میکنه و پرینتش میکنه روی کنسول گولنک (و اکثر زبان ها)
یک کپی از مقداری که ارسال شده درست میکنه در داخل تابع، وقتیکه تابع کارش به آخر میرسه اینجاست که Garbage collector میاد به سراغ این متغیر ها.
خوب حالا همون کارو با پوینتر انجامش میدیم
func myFunctionWithPointers(s *string){
fmt.Println(s, *s)
}func main() {
myOtherString := "another message here"
myFunctionWithPointers(&myOtherString)
}
خوب ایندفعه ما یکسری کارکتر جدید و عجیب داریم که اضافه شدن مثل * و &
در داخل () main علامت & داره به زبان گو اعلام میکنه که این یک آدرسی هستش از یک متغیر، حالا گولنگ میاد میره دیتا تایپ و جایی که این مقدار در مموری قرار گرفته پیدا میکنه و نهایتا آدرس ارسال میشه به فانکشن
در تابع myFunctionWithPointers علامت ستاره (*) داره به گو میگه که من دارم یک آدرس دریافت میکنم با دیتا تایپ رشته یا string. چیزی که مهمه اینکه پوینترها به تایپ نیاز دارند، پوینتر به یک عدد، پوینتر به یک رشته و یا پوینتر به یک slice و و و
بعد از اینکه دیتاتایپ مشخص شد، اون در بدنه تابع ما داریم به زبان گو میگیم که s یک پوینتر است و مقداری که در اون جا ذخیره شده برای ما برمیگرده
خوب حالا بریم سراغ structure
type myStructure struct {
Name string
}
func main() {
structure := myStructure{Name: "MyName"}
structureFunction(&structure)
}
func structureFunction(e *myStructure){
fmt.Println(e, *e, e.Name, (e).Name)
}
اولا ما مقدار استراکچر داریم پرینت میکنیم اما با مقدار & میبینیم که نشون داده میشه خوب این یعنی اینکه، یک پوینتر است به استراکچر با آن محتوا
وقتی که ما از ستاره استفاده میکنیم،ما داریم میگیم که مقدارها گرفته بشن بنابراین {MyName} که یک استراکچر فرمت شده است را دریافت میکنیم
وقتیکه شما دسترسی پیدا میکنی به یک فیلد از پوینتر استارکچر، گو میاد دقیقا به شما مقدار برمیگردونه.
مورد آخر هم دقیقا داره همون کار انجام میده که دیگه بهش نیازی نیست و همون روش قبل کفایت میکنه
حالا فرض کنین ما میخوایم متغیر آپدیت کنیم داخل تابع
func main() {
sliceValues := []string{"a","b", "c"}
appendToSlice(sliceValues)
fmt.Println(sliceValues)
}
func appendToSlice(c []string){
fmt.Println(c)
c = append(c, "d")
}
چی شده !!؟
عدم استفاده از این دوستمون پوینتر نتیجش شد اینکه آپدیتی شکل نگرفت چون خارج از تابع بوده!
func main() {
sliceValues := []string{"a","b", "c"}
appendToSliceWithPointer(&sliceValues)
fmt.Println(sliceValues)
}
func appendToSliceWithPointer(c *[]string){
fmt.Println(*c)
*c = append(*c, "d")
}
خوب بنظر ساده میاد،حالا چه اتفاقی می افته اگر ما maps داشته باشیم !؟
func main() {
myMap := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
maps(myMap)
fmt.Println(myMap)
}
func maps(c map[string]int){
c["d"] = 4
fmt.Println(c)
}
توی این مثال، تغییر در map رفتارش متفاوت بود !
آپدیت شدش ! داخل فانکشن به مقدار original map ! مگه داریم!؟
چرا چون که خود maps در واقع پونتر هستند و در نتیجه ما نیازی نداریم کار خاصی با map ها بکنیم
یه چیز کوچولو دیگه هم هست که باید بهش توجه کرد
حدس بزنین ! تابع ها هم همچنین پوینتر هستند !
func main() {
f := &myFunc
}
func myFunc(){
}
خوب این کار نمیکنه از اونجایی که myFunc خودش پوینتر هستش !
حالا از اونجایی که پوینتر عملا یک دیتاتایپ هستش، ورودی های تابع رفتار مشابهی دارند
type myStructure struct {
Name string
}
func (ms myStructure) noPointer(){
ms.Name = "xxxx"
fmt.Println(ms.Name)
}
func (ms *myStructure) withPointer(){
ms.Name = "yyyy"
fmt.Println(ms.Name)
}
func main() {
theStructure := myStructure{Name: "MyName"}
theStructure.noPointer()
fmt.Println(theStructure.Name)
theStructure.withPointer()
fmt.Println(theStructure.Name)
}
وقتیکه ورودی به عنوان یک structure استفاده میشه، مقدار تاثیری روش گذاشته نمیشه خارج از اون تابع
وقتیکه ورودی مدیریت میشه به عنوان یک پوینتر، مقدار رفتارش همونی میشه که ما انتظار داریم
اما اگر شما بخواین متد مستقیما صدا بزنین:
func (ms myStructure) noPointer(){
fmt.Println("Not a pointer")
}
func (ms *myStructure) withPointer(){
fmt.Println("a pointer")
}
func main() {
myStructure{}.noPointer()
myStructure{}.withPointer()
}
دومین تابع یعنی withPointer خیلی ساده اجرا نمیشه، withPointer یک ورودی لینک شده هستش به pointer to myStructure دیتا تایپ و {} myStructure یک instance از myStructure
ما میتونیم درستش کنیم با instancing structure و بعدش اضافه کردن یک پوینتر به اون
type myStructure struct {
Name string
}
func (ms myStructure) noPointer(){
fmt.Println("Not a pointer")
}
func (ms *myStructure) withPointer(){
fmt.Println("a pointer")
}
func main() {
myStructure{}.noPointer()
ms := &myStructure{}
ms.withPointer()
}
خوب این تمام چیزیه که شما نیاز دارین بهش برای شروع کار با پوینتر ها :)
سعی میکنم یه مقاله دیگه پیدا کنم یکم مباحث advance تری بگه. حالا برای شروع همچین بد هم نیستش :)
مرسی
مطلبی دیگر از این انتشارات
چرا خوبه که Golang رو یاد بگیریم؟
مطلبی دیگر از این انتشارات
اصول SOLID در زبان Go (قسمت صفرم-مقدمه )
مطلبی دیگر از این انتشارات
ارثبری در Go؟ آشنایی با Composition در Golang.