آموزش برنامه نویسی شیءگرا به زبان ++C
ترجمه: مهندس وحید بیرانوند
[ خانه ] [ کتاب های من ] [ همراه آنلاین ]
معرفی کتاب:
بنده این کتاب را زمانی که دانشجوی سال سوم بودم ترجمه کرده و با هزاران مشقت به چاپ رساندم. به دلیل خاطرات تلخ و شیرین فراوانی که با این کتاب دارم و شب های زیادی که تا صبح به کار کردن روی آن پرداختم دلبستگی زیادی به آن دارم. گرچه عواملی که گفتنش در اینجا مناسب نیست سبب شد که این اثر از لحاظ چاپی خوب از آب در نیاید، لیکن برای من یک چیز دیگری است. برای استفاده شما بخشی از متن فارسی، این کتاب را در ادامه آورده ام. متن انگلیسی کامل آن را در قسمت همراه آنلاین مربوط به این کتاب می توانید پیدا کنید.
آشنایی با کلاس ها و اشیاء
برنامه هایی که در C++ می نویسیم، عمدتاً مشتمل بر تابع main، و یک یا چند کلاس می باشند که هر کدام از آن کلاس ها حاوی اعضای داده ای و توابع عضو می باشند. می دانیم که هر زبان برنامه نویسی دارای یک سری انواع داده ای درونکار می باشد. مثلاً در اینجا برنامه نویس می تواند از انواع داده ای int، char، و ... استفاده کند. به طور کلی در مورد یک نوع داده ای به صورت یک گروه طبقه ای خاص یا یک رده از پدیده های موجود در جهان بیاندیشید دقیقاً مشابه انواع و اقسام ماشین، اشخاص، میوه، و شکل های هندسی. در ++C، برنامه نویس قادر است تا هر نوع مورد نیاز خود را ایجاد کند. هر یک از این انواع جدید می تواند دارای عملکردها (توابع، متدها) و داده های درونکار (داده های عضو) مربوط به خود می باشد. برنامه ها معمولاً برای حل مسائل موجود در دنیای واقعی طراحی و نوشته می شوند، مانند شبیه سازی کار یک سیستم گرمایشی. گرچه با استفاده از انواع داده ای از پیش تعریف شده نیز می توان برنامه های پیچیده ای نوشت، لیکن اگر بتوان اشیاء و موجودیت هایی که در برنامه های پیچیده در مورد آنها صحبت می کنیم را به نوعی نمایش دهیم، آنگاه حل مسائل پیچیده و بزرگ، به مراتب ساده تر می شود. به عبارت دیگر شبیه سازی کار یک سیستم گرمایشی ساده تر خواهد بود اگر بتوانیم متغیرهایی ایجاد کنیم که بتوانند اجزاء این سیستم از قبیل اتاق ها، حسگرهای گرمایی، ترموستات، و دیگ های بخار را نشان دهند. مسلماً اینگونه متغیرها واقعی تر و ملموس تر بوده، و لذا برنامه نویسی ساده تر می شود.
کلاس ها
با اعلان و ایجاد یک کلاس (class)، در واقع یک نوع جدید ایجاد می گردد. یک کلاس درست شبیه به مجموعه ای از متغیرها (اغلب از انواع مختلف درونکار ++C) و توابعی است که روی این متغیرها عمل می کنند. برای درک ماجرا، یک اتوموبیل را در نظر بگیرید. اتوموبیل مجموعه ای از چرخ ها، درها، صندلی ها، پنجره ها، و ... می باشد. این اجزا را می توان به عنوان متغیرها در نظر گرفت. حال فکر کنید که اتوموبیل چه کارهایی انجام می دهد: می تواند حرکت کند، شتاب بگیرد، سرعتش را کم کند، توقف کند، و ... . این اعمال را می توان به عنوان رفتارها، یا عملکردها، یا توابع، یا متدهای اتوموبیل در نظر گرفت. یک کلاس، این توانایی را برای برنامه نویس مهیا می سازد تا این متغیرها و توابع مختلف را درون یک موجودیت واحد به نام شیء (object) قرار دهد. مسلماً طبقه بندی هر چیز، محاسن یادی برای برنامه نویس به همراه دارد. با طبقه بندی چیزها، دسترسی و رجوع به آنها ساده تر می شود. از طرف دیگر، سرویس گیرندگان کلاس (قسمت هایی از برنامه که از کلاس مورد نظر استفاده می کنند) بدون نیاز به دانستن عملکرد داخلی کلاس، می توانند از اشیاء کلاس استفاده کنند. یک کلاس می تواند شامل هر ترکیبی از انواع متغیرهای گوناگون و یا حتی کلاس های دیگر باشد. متغیرهای موجود در کلاس ها را متغیرهای عضو یا داده های عضو می نامند. توابع درون یک کلاس متغیرهای عضو آن کلاس را دستکاری می کنند. این توابع به عنوان توابع عضو یا متدهای کلاس شناخته شده هستند.
اعلان یک کلاس
ابتدا نحوه تعریف کلاس و توابع عضو را توضیح می دهیم. سپس شرح می دهیم که یک شیء چگونه ایجاد می شود و چگونه می توان تابع عضو یک شیء را فراخوانی نمود.
کلاس Cat: قبل از آنکه تابع main بتواند شیئی از کلاس Cat ایجاد نماید، می بایست به کامپایلر بگوییم که این کلاس دارای چه متغیرهای عضو و توابع عضوی می باشد. این فرآیند معرفی کلاس به کامپایلر را تعریف کلاس می نامیم. در زیر کلاسی با نام Cat اعلان شده است:
class Cat
{
public:
unsigned int itsAge;
unsigned int itsWeight;
Meow () ;
};
اعلان این کلاس، هیچ حافظه ای برای یک گربه تخصیص نمی دهد. این اعلان تنها به کامپایلر می گوید که یک گربه چه چیزی است، شامل چه داده هایی است (itsAge و itsWeight) و چه کاری انجام می دهد (()Meow). این اعلان همچنین برای کامپایلر روشن می کند که یک گربه چقدر بزرگ است (یعنی کامپایلر چه مقدار فضا برای گربه ای که ایجاد می کنید باید کنار بگذارد). در این مثال اگر نوع int چهار بایت اشغال کند، اندازه یک گربه 8 بایت خواهد بود، چرا که برای توابع عضو، فضایی اشغال نمی شود. مطمئناً شما با شنیدن وصف یک گربه قادر به نوازش آن نخواهید بود، بلکه تنها گربه هایی را نوازش می کنید که موجودیت خارجی دارند. بین تصور یک گربه، و گربه ای که هم اکنون اتاق شما را به هم می ریزد، تمایز قائل شوید.
تعریف یک شیء
یک شیء از کلاس جدید، به همان ترتیبی اعلان می شود که یک متغیر از انواع پیش ساخته:
unsigned int number;
Cat Tom; //define a Cat
این کد متغیری به نام number، همچنین شیئی از کلاس (یا از نوع) Cat با نام Tom تعریف می کند.
دسترسی به اعضای کلاس
هنگامیکه یک شیء واقعی از کلاس Cat ایجاد می کنید (مانند Tom)، برای دسترسی به اعضای آن شیء از عملگر نقطه (.) استفاده می شود. لذا برای تخصیص عدد 50 به داده عضو itsWeight از شیء Tom، به صورت زیر عمل کنید:
Tom.itsWeight = 50;
به همین ترتیب برای اینکه گربه شما میو میو کند باید تابع ()Meow را فراخوانی کنید. برای انجام این کار به صورت زیر عمل کنید:
Tom.Meow();
نکته: به خاطر داشته باشید که مقادیر به انواع داده ای منسوب نمی شوند، بلکه مقادیر به متغیرها انتساب می یابند. به عنوان مثال شما هر گز نمی نویسید:
int = 5;
چرا که کامپایلر خطا می گیرد. بنابراین، ابتدا می بایست یک متغیر تعریف کنید، سپس 5 را به آن تخصیص دهید. به این صورت:
int x;
x = 5;
به همین ترتیب در مورد کلاس ها و اشیاء، نمی توان به صورت زیر مقداری را به کلاس Cat تخصیص داد:
Cat.itsAge = 5;
کامپایلر از این دستور نیز خطا می گیرد. بنابراین باید ابتدا یک شیء از کلاس Cat تعریف نمود، سپس 5 را به آن شیء تخصیص داد:
Cat Tom;
Tom.itsAge = 5;
فرض کنید سه سال بعد به همراه یک دوست در حال گردش هستید. به دوستتان یک گربه نشان می دهید و می گویید این Tom است. Tom یک شیرین کاری بلد بود. ”Tom صدای سگ در بیار“. دوستتان می خندد و می گوید: ”نه نادان، گربه نمی تواند عوعو کند“. به همین ترتیب اگر در برنامه خود بنویسید:
Cat Tom;
Tom.Bark();
کامپایلر نیز می گوید: ”نه احمق! گربه نمی تواند عوعو (bark) کند“ (البته کامپایلر شما کلمات متفاوتی چاپ می کند). کامپایلر به این دلیل می داند Tom قادر به عوعو کردن نیست، که کلاس تعریفی Cat هیچ تابع عضوی به نام ()Bark ندارد. اگر تابع ()Meow را نیز درون کلاس Cat تعریف نکنید، کامپایلر به Tom حتی اجازه میومیو کردن نمی دهد.
معرفی private و public
از جمله کلمات کلیدی دیگری که در اعلان یک کلاس مورد استفاده قرار می گیرند، کلمات private (خصوصی و پوشیده) و public (به معنای همگانی و عمومی) می باشند. به طور پیش فرض، همه اعضای یک کلاس، اعم از توابع و داده ها، private می باشند. ویژگی اعضای private آن است که تنها توسط توابع عضو آن کلاس قابل دسترسی هستند. ویژگی اعضای public آن است که در خارج از کلاس نیز قابل دسترسی هستند. برای روشنتر شدن مطلب همان کلاس Cat را با کمی تغییرات (حذف کلمه کلیدی public) در نظر بگیرید:
class Cat
{
unsigned int itsAge;
unsigned int itsWeight;
Meow () ;
};
در این اعلان، همگی اعضا (itsAge، itsWeight، و تابع ()Meow) به صورت private می باشند. حال اگر به صورت زیر عمل کنیم:
Cat Frisky;
Frisky.itsAge = 5;
کامپایلر اعلام خطا می کند. شما به کامپایلر می گویید: ”من می خواهم به متغیرهای عضو itsAge، itsWeight و تابع عضو Meow() که همگی از اعضای شیء Frisky هستند، دسترسی پیدا کنم. Frisky نیز از کلاس Cat اعلان شده است. مشکل چیست؟“ با همه این تفاسیر، در اینجا شما قصد دارید، به متغیر عضو itsAge از شیء Frisky، خارج از کلاس دسترسی پیدا کنید. قصد دارید مقداری خارج از کلاس را درون متغیر عضو وارد کنید. صرفاً به این دلیل که Frisky یک شیء از کلاس Cat می باشد، نمی توانید به قسمت های مختلف Frisky که خصوصی (private) می باشند، دسترسی پیدا کنید. این موضوع موجب سردرگمی برنامه نویسان تازه کار ++C می شود. پاسخ این است که Frisky می تواند به اعضای خود دسترسی داشته باشد ولی شما مجاز به این کار نمی باشید، چرا که اعضای Cat به صورت خصوصی اعلان شده اند. از بیرون یک کلاس، تنها می توانید به اعضای عمومی دسترسی داشته باشید. برنامه زیر یک کلاس با نام Cat را نشان می دهد که دارای داده های عضو عمومی یا public می باشد.
#include "iostream"
using std::cout;
using std::cin;
using std::endl;
class Cat
{
public:
unsigned int itsAge;
unsigned int itsWeight;
};
int main()
{
Cat Frisky;
Frisky.itsAge = 5;
cout << "Frisky is a cat who is ";
cout <
return 0;
}
اگر در این برنامه، خطی که کلمه public در آن نوشته شده است را به توضیحات تبدیل کنید، یک پیغام خطا صادر می شود، چرا که itsAge دیگر یک عضو public نمی باشد و لذا دسترسی به آن، خارج از کلاس، غیرمجاز است.
پنهان سازی داده های عضو
به عنوان یک قانون کلی، همواره می بایست داده های عضو یک کلاس، از دید هر موجودیت خارج از کلاس پنهان نگه داشته شود، یعنی امکان دستیابی مستقیم به داده های عضو یک کلاس از بیرون از کلاس میسر نباشد. اما برای برقراری ارتباط کلاس با محیط پیرامونش، بایستی تعدادی تابع به صورت public ایجاد کنیم، که این توابع برای خواندن و انتساب مقادیر به داده های عضو خصوصی (private) به کار می روند. به اینگونه توابع، توابع دستیابی گفته می شود. سایر بخش های برنامه به منظور دستیابی به متغیرهای عضو خصوصی، اقدام به فراخوانی توابع دستیابی می کنند.
به عنوان مثال در زیر کلاس Cat را مشاهده می فرمایید که تغییراتی در آن اعمال شده و داده های عضو private و توابع دستیابی به آن افزوده شده اند. قطعاً می دانید که این کد قابل اجرا نمی باشد.
class Cat
{
public:
//public accessors
unsigned int GetAge();
void SetAge (unsigned int Age);
unsigned int GetWeight();
void SetWeight (unsigned int Weight);
//public member functions
Meow();
private:
//private member data
unsigned int itsAge;
unsigned int itsWeight;
};
سئوال: چرا با این روش دستیابی غیرمستقیم، خود را به زحمت می اندازیم؟ توابع دستیابی این قابلیت را برای برنامه نویس فراهم می آورند تا جزئیات مربوط به نحوه ذخیره سازی داده ها را از چگونگی کاربرد آنها، مجزا نمایند. این قابلیت سبب می شود، تا برنامه نویس بتواند چگونگی ذخیره سازی داده ها را تغییر دهد (در صورت لزوم) بدون آنکه نیاز باشد توابعی که از آن داده ها استفاده می کنند، بازنویسی گردند. اصلاً، جهت استفاده از داده ها، این روش ساده تر و آسانتر از حالتی است که در آن همه توابع خارج از کلاس به داده های درون کلاس دسترسی دارند. به عنوان مثال، در کلاس فوق، اگر تابعی برای انجام کار خود نیاز به دانستن سن گربه داشته باشد، می تواند با فراخوانی تابع دستیابی GetAge() (که یک تابع عمومی بوده و سن گربه را برمی گرداند)، به سادگی مقدار متغیر عضو itsAge را مورد استفاده قرار دهد. حال اینکه چگونه این متغیر مورد دستیابی قرار گرفته است، برای کاربر اهمیتی ندارد. از طرف دیگر، فراخوانی تابع دستیابی GetAge()، نیازی به دانستن اینکه متغیر itsAge چگونه ذخیره شده است (به صورت long، unsigned، یا float)، ندارد. این تکنیک سبب می شود که نگهداری برنامه ها آسانتر و کم هزینه تر شود. زیرا اعمال تغییرات روی برنامه، باعث کهنه و منسوخ شدن برنامه نمی شود. اعلان توابع و داده ها به صورت private باعث می شود که کامپایلر اشتباهات برنامه نویسی را قبل از تبدیل شدن به اشکالات بزرگ و مخرب، پیدا کند. آقای استراستروپ (stroustrup)، مخترع C++، می گوید: ”سازوکار کنترل دسترسی در C++، راهکاری برای حفاظت برنامه در برابر سهل انگاری ها، تصادفات، و اتفاقات پیش بینی نشده است، نه راهکاری برای حفاظت در برابر کلاهبرداری ها و سوء استفاده های عمدی“.