ستقدّم هذه المقالة تعريفات الثوابت والمتغيرات في Go. كما سيُقدَّم مفهوم القيم غير المنمَّطة (untyped values) والتحويلات الصريحة (explicit conversions).
تُسمّى جميع القيم الحرفية (literals) التي قُدّمت في المقال السابق ثوابت غير مسمّاة (أو ثوابت حرفية)، باستثناء false وtrue، فهما ثابتان مسمّيان مُعرَّفان مسبقًا (مدمجان). وستُقدَّم تعريفات الثوابت المسمّاة المخصّصة أدناه في هذه المقالة.
القيم غير المنمَّطة والقيم المنمَّطة
في Go، بعض القيم غير منمَّطة. والقيمة غير المنمَّطة تعني أن نوع القيمة لم يتأكّد بعد. وعلى العكس من ذلك، فإن نوع القيمة المنمَّطة محدَّد.
لمعظم القيم غير المنمَّطة نوع افتراضي واحد لكل منها. والقيمة nil المُعرَّفة مسبقًا هي القيمة غير المنمَّطة الوحيدة التي ليس لها نوع افتراضي. وسنتعلّم المزيد عن nil في مقالات أخرى من Go 101 لاحقًا.
جميع الثوابت الحرفية (الثوابت غير المسمّاة) قيم غير منمَّطة. في الواقع، معظم القيم غير المنمَّطة في Go هي ثوابت حرفية وثوابت مسمّاة (ستُقدَّم أدناه في هذه المقالة). وتشمل القيم غير المنمَّطة الأخرى القيمة nil المذكورة للتوّ وبعض النتائج المنطقية التي تُعيدها بعض العمليات والتي ستُقدَّم في مقالات أخرى لاحقًا.
يُحدَّد النوع الافتراضي للثابت الحرفي من صيغته الحرفية.
- النوع الافتراضي للنص الحرفي هو
string. - النوع الافتراضي للقيمة الحرفية المنطقية هو
bool. - النوع الافتراضي للقيمة الحرفية الصحيحة هو
int. - النوع الافتراضي للقيمة الحرفية من نوع rune هو
rune(المعروف أيضًا بـint32). - النوع الافتراضي للقيمة الحرفية العشرية هو
float64. - إذا احتوت قيمة حرفية على جزء تخيّلي، فنوعها الافتراضي هو
complex128.
التحويلات الصريحة للثوابت غير المنمَّطة
مثل العديد من اللغات الأخرى، تدعم Go أيضًا تحويلات القيم. يمكننا استخدام الصيغة T(v) لتحويل قيمة v إلى النوع الذي يشير إليه T (أو ببساطة، النوع T). إذا كان التحويل T(v) قانونيًا، يعتبر المصرِّف T(v) قيمة منمَّطة من النوع T. وبالطبع، لجعل التحويل T(v) قانونيًا لنوع معيّن T، لا يمكن أن تكون القيمة v اعتباطية.
تنطبق القواعد المذكورة أدناه على كلٍّ من الثوابت الحرفية التي قُدّمت في المقال السابق والثوابت المسمّاة غير المنمَّطة التي ستُقدَّم قريبًا.
لقيمة ثابتة غير منمَّطة v، هناك حالتان يكون فيهما T(v) قانونيًا.
v(أو القيمة الحرفية التي تدل عليهاv) قابلة للتمثيل كقيمة من نوع أساسيT. والقيمة الناتجة ثابت منمَّط من النوعT.- النوع الافتراضي للقيمة
vنوع صحيح (intأوrune) وTنوع نصي. نتيجةT(v)نص من النوعTويحتوي على تمثيل UTF-8 للعدد الصحيح كنقطة ترميز Unicode. أما قيم الأعداد الصحيحة الخارجة عن نطاق نقاط ترميز Unicode الصالحة فتُنتج نصوصًا ممثَّلة بـ"\uFFFD"(المعروفة أيضًا بـ"\xef\xbf\xbd"). و0xFFFDهي نقطة الترميز لمحرف الاستبدال في Unicode. ويكون النص الناتج عن تحويل عدد صحيح محتويًا دائمًا على rune واحدة فقط لا غير. (ملاحظة: قد تسمح إصدارات Go اللاحقة بتحويل أعداد rune أو byte الصحيحة إلى نصوص فقط. منذ سلسلة أدوات Go 1.15، يحذّر الأمرgo vetمن التحويلات من أعداد صحيحة غير rune وغير byte إلى نصوص.)
في الواقع، لا تشترط الحالة الثانية أن تكون v ثابتًا. إذا كانت v ثابتًا، فإن نتيجة التحويل تكون ثابتًا أيضًا؛ وإلا فليست النتيجة ثابتًا.
على سبيل المثال، جميع التحويلات التالية قانونية.
// يحدث تقريب في الأسطر الثلاثة التالية.
complex128(1 + -1e-1000i) // 1.0+0.0i
float32(0.49999999) // 0.5
float32(17000000000000000)
// لا يوجد تقريب في هذه الأسطر.
float32(123)
uint(1.0)
int8(-123)
int16(6+0i)
complex128(789)
string(65) // "A"
string('A') // "A"
string('\u68ee') // "森"
string(-1) // "\uFFFD"
string(0xFFFD) // "\uFFFD"
string(0x2FFFFFFFF) // "\uFFFD"
والتحويلات التالية جميعها غير قانونية.
// 1.23 غير قابلة للتمثيل كقيمة من نوع int.
int(1.23)
// -1 غير قابل للتمثيل كقيمة من نوع uint8.
uint8(-1)
// 1+2i غير قابل للتمثيل كقيمة من نوع float64.
float64(1+2i)
// الثابت -1e+1000 يتجاوز سعة float64.
float64(-1e1000)
// الثابت 0x10000000000000000 يتجاوز سعة int.
int(0x10000000000000000)
// النوع الافتراضي للقيمة 65.0 هو float64،
// وهو ليس نوعًا صحيحًا.
string(65.0)
// النوع الافتراضي للقيمة 66+0i هو complex128،
// وهو ليس نوعًا صحيحًا.
string(66+0i)
من الأمثلة أعلاه، نعلم أن الثابت غير المنمَّط (مثل -1e1000 و0x10000000000000000) قد لا يكون قادرًا حتى على التمثيل كقيمة من نوعه الافتراضي.
لاحظ أنه في بعض الأحيان يجب كتابة صيغة التحويل الصريح على شكل (T)(v) لتجنّب الالتباس. وتحدث هذه الحالات غالبًا عندما لا يكون T معرّفًا.
سنتعلّم المزيد من قواعد التحويل الصريح لاحقًا في مقالات أخرى من Go 101.
مقدمة عن استنتاج الأنواع في Go
تدعم Go استنتاج الأنواع (type deduction). بعبارة أخرى، في كثير من الحالات لا يحتاج المبرمجون إلى تحديد أنواع بعض القيم في الشيفرة صراحةً. إذ يستنتج المصرِّف أنواع هذه القيم من السياق.
كثيرًا ما يُسمّى استنتاج الأنواع أيضًا استنتاج النوع (type inference).
في شيفرة Go، إذا احتاج موضع ما إلى قيمة من نوع معيّن وكانت قيمة غير منمَّطة (غالبًا ثابت) قابلة للتمثيل كقيمة من ذلك النوع، فيمكن استخدام القيمة غير المنمَّطة في ذلك الموضع. وسيعتبر المصرِّف القيمة غير المنمَّطة قيمة منمَّطة من ذلك النوع. وتشمل هذه المواضع معامَلًا به في عملية معامل، أو معطى في استدعاء دالة، أو قيمة وجهة أو قيمة مصدر في إسناد، وما إلى ذلك.
بعض الحالات لا تفرض متطلبات على أنواع القيم المستخدمة. وإذا استُخدمت قيمة غير منمَّطة في مثل هذه الحالة، سيعاملها المصرِّف كقيمة منمَّطة من نوعها الافتراضي.
يمكن النظر إلى حالتي استنتاج الأنواع على أنهما تحويلان ضمنيان.
ستعرض الأقسام التالية عن تعريف الثوابت والمتغيرات بعض حالات استنتاج الأنواع. وستُقدَّم المزيد من قواعد وحالات استنتاج الأنواع في مقالات أخرى.
تعريفات الثوابت
الثوابت غير المسمّاة كلها قيم منطقية وعددية ونصية. ومثل الثوابت غير المسمّاة، لا يمكن أن تكون الثوابت المسمّاة أيضًا إلا قيمًا منطقية وعددية ونصية. وتُستخدم الكلمة المفتاحية const لتعريف الثوابت المسمّاة. ويحتوي البرنامج التالي على بعض تعريفات الثوابت.
package main
// تعريف ثابتين منفردين. نعم،
// يمكن استخدام أحرف غير ASCII في المعرّفات.
const π = 3.1416
const Pi = π // <=> const Pi = 3.1416
// تعريف عدة ثوابت في مجموعة.
const (
No = !Yes
Yes = true
MaxDegrees = 360
Unit = "radian"
)
func main() {
// تعريف عدة ثوابت في سطر واحد.
const TwoPi, HalfPi, Unit2 = π * 2, π * 0.5, "degree"
}
تسمّي مواصفة لغة Go كل سطر يحتوي على رمز = في مجموعة تعريف الثوابت أعلاه مواصفة ثابت (constant specification).
في المثال أعلاه، الرمز * هو معامل الضرب والرمز ! هو معامل النفي المنطقي. وستُقدَّم المعاملات في المقال التالي، المعاملات الشائعة.
الرمز = يعني "الربط" وليس "الإسناد". وينبغي تفسير كل مواصفة ثابت على أنها ربط معرّف معلن بقيمة حرفية أساسية مقابلة. يُرجى قراءة القسم الأخير في هذه المقالة لمزيد من التوضيح.
في المثال أعلاه، الثابتان المسمّيان π وPi مربوطان بالقيمة الحرفية 3.1416. وقد يُستخدم الثابتان المسمّيان في مواضع كثيرة في الشيفرة. وبدون تعريفات الثوابت، ستُكتب القيمة الحرفية 3.1416 في تلك المواضع. وإذا أردنا تغيير القيمة الحرفية إلى 3.14 لاحقًا، فسيحتاج الأمر إلى تعديل مواضع كثيرة. أما بمساعدة تعريفات الثوابت، فلن تظهر القيمة الحرفية 3.1416 إلا في تعريف ثابت واحد، لذا يكفي تعديل موضع واحد فقط. وهذا هو الغرض الرئيسي من تعريفات الثوابت.
لاحقًا، سنستخدم مصطلح القيم غير الثابتة للدلالة على القيم التي ليست ثوابت. والمتغيرات التي ستُقدَّم أدناه تنتمي كلها إلى أحد أنواع القيم غير الثابتة.
لاحظ أنه يمكن تعريف الثوابت على مستوى الحزمة (خارج أي جسم دالة) وداخل أجسام الدوال. وتسمّى الثوابت المعرَّفة داخل أجسام الدوال ثوابت محلية. أما الثوابت المعرَّفة خارج أي جسم دالة فتسمّى ثوابت على مستوى الحزمة. وكثيرًا ما نسمّي ثوابت مستوى الحزمة ثوابت عامة (global constants).
ترتيب تعريف ثابتين على مستوى الحزمة ليس مهمًا. ففي المثال أعلاه، يمكن تبادل ترتيب تعريف No وYes.
جميع الثوابت المعرَّفة في المثال السابق غير منمَّطة. والنوع الافتراضي للثابت المسمّى غير المنمَّط هو نفس نوع القيمة الحرفية المربوطة به.
الثوابت المسمّاة المنمَّطة
يمكننا تعريف ثوابت منمَّطة، والثوابت المنمَّطة كلها مسمّاة. في المثال التالي، الثوابت الأربعة المعرَّفة كلها قيم منمَّطة. نوعا X وY هما float32 ونوعا A وB هما int64.
const X float32 = 3.14
const (
A, B int64 = -3, 5
Y float32 = 2.718
)
إذا عُرِّفت عدة ثوابت منمَّطة في مواصفة الثابت نفسها، فيجب أن تكون أنواعها متماثلة، تمامًا مثل الثابتين A وB في المثال أعلاه.
يمكننا أيضًا استخدام تحويلات صريحة لتزويد المصرِّف بمعلومات كافية لاستنتاج أنواع الثوابت المسمّاة المنمَّطة. ومقتطف الشيفرة أعلاه مكافئ للمقتطف التالي، حيث X وY وA وB كلها ثوابت منمَّطة.
const X = float32(3.14)
const (
A, B = int64(-3), int64(5)
Y = float32(2.718)
)
إذا رُبطت قيمة حرفية أساسية بثابت منمَّط، فيجب أن تكون القيمة الحرفية الأساسية قابلة للتمثيل كقيمة من نوع الثابت. وتعريفات الثوابت المنمَّطة التالية غير صالحة.
// خطأ: 256 يتجاوز سعة uint8
const a uint8 = 256
// خطأ: 256 يتجاوز سعة uint8
const b = uint8(255) + uint8(1)
// خطأ: 128 يتجاوز سعة int8
const c = int8(-128) / int8(-1)
// خطأ: -1 يتجاوز سعة uint
const MaxUint_a = uint(^0)
// خطأ: -1 يتجاوز سعة uint
const MaxUint_b uint = ^0
في الأمثلة أعلاه والتالية، ^ هو معامل النفي على مستوى البتات (bitwise-not).
تعريف الثابت المنمَّط التالي صالح على أنظمة التشغيل 64-بت، لكنه غير صالح على أنظمة 32-بت. لأن كل قيمة uint لها 32 بتًا فقط على أنظمة 32-بت. و(1 << 64) - 1 غير قابلة للتمثيل كقيم 32-بت. (هنا، << هو معامل الإزاحة اليسرى على مستوى البتات.)
const MaxUint uint = (1 << 64) - 1
إذن كيف نعرّف ثابتًا منمَّطًا من نوع uint ونربط به أكبر قيمة uint؟ استخدم الطريقة التالية بدلًا من ذلك.
const MaxUint = ^uint(0)
وبالمثل، يمكننا تعريف ثابت منمَّط من نوع int وربط أكبر قيمة int به. (هنا، >> هو معامل الإزاحة اليمنى على مستوى البتات.)
const MaxInt = int(^uint(0) >> 1)
ويمكن استخدام طريقة مشابهة للحصول على عدد بتات الكلمة الأصلية (native word)، والتحقق مما إذا كان نظام التشغيل الحالي 32-بت أو 64-بت.
// NativeWordBits إما 64 أو 32.
const NativeWordBits = 32 << (^uint(0) >> 63)
const Is64bitOS = ^uint(0) >> 63 != 0
const Is32bitOS = ^uint(0) >> 32 == 0
هنا، != و== هما معاملَا "لا يساوي" و"يساوي".
الإكمال التلقائي في تعريفات الثوابت
في تعريف الثوابت على نمط المجموعة، يمكن أن تكون مواصفات الثوابت الأخرى غير مكتملة باستثناء مواصفة الثابت الأولى. فمواصفة الثابت غير المكتملة تحتوي على قائمة معرّفات فقط. وسيكمل المصرِّف الأسطر غير المكتملة تلقائيًا نيابةً عنا بنسخ الجزء المفقود من أول مواصفة ثابت مكتملة تسبقها. على سبيل المثال، في وقت التصريف، سيكمل المصرِّف الشيفرة التالية تلقائيًا
const (
X float32 = 3.14
Y // هنا يجب وجود معرّف واحد
Z // هنا يجب وجود معرّف واحد
A, B = "Go", "language"
C, _
// في السطر أعلاه، يجب وجود
// المعرّف الفارغ.
)
لتصبح
const (
X float32 = 3.14
Y float32 = 3.14
Z float32 = 3.14
A, B = "Go", "language"
C, _ = "Go", "language"
)
iota في تعريفات الثوابت
ميزة الإكمال التلقائي بالإضافة إلى ميزة مولّد الثوابت iota تجلب سهولة كبيرة لبرمجة Go. إن iota ثابت مُعرَّف مسبقًا لا يمكن استخدامه إلا في تعريفات ثوابت أخرى. وهو معرَّف كما يلي
const iota = 0
لكن قيمة iota في الشيفرة قد لا تكون 0 دائمًا. فعندما يُستخدم الثابت iota المُعرَّف مسبقًا في تعريف ثابت مخصّص، ففي وقت التصريف، وداخل تعريف الثابت المخصّص، ستُعاد قيمته إلى 0 عند مواصفة الثابت الأولى من كل مجموعة ثوابت وتزداد بمقدار 1 مواصفة ثابت بعد مواصفة. بعبارة أخرى، في مواصفة الثابت رقم n من تعريف ثابت، تكون قيمة iota هي n (بدءًا من الصفر). لذا لا يكون iota مفيدًا إلا في تعريفات الثوابت على نمط المجموعة.
إليك مثالًا يستخدم ميزتي الإكمال التلقائي ومولّد الثوابت iota معًا. يُرجى قراءة التعليقات لمعرفة ما سيحدث في وقت التصريف. والرمز + في هذا المثال هو معامل الجمع.
package main
func main() {
const (
k = 3 // الآن، iota == 0
m float32 = iota + .5 // m float32 = 1 + .5
n // n float32 = 2 + .5
p = 9 // الآن، iota == 3
q = iota * 2 // q = 4 * 2
_ // _ = 5 * 2
r // r = 6 * 2
s, t = iota, iota // s, t = 7, 7
u, v // u, v = 8, 8
_, w // _, w = 9, 9
)
const x = iota // x = 0
const (
y = iota // y = 0
z // z = 1
)
println(m) // +1.500000e+000
println(n) // +2.500000e+000
println(q, r) // 8 12
println(s, t, u, v, w) // 7 7 8 8 9
println(x, y, z) // 0 0 1
}
المثال أعلاه مجرد عرض لقواعد ميزة مولّد الثوابت iota. وبالتأكيد، ينبغي في الممارسة استخدامها بطرق أكثر معنى. على سبيل المثال،
const (
Failed = iota - 1 // == -1
Unknown // == 0
Succeeded // == 1
)
const (
Readable = 1 << iota // == 1
Writable // == 2
Executable // == 4
)
هنا، الرمز - هو معامل الطرح، والرمز << هو معامل الإزاحة اليسرى. وسيُقدَّم هذان المعاملان في المقال التالي.
المتغيرات وتعريفات المتغيرات وإسنادات القيم
المتغيرات قيم مسمّاة. وتُخزَّن المتغيرات في الذاكرة في وقت التشغيل. ويمكن تعديل القيمة التي يمثّلها المتغير في وقت التشغيل.
جميع المتغيرات قيم منمَّطة. وعند تعريف متغير، يجب توفير معلومات كافية للمصرِّف لاستنتاج نوع المتغير.
تسمّى المتغيرات المعرَّفة داخل أجسام الدوال متغيرات محلية. أما المتغيرات المعرَّفة خارج أي جسم دالة فتسمّى متغيرات على مستوى الحزمة. وكثيرًا ما نسمّي متغيرات مستوى الحزمة متغيرات عامة (global variables).
هناك صيغتان أساسيتان لتعريف المتغيرات: الصيغة القياسية والصيغة المختصرة. ولا يمكن استخدام الصيغة المختصرة إلا لتعريف المتغيرات المحلية.
صيغ تعريف المتغيرات القياسية
يبدأ كل تعريف قياسي بالكلمة المفتاحية var، تليها اسم المتغير المعرَّف. ويجب أن تكون أسماء المتغيرات معرّفات.
فيما يلي بعض صيغ التعريف القياسية الكاملة. في هذه التعريفات، تُحدَّد أنواع المتغيرات المعرَّفة وقيمها الابتدائية جميعها.
var lang, website string = "Go", "https://golang.org"
var compiled, dynamic bool = true, false
var announceYear int = 2009
كما لاحظنا، يمكن تعريف عدة متغيرات معًا في تعريف متغير واحد. لاحظ أنه لا يمكن تحديد إلا نوع واحد في تعريف المتغير. لذا يجب أن تكون أنواع المتغيرات المتعددة المعرَّفة في سطر التعريف نفسه متماثلة.
نادرًا ما تُستخدم صيغ تعريف المتغيرات القياسية الكاملة في الممارسة لأنها مطوّلة. وفي الممارسة، تُستخدم أكثر الصيغتان البديلتان لتعريف المتغيرات القياسية المقدَّمتان أدناه. وفي الصيغتين البديلتين، يُغفل إما أنواع المتغيرات المعرَّفة وإما قيمها الابتدائية.
فيما يلي بعض تعريفات المتغيرات القياسية دون تحديد أنواع المتغيرات. سيستنتج المصرِّف أنواع المتغيرات المعرَّفة على أنها أنواع قيمها الابتدائية (أو أنواعها الافتراضية). والتعريفات التالية مكافئة في الواقع للتعريفات أعلاه. لاحظ أنه في التعريفات التالية يمكن أن تختلف أنواع المتغيرات المتعددة المعرَّفة في سطر التعريف نفسه.
// سيستنتج المصرِّف نوعي المتغيرين lang وdynamic
// على أنهما النوعان المدمجان "string"
// و"bool" على التوالي.
var lang, dynamic = "Go", false
// سيستنتج المصرِّف نوعي المتغيرين compiled وannounceYear
// على أنهما النوعان المدمجان
// "bool" و"int" على التوالي.
var compiled, announceYear = true, 2009
// سيستنتج المصرِّف نوع المتغير website
// على أنه النوع المدمج "string".
var website = "https://golang.org"
يمكن النظر إلى عمليات استنتاج الأنواع في المثال أعلاه على أنها تحويلات ضمنية.
فيما يلي بعض التعريفات القياسية دون تحديد قيم ابتدائية للمتغيرات. في هذه التعريفات، تُهيَّأ جميع المتغيرات المعرَّفة على القيم الصفرية لأنواعها.
// يُهيَّأ كليهما كنصوص فارغة.
var lang, website string
// يُهيَّأ كليهما على false.
var interpreted, dynamic bool
// يُهيَّأ n على 0.
var n int
يمكن تجميع عدة متغيرات في تعريف واحد بالصيغة القياسية باستخدام (). على سبيل المثال:
var (
lang, bornYear, compiled = "Go", 2007, true
announceAt, releaseAt int = 2009, 2012
createdBy, website string
)
المثال أعلاه منسَّق باستخدام الأمر go fmt الموفَّر في سلسلة أدوات Go. وفي المثال أعلاه، كل سطر من الأسطر الثلاثة داخل ()، ويُعرف هذا بمواصفة متغير (variable specification).
عمومًا، تعريف المتغيرات المرتبطة معًا يجعل الشيفرة أكثر قابلية للقراءة.
إسنادات القيم المحضة
في تعريفات المتغيرات أعلاه، تعني العلامة = الإسناد. وبمجرد تعريف متغير، يمكننا تعديل قيمته باستخدام إسنادات القيم المحضة. ومثل تعريفات المتغيرات، يمكن إسناد عدة قيم في إسناد محض واحد.
تُسمّى عناصر التعبير على يسار الرمز = في الإسناد المحض قيمًا وجهةً أو قيمًا هدفًا. ويجب أن تكون قيمًا قابلة للعنونة، أو تعبيرات فهرسة خريطة، أو المعرّف الفارغ (blank identifier). وستُقدَّم عناوين القيم والخرائط في مقالات لاحقة.
الثوابت غير قابلة للتغيير، لذا لا يمكن أن يظهر ثابت على الجانب الأيسر من إسناد محض كقيمة وجهة، بل يمكن أن يظهر فقط على الجانب الأيمن كقيمة مصدر. أما المتغيرات فيمكن استخدامها كقيم مصدر وقيم وجهة معًا، لذا يمكن أن تظهر على جانبي إسنادات القيم المحضة.
يمكن أن يظهر المعرّف الفارغ أيضًا على الجانب الأيسر من إسنادات القيم المحضة كقيمة وجهة، وفي هذه الحالة يعني ذلك أننا نتجاهل قيم الوجهة. ولا يمكن استخدام المعرّفات الفارغة كقيم مصدر في الإسنادات.
مثال:
const N = 123
var x int
var y, z float32
N = 9 // خطأ: الثابت N غير قابل للتعديل
y = N // صحيح: يُستنتج N كقيمة float32
x = y // خطأ: عدم تطابق الأنواع
x = N // صحيح: يُستنتج N كقيمة int
y = x // خطأ: عدم تطابق الأنواع
z = y // صحيح
_ = y // صحيح
x, y = y, x // خطأ: عدم تطابق الأنواع
x, y = int(y), float32(x) // صحيح
z, y = y, z // صحيح
_, y = y, z // صحيح
z, _ = y, z // صحيح
_, _ = y, z // صحيح
x, y = 69, 1.23 // صحيح
تستخدم الشيفرة في السطر الأخير من المثال أعلاه تحويلات صريحة لجعل قيم الوجهة والمصدر المتقابلة متوافقة. وتُقدَّم أدناه قواعد التحويل الصريح للقيم العددية غير الثابتة. ولا تدعم Go سلسلة الإسناد. على سبيل المثال، الشيفرة التالية غير قانونية.
var a, b int
a = b = 123 // خطأ نحوي
صيغ تعريف المتغيرات المختصرة
يمكننا أيضًا استخدام صيغ تعريف المتغيرات المختصرة لتعريف المتغيرات. ولا يمكن استخدام تعريفات المتغيرات المختصرة إلا لتعريف المتغيرات المحلية. لنطّلع على مثال يستخدم بعض تعريفات المتغيرات المختصرة.
package main
func main() {
// كلا المتغيرين lang وyear معرَّفان حديثًا.
lang, year := "Go language", 2007
// المتغير createdBy وحده معرَّف جديد.
// أما المتغير year فقد عُرِّف سابقًا، لذا
// تُعدَّل قيمته هنا فقط، أو يمكننا القول
// إنه أُعيد تعريفه.
year, createdBy := 2009, "Google Research"
// هذا إسناد محض.
lang, year = "Go", 2012
print(lang, " is created by ", createdBy)
println(", and released at year", year)
}
يجب أن يعرّف كل تعريف متغير مختصر متغيرًا جديدًا واحدًا على الأقل.
هناك عدة فروق بين تعريفات المتغيرات المختصرة والقياسية.
- في صيغة التعريف المختصرة، يجب إغفال الكلمة المفتاحية
varوأنواع المتغيرات. - يجب أن تكون علامة الإسناد
:=بدلًا من=. - في تعريف المتغير المختصر، يمكن أن تختلط المتغيرات القديمة والجديدة على يسار
:=. لكن يجب أن يوجد متغير جديد واحد على الأقل على اليسار.
لاحظ أنه بالمقارنة مع الإسنادات المحضة، هناك قيد على تعريفات المتغيرات المختصرة. في تعريف المتغير المختصر، يجب أن تكون جميع العناصر على يسار العلامة := معرّفات محضة. وهذا يعني أن بعض العناصر الأخرى القابلة للإسناد إليها، والتي ستُقدَّم في مقالات أخرى، لا يمكن أن تظهر على يسار :=. وتشمل هذه العناصر المعرّفات المؤهَّلة، وعناصر الحاويات، وفكّ إشارة المؤشرات، ومحدِّدات حقول البنيات. أما الإسنادات المحضة فليس عليها هذا القيد.
حول مصطلح "الإسناد"
لاحقًا، عند ذكر كلمة "إسناد"، فقد تعني إسنادًا محضًا، أو تعريف متغير مختصرًا، أو مواصفة متغير بقيم ابتدائية في تعريف متغير قياسي. في الواقع، يشمل تعريف أكثر عمومية أيضًا تمرير معطيات الدوال الذي سيُقدَّم في مقال لاحق.
نقول إن x قابل للإسناد إلى y إذا كانت الجملة y = x قانونية (تُصرَّف بنجاح). وبافتراض أن نوع y هو Ty، يمكننا أحيانًا، تسهيلًا للوصف، أن نقول أيضًا إن x قابل للإسناد إلى النوع Ty.
عمومًا، إذا كان x قابلًا للإسناد إلى y، فيجب أن يكون y قابلًا للتغيير، وأن يكون نوعا x وy متماثلين أو أن يكون x قابلًا للتحويل ضمنيًا إلى نوع y. وبالطبع، يمكن أن يكون y أيضًا المعرّف الفارغ _.
يجب استخدام كل متغير محلي معرَّف مرة واحدة على الأقل استخدامًا فعّالًا
لاحظ أن مصرِّف Go القياسي وgccgo لا يسمحان معًا بتعريف متغيرات محلية دون استخدامها. أما متغيرات مستوى الحزمة فليس عليها هذا القيد.
إذا استُخدم متغير محلي فقط كقيم وجهة، فسيُعتبر أيضًا غير مستخدم. على سبيل المثال، في البرنامج التالي، يُستخدم r فقط كوجهة.
package main
// بعض المتغيرات على مستوى الحزمة.
var x, y, z = 123, true, "foo"
func main() {
var q, r = 789, false
r, s := true, "bar"
r = y // r غير مستخدم.
x = q // q مستخدم.
}
سيؤدي تصريف البرنامج أعلاه إلى أخطاء التصريف التالية (بافتراض أن اسم الملف المصدري هو example-unused.go):
./example-unused.go:6:6: r declared and not used
./example-unused.go:7:16: s declared and not used
الإصلاح سهل، إذ يمكننا إسناد r وs إلى معرّفات فارغة لتجنّب أخطاء التصريف.
package main
var x, y, z = 123, true, "foo"
func main() {
var q, r = 789, false
r, s := true, "bar"
r = y
x = q
_, _ = r, s // اجعل r وs مستخدمين.
}
عمومًا، لا يُنصح باستخدام الإصلاح أعلاه في شيفرة الإنتاج. وينبغي استخدامه في مرحلة التطوير/التنقيح فقط. وليست عادة جيدة ترك متغيرات محلية غير مستخدمة في الشيفرة، لأن المتغيرات المحلية غير المستخدمة لها آثار سلبية على كلٍّ من قابلية قراءة الشيفرة وأداء تنفيذ البرنامج.
تؤثر علاقات الاعتماد بين متغيرات مستوى الحزمة على ترتيب تهيئتها
في المثال التالي،
var x, y = a+1, 5 // 8 5
var a, b, c = b+1, c+1, y // 7 6 5
يكون ترتيب تهيئة متغيرات مستوى الحزمة هو y = 5 ثم c = y ثم b = c+1 ثم a = b+1 ثم x = a+1.
هنا، الرمز + هو معامل الجمع، وسيُقدَّم في المقال التالي.
لا يمكن أن تعتمد متغيرات مستوى الحزمة على بعضها اعتمادًا دائريًا في تعريفها. وتفشل الشيفرة التالية في التصريف.
var x, y = y, x
قابلية القيم للعنونة
في Go، بعض القيم قابلة للعنونة (أي يوجد عنوان للعثور عليها). وجميع المتغيرات قابلة للعنونة وجميع الثوابت غير قابلة للعنونة. ويمكننا تعلّم المزيد عن العناوين والمؤشرات من مقال المؤشرات في Go، وتعلّم قيم أخرى قابلة للعنونة وغير قابلة للعنونة من مقالات أخرى لاحقًا.
التحويلات الصريحة على القيم العددية غير الثابتة
في Go، لا يمكن إسناد قيمتين منمَّطتين من نوعين أساسيين مختلفين إحداهما إلى الأخرى. بعبارة أخرى، يجب أن يكون نوعا قيمتي الوجهة والمصدر في الإسناد متماثلين إذا كانت القيمتان كلتاهما قيمتين أساسيتين. وإذا لم يكن نوع القيمة الأساسية المصدر مماثلًا لنوع القيمة الأساسية الوجهة، فيجب تحويل القيمة المصدر صراحةً إلى نوع قيمة الوجهة.
كما ذُكر أعلاه، يمكن تحويل قيم الأعداد الصحيحة غير الثابتة إلى نصوص. وهنا نقدّم حالتين قانونيتين إضافيتين للتحويلات المتعلقة بالقيم العددية غير الثابتة.
- يمكن تحويل القيم العشرية والصحيحة غير الثابتة صراحةً إلى أي أنواع عشرية وصحيحة أخرى.
- يمكن تحويل القيم المركّبة غير الثابتة صراحةً إلى أي أنواع مركّبة أخرى.
بخلاف تحويلات الأعداد الثابتة، يُسمح بالتجاوزات في تحويلات الأعداد غير الثابتة. وعند تحويل قيمة عشرية غير ثابتة إلى عدد صحيح، يُسمح أيضًا بالتقريب. وإذا لم تتجاوز قيمة عشرية غير ثابتة سعة نوع صحيح، فسيُهمَل الجزء الكسري من القيمة العشرية (باتجاه الصفر) عند تحويلها إلى النوع الصحيح.
في جميع التحويلات غير الثابتة التي تتضمن قيمًا عشرية أو مركّبة، إذا لم يستطع نوع النتيجة تمثيل القيمة، فإن التحويل ينجح لكن قيمة النتيجة تعتمد على التنفيذ.
في المثال التالي، لا يعمل التحويلان الضمنيان المقصودان في السطر 7 والسطر 18. كما أن التحويلين الصريحين في السطر 5 والسطر 16 غير مسموح بهما أيضًا.
const a = -1.23
// يُستنتج نوع b على أنه float64.
var b = a
// خطأ: الثابت 1.23 مقتطع إلى عدد صحيح.
var x = int32(a)
// خطأ: لا يمكن إسناد float64 إلى int32.
var y int32 = b
// صحيح: z == -1، ونوع z هو int32.
// يُهمَل الجزء الكسري من b.
var z = int32(b)
const k int16 = 255
// يُستنتج نوع n على أنه int16.
var n = k
// خطأ: الثابت 256 يتجاوز سعة uint8.
var f = uint8(k + 1)
// خطأ: لا يمكن إسناد int16 إلى uint8.
var g uint8 = n + 1
// صحيح: h == 0، ونوع h هو uint8.
// n+1 يتجاوز سعة uint8 ويُقتطع.
var h = uint8(n + 1)
يمكننا اعتبار أن القيمة a في السطر 3 تُحوَّل ضمنيًا إلى نوعها الافتراضي (float64)، فيُستنتج نوع b على أنه float64. وستُقدَّم المزيد من قواعد التحويل الضمني في مقالات أخرى لاحقًا.
نطاقات المتغيرات والثوابت المسمّاة
في Go، يمكننا استخدام زوج من { و} لتكوين كتلة شيفرة. ويمكن لكتلة الشيفرة أن تضمّ كتل شيفرة أخرى. وسيحجب متغير أو ثابت مسمّى معرَّف في كتلة شيفرة داخلية المتغيرات والثوابت المعرَّفة بالاسم نفسه في كتل الشيفرة الخارجية. على سبيل المثال، يعرّف البرنامج التالي ثلاثة متغيرات متمايزة، جميعها تسمّى x. ويحجب المتغير x الداخلي المتغير الخارجي.
package main
const y = 789
var x int = 123
func main() {
// المتغير x يحجب متغير مستوى
// الحزمة x المعرَّف أعلاه.
var x = true
// كتلة شيفرة متداخلة.
{
// هنا، كلا المتغيرين x وy على اليسار
// معرَّف جديد. أما الموجودان على اليمين
// فمعرَّفان في كتل خارجية.
x, y := x, y
// في كتلة الشيفرة هذه، يحجب المتغيران x وy
// المعرَّفان حديثًا المعرّفات المماثلة
// في الاسم المعرَّفة خارجيًا.
x, z := !x, y/10 // z وحده معرَّف جديد
y /= 100
println(x, y, z) // false 7 78
}
println(x) // true
println(z) // خطأ: z غير معرَّف.
}
نطاق متغير على مستوى الحزمة (أو ثابت مسمّى) هو الحزمة بأكملها التي عُرِّف فيها المتغير (أو الثابت المسمّى). ويبدأ نطاق المتغير المحلي (أو الثابت المسمّى) من نهاية تعريفه وينتهي عند نهاية كتلة الشيفرة الحاوية له الأكثر عمقًا. ولهذا لا يُصرَّف السطر الأخير في الدالة main في المثال أعلاه.
وستُشرح كتل الشيفرة ونطاقات المعرّفات بالتفصيل في الكتل والنطاقات لاحقًا.
المزيد عن تعريفات الثوابت
يمكن أن تتجاوز قيمة ثابت غير منمَّط نوعه الافتراضي
على سبيل المثال، تُصرَّف الشيفرة التالية بنجاح.
// ثلاثة ثوابت مسمّاة غير منمَّطة. قيمها
// المربوطة تتجاوز جميعها أنواعها
// الافتراضية. وهذا مسموح به.
const n = 1 << 64 // يتجاوز سعة int
const r = 'a' + 0x7FFFFFFF // يتجاوز سعة rune
const x = 2e+308 // يتجاوز سعة float64
func main() {
_ = n >> 2
_ = r - 0x7FFFFFFF
_ = x / 2
}
لكن الشيفرة التالية لا تُصرَّف، لأن الثوابت كلها منمَّطة.
// ثلاثة ثوابت مسمّاة منمَّطة. لا يُسمح
// لقيمها المربوطة بتجاوز أنواعها
// الافتراضية. والأسطر الثلاثة
// كلها تفشل في التصريف.
const n int = 1 << 64 // يتجاوز سعة int
const r rune = 'a' + 0x7FFFFFFF // يتجاوز سعة rune
const x float64 = 2e+308 // يتجاوز سعة float64
يُستبدل كل معرّف ثابت مسمّى بالقيمة الحرفية المربوطة به في وقت التصريف
يمكن النظر إلى تعريفات الثوابت على أنها وحدات ماكرو #define محسَّنة في لغة C. فتعريف الثابت يعرّف ثابتًا مسمّى يمثّل قيمة حرفية. وستُستبدل جميع مواضع ظهور الثابت المسمّى بالقيمة الحرفية التي يمثّلها في وقت التصريف.
إذا كان المعامَلان به في عملية معامل ثابتين معًا، فستُقيَّم العملية في وقت التصريف. يُرجى قراءة المقال التالي المعاملات الشائعة للتفاصيل.
على سبيل المثال، في وقت التصريف، ستُعتبر الشيفرة التالية
package main
const X = 3
const Y = X + X
var a = X
func main() {
b := Y
println(a, b, X, Y)
}
كما يلي
package main
var a = 3
func main() {
b := 6
println(a, b, 3, 6)
}