تکین وب | آموزش برنامه نویسی
تکین وب | آموزش برنامه نویسی

اشاره گرها در ++C

۲۰ شهریور ۱۳۹۴

در این بخش می خواهیم یکی از قوی ترین و مهم ترین ویژگی های زبان برنامه نویسی ++C یعنی اشاره گرها( pointers ) را مورد بررسی قرار دهیم .

معرفی اشاره گرها

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

  ۴ -۲ بایت : int

 ۱ بایت : char

   ۴ بایت : float

 ۸ بایت : double

در اینصورت اگر متغیری که از نوع عدد صحیح یا int می باشد را در حافظه ذخیره کنیم ، ۴ خانه پشت سر هم از حافظه اشغال می شود که طبق تعریف اولین خانه را آدرس آن متغیر می نامند .

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

باید بگوییم اشاره گرها را می توان در  متغیری ذخیره کرد . درست است که اشاره گر یک آدرس حافظه است و آدرس حافظه هم چیزی جز یک عدد نیست ، اما نمی توان آن را در انواع متغیرهایی که تاکنون با آن ها آشنا شده ایم ، ذخیره کرد . برای اینکه یک اشاره گر را ذخیره کنیم باید آن را در متغیری از نوع اشاره گر ذخیره کرد که به این نوع متغیرها ، متغیرهای اشاره گرمی گوییم . برای تعریف یک اشاره گر از دستور زیر استفاده می کنیم :

   ; متغیر*  نوع

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

int *ptr;

به طور مثال در تکه کد بالا متغیر ptr را از نوع *int تعریف کرده ایم که باید به این صورت خوانده شود : ptr اشاره گری به int است .

عملگرهای اشاره گر

عملگر آدرس که آن را با علامت & (ampersand) نمایش می دهند ، عملگری است که آدرس حافظه عملوند خود را برمی گرداند :

int y = 5 , x ;
int *yPtr ;
yPtr = &y ;
x = *yPtr 

همانطور که در تکه کد بالا می بینید ، متغیر y از نوع عدد صحیح و yPtr اشاره گری به  int تعریف شده اند . خط سوم بیان می کند که آدرس متغیر y به اشاره گر yPtr اختصاص یابد . پس متغیر yPtr به y اشاره می کند . اکنون متغیر yPtr به صورت غیرمستقیم به مقدار متغیر y دسترسی دارد . تحلیل این کد که نمایش حافظه پس از تخصیص فوق می باشد را در شکل زیر مشاهده می کنید :

ptr-a

رابطه ی اشاره گر توسط یک فلش یا یک پیکان از جعبه ای که اشاره گر yPtr در حافظه  به جعبه ای که متغیر y در حافظه است ، نشان داده شده است .

شکل دیگری از نمایش اشاره گر در حافظه به صورت شکل پایین می باشد . به این صورت که  متغیر y در مکان ۶۰۰۰۰۰ حافظه و اشاره گر yPtr در مکان ۵۰۰۰۰۰ حافظه ذخیره شده است .

location-a

 اما در خط ۴ ام محتوایات جایی که yPtr  به آن اشاره می کند ، در متغیر x قرار می گیرد .

به این نکته توجه کنید که یک وقتی یک اشاره گر از نوع int تعریف می شود باید به متغیرهایی از همان نوع اشاره کند  یعنی باید به متغیری که از نوع int تعریف شده است ، اشاره نماید . اگر نوع متغیری که آدرس آن در یک اشاره گر قرار می گیرد، با نوع اشاره گر یکسان نباشد ، یک خطا محسوب می شود .

همانطور که برای متغیرهای دیگر دستور cout و cin وجود دارد ، می توان بر روی اشاره گرها نیز این اعمال را انجام داد :

cout<< *yPtr <<endl;

این دستور مقدار متغیر y یعنی ۵ را همانند دستور زیر چاپ می کند :

cout<< y << endl ;

با استفاده از عملگر * به این روش ، مراجعه غیرمستقیم اشاره گر می گوییم . همچنین می توان از این عملگر برای بازیابی عبارت ورودی استفاده کرد :

cin>> *yPtr ;

دستور بالا مقدار ورودی را در متغیر y قرار می دهد . دستور پایین هم مقدار عددی ۹ را در متغیر y ذخیره می کند :

*yPtr = 9 ;

توجه شما را به کد زیر جلب می کنیم :

#include<iostream>
#include<stdlib.h>
using namespace std;
int main()
{
int a ;
int *aPtr;
a = 7 ;
aPtr = &a;
cout<<"The address of a is : "<<&a
<< "\nThe value of aPtr is " <<aPtr ;
cout << "\n\nThe value of a is " << a
<< "\nThe value of *aPtr is " << *aPtr;
cout << "\n\nShowing that * and & are inverses of "
<< "each other.\n&*aPtr = " <<&*aPtr
<< "\n*&aPtr = " <<*&aPtr<<endl;
system("pause");
}//end of main

خروجی این کد به صورت زیر می باشد :

 

 aptr

در کد بالا بعد از اینکه متغیر a و اشاره گر aPtr از نوع int تعریف شدند ، اشاره گر aPtr به  متغیر a اشاره می کند . آدرس متغیر a همانطور که در خروجی ملاحظه می کنید ، یک عبارت شامل عدد و حروف می باشد . همجنین طبق دستور aPtr = &a آدرس جایی که اشاره گر aPtr به آن اشاره می کند ، درست همان عبارت می باشد .

چون مقدار اولیه متغیر a عدد ۷ می باشد بنابراین طبق دستور cout<<*aPtr ، محتوای جایی که اشاره گر aPtr به آن اشاره می کند در خروجی جاپ خواهد شد .(یعنی مقدار ۷ ) . اما دو خط آخر خروجی نشان می دهد که * و & وارون یکدیگرند . به این صورت که طبق دستور aPtr*& ، آدرس محتوای جایی که aPtr به ان اشاره می کند درست همان محتوای آدرس جایی است که aPtr به آن اشاره می کند .

عملیات بر روی اشاره گر ها

به طور کلی می توان سه عمل را بر روی اشاره گر ها انجام داد :

  • انتساب اشاره گر ها به یکدیگر  :
int *Ptr1 , *Ptr2;
int x =50 , y= 100 ;
Ptr1 = &x ;
Ptr2 = &y ;
*Ptr1 = *Ptr2 ;

در تکه کد بالا در خط ۵ ام دستور Ptr1 = *Ptr2* باعث می شود که محتویات جایی که Ptr2 به آن اشاره می کند ، (Ptr2  به متغیر x که دارای مقدار اولیه ۵۰ می باشد اشاره می کند ) ، در جایی قرار می گیرد که اشاره گر Ptr1 به آن اشاره می کند .حال به دستور زیر توجه کنید :

Ptr1 = Ptr2 ;

این دستور باعث می شود Ptr1 به جایی اشاره کند که Ptr2 به آن اشاره می کند .

  • اعمال محاسباتی بر روی اشاره گر ها :

اعمالی مانند جمع و تفریق را می توان طبق دستور زیر انجام داد :

int *Ptr1 , *Ptr2 , sum , multi;
cin>> *Ptr1>>Ptr2 ;
sum = *Ptr1 + *Ptr2 ;
multi = *Ptr1 * *Ptr2 ;
cout<<sum <<" "<<multi;

علاوه بر این می توان با توجه به نوع اشاره گر ، عمل های دیگری مانند ++ و — را بر روی آن ها انجام داد . به طور مثال فرض کنید اشاره گر از نوع char باشد . در اینصورت با ++ کردن آن چون نوع char ، یک بایت از حافظه را اشغال می کند ، بنابراین در مرحله بعدی اشاره گر به خانه ی بعدی جایی که هم اکنون به آن اشاره می کند ، اشاره خواهد کرد یا اگر اشاره گری از نوع int تعریف شده باشد (با فرض اینکه عددصحیح ۲ بایت از حافظه را اشغال کند ) در اینصورت با ++ کردن آن ، به دو خانه ی بعدی اشاره خواهد کرد .

  • مقایسه اشاره گر ها :

در تکه کد زیر عمل مقایسه اشاره گرها را ملاحظه می کنید . با فرض اینکه  Ptr1 به محل ۱۰۰۰۰ حافظه اشاره کند ، چون Ptr2 به جای دیگری اشاره می کند ، بنابراین شرط if دارای ارزش نادرستی است :

int *Ptr1 , *Ptr2 , x , y ;
Ptr1 = &x ;
Ptr2 = &y ;
if(Ptr1 == Ptr2)
....
else
....

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

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

int cubeByValue( int ); // prototype
int main()
{
int number = 5;
cout << "The original value of number is " << number;
number = cubeByValue( number ); // pass number by value to cubeByValue
cout << "\nThe new value of number is " << number << endl;
} // end main
/*****************************************/
// calculate and return cube of integer argument
int cubeByValue( int n )
{
return n * n * n; // cube local variable n and return result
} // end function cubeByValue

این برنامه مکعب یک عدد را با روش اول یعنی فراخوانی با مقدار محاسبه می کند .

void cubeByReference( int * ); // prototype
int main()
{
int number = 5;
cout << "The original value of number is " << number;
cubeByReference( &number ); // pass number address to cubeByReference
cout << "\nThe new value of number is " << number << endl;
} // end main
/*****************************************/
// calculate cube of *nPtr; modifies variable number in main
void cubeByReference( int *nPtr )
{
*nPtr = *nPtr * *nPtr * *nPtr; // cube *nPtr
} // end function cubeByReference

اما در این برنامه از روش دوم یعنی فراخوانی با ارجاع ، برای محاسبه مکعب یک عدد استفاده شده است . در این روش الگوی تابع  cubeByReference ، در بالای main قرار داده شده است . پارامتر ورودی این تابع از نوع اشاره گر می باشد . در داخل main  بعد از تعریف متغیر number  به عنوان یک int و دستور cout برای مشاهده ی مقدار اصلی این متغیر ، تابع  cubeByReference فراخوانی می شود . اما توجه کنید که برخلاف برنامه ی قبل کاری با مقدار متغیر نداریم و آنچه مهم است آدرس متغیر می باشد . در این صورت با فرستادن آدرس متغیر number به عنوان ورودی ، کامپایلر به درون تابعی که خارج از main تعریف کرده ایم ، می رود . در این تابع که خروجی آن void  می باشد ، اشاره گر nPtr که همان ورودی تابع است ، به آدرس متغیر number اشاره می کند . سپس مقدار مکعب این عدد ، با عمل ضرب میان اشاره گرها به عنوان خروجی قرار خواهد گرفت . می توانید در شکل زیر قدم به قدم اجرای این برنامه را ملاحظه نمایید :

a-newcub

 آرایه ها و اشاره گرها

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

int a[5];
int *aPtr ;
aPtr = array ;
aPtr = &array[0];
*(aPtr + 3);

در خط اول آرایه ی a دارای ۵ عنصر و در خط دوم اشاره گر aPtr تعریف شده اند . همانطور که گفته شد نام یک آرایه ، یک اشاره گر می باشد . پس می توان گفت اگر اولین خانه ی آرایه در محل ۱۰۰۰ حافظه وجود داشته باشد ، a به محل ۱۰۰۰ حافظه اشاره خواهد کرد .

poiter-a

خط سوم نشان می دهد که aPtr و a هر دو به ابتدای آرایه اشاره می کنند . خط چهارم  هم  نمایش دیگری از آنچه درمورد خط سوم گفته شد ، می باشد . خط بعدی هم معادل عنصر ۴ام آرایه می باشد .

آرایه های پویای یک بعدی

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

برای تخصیص حافظه ی پویا از عملگر new و برای بازگراندن آن به سیستم از عملگر delete استفاده می کنیم :

int *p;
p = new int[10] ;

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

delete []p;

در دستور بالا هم حافظه ای که اختصاص داده شده بود توسط عملگر delete ، حذف می شود و به سیستم برگردانده می شود . شما می توانید با استفاده از دستور cin تعداد خانه های حافظه را تعیین نمایید :

int *p , n ;
cin>>n ;
p = new int[n];

آرایه های پویای دوبعدی

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

برای تعریف آرایه پویای دوبعدی به صورت زیر عمل می کنیم :

int **p , n;
cin>>n;
p = new int*[n];

خط اول دستور p** ، اشاره گر به اشاره گر است . دستور خط سوم هم بیان کننده ی یک آرایه‌ی پویای یک بعدی به طول n از اشاره‌گرهای صحیح می باشد . بنابراین عناصر [۰] p [۲] ،  p[1] ،p و… هر کدام یک اشاره گر از نوع int می باشند .

برای آزاد کردن حافظه در آرایه های دوبعدی از دستور زیر استفاده می کنیم :

for (int i = 0; i < n; i++){
delete[]p[i];
}
delete[]p;

این برنامه نحوه ی عملکرد یک آرایه ی دو بعدی را نشان می دهد :

#include"iostream"
#include<stdlib.h>
using namespace std;
void mat(int , int);
int main()
{
 int x , y;
 cout << "enter the row and column for making matrix :\n";
 cin >> x >> y;
 mat(x , y);
 system("pause");
 return 0;
}//end of main
//define function
void mat(int x , int y)
{
 int **p;
 p = new int*[x];
 for(int i =0 ; i<x ;i++)
 p[i]= new int[y];
 cout<<"enter elements : "<<endl;
 for(int i =0 ; i<x ;i++ )
 for(int j = 0 ;j<y ; j++)
 {
 cout<<"p["<<i<<"]["<<j<<"] = ";
 cin>>p[i][j];
 cout<<endl;
 }
 for(int i =0 ;i <x ; i++)//for releasing the memory you should write this code
 delete []p[i];
 delete[]p;
 cout<<"memory is released"<<endl;

}//end of function

 

مبحث اشاره گرها از جمله مهمترین مباحث  موجود در زبان برنامه نویسی ++c می باشد . بنابراین با تمرکز و دقت بسیار به نحوه ی عملکرد آن بپردازید .

در ضمن اگر اینستاگرامی هستید حتما @Takinweb را فالو کنید….

برچسب ها

درباره ی عطیه رحمانی

دیدگاه ها

2 دیدگاه ارسال شده !

  1. ghost می‌گه:

    سلام
    میشه برای این کدی ک نوشتید یک تابع پرینت هم بنویسید
    چجوری باید بفرستیمش چیزی رو که خوندیم ب تابع پرینت ؟

    • عطیه رحمانی می‌گه:

      سلام
      ممنونم از توجهتون
      اگه منظورتون کد آخری هستش که نوشتیم ،تابع پرینت برای پرینت کردن چی؟چی رو میخواین بخونه دقیقا؟ رشته ، عدد … چی؟

دیدگاه خود را به ما بگویید.

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