تُعَدّ المتغيرات مفهومًا برمجيًا مهمًا يتوجّب عليك فهمه وإتقانه، فهي رموز تدل على القيم التي تستخدِمها في برنامجك، وسنتحدّث في هذا المقال عن أساسيات المتغيرات وأفضل الممارسات عند التعامل معها.
فهم المتغيرات
تُخصَّص للمتغير من الناحية الفنية مساحة تخزينية في الذاكرة توضَع القيمة المرتبطة به فيها، ويُستخدَم اسم المتغير للإشارة إلى تلك القيمة المُخزَّنة في ذاكرة البرنامج والتي هي جزء من ذاكرة الحاسوب، ويشبه المُتغيِّر بأنه عنوان تُعلِّقه على قيمة مُعيَّنة كما في الصورة التالية، إذ تشير variable name إلى اسم المتغير وvalue إلى قيمته:
لنفترض أنه لدينا عددًا صحيحًا يساوي 1032049348
ونريد تخزينه في متغيِّر بدلًا من إعادة كتابة هذا العدد الطويل كل مرة، لذلك سنستخدِم شيئًا يُسهِّل تذكّره مثل المتغير i،
i := 1032049348
إذا نظرنا إليه على أنَّه عنوانٌ مرتبط بقيمة، فسيبدو على النحو التالي:
يمثّل i
اسم المتغير وتُربَط به القيمة 1032049348
التي هي من نوع عدد صحيح integer، كما تُعَدّ العبارة i := 1032049348
أنها تعليمة إسناد وتتألف من الأجزاء التالية:
-
اسم المتغير
i
. -
معامِل تعريف المتغير المختصر
=:
. -
القيمة التي أُسنِدَت إلى المتغير
1032049348
.
نوع البيانات تكتشفه اللغة تلقائيًا.
تُسند القيمة السابقة من خلال هذه الأجزاء إلى المتغير i، ونكون عند تحديد قيمة المتغير قد هيّئنا أو أنشأنا ذلك المتغير، وبعد ذلك يمكنك استخدام ذلك المتغير بدلًا من القيمة كما في المثال التالي:
package main import "fmt" func main() { i := 1032049348 fmt.Println(i) }
ويكون الخرج كما يلي:
1032049348
يسهل استخدام المتغيرات علينا إجراء العمليات الحسابية، إذ سنعتمد في المثال التالي على التعليمة السابقة i = 1032049348
وسنطرح من المتغير i
القيمة 813
كما يلي:
fmt.Println(i - 813)
ويكون الخرج كما يلي:
1032048535
يُجري جو Go العملية الحسابية ويطرح 813 من المتغير i
ويعيد القيمة 1032048535
.
يمكن ضبط المتغيرات وجعلها تساوي ناتج عملية حسابية ما، إذ سنجمع الآن عددين معًا ونخزِّن قيمة المجموع في المتغير x
:
x := 76 + 145
يشبه المثال أعلاه إحدى المعادلات التي تراها في كتب الجبر، إذ تُستخدَم المحارف والرموز لتمثيل الأعداد والكميات داخل الصيغ والمعادلات، وبصورة مماثلة تُعَدّ المتغيرات أسماءً رمزيةً تمثِّل قيمةً من نوع بيانات معيّن، والفرق في لغة جو أنه عليك التأكد دائمًا من كتابة المتغير على الجانب الأيسر من المعادلة، ولنطبع الآن x
كما يلي:
package main import "fmt" func main() { x := 76 + 145 fmt.Println(x) }
يكون الخرج كما يلي:
221
أعادت جو القيمة 221
لأنه قد أُسنِد إلى المتغير x
مجموع العددين 76
و 145
، كما يمكن أن تمثل المتغيرات أيّ نوع بيانات وليس الأعداد الصحيحة فقط كما يلي:
s := "Hello, World!" f := 45.06 b := 5 > 9 // ستُرجع قيمة منطقية، إما صواب أو خطأ array := [4]string{"item_1", "item_2", "item_3", "item_4"} slice := []string{"one", "two", "three"} m := map[string]string{"letter": "g", "number": "seven", "symbol": "&"}
فإذا طبعت أيًّا من المتغيرات المذكورة أعلاه، فستعيد جو قيمة المتغير، ففي الشيفرة التالية مثلًا سنطبع متغيرًا يمثّل شريحة slice من سلسلة نصية:
package main import "fmt" func main() { slice := []string{"one", "two", "three"} fmt.Println(slice) }
يكون الخرج كما يلي:
[one two three]
لقد حددنا الشريحة []string{"one", "two", "three"}
إلى المتغير slice
ثم استخدمنا دالة الطباعة fmt.Println
لطباعتها.
تأخذ المتغيرات مساحةً صغيرةً من ذاكرة الحاسوب لتسمح لك بوضع القيم في تلك المساحة.
التصريح عن المتغيرات
هناك عدة طرق للتصريح عن متغير في جو، فيمكن التصريح عن متغير يسمى i
من نوع البيانات int
بدون تهيئة، أي بدون قيمة أوليّة كما يلي:
var i int
يمكن تهيئة المتغير من خلال استخدام المعامِل =
كما يلي:
var i int = 1
يُطلق على كل من هذين النموذجين للتصريح بالتصريح الطويل للمتغيرات long variable declaration، في حين يمثِّل النوذج التالي الأسلوب القصير أو المختصر short variable declaration:
i := 1
في هذه الحالة نحن لانحدد نوع البيانات ولانستخدِم الكلمة المفتاحية var، حيث يستنتج جو الصنف تلقائيًّا.
تبنى مجتمع جو المصطلحات التالية من خلال الطرق الثلاث للتصريح عن المتغيرات:
-
استخدِم النموذج الطويل
var i int
عندما لا تُهيئ المتغير فقط. -
استخدِم النموذج المختصر
i := 1
، عند التصريح والتهيئة. - إذا لم تكن ترغب في أن يستنتج جو نوع البيانات الخاصة بك، ولكنك لا تزال ترغب في استخدام تصريح قصير للمتغير، فيمكنك تمرير القيمة إلى باني نوع البيانات الذي تريده كما يلي:
i := int64(1)
في حين لا يُعَدّ استخدام النموذج الطويل التالي من المصطلحات الشائعة في جو:
var i int = 1
يُعَدّ اتباع الطريقة التي يحدد فيها مجتمع جو عادات التصريح عن المتغيرات من الممارسات الجيدة، وذلك حتى يتمكن الآخرون من قراءة برامجك بسلاسة.
القيم الصفرية Zero Values
تأخذ أنواع البيانات المُعرّفة ضمن جو قيمًا صفرية على أساس قيمة أوليّة في حال لم تحدَّد لها قيمة أولية كما في المثال التالي:
package main import "fmt" func main() { var a int var b string var c float64 var d bool fmt.Printf("var a %T = %+v\n", a, a) fmt.Printf("var b %T = %q\n", b, b) fmt.Printf("var c %T = %+v\n", c, c) fmt.Printf("var d %T = %+v\n\n", d, d) }
يكون الخرج كما يلي:
var a int = 0 var b string = "" var c float64 = 0 var d bool = false
يُستخدم الرمز T%
داخل الدالة fmt.Printf
لطباعة نوع بيانات المتغير. لاحظ أن القيمة الصفرية المقابلة للسلاسل النصية تمثّلها السلسلة "" والقيمة الصفرية للبيانات المنطقية bool
تمثّلها القيمة false
. هذا مهم ففي بعض اللغات توضع قيم عشوائية للمتغيرات في حال لم تُهيئ بقيمة أولية، وبالتالي قد نرى أن متغيرًا منطقيًا يأخذ القيمة None أو شيء آخر، وهذا يُنتج عدم اتساقية للبيانات، فالصنف bool لا يمكن أن يكون إلا False أو True.
قواعد تسمية المتغيرات
تتميز تسمية المتغيرات بمرونة عالية، ولكن هناك بعض القواعد التي عليك أخذها في الحسبان كما يلي:
- يجب أن تكون أسماء المتغيرات من كلمة واحدة فقط بدون مسافات.
-
يجب أن تتكوّن أسماء المتغيرات من المحارف والأعداد والشرطة السفلية
_
فقط. - لا ينبغي أن تبدأ أسماء المتغيرات بعدد.
دعنا نلقي نظرةً على بعض الأمثلة باتباع القواعد المذكورة أعلاه:
صالح | غير صالح | التفسير |
---|---|---|
user-Name | userName | غير مسموح استخدام الواصلات Hyphens |
4i | i4 | لا يمكن البدء برقم |
user | user$ | لا يمكن استخدام أيّ رمز غير الشرطة السفلية |
user Name | userName | لا ينبغي للمتغير أن يتكون من أكثر من كلمة واحدة |
من الأمور التي يجب أخذها في الحسبان عند تسمية المتغيرات هو أنَّها حساسة لحالة المحارف، وهذا يعني أنَّ my_int
و MY_INT
و My_Int
و mY_iNt
كلها مختلفة، أي ينبغي أن تتجنب استخدام أسماء متغيرات متماثلة لضمان ألا يحدث خلط عندك أو عند المتعاونين معك حاليًا أو في المستقبل، كما أنّ حالة المحرف الأول من المتغير لها معنى خاص في جو، فإذا بدأ المتغير بمحرف كبير، فيمكن الوصول إلى هذا المتغير من خارج الحزمة التي أُعلن عنه فيها؛ أما إذا بدأ المتغير بمحرف صغير، فهو متاح فقط داخل الحزمة التي أُعلن عنه فيها كما في المثال التالي:
var Email string var password string
يبدأ هنا اسم المتغير Email
بمحرف كبير، وبالتالي يمكن الوصول إليه بواسطة حزم أخرى، في حين يبدأ المتغير password
بمحرف صغير ولا يمكن الوصول إليه إلا داخل الحزمة الُمعلن عنه فيها.
من الشائع في جو استخدام أسماء متغيرات مختَصرة جدًا أو قصيرة، إذ يُفضّل كتابة user
بدلًا من كتابة userName
، كما يلعب النطاق Scope دورًا في اختصار اسم المتغير، فكلما كان نطاق المتغير أصغر، كلما كان اسم المتغير أصغر:
names := []string{"Mary", "John", "Bob", "Anna"} for i, n := range names { fmt.Printf("index: %d = %q\n", i, n) }
عند استخدام المتغيرات نفسها في أماكن مختلفة من البرنامج، يُفضّل إعطاءها أسماء واضحة لكي لا يربك ذلك الأشخاص الآخرين الذين يقرأون شيفرة البرنامج، لكن في المثال أعلاه استخدمنا المتغيرين i
و n
لأننا نستخدِمهما مرةً واحدةً فقط ومباشرةً، لذا لن يسبب ذلك أيّ مشكلة في قراءة أو فهم الشيفرة.
يُفضّل عند تسمية المتغيرات استخدام تنسيق سنام الجَمل camelCase أو سنام الجمل المرتفع CamelCase عند كتابة المتغيرات بدلًا من استعمال الشرطة السفلية multi_word أو العادية multi-word. الجدول التالي يتضمن بعض الملاحظات المفيدة:
شائع | غير شائع | التفسير |
---|---|---|
user_name | userName | الشرطات السفلية غير شائعة الاستخدام |
index | i | لا يُفضّل استخدام الاسم الكامل وإنما الاسم المختَصر |
serveHttp | serveHTTP | يجب كتابة الاختصارات بمحارف كبيرة |
الخيار الأهم الذي عليك التمسك به هو الاتساق، فإذا بدأت العمل في مشروع يستخدِم تنسيق سنام الجَمل في تسمية المتغيرات، فمن الأفضل الاستمرار في استخدام ذلك التنسيق.
إعادة إسناد قيم للمتغيرات Reassigning
يمكن تغيير قيم المتغيرات في جو بسهولة كما تشير كلمة "متغير"، وهذا يعني أنه يمكنك إسناد قيمة مختلفة إلى متغير قد أُسنِدَت له قيمة مسبقًا بسهولة بالغة، إذ تُعَدّ القدرة على إعادة الإسناد مفيدةً للغاية، فقد تحتاج مثلًا خلال أطوار برنامجك إلى استلام قيم جديدة من المستخدِم وإسنادها إلى متغير قد حُدّدَت قيمته سابقًا أو قد تحتاج إلى تغيير قيمة متغير اعتمادًا على أحداث معينة، كما أنّ سهولة إعادة إسناد قيم المتغيرات مفيدة أيضًا في البرامج الكبيرة التي قد تحتاج خلالها إلى تغيير القيم باستمرار.
سنُسّند إلى المتغير i
الذي نوعه int
العدد 76
ثم نعيد إسناده بالقيمة 42
:
package main import "fmt" func main() { i := 76 fmt.Println(i) i = 42 fmt.Println(i) }
يكون الخرج كما يلي:
76 42
يوضِّح المثال أنه يمكننا تعريف متغير وإسناد قيمة له ثم تغيير قيمته بإعادة إسناد قيمة أخرى له ما بسهولة من خلال استخدام معامل الإسناد =.
ملاحظة: لاحظ أننا نستخدِم المعامِل =:
عند التهيئة والتصريح عن متغير والمعامِل =
عند إعادة الإسناد (تعديل قيمة المتغير)، وبالتالي لايمكنك استخدام المعامل =:
بدلًا من =
عند تغيير قيمة المتغير.
بما أنّ لغة جو هي لغة تحتاج إلى تحديد النوع typed language، فعند إسناد قيمة جديدة إلى متغير يجب أن تكون هذه القيمة من نوع بيانات المتغير، فلا يمكن مثلًا إسناد قيمة تُمثّل عددًا صحيحًا إلى متغير من نوع سلسلة نصيّة أو العكس، فإذا حاولت فعل ذلك:
i := 72 i = "Sammy"
فستحصل على الخرج التالي الذي يُمثّل خطأً، والذي يخبرك فيه بأن مثل هذه العملية غير مسموحة كما أوضحنا:
cannot use "Sammy" (type string) as type int in assignment
لا يمكنك أيضًا تعريف أكثر من متغير واحد بالاسم نفسه:
var s string var s string
ستحصل على خطأ كما يلي:
s redeclared in this block
لايمكنك استخدام المعامل =:
بدلًا من =
عند محاولة تعديل قيمة متغير كما أشرنا سابقًا كما يلي:
i := 5 i := 10
فالخرج التالي يُظهر خطًأ مفاده أنه لا يوجد متغير جديد على الطرف الأيسر، إذ تُفسِّر جو وجود المعامِل =:
بأننا نريد التصريح عن متغير جديد أو تهيئته بقيمة أولية.
no new variables on left side of :=
ستحسِّن تسمية المتغيرات الخاصة بك بطريقة سليمة قابلية قراءة برنامجك سواءً لك أو للآخرين ولاسيما عند العودة إليه مستقبلًا.
الإسناد المتعدد
يمكنك في لغة جو إسناد عدة قيم إلى عدة متغيرات في الوقت نفسه، إذ يتيح لك هذا تهيئة عدة متغيرات دفعةً واحدةً، ويمكن أن تكون كل من هذه المتغيرات من أنواع بيانات مختلفة كما يلي:
j, k, l := "shark", 2.05, 15 fmt.Println(j) fmt.Println(k) fmt.Println(l)
يكون الخرج كما يلي:
shark 2.05 15
هنا أُسند إلى المتغير j
السلسلة "shark"
والمتغير k
القيمة 2.05
والمتغير l
القيمة 15
.
إسناد قيم لعدة متغيرات في سطر واحد يمكن أن يجعل الشيفرة مختصَرةً ويقلل من عدد الأسطر، ولكن تأكد من أنّ ذلك ليس على حساب قابلية القراءة.
المتغيرات العامة والمحلية
عند استخدام المتغيرات داخل البرنامج، من المهم أن تضع نطاق المتغير variable scope في حساباتك، إذ يشير نطاق المتغير إلى المواضع التي يمكن الوصول منها إلى المتغير داخل الشيفرة، حيث لا يمكن الوصول إلى جميع المتغيرات من جميع أجزاء البرنامج، فبعض المتغيرات عامة وبعضها محلي، إذ تُعرّف المتغيرات العامة خارج الدوال؛ أما المتغيرات المحلية، فتوجد داخل الدوال، ويعطي المثال التالي فكرةً عن المتغيرات العامة والمحلية:
package main import "fmt" var g = "global" func printLocal() { l := "local" fmt.Println(l) } func main() { printLocal() fmt.Println(g) }
يكون الخرج كما يلي:
local
global
استخدمنا var g = "global"
للتصريح عن متغير عام خارج الدالة ثم عرّفنا الدالة printLocal()
وبداخلها المتغير المحلي l
المسنَد إليه قيمة ثم تعليمة لطباعته. في الدالة الرئيسية نستدعي الدالة printLocal()
ثم نطبع قيمة المتغير العام g
، وبما أنّ g
متغير عام، فيمكننا الوصول إليه من داخل الدالة printLocal()
مباشرةً كما في المثال التالي:
package main import "fmt" var g = "global" func printLocal() { l := "local" fmt.Println(l) fmt.Println(g) } func main() { printLocal() fmt.Println(g) }
يكون الخرج كما يلي:
local
global
global
يختلف هذا المثال عن المثال السابق في أننا نحاول الوصول إلى المتغير العام g
مباشرةً من داخل الدالة printLocal
وهذا مايُفسّره خرج البرنامج، حيث تطبع الدالة printLocal
قيمة كل من l
و g
هذه المرة وليس فقط l
.
إذا حاولت الوصول إلى قيمة المتغير المحلي من خارج الدالة، فستفشل وستظهر رسالة خطأ تشير إلى ذلك:
package main import "fmt" var g = "global" func printLocal() { l := "local" fmt.Println(l) } func main() { fmt.Println(l) }
يكون الخرج كما يلي:
undefined: l
أي أن المتغير غير مُعرّف في نطاق الدالة الرئيسية، إذ لا يمكنك الوصول إلى المتغير المحلي إلا ضمن النطاق الذي عُرّف ضمنه ونطاقه في هذا البرنامج هو الدالة printLocal
، فلنلق الآن نظرةً على مثال آخر بحيث نستخدِم اسم المتغير نفسه لمتغير عام ومتغير محلي كما يلي:
package main import "fmt" var num1 = 5 func printNumbers() { num1 := 10 num2 := 7 fmt.Println(num1) fmt.Println(num2) } func main() { printNumbers() fmt.Println(num1) }
يكون الخرج كما يلي:
10 7 5
صرّحنا في البرنامج السابق عن المتغير num1
مرتين؛ مرة ضمن النطاق العام var num1 = 5
والأخرى num1 := 10
ضمن نطاق محلي داخل الدالة printNumbers
.
عندما نطبع num1
من البرنامج الرئيسي -أي داخل الدالة main
-، سنرى قيمة 5 مطبوعةً لأن main
لا ترى سوى المتغير العام؛ أما عندما نطبع num1
من داخل الدالة printNumbers
، فإنه سيرى المتغير المحلي وسوف يطبع القيمة 10
، وعلى الرغم من أنّ printNumbers
يُنشئ متغيرًا جديدًا num1
ويُسند له قيمة 10
، إلا أنه لا يؤثر على المتغير العام وستبقى قيمته 5.
عند التعامل مع المتغيرات، من المهم أن تختار بين استخدام المتغيرات العامة أو المحلية. يُفضل في العادة استخدام المتغيرات المحلية، ولكن إذا وجدت نفسك تستخدم نفس المتغير في عدة دوال، فقد ترغب في جعله عامًا. أما إن كنت تحتاج المتغير داخل دالة أو صنف واحد فقط، فقد يكون الأولى استخدام متغير محلي.
الثوابت
تشبه الثوابت المتغيرات إلا أنه لا يمكن تعديلها بعد التصريح عنها، وهي مفيدة عندما تريد استخدام قيمة محددة في برنامجك ولا تريد تعديلها أبدًا مثل قيمة باي pi في الرياضيات 3.14 أو إذا أردنا التصريح عن معدل الضريبة لنظام عربة التسوق، فيمكننا استخدام ثابت ثم حساب الضريبة في مناطق مختلفة من برنامجنا، فإذا تغير معدل الضريبة في وقت لاحق، فسيتعيّن علينا فقط تغيير هذه القيمة في مكان واحد في برنامجنا؛ أما إذا استخدمنا متغيرًا، فمن الممكن تغيير القيمة عن طريق الخطأ في مكان ما في برنامجنا، مما قد يؤدي إلى حساب غير صحيح.
يمكننا استخدام الصيغة التالية للتصريح عن ثابت:
const shark = "Sammy" fmt.Println(shark)
يكون الخرج كما يلي:
Sammy
إذا حاولت تعديل قيمة الثابت بعد التصريح عنه، فسنحصل على الخطأ التالي:
cannot assign to shark
يمكن أن تكون الثوابت من دون نوع untyped
، وهذا مفيد عند التعامل مع أعداد مثل بيانات الأعداد الصحيحة، فإذا كان الثابت من النوع untyped
فيُحوّل صراحةً، حيث لا تُحوَّل الثوابت التي لديها نوع typed
.
package main import "fmt" const ( year = 365 leapYear = int32(366) ) func main() { hours := 24 minutes := int32(60) fmt.Println(hours * year) fmt.Println(minutes * year) fmt.Println(minutes * leapYear) }
يكون الخرج كما يلي:
8760 21900 21960
عرّفنا المتغير year
على أنه ثابت من دون نوع untyped، وبالتالي يمكن استخدامه مع أيّ نوع بيانات آخر؛ أما لو حددنا له نوعًا وليكن int32
كما فعلنا مع الثابت leapYear
، فلن نستطيع التعامل معه إلا مع بيانات من النوع نفسه لأنه typed constant.
عندما عرّفنا المتغير hours
بدون تحدبد نوعه، استنتجت جو تلقائيًا أنه من النوع int
لأننا أسندنا القيمة 24
له؛ أما عندما عرّفنا المتغير minutes
، فقد حددنا له صراحةً نوع البيانات int32
من خلال التعليمة minutes := int32(60)
.
دعنا الآن نتصفح كل عملية حسابية وسبب نجاحها:
hours * year
هنا المتغير hours
من النوع الصحيح والثابت year
من دون نوع، وبالتالي لإجراء العملية تحوِّل جو الثابت year
إلى النوع المطلوب؛ أي تحوّله إلى النوع int
.
minutes * year
هنا المتغير minutes
من النوع int32
والثابت year
من دون نوع، لذا تحوِّل جو الثابت year
إلى النوع int32
.
minutes * leapYear
المتغير minutes
والثابت leapYear
كلاهما من النوع int32
، لذا لن تحتاج جو لأيّ عملية تحويل فكلاهما متوافقان.
إذا حاولت إجراء عملية حسابية مثل الضرب بين نوعين مختلفين أي typed، فلن ينجح الأمر:
fmt.Println(hours * leapYear)
يكون الخرج كما يلي:
invalid operation: hours * leapYear (mismatched types int and int32)
هنا hours
هي من النوع int
كما تحدثنا والثابت leapYear
من النوع int32
، بما أنّ جو لغة تعتمد على تحديد النوع typed language، فهذا يعني أنّ النوعين int
و int32
غير متوافقين لإجراء العمليات الحسابية، ولحل المشكلة عليك تحويل أحدهما إلى نوع الآخر.
الخاتمة
لقد مررنا في هذا المقال على بعض حالات الاستخدام الشائعة للمتغيرات في جو، فالمتغيرات هي لبنة مهمة في البرمجة، إذ تُمثِّل حاضنةً لمختلف أنواع البيانات في جو ومن خلالها نستطيع إجراء العديد من العمليات وتخزين القيم التي نحتاجها.
ترجمة -وبتصرف- للمقال How To Use Variables and Constants in Go لصاحبه Gopher Guides.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.