عند استدعاء اي دالة وعند تمرير المعاملات يتم تمريره فى لغات البرمجة عن طريق ام عن طريق الاستدعاء على حسب القيمة call by value او الاستدعاء على حسب المرجع call by reference.
Call by Value
الطريقة الاشهر والاكثر استخدامًا وهى تمرير القيمة للدالة عن استدعائها وتستخدم هذه الطريقة فى لغة C و لغة ++C.
ويتم ذلك عن طريق اخذ نسخة من قيمة المعامل فعند التعديل على قيمة هذا المتغير داخل الدالة Local Scope لن يتم التأثير علي قيمته بعد اتنهاء الدالة.
مثال للتوضيح بلغة C
#include <stdio.h> int increment(int var) { var = var+1; return var; } int main() { int num1=20; int num2 = increment(num1); printf("num1 value is: %d", num1); printf("\nnum2 value is: %d", num2); return 0; }
فى المثال السابق بلغة C يتم استدعاء الدالة increment وتمرير المعامل num1 بعد ذلك يتم التعامل مع نسخة من قيمة المتغير وليس المتغير نفسه داخل الدالة لذلك عن طباعة قيمة num1 بعد استدعاء الدالة نجد انه لايتغير -لان الدالة تعاملت مع نسخه من وليس المتغير نفسه.
الخرج للكود السابق
num1 value is: 20 num2 value is: 21
Call by Reference
الاستراتيجية الاخرى لتمرير المعاملات هى Reference , فى هذه الطريقة بدلًا من تمرير نسخة من قيمة المتغير إلى الدالة يتم تمرير العنوان الذى يشير إلى هذا المتغير بمعنى انه عندما يتم التغيير فى قيمة المتغير داخل الدالة سيؤثر ذلك على المتغير الاصلى وليس نسخة منه.
تدعم العديد من لغات البرمجة Call by Reference ، مثل C أو C ++ ، لكن Perl تستخدمه بشكل افتراضي.
مثال للتوضيح بلغة C
#include<stdio.h> void change(int *num) { printf("Before adding value inside function num=%d \n",*num); (*num) += 100; printf("After adding value inside function num=%d \n", *num); } int main() { int x=100; printf("Before function call x=%d \n", x); change(&x);//passing reference in function printf("After function call x=%d \n", x); return 0; }
الخرج
Before function call x=100 Before adding value inside function num=100 After adding value inside function num=200 After function call x=200
فى المثال السابق عند التغير فى قيمة x داخل الدالة change يتم التغيير فى النسخة الاصلية x. لذلك عن طباعة قيمة المتغير x بعد تنفيذ الدالة نجد القيمه 200.
الآن ماذا عن الطريقة المستخدمة فى Python
الحقيقة ان هناك نوع ثالث من طرق تمرير المعامل وهى Pass by object reference وهو الاسلوب الذى تستخدمه لغة python.
فى بايثون عندم تقوم بعمليه Assign اي وضع قيمة معينة فى متغير فأنك تحجز مكان فى الذاكرة وتجعل متغير معين يشير لهذا المكان فى الذاكرة على سبيل المثال
x = 20
عند تمرير متغيرات غير قابل للتغيير immutable مثل integers و strings و tuples فالدالة تتعامل بطريقة شبيهة لل call-by-value.
مثال
def ref_demo(x): x=42 x=10 ref_demo(x) print(x)
الخرج يكون 10
ام فى حالة قمنا بتمرير متغير من النوع mutable اي قابل للتغيير مثل lists او objects فهناك حالتين
فى حالة التغيير على المتغير فى نفس المكان فى الذاكرة in-place فى هذه الحالة يتم التعامل على انها call-by-reference مثل المثال القادم.
مثال
def append_one(li): li.append(1) x = [0] append_one(x) print x
لان المتغير li مازال يشير على نفس القيمة فى الذاكرة حتى بعد تغيرها بالتالى التغييرات تكون فى المتغير الاصلى.
ام فى حالة اعادة Assign يتم التحويل إلى سلوك شبيه ب Call-by-value
مثال
def append_one(li): li = [0, 1] x = [0] append_one(x) print x
والخرج يكون
[0]
لانه عند اعادة assign ل li داخل الدالة فان li يشير على مكان جديد فى الذاكرة وبالتالى التغييرات داخل الدالة لن تغير فى الاصل.