جمع اشارهگرها (آدرسها و مقادیر) دو راه برای مراجعه به آدرس یک عنصر از آرایه وجود دارد . یکی بصورت nums+k در فرم اشارهگر و دیگری بصورت nums[k] در فرم آرایه . حال اجازه دهید با ارائه یک برنامه ساده ، رابطه بین عناصر آرایه و آدرس آنها را بررسی و ملاحظه نماییم . مثال - برنامه زیر را درنظر بگیرید : # include <stdio.h> main ( ) { static int x[6] = {10 , 11 , 12 , 13 , 14 , 15}; int i ; for ( i=0 ; i<6 ; + +i ) printf("\n i =%d x[i] = %d *(x+i) = %d &x[i] = %x x+i =%x", i , x[i] , *(x+i) , &x[i] , x+i) ; } (فرض بر این است که آدرس شروع آرایه ، 72 در مبنای 16 است) . خروجی برنامه | i = 0 | x[i] = 10 | *(x+i) = 10 | &x[i] = 72 | x+i = 72 | i = 1 | x[i] = 11 | *(x+i) = 11 | &x[i] = 74 | x+i = 74 | i = 2 | x[i] = 12 | *(x+i) = 12 | &x[i] = 76 | x+i = 76 | i = 3 | x[i] = 13 | *(x+i) = 13 | &x[i] = 78 | x+i = 78 | i = 4 | x[i] = 14 | *(x+i) = 14 | &x[i] = 7a | x+i = 7a | i = 5 | x[i] = 15 | *(x+i) = 15 | &x[i] = 7c | x+i = 7c |
از خروجی بالا فرق بین x[i] که معرف i اُمین عنصر آرایه است ، با &x[i] که آدرس آن را نمایش میدهد ، مشخص میگردد . در ضمن مشاهده میشود که مقدار i اُمین عنصر آرایه را میتوان با : x[i] و یا : *( x + i ) معرفی نمود . در ضمن میتوان تفاوت بین : x + i و *( x + i ) را ملاحظه کرد که اولی معرف آدرس و دومی نشان دهندة محتوای آن آدرس است . همچنین نتیجه گرفته میشود که اگر x[i] در سمت چپ یک دستور جایگذاری باشد ، میتوان به جای آن : *( x + i ) را بکار برد . اصولاً همه جا میتوانیم به جای x[i] همارز آن *(x+i) را بکار ببریم . به هرحال عباراتی مشابه : x و x + i و &x[i] که معرف آدرس هستند ، نمیتوانند در سمت چپ دستور جایگذاری بکار برده شوند . همچنین آدرس یک آرایه نمیتواند بطور دلخواه تغییر یابد . بنابراین عبارتی مشابه ++x مجاز نمیباشد . قبلاً بیان شد که نام آرایه ، بطور واقعی یک اشارهگر به اولین عنصر آرایه است . در نتیجه باید امکان داشته باشد که یک آرایه را بجای روش قراردادی متداول ، بعنوان یک متغیر اشارهگر تعریف کرد . به هرحال تعریف آرایه به روش قراردادی ، موجب میگردد که یک بلوک ثابت از حافظه ، در آغاز اجرای برنامه رزرو شود . ولی اگر آرایه برحسب یک متغیر اشارهگر توصیف شود ، با این عمل رزرو کردن جا ، اتفاق نمیافتد . درنتیجه موقع استفاده از اشارهگر برای معرفی یک آرایه ، نیاز است که به طریقی قبل از اینکه عناصر آرایه مورد پردازش قرار گیرد ، به عناصر آرایه ، حافظه اختصاص داده شود . در حالت کلی اختصاص اولیه حافظه در چنین مواردی ، با استفاده از تابع کتابخانهای malloc انجام میگیرد. گرچه شیوة انجام این کار از کاربردی به کاربرد دیگر فرق خواهد کرد . بعضی از این گونه کاربردها ، طی مثالهایی در ادامة این فصل ارائه میگردد . اگر آرایه بصورت یک متغیر اشارهگر تعریف گردد ، نمیتوان به عناصر آرایههای عددی ، مقدار اولیه نسبت داد . در نتیجه این گونه موارد نیاز به تعریف آرایه بصورت روش عادی و قراردادی دارد . مثال - اگر بخواهیم a را بصورت یک آرایه 10 عنصری از مقادیر صحیح تعریف کنیم ، میتوان آن را به جای : int a[10] ; بصورت : int *a ; نوشت . یعنی a را بعنوان متغیر اشارهگر تعریف کرد . به هرحال در روش دوم ، به a بعنوان آرایه 10 عنصری یک بلوک حافظه اختصاص داده نمیشود ، بلکه a بصورت یک متغیر اشارهگر تعریف شده است . برای اختصاص حافظه مورد نیاز به a جهت معرفی یک آرایة 10 عنصری ، میتوان از تابع malloc بصورت زیر استفاده نمود : a = malloc (10 * sizeof(int)) ; این تابع یک بلوک حافظه برای ذخیره کردن 10 مقادیر صحیح رزرو میکند . در دستور بالا ، اپراتور sizeof بزرگی نوع داده int را برحسب بایت برمیگرداند . این مقدار در 10 (تعداد عناصر آرایه) ضرب میشود تا فضای مورد نیاز برحسب بایت تعیین و رزرو شود . a ، بصورت اشارهگر به مقدار صحیح تعریف شده است و تابع malloc یک اشارهگر به کاراکتر برمیگرداند و میدانیم که در زبان C مقادیر صحیح و کاراکترها ، معادل هستند . لذا دستور بالا قابل قبول است . اگرچه از لحاظ اطمینان کامل میتوان از تبدیل نوع cast استفاده کرد و آن را بصورت زیر بکار برد : a = (int *) malloc (10 * sizeof (int)) ; این گونه اختصاص حافظه به روش اختصاص حافظه بصورت پویا موسوم است . به هرحال اگر قرار باشد به عناصر آرایه ، مقدار اولیه نیز اختصاص یابد ، باید a بجای متغیر اشارهگر بصورت آرایه توصیف گردد مشابه زیر : int a[10] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} ; یا : int [ ] = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10} در برنامهنویسی با C ، ممکن است برای مراجعه به عناصر آرایه بجای روش معمول ، عباراتی برحسب اشارهگرها بکار ببریم . این روش در آغاز کار کمی غیرطبیعی به نظر میآید ، ولی میتوان با کمی تمرین ، به سادگی تجربه لازم را در این مورد بدست آورد . در مثال زیر ضمن اینکه روش استفاده از تابع malloc نشان داده میشود ، به عناصر آرایه نیز با استفاده از این تکنیک دستیابی میگردد . مثال - مرتب کردن یک مجموعه اعداد (Sort ) . برنامه مورد نظر در زیر نشان داده شده است : # include <stdio.h> main ( ) { int k , m , *a ; void sort (int k , int *a) ; scanf ("%d" , &m) ; /* read in a value for m */ a = (int*) malloc (m * sizeof(int)) ; /* allocate memory */ for (k = 0 ; k<m ; + +k) /* read in the list of numbers */ scanf ("%d" , a + k) ; sort (m , a) ; for (k=0 ; k<m ; + +k) /* display sorted list , elements */ printf ("\n k=%d a =%d" , k+1 , *(a+k)) ; } void sort (int m , int *a) /* sort array in ascending order */ { int i , j , temp ; for ( i=1 ; i<m ; + +i ) for ( j=0 ; j<m-i ; + +j ) if (*(a+j) < *(a+j+1)) { temp = *(a+j) ; *(a+j) = *(a+j+1) ; *(a+j+1) = temp; } return ; } در این برنامه ، آرایهای با عناصری از نوع مقادیر صحیح ، بصورت یک اشارهگر به یک مقدار صحیح تعریف شده است . در آغاز به کمک تابع کتابخانهای malloc به متغیر اشارهگر ، حافظه ، اختصاص داده شده است . (یعنی حافظه مورد نیاز از سیستم گرفته شده و آدرس اولین بایت آن در a قرار داده شده است) . در هر دو تابع اصلی و فرعی برای پردازش هر عنصر از روش مراجعه به اشارهگر ، استفاده شده است . ملاحظه میکنیم که در تابع scanf نیز برای آدرس عنصر k اُم بجای : &a[k] از : a + k استفاده شده است . به طریق مشابه ، در تابع printf برای معرفی مقدار k اُمین عنصر ، بجای : a[k] از : *(a + k) استفاده شده است . ملاحظه میشود که در تابع فرعی نیز آرگومان آن بجای آرایه ، متغیر اشاره تعریف شده است . اشارهگرها و آرایههای چند بعدی دیدیم که عناصر یک آرایة یک بعدی میتواند برحسب یک اشارهگر (نام آرایه) و یک مقدار بعنوان آفست به منظور جبران کردن مقدار اندیس عنصر مورد نظر آرایه ، نمایش داده شود . مثلاً اگر نام آرایه ، a و عنصر مورد نظر ما : a[5] باشد ، میتوان به آن بصورت : *(a + 5) مراجعه کرد که در آن مقدار آفست همان 5 است که به نام آرایه افزوده شده است و به کمک اپراتور * به مقدار آن دسترسی پیدا میکنیم . حال میتوان گفت که یک آرایه نیز مجموعهای از آرایههای یکبعدی است . بنابراین میتوان یک آرایه دو بعدی را بصورت یک اشارهگر به یک گروه پیوسته و مجاور هم از آرایههای یک بعدی تعریف کرد . درنتیجه میتوان توصیف یک آرایه دو بعدی را بجای : data-type array[d1][d2] ; (که در آن منظور از d1 ، d2 بهترتیب بزرگی ابعاد اول و دوم آرایه است) بصورت زیر نوشت : data-type (*ptvar)[d2] ; این ایده را میتوان به آرایههای n بعدی تعمیم داد و آن را به جای : data-type array [d1][d2]...[dn] ; بصورت زیر نوشت : data-type (*ptvar)[d2][d3]...[dn] ; که در آنها ، data-type ، نوع عناصر آرایه و array نیز نام آرایه است . و عناصر : d1 , d2 , ..., dn نیز به ترتیب ماکزیمم عناصر هر اندیس یا هر بعد آرایه میباشند . درضمن توجه کنید که ptvar نیز نام متغیر اشارهگر است . - انتقال آرایه به تابع ( بعنوان آرگومان )
بطوری که در فصل آرایهها بیان شد ، در زبان C ، نام هر آرایهای که بعنوان آرگومان یک تابع بکار برده شود ، بعنوان آدرس اولین عنصر آرایه تفسیر میگردد . برای مثال برنامه زیر را درنظر بگیرید : main ( ) { float func( ) ; float x , array[15] ; ....... ....... x = func(array) ; /* same as func (&array [0]) */ ....... ....... } حال در تابع فرعی نیاز است که ما آرگومان را بعنوان اشارهگر به اولین عنصر آرایه توصیف کنیم . برای این کار ، دو راه بصورت زیر وجود دارد : راه دوم | | راه اول | func(ar) float ar[ ] ; { ....... ....... } | | func(ar) float *ar ; { ....... ....... } |
راه دوم ، ar را بهعنوان آرایهای با اندازه (یا بزرگی) نامشخص ، توصیف میکند . آرایه هماکنون در تابع اصلی ایجاد شده است ، آنچه گذر داده میشود ، یک اشارهگر به اولین عنصر از آرایه است . چون کامپایلر میداند که عبارت آرایه منتج به اشارهگر به اولین عنصر آرایه میگردد ، پس ar را مشابه توصیف ar در روش اول ، به یک اشارهگر از نوع float تبدیل میکند . بنابراین هر دو گونه ازنظر نحوة عملکرد ، معادل و همارز یکدیگر میباشند . به هرحال ازنظر واضحتر بودن ، ممکن است روش دوم ترجیح داده شود . زیرا این روش تأکید میکند که آنچه که باید گذر داده شود آدرس پایه یا آدرس اولین عنصر یک آرایه است . در روش اول ، راهی برای تشخیص اینکه آیا ar به آغاز یک آرایه از نوع float و یا تنها به یک عنصر از نوع float اشاره میکند یا نه ، وجود ندارد . دو اشارهگر را میتوان در یک عبارت رابطهای با یکدیگر مقایسه کرد . برای مثال اگر q و p دو اشارهگر باشند ، دستور زیر یک دستور درست میباشد : if (p<q) printf ("p points to lower memory than q") ; else printf ("q points to lower memory than p") ; در زبان C میتوان آرایهای از اشارهگرها تعریف کرد. یعنی آرایهای که عناصر آن اشارهگر باشند . دستور زیر آرایهای 10 عنصری از اشارهگرها را توصیف میکند : int *x[10] ; اینها اشارهگرهایی هستند که میتوانند آدرس متغیرهایی از نوع مقادیر صحیح را در خود داشته باشند. بعنوان مثال برای اختصاص دادن آدرس متغیری به نام z به عنصر سوم آرایه مزبور ، مینویسیم : *x[2] = &z ; همینطور برای بدست آوردن مقدار z از دستور **x[2] استفاده می کنیم . آرایهای از اشارهگرها را نیز میتوان مشابه آرایههای معمولی به یک تابع انتقال داد . یعنی به سادگی ، نام آرایه را بدون اندیس یا زیرنویس آن بعنوان آرگومان تابع قرار میدهیم . برای مثال تابع display میتواند آرایه x را بصورت زیر دریافت نماید : void display (int *a[ ] ) { int k ; for ( k=0 ; k<10 ; k+ +) printf (" %p" , *a[k] ) ; } توجه داشته باشید که در مثال بالا ، a یک اشارهگر به مقادیر صحیح نیست بلکه یک اشارهگر به آرایهای از اشارهگرهایی به مقادیر صحیح است . بنابراین نیاز است که پارامتر a بعنوان آرایهای از اشارهگرهایی به مقادیر صحیح به همان طریق که نشان داده شد ، توصیف شود . آرایههای اشارهگر اغلب برای نگهداری اشارهگرهایی به رشتهها بکار برده میشوند . ادامه دارد ....
|