Golangبرنامه نویسی

تفاوت Pointer در Go و Reference در زبان‌های دیگر؛ هرآنچه باید بدانید

تفاوت Pointer در Go و Reference در زبان‌های دیگر

اگر تازه وارد دنیای Go شده باشید، احتمالاً بعد از دیدن علامت * و & کمی گیج شده‌اید.

شاید هم قبلاً با زبان‌هایی مثل Java، Python یا C# کار کرده باشید و با خودتان گفته باشید:

مگر این زبان‌ها هم Reference ندارند؟ پس چرا Go دوباره Pointer معرفی کرده است؟

این سؤال کاملاً طبیعی است.

در واقع یکی از مهم‌ترین تفاوت‌های Go با بسیاری از زبان‌های مدرن، نحوه مدیریت حافظه است.

در این مقاله قصد داریم به صورت کاملاً ساده و عملی یاد بگیریم:

  • Pointer چیست؟
  • Reference چیست؟
  • تفاوت این دو چیست؟
  • چرا Go از Pointer استفاده می‌کند؟
  • چرا Go مثل C خطرناک نیست؟
  • در چه مواقعی باید از Pointer استفاده کنیم؟

در پایان این مقاله دیگر دیدن *person یا &value برایتان ترسناک نخواهد بود.

Pointer چیست؟

کامپیوتر تمام اطلاعات را داخل حافظه (RAM) ذخیره می‌کند.

فرض کنید این متغیر را داریم:

x := 10

در ظاهر فقط عدد ۱۰ را ذخیره کرده‌ایم.

اما در واقع اتفاق دیگری افتاده است.

کامپیوتر جایی در حافظه را انتخاب کرده و عدد ۱۰ را داخل آن قرار داده است.

اینجا:

  • مقدار برابر 10 است.
  • آدرس حافظه برابر 1000 است.

البته این آدرس فقط برای مثال است.

در دنیای واقعی آدرس‌ها چیزی شبیه این هستند:

0xc000012090

Pointer دقیقا چیست؟

Pointer یعنی: متغیری که به جای نگهداری مقدار، آدرس آن مقدار را نگهداری می‌کند.

مثلاً:

x := 10
p := &x

حالا:

x = 10
p = آدرس x

اگر چاپ کنیم:

fmt.Println(x)

خروجی:

10

اگر چاپ کنیم:

fmt.Println(&x)

خروجی چیزی شبیه:

0xc000012090

و اگر چاپ کنیم:

fmt.Println(p)

دقیقاً همان آدرس را خواهیم دید.

علامت & در Go چیست؟

علامت & یعنی:

آدرس این متغیر را به من بده.

مثال:

package main

import "fmt"

func main() {

    age := 30

    fmt.Println(&age)

}

خروجی:

0xc000012090

البته آدرس شما متفاوت خواهد بود.

علامت * در Go چیست؟

علامت * دو کاربرد دارد.

۱- تعریف Pointer

var p *int

یعنی:

p
یک Pointer به int است.

۲- دسترسی به مقدار

فرض کنید:

x := 50

p := &x

اگر بنویسیم:

fmt.Println(*p)

خروجی:

50

یعنی:

از روی آدرس برو مقدار واقعی را بخوان.

یک مثال واقعی

package main

import "fmt"

func main() {

    x := 20

    p := &x

    fmt.Println(x)

    fmt.Println(p)

    fmt.Println(*p)

}

خروجی تقریباً:

20

0xc000012090

20

اولی مقدار است.

دومی آدرس است.

سومی دوباره مقدار واقعی است.

تغییر مقدار از طریق Pointer

جذاب‌ترین بخش Pointer همین است.

package main

import "fmt"

func main() {

    x := 10

    p := &x

    *p = 100

    fmt.Println(x)

}
100

ما مستقیماً متغیر x را تغییر ندادیم.

بلکه از طریق آدرس حافظه مقدار آن را عوض کردیم.

تصویر ذهنی Pointer

فرض کنید خانه‌ای دارید.

خانه:

آدرس:
خیابان آزادی
پلاک ۲۰

داخل خانه:

تلویزیون

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

Pointer دقیقاً همین کار را انجام می‌دهد.

Pointer خودِ داده نیست.

بلکه آدرس داده است.

چرا اصلاً Pointer ساخته شد؟

فرض کنید یک Struct بسیار بزرگ دارید.

type User struct {

    Name string

    Email string

    Address string

    Phone string

    ...
}

اگر هر بار این Struct را به یک تابع ارسال کنیم، کل اطلاعات کپی می‌شود.

اگر این Struct چندین کیلوبایت حجم داشته باشد، برنامه به مرور کندتر خواهد شد.

اما اگر فقط Pointer ارسال کنیم:

func UpdateUser(u *User)

به جای کپی کردن کل Struct، فقط یک آدرس حافظه (که معمولاً ۸ بایت روی سیستم‌های ۶۴ بیتی است) ارسال می‌شود.

نتیجه:

  • مصرف حافظه کمتر
  • سرعت بیشتر
  • کپی کمتر
  • عملکرد بهتر

در بخش دوم وارد تفاوت‌های عمیق Pointer در Go با Reference در Java، Python، C#، C++ و Rust می‌شویم، همچنین توضیح می‌دهم چرا Pointerهای Go بسیار ایمن‌تر از Pointerهای زبان C هستند و با مثال‌های متعدد، رایج‌ترین اشتباهات برنامه‌نویسان را بررسی می‌کنیم.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

20 − 13 =

دکمه بازگشت به بالا