الفهرس:
- تفاصيل متعلقة بحزم الشيفرة: - يمكن استيراد الحزمة نفسها أكثر من مرة في ملف مصدري واحد.
- تفاصيل متعلقة بالتحكم في سير التنفيذ: - يمكن وضع فرع
defaultفي كتلتيswitchوselectقبل جميع فروعcaseأو بعد جميع فروعcaseأو بين فروعcase. - لا يمكن تكرار تعبيرات الحالات الثابتة العددية في كتلة
switch، لكن يمكن تكرار التعبيرات المنطقية. - تُقيَّم تعبيرات switch في كتلة
switchدائماً كقيم مُنمَّطة. - تعبير switch الافتراضي في كتلة
switchهو القيمة المُنمَّطةtrueمن النوعboolالمُعرَّف مسبقاً. - أحياناً، يمكن وضع القوس المفتوح
{لكتلة شيفرة صريحة في السطر التالي. - يجب أن تكون بعض كتل فروع
caseصريحة. - يمكن لنداءات الدوال المؤجَّلة المتداخلة تعديل قيم نتائج الإرجاع للدوال المحتوية.
- قد تكون بعض نداءات
recoverبلا أثر. - إنهاء برنامج بنداء الدالة
os.Exitوإنهاء goroutine بنداء الدالةruntime.Goexit. - تفاصيل متعلقة بالمعاملات: - أسبقية معامل الزيادة
++ومعامل النقصان--أدنى من أسبقية معامل إلغاء الإشارة*ومعامل أخذ العنوان&، وهما أدنى من أسبقية معامل اختيار الخاصية.في المحدِّدات. - تعتمد قاعدة استنتاج النوع للمعامَل به الأيسر غير المُنمَّط في عملية إزاحة البتات على ما إذا كان المعامَل به الأيمن ثابتاً أم لا.
- تفاصيل متعلقة بالمؤشرات: - يمكن تحويل قيم نوعَي مؤشر مختلفَي النوعين الكامنين إلى بعضهما بعضاً إذا كان النوعان القاعديان لنوعيهما الكامنين يتشاركان النوع الكامن نفسه.
- قد تكون عناوين قيم مختلفة صفرية الحجم متساوية أو غير متساوية.
- قد يكون النوع القاعدي لنوع مؤشر هو نوع المؤشر نفسه.
- تفصيل حول اختصارات المحدِّدات.
- تفاصيل متعلقة بالحاويات: - يمكن أحياناً تبسيط القيم الحرفية المركّبة المتداخلة.
- في بعض السيناريوهات، لا بأس في استخدام مؤشرات المصفوفات كمصفوفات.
- استرجاع العناصر من خرائط
nilلن يُنتج panic. النتيجة هي قيمة صفرية للعنصر. - حذف مدخل من خريطة
nilلن يُنتج panic. إنها عملية بلا أثر. - قد تشترك شريحة النتيجة لنداء الدالة
appendفي بعض العناصر مع الشريحة الأصلية أو لا تشترك. - قد يكون طول شريحة فرعية أكبر من الشريحة الأساسية التي اشتُقّت منها.
- اشتقاق شريحة فرعية من شريحة
nilلا بأس به إذا كانت جميع الفهارس المستخدمة في تعبير الشريحة الفرعية صفراً. وشريحة النتيجة تكون أيضاً شريحةnil. - المرور بـ range على خرائط
nilأو شرائحnilلا بأس به، إنها عملية بلا أثر. - المرور بـ range على مؤشر مصفوفة
nilلا بأس به إذا جرى تجاهل متغير التكرار الثاني أو حذفه. - يمكن تعديل طول الشريحة وسعتها كلٌّ على حدة.
- يجب أن تكون الفهارس في القيم الحرفية المركّبة للشرائح والمصفوفات ثوابت وغير سالبة.
- لا يمكن تكرار الفهارس أو المفاتيح الثابتة في القيم الحرفية المركّبة للشرائح/المصفوفات/الخرائط.
- عناصر المصفوفات غير القابلة للعنونة غير قابلة للعنونة أيضاً، لكن عناصر الشرائح غير القابلة للعنونة قابلة للعنونة دائماً.
- لا بأس في اشتقاق شرائح فرعية من شرائح غير قابلة للعنونة، لكن لا يُسمح بذلك من مصفوفات غير قابلة للعنونة. لا بأس في أخذ عناوين عناصر الشرائح غير القابلة للعنونة، لكن لا يُسمح بذلك لعناصر المصفوفات غير القابلة للعنونة.
- وضع مدخلات يكون فيها
NaNمفتاحاً في خريطة يشبه وضع المدخلات في ثقب أسود. - قد تكون سعة شريحة النتيجة لتحويل من نص إلى شريحة byte/rune أكبر من طول شريحة النتيجة.
- بالنسبة للشريحة
s، الحلقةfor i = range s {...}ليست مكافئة للحلقةfor i = 0; i < len(s); i++ {...}. - ترتيب التكرار على الخرائط غير مضمون أن يبقى نفسه من تكرار إلى الذي يليه.
- إذا أُنشئ مدخل خريطة أثناء تكرار على الخريطة، فقد يُكرَّر على ذلك المدخل أثناء التكرار أو قد يُتخطّى.
- تفاصيل متعلقة بالدوال والطرق: - لا يمكن لنداء دالة متعدد النتائج أن يختلط بتعبيرات أخرى عندما يُستخدم النداء كمصادر في إسناد أو كمعطيات لنداء دالة آخر.
- تُقيَّم بعض نداءات الدوال في وقت التصريف.
- تقابل كل طريقة دالة ضمنية.
- تفاصيل متعلقة بالواجهات: - مقارنة قيمتين من نوع واجهة لهما النوع الديناميكي نفسه غير القابل للمقارنة تُنتج panic.
- يمكن استخدام تأكيدات النوع لتحويل قيمة من نوع واجهة إلى نوع واجهة آخر، حتى لو لم يكن نوع الواجهة الأول مُنفِّذاً لنوع الواجهة الثاني.
- وجود النتيجة الثانية الاختيارية لتأكيد نوع فاشل من عدمه سيؤثر على سلوك تأكيد النوع.
- حول تأكيدات التحويل إلى واجهة المستحيلة التي يمكن كشفها في وقت التصريف.
- قيمتا
errorالمُعادتان من نداءَيerrors.Newبالمعطى نفسه غير متساويتين. - تفاصيل متعلقة بالقنوات: - القنوات المستقبِلة فقط لا يمكن إغلاقها.
- إرسال قيمة إلى قناة مغلقة يُنظر إليه كعملية غير حاجبة، وهذه العملية تُسبّب panic.
- تفاصيل إضافية متعلقة بالأنواع والقيم: - يمكن تعريف الأنواع داخل أجسام الدوال.
- بالنسبة للمصرِّف القياسي، قد تُعامَل الحقول صفرية الحجم في بنية كقيمة بحجم بايت واحد.
- NaN != NaN، و Inf == Inf.
- أسماء الطرق وحقول البنى غير المُصدَّرة من حزم مختلفة تُنظر إليها كأسماء مختلفة.
- في مقارنات قيم البنى، ستُتجاهل الحقول الفارغة.
- متفرقات: - تلزم الأقواس في عدة سيناريوهات نادرة ليجري تصريف الشيفرة بنجاح.
- فيضان المكدس غير قابل للاستعادة.
- بعض ترتيبات تقييم التعبيرات في Go تعتمد على تنفيذ المصرِّف.
- تفاصيل متعلقة بالحزم القياسية: - قد تختلف نتائج
reflect.DeepEqual(x, y)وx == y. - تعيد الطريقة
reflect.Value.Bytes()قيمة[]byte، وقد لا يكون نوع عنصرها،byte، هو نفسه نوع قيمة الشريحة Go التي يمثلها وسيط المستقبِل. - ينبغي أن نستخدم
os.IsNotExist(err)بدلاً منerr == os.ErrNotExistللتحقق من وجود ملف أم لا. - تعامل الحزمة القياسية
flagالأعلام المنطقية للأوامر بشكل مختلف عن أعلام الأعداد الصحيحة والنصوص. [Sp|Fp|P]rintfتدعم دوالها المعطيات الموضعية.
يمكن استيراد الحزمة نفسها أكثر من مرة في ملف مصدري واحد.
يمكن لملف Go مصدري أن يستورد الحزمة نفسها عدة مرات، لكن يجب أن تكون أسماء الاستيراد مختلفة. وتشير عمليات استيراد الحزمة نفسها هذه إلى نسخة الحزمة ذاتها.
مثلاً:
package main
import "fmt"
import "io"
import inout "io"
func main() {
fmt.Println(&inout.EOF == &io.EOF) // true
}
يمكن وضع فرع default في كتلتي switch وselect قبل جميع فروع case أو بعد جميع فروع case أو بين فروع case.
مثلاً:
switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
default: fmt.Println("n == 2")
}
switch n := rand.Intn(3); n {
default: fmt.Println("n == 2")
case 0: fmt.Println("n == 0")
case 1: fmt.Println("n == 1")
}
switch n := rand.Intn(3); n {
case 0: fmt.Println("n == 0")
default: fmt.Println("n == 2")
case 1: fmt.Println("n == 1")
}
var x, y chan int
select {
case <-x:
case y <- 1:
default:
}
select {
case <-x:
default:
case y <- 1:
}
select {
default:
case <-x:
case y <- 1:
}
لا يمكن تكرار تعبيرات الحالات الثابتة العددية في كتلة switch، لكن يمكن تكرار التعبيرات المنطقية.
مثلاً، يفشل البرنامج التالي في التصريف.
package main
func main() {
switch 123 {
case 123:
case 123: // خطأ: حالة مكررة
}
}
لكن البرنامج التالي يُصرَّف بنجاح.
package main
func main() {
switch false {
case false:
case false:
}
}
للاطلاع على الأسباب، يرجى قراءة هذه المسألة. هذا السلوك يعتمد على المصرِّف. في الواقع، لا يسمح مصرِّف Go القياسي أيضاً بتكرار تعبيرات حالات النصوص، لكن gccgo يسمح بذلك.
تُقيَّم تعبيرات switch في كتلة switch دائماً كقيم مُنمَّطة.
مثلاً، يُنظر إلى تعبير switch وهو 123 في كتلة switch التالية كقيمة من النوع int بدلاً من عدد صحيح غير مُنمَّط. لذا يفشل البرنامج التالي في التصريف.
package main
func main() {
switch 123 {
case int64(123): // خطأ: أنواع غير متوافقة
case uint32(789): // خطأ: أنواع غير متوافقة
}
}
تعبير switch الافتراضي في كتلة switch هو القيمة المُنمَّطة true من النوع bool المُعرَّف مسبقاً.
مثلاً، سيطبع البرنامج التالي true.
package main
import "fmt"
func main() {
switch { // <=> switch true {
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
أحياناً، يمكن وضع القوس المفتوح { لكتلة شيفرة صريحة في السطر التالي.
مثلاً:
package main
func main() {
var i = 0
Outer:
for
{ %% لا بأس به في السطر التالي
switch
{ %% لا بأس به في السطر التالي
case i == 5:
break Outer
default:
i++
}
}
}
ما النتيجة التي سيطبعها البرنامج التالي؟ true أم false؟ الجواب هو true. يرجى قراءة قواعد فواصل الأسطر في Go للأسباب.
package main
import "fmt"
func False() bool {
return false
}
func main() {
switch False()
{
case true: fmt.Println("true")
case false: fmt.Println("false")
}
}
يجب أن تكون بعض كتل فروع case صريحة.
مثلاً، يفشل البرنامج التالي في التصريف.
func demo(n, m int) (r int) {
switch n {
case 123:
if m > 0 {
goto End
}
r++
End: // خطأ صياغي: جملة مفقودة بعد التسمية
default:
r = 1
}
return
}
لكي يُصرَّف بنجاح، يجب أن تكون كتلة شيفرة فرع case صريحة:
func demo(n, m int) (r int) {
switch n {
case 123: {
if m > 0 {
goto End
}
r++
End:
}
default:
r = 1
}
return
}
بدلاً من ذلك، يمكننا إضافة فاصلة منقوطة بعد تعريف التسمية End::
func demo(n, m int) (r int) {
switch n {
case 123:
if m > 0 {
goto End
}
r++
End:;
default:
r = 1
}
return
}
يرجى قراءة قواعد فواصل الأسطر في Go للأسباب.
يمكن لنداءات الدوال المؤجَّلة المتداخلة تعديل قيم نتائج الإرجاع لأعمق دالة محتوية لها.
مثلاً:
package main
import "fmt"
func F() (r int) {
defer func() {
r = 789
}()
return 123 // <=> r = 123; return
}
func main() {
fmt.Println(F()) // 789
}
قد تكون بعض نداءات recover بلا أثر.
ينبغي أن نستدعي الدالة recover في الأماكن الصحيحة. يرجى قراءة الأماكن الصحيحة لاستدعاء الدالة المدمجة recover للتفاصيل.
إنهاء برنامج بنداء الدالة os.Exit وإنهاء goroutine بنداء الدالة runtime.Goexit.
يمكننا إنهاء برنامج من أي دالة بنداء الدالة os.Exit. يأخذ نداء الدالة os.Exit رمزاً من النوع int كمعطى ويعيد الرمز إلى نظام التشغيل.
مثال:
// exit-example.go
package main
import "os"
import "time"
func main() {
go func() {
time.Sleep(time.Second)
os.Exit(1)
}()
select{}
}
شغّله:
$ go run a.go
exit status 1
$ echo $?
1
يمكننا إنهاء goroutine بنداء الدالة runtime.Goexit. لا تملك الدالة runtime.Goexit أي وسائط.
في المثال التالي، لن تُطبع كلمة Java.
package main
import "fmt"
import "runtime"
func main() {
c := make(chan int)
go func() {
defer func() {c <- 1}()
defer fmt.Println("Go")
func() {
defer fmt.Println("C")
runtime.Goexit()
}()
fmt.Println("Java")
}()
<-c
}
أسبقية معامل الزيادة ++ ومعامل النقصان -- أدنى من أسبقية معامل إلغاء الإشارة * ومعامل أخذ العنوان &، وهما أدنى من أسبقية معامل اختيار الخاصية . في المحدِّدات.
مثلاً:
package main
import "fmt"
type T struct {
x int
y *int
}
func main() {
var t T
p := &t.x // <=> p := &(t.x)
fmt.Printf("%T\n", p) // *int
*p++ // <=> (*p)++
*p-- // <=> (*p)--
t.y = p
a := *t.y // <=> *(t.y)
fmt.Printf("%T\n", a) // int
}
تعتمد قاعدة استنتاج النوع للمعامَل به الأيسر غير المُنمَّط في عملية إزاحة البتات على ما إذا كان المعامَل به الأيمن ثابتاً أم لا.
package main
func main() {
}
const M = 2
// يُصرَّف بنجاح. يُستنتج أن 1.0 قيمة من النوع int.
var _ = 1.0 << M
var N = 2
// يفشل في التصريف. يُستنتج أن 1.0 قيمة من النوع float64.
var _ = 1.0 << N
يرجى قراءة هذا المقال للأسباب.
يمكن تحويل قيم نوعَي مؤشر مختلفَي النوعين الكامنين إلى بعضهما بعضاً إذا كان النوعان القاعديان لنوعيهما الكامنين يتشاركان النوع الكامن نفسه.
مثال:
package main
type MyInt int64
type Ta *int64
type Tb *MyInt
func main() {
var a Ta
var b Tb
// التحويل المباشر غير مسموح به.
//a = Ta(b) // خطأ
// لكن التحويل غير المباشر ممكن.
y := (*MyInt)(b)
x := (*int64)(y)
a = x // <=> السطر التالي
a = (*int64)(y) // <=> السطر التالي
a = (*int64)((*MyInt)(b))
_ = a
}
قد تكون عناوين قيم مختلفة صفرية الحجم متساوية أو غير متساوية.
ما إذا كان عنوانا قيمتين صفريتي الحجم متساويين أم لا يعتمد على المصرِّف وإصدار المصرِّف.
package main
import "fmt"
func main() {
a := struct{}{}
b := struct{}{}
x := struct{}{}
y := struct{}{}
m := [10]struct{}{}
n := [10]struct{}{}
o := [10]struct{}{}
p := [10]struct{}{}
fmt.Println(&x, &y, &o, &p)
// بالنسبة لمصرِّف Go القياسي (1.25.n)،
// تهرب x و y و o و p إلى الكومة، لكن
// تُخصَّص a و b و m و n على المكدس.
fmt.Println(&a == &b) // false
fmt.Println(&x == &y) // true
fmt.Println(&a == &x) // false
fmt.Println(&m == &n) // false
fmt.Println(&o == &p) // true
fmt.Println(&n == &p) // false
}
المخرجات المشار إليها في الشيفرة أعلاه تخص مصرِّف Go القياسي 1.25.n.
قد يكون النوع القاعدي لنوع مؤشر هو نوع المؤشر نفسه.
مثال:
package main
func main() {
type P *P
var p P
p = &p
p = **************p
}
وبالمثل،
- يمكن أن يكون نوع عنصر نوع شريحة هو نوع الشريحة نفسه،
- ويمكن أن يكون نوع عنصر نوع خريطة هو نوع الخريطة نفسه،
- ويمكن أن يكون نوع عنصر نوع قناة هو نوع القناة نفسه،
- ويمكن أن يكون نوعا المعطى والنتيجة لنوع دالة هو نوع الدالة نفسه.
package main
func main() {
type S []S
type M map[string]M
type C chan C
type F func(F) F
s := S{0:nil}
s[0] = s
m := M{"Go": nil}
m["Go"] = m
c := make(C, 3)
c <- c; c <- c; c <- c
var f F
f = func(F)F {return f}
_ = s[0][0][0][0][0][0][0][0]
_ = m["Go"]["Go"]["Go"]["Go"]
<-<-<-c
f(f(f(f(f))))
}
تفصيل حول اختصارات المحدِّدات.
بالنسبة لقيمة مؤشر، سواء كان نوعها مسمّى أم لا، إذا كان النوع القاعدي لنوعها (نوع المؤشر) نوع بنية، فيمكننا عندئذٍ اختيار حقول قيمة البنية المشار إليها بواسطة قيمة المؤشر من خلال قيمة المؤشر. لكن إذا كان نوع قيمة المؤشر نوعاً مسمّى، فلا يمكننا اختيار طرق قيمة البنية المشار إليها بواسطة قيمة المؤشر من خلال قيمة المؤشر.
package main
type T struct {
x int
}
func (T) m(){} // يملك T طريقة واحدة.
type P *T // نوع مؤشر مسمّى بمستوى واحد.
type PP *P // نوع مؤشر مسمّى بمستويين.
func main() {
var t T
var tp = &t
var tpp = &tp
var p P = tp
var pp PP = &p
tp.x = 12 // صحيح
p.x = 34 // صحيح
pp.x = 56 // خطأ: النوع PP ليس له حقل أو طريقة x
tpp.x = 78 // خطأ: النوع **T ليس له حقل أو طريقة x
tp.m() // صحيح. النوع *T يملك أيضاً طريقة "m".
p.m() // خطأ: النوع P ليس له حقل أو طريقة m
pp.m() // خطأ: النوع PP ليس له حقل أو طريقة m
tpp.m() // خطأ: النوع **T ليس له حقل أو طريقة m
}
يمكن أحياناً تبسيط القيم الحرفية المركّبة المتداخلة.
يرجى قراءة يمكن تبسيط القيم الحرفية المركّبة المتداخلة للتفاصيل.
في بعض السيناريوهات، لا بأس في استخدام مؤشرات المصفوفات كمصفوفات.
يرجى قراءة استخدام مؤشرات المصفوفات كمصفوفات للتفاصيل.
استرجاع العناصر من خرائط nil لن يُنتج panic. النتيجة هي قيمة صفرية للعنصر.
مثلاً، الدالتان Foo1 وFoo2 متكافئتان، لكن الدالة Foo2 أنظف بكثير من الدالة Foo1.
func Foo1(m map[string]int) int {
if m != nil {
return m["foo"]
}
return 0
}
func Foo2(m map[string]int) int {
return m["foo"]
}
حذف مدخل من خريطة nil لن يُنتج panic. إنها عملية بلا أثر.
مثلاً، البرنامج التالي لن يُنتج panic.
package main
func main() {
var m map[string]int // nil
delete(m, "foo")
}
قد تشترك شريحة النتيجة لنداء الدالة append في بعض العناصر مع الشريحة الأصلية أو لا تشترك.
يرجى قراءة إلحاق عناصر الحاويات وحذفها للتفاصيل.
قد يكون طول شريحة فرعية أكبر من الشريحة الأساسية التي اشتُقّت منها.
مثلاً،
package main
import "fmt"
func main() {
s := make([]int, 3, 9)
fmt.Println(len(s)) // 3
s2 := s[2:7]
fmt.Println(len(s2)) // 5
}
يرجى قراءة اشتقاق الشرائح من المصفوفات والشرائح للتفاصيل.
اشتقاق شريحة فرعية من شريحة nil لا بأس به إذا كانت جميع الفهارس المستخدمة في تعبير الشريحة الفرعية صفراً. وشريحة النتيجة تكون أيضاً شريحة nil.
مثلاً، البرنامج التالي لن يُنتج panic في وقت التشغيل.
package main
import "fmt"
func main() {
var x []int // nil
a := x[:]
b := x[0:0]
c := x[:0:0]
// طباعة "true" ثلاث مرات.
fmt.Println(a == nil, b == nil, c == nil)
}
يرجى قراءة اشتقاق الشرائح من المصفوفات والشرائح للتفاصيل.
المرور بـ range على خرائط nil أو شرائح nil لا بأس به، إنها عملية بلا أثر.
مثلاً، البرنامج التالي يُصرَّف بنجاح.
package main
func main() {
var s []int // nil
for range s {
}
var m map[string]int // nil
for range m {
}
}
المرور بـ range على مؤشر مصفوفة nil لا بأس به إذا جرى تجاهل متغير التكرار الثاني أو حذفه.
مثلاً، سيطبع البرنامج التالي 01234.
package main
import "fmt"
func main() {
var a *[5]int // nil
for i, _ := range a {
fmt.Print(i)
}
}
يمكن تعديل طول الشريحة وسعتها كلٌّ على حدة.
يمكننا تعديل طول الشريحة وسعتها كلٌّ على حدة عبر طريقة الانعكاس. يرجى قراءة تعديل خاصيتَي الطول والسعة لقيمة شريحة بشكل فردي للتفاصيل.
يجب أن تكون الفهارس في القيم الحرفية المركّبة للشرائح والمصفوفات ثوابت وغير سالبة.
مثلاً، تفشل الشيفرة التالية في التصريف.
var k = 1
// خطأ: يجب أن يكون الفهرس ثابتاً عددياً صحيحاً غير سالب
var x = [2]int{k: 1}
// خطأ: يجب أن يكون الفهرس ثابتاً عددياً صحيحاً غير سالب
var y = []int{k: 1}
لاحظ أن المفاتيح في القيم الحرفية المركّبة للخرائط غير مطلوب أن تكون ثوابت.
لا يمكن تكرار الفهارس أو المفاتيح الثابتة في القيم الحرفية المركّبة للشرائح/المصفوفات/الخرائط.
مثلاً، تفشل الشيفرة التالية في التصريف.
// خطأ: فهرس مكرر في القيمة الحرفية للمصفوفة: 1
var a = []bool{0: false, 1: true, 1: true}
// خطأ: فهرس مكرر في القيمة الحرفية للمصفوفة: 0
var b = [...]string{0: "foo", 1: "bar", 0: "foo"}
// خطأ: مفتاح مكرر "foo" في القيمة الحرفية للخريطة
var c = map[string]int{"foo": 1, "foo": 2}
يمكن استخدام هذه الميزة في تأكيد بعض الشروط في وقت التصريف.
عناصر المصفوفات غير القابلة للعنونة غير قابلة للعنونة أيضاً، لكن عناصر الشرائح غير القابلة للعنونة قابلة للعنونة دائماً.
السبب هو أن عناصر قيمة المصفوفة والمصفوفة ستُخزَّن في كتلة الذاكرة نفسها عندما تُخزَّن المصفوفة في الذاكرة. لكن الوضع مختلف بالنسبة للشرائح.
مثال:
package main
func main() {
// القيم الحرفية المركّبة للحاويات غير قابلة للعنونة.
// لا بأس في أخذ عناوين عناصر القيمة الحرفية للشريحة.
_ = &[]int{1}[0] // صحيح
// لا يمكن أخذ عناوين عناصر القيمة الحرفية للمصفوفة.
_ = &[5]int{}[0] // خطأ
// لا بأس في تعديل عناصر القيمة الحرفية للشريحة.
[]int{1,2,3}[1] = 9 // صحيح
// لا يمكن تعديل عناصر القيمة الحرفية للمصفوفة.
[3]int{1,2,3}[1] = 9 // خطأ
}
لا بأس في اشتقاق شرائح فرعية من شرائح غير قابلة للعنونة، لكن لا يُسمح بذلك من مصفوفات غير قابلة للعنونة.
السبب هو نفسه كما في التفصيل السابق.
مثال:
package main
func main() {
// عناصر الخرائط غير قابلة للعنونة في Go.
// الأسطر التالية تُصرَّف بنجاح. اشتقاق
// شرائح من شرائح غير قابلة للعنونة مسموح به.
_ = []int{6, 7, 8, 9}[1:3]
var ms = map[string][]int{"abc": {0, 1, 2, 3}}
_ = ms["abc"][1:3]
// الأسطر التالية تفشل في التصريف. اشتقاق
// شرائح من مصفوفات غير قابلة للعنونة غير مسموح به.
/*
_ = [...]int{6, 7, 8, 9}[1:3] // خطأ
var ma = map[string][4]int{"abc": {0, 1, 2, 3}}
_ = ma["abc"][1:3] // خطأ
*/
}
وضع مدخلات يكون فيها NaN مفتاحاً في خريطة يشبه وضع المدخلات في ثقب أسود.
السبب هو NaN != NaN، وهو تفصيل آخر سيُوصَف أدناه. قبل Go 1.12، لم يكن بالإمكان العثور على العناصر التي يكون فيها NaN مفتاحاً إلا في حلقة for-range؛ ومنذ Go 1.12، يمكن أيضاً طبع العناصر التي يكون فيها NaN مفتاحاً بدوال شبيهة بـ fmt.Print.
package main
import "fmt"
import "math"
func main() {
var a = math.NaN()
fmt.Println(a) // NaN
var m = map[float64]int{}
m[a] = 123
v, present := m[a]
fmt.Println(v, present) // 0 false
m[a] = 789
v, present = m[a]
fmt.Println(v, present) // 0 false
fmt.Println(m) // map[NaN:789 NaN:123]
delete(m, a) // بلا أثر
fmt.Println(m) // map[NaN:789 NaN:123]
for k, v := range m {
fmt.Println(k, v)
}
// الحلقة أعلاه تُخرج:
// NaN 123
// NaN 789
}
يرجى ملاحظة أنه قبل Go 1.12، كان نداءا fmt.Println(m) يطبعان كلاهما map[NaN:<nil> NaN:<nil>].
قد تكون سعة شريحة النتيجة لتحويل من نص إلى شريحة byte/rune أكبر من طول شريحة النتيجة.
ينبغي ألا نفترض أن طول شريحة النتيجة وسعتها متساويان دائماً.
في المثال التالي، إذا حُذف سطر fmt.Println الأخير، فإن مخرجَي السطرين السابقين له يطبعان القيمة نفسها 5؛ وإلا، فسيطبع أحدهما 5 والآخر 8 (بالنسبة لمصرِّف Go القياسي 1.25.n).
package main
import "fmt"
func main() {
s := "abcde"
x := []byte(s) // len(s) == 1
fmt.Println(cap([]byte(s))) // 32
fmt.Println(cap(x)) // 8
fmt.Println(x)
}
ستُكتب بعض الشيفرة المعيبة إذا افترضنا أن طول شريحة النتيجة وسعتها متساويان دائماً.
بالنسبة للشريحة s، الحلقة for i = range s {...} ليست مكافئة للحلقة for i = 0; i < len(s); i++ {...}.
قد تكون القيمتان النهائيتان لمتغير التكرار i مختلفتين بين الحلقتين.
package main
import "fmt"
var i int
func fa(s []int, n int) int {
i = n
for i = 0; i < len(s); i++ {}
return i
}
func fb(s []int, n int) int {
i = n
for i = range s {}
return i
}
func main() {
s := []int{2, 3, 5, 7, 11, 13}
fmt.Println(fa(s, -1), fb(s, -1)) // 6 5
s = nil
fmt.Println(fa(s, -1), fb(s, -1)) // 0 -1
}
ترتيب التكرار على الخرائط غير مضمون أن يبقى نفسه من تكرار إلى الذي يليه.
مثلاً، البرنامج التالي لن يعمل إلى ما لا نهاية:
package main
import "fmt"
func f(m map[byte]byte) string {
bs := make([]byte, 0, 2*len(m))
for k, v := range m {
bs = append(bs, k, v)
}
return string(bs)
}
func main() {
m := map[byte]byte{'a':'A', 'b':'B', 'c':'C'}
s0 := f(m)
for i := 1; ; i++{
if s := f(m); s != s0 {
fmt.Println(s0)
fmt.Println(s)
fmt.Println(i)
return
}
}
}
يرجى ملاحظة أن المدخلات في نتيجة ترميز JSON للخرائط تُرتَّب حسب مفاتيحها. ومنذ Go 1.12، فإن طبع الخرائط (بدوال الطباعة في الحزمة القياسية fmt) يُنتج أيضاً مدخلات مرتَّبة.
إذا أُنشئ مدخل خريطة أثناء تكرار على الخريطة، فقد يُكرَّر على ذلك المدخل أثناء التكرار أو قد يُتخطّى.
برهان:
package main
import "fmt"
func main() {
m := map[int]int{0: 0, 1: 100, 2: 200}
r, n, i:= len(m), len(m), 0
for range m {
m[n] = n*100
n++
i++
}
fmt.Printf("%d new entries, iterate %d and skip %d\n",
i, i - r, n - i,
)
}
شكراً لـ Valentin Deleplace على اقتراحَي التفصيلين أعلاه.
لا يمكن لنداء دالة متعدد النتائج أن يختلط بتعبيرات أخرى عندما يُستخدم النداء كمصادر في إسناد أو كمعطيات لنداء دالة آخر.
يرجى قراءة استخدام نداءات الدوال كتعبيرات للتفاصيل.
تُقيَّم بعض نداءات الدوال في وقت التصريف.
يرجى قراءة بعض نداءات الدوال تُقيَّم في وقت التصريف للتفاصيل.
تقابل كل طريقة دالة ضمنية.
يرجى قراءة تقابل كل طريقة دالة ضمنية للتفاصيل.
مقارنة قيمتين من نوع واجهة لهما النوع الديناميكي نفسه غير القابل للمقارنة تُنتج panic.
مثلاً:
package main
func main() {
var x interface{} = []int{}
_ = x == x // panic
}
يمكن استخدام تأكيدات النوع لتحويل قيمة من نوع واجهة إلى نوع واجهة آخر، حتى لو لم يكن نوع الواجهة الأول مُنفِّذاً لنوع الواجهة الثاني.
مثلاً:
package main
type Foo interface {
foo()
}
type T int
func (T) foo() {}
func main() {
var x interface{} = T(123)
// السطران التاليان يفشلان في التصريف للسبب
// نفسه: النوع interface{} لا يُنفِّذ Foo.
/*
var _ Foo = x // خطأ
var _ = Foo(x) // خطأ
*/
// لكن السطر التالي يُصرَّف ويعمل بنجاح.
var _ = x.(Foo) // صحيح
}
وجود النتيجة الثانية الاختيارية لتأكيد نوع فاشل من عدمه سيؤثر على سلوك تأكيد النوع.
إذا كانت النتيجة الثانية الاختيارية موجودة في تأكيد نوع فاشل، فلن يُنتج تأكيد النوع panic. وإلا، فسيحدث panic. مثلاً:
package main
func main() {
var x interface{} = true
// يفشل التأكيد، لكنه لا يُسبّب panic.
_, _ = x.(int)
// يفشل التأكيد، ما يُسبّب panic.
_ = x.(int)
}
حول تأكيدات التحويل إلى واجهة المستحيلة التي يمكن كشفها في وقت التصريف.
في وقت التصريف، يمكن استنتاج أن بعض تأكيدات التحويل إلى واجهة مستحيلة النجاح. مثلاً، التأكيد الظاهر في الشيفرة التالية:
package main
type Ia interface {
m()
}
type Ib interface {
m() int
}
type T struct{}
func (T) m() {}
func main() {
var x Ia = T{}
_ = x.(Ib) // panic: main.T ليس main.Ib
}
لن تجعل مثل هذه التأكيدات عملية تصريف الشيفرة تفشل (لكن البرنامج سيدخل في panic في وقت التشغيل). منذ سلسلة أدوات Go 1.15، يُنبّه الأمر go vet على مثل هذه التأكيدات.
قيمتا error المُعادتان من نداءَي errors.New بالمعطى نفسه غير متساويتين.
السبب هو أن الدالة errors.New ستنسخ معطى النص المُدخل وتستخدم مؤشراً إلى النص المنسوخ كقيمة ديناميكية لقيمة error المُعادة. ونداءان مختلفان سيُنتجان مؤشرين مختلفين.
package main
import "fmt"
import "errors"
func main() {
notfound := "not found"
a, b := errors.New(notfound), errors.New(notfound)
fmt.Println(a == b) // false
}
القنوات المستقبِلة فقط لا يمكن إغلاقها.
مثلاً، تفشل الشيفرة التالية في التصريف.
package main
func main() {
}
func foo(c <-chan int) {
close(c) // خطأ: لا يمكن إغلاق قناة مستقبِلة فقط
}
إرسال قيمة إلى قناة مغلقة يُنظر إليه كعملية غير حاجبة، وهذه العملية تُسبّب panic.
مثلاً، في البرنامج التالي، عندما يُختار فرع case الثاني، سيُنتج panic في وقت التشغيل.
package main
func main() {
var c = make(chan bool)
close(c)
select {
case <-c:
case c <- true: // panic: إرسال على قناة مغلقة
default:
}
}
يمكن تعريف الأنواع داخل أجسام الدوال.
يمكن تعريف الأنواع في أجسام الدوال. مثلاً،
package main
func main() {
type T struct{}
type S = []int
}
بالنسبة للمصرِّف القياسي، قد تُعامَل الحقول صفرية الحجم في بنية كقيمة بحجم بايت واحد.
يرجى قراءة بند الأسئلة الشائعة هذا للتفاصيل.
NaN != NaN، و Inf == Inf.
يتبع هذا معيار IEEE-754 ويتوافق مع معظم لغات البرمجة الأخرى:
package main
import "fmt"
import "math"
func main() {
var a = math.Sqrt(-1.0)
fmt.Println(a) // NaN
fmt.Println(a == a) // false
var x = 0.0
var y = 1.0 / x
var z = 2.0 * y
fmt.Println(y, z, y == z) // +Inf +Inf true
}
أسماء الطرق وحقول البنى غير المُصدَّرة من حزم مختلفة تُنظر إليها كأسماء مختلفة.
مثلاً، إذا عُرِّفت الأنواع التالية في الحزمة foo:
package foo
type I = interface {
about() string
}
type S struct {
a string
}
func (s S) about() string {
return s.a
}
وعُرِّفت الأنواع التالية في الحزمة bar:
package bar
type I = interface {
about() string
}
type S struct {
a string
}
func (s S) about() string {
return s.a
}
فإن:
- قيم النوعين
Sمن الحزمتين لا يمكن تحويل إحداهما إلى الأخرى. - نوعا الواجهة
Sمن الحزمتين يدلان على مجموعتَي طرق مختلفتين. - النوع
foo.Sلا يُنفِّذ نوع الواجهةbar.I. - النوع
bar.Sلا يُنفِّذ نوع الواجهةfoo.I.
package main
import "包2/foo"
import "包2/bar"
func main() {
var x foo.S
var y bar.S
var _ foo.I = x
var _ bar.I = y
// الأسطر التالية تفشل في التصريف.
x = foo.S(y)
y = bar.S(x)
var _ foo.I = y
var _ bar.I = x
}
في مقارنات قيم البنى، ستُتجاهل الحقول الفارغة.
الحقول الفارغة هي الحقول التي يكون اسمها المعرّف الفارغ _. سيطبع البرنامج التالي true.
package main
import "fmt"
type T struct {
_ int
_ bool
}
func main() {
var t1 = T{123, true}
var t2 = T{789, false}
fmt.Println(t1 == t2) // true
}
تلزم الأقواس في عدة سيناريوهات نادرة ليجري تصريف الشيفرة بنجاح.
مثلاً:
package main
type T struct{x, y int}
func main() {
// كل سطر من الأسطر الثلاثة التالية يجعل الشيفرة
// تفشل في التصريف. بعض الأقواس "{}" تُربك المصرِّفات.
/*
if T{} == T{123, 789} {}
if T{} == (T{123, 789}) {}
if (T{}) == T{123, 789} {}
var _ = func()(nil) // يُنظر إلى nil كنوع
*/
// يجب أن نضيف أقواساً مثل السطرين التاليين
// ليجري تصريف الشيفرة بنجاح.
if (T{} == T{123, 789}) {}
if (T{}) == (T{123, 789}) {}
var _ = (func())(nil) // يُنظر إلى nil كقيمة
}
فيضان المكدس ليس panic.
بالنسبة لمصرِّفات Go السائدة حالياً، تعدّ فياضانات المكدس أخطاء قاتلة. وبمجرد حدوث فيضان مكدس، سينهار البرنامج بأكمله دون طرق للاستعادة.
package main
func f() {
f()
}
func main() {
defer func() {
recover() // لا جدوى منه في منع انهيار البرنامج
}()
f()
}
نتيجة التشغيل:
runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow
runtime stack:
...
لمزيد من حالات الانهيار، يرجى قراءة مقال الويكي هذا.
بعض ترتيبات تقييم التعبيرات في Go تعتمد على تنفيذ المصرِّف.
يرجى قراءة ترتيبات تقييم التعبيرات في Go للتفاصيل.
قد تختلف نتائج reflect.DeepEqual(x, y) وx == y.
سيعيد نداء الدالة reflect.DeepEqual(x, y) دائماً false إذا كان نوعا معطياه مختلفين، بينما قد يعيد x == y القيمة true حتى لو كان نوعا المعامَلين بهما مختلفين.
الفرق الثاني هو أن نداء DeepEqual بمعطيين من قيمتي مؤشر من النوع نفسه يعيد ما إذا كانت القيمتان المشار إليهما بواسطة المؤشرين متساويتين بعمق أم لا. لذا قد يعيد النداء true حتى لو كان المؤشران غير متساويين.
الفرق الثالث هو أن نتيجة نداء DeepEqual قد تعيد true إذا كان كلا معطياه في سلسلتَي إشارة دائريتين (لتجنب التكرار اللانهائي في النداء).
الفرق الرابع هو أنه لا يُتوقع عموماً أن يُنتج نداء الدالة reflect.DeepEqual(x, y) panic، بينما سيُنتج x == y panic إذا كان المعامَلان بهما قيمتي واجهة ونوعاهما الديناميكيان متطابقين وغير قابلين للمقارنة.
مثال يوضح هذه الفروق:
package main
import (
"fmt"
"reflect"
)
func main() {
type Book struct {page int}
x := struct {page int}{123}
y := Book{123}
fmt.Println(reflect.DeepEqual(x, y)) // false
fmt.Println(x == y) // true
z := Book{123}
fmt.Println(reflect.DeepEqual(&z, &y)) // true
fmt.Println(&z == &y) // false
type Node struct{peer *Node}
var q, r, s Node
q.peer = &q // تكوين سلسلة إشارة دائرية
r.peer = &s // تكوين سلسلة إشارة دائرية
s.peer = &r
println(reflect.DeepEqual(&q, &r)) // true
fmt.Println(q == r) // false
var f1, f2 func() = nil, func(){}
fmt.Println(reflect.DeepEqual(f1, f1)) // true
fmt.Println(reflect.DeepEqual(f2, f2)) // false
var a, b interface{} = []int{1, 2}, []int{1, 2}
fmt.Println(reflect.DeepEqual(a, b)) // true
fmt.Println(a == b) // panic
}
لاحظ أنه إذا كان معطيا نداء DeepEqual كليهما قيمتي دالة، فلن يعيد النداء true إلا إذا كان معطيا الدالة كلاهما nil ونوعاهما متطابقين. وهذا مشابه لمقارنة قيم حاويات تحتوي عناصرها على قيم دوال أو مقارنة قيم بنى تحتوي حقولها على قيم دوال. لكن يرجى أيضاً ملاحظة أن نتيجة مقارنة شريحتين (من النوع نفسه) هي true دائماً إذا كانت الشريحتان تشتركان تماماً في العناصر نفسها (بعبارة أخرى، لهما الطول نفسه ولكل زوج من عناصرهما المتناظرة العنوان نفسه). مثال:
package main
import (
"fmt"
"reflect"
)
func main() {
a := [1]func(){func(){}}
b := a
fmt.Println(reflect.DeepEqual(a, a)) // false
fmt.Println(reflect.DeepEqual(a[:], a[:])) // true
fmt.Println(reflect.DeepEqual(a[:], b[:])) // false
a[0], b[0] = nil, nil
fmt.Println(reflect.DeepEqual(a[:], b[:])) // true
}
تعيد الطريقة reflect.Value.Bytes() قيمة []byte، وقد لا يكون نوع عنصرها، byte، هو نفسه نوع قيمة الشريحة Go التي يمثلها وسيط المستقبِل.
بافتراض أن النوع الكامن لنوع معرَّف MyByte هو النوع المُعرَّف مسبقاً byte، نعلم أن نظام أنواع Go يمنع التحويلات بين قيمتَي []MyByte و[]byte. لكن يبدو أن تنفيذ الطريقة Bytes للنوع reflect.Value يخالف هذا القيد جزئياً وبشكل غير مقصود، بالسماح بتحويل قيمة []MyByte إلى []byte.
مثال:
package main
import "bytes"
import "fmt"
import "reflect"
type MyByte byte
func main() {
var mybs = []MyByte{'a', 'b', 'c'}
var bs []byte
// bs = []byte(mybs) // هذا السطر يفشل في التصريف
v := reflect.ValueOf(mybs)
bs = v.Bytes() // صحيح. مخالفة لنظام أنواع Go.
fmt.Println(bytes.HasPrefix(bs, []byte{'a', 'b'})) // true
bs[1], bs[2] = 'r', 't'
fmt.Printf("%s \n", mybs) // art
}
لكن يبدو أن هذه المخالفة ليست ضارة. بل على العكس، تحقق بعض الفوائد. فمثلاً، بفضل هذه المخالفة، يمكننا استخدام الدوال في الحزمة القياسية bytes مع قيم []MyByte.
لاحظ أن الطريقة reflect.Value.Bytes() قد تُحذف لاحقاً.
ينبغي أن نستخدم os.IsNotExist(err) بدلاً من err == os.ErrNotExist للتحقق من وجود ملف أم لا.
قد يؤدي استخدام err == os.ErrNotExist إلى تفويت أخطاء.
package main
import (
"fmt"
"os"
)
func main() {
_, err := os.Stat("a-nonexistent-file.abcxyz")
fmt.Println(os.IsNotExist(err)) // true
fmt.Println(err == os.ErrNotExist) // false
}
بالنسبة للمشاريع التي تدعم Go 1.13+ فقط، يُوصى أكثر باستخدام errors.Is(err, os.ErrNotExist) للتحقق من وجود ملف أم لا.
package main
import (
"errors"
"fmt"
"os"
)
func main() {
_, err := os.Stat("a-nonexistent-file.abcxyz")
fmt.Println(errors.Is(err, os.ErrNotExist)) // true
}
تعامل الحزمة القياسية flag الأعلام المنطقية للأوامر بشكل مختلف عن أعلام الأعداد الصحيحة والنصوص.
توجد ثلاث صيغ لتمرير خيارات الأعلام.
-flag، للأعلام المنطقية فقط.-flag=x، لأي علم.-flag x، للأعلام غير المنطقية فقط.
ويرجى ملاحظة أن العلم المنطقي بالصيغة الأولى يُنظر إليه كالعلم الأخير، وتُعامل جميع العناصر التالية له كمعطيات.
package main
import "fmt"
import "flag"
var b = flag.Bool("b", true, "a boolean flag")
var i = flag.Int("i", 123, "an integer flag")
var s = flag.String("s", "hi", "a string flag")
func main() {
flag.Parse()
fmt.Print("b=", *b, ", i=", *i, ", s=", *s, "\n")
fmt.Println("arguments:", flag.Args())
}
إذا شغّلنا هذا البرنامج بالأعلام والمعطيات الموضحة أدناه
./exampleProgram -b false -i 789 -s bye arg0 arg1
فستكون المخرجات
b=true, i=123, s=hi
arguments: [false -i 789 -s bye arg0 arg1]
من الواضح أن هذه المخرجات ليست ما نتوقعه.
ينبغي أن نمرر الأعلام والمعطيات مثل
./exampleProgram -b=false -i 789 -s bye arg0 arg1
أو
./exampleProgram -i 789 -s bye -b arg0 arg1
للحصول على المخرجات التي نتوقعها:
b=true, i=789, s=bye
arguments: [arg0 arg1]
تدعم دوال [Sp|Fp|P]rintf المعطيات الموضعية.
سيطبع البرنامج التالي coco.
package main
import "fmt"
func main() {
// السطر التالي يطبع: coco
fmt.Printf("%[2]v%[1]v%[2]v%[1]v", "o", "c")
}