المصفوفات الثابتة في Go: كيفية تعريفها بسهولة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: السؤال الأبدي من Stack Overflow

\n\n

إذا سبق لك أن بحثت عن "Declare a constant array in Go"، فمن المؤكد أنك رأيت ذلك الموضوع الشهير على Stack Overflow بآلاف المشاهدات. نعم، أنت لست وحدك في حيرتك. في Go، لا يمكنك ببساطة كتابة const arr = [...]int{1, 2, 3} — سيخبرك المترجم بكلمة "لا" بلطف.

\n\n

لماذا؟ وماذا نفعل إذا أردنا قائمة بيانات غير قابلة للتغيير؟ في هذا المقال، سنتعمق في طبيعة الثوابت في Go، ونعرف لماذا لا يمكن أن تكون المصفوفات ثوابت، ونجد أفضل الحلول البديلة. مفاجأة: الحل موجود، بل إنه أجمل مما تتخيل.

\n\n

لماذا لا يسمح Go بتعريف مصفوفة ثابتة؟

\n\n

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

\n\n

هذا مثال بسيط لن يتم ترجمته:

\n\n
package main\n\nconst arr = [3]int{1, 2, 3} // خطأ: const initializer [3]int{1, 2, 3} is not a constant\n\nfunc main() {\n    fmt.Println(arr)\n}
\n\n

يقول المترجم: "هذا ليس ثابتًا!" وهو محق. حتى لو تم تعريف المصفوفة كـ const، فإن Go لا يضمن عدم تغيير محتواها عبر المؤشرات أو الشرائح. لذلك قرر مطورو اللغة: لا مصفوفات ثابتة.

\n\n

لكن لا تتعجل في الإحباط. هناك على الأقل ثلاث طرق لتجاوز هذا القيد، ولكل منها مزاياها.

\n\n

الطريقة 1: استخدام متغير مع تعليق "لا تلمس"

\n\n

أبسط طريقة وأكثرها سذاجة هي تعريف متغير والأمل ألا يغيره أحد. لكننا مطورون محترفون، أليس كذلك؟

\n\n
package main\n\nimport "fmt"\n\n// arr - مصفوفة ثابتة. من فضلك لا تغيرها.\nvar arr = [3]int{1, 2, 3}\n\nfunc main() {\n    fmt.Println(arr)\n    // arr[0] = 100 // أزل التعليق وسيحدث الخراب\n}
\n\n

العيوب واضحة: هذا ليس ثابتًا حقيقيًا. يمكن لأي شخص تغيير المصفوفة عن طريق التعيين. لكنه مناسب للنماذج الأولية السريعة أو المشاريع الصغيرة.

\n\n

الطريقة 2: دالة جالبة (الطريقة الصحيحة)

\n\n

يُستخدم هذا الأسلوب في كود الإنتاج. نخفي المصفوفة داخل دالة ونعيد نسخة منها. بهذا لا يمكن لأي شخص تغيير الأصل.

\n\n
package main\n\nimport "fmt"\n\n// getColors تعيد نسخة من مصفوفة الألوان.\n// المصفوفة محمية من التغييرات الخارجية.\nfunc getColors() [3]string {\n    return [3]string{"Red", "Green", "Blue"}\n}\n\nfunc main() {\n    colors := getColors()\n    fmt.Println(colors)\n    // colors[0] = "Yellow" // هذا سيغير النسخة، وليس الأصل\n}
\n\n

لماذا هذا جيد؟ لأن:

\n
    \n
  • لا يمكن تغيير المصفوفة من الخارج — الدالة تعيد نسخة.
  • \n
  • الكود قابل للقراءة والفهم.
  • \n
  • سهل الاختبار والصيانة.
  • \n
\n\n

لكن هناك نقطة: كل استدعاء للدالة ينشئ نسخة جديدة من المصفوفة. إذا كانت المصفوفة كبيرة (مثل 1000 عنصر)، فقد يكون ذلك مكلفًا. في هذه الحالة، استخدم الطريقة التالية.

\n\n

الطريقة 3: متغير حزمة مع وصول خاص

\n\n

إذا كنت تكتب حزمة وتريد حماية المصفوفة من التغييرات، اجعلها غير قابلة للتصدير (بحرف صغير) ووفر الوصول عبر دالة.

\n\n
package constants\n\n// المصفوفة متاحة فقط داخل الحزمة\nvar defaultPorts = [5]int{80, 443, 8080, 3000, 5000}\n\n// GetDefaultPorts تعيد نسخة من مصفوفة المنافذ.\nfunc GetDefaultPorts() [5]int {\n    return defaultPorts\n}
\n\n

الآن يمكن لمستخدمي حزمتك قراءة البيانات فقط، دون تعديلها. مثالي للمكتبات.

\n\n

الطريقة 4: استخدام iota لمجموعات ثابتة

\n\n

ماذا لو كنت تحتاج إلى الفهارس نفسها بدلاً من القيم؟ على سب

المدونات

توصيات الكتب