كما في C، تدعم Go أيضًا أنواع البنى. ستقدّم هذه المقالة المعرفة الأساسية بأنواع البنى وقيمها في Go.
أنواع البنى وحرفيات أنواع البنى
يبدأ كل حرفي نوع بنية غير مُسمّى بالكلمة المفتاحية struct، تليها سلسلة من تعريفات الحقول محصورة بين {}. عمومًا، يتكوّن كل تعريف حقل من اسم ونوع. ويمكن أن يكون عدد حقول نوع البنية صفرًا.
فيما يلي حرفي نوع بنية غير مُسمّى:
struct {
title string
author string
pages int
}
لنوع البنية أعلاه ثلاثة حقول. نوعا الحقلين title وauthor كلاهما string. ونوع الحقل pages هو int.
تسمّي بعض المقالات الحقول أيضًا متغيرات أعضاء.
يمكن تعريف الحقول المتتالية من النوع نفسه معًا.
struct {
title, author string
pages int
}
حجم نوع البنية هو مجموع أحجام جميع أنواع حقوله مضافًا إليه عدد من بايتات الحشو. تُستخدم بايتات الحشو لمحاذاة عناوين الذاكرة لبعض الحقول. ويمكننا تعلّم الحشو ومحاذاة عناوين الذاكرة في مقالة لاحقة.
حجم نوع بنية بلا حقول هو صفر.
يمكن ربط وسم بحقل بنية عند تعريف الحقل. وسوم الحقول اختيارية، والقيمة الافتراضية لكل وسم حقل هي نص فارغ. تسمح الصياغة بأي من صيغتي القيمة الحرفية النصية لوسوم الحقول. لكن عمليًا، ينبغي أن تُقدَّم وسوم حقول البنية كأزواج مفتاح-قيمة، وأن يُقدَّم كل وسم كقيمة حرفية نصية خام (...)، في حين تُقدَّم كل قيمة داخل وسم كقيمة حرفية نصية مُفسَّرة ("..."). مثلًا:
struct {
Title string `json:"title" myfmt:"s1"`
Author string `json:"author,omitempty" myfmt:"s2"`
Pages int `json:"pages,omitempty" myfmt:"n1"`
X, Y bool `myfmt:"b1"`
}
لاحظ أن وسَمي الحقلين X وY في المثال أعلاه متطابقان (مع أن استخدام وسوم الحقول بهذه الطريقة ممارسة سيئة).
يمكننا استخدام طريقة الانعكاس لفحص معلومات وسم الحقل.
يتوقف الغرض من كل وسم حقل على التطبيق. في المثال أعلاه، يمكن أن تساعد وسوم الحقول الدوالَّ في الحزمة القياسية encoding/json على تحديد أسماء الحقول في نصوص JSON، أثناء عملية ترميز قيم البنى إلى نصوص JSON أو فك ترميز نصوص JSON إلى قيم بنى. ولن تُرمِّز دوال الحزمة القياسية encoding/json وتفكّ ترميز سوى حقول البنية المُصدَّرة، ولهذا تكون الأحرف الأولى من أسماء الحقول في المثال أعلاه كلها كبيرة.
ليس من الجيد استخدام وسوم الحقول كتعليقات.
وخلافًا للغة C، لا تدعم بنى Go أنواع الاتحاد (unions).
جميع أنواع البنى المعروضة أعلاه غير مُسمّاة. وعمليًا، أنواع البنى المُسمّاة أكثر شيوعًا.
لا يمكن استخدام سوى الحقول المُصدَّرة لأنواع البنى الظاهرة في حزمة ما في حزم أخرى، وذلك باستيراد الحزمة. ويمكننا اعتبار حقول البنية غير المُصدَّرة متغيرات أعضاء خاصة/محمية.
تؤثر وسوم الحقول وترتيب تعريفات الحقول في نوع البنية في هوية نوع البنية. لا يكون نوعا بنية غير مُسمّيين متطابقين إلا إذا كان لهما التسلسل نفسه من تعريفات الحقول. ولا يكون تعريفا حقلين متطابقين إلا إذا كانت أسماؤهما على التوالي وأنواعهما على التوالي ووسومهما على التوالي كلها متطابقة، وكان كلاهما حقلًا مضمَّنًا أو لم يكن أي منهما كذلك. يُرجى ملاحظة أن اسمَي حقلين غير مُصدَّرين ينتميان إلى حزمتين مختلفتين يُنظر إليهما دائمًا على أنهما اسمان مختلفان.
لا يمكن أن يكون لنوع البنية حقل من نوع البنية نفسه، لا مباشرةً ولا تعاوديًا.
قيم البنى الحرفية والتعامل مع قيم البنى
في Go، تُسمّى الصيغة T{...}، حيث يجب أن يكون T حرفي نوع أو اسم نوع، قيمة حرفية مركّبة (composite literal)، وتُستخدم كقيم حرفية لبعض أصناف الأنواع، ومنها أنواع البنى وأنواع الحاويات التي ستُقدَّم لاحقًا.
لاحظ أن حرفي النوع T{...} قيمة ذات نوع، ونوعها هو T.
إذا كان لدينا نوع بنية S نوعه الكامن (underlying type) هو struct{x int; y bool}، فيمكن تمثيل القيمة الصفرية لـ S بالصيغتين التاليتين من صيغ القيم الحرفية المركّبة للبنى:
S{0, false}. في هذه الصيغة، لا توجد أسماء حقول، لكن يجب تقديم جميع قيم الحقول وفق ترتيب تعريفات الحقول.S{x: 0, y: false}وS{y: false, x: 0}وS{x: 0}وS{y: false}وS{}. في هذه الصيغة، كل عنصر حقل اختياري، وترتيب عناصر الحقول غير مهم. وستُضبط قيم الحقول الغائبة على أنها القيم الصفرية لأنواعها على التوالي. لكن إذا وُجد عنصر حقل، فيجب تقديمه بصيغةFieldName: FieldValue. وترتيب عناصر الحقول في هذه الصيغة لا يهم. وصيغةS{}هي أكثر تمثيلات القيمة الصفرية استخدامًا للنوعS.
إذا كان S نوع بنية مستوردًا من حزمة أخرى، فيُوصى باستخدام الصيغة الثانية للحفاظ على التوافق. تخيّل الحالة التي يضيف فيها القائم على صيانة الحزمة حقلًا جديدًا للنوع S، فسيجعل ذلك استخدام الصيغة الأولى غير صالح.
بالتأكيد، يمكننا أيضًا استخدام القيم الحرفية المركّبة للبنى لتمثيل قيمة بنية غير صفرية.
لقيمة v من النوع S، يمكننا استخدام v.x وv.y، ويُسمّيان مُحدِّدين (أو تعبيرَي مُحدِّد)، لتمثيل قيم حقول v. وتُسمّى v مستقبِل المُحدِّدات.
فيما بعد، نسمّي النقطة . في المُحدِّد معامل اختيار الخاصية.
مثال:
package main
import (
"fmt"
)
type Book struct {
title, author string
pages int
}
func main() {
book := Book{"Go 101", "Tapir", 256}
fmt.Println(book) // {Go 101 Tapir 256}
// إنشاء قيمة كتاب بصيغة أخرى.
// جميع الحقول الثلاثة مُحدَّدة.
book = Book{author: "Tapir", pages: 256, title: "Go 101"}
// لم يُحدَّد أي من الحقول. الحقلان title
// و author كلاهما ""، والحقل pages هو 0.
book = Book{}
// تحديد الحقل author فقط. الحقل title
// هو "" والحقل pages هو 0.
book = Book{author: "Tapir"}
// تهيئة قيمة بنية باستخدام المُحدِّدات.
var book2 Book // <=> book2 := Book{}
book2.author = "Tapir Liu"
book2.pages = 300
fmt.Println(book2.pages) // 300
}
تكون الفاصلة الأخيرة , في القيمة الحرفية المركّبة اختيارية إذا كان العنصر الأخير في القيمة الحرفية وعلامة الإغلاق } على السطر نفسه. وإلا فإن الفاصلة الأخيرة , مطلوبة. لمزيد من التفاصيل، يُرجى قراءة قواعد فواصل الأسطر في Go.
var _ = Book {
author: "Tapir",
pages: 256,
title: "Go 101", // هنا، يجب وجود الفاصلة ","
}
// الفاصلة "," الأخيرة في السطر التالي اختيارية.
var _ = Book{author: "Tapir", pages: 256, title: "Go 101",}
حول إسنادات قيم البنى
عند إسناد قيمة بنية إلى قيمة بنية أخرى، يكون الأثر كأثر إسناد كل حقل على حدة.
func f() {
book1 := Book{pages: 300}
book2 := Book{"Go 101", "Tapir", 256}
book2 = book1
// السطر أعلاه مكافئ
// للأسطر التالية.
book2.title = book1.title
book2.author = book1.author
book2.pages = book1.pages
}
لا يمكن إسناد قيمتَي بنيتين إحداهما إلى الأخرى إلا إذا كان نوعاهما متطابقين، أو كان لنوعَي قيمتَي البنية نوع كامن متطابق (مع مراعاة وسوم الحقول) وكان أحد النوعين على الأقل نوعًا غير مُسمّى.
قابلية حقول البنية للعنونة
حقول البنية القابلة للعنونة قابلة للعنونة أيضًا. وحقول البنية غير القابلة للعنونة غير قابلة للعنونة أيضًا. ولا يمكن تعديل حقول البنى غير القابلة للعنونة. وجميع القيم الحرفية المركّبة، بما فيها القيم الحرفية المركّبة للبنى، غير قابلة للعنونة.
مثال:
package main
import "fmt"
func main() {
type Book struct {
Pages int
}
var book = Book{} // book قابلة للعنونة
p := &book.Pages
*p = 123
fmt.Println(book) // {123}
// السطران التاليان يفشلان في التصريف، لأن
// Book{} غير قابلة للعنونة، وكذلك Book{}.Pages.
/*
Book{}.Pages = 123
p = &(Book{}.Pages) // <=> p = &Book{}.Pages
*/
}
لاحظ أن أسبقية معامل اختيار الخاصية . في المُحدِّد أعلى من أسبقية معامل أخذ العنوان &.
القيم الحرفية المركّبة غير قابلة للعنونة لكن يمكن أخذ عناوينها
عمومًا، لا يمكن أخذ العناوين إلا للقيم القابلة للعنونة. لكن توجد في Go صيغة سكر نحوي (syntactic sugar) تسمح لنا بأخذ عناوين القيم الحرفية المركّبة. والسكر النحوي استثناء في الصياغة لجعل البرمجة أكثر سهولة.
مثلًا،
package main
func main() {
type Book struct {
Pages int
}
// Book{100} غير قابلة للعنونة لكن
// يمكن أخذ عنوانها.
p := &Book{100} // <=> tmp := Book{100}; p := &tmp
p.Pages = 200
}
في مُحدِّدات الحقول، يمكن أن يكون إلغاء إشارة المستقبِلات ضمنيًا
في المثال التالي، للتبسيط، يمكن كتابة (*bookN).pages بالصيغة bookN.pages. وبعبارة أخرى، تُلغى إشارة bookN في المُحدِّدات المبسَّطة.
package main
func main() {
type Book struct {
pages int
}
book1 := &Book{100} // book1 مؤشر بنية
book2 := new(Book) // book2 مؤشر بنية آخر
// استخدام مؤشرات البنى كبنى.
book2.pages = book1.pages
// هذا السطر الأخير مكافئ للسطر أعلاه.
// بعبارة أخرى، إذا كان المستقبِل مؤشرًا،
// فستُلغى الإشارة إليه ضمنيًا.
(*book2).pages = (*book1).pages
}
حول مقارنات قيم البنى
لا يكون نوع البنية قابلًا للمقارنة إلا إذا لم يكن أي من أنواع حقوله (بما فيها الحقول التي أسماؤها المعرّف الفارغ _) غيرَ قابل للمقارنة.
لا تكون قيمتا بنيتين قابلتين للمقارنة إلا إذا أمكن إسناد إحداهما إلى الأخرى وكان نوعاهما قابلين للمقارنة. بعبارة أخرى، لا يمكن مقارنة قيمتَي بنيتين إحداهما بالأخرى إلا إذا كان نوعا قيمتَي البنية (القابلان للمقارنة) متطابقين، أو كان نوعاهما الكامنان متطابقين (مع مراعاة وسوم الحقول) وكان أحد النوعين على الأقل غير مُسمّى.
عند مقارنة قيمتَي بنيتين من النوع نفسه، تُقارن كل زوج من حقولهما المتناظرة (بالترتيب الظاهر في الشيفرة المصدرية). ولا تكون قيمتا البنية متساويتين إلا إذا كانت جميع حقولهما المتناظرة متساوية. وتتوقف المقارنة قبل الأوان عندما يُوجد زوج حقول غير متساوٍ أو يحدث panic. وفي المقارنات، تُهمَل الحقول التي أسماؤها المعرّف الفارغ _.
حول تحويلات قيم البنى
يمكن تحويل قيم نوعَي البنية S1 وS2 إلى نوعَي بعضهما بعضًا إذا كان لـ S1 وS2 النوع الكامن نفسه (بإهمال وسوم الحقول). وبوجه خاص، إذا كان S1 أو S2 نوعًا غير مُسمّى وكان نوعاهما الكامنان متطابقين (مع مراعاة وسوم الحقول)، فيمكن أن تكون التحويلات بين قيمهما ضمنية.
إذا كان لدينا أنواع البنى S0 وS1 وS2 وS3 وS4 في قصاصة الشيفرة التالية،
- لا يمكن تحويل قيم النوع
S0إلى الأنواع الأربعة الأخرى، والعكس صحيح، لأن أسماء الحقول المتناظرة مختلفة. - يمكن تحويل قيمتين من نوعين مختلفين من بين
S1وS2وS3وS4إلى نوعَي بعضهما بعضًا.
وبوجه خاص،
- يمكن تحويل قيم النوع الذي يدل عليه
S2ضمنيًا إلى النوعS3، والعكس صحيح. - يمكن تحويل قيم النوع الذي يدل عليه
S2ضمنيًا إلى النوعS4، والعكس صحيح.
لكن،
- يجب تحويل قيم النوع الذي يدل عليه
S2صراحةً إلى النوعS1، والعكس صحيح. - يجب تحويل قيم النوع
S3صراحةً إلى النوعS4، والعكس صحيح.
package main
type S0 struct {
y int "foo"
x bool
}
// S1 اسم مستعار لنوع غير مُسمّى.
type S1 = struct {
x int "foo"
y bool
}
// S2 أيضًا اسم مستعار لنوع غير مُسمّى.
type S2 = struct {
x int "bar"
y bool
}
// إذا أُهملت وسوم الحقول، فإن النوعين
// الكامنين لـ S3(S4) و S1 متطابقان. وإذا
// أُخذت وسوم الحقول في الحسبان، فإن النوعين
// الكامنين لـ S3(S4) و S1 مختلفان.
type S3 S2 // S3 نوع معرَّف (فلذلك مُسمّى)
type S4 S3 // S4 نوع معرَّف (فلذلك مُسمّى)
var v0, v1, v2, v3, v4 = S0{}, S1{}, S2{}, S3{}, S4{}
func f() {
v1 = S1(v2); v2 = S2(v1)
v1 = S1(v3); v3 = S3(v1)
v1 = S1(v4); v4 = S4(v1)
v2 = v3; v3 = v2 // يمكن أن تكون التحويلات ضمنية
v2 = v4; v4 = v2 // يمكن أن تكون التحويلات ضمنية
v3 = S3(v4); v4 = S4(v3)
}
في الواقع، لا يمكن إسناد قيمتَي بنيتين (أو مقارنتهما) إحداهما إلى الأخرى إلا إذا أمكن تحويل إحداهما ضمنيًا إلى نوع الأخرى.
يمكن استخدام أنواع البنى المجهولة في تعريفات الحقول
يُسمح باستخدام أنواع البنى المجهولة أنواعًا لحقول نوع بنية آخر. ويُسمح أيضًا باستخدام حرفيات أنواع البنى المجهولة في القيم الحرفية المركّبة.
مثال:
var aBook = struct {
// نوع الحقل author هو
// نوع بنية مجهول.
author struct {
firstName, lastName string
gender bool
}
title string
pages int
}{
author: struct { // نوع بنية مجهول
firstName, lastName string
gender bool
}{
firstName: "Mark",
lastName: "Twain",
},
title: "The Million Pound Note",
pages: 96,
}
عمومًا، ولتحسين قابلية القراءة، لا يُوصى باستخدام حرفيات أنواع البنى المجهولة في القيم الحرفية المركّبة.
المزيد حول أنواع البنى
هناك بعض المواضيع المتقدمة المرتبطة بأنواع البنى. ستُشرح لاحقًا في تضمين الأنواع وتخطيطات الذاكرة.