المصفوفات Arrays والشرائح Slices في لغة Go: بنية البيانات الأساسية بعمق تفصيلي
تُعد المصفوفات (Arrays) والشرائح (Slices) من البنى الأساسية المهمة في لغة البرمجة Go، وهي تعكس فلسفة اللغة في البساطة والأداء العالي والكفاءة في إدارة الذاكرة. يندرج فهم هذه البنى ضمن المهارات الجوهرية لأي مبرمج يتعامل مع لغة Go، خصوصًا وأن هذه اللغة تتميز بآلية دقيقة وواضحة في التعامل مع الذاكرة وتخصيصها، مما يجعل إتقان استخدام المصفوفات والشرائح أمرًا حيويًا لتطوير تطبيقات عالية الأداء والموثوقية.
هذا المقال يسلط الضوء على الفروقات الجوهرية بين المصفوفات والشرائح، وطريقة استخدامها، والفوائد التي تقدمها كل منهما، بالإضافة إلى الجوانب المتعلقة بالأداء، وإدارة الذاكرة، وطبيعة المؤشرات والنسخ، مدعومًا بأمثلة عملية وتفصيلية توضح المفاهيم النظرية.
المصفوفات Arrays في لغة Go
تعريف المصفوفة
المصفوفة في Go هي بنية بيانات ثابتة الطول تحتوي على عناصر من نفس النوع. يتم تعريف المصفوفة بتحديد نوع العنصر وعدد العناصر عند التصريح بها. الطول جزء من نوع المصفوفة نفسه.
govar numbers [5]int
في هذا المثال، numbers هي مصفوفة من 5 أعداد صحيحة (int)، ويتم تهيئة كل عنصر تلقائيًا بقيمة الصفر.
خصائص المصفوفة
-
ثبات الطول: لا يمكن تغيير حجم المصفوفة بعد إنشائها.
-
قيم افتراضية: جميع العناصر تتم تهيئتها بالقيم الافتراضية لنوعها.
-
جزء من النوع: الطول يُعتبر جزءًا من نوع المصفوفة، فـ
[5]intمختلف عن[6]int.
التهيئة Initialization
يمكن تهيئة المصفوفات عند التصريح عنها:
govar a = [3]string{"Go", "is", "fun"}
أو باستخدام البنية الضمنية:
gob := [...]int{1, 2, 3, 4}
يحدد المترجم حجم المصفوفة تلقائيًا بناءً على عدد العناصر.
النسخ والسلوك المرجعي
عند إسناد مصفوفة إلى متغير آخر، يتم نسخ جميع القيم:
goa := [3]int{1, 2, 3}
b := a
b[0] = 100
// a[0] لا يزال 1
وهذا يختلف عن كثير من اللغات التي تتعامل مع المصفوفات كمراجع.
الشرائح Slices في لغة Go
ما هي الشريحة؟
الشريحة هي بنية بيانات مرنة تُبنى على قمة المصفوفات. هي واجهة ديناميكية تتيح لك التعامل مع مقاطع من المصفوفات دون الحاجة إلى إنشاء نسخ منفصلة منها.
goslice := []int{10, 20, 30}
مكونات الشريحة
الشريحة تتكون من ثلاثة مكونات أساسية:
-
المؤشر (Pointer): يشير إلى أول عنصر في المصفوفة الأساسية.
-
الطول (Length): عدد العناصر التي تحتويها الشريحة.
-
السعة (Capacity): عدد العناصر الممكن الوصول إليها بدءًا من أول عنصر في الشريحة إلى نهاية المصفوفة الأساسية.
الإنشاء
يمكن إنشاء الشرائح بعدة طرق:
-
من مصفوفة موجودة:
goarr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // s تحتوي على [2 3 4]
-
باستخدام الدالة
make:
gos := make([]int, 5) // طولها 5، سعتها 5
t := make([]int, 3, 10) // طولها 3، سعتها 10
التعديل والمرجعية
الشرائح مرجعية بطبيعتها. أي تعديل على الشريحة يؤثر على المصفوفة الأصلية:
goarr := [3]int{1, 2, 3}
s := arr[:]
s[0] = 100
// arr[0] أصبح 100
استخدام append
الدالة append تسمح بإضافة عناصر جديدة إلى الشريحة. عند تجاوز السعة، يتم إنشاء مصفوفة جديدة ونقل البيانات:
gos := []int{1, 2}
s = append(s, 3, 4)
إذا لم تتجاوز السعة، سيتم تعديل الشريحة في مكانها، أما إذا تجاوزتها، يتم إنشاء مصفوفة جديدة.
الفرق بين المصفوفات والشرائح
| المعيار | المصفوفات Arrays | الشرائح Slices |
|---|---|---|
| الطول | ثابت | ديناميكي |
| المرجعية | غير مرجعية (تُنسخ عند الإسناد) | مرجعية (تشارك الذاكرة) |
| الكفاءة | أكثر كفاءة عند العمل ببيانات ثابتة | أكثر مرونة وسهولة في الاستخدام |
| الإسناد | ينسخ البيانات بالكامل | ينسخ المرجع فقط |
| التوسع | غير ممكن | ممكن باستخدام append |
إدارة الذاكرة والسعة في الشرائح
من أبرز النقاط التي تميز الشرائح عن المصفوفات هي طريقة إدارتها للذاكرة. الشرائح تحتفظ بمرجع إلى المصفوفة الأساسية، ما يسمح بإنشاء عدة شرائح من نفس المصفوفة دون الحاجة لنسخ البيانات، ولكن يجب الانتباه إلى سلوك السعة:
gos := []int{1, 2, 3}
s2 := s[:2]
s2 = append(s2, 100)
قد يؤدي التعديل على s2 إلى التأثير على s، أو لا، بحسب ما إذا تجاوزت السعة الأصلية أم لا. عند تجاوز السعة، تنشأ شريحة جديدة بمصفوفة جديدة في الذاكرة.
القطع Slicing
يمكن تقطيع الشرائح باستخدام النطاق [low:high]، أو الثلاثي [low:high:capacity] الذي يحدد سعة الشريحة الجديدة:
gos := []int{10, 20, 30, 40, 50}
t := s[1:3:4] // t تحتوي على [20 30] وسعتها 3
الحلقات والتكرار على الشرائح
التكرار على الشرائح يمكن أن يتم باستخدام حلقة for التقليدية أو باستخدام range:
gofor i := 0; i < len(s); i++ {
fmt.Println(s[i])
}
for i, v := range s {
fmt.Println(i, v)
}
مقارنات الشرائح
الشرائح لا يمكن مقارنتها مباشرة باستخدام == إلا إذا كانت nil، والمقارنة الوحيدة المسموح بها هي مع nil. للمقارنة بين محتويات الشرائح، يجب استخدام حلقات أو حزم خارجية مثل reflect.DeepEqual أو حزمة cmp.
نسخ الشرائح
لنسخ محتويات شريحة إلى أخرى، تُستخدم دالة copy:
gos1 := []int{1, 2, 3}
s2 := make([]int, len(s1))
copy(s2, s1)
إزالة العناصر من الشريحة
لا توجد دالة مدمجة لحذف عنصر، ولكن يمكن القيام بذلك يدويًا باستخدام التقطيع والدمج:
gos := []int{1, 2, 3, 4, 5}
i := 2 // حذف العنصر الثالث
s = append(s[:i], s[i+1:]...)
الشرائح متعددة الأبعاد
يمكن إنشاء شرائح تحتوي على شرائح أخرى (مصفوفات متعددة الأبعاد):
gomatrix := [][]int{
{1, 2, 3},
{4, 5, 6},
}
وهذا يسمح بتمثيل الجداول والمصفوفات ثنائية الأبعاد بكفاءة.
الأداء والنصائح العملية
-
استخدام المصفوفات مناسب للبيانات الثابتة التي لا تتغير كثيرًا، حيث توفر أداءً أعلى.
-
الشرائح مناسبة للبيانات المتغيرة والعمليات الديناميكية.
-
يفضل استخدام
copyبدلاً من الحلقات اليدوية عند نقل البيانات بين الشرائح. -
تجنب ترك شرائح تشير إلى مصفوفات كبيرة لم تعد مطلوبة، لتفادي تسرب الذاكرة.
-
استخدم
[:0]لإعادة تهيئة شريحة دون فقدان السعة:
gos = s[:0] // طول 0، سعة محفوظة
الخلاصة التقنية
المصفوفات والشرائح تشكلان أساسًا مهمًا في بنية البيانات في لغة Go، ولكل منهما حالات استخدام خاصة تعتمد على طبيعة المهمة المطلوبة. المصفوفات توفر أداءً عاليًا وثباتًا في البنية، بينما توفر الشرائح مرونة كبيرة تجعلها الخيار الأكثر استخدامًا في تطوير التطبيقات.
جدول مقارنة بين الخصائص التقنية للمصفوفات والشرائح
| الخاصية | المصفوفة (Array) | الشريحة (Slice) |
|---|---|---|
| الحجم | ثابت | متغير |
| القابلية للتعديل | لا يمكن التوسع | يمكن التوسع |
| المرجعية | تُنسخ عند الإسناد | مرجعية (تشارك الذاكرة) |
| الكفاءة | عالية عند الثبات | متوسطة وتعتمد على الاستخدام |
| التهيئة الديناميكية | غير ممكن | ممكن باستخدام make أو append |
| المقارنة المباشرة | ممكن | غير ممكن |
| الحذف | غير عملي | ممكن باستخدام append وslice |
| الاستعمال النموذجي | البيانات الثابتة أو الحرجة | العمليات الديناميكية والمرنة |
المصادر والمراجع
-
Go Programming Language Specification: https://golang.org/ref/spec
-
Effective Go: https://golang.org/doc/effective_go

