حال ببینیم وقتی مقادیری را به تابع گذر میدهیم ، چه اتفاقی رخ میدهد . در اینجا برنامه سادهای را ملاحظه میکنید که دو مقدار صحیح 4 و 7 را به تابعی به نام gets2 میفرستد : main ( ) { int x = 4 , y = 7 ; gets2 (x , y) ; } void gets2(xx , yy) /* print out values of two arguments */ int xx , yy ; { printf ("first is %d , second is %d" , xx , yy) ; } این تابع عمل خاصی انجام نمیدهد ، فقط دو مقداری را که به آن گذر داده شده است ، چاپ میکند ؛ اما تابع مزبور نکته مهمی را نشان میدهد : نکته مهم این است که تابع دو مقدار از برنامه فراخواننده آن دریافت میکند و آنها را بطور جداگانه ، یعنی بهصورت دوبله در فضای حافظه خاص خودش ذخیره میکند . حتی تابع میتواند به آن دو مقدار ، اسامی متفاوتی (مانند مثال مورد نظر ما) که فقط در تابع مزبور شناخته شده است ، اختصاص دهد ، که در اینجا این اسامی جدید نیز xx و yy است که به جای x و y بکار رفته است (البته میتوانست همان x و y نیز بکار برده شود) . شکل زیر این مکانیسم را نمایش میدهد . حال این تابع میتواند روی متغیرهای جدید xx و yy بدون اینکه تأثیری روی x و y داشته باشد ، هر عملی را انجام دهد ( اگر اسامی یکسان انتخاب میشد ، باز هم در شیوة کار تغییری حاصل نمیشد ) . اشارهگرها ، اغلب بعنوان آرگومان ، به یک تابع گذر داده میشود . این عمل اجازه میدهد که عناصر دادههای برنامه فراخواننده (که معمولا تابع main است) بهوسیلة تابع فراخوانده شده ، قابل دستیابی باشند و در داخل تابع فراخوانده شده تغییر یابند و نتیجه در تابع یا برنامه فراخواننده نیز اعمال گردد . این گونه کاربرد اشارهگر را گذر دادن آرگومانها به وسیلة آدرس و یا محل نامند . وقتی که آرگومانها به وسیلة مقدار ، گذر داده میشوند . عناصر داده به تابع کپی میشوند . بنابراین هر گونه تغییرات اعمال شده در روی آنها در درون تابع یا روتین فراخواننده ، اثر نمیگذارد . وقتی که آرگومان بصورت آدرس انتقال مییابد (یعنی وقتی که یک اشارهگر به یک تابع گذر داده میشود) ، در واقع آدرس آن قلم از داده به تابع فرستاده میشود . حال محتوای آن آدرس به راحتی هم در درون تابع مزبور و هم در تابع فراخواننده قابل دستیابی است . بهعلاوه هر تغییراتی که روی آن قلم داده انجام پذیرد (یعنی هر گونه تغییراتی که در محتوای آدرس مورد نظر انجام گیرد) هم در تابع فراخوانده شده و هم در برنامه یا تابع فراخواننده تأثیر میگذارد و تشخیص داده میشود . دو برنامة نمایش داده شده در مثالهای 1 و 2 دو گونه از تابعی به نام add1 را نشان میدهد که آرگومان خود را یک واحد افزایش میدهد . مثال 1 متغیر count را با روش فراخوانی با مقدار ، به تابع گذر میدهد . تابع add1 آرگومان خود را یک واحد افزایش میدهد و مقدار جدید را با دستور return به تابع main برمیگرداند . مقدار جدید در تابع main به count نسبت داده میشود .در مثال 2 ، برنامه مورد نظر ، متغیر count را با فراخوانی آدرس ، گذر میدهد . یعنی آدرس count (نه مقدار آن) به تابع add1 گذر داده میشود . تابع add1 یک اشارهگر به مقدار صحیح را که در اینجا countptr نامیده شده است بعنوان آرگومان دریافت میکند . تابع add1 مقداری را که با countptr به آن اشاره شده است (یعنی محتوای محلی را که آدرس آن در اشارهگر countptr قرار دارد) یک واحد افزایش میدهد . این عمل همچنین مقدار count را در main تغییر میدهد. مثال 1.- افزایش مقدار یک متغیر با بکار بردن فراخوانی با مقدار : #include <stdio.h> main ( ) { int count = 7 ; int add1(int) ; printf ("the original value of count is %d\n" , count) ; count = add1 (count) ; printf ("the new value of count is %d\n" , count) ; } int add1(int c) { return ++c ; \* incrementsl variable c */ } خروجی برنامه | the original value of count is 7 the new value count is 8 |
مثال 2 - افزایش مقدار یک متغیر به وسیلة فراخوانی با آدرس : # include <stdio.h> main ( ) { int count = 7 ; int add1 (int *) ; printf("the original value of count is %d\n" , count) ; add1 (& count) ; printf ("the new value of count is %d/n" , count) ; } void add1 (int *countptr) { + + (*countptr) ; /* increments count in main */ } خروجی برنامه | the original value of count is 7 the new value count is 8 |
پارامتر متناظر تابعی که یک آدرس را بعنوان آرگومان دریافت میکند ، باید یک اشارهگر باشد . مثلاً عنوان تابع add1 در مثال قبلی بصورت زیر است : void add1 (int *countptr) و بیان میکند که add1 آدرس یک متغیر از نوع int را دریافت خواهد کرد و آن را در countptr ذخیره خواهد نمود . - انتقال دوطرفه اطلاعات ( بین تابع اصلی و فرعی به کمک اشارهگرها )
وقتی که تابعی ، آدرس متغیری را در برنامه فراخواننده آن بداند ، میتواند هم مقادیری را در این متغیرها قرار دهد (یعنی مقادیر آنها را تغییر دهد) و هم مقادیر آن متغیرها را بکار برد . بنابراین به کمک اشارهگرها میتوان مقادیر را هم از برنامه فراخواننده به تابع فراخوانده شده و هم از تابع فراخوانده شده به برنامه فراخواننده آن (درواقع در هر دو جهت) گذر داد . البته قبلاً در مبحث توابع ، انتقال مقادیر به روش فراخوانی با مقدار را دیدهایم . ولی روش مذکور در بالا ، ما را قادر میسازد که بیش از این مقدار را به برنامه یا تابع فراخواننده برگردانیم . و بدین طریق محدودیتی که در برگرداندن نتایج تابع فرعی به کمک نام آن تابع وجود دارد و در آن فقط میتوان یک مقدار را با نام تابع برگرداند ، از بین برد . مثال زیر اهمیت و کاربرد آن این موضوع را نشان میدهد . مثال - برنامهای بنویسید که ضرایب معادله درجه دومی را بخواند و سپس با فراخوانده شدن تابع فرعی به نام root ریشههای معادله مزبور محاسبه گردد و به تابع اصلی برگردانده شود . اگر معادله ریشه حقیقی نداشته باشد ، تابع فرعی هر دو ریشه را بعنوان صفر برگرداند . در ضمن اگر معادله ریشه داشته باشد ، ضرایب معادله همراه با ریشههای آن در تابع اصلی چاپ شود در غیر اینصورت ضرایب آن همراه با پیغام مناسب چاپ شود . حل : برنامه مورد نظر در زیر نشان داده شده است : #include <stdio.h> # include <math.h> main ( ) { float a , b , c , x1 , x2 ; scanf ("%f %f %f" , &a , &b , &c) ; root (a , b , &x1 , &x2) ; if (x1 = = 0 && x2 = = 0) printf ("\n %f %f %f no real solution" , a , b , c) ; else printf ("\n %f %f %f %f %f" , a , b , cx1 , x2) ; } void root (a , b , c , px1 , px2) float a , b , c , *px1 , *px2 ; { float d , delta ; delta = b*b - 4*a*c ; if (delta < 0) { *px1 = *px2 = 0 ; return ; } else { d = sqrt (delta) ; *px1 = (-b+d) / (2*a) ; *px2 = (-b-d) / (2*a) ; return ; } } در فراخوانی تابع root آدرس متغیرهای x2 , x1 (که باید ریشههای معادله را بپذیرد) به تابع مذکور گذر داده میشود و سپس در تابع root اگر معادله دارای ریشههای حقیقی باشد ، مقادیر آنها به متغیرهای x2 , x1 نسبت داده میشود (یعنی در محلهایی که آدرس آنها در متغیر اشارهگر px1 و px2 میباشد قرار میگیرد) در غیر اینصورت به هر دو متغیر ، مقدار صفر نسبت داده میشود . بدین طریق بیش از یک مقدار از تابع فرعی به اصلی برگردانده میشود . بین اشارهگرها و آرایهها رابطه نزدیکی وجود دارد . قطعه برنامه زیر را درنظر بگیرید : char str[80] , *p ; p = str ; میدانیم که نام آرایه ، همان آدرس اولین عنصر آرایه است . بنابراین دو دستور : p = &str[0] ; p = str ; همارز میباشند . پس در قطعه برنامه بالا در اشارهگر p ، آدرس آرایه (یعنی آدرس اولین عنصر آرایه) قرار داده شده است . حال اگر بخواهیم به پنجمین عنصر در str دسترسی داشته باشیم ، میتوانیم این کار را با دو طریق زیر انجام دهیم : str[4] یا *(p+4) هر دو دستور ، پنجمین عنصر را برمیگردانند . همینطور اگر داشته باشیم : int a[15] , *p ; p = &a[0] ; دو عبارت a[3] و *(p+3) همارز بوده و هر دو چهارمین عنصر از 15 عنصر را برمیگردانند . یعنی بطور کلی عنصر : a[k] همارز با : *(p + k) خواهد بود و هر دو محتوای خانه k اُم آرایه a و یا (k+1) اُمین عنصر از مجموعه عناصر مزبور را برمیگردانند و همینطور عبارت مزبور همارز : *(a+k) میباشد . زیرا a نام آرایه ، معرف آدرس آغاز آن میباشد و (a+k) آدرس خانه k اُم آرایه خواهد بود و در نتیجه عنصر : *(a + k) محتوای خانه k اُم از آرایه مزبور را برمیگرداند . بنابراین C ، سه روش برای دستیابی به عناصر آرایه در اختیار ما قرار میدهد . اما مهم است بدانیم که دستیابی به عناصر آرایه از طریق اشارهگر ، سریعتر از روش استفاده از اندیس است . لذا روش : *(p + k) و همینطور : *(a + k) سریعتر از a[k] عمل میکند . بدین لحاظ استفاده از اشارهگرها برای دستیابی به عناصر آرایه ، یک روش بسیار متداول در زبان C میباشد . حال برای تفسیر k در عبارتهای *(a+k) و *(p+k) به برنامه زیر توجه نمایید . /* prints out values from array */ main ( ) { static int nums[ ] = {92 , 81 , 70 , 69 , 58} ; int k ; for (k = 0 ; k<5 ; k+ +) printf (" %d\n " , nums[k]) ; } برنامه مزبور یک برنامه ساده است که در آن برای دستیابی به عناصر آرایه ، از روش متعارف علامتگذاری آرایه استفاده شده است . حال همان برنامه با روش بکارگیری از اشارهگر بصورت زیر خواهد بود : /* uses pointers to print out values from array */ main ( ) { static int nums[ ]= {92 , 81 , 70 , 69 , 58} int k ; for (k=0 ; k<5 ; k++) printf (" %d\n ", *(nums + k)) ; } شکل زیر نشان میدهد که منظور از *(nums+k) محتوای k خانه ، بعد از nums است که در آن بزرگی هر خانه مساوی بزرگی داده یا شئی مورد نظر در آرایه برحسب بایت است که در مثال بالا برابر 2 بایت میباشد . همچنین در عبارت : *(nums + k) نقش اپراتور ستاره را بهعنوان عملگر غیرمستقیم ملاحظه میکنید که محتوای خانه یا آدرس nums+k را در اختیار قرار میدهد. ادامه دارد ...
|