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


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

فصل نهم شی گرایی »در «++ C

mo-mah.persianblog.ir

mohsen_mahyar@yahoo.com

فصل نهم

 

C++ در « شی گرایی »

9 مقدمه ‐1

اولین نر م افزار برای نخستین رایانه ها، زنجیره ای از صفر و یک ها بود که فقط عدۀ

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

تا نرم افزارهای بی شتری ایجاد شود. برای این منظور، برنامه نویسان مجبور بودند با

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

یک نرم افزار صرف شود. از این گذشته، اگر ایرادی در کار برنامه یافت می شد، پیدا

جهش « زبان اسمبلی 1 » کردن محل ایراد و رفع آن بسیار مشکل و طاقت فرسا بود. ابداع

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

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

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

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

1 – Assembly300 برنامه سازی پیشرفته

برای یک جمع ساده به معنای ده ها هزار خط کد برای یک برنامۀ حسابداری روزانه

دروازه های « زبان های سطح بالا » . است. این بار انبوه کدهای اسمبلی مشکل ساز شدند

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

زبان های سطح بالا دو نشان درخشان از ادبیات و ریاضیات همراه خود آوردند:

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

که سرعت تولید و « تابع » تکراری و طویل اسمبلی خلاص شوند و دوم مفهوم

عیب یابی نرم افزار را چندین برابر کرد. اساس کار این گونه بود که وظیفۀ اصلی

برنامه به وظایف کوچ کتری تقسیم می شد و برای انجام دادن هر وظیفه، تابعی نوشته

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

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

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

می گرفت و به سرعت محل خطا یافت شده و اصلاح می شد. علاوه بر این، برای بهبود

دادن نر مافزار موجود یا افزودن امکانات اضافی به آن، دیگر لازم نبود که برنامه از نو

نوشته شود؛ فقط توابع مورد نیاز را تولید کرده یا بهبود می دادند و آن را به برنامۀ

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

نرم افزارهای بزرگ ایجاد شدند و بحث مدیریت پروژ ههای نرم افزاری و شیوه های تولید

نرم افزار و چرخۀ حیات و ... مطرح شد.

نرم افزارهای بزرگ، تجربیات جدیدی به همراه آوردند و برخی از این تجربیات

نشان می داد که توابع چندان هم بی عیب نیستند. برای ایجاد یک نرم افزار، توابع زیادی

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

خروجی یک تابع تغییر کند، سایر توابعی که با تابع مذکور در ارتباط بودند نیز باید

شناسایی می شدند و به تناسب تغییر می نمودند. این موضوع، اصلاح نرم افزارها را

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

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

بنابراین باز هم متخصصین به فکر راه چاره افتادند. پس از ریاضی و ادبیات، این بار

نوبت فلسفه بود.

 

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

 

فصلم / شی گرایی 301

رهیافت جدیدی بود که برای مشکلات بالا راه حل داشت. این « شی گرایی 1 »

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

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

مثل اسم، رنگ، وزن، حجم و ... . همچنین هر شی رفتارهای شناخته شده ای نیز دارد

مثلا در برابر نیروی جاذبه یا تابش نور یا وارد کردن فشار واکنش نشان می دهد. اشیا را

می توان با توجه به مشخصات و رفتار آن ها دسته بندی کرد. برای نمونه، می توانیم همۀ

قرار دهیم و همۀ « جانداران » هستند را در دست های به نام « تنفس » اشیایی که دارای رفتار

بگذاریم. بدیهی « جامدات » اشیایی که چنین رفتاری را ندارند در دستۀ دیگری به نام

است که اعضای هر دسته را می توانیم با توجه به جزییات بیشتر و دقیق تر به زیر

و « گیاهان » دسته هایی تقسیم کنیم. مثلا دستۀ جانداران را می توانیم به زیر دسته های

بخش بندی کنیم. البته هر عضو از این دسته ها، علاوه بر این که « انسان ها » و « جانوران »

مشخصاتی مشابه سایر اعضا دارد، مشخصات منحصر به فردی نیز دارد که این تفاوت

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

نام، سن، وزن، رنگ مو، رنگ چشم و مشخصات فردی دیگر است که باعث می شود

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

می گویند و به نمونه های هر کلاس « کلاس 2 » در بحث شی گرایی به دست هها

می نامند و به رفتارهای هر شی « صفت 4 » گفته می شود. مشخصات هر شی را « شی 3 »

می گویند. درخت سرو یک شی از کلاس درختان است که برخی از صف تهای « متد 5 »

آن عبارت است از: نام، طول عمر، ارتفاع، قطر و ... و برخی از متدهای آن نیز عبارتند

از: غذا ساختن، سبز شدن، خشک شدن، رشد کردن، ... .

اما این شی گرایی چه گرهی از کار برنام هنویسان می گشاید؟ برنام هنویسی شی گرا

بر سه ستون استوار است:

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

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

1 – Object orienting 2 – Class 3 – Object

4 – Attribute 5 – Method 6 – Encapsulation

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

کردن برنامه راح تتر صورت می گیرد و تغییر در یک بخش از برنامه، سایر بخش ها را

نیز می گویند « تجرید 1 » دچار اختلال نمی کند. مخفی کردن جزییات پیاده سازی که به آن

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

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

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

به جای « محصورسازی » یا « کپسوله کردن » دست کاری کند. در برخی از کتا بها از واژۀ

بسته بندی استفاده شده.

ب. وراثت 2: در دنیای واقعی، وراثت به این معناست که یک شی وقتی متولد

می شود، خصوصیات و ویژگی هایی را از والد خود به همراه دارد هرچند که این شیء

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

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

صفات و متدهای شیء والدش را دارا بوده والبته صفات و متدهای خاص خود را نیز

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

بر این که می توان از کدهای قبلی استفاده مجدد کرد، در زمان نیز صرفه جویی شده و

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

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

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

هر فرزندی مثل والدش اثر انگشت دارد، ولی اثر انگشت هر شخص با والدش یا هر

شخص دیگر متفاوت است. پس اثر انگشت در انسان ها چندشکلی دارد.

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

برنامه پیاده سازی کنیم.

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

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

شی نیز متغیری است که از نوع یک کلاس است. به طور کلی یک شی را می توان

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

1 – Abstraction 2 – Inheritance 3 – Polymorphism

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 303

خاص خودش را دارد. تعاریف کلاس مشخص می کند شیئی که از روی آن کلاس

ساخته می شود چه رفتاری دارد. واضح است که به منظور استفاده از شی گرایی، ابتدا

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

اشیایی از نوع این کلاس ها اعلان نماییم. بقیۀ برنامه را این اشیا پیش می برند.

9 اعلان کلاس ها ‐2

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

می شوند، اعداد کسری (گویا) هستند:

class Ratio

{ public:

void assign(int, int);

viod print();

private:

int num, den;

};

شروع می شود، سپس نام کلاس می آید و اعلان class اعلان کلاس با کلمۀ کلیدی

اعضای کلاس درون یک بلوک انجام می شود و سرانجام یک سمیکولن بعد از بلوک

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

نام دارد. می توانیم تشخیص بدهیم که در بلوک این کلاس دو تابع و دو متغیر Ratio

را تابع عضو 1 می گوییم زیرا آن ها print() و assign() اعلان شده است. توابع

نیز گفته شده است. « سرویس » یا « متد » ، عضو این کلاس هستند. به توابع عضو

را نیز دادۀ عضو 2 می گوییم. به غیر از توابع و داد ههای عضو، den و num متغیرهای

هر . private و عبارت public دو عبارت دیگر نیز به چشم می خورد: عبارت

محسوب می شود « عضو عمومی 3 » اعلان شود، یک public عضوی که ذیل عبارت

محسوب « عضو خصوصی 4 » اعلان شود، یک private و هر عضوی که ذیل عبارت

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

1 – Function member 2 – Data member

3 – Public member 4 – Private member

 

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

کلاس در خارج از کلاس قابل دستیابی هستند اما اعضای خصوصی فقط در داخل

« مخفی سازی اطلاعات » همان کلاس قابل دستیابی هستند. این همان خاصیتی است که

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

مشخص شد هاند. private

حال ببینیم که چطور می توانیم از کلا سها در برنامۀ واقعی استفاده کنیم. به مثال

زیر دقت کنید.

Ratio 9 پیاده سازی کلاس ‐ * مثال 1

class Ratio

{ public:

void assign(int, int);

void print();

private:

int num, den;

};

int main()

{ Ratio x;

Ratio y;

x.assign(13, 7);

y.assign(19,5);

cout << "x = ";

x.print();

cout << endl;

cout << "y = ";

y.print();

cout << endl;

}

void Ratio::assign(int numerator, int denumirator)

{ num = numerator;

den = denumirator;

}

فصلم / شی گرایی 305

void Ratio::print()

{ cout << num << '/' << den;

}

x = 13/7

y = 19/5

اعلان شده. سپس در برنامۀ اصلی دو متغیر Ratio در برنامۀ بالا، ابتدا کلاس

اعلان شده اند. به متغیری که از نوع یک کلاس باشد Ratio از نوع y و x به نام های

داریم. هر یک از این y و x می گوییم. پس در برنامۀ بالا دو شی به نا مهای « شی » یک

اشیا دارای دو دادۀ خصوصی مخصوص به خود هستند و همچنین توانایی دستیابی به

دو تابع عضو عمومی را نیز دارند. این دو شی را

می توان مانند شکل مقابل تصور نمود.

موجب x.assign( عبارت ;( 13,7

برای شیء assign() می شود که تابع عضو

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

ابتدا نام شی و سپس یک نقطه و پس از آن، تابع عضو مورد نظر. در این صورت، شیء

x شیء x.assign( مذکور را مالک 1 فراخوانی می نامیم. در عبارت ;( 13,7

مالک فراخوانی تابع عضو

است و این تابع assign()

عملیاتی انجام می x روی شیء

دهد. تصویر مقابل این موضوع را

بیان می کند.

در خطوط انتهایی برنامه آمده print() و assign() تعریف دو تابع عضو

به همراه عملگر جداسازی دامنه :: قبل از نام هر تابع ذکر شده Ratio اما نام کلاس

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

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

9 را تفسیر کنیم. ابتدا کلاس ‐ با توجه به توضیحات بالا، می توانیم برنامۀ مثال 1

num

den

13

7

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

 

Ratio Ratio

num

den

13

7

y

assign()

print()

Ratio

num

den

13

7

Ratio

x

1 – Owner

 

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

از روی این کلاس ساخته y و x اعلان شده است. در برنامۀ اصلی دو شیء Ratio

هستند. با den و num شده که هر کدام دارای دو عضو دادۀ خصوصی به نا مهای

مقادیر 7 و 13 به ترتیب درون اعضای خصوصی x.assign( فراخوانی ;( 13,7

مقادیر y.assign( قرار می گیرد. با فراخوانی ;( 19,5 x از شیء den و num

قرار می گیرد. y از شیء den و num 19 و 5 به ترتیب درون اعضای خصوصی

مقادیر اعضای خصوصی y.print(); و x.print(); سپس با فراخوانی

کاملا از y و x در خروجی چاپ می شود. دقت کنید که دو شیء y و شیء x شیء

یکدیگر مجزا هستند و هر کدام داده های خاص خود را دارد.

از اعضای خصوصی هستند، نمی توانیم درون den و num چون متغیرهای

و assign() برنامۀ اصلی به آن ها مستقیما دستیابی کنیم. این کار فقط از طریق توابع

میسر است زیرا این توابع عضوی از کلاس هستند و می توانند به اعضای print()

خصوصی همان کلاس دستیابی کنند.

که در بالا اعلان شد، کارایی زیادی ندارد. می توانیم با افزودن Ratio کلاس

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

Ratio 9 افزودن توابع عضو بیشتر به کلاس ‐ * مثال 2

class Ratio

{ public:

void assign(int, int);

double convert();

void invert();

void print();

private:

int num, den;

};

int main()

{ Ratio x;

x.assign(22, 7);

cout << "x = ";

x.print();

فصلم / شی گرایی 307

cout << " = " << x.convert() << endl;

x.invert();

cout << "1/x = "; x.print();

cout << endl;

}

void Ratio::assign(int numerator, int denumirator)

{ num = numerator;

den = denumirator;

}

double Ratio::convert()

{ return double(num)/den;

}

void Ratio::invert()

{ int temp = num;

num = den;

den = temp;

}

void Ratio::print()

{ cout << num << '/' << den;

}

x = 22/7 = 3.14286

1/x = 7/22

کارایی اشیای کلاس invert() و convert() در مثال بالا با افزودن توابع عضو

بهبود یافته است. این دو تابع عضو نیز به صورت اعضای عمومی تعریف Ratio

عدد convert() شده اند تا بتوان در برنامۀ اصلی به آن ها دستیابی داشت. تابع

نیز invert() کسری درون شیء مالک را به یک عدد اعشاری تبدیل می کند و تابع

عدد کسری درون شیء مالک را معکوس می نماید.

می بینید که به راحتی می توانیم قابلیت های یک برنامۀ شی گرا را بهبود یا تغییر

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

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

تعریف public را نیز به صورت den و num می توانستیم اعضای داده ای

کنیم تا بتوانیم درون برنامۀ اصلی به صورت مستقیم به این اعضا دستیابی کنیم اما اصل

پنهان سازی اطلاعات این امر را توصیه نمی کند. این اصل می گوید که تا حد امکان

داده های یک کلاس را به صورت خصوصی تعریف کنید و دستیابی به آن ها را به توابع

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

و از تغییرات ناخواسته در امان می مانند.

به شکل خودکفا Ratio 9 اعلان کلاس ‐ * مثال 3

را نشان می دهد که تعریف توابع عضو نیز درون Ratio کد زیر، اعلان کلاس

همان کلاس قرار گرفته است:

class Ratio

{ public:

void assign(int n, int d) { num = n; den = d; }

double convert() { return double(num)/den; }

void invert() { int temp = num; num = den; den = temp;}

void print() { cout << num << '/' << den; }

private:

int num, den;

};

9 مقایسه نمایید. در اعلان فوق، ‐ در مثال 2 Ratio اعلان فوق را با اعلان کلاس

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

9 باشد ‐ جداسازی دامنه :: نیست. ممکن است اعلان مذکور خواناتر از اعلان مثال 2

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

جداسازی دامنه و تعریف های خارج از کلاس برای توابع عضو استفاده کنند. در

حقیقت بدنۀ توابع اغلب در فایل جداگانه ای قرار می گیرد و به طور مستقل کامپایل

می شود. این با اصل پنهان سازی اطلاعات همسویی بیشتری دارد. در پروژه های گروهی

تولید نرم افزار، معمولا پیاده سازی کلاس ها به عهدۀ مفسرین است و استفاده از کلاس ها

در برنامۀ اصلی بر عهدۀ برنامه نویسان گذاشته می شود. برنامه نویسان فقط مایلند بدانند

که کلاس ها چه کارهایی می توانند بکنند و اصلا علاق های ندارند که بدانند کلا سها

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 309

چطور این کارها را انجام می دهند (نباید هم بدانند). برای مثال برنامه نویسان می دانند در

دستور تقسیم یک عدد اعشاری بر یک عدد اعشاری دیگر، حاصل چه خواهد بود اما

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

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

ندارند و این مطالب اصلا برای آ نها اهمیت ندارد. فقط کافی است از صحیح بودن

نتیجه مطمئن باشند. با رعایت کردن اصل پنهان سازی اطلاعات، هم تقسیم کارها بین

افراد گروه بهتر انجام می شود و هم از درگیرکردن برنامۀ اصلی با جزئیات نامربوط

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

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

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

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

می شود.

9 سازنده ها ‐3

برای assign() 9 اعلان شد از تابع ‐ که در مثال 1 Ratio کلاس

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

را برای آن شی فرا بخوانیم تا بتوانیم مقادیری را به اعضای assign() باید تابع

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

می توانیم هم زمان با اعلان یک متغیر، آن را مقداردهی اولیه کنیم مثل :

int n=22;

float x=33.0;

نیز به همین شیوه مقداردهی اولیه Ratio منطقی تر خواهد بود اگر بتوانیم برای کلاس

این امکان را فراهم کرده که برای مقداردهی اولیه به اشیای یک C++ . تدارک ببینیم

کلاس، از تابع خاصی به نام تابع سازنده 3 استفاده شود. تابع سازنده یک تابع عضو

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

1 – Interface 2 - Implementation 3 – Constructor Function

 

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

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

از یک تابع سازنده استفاده کنیم. assign() چطور می توانیم به جای تابع

Ratio 9 ایجاد تابع سازنده برای کلاس ‐ * مثال 4

class Ratio

{ public:

Ratio(int n, int d) { num = n; den = d; }

void print() { cout << num << '/' << den; }

private:

int num, den;

};

int main()

{ Ratio x(13,7) , y(19,5);

cout << "x = ";

x.print();

cout << "and y = ";

y.print();

}

x = 13/7 and y = 19/5

اعلان شد، تابع سازنده به طور خودکار فراخوانی x در کد بالا به محض این که شیء

آن ارسال می شود. تابع این مقادیر را به d و n شده و مقادیر 13 و 7 به پارامترهای

تخصیص می دهد. لذا اعلان den و num اعضای داده ای

Ratio x(13,7), y(19,5);

9 معادل است: ‐ با خطوط زیر از مثال 1

Ratio x, y;

x.assign(13,7);

y.assign(19,5);

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

و آن را مقداردهی نماید و با اجرای وظایفی که در تابع سازنده منظور شده، شیء

جدید را برای استفاده آماده کند.

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 311

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

5 را ببینید). این سازند هها، از طریق فهرست ‐ چندشکلی داشته باشد (بخش 13

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

Ratio 9 افزودن چند تابع سازندۀ دیگر به کلاس ‐ * مثال 5

class Ratio

{ public:

Ratio() { num = 0; den = 1; }

Ratio(int n) { num = n; den = 1; }

Ratio(int n, int d) { num = n; den = d; }

void print() { cout << num << '/' << den; }

private:

int num, den;

};

int main()

{ Ratio x, y(4), z(22,7);

cout << "x = ";

x.print();

cout << "\ny = ";

y.print();

cout << "\nz = ";

z.print();

}

x = 0/1

y = 4/1

z = 22/7

سه سازنده دارد: اولی هیچ پارامتری ندارد و شیء اعلان Ratio این نسخه از کلاس

شده را با مقدار پیش فرض 0 و 1 مقداردهی می کند. دومین سازنده یک پارامتر از نوع

دارد و شیء اعلان شده را طوری مقداردهی می کند که حاصل کسر با مقدار آن int

9 است. ‐ پارامتر برابر باشد. سومین سازنده نیز همان سازندۀ مثال 4

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

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

است که هیچ پارامتری ندارد. به این سازنده سازندۀ پیش فرض 1 می گویند. اگر در یک

کلاس، سازندۀ پیش فرض ذکر نشود، کامپایلر به طور خودکار آن را برای کلاس مذکور

9 که سازندۀ پیش فرض منظور نکرده ایم، یکی به طور ‐ ایجاد می کند. در مثال 1

خودکار برای آن کلاس منظور خواهد شد.

9 فهرست مقداردهی در سازنده ها ‐4

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

یک واحد دستوری مخصوص پیش بینی شده که تولید C++ نمی دهند. به همین دلیل در

سازنده را تسهیل می نماید. این واحد دستوری فهرست مقداردهی 2 نام دارد.

9 دقت کنید. این سازنده را می توانیم با استفاده از ‐ به سومین سازنده در مثال 5

فهرست مقداردهی به شکل زیر خلاصه کنیم:

Ratio(int n, int d) : num(n), den(d) { }

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

اکنون درون فهرست مقداردهی جای داده شده اند (فهرست مقداردهی با حروف تیره تر

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

Ratio نیز در انتها می آید (که اکنون خالی است). در مثال بعدی، سازنده های کلاس

را با فهرست مقداردهی خلاصه کرده ایم.

Ratio 9 استفاده از فهرست مقداردهی در کلاس ‐ * مثال 6

class Ratio

{ public:

Ratio() : num(0) , den(1) { }

Ratio(int n) : num(n) , den(1) { }

Ratio(int n, int d) : num(n), den(d) { }

private:

int num, den;

};

1 – Default constructor 2 – Initializing list

 

فصلم / شی گرایی 313

سازنده ها را می توانیم از این هم ساد هتر کنیم. می توانیم با استفاده از پارامترهای

پیش فرض، این سه سازنده را با هم ادغام کنیم. به مثال بعدی توجه کنید.

Ratio 9 به کار گیری پارامترهای پیش فرض در سازندۀ کلاس ‐ * مثال 7

class Ratio

{ public:

Ratio(int n=0, int d=1) : num(n), den(d) { }

private:

int num, den;

};

int main()

{ Ratio x, y(4), z(22,7);

}

4 و / برابر با 1 y 0 و شیء / برابر با 1 x در این مثال وقتی که برنامه اجرا شود، شیء

22 خواهد شد. / برابر با 7 z شیء

5 گفتیم که وقتی پارامترهای واقعی به تابع ارسال نشود، به ‐ قبلا در بخش 15

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

از x دارای مقدار پیش فرض 1 است. وقتی شیء d دارای مقدار پیش فرض 0 و پارامتر

و x.n و بدون هیچ پارامتری اعلان می شود، این مقادیر پیش فرض به Ratio نوع

خواهد بود. هنگامی که x.den= و 1 x.num= تخصیص می یابند. بنابراین 0 x.d

خواهد شد ولی چون y.num= فقط با مقدار ارسالی 4 اعلان می شود، 4 y شیء

قرار می گیرد. y.den ذکر نشده، مقدار پیش فرض 1 در y پارامتر دوم در اعلان شیء

با دو پارامتر ارسالی 22 و 7 اعلان شده. پس در این شی مقادیر پیش فرض z شیء

برابر با 7 خواهد شد. z.den برابر با 22 و z.num نادیده گرفته شده و

9 توابع دستیابی ‐5

اعلان (private) داده های عضو یک کلاس معمولا به صورت خصوصی

می شوند تا دستیابی به آ نها محدود باشد اما همین امر باعث می شود که نتوانیم در

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

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

توابع دستیابی 1 استفاده می کنیم. تابع دستیابی یک تابع عمومی عضو کلاس است و به

همین دلیل اجازۀ دسترسی به اعضای داده ای خصوصی را دارد. از طرفی توابع دستیابی

را طوری تعریف می کنند که فقط مقدار اعضای داده ای را برگرداند ولی آن ها را تغییر

ندهد. به بیان ساده تر، با استفاده از توابع دستیابی فقط می توان اعضای داده ای خصوصی

را خواند ولی نمی توان آن ها را دس تکاری کرد.

Ratio 9 افزودن توابع دستیابی به کلاس ‐ * مثال 8

class Ratio

{ public:

Ratio(int n=0, int d=1) : num(n) , den(d) { }

int numerator() { return num; }

int denomerator() { return den; }

private:

int num, den;

};

int main()

{ Ratio x(22,7);

cout << x.numerator() << '/' << x.denumerator() << endl;

}

مقادیر موجود در داد ههای denumerator() و numerator() در این جا توابع

عضو خصوصی را نشان می دهند.

9 توابع عضو خصوصی ‐6

تاکنون توابع عضو را به شکل یک عضو عمومی کلاس اعلان کردیم تا بتوانیم در

برنامۀ اصلی آن ها را فرا بخوانیم و با استفاده از آ نها عملیاتی را روی اشیا انجام دهیم.

توابع عضو را گاهی می توانیم به شکل یک عضو خصوصی کلاس معرفی کنیم. واضح

است که چنین تابعی از داخل برنامۀ اصلی به هیچ عنوان قابل دستیابی نیست. این تابع

1 – Access function

 

فصلم / شی گرایی 315

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

تابع سودمند 1 محلی می گوییم.

9 استفاده از توابع عضو خصوصی ‐ * مثال 9

class Ratio

{ public:

Ratio(int n=0, int d=1) : num(n), den(d) { }

void print() { cout << num << '/' << den << endl; }

void printconv() { cout << toFloat() << endl; }

private:

int num, den;

double toFloat();

};

double Ratio::toFloat()

{ // converts Rational number to Float

return num/den;

}

int main()

{ Ratio x(5, 100);

x.print();

x.printconv();

}

5/100

0.05

toFloat() دارای یک تابع عضو خصوصی به نام Ratio در برنامۀ بالا، کلاس

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

استفاده شده و به انجام وظیفۀ printconv() این تابع فقط درون بدنۀ تابع عضو

آن کمک می نماید و هیچ نقشی در برنامۀ اصلی ندارد. یادآوری می کنیم که چون تابع

toFloat() عضوی از کلاس است، می تواند به تابع خصوصی printconv()

دستیابی داشته باشد.

1 – Utility function

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

توابعی که فقط به انجام وظیفۀ سایر توابع کمک می کنند و در برنامۀ اصلی هیچ

کاربردی ندارند، بهتر است به صورت خصوصی اعلان شوند تا از دسترس سایرین در

امان بمانند.

9 سازندۀ کپی ‐7

می دانیم که به دو شیوه می توانیم متغیر جدیدی تعریف نماییم:

int x;

int x=k;

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

که k مقدار موجود در متغیر x همین کار انجام می گیرد با این تفاوت که پس از ایجاد

است. k یک کپی از x کپی می شود. اصطلاحا x از قبل وجود داشته درون

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

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

Ratio x;

اعلان می شود. حال ببینیم چطور Ratio از نوع کلاس x در تعریف بالا، شی

می توانیم به شیوۀ دوم یک کپی از شیء موجود ایجاد کنیم. برای این کار از تابع عضوی

به نام سازندۀ کپی 1 استفاده می کنیم. این تابع نیز باید با نام کلاس هم نام باشد ولی

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

این پارامتر باید هم نوع کلاس مذکور باشد. این پارامتر، همان شیئی است که می خواهیم

از روی آن کپی بسازیم. علت این که پارامتر مذکور به طریقۀ ارجاع ثابت ارسال

می شود این است که شیئی که قرار است کپی شود نباید توسط این تابع قابل تغییر

باشد. کدهای زیر هر دو تابع سازندۀ پیش فرض و سازندۀ کپی را نشان می دهد:

Ratio(); // default constructor

Ratio(const Ratio&); // copy constructor

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

1 – Copy constructor

 

فصلم / شی گرایی 317

استفاده از این تابع، از روی یک شیء موجود یک کپی بسازیم:

Ratio y(x);

را x ایجاد می کند و تمام مشخصات شیء Ratio از نوع y کد بالا یک شی به نام

درون آن قرار می دهد. اگر در تعریف کلاس، سازندۀ کپی ذکر نشود (مثل همۀ

کلاس های قبلی) به طور خودکار یک سازندۀ کپی پیش فرض به کلاس افزوده خواهد

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

روی برنامه تان داشته باشید.

Ratio 9 افزودن یک سازندۀ کپی به کلاس ‐ * مثال 10

class Ratio

{ public:

Ratio(int n=0, int d=1) : num(n), den(d) { }

Ratio(const Ratio& r) : num(r.num), den(r.den) { }

void print() { cout << num << '/' << den; }

private:

int num, den;

};

int main()

{ Ratio x(100,360);

Ratio y(x);

cout << "x = ";

x.print();

cout << ", y = ";

y.print();

}

x = 100/360, y = 100/360

می بینید که در تعریف تابع سازندۀ کپی نیز می توان از فهرست مقداردهی پیش فرض

و num استفاده کرد. در مثال بالا، تابع سازندۀ کپی طوری تعریف شده که عنصرهای

به درون عنصرهای متناظر در شیء جدید کپی شوند. دستور r از پارامتر den

ساخته شده و سازندۀ کپی فرا خوانده y باعث می شود که شیء Ratio y(x);

کپی شوند. y درون x شود تا مقادیر موجود در شیء

 

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

سازندۀ کپی در سه وضعیت فرا خوانده می شود:

1 – وقتی که یک شی هنگام اعلان از روی شیء دیگر کپی شود

2 – وقتی که یک شی به وسیلۀ مقدار به یک تابع ارسال شود

3 – وقتی که یک شی به وسیلۀ مقدار از یک تابع بازگشت داده شود

برای درک این وضعیت ها به مثال زیر نگاه کنید.

9 دنبال کردن فراخوانی های سازندۀ کپی ‐ * مثال 11

class Ratio

{ public:

Ratio(int n=0, int d=1) : num(n), den(d) { }

Ratio(const Ratio& r) : num(r.num), den(r.den)

{ cout << "COPY CONSTRUCTOR CALLED\n"; }

private:

int num, den;

};

Ratio test(Ratio r) // calls the copy constructor, copying ? to r

{ Ratio q = r; // calls the copy constructor, copying r to q

return q; // calls the copy constructor, copying q to ?

}

int main()

{ Ratio x(22,7);

Ratio y(x); // calls the copy constructor, copying x to y

f(y);

}

COPY CONSTRUCTOR CALLED

COPY CONSTRUCTOR CALLED

COPY CONSTRUCTOR CALLED

COPY CONSTRUCTOR CALLED

بدنۀ سازندۀ کپی در برنامۀ بالا شامل یک پیغام است که هر وقت سازندۀ کپی

فراخوانی شود، با چاپ آن پیغام آگاه شویم که سازندۀ کپی فراخوانی شده. همان طور

که خروجی برنامه نشان می دهد، سازندۀ کپی چهار بار در برنامۀ بالا فراخوانی شده:

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 319

کپی می کند. y را درون x اعلان می شود، فراخوانی شده و y ‐ هنگامی که

y ارسال می شود، فراخوانی شده و test() به وسیلۀ مقدار به تابع y ‐ هنگامی که

کپی می کند. r را به درون

کپی می کند. q را به درون r اعلان می شود، فراخوانی شده و q ‐ هنگامی که

بازگشت داده می شود، فراخوانی test() به وسیلۀ مقدار از تابع q ‐ هنگامی که

می شود. حتی اگر چیزی را جایی کپی نکند.

ظاهری شبیه عمل جایگزینی دارد اما این کد در حقیقت Ratio q=r; دستور

است. Ratio q(r); سازندۀ کپی را فراخوانی می کند و درست شبیه دستور

اگر یک سازندۀ کپی در تعریف کلاستان نگنجانید، به طور خودکار یک سازندۀ

کپی برای آن منظور می شود که این سازنده به شکل پیش فرض تمام اطلاعات موجود

در شیء جاری را به درون شیء تازه ساخته شده کپی می کند. اغلب اوقات این همان

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

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

اجرای برنامه، این اشار هگر را به خان های از حافظه اشاره می دهید. حال اگر از این شی

یک کپی بسازید بدون این که از سازندۀ کپی مناسبی استفاده کنید، شیء جدید نیز به

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

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

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

9 نابود کننده ‐8

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

فراخوانی می شود. وقتی که شی به پایان زندگی اش برسد، تابع عضو دیگری به طور

خودکار فراخوانی می شود تا نابودکردن آن شی را مدیریت کند. این تابع عضو،

« منهدم کننده » یا « تخریب گر » نابودکننده 1 نامیده می شود (در برخی از کتا بها به آن

1 – Destructor

 

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

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

وظیفه دارد آن منابع را آزاد کند.

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

باشد با این تفاوت که یک علامت نقیض ~ به آن پیشوند شده. مثل تابع سازنده و

سازندۀ کپی، اگر نابود کننده در تعریف کلاس ذکر نشود، به طور خودکار یک

نابودکنندۀ پیش فرض به کلاس افزوده خواهد شد.

Ratio 9 افزودن یک نابودکننده به کلاس ‐ * مثال 12

class Ratio

{ public:

Ratio() { cout << "OBJECT IS BORN.\n"; }

~Ratio() { cout << "OBJECT DIES.\n"; }

private:

int num, den;

};

int main()

{ { Ratio x; // beginning of scope for x

cout << "Now x is alive.\n";

} // end of scope for x

cout << "Now between blocks.\n";

{ Ratio y;

cout << "Now y is alive.\n";

}

}

OBJECT IS BORN.

Now x is alive.

OBJECT DIES.

Now between blocks.

OBJECT IS BORN.

Now y is alive.

OBJECT DIES.

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

کلاس متولد شده یا می میرد، از تولد و مرگ آن آگاه شویم. خروجی نشان می دهد که

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 321

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

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

برخورد main() پایان بلوک محلی برسد می میرد. یک شیء ثابت وقتی به پایان تابع

شود، می میرد. شیئی که درون یک تابع تعریف شده، در پایان آن تابع می میرد.

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

خوب، توابع سازنده و سازندۀ کپی و نابودکننده را خودش در تعریف کلاس هایش

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

9 اشیای ثابت ‐9

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

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

ثابت آشنا را نشان می دهند:

const char BLANK = ' ';

const int MAX_INT = 2147483647;

const double PI = 3.141592653589793;

void int(float a[], const int SIZE);

به صورت یک شیء ثابت اعلان کرد: const اشیا را نیز می توان با استفاده از عبارت

const Ratio PI(22,7);

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

print() عضو را برای اشیای ثابت فراخوانی کنید. مثلا در مورد کد فوق گرچه تابع

نمی توانیم آن را فراخوانی PI است اما در مورد شیء ثابت Ratio عضوی از کلاس

کنیم:

PI.print(); // error: call not allowed

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

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

تعریف کنیم. برای این که یک تابع const اشیای ثابت کار کنند را باید به صورت

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

را بین فهرست پارامترها و تعریف بدنۀ const این چنین تعریف شود، کلمۀ کلیدی

را به شکل زیر تغییر Ratio در کلاس print() آن قرار می دهیم. مثلا تعریف تابع

می دهیم:

void print() const { cout << num << '/' << den << endl; }

اکنون می توانیم این تابع را برای اشیای ثابت نیز فراخوانی نماییم:

const Ratio PI(22,7);

PI.print(); // o.k. now

9 اشاره گر به اشیا ‐10

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

می تواند اشیای داده ای متنوع و متفاوتی داشته باشد، اشاره گر به اشیا بسیار سودمند و

مفید است. بهتر است قبل از مطالعۀ مثال های زیر، فصل هفتم را مرور کنید.

9 استفاده از اشار هگر به اشیا ‐ * مثال 13

class X

{ public:

int data;

};

main()

{ X* p = new X;

(*p).data = 22; // equivalent to: p->data = 22;

cout << "(*p).data = " << (*p).data << " = " << p->data << endl;

p->data = 44;

cout << " p->data = " << (*p).data << " = " << p->data << endl;

}

(*p).data = 22 = 22

p->data = 44 = 44

است و x یک شیء *p است. پس x اشاره گری به شیء p ، در این مثال

آن *p دادۀ عضو آن شی را دستیابی می کند. حتما باید هنگام استفاده از (*p).data

را درون پرانتز قرار دهید زیرا عملگر انتخاب عضو (.) تقدم بالاتری نسبت به عملگر

نوشته شود، کامپایلر *p.data مقداریابی (*) دارد. اگر پرانتزها قید نشوند و فقط

تفسیر خواهد کرد که این باعث خطا می شود. *(p.data) این خط را به صورت

 

فصلم / شی گرایی 323

هر دو به p->data و (*p).data این مثال نشان می دهد که دو عبارت

استفاده کنند p->data یک معنا هستند. بیشتر برنام هنویسان ترجیح می دهند از ترکیب

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

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

Node 9 فهرست های پیوندی با استفاده از کلاس ‐ * مثال 14

به کلاسی که در زیر اعلان شده دقت کنید:

class Node

{ public:

Node(int d, Node* p=0) : data(d), next(p) { }

int data;

Node* next;

};

تعریف می کند که اشیای این کلاس دارای دو عضو Node عبارت بالا کلاسی به نام

است و دیگری یک اشاره گر از نوع همین int داده ای هستند که یکی متغیری از نوع

کلاس. شاید عجیب باشد که عضوی از کلاس به شیئی از نوع همان کلاس اشاره کند

اما این کار واقعا ممکن است و باعث می شود بتوانیم یک شی را با استفاده از همین

از s و r و q اشاره گر به شیء دیگر پیوند دهیم و یک زنجیره بسازیم. مثلا اگر اشیای

باشند، می توانیم پیوند این سه شی را به صورت زیر مجسم کنیم: Node نوع

به تابع سازنده نیز دقت کنید که چطور هر دو عضو داده ای شیء جدید را

مقداردهی می کند. اکنون این کلاس را در برنامۀ زیر به کار می گیریم:

int main()

{ int n;

Node* p;

Node* q=0;

while (cin >> n)

int data

q

Node* next

int data

r

int data

s

Node* next Node* next324 برنامه سازی پیشرفته

{ p = new Node(n, q);

q = p;

}

for ( ; p->next; p = p->next)

cout << p->data << " -> ";

cout << "*\n";

}

22 33 44 55 66 77 ^d

77 -> 66 -> 55 -> 44 -> 33 -> *

به یک q ساخته می شود که q و p به نام Node در این برنامه، ابتدا دو اشاره گر از نوع

پس از اولین ورودی، حافظۀ جدیدی برای while شیء خالی اشاره دارد. در حلقۀ

قرار p از اشاره گر data منظور می شود و عدد وارد شده در عضو داده ای p شیء

به p می شود. یعنی عضو اشاره گر q برابر با next می گیرد و همچنین عضو داده ای

به شیء q قرار می گیرد. حالا q در شیء p اشاره می کند. سپس آدرس شیء q حافظۀ

به یک شیء خالی. p اشاره دارد و p

منظور می شود و p پس از دومین ورودی، مجددا حافظۀ جدیدی برای

به q به حافظۀ موجود قبلی اشاره می کند و p انتساب های فوق تکرار می شود تا این که

اشاره می کند. شکل زیر روند اجرای برنامه را نشان می دهد. p شیء

0

q

next

0

q

next

data

p

next

0

q

next

data

next

data

p

next

الف - قبل از

شروع حلقه

ب - پس از اولین

تکرار حلقه

ج - پس از دومین

تکرار حلقه

 

 

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

 

فصلم / شی گرایی 325

ر ا فشار ندهد، حلقه ادامه (Ctrl+Z) تا زمانی که کاربر کاراکتر پایان فایل

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

وظیفۀ پیمایش فهرست پیوندی را دارد. به این صورت که تا for می شود. حلقۀ

وقتی

نباشد، حلقه ادامه می یابد و عضو داد های گره فعلی را چاپ NUL برابر با p->next

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

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

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

ساختمان » و الگوریتم ها و مزایای آن به شاخۀ مستقلی در برنامه نویسی تبدیل شده و

نام گرفته است. به اختصار می گوییم که اشاره گر به اشیا برای ساختن « داده ها 1

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

پردازش های سریع مثل جستجو در فهرست های طولانی (مانند فرهنگ لغات) یا

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

ساختمان داده ها مراجعه کنید.

9 اعضای داده ای ایستا ‐11

هر وقت که شیئی از روی یک کلاس ساخته می شود، آن شی مستقل از اشیای

دیگر، داده های عضو خاص خودش را دارد. گاهی لازم است که مقدار یک عضو

داده ای در همۀ اشیا یکسان باشد. اگر این عضو مفروض در همۀ اشیا تکرار شود، هم از

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

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

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

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

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

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

class X

{ public:

1 – Data structure 2 – Static member

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

static int n; // declaration of n as a static data member

};

int X::n = 0; // definition of n

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

مقداردهی کرد.

متغیرهای ایستا به طور پیش فرض با صفر مقداردهی اولیه می شوند. بنابراین

مقداردهی صریح به این گونه متغیرها ضروری نیست مگر این که بخواهید یک مقدار

اولیۀ غیر صفر داشته باشید.

9 یک عضو داده ای ایستا ‐ * مثال 15

اعلان می کند که این کلاس یک عضو داد های widget کد زیر، کلاسی به نام

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

ساخته می شود، از طریق سازنده مقدار widget می دارد. هر وقت که یک شیء

نابود می شود، از widget یک واحد افزایش می یابد و هر زمان که یک شیء count

یک واحد کاهش می یابد: count طریق نابودکننده مقدار

class Widget

{ public:

Widget() { ++count; }

~Widget() { --count; }

static int count;

};

int Widget::count = 0;

main()

{ Widget w, x;

cout << "Now there are " << w.count << " widgets.\n";

{ Widget w, x, y, z;

cout << "Now there are " << w.count << " widgets.\n";

}

cout << "Now there are " << w.count << " widgets.\n";

Widget y;

cout << "Now there are " << w.count << " widgets.\n";

}

فصلم / شی گرایی 327

Now there are 2 widgets.

Now there are 6 widgets.

Now there are 2 widgets.

Now there are 3 widgets.

درون بلوک داخلی ایجاد شده است. widget توجه کنید که چگونه چهار شیء

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

ها از 6 به 2 تقلیل می یابد. widget کل

یک عضو داده ای ایستا مثل یک متغیر معمولی است: فقط یک نمونه از آن

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

که عضو داده ای ایستا عضوی از کلاس است، می توانیم آن را به شکل یک عضو

خصوصی نیز اعلان کنیم.

9 یک عضو داده ای ایستا و خصوصی ‐ * مثال 16

class Widget

{ public:

Widget() { ++count; }

~Widget() { --count; }

int numWidgets() { return count; }

private:

static int count;

};

int Widget::count = 0;

main()

{ Widget w, x;

cout << "Now there are " << w.numWidgets() << " widgets.\n";

{ Widget w, x, y, z;

cout << "Now there are " << w.numWidgets() << " widgets.\n";

}

cout << "Now there are " << w.numWidgets() << " widgets.\n";

Widget y;

cout << "Now there are " << w.numWidgets() << " widgets.\n";

}

 

328 برنامه سازی پیشرفتهmohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

به count 9 کار می کند با این تفاوت که متغیر ایستای ‐ این برنامه مانند مثال 15

شکل یک عضو خصوصی اعلان شده

و به همین دلیل به تابع دستیابی

نیاز داریم تا numWidgets()

بتوانیم درون برنامۀ اصلی به متغیر

دسترسی داشته باشیم. count

را مانند مقابل تصور کنیم: w و y و x و اشیای Widget می توانیم کلاس

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

9 توابع عضو ایستا ‐12

count با دقت در مثال قبلی به دو ایراد بر می خوریم: اول این که گرچه متغیر

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

برای خواندن آن استفاده کرد هایم. این باعث می شود که مجبور w در مثال قبلی از شیء

شویم همیشه مواظب باشیم عضو ایستای مفروض از طریق یک شی که الان موجود

است فراخوانی شود. مثلا در مثال قبلی اگر در قسمتی از برنامه، دور از چشم ما، شیء

از آن به بعد مخاطره آمیز خواهد w.numWidgets() نابود شود، آنگاه فراخوانی w

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

را دستیابی کنیم. برای رفع این دو ایراد کافی است تابع دستیابی کننده را نیز به count

شکل ایستا تعریف کنیم.

9 یک تابع عضو ایستا ‐ * مثال 17

کد زیر همان کد مثال قبلی است با این فرق که در این کد، تابع دستیابی کننده

نیز به شکل ایستا اعلان شده است:

class Widget

{ public:

Widget() { ++count; }

~Widget() { --count; }

static int num() { return count; }

Widget()

~Widget()

numWidgets()

count 3

Widget

x

y

w

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 329

private:

static int count;

};

int Widget::count = 0;

int main()

{ cout << "Now there are " << Widget::num() << " widgets.\n";

Widget w, x;

cout << "Now there are " << Widget::num() << " widgets.\n";

{ Widget w, x, y, z;

cout << "Now there are " << Widget::num() << " widgets.\n";

}

cout << "Now there are " << Widget::num() << " widgets.\n";

Widget y;

cout << "Now there are " << Widget::num() << " widgets.\n";

}

به صورت ایستا تعریف شود، از اشیای کلاس مستقل می شود و num() وقتی تابع

برای فراخوانی آن نیازی به یک شیء موجود نیست و می توان با کد

به شکل مستقیم آن را فراخوانی کرد. Widget::num()

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

اکیدا توصیه می کنیم که قبل از مطالعۀ فصل بعدی، هم ۀ تمرین های پایان این فصل را

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

که شی گرایی را مفیدتر می کنند.

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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

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

1 – کدام گزینه از مزایای شی گرایی نیست؟

الف – وراثت ب – چندریختی ج – بست هبندی د – نمونه سازی

2 – در اعلان یک کلاس، کدام گزینه صحیح است؟

استفاده می شود private الف – برای معرفی اعضای عمومی کلاس از عبارت

استفاده می شود public ب – برای معرفی اعضای خصوصی کلاس از عبارت

ج – توابع و متغیرها هر دو می توانند عضو یک کلاس باشند

استفاده می شود funct د – برای معرفی اعضای تابعی کلاس از عبارت

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

الف – به اعلان کلاس، رابط کلاس گفته می شود

ب – به بدنۀ کلاس، پیاده سازی کلاس گفته می شود

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

د – به تابعی که عضو یک کلاس باشد، تابع دستیابی گفته می شود

4 – از گزینه های زیر، کدام صحیح است؟

الف – هر کلاس فقط یک سازنده و فقط یک نابودکننده دارد

ب – هر کلاس فقط یک سازنده دارد و می تواند چند نابودکننده داشته باشد

ج – هر کلاس فقط یک نابودکننده دارد و می تواند چند سازنده داشته باشد

د – هر کلاس می تواند چند سازنده و چند نابودکننده داشته باشد

5 – تابع دستیابی چیست؟

الف – یک تابع عضو عمومی کلاس است که به یک دادۀ عضو عمومی دستیابی دارد

ب – یک تابع عضو خصوصی کلاس است که به یک دادۀ عضو خصوصی دستیابی دارد

ج – یک تابع عضو عمومی کلاس است که به یک دادۀ عضو خصوصی دستیابی دارد

د – یک تابع عضو خصوصی کلاس است که به یک داده عضو عمومی دستیابی دارد

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 331

داشته باشیم آنگاه کدام تابع زیر، سازندۀ کپی را برای vector 6 – اگر کلاسی به نام

این کلاس اعلان می کند؟

vector(const vector&) – ب vector() – الف

vector*(const vector) – د ~vector() ‐ ج

7 – سازندۀ کپی وقتی فراخوانی می شود که:

الف – یک شی به وسیلۀ مقدار به یک تابع فرستاده شود

ب – یک شی به وسیلۀ ارجاع به یک تابع فرستاده شود

ج – یک شی به وسیلۀ ارجاع ثابت به یک تابع فرستاده شود

د – یک شی به وسیلۀ ارجاع از یک تابع بازگشت داده شود

8 – اگر در تعریف یک کلاس، سازندۀ کپی ذکر نشود آنگاه:

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

ب – اشیای آن کلاس را نمی توان به تابع فرستاد

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

د – یک سازندۀ کپی پیش فرض به طور خودکار به کلاس افزوده می شود

به شکل محلی اعلان شده باشد، آنگاه : f() در تابع مفروض x 9 – اگر شیء

می میرد main() ایجاد می شود و در انتهای تابع x شیء main() الف – با شروع تابع

می میرد f() ایجاد می شود و در انتهای تابع x شیء main() ب – با شروع تابع

می میرد f() ایجاد می شود و در انتهای تابع x شیء f() ج – با شروع تابع

می میرد main() ایجاد می شود و در انتهای تابع x شیء f() د – با شروع تابع

10 – کدام گزینه در مورد کلاس ها صحیح است؟

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

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

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

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

 

 

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

اشیایی از x و 2 x بوده و 1 vector یک عضو ایستا برای کلاس k 11 – اگر متغیر

باشند، آنگاه: vector کلاس

فقط یک نمونه در سراسر برنامه موجود است. k الف – از

خاص خود را دارند k هر کدام عضو x و 2 x ب – 1

استفاده می کند k از همان x است و 2 k دارای عضو x ج – فقط 1

استفاده می کند k از همان x است و 1 k دارای عضو x د – فقط 2

12 – کدام گزینه در مورد اعضای ایستای کلاس، صحیح نیست؟

مشخص می شوند static الف – اعضای ایستا با کلمۀ کلیدی

ب – اعضای ایستا می توانند عضو عمومی کلاس باشند

ج – اعضای ایستا می توانند عضو خصوصی کلاس باشند

اعلان می شوند static: د – اعضای ایستا در بخش

در کدام تابع عضو کلاس استفاده می شود؟ « فهرست مقداردهی » ‐ 13

الف – تابع سودمند محلی ب – تابع نابودکننده

ج – تابع سازنده د – تابع دستیابی

media m2=m باشد، آنگاه با اجرای کد ; 1 media از کلاس m 14 – اگر شیء 1

کدام تابع عضو کلاس فراخوانی می شود؟

الف – تابع سازنده ب – تابع سازندۀ کپی

ج – تابع دستیابی د – تابع سودمند محلی

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

فصلم / شی گرایی 333

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

1‐ تفاوت بین یک عضو عمومی و یک عضو خصوص ی از یک کلاس را توضی ح

دهید.

2‐ تفاوت بین رابط کلاس و پیاده سازی کلاس را توضی ح دهید.

3‐ تفاوت بین تابع عضو کلاس و تابع کاربردی را توضی ح دهید.

4‐ تفاوت بین سازنده و نابودکننده را توضی ح دهید.

5‐ تفاوت بین سازندۀ پیش فرض و سازنده های دیگر را توضی ح دهید.

6‐ تفاوت بین سازندۀ کپی و عملگر جایگزین ی را توضی ح دهید.

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

8‐ نام تابع سازنده چگونه باید باشد؟

9‐ نام تابع نابودکننده باید چگونه باشد؟

10 ‐ هر کلاس چه تعداد سازنده م یتواند داشته باشد؟

11 ‐ هر کلاس چه تعداد نابودکننده می تواند داشته باشد؟

12 ‐ چگونه و چرا از عملگر جداسازی حوزه :: در تعریف کلاس ها استفاده

می شود؟

13 ‐ کدام تابع عضو به طور خودکار توسط کامپایلر ایجاد م یشود اگر برنام هنویس آن

را صریحا در تعریف کلاس نگنجانیده باشد؟

14 ‐ در کد زیر چند دفعه سازندۀ کپی فراخوانی م یشود؟

Widget f(Widget u)

{ Widget v(u);

Widget w = v;

return w;

}

main()

{ Widget x;

Widget y = f(f(x));

}

 

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

وجود پرانتزها ضروری است؟ (*P).data 15 ‐ چرا در عبارت

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

پیاده سازی کنید. یک (x, y, z) را برای نقاط سه بعدی Point 1‐ کلاس

تا نقطۀ مورد نظر را منفی negate() سازندۀ پیش فرض، یک سازندۀ کپی، یک تابع

0) و یک تابع , 0, برای برگرداندن فاصله از مبداء ( 0 norm() کند، یک تابع

به این کلاس اضافه کنید. print()

پیاده سازی کنید. یک سازندۀ int را برای پشته هایی از نوع stack 2‐ کلاس

pop() و push() پیش فرض، یک نابودکننده و توابع اجرای عملیات معمول پشته

را به این کلاس اضافه کنید. از آرایه ها برای این isFull() و isEmpty() و

پیاده سازی استفاده کنید.

را پیاد هسازی کنید. هر شی از این کلاس، یک زمان ویژه از روز را Time 3‐ کلاس

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

برای جلو advance(int h,int m,int s) یک سازنده، توابع دستیابی، تاب ع

برای reset(int h,int m,int s) بردن زمان فعلی یک شی ء موجود، تابع

به این کلاس اضافه کنید. print() نو کردن زمان فعلی یک شی ء موجود و یک تابع

را برای تولید کردن اعداد شبه تصادفی پیاده سازی کنید. Random 4‐ کلاس

را پیاده سازی کنید. هر شی از این کلاس، نمایان گر یک انسان person 5‐ کلاس

است. اعضای داد های این کلاس باید شامل نام شخص، سال تولد و سال وفات باشد.

به این print() یک تابع سازندۀ پیش فرض، نابودکننده، توابع دستیابی و یک تابع

کلاس اضافه کنید.

2 پیاده سازی کنید: × را برای آرایه های 2 Matrix 6‐ کلاس

⎥⎦

⎢⎣

c d

a b

که معکوس آرایه را inverse() یک سازندۀ پیش فرض، یک سازندۀ کپی ، یک تابع

که دترمینان آرایه را برمی گرداند، یک تابع بولی det() برمی گرداند، یک تابع

 

فصلم / شی گرایی 335

که بسته به این که دترمینان صفر باشد یا نه مقدار یک یا صفر را isSingular()

به این کلاس اضافه کنید. print() برمی گرداند و یک تابع

پیاده سازی کنید. یک سازندۀ (x, y) برای نقاط دو بعدی point 7‐ یک کلاس

برای تبدیل نقطۀ مورد نظر به negate() پیش فرض، یک سازندۀ کپی، یک تابع

برای برگرداندن فاصلۀ نقطه از مبداء ( 0,0 ) و یک تابع norm() منفی، یک تابع

به این کلاس اضافه کنید. print()

را پیاده سازی کنید. هر شی در این کلاس یک دایره را نشان Circle 8‐ کلاس

نگهداری float از مرکز را به صورت y و x می دهد که شعاع آن و مختصات

و یک تابع area() می کند. یک سازندۀ پیش فرض، توابع دستیابی، یک تابع

که محیط دایرۀ مذکور را برمی گرداند، به این کلاس اضافه circumference()

کنید.

به آن، تغییر دهید. تابع count() در مسألۀ 2 را با افزودن تابع Stack 9‐ کلاس

مذکور تعداد اقلام درون پشته را برمی گرداند.

تغییر دهید. تابع print() در مسألۀ قبل را با افزودن تابع Stack 10 ‐ کلاس

مذکور محتویات پشته را چاپ می کند.

،int در مسألۀ قبل را طوری تغییر دهید که به جای مقادیر نوع Stack 11 ‐ کلاس

را نگهداری کند. float مقدارهای

area() در مسألۀ 8 را طوری تغییر دهید که شامل تابع Circle 12 ‐ کلاس

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

3 را × در مسألۀ 6 را طوری تغییر دهید که آرایه های 3 Matrix 13 ‐ کلاس

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

mohsen_mahyar@yahoo.com

mo-mah.persianblog.ir

 

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