جلسات گذشته با مفاهیم اولیه ی شئ گرایی آشنا شدیم این مفاهیم در عین سادگی مفاهیم بسیار پایه ای هستن که به فهم راحت مطالب جلسات آینده کمک بسیاری می کنند. پس بهتر است قبل از مطالعه ی این جلسه مروری بر جلسه ی قبل داشته باشید هر چند مطالب ارائه شده در این جلسه وابستگی چندانی به مطالب پیشین ندارد. اما همین ارتباط کم اندک اندک گسترده شده و مشکلاتی را ایجاد می کند
اشاره گر اشیا:
تمامی مفاهیم اشاره گر اشیا مانند اشاره گر استراکچر ها یا همان ساختمان هاست
به تعریف یک اشاره گر از کلاس MyClass توجه نمایید
MyClass *ptrMyObject;
همانطور که می بینید این تعریف هیچ فرقی با تعریف یک اشاره گر استراکچر و یا حتی یک نوع اولیه مثلا intندارد
int *ptrMyInt;
دقت کنید که تا اینجا فقط یک اشاره گر تعریف شده و هیچ شی ساخته نشده ! همچنین این اشاره گر هیچ گونه مقداردهی اولیه نشده پس به محل نامشخصی از حافظه اشاره می کند. در واقع گویی که به یک شئ اشاره می کند.شئ که وجود ندارد! در صورت تلاش برای دستیابی به اعضا این شئ نتایج نامعلومی چون مختل شدن عملکر برنامه و... خواهد دارد. پس بهتر است در هنگام تعریف یک اشاره گر حتما یک مقدار اولیه به آن بدهید مثلا آدرس یک شی را به آن انتساب دهید که این شئ می تواند از قبل مو جود بوده باشد یا ایجاد شود یا حتی مقدار NULL را در آن قرار دهید.
مانند دستور زیر
MyClass *ptrMyObject = NULL;
در دستور زیر آدرس شئ MyObject که از قبل و جود داشته با عملگر & بدست آمده و در اشاره گر قرار گرفته
MyClass *ptrMyObject = &MyObject;
به نحوه ی ایجاد شی توسط عملگر new و نحوه ی انتساب آن به اشاره گر توجه کنید
MyClass *ptrMyObject = new MyClass;
یک شئ از نوع کلاس MyClass توسط عملگر newایجاد شده و آدرس آن در اشاره گر قرار می گیرد به یاد داشته باشید که اگر عملگر newبه هر دلیلی موفق به تخصیص حافظه و ایجاد شئ نشود مقدار NULLرا بر می گرداند پس باید قبل از استفاده از اشاره گر با یک دستور شرطی از و جود شئ اطمینان حاصل کنیم.
به این صورت
MyClass *ptrMyObject = new MyClass;
if(ptrMyObject==NULL)
{
cout<<”Can not allocate memory”<
}
else
{
سایر دستور ها برای اجرا در صورت تخصیص حافظه و وجود شئ
}
البته روش دیگر برای کنترل این خطا فراخوانی تابع exit(1) در صورت NULLبودن اشاره گر است که در هدر فایل های stdlib.hوprocess.hتعریف شده است. این تابع موجب پایان اجرا برنامه می شود که برای کنترل خطا در برنامه های بزرگ چندان جالب نیست آرگمان این تابع وقتی یک باشد به سیستم عامل می گوید که اجرا برنامه در اثر خطایی متوقف شد و مقدار صفر برای این آرگمان می گوید که اجرا برنامه به صورت عادی متوقف شده
نکته:
بهتر است همیشه اشاره گرها یا به یک شئ موجود اشاره کنند یا حاوی مقدار NULL باشند. تا زمینه های بروز خطا کاهش یابد پس مثلا هر گاه شئی را با عملگر delete از بین می برید بهتر است تمام اشاره گر هایی را که به آن شئ اشاره میکنند را یا با آدرس شئ دیگری مقدار دهی کنید یا با مقدارNULL .
مانند:
delete ptrToMyObject;
ptrToMyObject = NULL;
آرگمان توابع سازنده را هم به صورت
MyClass *ptrMyObject = new MyClass(arg1,…,argn);
می توان ارسال کرد.
مانند
MyClass *ptrMyObject = new MyClass(1,2);
و اما دسترسی به اعضای اشیائ از طریق اشاره گر به همان روش استراکچر ها انجام می شود.
(*ptrMyObject).member
یا
ptrMyObject->member
که روش دوم روشی ساده و بسیار مرسوم است در این جا منظور از member هر نوع عضو از جمله توابع و فیلدهای شئ هستند اگر کلاسمان همان کلاس اعداد کسری جلسات قبل باشد دسترسی به فیلد a(صورت کسر) و تابع GetAsFloat();به صورت زیر است
ptrMyObject->a;
ptrMyObject->GetAsFloat();
آرایه ای از اشیا:
همانطور که می توانیم آرایه ای از انواع اولیه یا استراکچرها داشته باشیم می توانیم آرایه ای از اشیا نیز داشته باشیم
MyClass MyObjectsArray[size];
اما مشکلی که وجود داره اینه که در تعریف آرایه ای از اشیا نمی توان مقدار دهی اولیه به اشیا داشته باشیم! در نتیجه باید کلاسمون حتما یه تابع سازنده ی بدون آرگمان داشته باشه تا با خطای کامپایلری روبرو نشیم.
روش بالا روش تعریف آرایه ای باطول ثابت هست اما با استفاده از عملگر های newو delete می توان آرایه هایی از اشیا با طولی که در حین اجرا برنامه مشخص میشود ایجاد نمود. می دانیم که نام آرایه به تنهایی و بدون [n] یک اشاره گر است به خانه ی اول آرایه پس کافی است یک اشاره گر تعریف کنیم و سپس با عملگر new آرایه مورد نظرمان را ایجاد کنیم و با توجه به این که مقدار برگشتی از این عملگر آدرس اولین خانه ی آرایه ی ایجاد شده است این مقدار را به اشاره گرمان نسبت دهیم .
MyClass *MyObjectsArray = new MyClass[arraysize];
بعد از این می توانیم با این اشاره گر همچون یک آرایه ی که به روش قبل ایجاد شده بود برخورد کنیم.البته در این روش هم نمی توانیم مقدار دهی اولیه به اشیا داشته باشیم
لازم به یاد آوریست که دسترسی به اعضا اشیا در آرایه ی از اشیا به صورت(مانند آرایه ای از استراکچرها)
MyObjectsArray[index].member;
انجام می شود.
توابع دوست:
گاهی اوقات لازم می شود که تابعی غیر از توابع عضو کلاس به اعضاء خصوصی یک کلاس(شئ) دسترسی داشته باشد. شاید عجیب به نظر بیاید! پس کپسوله بودن و پنهانسازی اطلاعات چی میشه؟! با همه ی این حرف ها گاهی اوقات نیاز به چنین توابعی به وجود میاد که یه نمونش سربار گذاری بعضی عملگر هاست که در جلسات آینده در موردش صحبت می کنیم.
تعریف:
توابع دوست توابعی هستند که عضو کلاس نبوده اما به تمامی اعضاء کلاس دسترسی دارند.
نکته:
با توجه به عضو نبودن توابع دوست این توابع دارای اشاره گر thisنمی باشند پس برای دسترسی به اشیاء باید اشیاء را از طریق آرگمان های تابع به تابع فرستاد.
برای تعریف تابع دوست باید ابتدا تابع را مانند یک تابع معمولی که عضو هیچ کلاسی نیست, خارج از بدنه ی کلاس تعریف نمود سپس این تابع را درون بدنه ی کلاس و با قرار دادن کلمه ی کلیدی friend در ابتدای اعلان تابع, اعلان نمود.
می دونین جمع کردن یک مثال خوب و کار بردی برای توابع دوست یکمی مشکله مجبوریم به یه مثال نه چندان جالب اکتفا کنیم.
می خوایم یه تابع دوست یه کلاس اعداد کسری که جلسات قبل تعریف کردیم اضاف کنیم این تابع کارش اینه که یه فیلد private رو از کلاس دومی که تعریف می کنیم با یک عدد کسری مقایسه کنه و در صورت تساوی عدد یک را برگرداند.
اعلان پیشروclass MyClass2; //forward declarion
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);
friend int IsEqual(MyClass obj1, MyClass2 obj2);
};
class MyClass2{
private:
int b;
public:
int Setb(int tmpb);
int Getb();
friend int IsEqual(MyClass obj1, MyClass2 obj2);
};
int IsEqual(MyClass obj1, MyClass2 obj2)
{
if((obj1.a/obj1.b)==obj2.a)
return 1;
return 0;
}
احتمالا عجیب ترین قسمت این قطعه کد اولین خط آن است به این خط می گویند اعلان پیشرو علت استفاده از این تعریف اینه که در تعریف کلاس اعداد کسری از کلاس MyClass2 استفاده شده که خودش هنوز تعریف نشده بنا براین از تعریف پیشرو استفاده می کنیم تا کامپایلر بدونه که در ادامه 2MyClass تعریف خواهد شد.
همانطور که می بینید تابع IsEqualدر بدنه ی هر دو کلاس به عنوان دوست اعلان شده سپس مانند یک تابع معمولی با دو آرگمان تعریف شده. برای اجرای این تابع هم هیچ تفاوتی با توابع معمولی دیگر که عضوی از هیچ کلاسی نیستند و حتی تابع دوست هم نیستند و جود ندارد. تنها تفاوت در دسترسی به تمام اعضا کلاسهایی هست که این تابع بعنوان دوست آنها معرفی شده. به فراخوانی این تابع توجه کنید:
IsEqual(obj1, obj2);
نکته:
یک تابع می تواند دوست یک یا چندین کلاس باشد.
این مثال فقط برای درک مطلب بود احتمالا متوجه کاربردی نبودن آن شده اید!
تا جایی که من می دونم در زبان های کاملا شئ گرا چیزی به نام تابع دوست وجود نداره چون اصولا در این زبان ها توابع باید حتما عضوی از کلاس ها باشند در مقابل زبان های خانواده ی C که بیشتر به زبان C نزدیک هستن بهشون می گن ساختیافته شئ گرا! به این معنی که توابع می تونن عضو هیچ کلاسی نباشن خب در چنین وضعی طبیعیه که مفهومی مثل تابع دوست هم بوجود بیاد!
در کنار این مفهومی داریم به عنوان کلاس دوست.
کلاس دوست:
اگر دو کلاس a,b داشته باشیم و کلاس b در بدنه ی کلاس a به عنوان دوست اعلان شده باشد آنگاه کلاس b چون دوست کلاس a است به تمام اعضا کلاس a از جمله اعضا خصوصی دسترسی دارد. به نحوه ی تعریف مطالب گفته شده توجه کنید.
class a
{
اعضا کلاس// members
friend class b;
}
class b
{
اعضا کلاس// members
}
مفهوم کلاس ها و توابع دوست در کپسوله سازی و پنهان سازی اطلاعات بسیار مفید است که در جلسات آینده با مثال های واقعی با آنها آشنا می شویم.چرا که مثال های کوچک و غیر عملی در این رابطه چندان حق مطلب را ادا نمی کند.
اعضا استاتیکstatic :
در حالات عادی هر شئ یک کپی از تمام فیلد های داده ای کلاس دارد اما گاهی پیش می آید که می خواهیم فقط یک کپی از برخی فیلد ها وجود داشته باشد و این کپی بین تمام اشیا مشترک باشد. در نتیجه هر تغییری که در آن فیلد از طریق یکی از اشیا کلاس مذکور ایجاد می شود در تمامی اشیا منعکس می شود. نکته ی جالبتر این که بدون نیاز به ایجاد یک شئ از کلاس می توانیم به این گونه فیلدها دسترسی داشته باشیم. به این فیلدها , فیلد ها ی static می گویند . برای ایجاد یک فیلد استاتیک کافیست که کلمه ی static را به ابتدای تعریف فیلد در کلاس اضاف کنیم .
class MyClass
{
public:
static int a;
}
خب این تفاوت دستوری فیلد های استاتیک بود اما تفاوت فنی اینه که وقتی یک شئ از کلاس مورد نظر ساخته میشه حافظه ای به فیلد استاتیک تخصیص داده نمی شه و به عبارتی این فیلد هیچ وقت به وجود نمی آید پس باید به روشی این فیلد را ایجاد کنیم به این صورت که فیلدهای استاتیک را در محلی خارج از تمام توابع برنامه (معمولا بلا فاصله قبل از تابع main() با استفاده از نام کلاس و عملگر :: شبیه به یک متغیر عمومی تعریف می کنند.
مانند:
ایجاد و تخصیص حافظه به فیلد استاتیک int MyClass::a;//
void main()
{
دسترسی به فیلد استاتیک از طریق نام کلاسMyClass::a=23;//
MyClass obj;
دسترسی به فیلد استاتیک از طریق شئ ایجاد شده//obj.a=24;
return 0;
}
نکته:
فیلد های استاتیک به صورت پیش فرض دارای مقدار اولیه ی صفر هستند.
ایجاد و تخصیص حافظه به فیلد استاتیک و مقدار دهی اولیه int MyClass::a=1;//
void main()
{
MyClass::a=23;
MyClass obj;
.
.
{.
اگر به تعریف فیلد a در MyClass دقت کرده باشید متوجه شدید که این فیلد به صورت عمومی تعریف شده.پس فیلد های استاتیک خصوصی (private) هم می توان تعریف نمود. کلیت قضیه کاملا مثل همان فیلد های استاتیک عمومی هست با این تفاوت که سطح دسترس به این گونه فیلدها خصوصی یا (private) است
به عبارتی دیگر تنها تفاوت یک فیلد استاتیک خصوصی با یک فیلد معمولی خصوصی مشترک بودن آن بین تمام اشیا است در نتیجه فقط توابع عضو , توابع دوست , کلاس های دوست اجازه ی دسترسی به این فیلد ها را دارند
به مثال توجه کنید.
class staticMembers
{
public:
static int puba;
private:
static int prva;
};
int staticMembers::puba=1;
int staticMembers::prva=1;
int main(){
clrscr();
staticMembers obj;
cout<<staticMembers::puba<
cout<
دسترسی غیر مجاز به فیلد استاتیک خصوصی//cout<<staticMembers::prva<
دسترسی غیر مجاز به فیلد استاتیک خصوصی //cout<
getch();
return 0;
}
همانطور که می بینید به فیلد های استاتیک خصوصی می توان در دستور مربوط به ایجاد این فیلد مقدار اولیه داد. اما بعد از آن دیگر نمی توان به این فیلد در خارج از کلاس دسترسی داشت
نکته:
درخارج از کلاس دسترسی داشت یعنی این دسترسی در خارج از توابع عضو یا توابع دوست کلاس ویا کلاس های دوست (خود تون رو با این چیزها گیج نکنید عضو خصوصی یعنی این که هیچ چیزی اجازه دسترسی به اون عضو رو نداره مگر این که خودش عضو باشه یا رابطه ی دوستی با کلاسمون داشته باشه البته در این بین توابع استاتیک که تا لحظاتی دیگر تو ضیح می دهم فقط با وجود این که عضو کلاس هستند فقط به اعضا استاتیک دسترسی دارند و ...)
برای رفع این مشکل(عبارت زرد رنگ بالاJ) می توان توابع دسترسی که قبلا برای اعضا خصوصی تعریف می کردیم تعریف کرد. اما مشکل دیگر اینست که برای دسترسی به فیلد استاتیک خصوصی مجبور می شویم حتما از طریق یک شئ این کار را انجام دهیم یعنی اینکه باید حتما یک شئ داشته باشیم تا بتوانیم از طریق فرا خوانی توابع دسترسی, که عضو شئ هستند به مقصودمان برسیم.اما می توانیم این مشکل را با توابع استاتیک عمومی حل کنیم
توابع استاتیک:
توابعی هستند که
- تعریفشان با کلمه ی کلیدی staticشروع می شود
- فاقد اشاره گر thisهستند
- فقط به اعضا استاتیک کلاس دسترسی دارند
- با توجه به عدم وجود اشاره گر this می توان برای دسترسی یه اشیا مانند توابع دوست برایشان آرگمان هم نوع با کلاس تعریف نمود.در واقع چون این توابع عضو کلاس هستند در صورت دسترسی به یک شی از نوع کلاسی که عضوش هستند و به هر طریقی که باشد به تمام اعضا آن شی دسترسی دارند
نحوه ی تعریف تابع استاتیک:
class MyClass
{
private:
static int prva;
public:
static int get_prva();
}
int MyClass::get_prva()
{
دسترسی به فیلد استاتیک خصوصی// return prva;
توابع استاتیک می توانند خصوصی یا عمومی باشند.
از نظر مهندسی نرم افزار استفاده بی مورد و بیش از اندازه از اعضا استاتیک در طراحی کلاسها نامناسب می باشد.
نمونه ای کوچک کار برد اعضا استاتیک:
حتماٌ با مفهوم سیستم پنجره ای و پنجره ی فعال آشنا هستید همه ی سیستم عامل
های امروزی ازسیستم های پنجره ای استفاده می کنند در حال حاضر شما دارید این مطلب رو از درون یک پنجره می خوانید حتما این را هم می دانید که در هر لحظه پیام های صفحه کلید و سایر ورودی ها فقط برای یک پنجره فرستاده می شود که به آن پنجره فعال می گویند که در شرایط معمول پنجره ای است که برروی تمام پنجره ها قرار دارد.اگر بخواهیم چنین سیستمی را پیاده سازی کنیم می توانیم کلاسی به عنوان کلاس پنجره داشته باشیم این کلاس می تواند در هر لحظه دسترسی به پنجره ی فعال را میسر سازد به ما امکان حرکت بین پنجره هارا بدهد و خودش تمام وظایف ترسیمات مجدد پنجره ها را انجام دهد و مثلا تعداد پنجره های موجود را هم بداند. خب کاری که باید انجام داد استفاده از یک فیلد استاتیک خصوصی برای ذخیره ی اشاره گری به پنجره ی فعال هست و.... فکر می کنم این مثال خیلی خوبی باشه که جلسه ی آینده حلش می کنیم تا از دنیای پر قدرت و ساده ی شی گرایی لذت ببریم فکر نمی کنم الا بتونین لذتی رو که می گم حس کنید اما وقتی این مثال جلوی چشماتون حل بشه حتما احساسش می کنید و لحظاتی در حیرت خواهید ماند! J
با توجه به تئوری بودن بیش از اندازه ی این جلسه و به منظور یادگیری بهتر جلسه ی آینده به ساخت و بررسی چند کلاس مفید از جمله ایجاد کلاس String(البته کلاسی بااین نام در هدر فایل string.h وجود داره اما دلیل نمیشه که این تمرین رو انجام ندیم)برای کار ساده تر با رشته ها می پردازیم تا ضمن تمرین ,مقدماتی برای مطالب آینده فراهم شود.
یه پیش نهاد سعی کنید کلاسی برای کار بارشته ها با توانایی های زیر ایجاد کنید.
1.تابعی برای الحاق رشته.
2.تابعی برای درج زیر رشته در رشته ی موجود
3.تابعی برای حذف یک کاراکتر از رشته در محلی از رشته.
4.تابعی برای چاپ رشته
5.تابعی برای انتساب رشته(از طریق آرگمان تابع)
تخصیص حافظه به صورت پویا باشد
از آرایه ی کاراکتری برای ذخیره ی رشته استفاده شود.



