مؤشرات Go
احتفظت Go بالمؤشرات لضمان الأداء إلى حد ما، وفي نفس الوقت قيدت استخدام المؤشرات من أجل GC أفضل والأمان.
الإنشاء
هناك عاملان شائعان للمؤشرات: أحدهما عامل أخذ العنوان &، والآخر عامل فك المرجع *. أخذ عنوان متغير يُرجع مؤشراً للنوع المقابل، مثلاً:
func main() {
num := 2
p := &num
fmt.Println(p)
}المؤشر يخزن عنوان المتغير num:
0xc00001c088عامل فك المرجع له استخدامان: الأول هو الوصول للعنصر الذي يشير إليه المؤشر، أي فك المرجع، مثلاً:
func main() {
num := 2
p := &num
rawNum := *p
fmt.Println(rawNum)
}p مؤشر، وفك مرجع نوع المؤشر يسمح بالوصول للعنصر الذي يشير إليه. الاستخدام الثاني هو تعريف مؤشر، مثلاً:
func main() {
var numPtr *int
fmt.Println(numPtr)
}<nil>*int يعني أن نوع هذا المتغير هو مؤشر لـ int، لكن المؤشر لا يمكن تعريفه فقط، بل يجب تهيئته وتخصيص ذاكرة له، وإلا سيكون مؤشراً فارغاً ولا يمكن استخدامه بشكل طبيعي. إما استخدام عامل أخذ العنوان لإسناد عنوان متغير آخر لهذا المؤشر، أو استخدام الدالة المدمجة new لتخصيص الذاكرة يدوياً، مثلاً:
func main() {
var numPtr *int
numPtr = new(int)
fmt.Println(numPtr)
}الأكثر شيوعاً هو استخدام المتغيرات القصيرة:
func main() {
numPtr := new(int)
fmt.Println(numPtr)
}الدالة new تقبل معاملاً واحداً فقط هو النوع، وتُرجع مؤشراً للنوع المقابل، الدالة ستخصص ذاكرة لهذا المؤشر، ويشير المؤشر للقيمة الصفرية للنوع المقابل، مثلاً:
func main() {
fmt.Println(*new(string))
fmt.Println(*new(int))
fmt.Println(*new([5]int))
fmt.Println(*new([]float64))
}
0
[0 0 0 0 0]
[]حظر العمليات على المؤشرات
في Go لا يتم دعم العمليات على المؤشرات، أي أن المؤشر لا يمكن إزاحته، لنرى أولاً كود C++:
int main() {
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = &arr[0];
cout << &arr << endl
<< p << endl
<< p + 1 << endl
<< &arr[1] << endl;
}0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884يمكن ملاحظة أن عنوان المصفوفة يطابق عنوان أول عنصر فيها، وبعد إضافة 1 للمؤشر، العنصر الذي يشير إليه هو العنصر الثاني للمصفوفة. المصفوفات في Go كذلك أيضاً، لكن الفرق هو أن المؤشر لا يمكن إزاحته، مثلاً:
func main() {
arr := [5]int{0, 1, 2, 3, 4}
p := &arr
println(&arr[0])
println(p)
// محاولة إجراء عملية على المؤشر
p++
fmt.Println(p)
}هذا البرنامج لن يتم ترجمته، والخطأ كالتالي:
main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)TIP
توفر المكتبة القياسية unsafe العديد من العمليات للبرمجة منخفضة المستوى، بما في ذلك العمليات على المؤشرات، انتقل لـ المكتبة القياسية - unsafe لمعرفة التفاصيل.
new و make
في الأقسام السابقة تم ذكر الدالتين المدمجتين new و make عدة مرات، لكنهما متشابهتان قليلاً ومختلفتان، لنراجع:
func new(Type) *Type- القيمة المُرجعة هي مؤشر للنوع
- يقبل معاملاً هو النوع
- مخصصة لتخصيص مساحة ذاكرة للمؤشرات
func make(t Type, size ...IntegerType) Type- القيمة المُرجعة هي قيمة، وليست مؤشراً
- المعامل الأول هو النوع، والمعاملات متغيرة الطول تختلف حسب النوع المُمرر
- مخصصة لتخصيص ذاكرة للشرائح وجداول التعيين والقنوات
فيما يلي بعض الأمثلة:
new(int) // مؤشر int
new(string) // مؤشر string
new([]int) // مؤشر شريحة أعداد صحيحة
make([]int, 10, 100) // شريحة أعداد صحيحة بطول 10 وسعة 100
make(map[string]int, 10) // جدول تعيين بسعة 10
make(chan int, 10) // قناة بحجم مخزن مؤقت 10