تٌعَدّ المتغيرات مفهومًا برمجيًا هامًا يشير إلى القيم ونوع القيم التي تستخدمها في برنامجك، إذ يحدد نوع المتغير -أو نوع البيانات- نوع القيم التي يمكنه تخزينها والعمليات التي يمكن إجراؤها عليه. من الناحية الفنية، يُخصَّص للمتغير مساحة تخزينية في الذاكرة توضع القيمة المرتبطة به فيها. يُستخدم اسم المتغير للإشارة إلى تلك القيمة المُخزَّنة في ذاكرة البرنامج التي هي جزء من ذاكرة الحاسوب.
ستغطي هذه المقالة أنواع المتغيرات المهمة والأساسية التي تحتاجها مبدأيًا، كما ستتعرّف على المتغيرات في جو وستكون قادرًا على فهمها والتعامل معها، وبالتالي ستمتلك القدرة على كتابة تعليمات برمجية أكثر وضوحًا وتعمل بكفاءة.
يمكن التفكير في أنواع المتغيرات كما العالم الحقيقي، فنحن نتعامل مثلًا مع الأعداد من مجموعات عددية مختلفة مثل مجموعة الأعداد الطبيعية (0 ، 1 ، 2 ، …) والأعداد الصحيحة (… ، -1 ، 0 ، 1 ، …) والأعداد غير الكسرية مثل π.
عند التعامل مع المسائل الرياضية قد نجمع بين الأعداد التي تنتمي إلى مجموعات عددية مختلفة لنحصل على نتيجة ما مثلًا إضافة العدد 5 إلى π:
5 + π
يمكننا الآن الاحتفاظ بالمعادلة كما هي لتكون إجابةً أو يمكننا تقريب العدد π إلى منزلة عشرية مناسبة ثم جمعه مع العدد 5، أي كما يلي:
5 + π = 5 + 3.14 = 8.14
لكن إذا حاولت جمع أيّ عدد مع نوع بيانات آخر وليكن كلمة مثلًا، فسيكون هذا بدون معنى مثل:
shark + 8
هناك فصل بين أنواع البيانات في الحواسيب، إذ يختلف كل نوع عن الآخر بحيث يوجد نوع مخصص للكلمات ونوع آخر للأعداد، ويجب الانتباه عند استخدام أو تطبيق العمليات على هذه المتغيرات.
الأعداد الصحيحة
كما هو الحال في الرياضيات، فإن الأعداد الصحيحة Integers في الحاسب هي أعداد موجبة أو سالبة إضافةً إلى الصفر، أي (… ، -1 ، 0 ، 1 ، …).
يُعبَّر عن الأعداد الصحيحة في لغة جو من خلال النوع int
، كما يجب أن تكتب العدد الصحيح كما هو بدون فواصل بين الأعداد كما في اللغات البرمجية الأخرى، فعادةً تُفصَل الأعداد الكبيرة بفواصل لتسهيل قرائتها، فقد يُكتب المليون مثلًا بالصورة 1,000,000 وفي لغات البرمجة هذا غير مسموح، ويمكن طباعة العدد الصحيح ببساطة كما يلي:
fmt.Println(-459)
والنتيجة هي:
-459
أو يمكنك تعريف متغير من النوع الصحيح ليمثل هذا العدد كما يلي:
var absoluteZero int = -459 fmt.Println(absoluteZero)
والنتيجة هي:
-459
يمكنك أيضًا إجراء العمليات الحسابية على الأعداد الصحيحة في جو، ففي الشيفرة التالية سنستخدِم معامل الإسناد :=
لتعريف وتقييم المتغير sum:
sum := 116 - 68 fmt.Println(sum)
والنتيجة هي:
48
نلاحظ أنه قد طُرح العدد 68 من العدد 116 وخُزّن الناتج 48 في المتغيّر sum.
ستتعرف على المزيد من التفاصيل المتعلقة بالتصريح عن المتغيرات لاحقًا، وهناك العديد من الطرق لاستخدام الأعداد الصحيحة في جو ستتعرف على معظمها خلال رحلتك التعليمية هذه.
الأعداد العشرية
يُعَدّ العدد ذو الفاصلة العشرية Floating-Point عددًا عشريًا ويمكن كتابته على صورة حاصل ضرب في على النحو التالي: 10*12.5 = 2^10*1.25 = 125.
وتُستخدَم الأعداد العشرية لتمثيل الأعداد الحقيقية التي لا يمكن التعبير عنها بالأعداد الصحيحة، إذ تتضمن الأعداد الحقيقية جميع الأعداد الكسرية وغير الكسرية، ولهذا السبب يمكن أن تحتوي أرقام عشرية على جزء كسري مثل 9.0 أو 116.42- في جو، فالعدد العشري في جو هو أيّ عدد يحتوي على فاصلة عشرية ببساطة:
fmt.Println(-459.67)
والنتيجة هي:
-459.67
يمكنك أيضًا تعريف متغير يمثّله كما يلي:
absoluteZero := -459.67 fmt.Println(absoluteZero)
والنتيجة هي:
-459.67
يمكنك أيضًا إجراء العمليات الحسابية عليه كما فعلنا مع الأعداد الصحيحة:
var sum = 564.0 + 365.24 fmt.Println(sum)
والنتيجة هي:
929.24
يجب أن تأخذ في الحسبان أنّ العدد 3 لا يساوي العدد 3.0، إذ يشير العدد 3 إلى عدد صحيح في حين 3.0 إلى عدد عشري.
حجم الأنواع العددية
يحتوي جو على نوعين من البيانات العددية بالإضافة إلى التمييز بين الأعداد الصحيحة والعشرية، وهما الساكنة static والديناميكية dynamic، فالنوع الأول مستقل عن المعمارية architecture-independent، أي أنّ حجم البيانات بواحدة البِتّ bit لا يتغير بغض النظر عن الجهاز الذي تعمل عليه الشيفرة.
معظم معماريات أجهزة الحاسب في هذه الأيام هي إما 32 بِتّ أو 64 بِتّ، فإذا فرضنا مثلًا أنك تريد تطوير برنامج لحاسبك المحمول، فسترغب بجعله يعمل بنظام ويندوز حديث معماريته 64 بِتّ، لكن إذا كنت تعمل على تطوير برنامج لجهاز مثل ساعة اللياقة البدنية fitness watch، فربما ستُجبر على العمل بمعمارية 32 بِتّ.
إذا استخدمت نوع المتغيرات المستقلة مثل int32
، فسيكون حجم التخزين للمتغير ثابتًا بغض النظر عن المعمارية التي تُجري عملية التصريف عليها، أي باختصار، سيجعل استخدامك للمتغيرات من النوع الأول هذا المتغير ذا حجم ثابت في أيّ معمارية أو بيئة تستخدمها.
يُعَدّ النوع الثاني نوعًا خاصًا بالتنفيذ implementation-specific، إذ يمكن هنا أن يختلف حجم البِتّ بناءً على المعمارية التي بُنيَ البرنامج عليها، فإذا استخدمت النوع int
، فسيكون حجم المتغير هو 32 بِتّ عند تصريف برنامج جو لمعمارية 32 بِتّ؛ أما إذا صُرِّف البرنامج بمعمارية 64 بِتّ، فسيكون حجم المتغير 64 بِتّ، لذا يُسمى ديناميكيًا.
هناك أنواع لأنواع البيانات نفسها إضافةً إلى أنواع البيانات ذات الأحجام المختلفة، فالأعداد الصحيحة مثلًا لها نوعين أساسيين هما ذات إشارة signed وعديمة الإشارة unsigned، فالنوع int8 هو عدد صحيح له إشارة سالبة أو موجبة ويمكن أن يكون له قيمة من -128 إلى 127. أما النوع uint8 فهو عدد صحيح عديم الإشارة أي موجب دومًا، ويمكن أن يكون له فقط قيمة موجبة من 0 إلى 255.
يمتلك جو الأنواع التالية المستقلة عن نوع المعمارية architecture-independent للأعداد الصحيحة:
uint8 unsigned 8-bit integers (0 to 255) uint16 unsigned 16-bit integers (0 to 65535) uint32 unsigned 32-bit integers (0 to 4294967295) uint64 unsigned 64-bit integers (0 to 18446744073709551615) int8 signed 8-bit integers (-128 to 127) int16 signed 16-bit integers (-32768 to 32767) int32 signed 32-bit integers (-2147483648 to 2147483647) int64 signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
تأتي أعداد الفاصلة العشرية والأعداد المركبة أو العقدية complex numbers أيضًا بأحجام مختلفة:
float32 IEEE-754 32-bit floating-point numbers float64 IEEE-754 64-bit floating-point numbers complex64 complex numbers with float32 real and imaginary parts complex128 complex numbers with float64 real and imaginary parts
يوجد أيضًا نوعان بديلان alias للأعداد بغية إعطائها اسمًا هادفًا:
byte alias for uint8 rune alias for int32
الهدف من الأسماء البديلة مثل البايت byte هو تحسين مقروئية الشيفرة وتوضيحها مثل حالة استعمال الاسم byte للإشارة إلى تخزين السلاسل النصية لكونه مقياسًا شائعًا في الحوسبة المتعلقة بالمحارف والنصوص خلاف حالة استعمال الاسم uint8 الذي يشيع استعماله مع الأعداد رغم أن كلاهما يصيران إلى نوع واحد عند تصريف البرنامج، فبذلك مثلًا أينما رأيت التسمية byte في برنامج تعرف أنك تتعامل مع بيانات نصية وأينما رأيت الاسم uint8 تعرف أنك تتعامل مع بيانات عددية.
الاسم البديل رون rune مختلف قليلًا، فعلى عكس البايت byte و uint8 اللذان يمثلان البيانات نفسها، يمكن أن يكون الرون بايتًا واحدًا أو أربعة بايتات، أي مجالًا من 8 إلى 32 بت وهو مايحدده النوع int32، كما يُستخدم الرون لتمثيل محرف الترميز الموحد يونيكود Unicode (معيار يُمكّن الحواسيب من تمثيل النصوص المكتوبة بأغلب نُظم الكتابة ومعالجتها، بصورة متناسقة).
يحتوي جو إضافةً إلى ذلك على الأنواع التالية الخاصة بالتنفيذ:
uint unsigned, either 32 or 64 bits int signed, either 32 or 64 bits uintptr unsigned integer large enough to store the uninterpreted bits of a pointer value
يُحدَّد حجم الأنواع الخاصة بالتنفيذ من خلال المعمارية التي صُرّف البرنامج من أجلها.
اختيار حجم الأنواع العددية في برنامجك
عادةً ما يكون اختيار الحجم الصحيح للنوع مرتبطًا بأداء المعمارية المستهدفة التي تبرمج عليها أكثر من ارتباطه بحجم البيانات التي تعمل عليها، وهناك إرشادات أساسية عامة يمكنك اتباعها عند بداية أي عملية تطوير.
تحدثنا سابقًا أنّ هناك أنواع مستقلة عن المعمارية وأنواع خاصة بالتنفيذ، فبالنسبة لبيانات الأعداد الصحيحة من الشائع في جو استخدام أنواع تنفيذ مثل int
أو uint
بدلًا من int64
أوuint64
، إذ ينتج عن ذلك عادةً سرعة معالجة أكبر على المعمارية المستهدفة، فإذا كنت تستخدم int64
مثلًا وأنجزت عملية التصريف على معمارية 32 بِتّ، فسيستغرق الأمر ضعف الوقت على الأقل لمعالجة هذه القيم بحيث تستغرق دورات معالجة إضافية لنقل البيانات من معمارية لأخرى؛ أما إذا استخدمت int
بدلًا من ذلك، فسيعرّفه البرنامج على أنه حجم 32 بِتّ لمعمارية 32 بِتّ وسيكون أسرع في المعالجة.
إذا كنت تعرف أنّ بياناتك لن تتجاوز مجالًا محددًا من القيم، فإن اختيار نوع مستقل عن المعمارية يمكن أن يزيد السرعة ويقلل من استخدام الذاكرة، فإذا كنت تعلم مثلًا أنّ بياناتك لن تتجاوز القيمة 100 وستكون موجبةً فقط، فسيجعل اختيار uint8
برنامجك أكثر كفاءةً لأنه يتطلب ذاكرةً أقل.
بعد أن تعرّفت على بعض المجالات المحتملة لأنواع البيانات العددية، فلنلقِ نظرةً على ما سيحدث إذا تجاوزت هذه النطاقات في البرنامج.
الفرق الطفحان والالتفاف Overflow vs Wraparound
تتعامل جو مع حالة الطفحان overflow أو بلوغ الحد الأعظمي wraparound عند محاولة تخزين قيمة أكبر من نوع البيانات المصمم لتخزينه اعتمادًا على ما إذا كانت القيمة محسوبة في وقت التصريف compile time أو في وقت التشغيل runtime، إذ يحدث خطأ في وقت التصريف عندما يعثر البرنامج على خطأ أثناء محاولته إنشاء البرنامج؛ أما الخطأ في وقت التشغيل، فيحدث بعد تصريف البرنامج أثناء تنفيذه بالفعل.
ضبطنا قيمة المتغير maxUint32
في المثال التالي إلى أعلى قيمة ممكنة:
package main import "fmt" func main() { var maxUint32 uint32 = 4294967295 // Max uint32 size fmt.Println(maxUint32) }
سيكون الخرج:
Output4294967295
إذا أضفنا العدد 1
إلى المتغير maxUint32
في وقت التشغيل، فسيحدث التفاف إلى القيمة 0
، أي سيكون الخرج:
0
سنعدِّل الآن البرنامج ونضيف العدد 1
إلى المتغير maxUint32
قبل وقت التصريف:
package main import "fmt" func main() { var maxUint32 uint32 = 4294967295 + 1 fmt.Println(maxUint32) }
إذا عثر المُصرّف compiler في وقت التصريف على قيمة كبيرة جدًا بحيث لا يمكن الاحتفاظ بها في نوع البيانات المحدد، فسيؤدي ذلك إلى حدوث خطأ طفحان overflow error، وهذا يعني أنّ القيمة المحسوبة كبيرة جدًا بالنسبة لنوع البيانات الذي حددته.
إذًا سيظهر الخطأ التالي في الشيفرة السابقة لأنّ المصرف قد عثر على قيمة كبيرة جدًا لا تُلائم مجال النوع المحدَّد:
prog.go:6:36: constant 4294967296 overflows uint32
ومعرفة أحجام بياناتك وما تتعامل معه يساعد على تجنب أخطاء الطفحان تلك في برنامجك مستقبلًا لذا يجب الانتباه!
ستتعرّف الآن على كيفية تخزين القيم المنطقية أو البوليانية boolean.
القيم المنطقية
يمتلك نوع البيانات المنطقية قيمتين؛ إما صح true أو خطأ false، وتعرَّف على أنها من النوع bool
عند التصريح عنها، كما تُستخدَم القيم المنطقية لتمثيل قيم الحقيقة المرتبطة بالجبر المنطقي في الرياضيات والذي يُعلم الخوارزميات في علوم الحاسوب، كما يُعبَّر عن القيمتَين true
و false
في جو بمحرفين صغيرين دائمًا وهما t
و f
على التوالي.
تُعطينا العديد من العمليات الحسابية إجابات يتم تُقيَّم إما بصح أو خطأ مثل:
- أكبر من: 500 > 100 إجابتها true أو 1 > 5 إجابتها false.
- أقل من: 200 < 400 true أو 4 < 2 false.
- يساوي: 5 = 5 true أو 500 = 400 false.
يمكنك تخزين قيمة منطقية في متغير بالصورة التالية:
myBool := 5 > 8
ثم يمكنك طباعتها من خلال الدالة ()fmt.Println
كما يلي:
fmt.Println(myBool)
بما أن العدد 5
ليس أكبر من 8
، فسنحصل على الخرج التالي:
false
عندما تكتب المزيد من البرامج في جو، ستصبح أكثر درايةً بكيفية عمل القيم المنطقية وكيف يمكن للوظائف والعمليات المختلفة التي تُقيَّم إلى صح أو خطأ أن تغيِّر مسار البرنامج.
السلاسل النصية Strings
تُعَدّ السلسلة النصية سلسلةً من محرف (حرف أبجدي أو عدد أو رمز) واحد أو أكثر، ويمكن أن تكون سلسلة ثابتة أو متغيرة.
يوجد صياغتان لتعريف السلاسل النصية في جو، فإذا استخدمت علامتَي الاقتباس الخلفية ```
لتمثيل السلسلة بداخلها، فهذا يعني أنك ستُنشئ سلسلةً أوليّةً raw string literal؛ أما إذا استخدمت علامتَي الاقتباس المزدوجة "
، فهذا يعني أنك ستُنشئ سلسلةً مُفسّرةً interpreted string literal.
السلسلة النصية الأولية
هي تسلسل من المحارف بين علامتَي اقتباس خلفية ````` وتُسمى أيضًا علامتَي التجزئة الخلفية back ticks.
سيُعرض كل شيء داخل هاتين العلامتين كما هو، وهذا لا يشمل علامتَي الاقتباس، فهما مجرد دليل لنقطة بداية ونهاية السلسلة مثل:
a := `Say "hello" to Go!` fmt.Println(a)
سيكون الخرج كما يلي:
Say "hello" to Go!
يُستخدَم رمز الشرطة المائلة الخلفية \
(انتبه؛ الشرطة المائلة للخلف نقصد بها الرمز \ وليس الرمز /) لتمثيل المحارف الخاصة ضمن السلسلة النصية، فإذا عُثر في سلسلة نصيّة مُفسّرة على الرمز n\
فهذا يعني الانتقال إلى سطر جديد، ولكن الشرطة المائلة الخليفة ليس لها أيّ معنى ضمن السلاسل الأولية كما في المثال التالي:
a := `Say "hello" to Go!\n` fmt.Println(a)
سيكون الخرج كما يلي:
Say "hello" to Go!\n
لاحظ أنّ السلسلة النصية السابقة هي سلسلة أوليّة، أي غير مُفسّرة، وبالتالي تُعرَض كما هي ولن يكون للرمز n\
أيّ معنى خاص.
تُستخدَم السلاسل الأولية أيضًا لإنشاء سلاسل متعددة الأسطر:
a := `This string is on multiple lines within a single back quote on either side.` fmt.Println(a)
سيكون الخرج كما يلي:
This string is on multiple lines within a single back quote on either side.
لاحظ هنا تأثير السلاسل الأولية؛ إذ ذكرنا سابقًا أنها تطبع السلسلة كما هي.
السلسلة النصية المفسرة
هي سلسلة نصية موضوعة بين علامتَي اقتباس مزدوجتين " "
، إذ سيعرض كل شيء موجود بداخل علامتي الاقتباس باستثناء الأسطر الجديدة وعلامات الاقتباس المزدوجة نفسها إلا ما جرى تهريبه منها، فإذا أردت إظهار هذه الرموز في هذه السلسلة، فيمكنك استخدام محرف الهروب \
قبلها مثل:
a := "Say \"hello\" to Go!" fmt.Println(a)
سيكون الخرج كما يلي:
Say "hello" to Go!
ستستخدِم غالبًا السلاسل المُفسرة لأنها تتعامل مع المحارف الخاصة وتسمح بتخطيها أيضًا وبالتالي تملك تحكمًأ أكبر.
سلاسل صيغة التحويل الموحد UTF-8
تُعَدّ صيغة التحويل الموحد UTF-8 أو UTF اختصارًا للمصطلح الذي يترجم إلى صيغة تحويل نظام الحروف الدولي الموحد، وهذا الترميز وضع من قبل كل من روب بايك وكين تومسن لتمثيل معيار نظام الحروف الدولي الموحد للحروف الأبجدية لأغلب لغات العالم، وتُمثَّل فيه الرموز ذات العرض المتغير بحجم يتراوح بين بايت واحد وأربعة بايت للرمز الواحد.
يدعم جو صيغة التحويل هذه دون أي إعداد خاص أو مكتبات أو حزم، ويمكن تمثيل الأحرف الرومانية مثل الحرف A بقيمة ASCII مثل الرقم 65، لكن سيكون ترميز UTF-8 للمحارف الخاصة مثل المحرف 世 مطلوبًا، كما يستخدِم جو النوع رون لبيانات UTF-8.
a := "Hello, 世界"
يمكنك استخدام الكلمة المفتاحية range
داخل الحلقة for
للتكرار على فهارس أيّ سلسلة نصية، إذ ستُغطى الحلقات لاحقًا في المقالات القادمة، كما تُستخدم الحلقة for
في المثال التالي لحساب عدد البايتات ضمن سلسلة محددة:
package main import "fmt" func main() { a := "Hello, 世界" for i, c := range a { fmt.Printf("%d: %s\n", i, string(c)) } fmt.Println("length of 'Hello, 世界': ", len(a)) }
عرّفنا المتغير a
في المثال السابق وأسندنا إليه السلسة النصية Hello, 世界
التي تحتوي على محارف UTF-8، ثم استخدمنا حلقة for
مع الكلمة المفتاحية range
للتكرار على محارف السلسلة، إذ تُفهرس الكلمة المفتاحية range
عناصر السلسلة المحددة وتعيد قيمتين الأولى هي فهرس البايت والثانية هي قيمة البايت ا-لتي تمثِّل هنا محرف السلسلة- وبترتيب ورودها نفسه في السلسلة.
تُستخدَم الدالة fmt.Printf
كما هي العادة من أجل الطباعة على الشاشة، حيث حددنا لها التنسيق بالصورة d: %s\n%
، إذ تشير d%
إلى وجود عدد صحيح مكانها وهو الفهرس i
الذي مثّل المُعطى الثاني للدالة، والرمز s%
إلى وجود سلسلة نصية أو محرف وهو المحرف c
المُمثّل بالمُعطى الثالث والرمز الأخير للانتقال إلى سطر جديد، ولاحظ أيضًا أننا وضعنا c
ضمن الدالة string
وذلك لكي تعامل معاملة سلسلة، ثم طبعنا أخيرًا طول المتغير a
من خلال استخدام الدالة len
.
كما ذكرنا سابقُا؛ الرون هو اسم بديل للنوع int32
ويمكن أن يتكون من واحد إلى أربعة بايت، إذ يُستخدم 3 بايتات لتمثيل المحرف 世
والكلمة المفتاحية range
تُدرك ذلك، وبالتالي عندما تصل إليه تقرأ 3 بايتات متتالية على أنها فهرس لمحرف واحد وهذا واضح من الخرج التالي.
0: H 1: e 2: l 3: l 4: o 5: , 6: 7: 世 10: 界 length of 'Hello, 世界': 13
لاحظ أنه عند طباعة الطول حصلنا على 13، بينما عدد الفهارس هو 8 وهذا يُفسّر ماذكرناه في الأعلى.
لن تعمل دائمًا مع سلاسل UTF-8، ولكن عندما تعمل معها ستكون مُدركًا لسبب تمثيلها باستخدام النوع رون الذي يمكن أن يكون طوله من 8 إلى 32 بت وليس int32
واحدة فقط.
التصريح عن أنواع البيانات للمتغيرات
بعد أن تعرفت على أنواع البيانات الأولية المختلفة، سننتقل إلى كيفية إسناد هذه الأنواع إلى المتغيرات في جو.
يمكننا تحديد متغير باستخدام الكلمة المفتاحية var
متبوعة باسم المتغير ونوع البيانات المطلوب، إذ سنصرّح في المثال التالي عن متغير يسمى pi
من النوع float64
، أي الكلمة المفتاحية var
هي أول ما يُكتب عند التصريح عن متغير، ثم اسم المتغير ثم نوع البيانات:
var pi float64
يمكن إسناد قيمة ابتدائية للمتغير اختياريًا:
var pi float64 = 3.14
تُعَدّ جو لغةً ثابتة الأنواع statically-typed language مثل لغة C و Java و ++C، وهذا يعني أن كل تعليمة في البرنامج تُفحَص في وقت التصريف، كما يعني أيضًا أنّ نوع البيانات مرتبط بالمتغير؛ أما في اللغات ديناميكية الأنواع مثل بايثون أو PHP، فيرتبط نوع البيانات بالقيمة، فقد يُصرَّح مثلًا عن نوع البيانات في جو عند التصريح عن المتغير كما يلي:
var pi float64 = 3.14 var week int = 7
يمكن أن يكون كل من هذه المتغيرات نوع بيانات مختلف إذا عرّفته بطريقة مختلفة، لكن بعد التصريح عنه لأول مرة لن تكون قادرًا على تغيير ذلك؛ أما في لغة PHP فالأمر مختلف، إذ يرتبط نوع البيانات بالقيمة كما يلي:
$s = "sammy"; // يأخذ المتغير ديناميكًا نوع سلسلة نصية $s = 123; // يأخذ المتغير ديناميكيًا نوع عدد صحيح
كان المتغير s
سلسلةً نصيةً ثم تغيَّر إلى عدد صحيح اوتوماتيكيًا تبعًا للقيمة المُسندة إليه.
سنتعرّف الآن على أنواع البيانات الأكثر تعقيدًا مثل المصفوفات.
المصفوفات Arrays
تُعَدّ المصفوفة متغيرًا واحدًا يتألف من تسلسل مرتب من العناصر ذات النوع نفسه، وكل عنصر في المصفوفة يمكن تخزين قيمة واحدة فيه. عناصر المصفوفة تتميز عن بعضها من خلال رقم محدد يعطى لكل عنصر يسمى فهرس index، وأول عنصر في المصفوفة دائمًا يكون فهرسه 0.
تُحدَّد سعة المصفوفة لحظة إنشائها، وبمجرد تعريف حجمها لا يمكن تغييره، وبالتالي بما أنّ حجم المصفوفة ثابت، فهذا يعني أنه يخصص الذاكرة مرةً واحدةً فقط، وهذا يجعل المصفوفات غير مرنة نوعًا ما للعمل معها، لكنه يزيد من أداء برنامجك، ولهذا السبب تُستخدم المصفوفات عادةً عند تحسين البرامج.
تُعَدّ الشرائح Slices التي ستتعرف عليها بعد قليل أكثر مرونةً، إذ تُعَدّ نوعًا من البيانات القابلة للتغيير وتكوّن ما قد تعتقد أنه مصفوفات بلغات أخرى.
تُعرّف المصفوفات من خلال التصريح عن حجمها ثم نوع البيانات ثم القيم المحددة بين الأقواس المعقوصة {}
.
[capacity]data_type{element_values}
مثال:
[3]string{"blue coral", "staghorn coral", "pillar coral"}
يمكنك إسناد المصفوفة إلى متغير ثم طباعتها كما يلي:
coral := [3]string{"blue coral", "staghorn coral", "pillar coral"} fmt.Println(coral)
سيكون الخرج كما يلي:
[blue coral staghorn coral pillar coral]
الشرائح Slices
تُعَدّ الشريحة تسلسلًا مرتبًا من العناصر وطولها قابلًا للتغيير، إذ يمكن للشرائح أن تزيد حجمها ديناميكيًا.
إذا لم يكن للشريحة حجم ذاكرة كافي لتخزين العناصر الجديدة عند إضافة عناصر جديدة إلى شريحة، فستطلب ذاكرةً أكبر من النظام حسب الحاجة، ولهذا السبب هي شائعة الاستخدام أكثر من المصفوفات.
يُصرّح عن الشرائح من خلال تحديد نوع البيانات مسبوقًا بقوس فتح وإغلاق مربع []
والقيم بين أقواس معقوصة {}
، وفيما يلي مثالًا عن شريحة من الأعداد الصحيحة:
[]int{-3, -2, -1, 0, 1, 2, 3}
شريحة من الأعداد الحقيقية:
[]float64{3.14, 9.23, 111.11, 312.12, 1.05}
شريحة من السلاسل النصية:
[]string{"shark", "cuttlefish", "squid", "mantis shrimp"}
يمكنك أيضًا إسنادها إلى متغير:
seaCreatures := []string{"shark", "cuttlefish", "squid", "mantis shrimp"}
ثم طباعة هذا المتغير:
fmt.Println(seaCreatures)
سيكون الخرج كما يلي:
[shark cuttlefish squid mantis shrimp]
يمكنك استخدام الكلمة المفتاحية append
لإضافة عنصر جديد إلى الشريحة، وسنضيف السلسلة seahorse
إلى الشريحة السابقة على سبيل المثال كما يلي:
seaCreatures = append(seaCreatures, "seahorse")
سنطبع الشريحة الآن للتأكد من نجاح العملية:
fmt.Println(seaCreatures)
سيكون الخرج كما يلي:
[shark cuttlefish squid mantis shrimp seahorse]
كما تلاحظ، إذا كنت بحاجة إلى إدارة حجم غير معروف من العناصر، فستكون الشريحة الخيار الأفضل.
الخرائط Maps
تُعَدّ الخرائط -أو الروابط- نوع بيانات مختلف يُمثّل قاموسًا أو رابطًا يربط قيمة مع مفاتحها، إذ تستخدِم الخرائط أزواج المفاتيح والقيم لتخزين البيانات، كما تُعَدّ مفيدةً جدًا عندما يتطلب الأمر البحث عن القيم بسرعة بواسطة الفهارس أو المفاتيح هنا، فمثلًا في حالة كان لديك مستخدِمين وتريد تسجيلهم في بنية بيانات تربط معلومات كل مستخدم -أي القيمة- بعدد فريد لهذا المستخدِم -أي مفتاح-، فالخرائط أُنشئت لهذا الأمر.
تُنشأ الخرائط باستخدام الكلمة المفتاحية map
متبوعة بنوع بيانات المفاتيح بين قوسين معقوفين []
متبوعًا بنوع بيانات القيم، ثم أزواج القيم والمفاتيح في أقواس معقوصة.
map[key]value{}
عادةً ما تُستخدَم الخرائط للاحتفاظ بالبيانات المرتبطة ببعضها كما عرضنا في المثال السابق أو كما في المثال التالي:
map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"}
يمكنك أن تلاحظ أنه بالإضافة إلى الأقواس المعقوصة، توجد أيضًا نقطتان في جميع أنحاء الخريطة، إذ تمثل الكلمات الموجودة على يسار النقطتين المفاتيح وعلى اليمين القيم، كما تجدر الملاحظة أيضًا إلى أنّ المفاتيح يمكن أن تكون من أيّ نوع بيانات في جو، فالمفاتيح الموجودة في الخريطة (الرابطة) أعلاه هي الاسم والحيوان واللون والموقع، ويفضل أن تكون من الأنواع القابلة للمقارنة وهي الأنواع الأولية primitive types مثل السلاسل النصية string
والأعداد الصحيحة ints
وما إلى ذلك، إذ يُحدد النوع الأساسي من خلال اللغة ولا يُنشأ من دمج أيّ أنواع أخرى، كما يمكن للمستخدِم أيضًا تحديد أنواع جديدة، إلا أنه يُفضّل إبقاءها بسيطةً لتجنب أخطاء البرمجة.
سنخزّن الخريطة أعلاه ضمن متغير ثم سنطبعها:
sammy := map[string]string{"name": "Sammy", "animal": "shark", "color": "blue", "location": "ocean"} fmt.Println(sammy)
سيكون الخرج كما يلي:
map[animal:shark color:blue location:ocean name:Sammy]
إذا أردت الوصول إلى لون سامي sammy وطباعته فيمكنك كتابة ما يلي:
fmt.Println(sammy["color"])
سيكون الخرج كما يلي:
blue
تُعَدّ الخرائط أو الروابط عناصر مهمة في كثير من البرامج التي قد تُنشأ في جو كونها توفِّر إمكانية فهرسة عملية الوصول إلى البيانات.
خاتمة
إلى هنا تكون قد تعرّفت على أنواع البيانات الأساسية في جو إضافةً إلى بُنى البيانات المهمة مثل المصفوفات والشرائح والخرائط، إذ ستكوِّن الفروق بين هذه الأنواع أهميةً كبيرةً لك أثناء تطوير مشاريعك باستخدام جو، وستجعلك تدرك أهمية ذلك أكثر، كما أنّ فهمك الجيد لأنواع البيانات سيجعلك قادرًا على معرفة متى وكيف وفي أيّ وقت ستستخدِم أو ستغيّر نوع بيانات المتغير حسب الحاجة.
ترجمة -وبتصرف- للمقال Understanding Data Types in Go لصاحبه Gopher Guides.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.