سلام دوستان .
به دليل مشغله ي كاري كه دارم نتونستم خودم بنويسم.
اگه مشكلي چيزي بود پيغام بزنيد بيام توضيح بيشتري بدم .
اشاره گرها در زبان ++C
یکی از مهمترین مباحث کاربردی هر زبان برنامه نویسی اشاره گر و مفهوم آن است، که کاربرد گسترده ای در شاخه ساختمان داده ها نیز دارد. در این فرصت با مفهوم اشاره گر و همیطور روش تعریف آن در زبان ++C بیشتر آشنا می شویم. باید توجه داشته باشید که سوای روش تعریف اشاره گر در این زبان، کلیت مفهوم آن در بین تمام زبانها مشترک است.
فبل از شروع بحث دو مطلب مهم را یادآوری می کنم:
1- همانطور که می دانید تک تک بایتهای حافظه برای خود آدرسی دارند، که یک عدد صحیح و مثبت است. این آدرس دقیقا مانند کد پستی عمل می کند. یعنی کاملا منحصربفرد بوده، و می توان از آن برای ارجاع به بایت استفاده کرد.
2- هر متغیری پس از تعریف، متناسب با نوع خود چند بایت از حافطه را اشغال می کند. این بایتها همگی متوالی بوده و در حافظه پشت سر هم قرار دارند.
به زبان ساده، اشاره گر نوعی متغیر است که محتوای آن آدرس یکی از خانه های حافظه کامپیوتر است. به این مثال توجه کنید:
کد:
long int a;
long int *b;
b = &a;
دو خط اول متغیر a را به عنوان متغیر صحیح و b را یه اشاره گر صحیح معرفی می کنند. این تعریف اشاره گر به کامپایلر می گوید که آدرس موجود در اشاره گر b مربوط به یک متغیر صحیح است. توسط خط سوم آدرس متغیر a در b قرار می گیرد. مثلا اگه متغیر a بایتهای شماره 1000 تا 1003 را در اختیار داشته باشد، مقدار 1000 برای متغیر b در نظر گرفته می شود. اصطلاحا گفته می شود که اشاره گر b به a اشاره می کند. اینجا ممکن است دو سوال پیش بیاد: چرا فقط آدرس بایت اول در اشاره گر قرار می گیرد؟ و چرا برای اشاره گر نوع تعیین می شود؟ مگر نه اینکه آنها حاوی آدرس حافظه هستند؟ به چه دلیل نوع محلی که اشاره گر به آن اشاره دارد مشخص می شود؟
به مثال زیر توجه کنید:
کد:
long int a, *b, c;
a = 69355;
b = &a;
c = *b;
عبارت b* را بخوانید: "محتوای محلی که متغیر b به آن اشاره دارد". پس خط آخر مقدار ۶9355 را به c اختصاص می دهد (به کاربردهای متفاوت اپراتورهای & و * دقت کنید). فرض کنیم بایتهای شماره 1000 تا 1003 برای ذخیره کردن مقدار متغیر a استفاده شده باشند (متغیر از نوع long int در حالت استاندارد چهار بایت اندازه دارد). پس اشاره گر b مقدار 1000 را دارد. وقتی برنامه به خط آخر می رسد باید محتوای محلی را که b به آن اشاره می کند در c قرار دهد. اما از کجا متوجه می شود که علاوه بر بایت شماره 1000 باید سه بایت بعدی را هم استفاده کند؟ و چطور متوجه می شود که باید این چهار بایت را به صورت عدد صحیح تفسیر کند؟ ممکن است این چهار بایت مربوط به یک عدد اعشاری چهار بایتی float باشد. به همین خاطر است که نوع اشاره گر تعیین می شود. وقتی اشاره گر b از نوع long int مشخص شده است، برنامه متوجه می شود که باید از چهار بایت به صورت عدد صحیح استفاده کند. اگر تعیین نوع برای اشاره گر انجام نمی شد مشکلات زیادی به وجود می آمد. البته زبان برنامه نویسی ++C اشاره گرهای بدون نوع (void) هم دارد، که کاربرد اختصاصی خود را دارند.
مطمئنا کاربرد اشاره گرها تنها محدود به مثال بالا نمی شود! یکی از کاربردهای مهم اشاره گر مربوط به انتقال داده ها بین توابع مختلف در برنامه است. متغیرهایی که در حالت عادی به عنوان پارامتر به یک تابع ارسال می شوند، از تغییر پیدا کردن توسط تابع مصون هستند. چرا که تابع یک کپی از آنها را دریافت می کند. مثلا:
کد:
void func( int n )
{
n++;
}
void main( )
{
int n = 10;
func( n );
cout << n;
}
متغیر n مربوط به تابع func یک واحد افزایش پیدا می کند. اما این تغییر تاثیری در متغیر n در تابع اصلی ندارد. پس عدد 10 توسط cout به خروجی ارسال می شود. اما مواقعی هست که ما نیاز داریم بتوانیم مقدار متغیر را تغییر دهیم. مانند تابعی که دو متغیر را دریافت کرده و مقدار آنها را با هم عوض می کند. اینجا اشاره گر به کمک ما می آید:
کد:
void swap( int *a, int *b )
{
int t;
t = *a;
*a = *b;
*b = t;
}
void main( )
{
int m = 15, n = 10;
swap( &m, &n );
cout << "m = " << m << " , n = " << n;
}
به جای محتویات متغیرهای m و n، آدرس حافظه آنها به تابع ارسال می شود. پس اشاره گر a به m و اشاره گر b به n اشاره می کنند. حال به مراحل مختلف تابع swap توجه کنید:
خط دوم: محتوای محلی که a به آن اشاره دارد (یعنی مقدار m) در t قرار می گیرد. پس t = 15.
خط سوم: محتوای محلی که b به آن اشاره دارد (یعنی مقدار n) به محلی که a به آن اشاره دارد (یعنی m) منصوب می شود. پس m = 10.
خط چهارم: محتوای t به محلی که b به آن اشاره دارد (یعنی n) منصوب می شود. پس n = 15.
بعد از اینکه کنترل به تابع اصلی باز می گردد، مقادیر m و n با هم عوض شده اند، و خروجی به این صورت است:
اما مهمترین و اصلی ترین کاربرد اشاره گرها مربوط به کار با متغیرهای پویا است. متغیرهای عادی که در برنامه ها مورد استفاده قرار می گیرند ایستا هستند. یعنی حافظه آنها توسط خود برنامه اختصاص داده شده و در پایان نیز توسط خود برنامه آزاد می شوند. متغیرهای پویا عکس این حالت هستند. یعنی باید خودتان حافظه بگیرید و خودتان آزاد کنید! این نوع متغیر توسط اشاره گر کنترل می شود. به مثال ساده زیر توجه کنید:
کد:
void main( )
{
int *p;
p = new int;
*p = 5;
cout << *p;
delete p;
}
توسط دستور new حافظه ای به عنوان عدد صحیح رزرو شده، و آدرس آن در اشاره گر p قرار می گیرد. بعد از انجام دادن هر عملیات دلخواهی روی مقدار ذخیره شده در این حافظه، با استفاده از دستور delete حافظه آزاد می شود.
تخصیص حافظه نیز دو کاربرد بسیار بسیار مهم دارد: آرایه های پویا و پیاده سازی ساختارهای مبجث ساختمان داده ها.
اشاره گر به نابع:
زمانی که یک برنامه اجرا می شود، کدهای مورد نیاز آن در حافظه اصلی کامپیوتر بارگذاری می شوند، تا اجرای آن با سرعت بیشتری انجام گیرد. بنابراین کدهای برنامه نیز همانند متغیرهای مورد استفاده در آن شامل آدرسی هستند. هر تابع هم بلوکی از حافظه را در اختیار می گیرد که آدرس شروع آن به عنوان آدرس تابع در نظر گرفته می شود. یک اشاره گر امکان نگه داری چنین آدرسی را نیز دارد.
تعریف چنین اشاره گری را با یک مثال نشان می دهم. فرض کنید تابعی به صورت زیر داریم:
کد:
long int func( int m, float n );
اشاره گر به چنین تابعی اینگونه تعریف می شود:
کد:
long int (*p)( int, float );
این تعریف مشخص می کند که p یک اشاره گر به مجموعه توابعی است که دو پارامتر به ترتیب از نوع int و float دارند و یک متغیر صحیح از نوع long int بر می گردانند. آدرس هر تابعی که چنین ساختاری داشته باشد می تواند در اشاره گر p قرار بگیرد. به قرار داده پرانتز در دو طرف تعریف اشاره گر p هم توجه داشته باشید. نبود این پرانتزها تعبیر دیگری را برای کامپایلر تداعی می کند که در نهایت به خطا منجر می شود.
پس از تعریف چنین اشاره گری، با دستور انتصابهای زیر می توانید آدرس تابعی را در آن قرار دهید:
هر دو دستور آدرس تابع func را در اشاره گر p قرار می دهند.
پس از این مقدار دهی می توانید از اشاره گر برای فراخوانی تابع به صورت زیر استفاده کنید:
این خط معادل دستور زیر است:
کد:
cout << func( 6, 7.5 );
اشاره گر به توابع کاربردهای ویژه ای دارد که بحث جداگانه ای را می طلبد.
اشاره گر به ساختمان و کلاس:
متغیرهای تعریف شده از ساختمانها و کلاسها نیز همچون متغیرهای عادی فضایی در حافظه کامپیوتر در اختیار می گیرند که می توان اشاره گر به آنها تعریف کرد. فرض کنید ساختمانی با تعریف زیر داریم:
کد:
struct student
{
char name[ 100 ];
float average;
int age;
};
با داشتن چنین ساختاری دستورات زیر معتبر هستند:
کد:
student st, *p;
p = &st;
(*p).age = 14;
در خط اول متغیر st از نوع ساختمان student و اشاره گر p به این نوع ساختمان تعریف شده است. در خط بعدی آدرس متغیر st در اشاره گر p قرار گرفته و در خط آخر محتوی فیلد age مربوط به محلی که p به آن اشاره دارد (در اینجا st) برابر 14 می شود.
در زبان برنامه نویسی ++C روش دیگری نیز برای دسترسی به فیلدهای یک ساختمان از طریق اشاره گر وجود دارد. دو عبارت زیر معادل هم هستند:
کد:
(*p).age = 14;
p->age = 14;
استفاده از عملگر <- خوانایی برنامه را بیشتر می کند.
اشاره گر به اشاره گر:
اشاره گر متغیری است که محتوی آن آدرس خانه ای از حافظه است. پس خود این اشاره گر هم در خانه ای از حافظه قرار دارد. اشاره گر به اشاره گر متغیری است که آدرس خانه حافظه ای را در خود نگه می دارد که محتوای آن خود آدرس یکی از خانه های حافظه است. به عبارت دیگر محتوی اشاره گر معمولی، آدرس خانه حافظه متغیرهایی از نوع متغیرهای استاندارد غیر اشاره گر زبان برنامه نویسی ++C، و ساختمانها و کلاسها و توابع است. اما اشاره گر به اشاره گر آدرس خانه حافظه متغیری از نوع اشاره گر معمولی را نگه می دارد.
برای تعریف چنین اشاره گری از ** استفاده می کنیم:
کد:
int a;
int *p1 = &a;
int **p2 = &p1;
اشاره گر به اشاره گر کاربرهای مهمی مانند تعریف آرایه های پویای دو بعدی دارد.
عملیات ریاضی روی اشاره گرها:
محتوی یک اشاره گر آدرس خانه حافظه است که عدد صحیحی می باشد. روی این عدد صحیح می توان دو عمل جمع و تفریق را انجام داد. اما این عملیات محدودیتها و نکاتی رو شامل می شود. آدرس خانه حافظه را می توان به شماره پلاک منازل تشبیه کرد. ما هیچ دو شماره پلاک را با هم جمع یا از هم کم نمی کنیم! در مورد اشاره گرها هم اینگونه است و نمی توان دو اشاره گر را جمع و یا یکی را از دیگری کم کرد. اما می توان عدد صحیحی را به آن اضافه نمود یا از آن کم کرد:
کد:
long int a = 100, *b, *c;
b = &a;
c = b + 1;
c++;
فرض کنیم متغیر a از خانه شماره 1000 شروع شده باشد. پس مقدار b با توچه به دستورات فوق 1000 خواهد بود. در خط بعدی b را با عدد یک جمع زده و در c قرار می دهیم. اما مقدار اشاره گر c بر خلاف حالت عادی 1001 نخواهد شد. متغیر c یک اشاره گر به عدد صحیح بزرگ است. زمانی که آن را با عدد یک جمع می زنیم، این اشاره گر به اندازه فضای مصرفی عدد صحیح بزرگ (در حالت استاندارد چهار بایت) پیش رفته و به خانه 1004 اشاره خواهد کرد. به همین ترتیب اگر از b یک واحد کم می کردیم به جای 999 به خانه شماره 996 اشاره می کرد. دلیل این مساله هم مشخص است. این چهار بایت در کنار هم معنی عدد صحیح بزرگ را دارند و حرکت در داخل بایتهای آن معنی ندارد. اگر اشاره گر به نوع دیگری تعریف شده بود، دقیقا به میزان فضای مصرفی همان نوع حرکت به جلو یا عقب صورت می گرفت. در خط آخر قطعه کد بالا هم باز یک پیش روی به جلو صورت گرفته و مقدار 1008 در خود اشاره گر c قرار می گیرد.
به نقل از :
labod.ir
موفق باشيد