تبليغاتX
C++ آموزش - جلسه ی هفدهم (شی گرایی قسمت دوم _ آشنایی با تابع سازنده ی کپی ,تابع مخرب و اعضاء public, private)

جلسه ی قبل با نحوه ی ساخت یک کلاس ساده و نمونه سازی(ساخت اشیاء) از آن کلاس آشنا شدیم و کلاسی با نام MyClass  برای کار با اعداد کسری ساختیم و در مورد توابع سازنده و مقدار دهی اولیه به فیلدها از طریق توابع سازنده و مفهوم اشاره گر this را بررسی کردیم.

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

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

 

تعریف و مقدار دهی اولیهMyClass n1(1,2) , n2(1,4);

دستور انتساب n1=n2;

 

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

MyClass::MyClass( MyClass& tmp )

{

اعمال لازم برای ایجاد کپی

}

نکته:

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

 

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

n1.MyClass(n2);

پس کافی است در بدنه ی این تابع  فیلدهای مورد نیاز را از شئ n2که حالا در آرگمان تابع در دسترس قرار دارد بگیریم و در فیلد های متناظر شئn1کپی کنیم.

 

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

class MyClass{

public:

تابع سازندهMyClass(int tmpa ,int tmpb);

تابع سازنده ی کپیMyClass( MyClass& tmp );

int a,b;

float GetAsFloat();

void InverseIt();

void Add(MyClass tmp);

};

 

بدنه ی تابع سازندهMyClass::MyClass(int tmpa ,int tmpb)

{

a=tmpa;

b=tmpb;

}

بدنه ی تابع سازنده ی کپی MyClass::MyClass(( MyClass& tmp)

{

a=tmp.a;

b=tmp.b;

}

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

بدنه ی تابع سازنده ی کپی MyClass::MyClass(( MyClass& tmp)

{

a=tmp.a;

b=tmp.b;

c=new int;

*c=*tmp.c;

}

تابه حال تمام توابع را خارج از بدنه ی کلاس می نوشتیم اما روش دیگری هم وجود دارد که در بدنه ی کلاس به جای گذاشتن سمی کالن(;) در انتهای اعلان تابع همان جا

بدنه ی تابع را هم تعریف می کنند این شیوه فقط برای توابع کوچک مناسب است چون در هنگام کامپیال شدن کامپایلر هر جا نیاز به فراخوانی این توابع باشد یک نسخه از تابع مذکور را کپی می کند در حالی که در حالت عادی کامپایل به گونه ای انجام می شود که توابع در محل مورد نیاز فراخوانی می شوند پس این شیوه ی تعریف مستقيم در خود بدنه ی کلاس که به تعریف inlineمعروف است موجب افزایش حجم برنامه می شود و از کارایی می کاهد. در هر حال این روش برای توابع کوچک که کم فراخوانی میشوند می تواند باعث بهبود سرعت شود چون نیاز به فراخوانی نیست.

به نحوه ی تعریف تابع سازنده توجه کنید.

class MyClass{

public:

تعریف مستقیم بدنه ی تابع در بدنه ی کلاسMyClass(int tmpa ,int tmpb)

{

a=tmpa;

b=tmpb;

}

تابع سازنده ی کپیMyClass( MyClass& tmp );

.

.

.

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

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

class MyClass{

public:

MyClass(int tmpa ,int tmpb) : a(tmpa) , b(tmpb) {//you can insert some code} تابع سازنده ی کپیMyClass( MyClass& tmp );

.

.

.

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

class MyClass{

public:

تابع سازنده//

MyClass(int tmpa ,int tmpb) : a(tmpa) , b(tmpb) {//you can insert some code}

تابع سازنده ی کپی//

MyClass( MyClass& tmp ) : a(tmp.a) , b(tmp.b) {//you can insert some code}

.

.

.

تابع مخرب:

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

class MyClass{

public:

تعریف الگوی تابع سازندهMyClass();

MyClass(int tmpa, int tmpb);

تعریف الگوی تابع مخرب~MyClass();

.

.

;{.

تعریف بدنه ی تابع مخرب

MyClass::~MyClass()

{

کد های مربوط به آزاد سازی منابع مصرف شده

}

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

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

نکته:

اشیاء در زمان های گوناگونی از بین می روند واضحترین زمان هنگامیست که با عملگر delete شئی را که با عملگر newایجاد شده از بین می بریم. موردی دیگر زمانیست که شئی که درون یک تابع  به صورت تعریف مستقیم (بدون استفاده از عملگر new)ایجاد شده بعد از پایان اجرا تابع از بین می رود دو واقع زمان خاتمه ی حیات یک شئ مثل همان دوره ی حیات متغیر های انواع اولیه هست به این معنی که تحت هر شرایطی که مثلا یک متغیر intاز بین میرود اگر آن متغییر یک شئ بود باز هم از بین می رفت.

 

اعضاء خصوصی و عمومی(public,private):

خب بر می گردیم به کلاسی که تعریف کردیم در این کلاس تمام اعضا به صورت publicتعریف شده اند به این معنی که در هر جای برنامه اگر شئی از این کلاس داشته باشیم که فرضا نام آن obj باشد می توان با عملگر نقطه یه هر عضوی از آن دسترسی پیدا کرد این موضوع در مواردی می تواند مشکلاتی ایجاد کند برای مثال در قطعه کد زیر

MyClass obj(1,2);

obj.b=0;

cout<

در این کد کاربر ابتدا مخرج را صفر نموده و بعد می خواهد عدد ممیز شناور معادل آن را بگیرد می دانیم که تابع GetAsFloat(); این کار را با تقسیم a/b انجام می دهد نتیجه این که برنامه باید 1را بر صفر تقسیم کند که برنامه را با مشکل جدی روبرو می کند. راه حل جلوگیری از دسترسی مستقیم کاربر به مخرج است به این صورت که مخرج را به صورت عضو privateتعریف می کند چنین اعضایی از درون توابع عضو قابل دسترسی اند اما در خارج از کلاس غیر قابل دسترسی هستند. و تلاش برای دسترسی به آنها با خطای کامپایلری مواجه می شود! سپس یک تابع عمومی می نویسیم که کاربر با فراخوانی آن و ارسال مقدار به آرگمان آن به مخرج مقدار دهی میکند. نتیجه این که در بدنه ی تابع قبل از انتساب هر عددی به مخرج می توانیم بررسی کنیم که عدد مذکور صفر نباشد. و جلوی صفر شدن مخرج را بگیریم. به این مفهوم پنهان سازی کد یا اطلاعات  می گویند

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

}نام کلاسclass

public:

اعلان توابع عضو عمومی

اعلان فیلد ها (متغیر های عضو)عمومی

private:

اعلان توابع عضو خصوصی

اعلان فیلد ها (متغیر های عضو) خصوصی

};

نکته:

اینکه اول اعضاء خصوصی تعریف شوند یا عمومی هیچ فرقی ندارد حتی می توان چندین بار از کلمات کلیدی private,publicاستفاده نمود مثلا اول مقداری از اعضاء عمومی سپس خصوصی و دوباره مقداری از اعضا عمومی را تعریف کرد و حتی این روند را ادامه داد!

 

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

class MyClass{

private:

int b;

public:

int a;

MyClass(int tmpa ,int tmpb) : a(tmpa) , b(tmpb) { }

MyClass( MyClass& tmp ) : a(tmp.a) , b(tmp.b) { }

int Setb(int tmpb);

int Getb();

float GetAsFloat();

void InverseIt();

void Add(MyClass tmp);

};

int MyClass::Setb(int tmp)

{

if(tmpb!=0){

b=tmpb;     

return 1;      

}else  

return 0;      

}

int MyClass::Getb()

{

return b;      

}

به این گونه توابع , توابع دسترسی می گویند که معمولا با عناوین getوsetتعریف می شوند. توجه کنید که در تابع Setb()یک مقدار بازگشتی هم در نظر گرفته شده که تابع در صورت موفقیت انتساب مقدار یک و در صورت شکست مقدار صفر را بر می گرداند تا برنامه نویس بتواند متوجه شود که عمل اشتباهی صورت گرفته و اقدامات لازم را برای کنترل خطا انجام دهد علاوه بر فیلد ها یا متغیر های خصوصی توابع خصوصی هم قابل تعریف اند. معمولا توابعی که در عملکرد های داخلی توابع عضو فراخوانی میشوند و فراخوانی آنها خارج از کلاس و توسط کاربر(فردی که می خواهد در کد نویسی های خود از کلاس مذکور استفاده کند)ممکن است موجب اختلال عملکر کلاس شود, به صورت خصوصی تعریف می شوند.تا بیرون از کلاس در دسترس نباشد.

نکته:

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

 

جلسه ی آینده در مورد اشاره گر اشیا , ایجاد پویای اشیاء با عملگرهای new,delet و آرایه ی از اشیاه و همچنین توابع دوست بحث می کنیم.

+ نوشته شده توسط سجاد مهدی بیرقدار در سه شنبه شانزدهم بهمن 1386 و ساعت 1:38 |