ترحيل المعاملات بالإشارة في ++C
ترحيل المعاملات بالإشارة
قدمنا في الأمثلة السابقة كيفية ترحيل قيم المتغيرات بين الدوال , وفيها تقوم الدالة المستدعاة بإنشاء متغير من نفس نوع المتغير الموجودة في الدالة المستدعية , حتي تخزن فيها القيم المحولة إليها تمهيدا لإجراء ما يلزم عليها من عمليات , فليس في إمكان الدالة المستدعاه التعامل مع متغيرات الدالة المستدعية مباشرة , ولذا فهي تنسخ لنفسها نسخة منها . ومن وجهة نظر معينة فإن هذا مرغوبا , حيث يمثل أمانا لمتغيرات الدالة المستدعية ضد أي تغيير خاطئ من الدالة المستدعاة .
ويعمل أسلوب ترحيل المعاملات بالإشارة refeance argument ] يمكن أن تسمي أيضا ترحيل مرجعي [ بأسلوب مغاير ؛ فبدلا من إرسال قيمة المتغير المراد معالجته , نرسل لها ما يشير إليه لديها , وتتعامل الدالة المستدعية مع هذا المتغير مباشرة وهو في مكانه من الدالة المستدعيه . بمعني أن معاملات الدالة المستدعاة لا تختزن القيم المرحلة لها من الدالة المستدعية , بل تختزن فقط إشارة (أو مرجع) reference للمتغير المراد التعامل معه , ( الواقع أن ما يرحل بين الدالتين هو عنوان المتغير في الذاكرة , وبذلك تستبدل الدالة المستدعاة عليه , ولكن هذا التفصيل ليس له أهمية في فهم فكرة هذا الأسلوب ) , ويميز المعامل الذي يرسل بالإشارة للمتغير وليس بقيمته بعلامة & تلحق باسمه .
وإليك برنامج يطبق هذا الأسلوب :
ref.cpp
// ref.cpp
// demonstrates passing by reference
#include <iostream.h>
#include <conio.h>
void main()
{
void intfrac(float, float&, float&); // prototype
float number, intpart, fracpart; // float variables
do
{
cout << "\nEnter a real number: "; // number from user
cin >> number;
intfrac(number, intpart, fracpart); // find int and frac
cout << "Integer part is " << intpart // print them
<< ", fraction part is " << fracpart;
} while( number != 0 ); // exit loop on 0
getche();
}
// intfrac()
// finds integer and fractional parts of real number
void intfrac(float n, float& intp, float& fracp)
{
intp = float( long(n) ); // convert to long, back to float
fracp = n - intp; // subtract integer part
}
تطلب الدالة الأصلية من المستخدم أن يدخل رقما حقيقيا (عدد صحيح وكسر عشري ) , وتقوم الدالة الفرعية بفصل الجزئين . ثم تعيدها للدالة الأصلية التي تتولي إخراجهما علي الشاشة , مثلا :
Enter a real number: 99.44
Integer part is 99, fractional part is 0.44
وتقوم الدالة الفرعية بفصل الجزء الصحيح بتحويل العدد بأكمله من نوع float إلي نوعين long int عن طريق الأمر :
(راجع التحويل القسري , الفصل الثالث ) ويتسبب هذا التحويل للنوع في أن يحذف الجزء الكسري ( متغيرات الأعداد الصحيحة لا تخزن إلا الجزء الصحيح ) , ثم يعاد الناتج ( الجزء الصحيح ) إلي سيرته الأولي (كعدد كسري ) بتحويل قسري أخر , مخزنا في المتغير intp :
Intp = float ( long(n) );
] لاحظ تحويل قسريين في عبارة واحد [ .
أما الجزء الكسري والمخزن في المتغير fracp فهو ببساطة العدد الأصلي n مطروحا منه الجزء الصحيح intp .
تلاحظ أن معاملي الدالة الفرعية الخاصين بالجزء الصحيح والجزء الكسري معرفين كمعاملات بالإشارة , أما المعامل الأول , الخاص بالعدد الرحل نفسه , فقد عرف علي أنه معامل عادي (معامل (مراحل) بالقيمة value argument ) .
والأهمية العملية لهذا الأسلوب هو إمكانية أن الدالة المستدعاة يمكنها التعامل المباشر مع متغيرات الدالة المستدعية من جهة . ولك أن تدرك ميزة ذلك إذا كان البيان المراحل هيكل بيانات ضخم , مثل هيكل معقد أو مصفوفة كبيرة , وما يسببه نسخ هذه البيانات من إهدار لمسافة التخزين بالذاكرة .
والميزة الجوهرية الثانية لهذا الأسلوب هو إمكانية إعادة أكثر من قيمة للدالة المستدعاه وهو أمر غير متيسر في أسلوب إمرار القيم كما سبق القول . ففي البرنامج المشار إليه أمكن إعادة الجزء الصحيح والجزء الكسري معا من الدالة المستدعاه للدالة الأصلية ويبين الشكل هذا الأسلوب .
شكل الإستدعاء غير المباشر
] في الشكل يعبر المؤلف عن أسماء المتغيرات في الدالة المستدعاة بأنها aliases أي أسماء مستعارة [ .
مثال أخر :
المثال التالي تطبيق أخر علي الإمرار بالإشارة والدالة الفرعية فيه وظيفتها أن ترتب أي عددين تصاعديا في حالة كونهما غير مرتبين .
reforder.cpp
// reforder.cpp
// orders two arguments passed by reference
#include <iostream.h>
#include <conio.h>
void main()
{
void order(int&, int&); // prototype
int ras1=99, ras2=11; // this pair not ordered
int ras3=22, ras4=88; // this pair ordered
order(ras1, ras2); // order each pair of numbers
order(ras3, ras4);
cout << endl << "ras1=" << ras1; // print out all numbers
cout << endl << "ras2=" << ras2;
cout << endl << "ras3=" << ras3;
cout << endl << "ras4=" << ras4;
getche();
}
void order(int& numb1, int& numb2) // orders two numbers
{
if(numb1 > numb2) // if 1st larger than 2nd,
{
int temp = numb1; // swap them
numb1 = numb2;
numb2 = temp;
}
}
إمرار الهياكل بالإشارة
نقدم لك المثال التالي لبيان كيفية التعامل معه الهياكل بالأسلوب غير المباشر . وتقوم الدالة الفرعية بضرب الأبعاد في معامل معين , يمكن النظر إليه كمقياس رسم .
referst.cpp
// referst.cpp
// demonstrates passing structure by reference
// UCS Laboratories
#include <iostream.h>
#include <conio.h>
struct Distance // English distance
{
int feet;
float inches;
};
void scale( Distance&, float ); // function
void engldisp( Distance ); // declarations
void main()
{
Distance ucs1 = { 12, 6.5 }; // initialize ucs1 and ucs2
Distance ucs2 = { 10, 5.5 };
cout << "\nucs1 = "; engldisp(ucs1); // display old ucs1 and ucs2
cout << "\nucs2 = "; engldisp(ucs2);
scale(ucs1, 0.5); // scale ucs1 and ucs2
scale(ucs2, 0.25);
cout << "\nucs1 = "; engldisp(ucs1); // display new ucs1 and ucs2
cout << "\nucs2 = "; engldisp(ucs2);
getche();
}
// scale()
// scales value of type Distance by factor
void scale( Distance& ucs, float factor)
{
float inches = (ucs.feet*12 + ucs.inches) * factor;
ucs.feet = inches / 12;
ucs.inches = inches - ucs.feet * 12;
}
// engldisp()
// display structure of type Distance in feet and inches
void engldisp( Distance ucs ) // parameter ucs of type Distance
{
cout << ucs.feet << "\'-" << ucs.inches << "\"";
}
وقد طبعت الدالة مرتين , مرة لتخفيض أبعاد الأول بمقدار النصف , ومرة لتخفيض أبعاد الهيكل الثاني بمقدار الربع . وتلاحظ أن هذه العمليات جرت علي كل من d1,d2 مباشرة , فإذا لم تعد أي شئ , بل مارست وظيفتها علي الهيكلين وهما بمقرهما في الدالة الأصلية .
ملاحظات حول إمرار الإشارة
هذا الأسلوب متبع في كل من لغتي البيزك والباسكال , ولكنه غير متبع في السي التقليدية فهي تتبع أسلوب المؤشرات عوضا عنه .
تعليقات
إرسال تعليق