البرمجة

المصفوفات والشرائح في Go

المصفوفات Arrays والشرائح Slices في لغة Go: بنية البيانات الأساسية بعمق تفصيلي

تُعد المصفوفات (Arrays) والشرائح (Slices) من البنى الأساسية المهمة في لغة البرمجة Go، وهي تعكس فلسفة اللغة في البساطة والأداء العالي والكفاءة في إدارة الذاكرة. يندرج فهم هذه البنى ضمن المهارات الجوهرية لأي مبرمج يتعامل مع لغة Go، خصوصًا وأن هذه اللغة تتميز بآلية دقيقة وواضحة في التعامل مع الذاكرة وتخصيصها، مما يجعل إتقان استخدام المصفوفات والشرائح أمرًا حيويًا لتطوير تطبيقات عالية الأداء والموثوقية.

هذا المقال يسلط الضوء على الفروقات الجوهرية بين المصفوفات والشرائح، وطريقة استخدامها، والفوائد التي تقدمها كل منهما، بالإضافة إلى الجوانب المتعلقة بالأداء، وإدارة الذاكرة، وطبيعة المؤشرات والنسخ، مدعومًا بأمثلة عملية وتفصيلية توضح المفاهيم النظرية.


المصفوفات Arrays في لغة Go

تعريف المصفوفة

المصفوفة في Go هي بنية بيانات ثابتة الطول تحتوي على عناصر من نفس النوع. يتم تعريف المصفوفة بتحديد نوع العنصر وعدد العناصر عند التصريح بها. الطول جزء من نوع المصفوفة نفسه.

go
var numbers [5]int

في هذا المثال، numbers هي مصفوفة من 5 أعداد صحيحة (int)، ويتم تهيئة كل عنصر تلقائيًا بقيمة الصفر.

خصائص المصفوفة

  • ثبات الطول: لا يمكن تغيير حجم المصفوفة بعد إنشائها.

  • قيم افتراضية: جميع العناصر تتم تهيئتها بالقيم الافتراضية لنوعها.

  • جزء من النوع: الطول يُعتبر جزءًا من نوع المصفوفة، فـ [5]int مختلف عن [6]int.

التهيئة Initialization

يمكن تهيئة المصفوفات عند التصريح عنها:

go
var a = [3]string{"Go", "is", "fun"}

أو باستخدام البنية الضمنية:

go
b := [...]int{1, 2, 3, 4}

يحدد المترجم حجم المصفوفة تلقائيًا بناءً على عدد العناصر.

النسخ والسلوك المرجعي

عند إسناد مصفوفة إلى متغير آخر، يتم نسخ جميع القيم:

go
a := [3]int{1, 2, 3} b := a b[0] = 100 // a[0] لا يزال 1

وهذا يختلف عن كثير من اللغات التي تتعامل مع المصفوفات كمراجع.


الشرائح Slices في لغة Go

ما هي الشريحة؟

الشريحة هي بنية بيانات مرنة تُبنى على قمة المصفوفات. هي واجهة ديناميكية تتيح لك التعامل مع مقاطع من المصفوفات دون الحاجة إلى إنشاء نسخ منفصلة منها.

go
slice := []int{10, 20, 30}

مكونات الشريحة

الشريحة تتكون من ثلاثة مكونات أساسية:

  • المؤشر (Pointer): يشير إلى أول عنصر في المصفوفة الأساسية.

  • الطول (Length): عدد العناصر التي تحتويها الشريحة.

  • السعة (Capacity): عدد العناصر الممكن الوصول إليها بدءًا من أول عنصر في الشريحة إلى نهاية المصفوفة الأساسية.

الإنشاء

يمكن إنشاء الشرائح بعدة طرق:

  1. من مصفوفة موجودة:

go
arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] // s تحتوي على [2 3 4]
  1. باستخدام الدالة make:

go
s := make([]int, 5) // طولها 5، سعتها 5 t := make([]int, 3, 10) // طولها 3، سعتها 10

التعديل والمرجعية

الشرائح مرجعية بطبيعتها. أي تعديل على الشريحة يؤثر على المصفوفة الأصلية:

go
arr := [3]int{1, 2, 3} s := arr[:] s[0] = 100 // arr[0] أصبح 100

استخدام append

الدالة append تسمح بإضافة عناصر جديدة إلى الشريحة. عند تجاوز السعة، يتم إنشاء مصفوفة جديدة ونقل البيانات:

go
s := []int{1, 2} s = append(s, 3, 4)

إذا لم تتجاوز السعة، سيتم تعديل الشريحة في مكانها، أما إذا تجاوزتها، يتم إنشاء مصفوفة جديدة.


الفرق بين المصفوفات والشرائح

المعيار المصفوفات Arrays الشرائح Slices
الطول ثابت ديناميكي
المرجعية غير مرجعية (تُنسخ عند الإسناد) مرجعية (تشارك الذاكرة)
الكفاءة أكثر كفاءة عند العمل ببيانات ثابتة أكثر مرونة وسهولة في الاستخدام
الإسناد ينسخ البيانات بالكامل ينسخ المرجع فقط
التوسع غير ممكن ممكن باستخدام append

إدارة الذاكرة والسعة في الشرائح

من أبرز النقاط التي تميز الشرائح عن المصفوفات هي طريقة إدارتها للذاكرة. الشرائح تحتفظ بمرجع إلى المصفوفة الأساسية، ما يسمح بإنشاء عدة شرائح من نفس المصفوفة دون الحاجة لنسخ البيانات، ولكن يجب الانتباه إلى سلوك السعة:

go
s := []int{1, 2, 3} s2 := s[:2] s2 = append(s2, 100)

قد يؤدي التعديل على s2 إلى التأثير على s، أو لا، بحسب ما إذا تجاوزت السعة الأصلية أم لا. عند تجاوز السعة، تنشأ شريحة جديدة بمصفوفة جديدة في الذاكرة.


القطع Slicing

يمكن تقطيع الشرائح باستخدام النطاق [low:high]، أو الثلاثي [low:high:capacity] الذي يحدد سعة الشريحة الجديدة:

go
s := []int{10, 20, 30, 40, 50} t := s[1:3:4] // t تحتوي على [20 30] وسعتها 3

الحلقات والتكرار على الشرائح

التكرار على الشرائح يمكن أن يتم باستخدام حلقة for التقليدية أو باستخدام range:

go
for 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:

go
s1 := []int{1, 2, 3} s2 := make([]int, len(s1)) copy(s2, s1)

إزالة العناصر من الشريحة

لا توجد دالة مدمجة لحذف عنصر، ولكن يمكن القيام بذلك يدويًا باستخدام التقطيع والدمج:

go
s := []int{1, 2, 3, 4, 5} i := 2 // حذف العنصر الثالث s = append(s[:i], s[i+1:]...)

الشرائح متعددة الأبعاد

يمكن إنشاء شرائح تحتوي على شرائح أخرى (مصفوفات متعددة الأبعاد):

go
matrix := [][]int{ {1, 2, 3}, {4, 5, 6}, }

وهذا يسمح بتمثيل الجداول والمصفوفات ثنائية الأبعاد بكفاءة.


الأداء والنصائح العملية

  • استخدام المصفوفات مناسب للبيانات الثابتة التي لا تتغير كثيرًا، حيث توفر أداءً أعلى.

  • الشرائح مناسبة للبيانات المتغيرة والعمليات الديناميكية.

  • يفضل استخدام copy بدلاً من الحلقات اليدوية عند نقل البيانات بين الشرائح.

  • تجنب ترك شرائح تشير إلى مصفوفات كبيرة لم تعد مطلوبة، لتفادي تسرب الذاكرة.

  • استخدم [:0] لإعادة تهيئة شريحة دون فقدان السعة:

go
s = s[:0] // طول 0، سعة محفوظة

الخلاصة التقنية

المصفوفات والشرائح تشكلان أساسًا مهمًا في بنية البيانات في لغة Go، ولكل منهما حالات استخدام خاصة تعتمد على طبيعة المهمة المطلوبة. المصفوفات توفر أداءً عاليًا وثباتًا في البنية، بينما توفر الشرائح مرونة كبيرة تجعلها الخيار الأكثر استخدامًا في تطوير التطبيقات.


جدول مقارنة بين الخصائص التقنية للمصفوفات والشرائح

الخاصية المصفوفة (Array) الشريحة (Slice)
الحجم ثابت متغير
القابلية للتعديل لا يمكن التوسع يمكن التوسع
المرجعية تُنسخ عند الإسناد مرجعية (تشارك الذاكرة)
الكفاءة عالية عند الثبات متوسطة وتعتمد على الاستخدام
التهيئة الديناميكية غير ممكن ممكن باستخدام make أو append
المقارنة المباشرة ممكن غير ممكن
الحذف غير عملي ممكن باستخدام append وslice
الاستعمال النموذجي البيانات الثابتة أو الحرجة العمليات الديناميكية والمرنة

المصادر والمراجع