ایران سرفراز- نرم افزار وپروژهای دانشجویی


نرم افزار وپروژهای دانشجویی

فصل هشتم استاندارد رشته های کاراکتری و فایل ها در » در++ C

<!-- /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:"Times New Roman"; mso-fareast-font-family:"Times New Roman";} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:35.4pt; mso-footer-margin:35.4pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->

mohsen_mahyar@yahoo.com

فصل هفتم

      C++ در « اشاره گرها 1 و ارجاع ها 2 »

7 مقدمه ‐1

حافظۀ رایانه را می توان به صورت یک آرایۀ بزرگ در

در RAM نظر گرفت. برای مثال رایانه ای با 256 مگابایت

حقیقت حاوی آرایه ای به اندازۀ 268،435،456

228 ) خانه است که اندازۀ هر خانه یک بایت است. این =)

خانه ها دارای ایندکس صفر تا 268،435،455 هستند.

به ایندکس هر بایت، آدرس حافظۀ آن می گویند. آدرس های

حافظه را با اعداد شانزده دهی نشان می دهند. پس رایانۀ مذکور

0 می باشد. هر وقت که x0fffffff 0 تا x دارای محدوده آدرس 00000000

نوع » : متغیری را اعلان می کنیم، سه ویژگی اساسی به آن متغیر نسبت داده می شود

و n و نام int نوع int n; آن. مثلا اعلان « آدرس حافظه » و « نام متغیر » و « متغیر

در آن قرار می گیرد را به یکدیگر مرتبط n آدرس چند خانه از حافظه که مقدار

را n 0 است. بنابراین می توانیم x0050cdc می سازد. فرض کنید آدرس این متغیر 0

0x00000000

0x00000001

0x00000002

0x00000003

0x00000004

0x00000005

0x00000006

0x00000007

0x0064fdc5

0x0064fdc6

0x0064fdc7

0x0064fdc8

0x0ffffffe

 

0x0fffffff

1 – Pointers 2 – References

فصل هفتم / اشاره گرها و ارجا ع ها 215

مانند شکل زیر مجسم کنیم:

خود متغیر به شکل جعبه نمایش داده شده. نام متغیر،

در بالای جعبه است و آدرس متغیر در سمت چپ جعبه و ،n

در زیر جعبه نشان داده شده. در بیشتر ،int ، نوع متغیر

چهار بایت از حافظه را اشغال می نماید. int رایانه ها نوع

بنابراین همان طور که در شکل مقابل نشان داده شده است،

یک بلوک چهاربایتی از حافظه را اشغال می کند که n متغیر

0 است. x0050cdc 0 تا 3 x0050cdc شامل بایت های 0

توجه کنید که آدرس شی، آدرس اولین بایت از بلوکی است که شی در آن جا ذخیره

مقداردهی اولیه شود، آنگاه بلوک حافظه int n= شده. اگر متغیر فوق به شکل ; 32

به شکل زیر خواهد بود. مقدار

32 در چهار بایتی که برای آن

متغیر منظور شده ذخیره می شود.

در این فصل به بررسی و نحوۀ استفاده از آدر سها خواهیم پرداخت.

7 عملگر ارجاع ‐1

& برای بدست آوردن آدرس یک متغیر می توان از عملگر ارجاع 1 C++ در

را n آدرس متغیر &n نیز می گویند. عبارت « علمگر آدرس » استفاده نمود. به این عملگر

به دست می دهد.

7 چاپ آدرس یک متغیر ‐ * مثال 1

int main()

{ int n=44;

cout << " n = " << n << endl; // prints the value of n

cout << "&n = " << &n << endl; // prints the address of n

}

n = 44

&n = 0x00c9fdc3

0x0050cdc0

n

int

0x0050cdb8

0x0050cdb9

0x0050cdc0

0x0050cdc1

0x0050cdc2

0x0050cdc3

0x0050cdc4

0x0050cdc5

0x0050cdb8

0x0050cdb9

0x0050cdc0

0x0050cdc1

0x0050cdc2

0x0050cdc3

0x0050cdc4

0x0050cdc5

32 0x0050cdc0 32

n

int

1 – Reference operator

 

216 برنامه سازی پیشرفته

0 است. می توان x00c9fdc در این اجرا برابر با 3 n خروجی نشان می دهد که آدرس

فهمید که این مقدار باید یک آدرس باشد زیرا به شکل شانزده دهی نمایش داده شده.

0 می توان تشخیص داد. معادل دهدهی عدد بالا x اعداد شانزد هدهی را از روی علامت

مقدار 13,237,699 می باشد.

نمایش دادن آدرس یک متغیر به این شیوه خیلی مفید نیست. عملگر ارجاع &

استفاده های مهم تری دارد. یک کاربرد آن را در فصل پنجم گفته ایم: ساختن پارامترهای

ارجاع در اعلان تابع. اکنون کاربرد دیگری معرفی می کنیم که خیلی به کاربرد قبلی شبیه

است؛ اعلان متغیرهای ارجاع.

7 ارجاع ها ‐2

یک اسم مستعار یا واژۀ مترادف برای متغیر دیگر است. نحو اعلان « ارجاع » یک

یک ارجاع به شکل زیر است:

type& ref_name = var_name;

نام متغیری var_name نام مستعار است و ref_name ، نوع متغیر است type

است که می خواهیم برای آن نام مستعار بسازیم. برای مثال در اعلان :

int& rn=n; // r is a synonym for n

باید قبلا اعلان شده باشد. n است. البته n یک ارجاع یا نام مستعار برای rn

7 استفاده از ارجاع ها ‐ * مثال 2

اعلان م یشود: n به عنوان یک ارجاع به rn در برنامۀ زیر

int main()

{ int n=44;

int& rn=n; // rn is a synonym for n

cout << "n = " << n << ", rn = " << rn << endl;

--n;

cout << "n = " << n << ", rn = " << rn << endl;

rn *= 2;

cout << "n = " << n << ", rn = " << rn << endl;

}

فصل هفتم / اشاره گرها و ارجا ع ها 217

n = 44, rn = 44

n = 43, rn = 43

n = 86, rn = 86

نام های متفاوتی برای یک متغیر است. این دو همیشه مقدار یکسانی دارند. اگر rn و n

نیز افزایش یافته است. n ، افزایش یابد rn نیز کاسته شده و اگر rn ، کاسته شود n

همانند ثابت ها، ارجاع ها باید هنگام اعلان مقداردهی اولیه شوند با این تفاوت که

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

int& rn=44; // ERROR: 44 is not a variable;

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

به حافظۀ آن متغیر، ارجاع rn هشدار اعلام می کنند که یک متغیر موقتی ایجاد شده تا

داشته باشد.

درست است که ارجاع با یک متغیر مقداردهی می شود، اما ارجاع به خودی خود

یک متغیر نیست. یک متغیر، فضای ذخیره سازی و نشانی مستقل دارد، حال آن که

ارجاع از فضای ذخیره سازی و نشانی متغیر دیگری بهره می برد.

7 ارجاع ها متغیرهای مستقل نیستند ‐ * مثال 3

int main()

{ int n=44;

int& rn=n; // rn is a synonym for n

cout << " &n = " << &n << ", &rn = " << &rn << endl;

int& rn2=n; // rn2 is another synonym for n

int& rn3=rn; // rn3 is another synonym for n

cout << "&rn2 = " << &rn2 << ", &rn3 = " << &rn3 << endl;

}

& n = 0x0064fde4, &rn = 0x0064fde4

&rn2 = 0x0064fde4, &rn3 = 0x0064fde4

rn و 3 rn و 2 rn . است n در برنامۀ فوق فقط یک شی وجود دارد و آن هم

با rn و 3 rn و 2 rn هستند. خروجی نیز تایید می کند که آدرس n ارجاع هایی به

یکی است. یک شی می تواند چند ارجاع داشته باشد. n آدرس

 

 

 

 

 

 

218 برنامه سازی پیشرفته

ارجاع ها بیشتر برای ساختن پارامترهای ارجاع در توابع به کار می روند. تابع

می تواند مقدار یک آرگومان را که به طریق ارجاع ارسال شده تغییر دهد زیرا آرگومان

اصلی و پارامتر ارجاع هر دو یک شی هستند. تنها فرق این است که دامنۀ پارامتر

ارجاع به همان تابع محدود شده است.

7 اشاره گرها ‐3

نگهداری کنیم و اعداد int می دانیم که اعداد صحیح را باید در متغیری از نوع

به همین ترتیب کاراکترها را باید در .float اعشاری را در متغیرهایی از نوع

.bool نگهداریم و مقدارهای منطقی را در متغیرهایی از نوع char متغیرهایی از نوع

اما آدرس حافظه را در چه نوع متغیری باید قرار دهیم؟

عملگر ارجاع & آدرس حافظۀ یک متغیر موجود را به دست می دهد. می توان این

آدرس را در متغیر دیگری ذخیره نمود. متغیری که یک آدرس در آن ذخیره می شود

اشاره گر نامیده می شود. برای این که یک اشاره گر اعلان کنیم، ابتدا باید مشخص کنیم

که آدرس چه نوع داده ای قرار است در آن ذخیره شود. سپس از عملگر اشاره *

استفاده می کنیم تا اشار هگر را اعلان کنیم. برای مثال دستور :

float* px;

float اعلان می کند که این اشاره گر، آدرس متغیرهایی از نوع px اشاره گری به نام

را نگهداری می نماید. به طور کلی برای اعلان یک اشار هگر از نحو زیر استفاده می کنیم:

type* pointername;

نوع متغیرهایی است که این اشاره گر آدرس آن ها را نگهداری می کند و type که

نام اشاره گر است. pointername

ذخیره int* را فقط می توان در اشاره گری از نوع int آدرس یک شی از نوع

float* را فقط می توان در اشاره گری از نوع float کرد و آدرس یک شی از نوع

ذخیره نمود. دقت کنید که یک اشار هگر، یک متغیر مستقل است.

فصل هفتم / اشاره گرها و ارجا ع ها 219

7 به کارگیری اشاره گرها ‐ * مثال 4

به نام int* و یک اشاره گر از نوع n به نام int برنامۀ زیر یک متغیر از نوع

را اعلان می کند: pn

int main()

{ int n=44;

cout << "n = " << n << ", &n = " << &n << endl;

int* pn=&n; // pn holds the address of n

cout << " pn = " << pn << endl;

cout << "&pn = " << &pn << endl;

}

n = 44, &n = 0x0064fddc

pn = 0x0064fddc

&pn = 0x0064fde0

با مقدار 44 مقداردهی شده و آدرس آن n متغیر

یعنی آدرس &n با مقدار pn 0 می باشد. اشاره گر x0064fddc

برابر با pn مقداردهی شده. پس مقدار درون n

0 است (خط دوم خروجی این موضوع را تایید x0064fddc

را به pn آدرس &pn . یک متغیر مستقل است و آدرس مستقلی دارد pn می کند) . اما

است. n مستقل از متغیر pn دست می دهد. خط سوم خروجی ثابت می کند که متغیر

و n تصویر زیر به درک بهتر این موضوع کمک می کند. در این تصویر ویژگی های مهم

n است و n یک اشار هگر به pn . نشان داده شده pn

مقدار 44 دارد.

یعنی « اشاره می کند n به pn» وقتی می گوییم

قرار دارد. n آدرس pn درون

7 مقداریابی ‐4

باشد. با این حساب n اشار هگری به pn دارای مقدار 22 باشد و n فرض کنید

به مقدار 22 رسید. با استفاده از * می توان مقداری که اشاره گر pn باید بتوان از طریق

به آن اشاره دارد را به دست آورد. به این کار مقداریابی اشاره گر می گوییم.

n 44

int

0x0064fddc

pn 0x0064fddc

int*

0x0064fde0

n 44

int

int*

pn

 

220 برنامه سازی پیشرفته

7 مقداریابی یک اشاره گر ‐ * مثال 5

7 است. فقط یک خط کد بیشتر دارد: ‐ این برنامه همان برنامۀ مثال 4

int main()

{ int n=44;

cout << "n = " << n << ", &n = " << &n << endl;

int* pn=&n; // pn holds the address of n

cout << " pn = " << pn << endl;

cout << "&pn = " << &pn << endl;

cout << "*pn = " << *pn << endl;

}

n = 44, &n = 0x0064fdcc

pn = 0x0064fdcc

&pn = 0x0064fdd0

*pn = 44

است زیرا هر دو یک مقدار دارند. n یک اسم مستعار برای *pn ظاهرا

یک اشاره گر به هر چیزی می تواند اشاره کند، حتی به یک اشاره گر دیگر. به مثال

زیر دقت کنید.

7 اشاره گری به اشاره گرها ‐ * مثال 6

7 است: ‐ این کد ادامۀ ساختار برنامۀ مثال 4

int main()

{ int n=44;

cout << " n = " << n << endl;

cout << " &n = " << &n << endl;

int* pn=&n; // pn holds the address of n

cout << " pn = " << pn << endl;

cout << " &pn = " << &pn << endl;

cout << " *pn = " << *pn << endl;

int** ppn=&pn; // ppn holds the address of pn

cout << " ppn = " << ppn << endl;

cout << " &ppn = " << &ppn << endl;

cout << " *ppn = " << *ppn << endl;

cout << "**ppn = " << **ppn << endl;

 

 

فصل هفتم / اشاره گرها و ارجا ع ها 221

}

n = 44

&n = 0x0064fd78

pn = 0x0064fd78

&pn = 0x0064fd7c

*pn = 44

ppn = 0x0064fd7c

&ppn = 0x0064fd80

*ppn = 0x0064fd78

**ppn = 44

اشاره n اشاره گری است که به pn . تعریف شده int از نوع n در برنامۀ بالا متغیر

اشاره می کند. pn اشاره گری است که به ppn . باشد int* باید pn دارد. پس نوع

*ppn اشاره دارد، پس pn به ppn باشد. همچنین چون int** باید ppn پس نوع

را می دهد. n مقدار *pn اشاره دارد، پس n به pn را نشان می دهد و چون pn مقدار

را بر n مقدار **ppn اگر این دو دستور را کنار هم بچینیم نتیجه این می شود که

می گرداند. خروجی حاصل از اجرای برنامۀ بالا این گفته ها را تصدیق می نماید.

خواهد T* باشد، آنگاه اشاره گر به آن از نوع T به طور کلی اگر متغیری از نوع

می گویند زیرا از روی نوع دیگری ساخته شده است. « مشتق شده » یک نوع T* بود. به

داشته باشیم T* خود یک نوع جدید است. حال اگر بخواهیم اشاره گری به نوع T*

(یعنی اشاره گری به اشار هگر دیگر) طبق قاعدۀ فوق این اشاره گر جدید را باید از نوع

تعریف نمود. نمودار فوق نحوۀ کار اشار هگرها در مثال بالا را نشان می دهد. T**

از نوع pn . هر دو اشار هگر هستند اما از یک نوع نیستند ppn و pn گرچه

است. int** از نوع ppn است و int*

عملگر مقداریابی * و عملگر ارجاع & معکوس یکدیگر رفتار می کنند. اگر این

آدرس &n ، یک متغیر باشد n دو را با هم ترکیب کنیم، یکدیگر را خنثی می نمایند. اگر

قرار &n آن متغیر است. از طرفی با استفاده از عملگر * می توان مقداری که در آدرس

یک p خواهد بود. همچنین اگر n برابر با خود *&n گرفته را به دست آورد. بنابراین

به آن اشاره دارد را می دهد. از طرفی با استفاده از p مقداری که *p ، اشاره گر باشد

برابر &*p قرار گرفته را بدست آوریم. پس *p عملگر & می توانیم آدرس چیزی که در

n 44

int

int*

pn

int**

ppn

 

222 برنامه سازی پیشرفته

&*n با *&n خواهد بود. ترتیب قرارگرفتن این عملگرها مهم است. یعنی p با خود

برابر نیست. علت این امر را توضیح دهید.

یک اشاره گر (int* عملگر * دو کاربرد دارد. اگر پسوندِ یک نوع باشد (مثل

آنگاه مقداری (*p به آن نوع را تعریف می کند و اگر پیشوندِ یک اشاره گر باشد (مثل

به آن اشاره می کند را برمی گرداند. عملگر & نیز دو کاربرد دارد. اگر پسوند یک p که

یک نام مستعار تعریف می کند و اگر پیشوند یک متغیر باشد (int& نوع باشد (مثل

آدرس آن متغیر را می دهد. (&n (مثل

7 چپ مقدارها، راست مقدارها ‐6

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

جایگزینی قرار می گیرد و بخشی که در سمت راست علامت جایگزینی قرار می گیرد.

در سمت چپ قرار گرفته و مقدار 55 در سمت n متغیر n = مثلا دستور ; 55

55 نوشت زیرا مقدار 55 یک ثابت = n; راست. این دستور را نمی توان به شکل

است و نمی تواند مقدار بگیرد. پس هنگام استفاده از عملگر جایگزینی باید دقت کنیم

که چه چیزی را در سمت چپ قرار بدهیم و چه چیزی را در سمت راست.

« چپ مقدار 1 » چیزهایی که می توانند در سمت چپ جایگزینی قرار بگیرند

خوانده می شوند و چیزهایی که می توانند در سمت راست جایگزینی قرار بگیرند

نامیده می شوند. متغیرها (و به طور کلی اشیا) چپ مقدار هستند و « راست مقدار 2 »

راست مقدار هستند. ("ABC" لیترال ها (مثل 15 و

یک ثابت در ابتدا به شکل یک چپ مقدار نمایان می شود:

const int MAX = 65535; // MAX is an lvalue

اما از آن پس دیگر نمی توان به عنوان چپ مقدار از آن ها استفاده کرد:

MAX = 21024; // ERROR: MAX is constant

گفته می شود. مثل آرایه ها: « تغییر ناپذیر » به این گونه چپ مقدارها، چپ مقدارهای

int a[] = {1,2,3}; // O.K

a[] = {1,2,3}; // ERROR

1 – L_values 2- R_values

فصل هفتم / اشاره گرها و ارجا ع ها 223

« تغییر پذیر » مابقی چپ مقدارها که می توان آن ها را تغییر داد، چ پمقدارهای

نامیده می شوند. هنگام اعلان یک ارجاع به یک چپ مقدار نیاز داریم:

int& r = n; // O.K. n is an lvalue

اما اعلان های زیر غیرمعتبرند زیرا هیچ کدام چپ مقدار نیستند:

int& r = 44; // ERROR: 44 is not an lvalue

int& r = n++; // ERROR: n++ is not an lvalue

int& r = cube(n); // ERROR: cube(n) is not an lvalue

یک تابع، چ پمقدار نیست اما اگر نوع بازگشتی آن یک ارجاع باشد، می توان

تابع را به یک چپ مقدار تبدیل کرد.

7 بازگشت از نوع ارجاع ‐7

در بحث توابع، ارسال از طریق مقدار و ارسال از طریق ارجاع را دیدیم. این دو

شیوۀ تبادل در مورد بازگشت از تابع نیز صدق می کند: بازگشت از طریق مقدار و

بازگشت از طریق ارجاع. توابعی که تاکنون دیدیم بازگشت به طریق مقدار داشتند.

یعنی همیشه یک مقدار به فراخواننده برمی گشت. می توانیم تابع را طوری تعریف کنیم

را m که به جای مقدار، یک ارجاع را بازگشت دهد. مثلا به جای این که مقدار

را بازگشت دهد. m بازگشت دهد، یک ارجاع به

وقتی بازگشت به طریق مقدار باشد، تابع یک راست مقدار خواهد بود زیرا

مقدارها لیترال هستند و لیترال ها راست مقدارند. به این ترتیب تابع را فقط در سمت

m = f(); : راست یک جایگزینی می توان به کار برد مثل

وقتی بازگشت به طریق ارجاع باشد، تابع یک چ پمقدار خواهد بود زیرا

ارجاع ها چپ مقدار هستند. در این حالت تابع را می توان در سمت چپ یک جایگزینی

f() = m; : قرار داد مثل

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

را به عنوان پسوند نوع بازگشتی درج کنیم.

 

 

224 برنامه سازی پیشرفته

7 بازگشت از نوع ارجاع ‐ * مثال 8

int& max(int& m, int& n) // return type is reference to int

{ return (m > n ? m : n); // m and n are non-local references

}

int main()

{ int m = 44, n = 22;

cout << m << ", " << n << ", " << max(m,n) << endl;

max(m,n) = 55; // changes the vale of m from 44 to 55

cout << m << ", " << n << ", " << max(m,n) << endl;

}

44, 22, 44

55, 22, 55

مقدار بزرگ تر را پیدا کرده و سپس ارجاعی به آن را باز n و m از بین max() تابع

را m آدرس max(m,n) بزرگ تر باشد، تابع n از m می گرداند. بنابراین اگر

مقدار 55 در حقیقت max(m,n) = برمی گرداند. پس وقتی می نویسیم ; 55

max(m,n) باشد). به بیانی ساده، فراخوانی m>n قرار می گیرد (اگر m درون متغیر

را بر می گرداند نه مقدار آن را. m خود

اخطار: وقتی یک تابع پایان می یابد، متغیرهای محلی آن نابود می شوند. پس هیچ

وقت ارجاعی به یک متغیر محلی بازگشت ندهید زیرا وقتی کار تابع تمام شد، آدرس

متغیرهای محلی اش غیر معتبر می شود و ارجاع بازگشت داده شده ممکن است به یک

را بر n یا m در مثال بالا یک ارجاع به max() مقدار غیر معتبر اشاره داشته باشد. تابع

خودشان به طریق ارجاع ارسال شد هاند، پس محلی نیستند و n و m می گرداند. چون

بازگرداندن ارجاعی به آ نها خللی در برنامه وارد نمی کند.

دقت کنید: max() به اعلان تابع

int& max(int& m, int& n)

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

7 به کارگیری یک تابع به عنوان عملگر زیرنویس آرایه ‐ * مثال 9

float& component(float* v, int k)

 

فصل هفتم / اشاره گرها و ارجا ع ها 225

{ return v[k-1];

}

int main()

{ float v[4];

for (int k = 1; k <= 4; k++)

component(v,k) = 1.0/k;

for (int i = 0; i < 4; i++)

cout << "v[" << i << "] = " << v[i] << endl;

}

v[0] = 1

v[1] = 0.5

v[2] = 0.333333

v[3] = 0.25

به « شماره گذاری از صفر » از v باعث می شود که ایندکس آرایه component() تابع

است. v[ معادل [ 2 component(v, تغییر کند. بنابراین ( 3 « شماره گذاری از یک »

این کار از طریق بازگشت از طریق ارجاع ممکن شده است.

7 آرایه ها و اشاره گرها ‐8

گرچه اشاره گرها از انواع عددی صحیح نیستند اما بعضی از اعمال حسابی را

می توان روی اشاره گرها انجام داد. حاصل این می شود که اشاره گر به خانۀ دیگری از

حافظه اشاره می کند. اشاره گرها را می توان مثل اعداد صحیح افزایش و یا کاهش داد و

می توان یک عدد صحیح را به آن ها اضافه نمود یا از آن کم کرد. البته میزان افزایش یا

کاهش اشاره گر بستگی به نوع داده ای دارد که اشاره گر به آن اشاره دارد. این موضوع را

با استفاده از مثال زیر بررسی می کنیم.

7 پیمایش آرایه با استفاده از اشاره گر ‐ * مثال 10

این مثال نشان می دهد که چگونه می توان از اشار هگر برای پیمایش یک آرایه

استفاده نمود:

int main()

{ const int SIZE = 3;

 

226 برنامه سازی پیشرفته

short a[SIZE] = {22, 33, 44};

cout << "a = " << a << endl;

cout << "sizeof(short) = " << sizeof(short) << endl;

short* end = a + SIZE; // converts SIZE to offset 6

short sum = 0;

for (short* p = a; p < end; p++)

{ sum += *p;

cout << "\t p = " << p;

cout << "\t *p = " << *p;

cout << "\t sum = " << sum << endl;

}

cout << "end = " << end << endl;

}

a = 0x3fffd1a

sizeof(short) = 2

p = 0x3fffd1a *p = 22 sum = 22

p = 0x3fffd1c *p = 33 sum = 55

p = 0x3fffd1e *p = 44 sum = 99

end = 0x3fffd20

تعداد بایت هایی که یک نوع بنیادی اشغال می نماید را نشان می دهد. sizeof() تابع

در این رایانه 2 بایت از حافظه را short خط دوم خروجی نشان می دهد که نوع

با سه عنصر اعلان شده. همچنین short اشغال می کند. در برنامۀ بالا آرایه ای از نوع

p اعلان شد هاند. چون short از نوع اشار هگر به end و p اشاره گرهایی به نام

یک واحد افزایش یابد، دو بایت به آدرس p است، هر زمان که short اشاره گر به

بعدی پیشروی می نماید (نه به short در حقیقت به عدد p اضافه می کند و p درون

قرار p درون اشاره گر a ابتدا آدرس آرایۀ for خانۀ بعدی حافظه). در حلقۀ

به اولین عنصر آرایه اشاره می کند. هر بار که حلقه تکرار شود، یک p می گیرد. پس

به عنصر بعدی آرایه اشاره می نماید. عبارت p اضافه می شود و p واحد به

اضافه می کند. sum نیز مقدار آن عنصر را دریافت کرده و به مقدار sum += *p;

این مثال نشان می دهد که هر گاه یک اشار هگر افزایش یابد، مقدار آن به اندازۀ

اشاره گری به p تعداد بایت های شیئی که به آن اشاره می کند، افزایش می یابد. مثلا اگر

 

فصل هفتم / اشاره گرها و ارجا ع ها 227

یک p برابر با هشت بایت باشد، هر گاه که sizeof(double) باشد و double

هشت بایت به پیش می رود. مثلا کد زیر : p واحد افزایش یابد، اشاره گر

float a[8];

float* p = a; // p points to a[0]

++p; // increases the value of p by sizeof(float)

را 4 بایت افزایش p مقدار درون ++p ها 4 بایت را اشغال کنند آنگاه float اگر

را 20 بایت افزایش می دهد. با استفاده از p مقدار درون p += می دهد و ; 5

خاصیت مذکور می توان آرایه را پیمایش نمود: یک اشاره گر را با آدرس اولین عنصر

آرایه مقداردهی کنید، سپس اشاره گر را پی در پی افزایش دهید. هر افزایش سبب

می شود که اشاره گر به عنصر بعدی آرایه اشاره کند. یعنی اشاره گری که به این نحو به

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

مستقیما به عنصر مورد نظر در آرایه دستیابی کنیم:

float* p = a; // p points to a[0]

p += 5; // now p points to a[5]

یک نکتۀ ظریف در ارتباط با آرایه ها و اشار هگرها وجود دارد: اگر اشاره گر را

بیش از ایندکس آرایه افزایش دهیم، ممکن است به بخش هایی از حافظه برویم که هنوز

تخصیص داده نشده اند یا برای کارهای دیگر تخصیص یافته اند. تغییر دادن مقدار این

بخش ها باعث بروز خطا در برنامه و کل سیستم می شود. همیشه باید مراقب این خطر

باشید. کد زیر نشان می دهد که چطور این اتفاق رخ می دهد.

float a[8];

float* p = a[7]; // points to last element in the array

++p; // now p points to memory past last element!

*p = 22.2; // TROUBLE!

مثال بعدی نشان می دهد که ارتباط تنگاتنگی بین آرایه ها و اشاره گرها وجود دارد. نام

به اولین عنصر آرایه است. همچنین (const) آرایه در حقیقت یک اشاره گر ثابت

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

 

 

228 برنامه سازی پیشرفته

7 پیمایش عناصر آرایه از طریق آدرس ‐ * مثال 11

int main()

{ short a[] = {22, 33, 44, 55, 66};

cout << "a = " << a << ", *a = " << *a << endl;

for (short* p = a; p < a +5; p++)

cout << "p = " << p << ", *p = " << *p << endl;

}

a = 0x3fffd08, *a = 22

p = 0x3fffd08, *p = 22

p = 0x3fffd0a, *p = 33

p = 0x3fffd0c, *p = 44

p = 0x3fffd0e, *p = 55

p = 0x3fffd10, *p = 66

p = 0x3fffd12, *p = 77

اشاره می کنند و هر دو short مانند هم هستند: هر دو به نوع p و a ، در نگاه او ل

یک اشاره گر ثابت است و نمی تواند افزایش a 0 هستند. اما x3fffd دارای مقدار 08

را افزایش می دهیم تا آرایه را پیمایش p یابد تا آرایه پیمایش شود. پس به جای آن

به شکل زیر ارزیابی می شود: a+ حلقه را خاتمه می دهد. 5 (p < a+ کنیم. شرط ( 5

0x3fffd08 + 5*sizeof(short) = 0x3fffd08 + 5*2 = 0x3fffd08 + 0xa = 0x3fffd12

باشد ادامه می یابد. p < 0x3fffd پس حلقه تا زمانی که 12

عملگر زیرنویس [] مثل عملگر مقداریابی * رفتار می کند. هر دوی این ها

می توانند به عناصر آرایه دسترسی مستقیم داشته باشند.

a[0] == *a

a[1] == *(a + 1)

a[2] == *(a + 2)

...

...

پس با استفاده از کد زیر نیز می توان آرایه را پیمایش نمود:

for (int i = 0; i < 8; i++)

cout << *(a + i) << endl;

 

فصل هفتم / اشاره گرها و ارجا ع ها 229

7 مقایسۀ الگو ‐ * مثال 12

عنصر n به دنبال 2 a عنصر اول آرایۀ 1 n در میان 1 loc() در این مثال، تابع

از a برمی گرداند که 2 a می گردد. اگر پیدا شد، یک اشاره گر به درون 1 a اول آرایۀ 2

را برمی گرداند. NULL آن جا شروع می شود وگرنه اشاره گر

short* loc(short* a1, short* a2, int n1, int n2)

{ short* end1 = a1 + n1;

for (short* p1 = a1; p1 <end1; p1++)

if (*p1 == *a2)

{ for (int j = 0; j < n2; j++)

if (p1[j] != a2[j]) break;

if (j == n2) return p1;

}

return 0;

}

int main()

{ short a1[9] = {11, 11, 11, 11, 11, 22, 33, 44, 55};

short a2[5] = {11, 11, 11, 22, 33};

cout << "Array a1 begins at location\t" << a1 << endl;

cout << "Array a2 begins at location\t" << a2 << endl;

short* p = loc(a1, a2, 9, 5);

if (p)

{ cout << "Array a2 found at location\t" << p << endl;

for (int i = 0; i < 5; i++)

cout << "\t" << &p[i] << ": " << p[i] << "\t"

<< &a2[i] << ": " << a2[i] << endl;

}

else cout << "Not found.\n";

}

Array a1 begins at location 0x3fffd12

Array a2 begins at location 0x3fffd08

Array a2 found at location 0x3fffd16

0x3fffd16: 11 0x3fffd08: 11

0x3fffd18: 11 0x3fffd0a: 11

0x3fffd1a: 11 0x3fffd0c: 11

0x3fffd1c: 22 0x3fffd0e: 22

0x3fffd1e: 33 0x3fffd10: 33

 

230 برنامه سازی پیشرفته

را در p الگوریتم مقایسۀ الگو از دو حلقه استفاده می کند. حلقۀ بیرونی اشاره گر 1

به آن اشاره می کند با اولین عنصر p جلو می برد تا جایی که عنصری که 1 a آرایۀ 1

p برابر باشد. آنگاه حلقۀ درونی شروع می شود. در این حلقه عناصر بعد از 1 a آرایۀ 2

مقایسه می شوند. اگر نابرابری پیدا شود، حلقۀ a یکی یکی با عناصر متناظرشان در 2

دوباره p درونی فورا خاتمه یافته و حلقۀ بیرونی دور جدیدش را آغاز می کند. یعنی 1

به پیش می رود تا به عنصر بعدی برسد که این عنصر با اولین عنصر a در آرایۀ 1

برابر باشد. ولی اگر حلقۀ داخلی بدون توقف به پایان رسید، به این معناست a آرایۀ 2

یافت p در محل 1 a برابرند. پس 2 a با عناصر متناظرشان در 2 p که عناصر بعد از 1

p به عنوان مکان مورد نظر بازگردانده می شود. دقت کنید که اگر چه 1 p شده است و 1

استفاده شده. همان طور که قبلا گفتیم این عبارت با p1[j] آرایه نیست ولی به شکل

یکی است. برنامۀ آزمون وارسی می کند که واقعا آدر سها بررسی شوند p1+j عبارت

و مقادیر درون آدرس ها یکسان باشد.

new 7 عملگر ‐13

وقتی یک اشاره گر شبیه این اعلان شود:

float* p; // p is a pointer to a float

چهار sizeof(float) تخصیص داده می شود (معمولا p یک فضای چهاربایتی به

ایجاد شده است اما به هیچ جایی اشاره نمی کند زیرا هنوز آدرسی p بایت است). حالا

درون آن قرار نگرفته. به چنین اشاره گری اشاره گر سرگردان می گویند. اگر سعی کنیم

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

*p = 3.14159; // ERROR: no storage has been allocated for *P

به هیچ آدرسی اشاره نمی کند و سیستم عامل نمی داند که مقدار p خطاست. زیرا

3.14159 را کجا ذخیره کند. برای رفع این مشکل می توان اشاره گرها را هنگام

اعلان، مقداردهی کرد:

float x = 0; // x cintains the value 0

float* p = &x // now p points to x

*p = 3.14159; // O.K. assigns this value to address that p points to

 

فصل هفتم / اشاره گرها و ارجا ع ها 231

اشاره می کند و آدرس آن x به p دستیابی داشت زیرا حالا *p در این حالت می توان به

قرار p را دارد. راه حل دیگر این است که یک آدرس اختصاصی ایجاد شود و درون

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

صورت می پذیرد:

float* p;

p = new float; // allocates storage for 1 float

*p = 3.14159; // O.K. assigns this value to that

storage

به p را مقداردهی می کند نه آدرسی که p فقط خود new دقت کنید که عملگر

آن اشاره می کند. می توانیم سه خط فوق را با هم ترکیب کرده و به شکل یک دستور

بنویسیم:

float* p = new float(3.141459);

تعریف می شود و سپس یک بلوک خالی از float* از نوع p با این دستور، اشاره گر

تخصیص می یابد و همچنین مقدار p منظور شده و آدرس آن به float نوع

نتواند خانۀ خالی در حافظه new 3.14159 در آن آدرس قرار می گیرد. اگر عملگر

یا « اشاره گر تهی » ، پیدا کند، مقدار صفر را برمی گرداند. اشاره گری که این چنین باشد

می نامند. با استفاده از کد هوشمند زیر می توانیم مراقب باشیم که اشاره گر تهی NULL

ایجاد نشود:

double* p = new double;

if (p == 0) abort(); // allocator failed: insufficent memory

else *p = 3.141592658979324;

فراخوانی شده و abort() در این قطعه کد، هرگاه اشار هگری تهی ایجاد شد، تابع

این دستور لغو می شود.

تاکنون دانستیم که به دو طریق می توان یک متغیر را ایجاد و مقداردهی کرد.

روش اول:

float x = 3.14159; // allocates named memory

 

232 برنامه سازی پیشرفته

و روش دوم:

float* p = new float(3.14159); // allocates unnamed memory

هنگام کامپایل تخصیص می یابد. در حالت دوم x در حالت اول، حافظۀ مورد نیاز برای

حافظۀ مورد نیاز در زمان اجرا و به یک شیء بی نام تخصیص می یابد که با استفاده از

قابل دستیابی است. *p

delete 7 عملگر ‐14

دارد. کارش این است که حافظۀ new عملی برخلاف عملگر delete عملگر

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

را تنها روی delete کارهای دیگر یا حتی تخصیص های جدید استفاده کند. عملگر

ایجاد شده اند. وقتی حافظۀ یک new اشاره گرهایی می توان به کار برد که با دستور

اشاره گر آزاد شد، دیگر نمی توان به آن دستیابی نمود مگر این که دوباره این حافظه

تخصیص یابد:

float* p = new float(3.14159);

delete p; // deallocates q

*p = 2.71828; // ERROR: q has been deallocated

به آن تخصیص یافته new در کد بالا آزاد شود، حافظ های که توسط p وقتی اشاره گر

به حافظۀ آزاد اضافه می شود. وقتی sizeof(float) بود، آزاد شده و به میزان

اشاره گری آزاد شد، به هیچ چیزی اشاره نمی کند؛ مثل متغیری که مقداردهی نشده. به

این اشار هگر، اشار هگر سرگردان می گویند.

اشاره گر به یک شیء ثابت را نمی توان آزاد کرد:

const int* p = new int;

delete p; // ERROR: cannot delete pointer to const objects

ثابت ها نمی توانند تغییر کنند » علت این است که

اگر متغیری را صریحا اعلان کرده اید و سپس اشار هگری به آن نسبت داد هاید، از

استفاده نکنید. این کار باعث اشتباه غیر عمدی زیر می شود: delete عملگر

 

فصل هفتم / اشاره گرها و ارجا ع ها 233

float x = 3.14159; // x contains the value 3.14159

float* p = &x; // p contains the address of x

delete p; // WARNING: this will make x free

آزاد شود. این اشتباه را به x کد بالا باعث می شود که حافظۀ تخصیص یافته برای

سختی می توان تشخیص داد و اشکال زدایی کرد.

7 آرایه های پویا ‐9

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

تخصیص داده می شود:

float a[20]; // a is a const pointer to a block of 20 floats

float* const p = new float[20]; // so is p

اشاره float اشاره گرهای ثابتی هستند که به بلوکی حاوی 20 متغیر p و هم a هم

بسته بندی ایستا 2 می گویند زیرا این کد باعث می شود که حافظۀ مورد a دارند. به اعلان

در زمان کامپایل تخصیص داده شود. وقی برنامه اجرا شود، به هر حال a نیاز برای

حافظۀ مربوطه تخصیص خواهد یافت حتی اگر از آن هیچ استفاده ای نشود. می توانیم با

استفاده از اشار هگر، آرایۀ فوق را طوری تعریف کنیم که حافظه مورد نیاز آن فقط در

زمان اجرا تخصیص یابد:

float* p = new float[20];

p را در اختیار گذاشته و اشار هگر float دستور بالا، 20 خانۀ خالی حافظه از نوع

می گویند. به این طرز « آرایۀ پویا 1 » ، را به خانۀ اول آن نسبت می دهد. به این آرایه

می گویند. « بسته بندی زمان جرا » ایجاد اشیا بسته بندی پویا 3 یا

در a را با یکدیگر مقایسه کنید. آرایۀ ایستای p و آرایۀ پویای a آرایۀ ایستای

زمان کامپایل ایجاد می شود و تا پایان اجرای برنامه، حافظۀ تخصیصی به آن مشغول

در زمان اجرا و هر جا که لازم شد ایجاد می شود و پس از p می ماند. ولی آرایۀ پویای

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

delete [] p;

1 – Dynamic arrays 2 – Static binding 3 – Dynamic binding

 

 

234 برنامه سازی پیشرفته

p باید حتما قید شوند زیرا p براکت ها [] قبل از نام p برای آزاد کردن آرایۀ پویای

به یک آرایه اشاره دارد.

7 استفاده از آرای ههای پویا ‐ * مثال 15

در برنامۀ زیر یک آرایۀ پویا ایجاد می کند: get() تابع

void get(double*& a, int& n)

{ cout << "Enter number of items: "; cin >> n;

a = new double[n];

cout << "Enter " << n << " items, one per line:\n";

for (int i = 0; i < n; i++)

{ cout << "\t" << i+1 << ": ";

cin >> a[i];

}

}

void print(double* a, int n)

{ for (int i = 0; i < n; i++)

cout << a[i] << " " ;

cout << endl;

}

int main()

{ double* a; // a is simply an unallocated pointer

int n;

get(a,n); // now a is an array of n doubles

print(a,n);

delete [] a; // now a is simply an unallocated pointer again

get(a,n); // now a is an array of n doubles

print(a,n);

}

Enter number of items: 4

Enter 4 items, one per line:

1: 44.4

2: 77.7

3: 22.2

4: 88.8

44.4 77.7 22.2 88.8

Enter number of items: 2

 

فصل هفتم / اشاره گرها و ارجا ع ها 235

Enter 2 items, one per line:

1: 3.33

2: 9.99

3.33 9.99

ایجاد می شود. سپس double* از نوع a وقتی برنامه اجرا می شود، ابتدا اشاره گر تهی

یک آرایۀ پویا ایجاد کرده get() فرستاده می شود. تابع get() این اشاره گر به تابع

می گذارد. نکته جالب این جاست که تعداد a و آدرس اولین خانۀ آن را درون اشار هگر

فراخوانی شد، از get() عناصر آرایه هنگام اجرا مشخص می شود. یعنی وقتی تابع

new کاربر پرسیده می شود که اندازۀ آرایه چقدر باشد. سپس با استفاده از عملگر

مقادیر آرایه for آرایه ای به همان اندازه ساخته می شود. پس از آن با استفاده از حلقۀ

درون cin >> a[i]; یکی یکی از ورودی دریافت شده و با استفاده از عبارت

7 را ‐ معادل است. مثال 12 a+i با عبارت a[i] عناصر آرایه قرار می گیرد (عبارت

و تعداد عناصرش به برنامۀ اصلی باز می گردد. تابع a ببینید). در نهایت آرایۀ

وظیفه دارد که با پیش بردن اشار هگر روی این آرایه، مقادیر موجود در آن را print()

آرایۀ مورد نظر آزاد می شود. delete چاپ کند. پس از این کار، با استفاده از عملگر

می توان آرایۀ جدیدی ساخت. این آرایۀ جدید get() دوباره با فراخوانی تابع

می تواند اندازۀ متفاوتی داشته باشد. توجه کنید که عملگر زیرنویس [] حتما باید در

در تابع a به کار رود تا کل آرایه آزاد شود. همچنین ببینید که پارامتر delete دستور

به چه شکلی اعلان شده: get()

void get(double*& a, int& n)

مقداری را نسبت دهد. به همین a قرار است که به اشار هگر تهی get() تابع

باید به شکل ارجاع ارسال شود تا تابع بتواند مقدار آن را دس تکاری کند. چون a دلیل

خواهد double*& است، پس شکل ارجاعی اش به صورت double* از نوع a

بود. برهان بالا به این معناست که تابع می تواند اشار هگرهایی محلی داشته باشد و اگر

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

به شکل ارجاع ارسال نشده است a اشاره گر print() بگویید چرا در تعریف تابع

را درون تابع به درستی چاپ کرد؟ a ولی با وجود این می توان عناصر

 

 

236 برنامه سازی پیشرفته

7 اشاره گر ثابت ‐10

تفاوت دارد. این تفاوت در قالب مثال « اشاره گر ثابت » با « اشاره گر به یک ثابت »

زیر نشان داده شده است.

7 اشاره گرهای ثابت و اشاره گرهایی به ثابت ها ‐ * مثال 16

اشاره به ،cp اشار هگر ثابت ،p در این کد چهار اشار هگر اعلان شده. اشاره گر

: cpc اشاره گر ثابت به یک ثابت ،pc یک ثابت

int n = 44; // an int

int* p = &n; // a pointer to an int

++(*p); // OK: increments int *p

++p; // OK: increments pointer p

int* const cp = &n; // a const pointer to an int

++(*cp); // OK: increments int *cp

++cp; // illegal: pointer cp is const

const int k = 88; // a const int

const int * pc = &k; // a pointer to a const int

++(*pc); // illegal: int *pc is const

++pc; // OK: increments pointer pc

const int* const cpc = &k; // a const pointer to a const int

++(*cpc); // illegal: int *pc is const

++cpc; // illegal: pointer cpc is const

و (++p) قابل افزایش است p است. هم خود n اشاره گری به متغیر p اشاره گر

یک cp اشاره گر .(++(*P)) به آن اشاره می کند قابل افزایش است p هم مقداری که

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

اشاره گری است که به آدرس pc آن آدرس است را می توان دس تکاری کرد. اشاره گر

به آن اشاره pc را می توان تغییر داد ولی مقداری که pc یک ثابت اشاره دارد. خود

یک اشاره گر ثابت به یک شیء ثابت است. نه cpc دارد قابل تغییر نیست. در آخر هم

است. cpc قابل تغییر است و نه مقداری که آدرس آن در cpc مقدار

 

فصل هفتم / اشاره گرها و ارجا ع ها 237

7 آرایه ای از اشاره گرها ‐11

می توانیم آرایه ای تعریف کنیم که اعضای آن از نوع اشاره گر باشند. مثلا دستور:

float* p[4];

اعلان (float یعنی اشاره گری به ) float* را با چهار عنصر از نوع p آرایۀ

می کند. عناصر این آرایه را مثل اشار هگر های معمولی می توان مقداردهی کرد:

p[0] = new float(3.14159);

p[1] = new float(1.19);

این آرایه را می توانیم شبیه شکل مقابل مجسم کنیم:

مثال بعد نشان می دهد که آرایه ای از اشار هگرها به

چه دردی می خورد. از این آرایه می توان برای مرتب کردن

یک فهرست نامرتب به روش حبابی استفاده کرد. به

جای این که خود عناصر جابجا شوند، اشاره گرهای آن ها جابجا می شوند.

7 مرتب سازی حبابی غیرمستقیم ‐ *مثال 17

void sort(float* p[], int n)

{ float* temp;

for (int i = 1; i < n; i++)

for (int j = 0; j < n-i; j++)

if (*p[j] > *p[j+1])

{ temp = p[j];

p[j] = p[j+1];

p[j+1] = temp;

}

}

for آرایه ای از اشاره گرها را می گیرد. سپس درون حلقه های تودرتوی sort() تابع

بررسی می کند که آیا مقادیری که اشاره گرهای مجاور به آن ها اشاره دارند، مرتب

هستند یا نه. اگر مرتب نبودند، جای اشاره گرهای آن ها را با هم عوض می کند. در پایان

به جای این که یک فهرست مرتب داشته باشیم، آرایه ای داریم که اشار هگرهای درون

آن به ترتیب قرار گرفته اند.

3.14159

double

1.19

double238 برنامه سازی پیشرفته

7 اشاره گری به اشاره گر دیگر ‐12

یک اشار هگر می تواند به اشار هگر دیگری اشاره کند. مثلا:

char c = 't';

char* pc = &c;

char** ppc = &pc;

char*** pppc = &ppc;

***pppc = 'w'; // changes value of c to 'w'

است. c اشاره گری به متغیر کاراکتری pc حالا

است و اشار هگر pc اشاره گری به اشار هگر ppc

اشاره دارد. مثل ppc هم به اشاره گر pppc

شکل مقابل:

مستقیما pppc با این وجود می توان با اشاره گر

رسید. c به متغیر

7 اشاره گر به توابع ‐13

این بخش ممکن است کمی عجیب به نظر برسد. حقیقت این است که نام یک

تابع مثل نام یک آرایه، یک اشاره گر ثابت است. نام تابع، آدرسی از حافظه را نشان

می دهد که کدهای درون تابع در آن قسمت جای گرفته اند. پس بنابر قسمت قبل اگر

اشاره گری به تابع اعلان کنیم، در اصل اشار هگری به اشار هگر دیگر تعریف کرده ایم. اما

این تعریف، نحو متفاوتی دارد:

int f(int); // declares function f

int (*pf)(int); // declares function pointer pf

pf = &f; // assigns address of f to pf

اشاره گری به یک pf همراه با * درون پرانتز قرار گرفته، یعنی این که pf اشاره گر

هم درون پرانتز آمده است، به این معنی که تابعی که int تابع است. بعد از آن یک

را می توانیم به شکل pf دارد. اشاره گر int به آن اشاره می نماید، پارامتری از نوع pf

زیر تصور کنیم:

pppc

ppc

pc

c '\t'

0123

P

 

 

 

 

فصل هفتم / اشاره گرها و ارجا ع ها 239

فایدۀ اشاره گر به توابع این است که به این

طریق می توانیم توابع مرکب بسازیم. یعنی

می توانیم یک تابع را به عنوان آرگومان به تابع

دیگر ارسال کنیم! این کار با استفاده از اشار هگر

به تابع امکان پذیر است.

7 تابع مرکب جمع ‐ * مثال 18

: n و عدد صحیح pf در این مثال دو پارامتر دارد: اشاره گر تابع sum() تابع

int sum(int (*)(int), int);

int square(int);

int cube(int);

int main()

{ cout << sum(square,4) << endl; // 1 + 4 + 9 + 16

cout << sum(cube,4) << endl; // 1 + 8 + 27 + 64

}

یک پارامتر غیر معمول دارد. نام تابع دیگری به عنوان آرگومان به آن sum() تابع

فراخوانی شود، مقدار sum(square, ارسال شده. هنگامی که ( 4

بازگشت داده می شود. square(1)+square(2)+square(3)+square(4)

مقدار sum(square, را برمی گرداند، فراخوانی ( 4 k*k مقدار square(k) چون

1+4+9+16 را محاسبه نموده و بازمی گرداند. تعریف توابع و خروجی آزمایشی =30

به شکل زیر است:

int sum(int (*pf)(int k), int n)

{ // returns the sum f(0) + f(1) + f(2) + ... + f(n-1):

int s = 0;

for (int i = 1; i <= n; i++)

s += (*pf)(i);

return s;

}

int square(int k)

{ return k*k;

}

pf

f

int f(int n)

{

...

}

 

 

240 برنامه سازی پیشرفته

int cube(int k)

{ return k*k*k;

}

30

100

یک اشاره گر به تابع است. اشاره گر به تابعی sum() در فهرست پارامترهای تابع pf

در تابع k . را برمی گرداند int دارد و مقداری از نوع int که آن تابع پارامتری از نوع

به تابعی اشاره pf اصلا استفاده نشده اما حتما باید قید شود تا کامپایلر بفهمد که sum

یا square(i) معادل با (*pf)(i) دارد. عبارت int دارد که پارامتری از نوع

خواهد بود، بسته به این که کدام یک از این دو تابع به عنوان آرگومان به cube(i)

ارسال شوند. sum()

آدرس شروع تابع square نام تابع، آدرس شروع تابع را دارد. پس

sum(square, به شکل ( 4 sum() را دارد. بنابراین وقتی تابع square()

فرستاده می شود. با pf است به اشاره گر square فراخوانی شود، آدرسی که درون

به pf به آرگومان تابعی فرستاده می شود که i مقدار (*pf)(i) استفاده از عبارت

آن اشاره دارد.

NULL و NUL 7‐14

است اما این مقدار را به هر نوع بنیادی دیگر int ثابت صفر ( 0) از نوع

می توان تخصیص داد:

char c = 0; // initializes c to the char '\0'

short d = 0; // initializes d to the short int 0

int n = 0; // initializes n to the int 0

unsigned u = 0; // initializes u to the unsigned int 0

float x = 0; // initializes x to the float 0.0

double z = 0; // initializes z to the double 0.0

مقدار صفر معناهای گوناگونی دارد. وقتی برای اشیای عددی به کار رود، به

معنای عدد صفر است. وقتی برای اشیای کاراکتری به کار رود، به معنای کاراکتر تهی یا

معادل کاراکتر ' 0\' نیز هست. وقتی مقدار صفر برای اشاره گر ها NUL . است NUL

یک کلمۀ کلیدی است و NULL . است NULL یا « هیچ چیز » به کار رود، به معنای

فصل هفتم / اشاره گرها و ارجا ع ها 241

یا صفر در یک اشاره گر قرار NULL کامپایلر آن را می شناسد. هنگامی که مقدار

0 در حافظه اشاره دارد. این خانۀ حافظه، یک خانۀ x می گیرد، آن اشاره گر به خانه 0

استثنایی است که قابل پردازش نیست. نه می توان آن خانه را مقداریابی کرد و نه

می گویند. « هیچ چیز » NULL می توان مقداری را درون آن قرار داد. به همین دلیل به

اعلان می کنیم، خوب است که ابتدا آن new وقتی اشاره گری را بدون استفاده از

کنیم تا مقدار زبالۀ آن پاک شود. اما همیشه باید به خاطر داشته باشیم که NULL را

را نباید مقداریابی نماییم: NULL اشاره گر

int* p = 0; // p points to NULL

*p = 22; // ERROR: cannot dereference the NULL pointer

پس خوب است هنگام مقداریابی اشاره گرها، احتیاط کرده و بررسی کنیم که آن

نباشد: NULL اشاره گر

if (p) *p = 22; // O.K.

صفر نباشد. می دانید که شرط بالا p وقتی اجرا می شود که *p= حالا دستور ; 22

معادل شرط زیر است:

if (p != NULL) *p = 22;

اشاره گر ها را نمی توان نادیده گرفت. آن ها سرعت پردازش را زیاد می کنند و

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

با به کارگیری اشاره گرها می توان اشیایی پیچیده تر و کارآمدتر ساخت. این موضوع در

بحث می شود و یکی از مه مترین مباحث در برنام هنویسی « ساختمان داده ها » قالب

است. همچنین اشاره گرها در برنامه نویسی سطح پایین و برای دست کاری منابع

سخت افزاری نیز فراوان به کار می روند. اما اشاره گرها امنیت برنامه را به خطر

می اندازند. می توانند به راحتی برنامۀ حاضر یا برنام ههایی که هم زمان با برنامۀ فعلی در

حال کارند را خراب کنند. نمونه ای از این بحران را در مثال های این فصل دیدیم. اگر

می خواهید از مزایای اشاره گرها بهره ببرید و کارآیی برنامه تان را بهبود ببخشید، همیشه

باید محتاطانه با اشاره گرها برخورد کنید. این فقط وقتی ممکن است که اشاره گرها را

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

 

242 برنامه سازی پیشرفته

پرسش های گزینه ای

از کدام گزینه استفاده می شود؟ n 1 – برای به دست آوردن آدرس متغیر

(n) ( د n ( ج *n ( ب &n ( الف

کدام گزینه صحیح است؟ int& k=n; 2 – در مورد دستور

را دارد n است که مقدار n متغیری مستقل از k ( الف

را دارد n است که آدرس n متغیری مستقل از k ( ب

است n یک ارجاع به k ( ج

است n یک اشار هگر به k ( د

چیست؟ int& k= 3 – حاصل اجرای کد ; 37

قرار می گیرد k الف) مقدار 37 در متغیر مستعار

نام مستعار آن است، قرار می گیرد k ب) مقدار 37 در متغیری که

است، قرار می گیرد k ج) مقدار 37 در متغیری که آدرس آن در

د) کامپایلر خطا می گیرد زیرا ارجا عها را نمی توان با مقدار صریح مقداردهی کرد

کدام گزینه صحیح است؟ int& c=n; 4 – در مورد دستور

آدرس های یکسان و مقدارهای یکسان دارند n و c ( الف

آدرس های متفاوت و مقدارهای متفاوت ولی نوع یکسان دارند n و c ( ب

آدرس های یکسان ولی مقدارهای متفاوت دارند n و c ( ج

آدرس های متفاوت ولی مقدارهای یکسان دارند n و c ( د

کدام گزینه صحیح است؟ float* p=k; 5 – در رابطه با عبارت

در آن ذخیره می شود k متغیر مستقلی است که مقدار p الف) اشاره گر

در آن ذخیره می شود k متغیر مستقلی است که آدرس p ب) اشاره گر

در آن ذخیره می شود k متغیر مستقلی است که نام p ج) اشاره گر

است k متغیر مستقل نیست و فقط یک نام مستعار برای متغیر p د) اشاره گر

0 باشد و سپس x000cc از نوع صحیح با مقدار 50 و آدرس 70 nt 6 – اگر

اعلان شود، آنگاه حاصل عبارت int* p=&nt; به شکل p اشاره گر

چه خواهد بود؟ cout << p;

 

فصل هفتم / اشاره گرها و ارجا ع ها 243

الف) مقدار 50 در خروجی چاپ می شود

0 در خروجی چاپ می شود x000cc ب) مقدار 70

در خروجی چاپ می شود "&nt" ج) عبارت

در خروجی چاپ می شود p د) آدرس

را طوری تعریف کنیم که به l باشد و بخواهیم float اشاره گری به نوع k 7 – اگر

اشاره کند، آنگاه : k

تعریف شود float* باید از نوع l ( الف

تعریف شود float** باید از نوع l ( ب

تعریف شود float باید از نوع l ( ج

تعریف شود float& باید از نوع l ( د

یک اشاره گر باشد، آنگاه حاصل عبارت زیر چیست؟ pt 8 – اگر

cout << pt << endl << *pt << endl << &pt ;

به آن اشاره pt و روی سطر دوم مقداری که pt الف) روی سطر اول آدرس درون

چاپ می شود pt دارد و روی سطر سوم آدرس خود

به آن اشاره می کند و روی سطر دوم آدرس درون pt ب) روی سطر اول مقداری که

چاپ می شود pt و روی سطر سوم آدرس خود pt

به آن اشاره pt و روی سطر دوم مقداری که pt ج) روی سطر اول آدرس خود

چاپ می شود pt می کند و روی سطر سوم آدرس درون

و روی سطر pt و روی سطر دوم آدرس خود pt د) روی سطر اول آدرس درون

به آن اشاره دارد چاپ می شود pt سوم مقداری که

یک متغیر از نوع بنیادی باشد آنگاه : n 9 – اگر

&(*n)=n ( ب *(&n)=n ( الف

&n=n ( د *n=n ( ج

را به شکل بازگشت از نوع ارجاع اعلان f() 10 – کدام یک از اعلا نهای زیر، تابع

می کند؟

int* f(int k) ( ب int f(int& k) ( الف

void f(int& k) ( د int& f(int k) ( ج

 

244 برنامه سازی پیشرفته

باشد، آنگاه حاصل sizeof(float)= باشد و 4 float* از نوع p 11 – اگر

چیست؟ ++p; عبارت

یک بایت افزایش می یابد p الف) آدرس درون

چهاربایت افزایش می یابد p ب) آدرس درون

دو بایت افزایش می یابد p ج) آدرس درون

د) کامپایلر خطا می گیرد زیرا اشاره گر ها را نمی توان افزایش داد

12 – کدام گزینه صحیح نیست؟

را نمی توان آزاد کرد (const) الف) اشاره گر به یک شیء ثابت

می توان یک آدرس آزاد را به یک اشار هگر اختصاص داد new ب) با استفاده از عملگر

می توان آدرس تخصیص یافته به یک اشاره گر را آزاد کرد delete ج) با استفاده از عملگر

د) پس از آزاد کردن یک اشاره گر هنوز می توان به آدرس آن دستیابی نمود

نتواند یک آدرس آزاد پیدا کند آنگاه new 13 – اگر عملگر

را برمی گرداند NULL الف) مقدار

را برمی گرداند NUL ب) مقدار

ج) خطای زمان اجرا رخ می دهد و برنامه متوقف می شود

د) دوباره تلاش می کند تا این که یک فضای آزاد بیابد

14 – کدام عبارت صحیح است؟

الف) آرایه پویا در زمان اجرا ایجاد می شود ولی آرایه ایستا در زمان کامپایل ایجاد می شود

ب) آرایه پویا را در هر زمانی می توان آزاد کرد ولی آرایه ایستا تا پایان برنامه باقی می ماند

ج) اندازه آرایه پویا متغیر است ولی اندازه آرایه ایستا ثابت است

د) همه موارد فوق صحیح است

کدام عبارت صحیح نیست؟ float* t[ 15 – در رابطه با عبارت ;[ 10

یک آرایه پویا است t الف) آرایه

یک آرایه ایستا است t ب) آرایه

در هنگام اجرا قابل تغییر است t ج) اندازه آرایه

در زمان اجرا ساخته می شود t د) آرایه

 

فصل هفتم / اشاره گرها و ارجا ع ها 245

پرسش های تشریحی

1‐ چگونه به آدرس حافظۀ یک متغیر دستیابی پیدا می کنید؟

2‐ چگونه به محتویات مکانی از حافظه که آدرس آن در یک متغیر اشار هگر ذخیره

شده است دستیابی می کنید؟

3‐ تفاوت بین دو اعلان زیر را شر ح دهید:

int n1=n;

int& n2=n;

4‐ تفاوت بین دو نحوۀ استفاده از عملگر ارجاع & در کدهای زیر را شرح دهید:

int& r = n;

p = &n;

5‐ تفاوت بین دو نحوۀ استفاده از عملگر اشاره * در کدهای زیر را شرح دهید:

int* q = p;

n = *p;

6‐ درست یا نادرست است ؟ توضی ح دهید:

(&x == &y) آنگاه (x == y) الف ) اگر

(*x == *y) آنگاه (x == y) ب ) اگر

7‐ الف ) یک اشاره گر سرگردان چیست ؟

ب ) از مقداریابی یک اشاره گر سرگردان چه نتیج ۀ ناخوشایندی حاصل می شود؟

ج ) چگونه می توان از ای ن نتیجۀ نامطلوب اجتناب کرد؟

8‐ چه خطایی در کد زیر است ؟

int& r = 22;

9‐ چه خطایی در کد زیر است ؟

int* p = &44;

10 ‐ چه خطایی در کد زیر است ؟

char c = 'w';

char p = &c;

7 را شبیه این اعلان کرد: ‐ مثال 6 ppn 11 ‐ چرا نمی توان متغیر

int** ppn = &&n;

است ؟ « بسته بندی پویا » و « بسته بندی ایستا » 12 ‐ چه تفاوتی بین

13 ‐ چه اشتباهی در کد زیر است ؟

char c = 'w';

char* p = c;

 

246 برنامه سازی پیشرفته

14 ‐ چه اشتباهی در کد زیر است ؟

short a[32];

for (int i = 0; i < 32; i++)

*a++ = i*i;

15 ‐ مقدار هر یک از متغیرها ی زیر را بعد از اجرا شد ن کد زیر مشخص کنید. فرض

در محلی از حافظه ذخیره شده که m کنید که هرعدد صحیح 4 بایت را اشغال می کند و

0 شروع می شود. x3fffd از بای ت 00

int m = 44;

int* p = &m;

int& r = m;

int n = (*p++);

int* q = p – 1;

r = *(--p) + 1;

++*q;

*q ( ج r ( ث *p ( ت &m ( پ n ( ب m ( الف

16 ‐ هر یک از موارد زیر را در دست ههای چپ مقدار تغییرپذیر، چپ مقدار تغییرناپذیر

یا غیرچپ مقدار طبقه بندی کنید:

double x = الف ) ; 1.23

4.56*x + ب ) 7.89

const double y = پ ) ; 1.23

double a[8] = { ت ) ;{ 0.0

a[ ث ) [ 5

double f() { return ج ) ;{ 1.23

f( چ ) ( 1.23

double& r = x; ( ح

double* p = &x; ( خ

*p ( د

const double* p = &x; ( ذ

double* const p = &x; ( ر

17 ‐ چه اشتباهی در کد زیر است ؟

float x = 3.14159;

float* p = &x;

short d = 44;

فصل هفتم / اشاره گرها و ارجا ع ها 247

short* q = &d;

p = q;

18 ‐ چه اشتباهی در کد زیر است ؟

int* p = new int;

int* q = new int;

cout << "p = " << p << ", p + q = " << p + q

<< endl;

انجام دهید چیست ؟ NULL 19 ‐ تنها کاری که نباید هرگز با اشار هگر

چیس ت و بگویید آ ن چگونه می تواند p 20 ‐ در اعلان زیر توضی ح دهید که نوع

استفاده شود:

double**** p;

برای هر یک از q و p 0 داشته باشد، آنگاه مقادیر x3fffd1c آدرس x 21 ‐ اگر

موارد زیر چیست ؟

double x = 1.01;

double* p = &x;

double* q = p + 5;

باشد، کدام یک int متغیری ار نوع n باشند و int اشاره گرهایی به q و p 22 ‐ اگر

از موارد زیر مجاز اس ت؟

p + n ( پ p – q ( ب p + q ( الف

n - q ( ج n + p ( ث p – n ( ت

23 ‐ این گفته که یک آرایه در حقیقت یک اشاره گر ثاب ت است چه معنی می دهد؟

24 ‐ وقتی تنها آدرس اولین عنصر ی ک آرایه به تابع ارسال می شود، چگونه است که

تابع می تواند هر عنصری از آرایه را دستیابی کند؟

درست i و یک عدد صحیح a 25 ‐ توضیح دهید چرا س ه شرط زیر برای آرایۀ

است ؟

a[i] == *(a + i);

*(a + i) == i[a];

a[i] == i[a];

26 ‐ تفاوت بین دو اعلان زیر را توضی ح دهید:

double * f();

double (*f());

27 ‐ برای هر یک از موارد زیر یک اعلان بنویسید:

float الف ) یک آرایه از هشت

float ب ) یک آرایه از هشت اشاره گر به

 

248 برنامه سازی پیشرفته

float پ ) یک اشاره گر به یک آرایه از هشت

float ت ) یک اشاره گر به یک آرایه از هشت اشاره گر به

را برمی گرداند float ث ) یک تابع که یک

را برمی گرداند float ج ) یک تابع که یک اشاره گر به

را برم یگرداند float چ ) یک اشاره گر به تابع که یک

را برمی گرداند float ح ) یک اشاره گر به یک تابع که یک اشاره گر به

تمرین های برنامه نویسی

1‐ تابعی بنویسید که از اشاره گرها برای جستجوی آدرس یک عدد صحیح مفروض

در یک آرایه استفاده کند. اگر عدد مفروض پیدا شود تابع آدرس آن را برگرداند و در

را برگرداند. null غیر این صورت

دریافت کند و آرایه جدیدی را برگرداند float اشاره گر به n 2‐ تابعی بنویسید که

باشد. float عدد n که شامل مقادیر آن

اشاره گر در آرایه n هایی که با float 3‐ کد تابع زیر را بنویسید. این تابع مجموع

به آن ها اشاره می شود را بر می گرداند. p

float sum(float* p[], int n);

های منفی که به وسیله flat 4‐ کد تابع زیر را بنویسید. این تابع علامت هر یک از

به آن ها اشاره می شود را تغییر می دهد. p اشاره گر در آرایه n

void abs(float* p[], int n);

های اشاره شده float 5‐ کد تابع زیر را بنویسید. این تابع به صورت غیر مستقیم

را با مرتب سازی اشاره گرها مرتب می کند. p اشاره گر در آرایه n توسط

void sort(float* p[], int n);

را می شمارد تا این که s 6‐ کد تابع زیر را بنویسید. این تابع تعداد بایت های درون

به کاراکتر ' 0\' اشاره کند. s

unsigned len(const char* s);

را داخل *s بایت اول حافظه با شروع از 2 n 7‐ کد تابع زیر را بنویسید. این تابع

می تواند s تعداد بایت هایی است که 2 n . کپی می کند *s بایت های شروع شده از 1

افزایش یابد قبل از این که به کاراکتر ' 0\' اشاره کند.

void cpy(char* s1, const char* s2);

 

 

فصل هفتم / اشاره گرها و ارجا ع ها 249

را با بایت های *s بایت با شروع از 2 n 8‐ کد تابع زیر را بنویسید. این تابع حداکثر

می تواند s تعداد بایت هایی است که 2 n مقایسه می کند که *s متناظر با شروع از 1

بایت معادل باشند n افزایش یابد قبل از این که به کاراکتر ' 0\' اشاره کند. اگر همه

تابع باید 0 را برگرداند. در غیر این صورت بسته به این که در اولین اختلاف بایت

باشد مقدار 1- یا 1 را s کوچک تر یا بزرگ تر از بایت موجود در 2 s موجود در 1

برگرداند.

int cmp(char* s1, char* s2);

c را به دنبال کاراکتر s بایت با شروع از n 9‐ کد تابع زیر را بنویسید. این تابع

می تواند افزایش یابد قبل از این که s تعداد بایت هایی است که n جستجو می کند که

پیدا شود یک اشاره گر به آن برگردانده c به کاراکتر تهی ' 0\' اشاره کند. اگر کاراکتر

برگردانده می شود. NULL می شود و در غیر این صورت

char* chr(char* s, char c);

را برمی f(n) و ... و f( و ( 2 f( مقدار ( 1 n 10 ‐ تابع زیر که حاصل ضرب

7 نگاه کنید). ‐ گرداند بنویسید (به مثال 18

int product(int (*pf)(int k), int n);

11 ‐ قانون ذوزنقه را برای انتگرال گیری یک تابع پیاده سازی کنید. از تابع زیر استفاده

نمایید:

double trap(double (*pf)(double x), double a,

double b, int n);

فاصله ای b و a . که باید انتگرال گیری شود اشاره می کند f به تابع pf در این جا

تعداد زیر فاصله های استفاده شده n در آن فاصله انتگرال گیری می شود و f است که

مقدار 1.41421 trap(square, 1, 2, است. برای مثال فراخوانی ;( 100

را بر می گرداند. قانون زوذنقه مجموع مساحت های ذوزنقه که مساحت تقریبی زیر

باشد آنگاه تابع مذکور مقدار h = است را برمی گرداند. به طور مثال اگر 5 f نمودار

پهنای هر یک از ذوزنقه های زیر است: h = (b-a)/ زیر را بر می گرداند که 5

[ ( ) 2 ( ) 2 ( 2 ) 2 ( 3 ) 2 ( 4 ) ( )]

2

h f a + f a + h + f a + h + f a + h + f a + h + f b

 

   + MOHSEN GHASEMI - ۱٢:٢٧ ‎ق.ظ ; ۱۳۸٩/٥/٢۳