تماس درباره   صفحه اصلی
  زبان ++C > اشاره گرها  
 
 

اشاره گرها


در این بخش مبحث مهم اشاره گرها در C معرفی می شود. اشاره گر متغيری است که آدرس متغير ديگری را نگه می دارد که اصطلاحا گفته می شود دارد به آن متغيری اشاره می کند. به دو عملگر * (address-of operator) و & (indirection operator) با اشاره گرها نياز می شود. اشاره گرها روش قدرتمند و انعطاف پذیری برای کار با انواع داده ها در برنامه نظير آرايه ها، رشته ها و ساختمان ها است. کاربرد اشاره گرها برای استفاده از حافظه پویا هم در اینجا توضیح داده خواهد شد.

متغیر اشاره گر
حافظه پویا
محاسبات روی اشاره گر
اشاره گر و آرايه
اشاره گر به ساختمان
اشاره گر به اشاره گر
اشاره گر به تابع
اشاره گر وپارامتر مرجع
آرگومان های خط فرمان


اشاره گرها بخش قدرتمند و دشوار C و ++C هستند. اکثر مبتدیان در درک مناسب اشاره گرها مشکل دارند. متغیرهای استاندارد برچسب هایی هستند که برای تعیین بخش هائی از حافظه کنار گذاشته می شوند تا داده از نوع خاصی را ذخیره کنند درحاليکه یک اشاره گر به بخش هایی از حافظه که توسط متغیر دیگری اشغال شده اشاره می کند. یک متغیر استاندارد یک مقدار را ذخیره می کند اما یک اشاره گر آدرس محلی در حافظه را ذخیره می کند.

متغیر اشاره گر

یک متغير اشاره گر (pointer variable) متغیری است که حاوی آدرس داده ، متغیر دیگر يا تابع است. برای ذخیره آدرس اولین مرحله اعلان متغیر اشاره گر است. اعلان متغير اشاره گر تقریبا مشابه متغيرهای ديگر است تنها باید قبل از نام متغیر علامت ستاره (*) برای نشان داده اینکه یک اشاره گر است اضافه شود. هنگام تعريف اشاره گر نوع داده ای که به آن اشاره می کند باید مشخص شود. اعلان اشاره گر از فرم کلی زیر تبعیت می کند:

typename *ptrname;

typename نوع داده است که اشاره گر به آن اشاره می کند. علامت (*) عملگری است که بیان می کند متغیر اشاره گری به داده ای از نوع typename است. اشاره گر می تواند همراه با متغیر های غیراشاره گری هم اعلان شود.


مثال. متغیر ptr اشاره گری به یک داده صحیح است.

int *ptr;
int* ptr;

مثال. در اعلان زیر age از نوع صحيح و ptr اشاره گری به داده صحيح است.

int *ptr, age;


بعد از اعلان متغیر اشاره گر باید آنرا به جائی اشاره داد يعنی آدرس مکانی از حافظه که حاوی داده مورد نظر است را به اشاره گر اختصاص داد. اگر یک اشاره گر فقط اعلان شود و مقداردهی نشود ممکن است به محل دلخواهی از حافظه اشاره کند و استفاده از آن بدون توجه به اين مسئله می تواند مشکلات مهمی روی سیستم تولید کند. یک تکنیک کلی مقداردهی اشاره گر با مقدار NULL يا صفر است.

راه ديگر برای مقداردهی اشاره گر ذخیره آدرس متغیر دیگر در آن است. وقتی برنامه ای اجرا می شود کلیه اجزای آن در حافظه قرار می گیرد. بنابراین هر جز از برنامه از جمله متغیرها دارای آدرس هستند. عملگر & (address-of operator) آدرس این اجزا را می دهد. عملگر & آدرس عملوند خود را برمی گرداند که می توان این آدرس را درون یک متغیر اشاره گر ذخیره کرد.

مقداردهی یک اشاره گر می تواند فرم کلی زیر را داشته باشد:

pointer = &variable;


مثال. دستور زیر آدرس متغیر age را به اشاره گر ptr اختصاص می دهد.

ptr = &age;

مثال. در برنامه زير اشاره گر j آدرس متغیر i را نشان می دهد.

#include <iostream.h>
int main(){
   int i;
   int* j;
   j = &i;
   i = 4;
   cout << "i is " << i;
   cout << "\n j is " << j;
   return 0;
}


اشاره گرها نوعدار هستند یعنی باید به کامپایلر بگوئید که نوع متغیری که اشاره گر به آن اشاره می کند چیست. نوع اشاره گر و متغیری که به آن اشاره می کند باید یکسان باشد.


مثال.. دستورات زير خطا تولید می کند چون نمی توان اشاره گر int را به نوع char اشاره داد.

char c='0';
int *p=&c;


بعد از مقداردهی از طریق متغیر اشاره گر می توان با داده کار کرد. برای دسترسی به محلی که اشاره گر اشاره می کند از عملگر * استفاده می شود. عملگر* محتوای آدرسی از حافظه را برمی گرداند و عملگر مرجع (indirection operator) نامیده می شود چون در واقع یک ارجاع به آدرسی در حافظه است.


مثال. متغیر اشاره گر ptr به داده صحیح اشاره می کند. توجه کنید چگونه از typedef برای نامگذاری نوع اشاره گر استفاده شده است.

typedef int* IntPtr;
IntPtr ptr;
int age;
age =19;
ptr = &age; // get address of the AgeOfMary variable
cout << "ptr points to " << *ptr << endl;
cout << "age is " << age << endl;

Pointer To Integer

برای اشاره گر ptr که به متغیر age اشاره کند موارد زیر صدق می کند:

• ptr* و age هردو به محتوای متغیر age ارجاع می کنند.
• ptr و age& هر دو به آدرس متغیر age هستند.


عملگر * هم به عنوان عملگر مرجع و هم عملگر ضرب استفاده می شود. کامپایلر از نحوه به کار بردن عملگر * در دستور تشخیص می دهد منظور کدام است. وقتی عملگر * بعد از اسم اشاره گری می آید منظور محلی که است که اشاره گر به آن دارد اشاره می کند.

هر بایت حافظه دارای آدرس جداگانه ای است. بنابراین متغیرهائی مانند نوع int، float و ... که چندبایت در حافظه مصرف می کنند چند آدرس را اشغال می کنند. آدرس یک متغیر در حقیقت آدرس اولین بایت آن است.


مثال. آدرس اولین بایت متغیر به اشاره گر اختصاص داده می شود بنابراین ptrint برابر با 1000، ptrchar برابر با 1003 و ptrfloat برابر با آدرس 1006 می شود.

int i = 12252;
char c = 90;
float f = 1200.156004;
int *ptrint;
char *ptrchar;
float *ptrfloat;
...
ptrint = &i;
ptrchar = &c;
ptrfloat = &f;

Pointer To Multi Byte Data

برنامه نمایش آدرس اجزای برنامه


حافظه پویا

برای درک بهتر اشاره گرها نیاز است درباره نحوه ذخیره اطلاعات درحافظه بیشتر بدانید. حافظه RAM شامل مجموعه ای از محل های ذخیره سازی است که هر محل توسط یک آدرس منحصر بفرد مشخص می شود. وقتی برنامه ای را اجرا می کنید کد برنامه (دستورالعمل های زبان ماشین برنامه) و داده هائی که برنامه با آنها کار می کند در حافظه قرار می گیرند. اگر متغیری در برنامه تعریف شده باشد کامپایلر محلی از حافظه را برای آن کنار گذاشته آدرس آنرا به اسم متغیر مربوط می کند. بنابراین وقتی در برنامه از اسم متغیر استفاده می شود اتوماتیک به آدرس موردنظر مراجعه می شود.

اشاره گرها وقتی نقش ایفا می کنند که بخواهیم از حافظه پویا (يا heap) استفاده کنیم. ناحيه Heap فضای آزاد حافظه است که در اخت‍يار هيچ برنامه ای نيست و می تواند به صورت پويا استفاده می شود، يعنی در حين اجرای برنامه در صورت نياز اختصاص داده می شود و هنگامی که ديگر به آن احتياج نباشد آزاد می شود.

دستورات new و delete برای تخصيص و بازپس گيری حافظه هنگام کار با حافظه پويا استفاده می شوند.

دستور new فضائی از حافظه آزاد را به برنامه اختصاص می دهد. میزان فضای موردنیاز توسط نوع داده ای که بعد از دستور ذکر می شود تعیین می شود. اگر دستور موفق باشد اشاره گری به فضای گرفته شده را برمیگرداند. اگر شکست بخورد مقدار NULL را برمیگرداند.

بعد از اینکه کار با حافظه پویا باید حافظه گرفته شده توسط دستور delete پس داده شود. به این ترتیب حافظه می تواند مجدد توسط برنامه های دیگر استفاده شود.


مثال.

IntPtr = new int;
if (IntPtr == NULL) {
   cerr << "Could not allocate sufficient space" << endl;
   exit(1);
   }
*IntPtr = 55;
delete IntPtr;
IntPtr = 0;


بعد از delete یک اشاره گر آترا با مقدار 0 پر کنید تا تصادفا آن را دوباره استفاده نکنید.

اگر فضا های گرفته شده از حافظه آزاد پس داده نشود و برنامه خاتمه پیدا کند برنامه اصطلاحا دارای مشکل memory leak است. میزان حافظه موجود یعد از اجرای برنامه کمتر از قبل از اجرای آن است. مگراینکه کامپیوتر راه اندازی مجدد شود. بنابراین سعی کنید همیشه حافظه های گرفته شده را آزاد کنید.

دستورات new و delete دو حالت دارند؛ برای آرا‍یه ها اين دو دستور به صورت زیر استفاده می شوند.

new[]();
delete[]();


مثال. برنامه زیر از حافظه پویا استفاده می کند. به آرایه a به اندازه 10 عدد صح‍یح حافظه اختصاص داد ه می شود. به b عددی صحیحی اختصاص می دهد با مقدار اولیه 89.

#include <iostream.h>
int main() {
   int * a= new int[10];
   int * b=new int(89) ;
   cout << "*b=" << *b << endl;
   *(a+5)=9;
   cout << "a[5]=" << *(a+5) << endl;
   delete [] a;
   delete b;
   return 0;
}


نکته. برای هر دستور new یک دستور delete باید باشد.
نکته. بررسی کنید دستور new خطا یا NULL برنگردانده باشد.
نکته. برای آرایه ها [] در دستورات new و delete لازم است.
نکته. توابع ()malloc و ()free معادل دستورات new و delete هستند.


محاسبات روی اشاره گر

عملیات جمع، تفریق، افزایش و کاهش را می توان روی متغيرهای اشاره گر انجام داد. چون اشاره گر آدرسی در حافظه است وقتی محاسباتی روی آن انجام می گیرد رفتار متفاوتی نشان می دهد. وقتی عمل جمع عددی با متغير اشاره گر صورت می گيرد اشاره گر به اندازه حاصلضرب عدد در تعداد بایت های نوع داده ای که اشاره می کند جلو می رود. همين برای عمل تفريق هم صدق می کند. اگر مقداری از متغير اشاره گر کم شود در محاسبات تعداد بايت های نوع داده محسوب می شود.

عملگر افزایش (++) مقدار متغیر را یکی اضافه می کند در حالیکه متغير اشاره گر را به تعداد بایت های نوع داده آن حرکت می دهد. اگر یک اشاره گر به عدد float دارید چون نوع float چهار بایت دارد با افزایش اشاره گر 4 واحد به آن اضافه می شود. بنابراین به 4 بایت بعدی حافظه اشاره می کند و دیگر به همان 4 بایت قبلی اشاره نمی کند.


مثال. اگر int را چهاربايت درنظر بگيريم، اشاره گر p هشت بايت به جلو حرکت می کند.

int a;
int *p;
p=&a;
p=p+2;


عمل ديگری که روی اشاره گر ها انجام می شو تفاضل است. می توان مقدار دو اشاره گر را از هم کم کرد و فاصله بين آنها را بدست آورد.


مثال. اگر ptr1 و ptr2‌ هر دو اشاره گر باشند عبارت زير اختلاف فاصله آنها را می دهد.

ptr1 - ptr2


نکته. اگر اشاره گر به محل ناشناخته ای از حافظه حرکت کند و عملی روی آن انجام دهید ممکن است عملیات برنامه های دیگر کامپیوتر را با اشکال مواجه کنید.

مقايسه دو متغير اشاره گر با هم تنها زمانی معتبر است که هردو به يک نوع داده اشاره کنند.


مثال. عبارت زير زمانی درست است که اشاره گر ptr1 به آدرسی قبل از ptr2 اشاره کند.

ptr1 < ptr2


توجه کنيد که عمل‍يات ضرب و تقسیم روی یک اشاره گر انجام نمی گيرد و باعث بروز خطای کامپايلر می شود.


اشاره گر و آرايه

ارتباط خاصی بين اشاره گرها و آرايه ها در ++C وجود دارد. از اشاره گرمی توان برای پيمايش آرايه ها استفاده کرد. در حقيقت اسم يک آرايه بدون هيچ انديسی اشاره گری به اولين خانه آن است. اگر آرايه ای به نام []array تعريف کرده باشيد array به اولين خانه آرايه اشاره می کند. بنابراين می توان به صورت غير مستقيم توسط عملگر * به عناصر آن دسترسی پيدا کرد. يعنی *array اولين خانه آرايه است و *(array+1) خانه دوم و به همين ترتيب الی آخر.

*(array) == array[0]
*(array + 1) == array[1]
*(array + 2) == array[2]
...
*(array + n) == array[n]

به عملگر & برای بدست آوردن آدرس آرايه نياز نيست البته می توان توسط &array[0] هم آدرس اولين عنصر آرايه را بدست آورد يعنی array == &array[0].


مثال. برنامه زير عناصر آرايه A را توسط اشاره گر نمايش می دهد.

#define MAX 10
int A[MAX] = {100, 90, 80, 70, 60, 50, 40, 30, 20, 10};
for (int i=0 ; i<MAX ; i++)
   cout << *(A+i) << endl;

شکل زير ارتباط آرايه و آدرس های را نشان می دهد.

Pointer And Array

نکته. استفاده از اشاره گر برای آرايه روش سريع تری نسبت به نوشتن انديس آرايه است.
نکته. هنگام کار کردن با آرايه توسط اشاره گر کامپايلر شروع و پايان آرايه را چک نمی کند بنابراين خودتان بايد مواظب باشيد از محدوده عناصر آرايه عبور نکنيد.
نکته. بخاطر داشته باشيد اسم آرايه يک ثابت اشاره گری است و نمی تواند تغيير کند و در طی اجرای برنامه ثابت می ماند.


اشاره گر و رشته

رشته يک آرايه کاراکتری است که به کاراکتر null ختم می شود. مانند آرايه نام رشته اشاره گری به اولين کاراکتر آن است بنابراين برای کار با رشته ها يک اشاره گر به کاراکتر بکار می آيد.


مثال. متغير Msg1 اشاره گری به کاراکتر است که با يک ثابت رشته ای مقداردهی اوليه شده است. 1Msg به اولين کاراکتر اين رشته اشاره می کند.

char *Msg1 = "This is a message";


راه ديگر برای استفاده اشاره گر برای رشته ها اختصاص فضای پويا به متغير اشاره گر است.


مثال. Msg2 متغير اشاره گری است که در حافظه پويا ايجاد شده است.

char *Msg2;
Msg2 = new char[16];
if (Msg2 == NULL) {
   cerr << "Could not allocate sufficient space" << endl;
   exit(1);
   }
strcpy(Msg2, "A new message");
cout << Msg2 << endl;
delete [] Msg2;


اشاره گر به ساختمان

مشابه هر نوع داده ديگری می توان اشاره گری به ساختمان در برنامه اعلان کرد. اشاره گر به ساختمان معمولا برای ارسال ساختمان به تابع استفاده می شود. علاوه براين برای پياده سازی ساختمان داده مهم ليست پيوندی هم بکار می رود.

برای دسترسی به عناصر ساختمان از طريق اشاره گر باید از عملگر -> (indirect membership operator) استفاده شود.


مثال. استفاده از اشاره گر برای دسترسی به ساختمان

#include <iostream.h>
typedef struct account {
   float balance;
}
account *ptraccout;
int main() {
   ptraccount = new account;
   ptraccount->balance=2000;
   cout << ptraccount->balance;
   delete ptraccount;
   return 0;
}


راه ديگر برای دسترسی به اجزای ساختمان توسط اشاره گر استفاده از عملگر مرجع است. اشاره گر به همراه علامت * بايد درون پرانتز قرار گيرند زيرا عملگر (.) الويت بيشتری نسبت به (*) دارد.

(*ptraccount).balance = 2000;


برنامه پياده سازی ليست پيوندی


اشاره گر به اشاره گر

C++ اجازه می دهد که اشاره گری به اشاره گر دیگر داشته باشید. چون یک اشاره گر یک روش غیر مستقیم دسترسی به یک متغیر است به همين دليل اشاره گر به اشاره گر غیر مستقیم چندگانه (multiple indirection) ناميده می شود. برای تولید اشاره گر به اشاره گر یک ستاره برای هر لایه از ارجاع اضافه می شود. بندرت اتفاق می افتد که اشاره گر به اشاره گر را در برنامه ای استفاده شود.


مثال.

char x;
char *y;
char **z;
x='z';
y=&x;
z=&y;

Pointer To Pointer

اشاره گر به تابع

وقتی تابع کامپايل شده و در حافظه برای اجرا قرار می گيرد قسمتی از حافظه را اشغال می کند بنابراين دارای آدرس است که می توان آن را به اشاره گری اختصاص داد. اشاره گر به تابع راهی برای فراخوانی تابع به صورت غيرمستقيم است علاوه بر اين برای ارسال تابع به عنوان پارامتر به تابع دیگر هم بکار می آيد.

اعلان و استفاده اشاره گر به تابع درنظر اول کمی متفاوت است ولی از همان قاعده تبعيت می کند. برای اعلان اشاره گر به تابع نام تابع درون پرانتز قرار می گيرد و قبل از آن علامت * قرار می گيرد. مشابه زير:

datatype (*pointerToFunction) (pElement) = NULL ;

*pointerToFunxtion اشاره گر به تابع است که درون پرانتز بايد باشد. سمت چپ آن نوع برگشتی و سمت راست آن پارامترهای تابع قرار می گيرند. سپس با NULL مقداردهی می شود.


مثال. funcPtr اشاره گر به تابعی است که هيچ آرگومان و مقدار برگشتی ندارد.

void (*funcPtr)() = NULL ;


مشابه آرايه ها، برای بدست آوردن آدرس تابع نيازی به عملگر آدرس & نيست. فراخوانی تابع توسط اشاره گر مشابه فراخوانی عادی تابع است.


مثال. متغير p اشاره گری به تابع square است. تابع به دو طريق فراخوانی شده است که خروجی هر دو فراخوانی يکسان است.

#include <iostream.h>
double square(double x); // The function prototype.
double (*p)(double x) = NULL; // The pointer declaration.
main() {
   p = square; // Initialize p to point to square().
   Cout << square(6) << p(6));
   return(0);
}
double square(double x) {
return x * x;
}


اشاره گر وپارامتر مرجع

همانطور که قبلا در بخش تابع گفته شد آرگومان به دو صورت مقداری و مرجع می تواند به تابع ارسال شود. در حالت مرجع آدرس آرگومان به تابع داده مي شود اين کار بسادگي با اضافه کردن علامت & (عملگر ادرس) قبل از پارامتر در خط اعلان تابع انجام مي شود.


آرگومان های خط فرمان

هنگامی که يک برنامه از خط فرمان سيستم عامل فراخوانی می شود آرگومان هائی را می توان به تابع main ارسال کرد. پارامترهای تابع main به شکل زير هستند.

int main(int argc, char* argv[])
{
...
}

argv هميشه آرايه ای رشته ای است که شامل دستوری است که در خط فرمان وارد می شود. فضای خالی، اجزای فرمان را از هم جدا و تبديل به آرگومان های جداگانه در آرايه می کند. argc تعداد عناصر درون آرايه پارامتر دوم است. argv[0] شامل مسير و نام خود برنامه است.


مثال. برنامه زير کليه آرگومان های خط فرمان را نمايش می دهد.

//CommandLineArgs.cpp
#include <iostream.h>
int main(int argc, char* argv[]) {
cout << "argc = " << argc << endl;
for(int i = 0; i < argc; i++)
cout << "argv[" << i << "] = "
<< argv[i] << endl;
}


اسامی argv و argc برای آرگومان های خط فرمامن الزامی نيست و می توان از شناسه های ديگر استفاده کرد ولی اين دو اسم متعارف هستند و استفاده از اسامی ديگر باعث گيج شدن افراد ديگر می شود.


 


 


صفحه اصلی| PDF| درباره| تماس