& و*
يحمل المؤشر (pointer) عنوان قيمة في الذاكرة. يعطيك &x مؤشرًا إلى x. ويتبع *p المؤشر إلى القيمة.
للنجمة دوران. في النوع (*int) تعني "مؤشرًا إلى int". وفي التعبير (*p) تعني "القيمة التي يشير إليها p". طباعة p نفسه تعرض عنوانًا مثل 0xc000012345، يختلف بين التشغيلات.
لماذا توجد المؤشرات: المشاركة بدل النسخ
تمرّر Go كل شيء بالقيمة. تحصل الدالة على نسخة من كل وسيط، فلا تستطيع تغيير متغيّر المستدعي. تمرير مؤشر ينسخ العنوان بدلًا من ذلك، فيصل الطرفان إلى القيمة نفسها.
لا تملك Go مراجع بمعنى C++. "التمرير بالمرجع" في Go يعني تمرير مؤشر بالقيمة.
المؤشرات إلى البنى
مؤشرات البنى أشيع المؤشرات في شيفرات Go. الوصول إلى الحقول يلغي المرجعية تلقائيًا: p.Name تعني (*p).Name.
تتبع التوابع المنطق نفسه: مستقبِل المؤشر يتيح للتابع تغيير البنية. تشرح صفحة التوابع الاختيار بين مستقبِل القيمة ومستقبِل المؤشر.
new
تحجز new(T) قيمة T صفرية وتعيد عنوانها. هذا مثل تعريف متغيّر وأخذ عنوانه.
p := new(int) // *int pointing at 0
var x int
q := &x // same thing, two lines
a := new(Account) // same as &Account{}
عمليًا &T{...} أشيع مع البنى لأنها تتيح ضبط الحقول في التعبير نفسه. وnew مفيدة للمؤشرات إلى الأنواع الأساسية. لا تخلطها بـ make، التي تبني الشرائح والخرائط والقنوات وتعيدها جاهزة للاستخدام، لا كمؤشرات.
إعادة مؤشر إلى متغيّر محلي آمنة
في C تُعدّ إعادة عنوان متغيّر محلي خطأ. أما في Go فهي شيفرة عادية.
يقرّر تحليل الهروب (escape analysis) في المترجم هل تعيش القيمة في المكدّس أم في الكومة. لا تختار أنت أبدًا، ولا تحرّر الذاكرة أبدًا: يستعيدها جامع النفايات عندما لا يشير إليها شيء. يطبع go build -gcflags=-m هذه القرارات إن كنت فضوليًا.
المؤشرات nil
القيمة الصفرية لأي نوع مؤشر هي nil. وإلغاء مرجعية nil يسبّب panic.
المخرجات:
1
2
true
recovered: runtime error: invalid memory address or nil pointer dereference
دون recover كان البرنامج سينهار بتلك الرسالة وأثر مكدّس يبدأ بـ panic: runtime error: invalid memory address or nil pointer dereference و[signal SIGSEGV: segmentation violation ...]. المصادر المعتادة:
- متغيّر مؤشر أو حقل بنية لم يُضبط قط
m[key]علىmap[string]*Tلمفتاح مفقود، الذي يعيدnil- تجاهل خطأ:
f, _ := os.Open(path)تتركfمساويًا لـ nil عند الفشل - تابع بمستقبِل مؤشر يُستدعى على مؤشر nil، ثم يقرأ حقلًا
استدعاء تابع على مؤشر nil مسموح بحد ذاته. لا تحدث panic إلا عندما يلمس التابع حقول المستقبِل.
متى تستخدم مؤشرًا
استخدم مؤشرًا عندما:
- يجب أن تغيّر دالة أو تابع قيمة المستدعي
- تكون القيمة بنية كبيرة تُمرَّر كثيرًا
- لا يجوز نسخ النوع (يحتوي
sync.Mutex، أو يمثّل موردًا فريدًا مثل اتصال) - تحتاج التعبير عن "لا قيمة" بـ
nil، مثلًا حقل اختياري في بنية JSON (يميّز*intبين "غائب" و0)
لا تستخدم مؤشرًا عندما:
- تكون القيمة صغيرة وللقراءة فقط (
time.Time، أوPoint) - يكون النوع أصلًا قيمة شبيهة بالمرجع: الشرائح والخرائط والقنوات والدوال والواجهات نادرًا ما تحتاج
*أمامها. و*[]intأو*map[string]intخطأ في كل الحالات تقريبًا
المؤشرات ليست أسرع تلقائيًا. نسخ بنية صغيرة رخيص، وقد يفرض المؤشر حجزًا في الكومة ويضيف عملًا لجامع النفايات.
المؤشرات ومتغيّرات الحلقة
أخذ عنوان متغيّر الحلقة آمن منذ Go 1.22، لأن لكل تكرار متغيّره الخاص:
يطبع هذا 0 1 2. قبل Go 1.22 (الذي أدخل أيضًا range 3) كانت الحلقة نفسها مكتوبة بـ for i := 0; i < 3; i++ تطبع 3 3 3، لأن المؤشرات الثلاثة كانت تتشارك متغيّرًا واحدًا. لاحظ أن &v في for _, v := range items يشير إلى نسخة من العنصر، لا إلى العنصر في الشريحة. للحصول على مؤشر إلى العنصر نفسه استخدم &items[i].
أخطاء شائعة
- إلغاء المرجعية دون فحص. افحص
if p == nilحيثما كانت nil ممكنة. - مؤشر إلى قيمة range. يشير
&vإلى نسخة؛ استخدم&s[i]. - المؤشرات إلى الشرائح والخرائط. نادرًا ما تُحتاج. أعد الشريحة الجديدة بدلًا من ذلك.
- الاحتفاظ بـ
&s[i]عبرappend. إذا أعادتappendالحجز، يبقى المؤشر يشير إلى المصفوفة القديمة وتذهب الكتابات اللاحقة إلى مكان لا يُرى.
الأسئلة الشائعة
ماذا يعني & و* في Go؟
يأخذ &x عنوان x ويعطي مؤشرًا من النوع *T. ويلغي *p مرجعية المؤشر: يقرأ القيمة التي يشير إليها أو يكتبها. وفي الأنواع تعني *T "مؤشرًا إلى T".
ما الفرق بين new وmake في Go؟
تحجز new(T) قيمة T صفرية وتعيد *T؛ وتعمل مع أي نوع. أما make فتعمل فقط مع الشرائح والخرائط والقنوات، وتعيد قيمة مهيّأة (لا صفرية ولا مؤشرًا) من ذلك النوع: make(map[string]int) جاهزة للاستخدام، بينما new(map[string]int) مؤشر إلى خريطة nil.
هل يمكنني إعادة مؤشر إلى متغيّر محلي في Go؟
نعم. بخلاف C، هذا آمن. يرى تحليل الهروب في المترجم أن المتغيّر يعيش أطول من الدالة فيحجزه في الكومة. func newInt() *int { x := 5; return &x } شيفرة Go صحيحة.
ما سبب رسالة "invalid memory address or nil pointer dereference" في Go؟
القراءة أو الكتابة عبر مؤشر nil: متغيّر *T لم يُسند إليه شيء قط، أو بحث عن مفتاح مفقود في map[K]*V، أو دالة أعادت nil مع خطأ لم تفحصه. افحص nil، أو هيّئ المؤشر قبل استخدامه.
هل تملك Go حساب المؤشرات؟
لا. لا يمكنك إضافة رقم إلى مؤشر أو فهرسة الذاكرة عبره. تسمح الحزمة unsafe بذلك للشيفرة منخفضة المستوى، دون أي ضمانات أمان. وتستخدم شيفرات Go العادية الشرائح للذاكرة المتصلة.