چطور این ۲ هفته رو گذروندم

هوف! بالاخره پنل ادمین وبلاگمو باز کردم. بعد از آخرین پستم که تقریبا ۱۷ روز پیش بود حالا دوباره اومدم که فرآیند یادگیری‌ و مطالعه‌م رو شروع کنم. توی این تقریبا ۲ هفته کار زیادی نکردم. اولین برنامه‌ی اندرویدم رو نوشتم، چندتا فیلم دیدم و GTA V رو نصب کردم.

۵ تومن

داستان این برنامه برمیگرده به اونجا که شروع کردم در مقابل هرنوع کمکی که به دوستام میکردم، بهشون بگم ۵ تومن شد :))))

این داستان انقدر پیش رفت که تبدیل به تیکه کلام شد، یه سری واقعا این ۵ تومن هارو پرداخت کردن و دست آخر دیدم حسابشون داره از دستم در میره. خب چه چیزی بهتر از کامپیوتر برای نگهداری حسابا؟ ۵ تومن پول رایج مملکت مشوق من شد تا اولین برنامه‌ی خودم با کیوت بنویسم. و هیجان انگیز تر از اون؟ یه برنامه‌ی اندروید هم براش نوشتم! کیوت خیلی جالبه اما اگر تسلط کافی به خودِ سی++ و api های اندروید(یا هر بستر دیگه ای. مثلا iOS) ندارید و نقشه‌تون ایجاد یه برنامه‌ی مالتی پلتفرم نیست، بهتره که از زبان های native اون پلتفرم استفاده کنید.

این پروژه‌ی تمرینی چیز های جالب زیادی بهم یاد داد. مثلا اینکه ما داریم تو ایران زندگی می‌کنیم و واقعا بدبختیم. که ۲۵ دلار پول برای developer شدن توی گوگل پلی به پول ما خدا تومن میشه. که حتی همون خدا تومن رو هم نمیشه مثل آدم پرداخت کرد. بگذریم…

Cinema Paradiso

عجیبه که این فیلم رو انقدر دیر دیدم. درواقع پیش خودم فکر میکنم که چندتا فیلم/کتاب/موسیقی به این شکل زیبا توی جهان هست که من ازشون بی خبرم؟ و حتی تا آخر عمرم هم ازشون بهره ای نمیبرم. انگار واکنش من به لذت ها اینه. نوعی ناخوشی گُنگ حتی در خوشی هام هست.

GTA V

بازی GTA V توسط اپیک گیمز رایگان شد! البته، قبل از هرچیز باید بگم که لانچر اپیک گیمز یه آشغال به تمام معناست! نمیتونی دانلود/نصب برنامه رو متوقف کنی و بعدا ادامه بدی، انقدر تحریما رو سخت گرفته که حتما برای دانلودش هم باید فیلتر شکنت روشن باشه. د آخه مومن، پاچه خواری تا کجا؟ مگه فقط تو توی آمریکایی؟ میمیری مثل بقیه کمپانیا محدود کنی؟ بهرحال، لانچرش واقعا آشغاله.

و اما خود بازی. همون موقعی که تازه GTA V اومد همیشه دوست داشتم که روی سیستم خودم بازی کنمش. سیستم من ضعیف بود و هیچوقت نشد. کرک هاش درست کار نمیکردن و مشکلات دیگه. حتی روی پلتفرم های دیگه مثل کنسول هم شاید کلا ۲ بار بازی کردم.

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

در پایان

کلا اینکه این هفته هم مثل اکثر هفته های دیگه‌ی زندگیم آنچنان مفید نبود. اما حداقل لذت بخش بود. مفید نبود اما وقت تلف کردن هم نبود، چون وقتی که لذت بردی یعنی وقتت تلف نشده.

نکات جدیدی که از فصل ۱۱ سی++ دایتل یاد گرفتم

این فصل از دایتل درباره‌ی ویژگی ارث‌بری توی سی++ بود. نکاتی که برام جدیده رو توی این پست نوشتم.

تفاوت is-a relationship و has-a relationship

وقتی یک کلاسی(A) از یک کلاس دیگه(B) ارث بری می‌کنه، میگیم که رابطه‌شون از نوع is-a relationship هست. یعنی کلاس A نوعی از کلاس B عه؛ مثالش؟ «خودرو» و «وسیله نقلیه». خودرو از وسیله‌ی نقلیه مشتق شده(نمیدونم این تعبیرِ مشتق شدن درسته یا نه) و تمام ویژگی های یک وسیله‌ی نقلیه رو داره. درواقع، «خودرو یک وسیله‌ی نقلیه‌ست».

اما وقتی یک کلاس در data-member های خود یک شئ از یک کلاس دیگه رو داره(composition) میگیم رابطه‌شون has-a relationship هست. توی مثال خودرو، میشه فرمون خودرو رو مثال زد که خودش یک شئ هست و عضوی از خودرو محسوب میشه(نه نوعی از خودرو!).

Constructor

در حالت عادی، توابع سازنده(constructor)، نابودکننده(destructor) و اوپراتور تخصیص (operator= ) از کلاس والد ارث برده نمیشن.

وقتی یک شئ از کلاس فرزند(B) ساخته میشه اگه کلاس والد(A) دارای default constructor باشه، بدون اینکه کانستراکتور والد رو صراحتا صدا بزنیم، خودش به صورت ضمنی اجرا میشه. اما اگه کلاس A دارای default constructor نباشه باید حتما خودمون کانستراکتور کلاس A رو توی کانستراکتور کلاس B صدا بزنیم.

دسترسی به عناصر private والد

کلاس فرزند نمیتونه به طور مستقیم به عناصر private کلاس والد دسترسی داشته باشه. برای اینکه بشه اینکار رو انجام داد، اون عناصر مورد نظر توی کلاس والد باید به عنوان protected تعریف شده باشن یا اینکه کلاس فرزند از طریق توابع public کلاس والد اقدام به دسترسی گرفتن به عناصر پرایوت بکنه.

با استفاده از protected access specifier به دلیل اینکه میتونید به صورت مستقیم به عناصر مورد دسترسی داشته باشید، برنامه‌تون کمی سریعتر میشه. در مقابل استفاده از توابع public باعث میشه برنامه‌تون کمی کند تر باشه.

اما این روش(protected) مشکلاتی هم داره:

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

دوم اینکه برنامه رو شکننده (اصطلاحا fragile) میکنه. یعنی کلاس فرزند جای اینکه بر پایه خدماتی(service) باشه که کلاس والد ارائه میده، بر پایه‌ی پیاده سازی (implementation) کلاس والد توسعه پیدا میکنه. مشکلش چیه؟ از نظر مهندسی نرم افزار بهتره که تغییرات رو localize بکنیم. یعنی برنامه طوری نباشه که اگر اسم یکی از عناصر رو توی کلاس والد تغییر دادیم، مجبور بشیم تمام جاهایی از کلاس فرزندمون که به اون عضو رفرنس دادیم رو تغییر بدیم و استفاده از protected پتانسیل بوجود اومدن چنین مشکلی رو ایجاد می‌کنه.

مگه نگفتی استفاده از توابع public برنامه رو کندتر می‌کنه؟

چرا. اما بهتره که ما اصول مهندسی نرم افزار رو رعایت کنیم و بهینه کردن کد(optimization) توی این مورد رو به دست کامپایلر بسپاریم. چون خیلی وقتا کامپایلر خودش بهینه سازی های خوبی انجام میده(مثلا ممکنه توابع set و get رو به حالت inline دربیاره). به قول معروف، Do not second-guess the compiler .

فرق بین ارث بری public و بقیه

خود کتاب یه جدول خیلی خوب رسم کرده که من عکسش رو میذارم. به نظرم خوندن همون کافیه 🙂

جدولی که توضیح میده هرکدوم از انواع ارث بری public, private, protected چه ویژگی هایی دارن.

ارث بردن constructor

بالاتر گفتم که در حالت عادی کانستراکتور از کلاس والد به ارث برده نمیشه. اما توی C++ 11 ویژگی ای اضافه شده که اجازه میده کانستراکتور والد توسط کلاس فرزند به ارث برده بشه.

برای اینکار باید یه همچین چیزی توی تعریف کلاستون بنویسید:

using BaseClass :: BaseClass ;

که BaseClass اسم کلاس والد هست. با اینکار، کامپایلر به ازای کانستراکتور های موجود توی کلاس والد، یه کانستراکتور توی کلاس فرزند میسازه که به طور خودکار تابع متناظرش توی کلاس والد رو صدا بزنه و data-member های کلاس فرزند رو default initialize بکنه.

چندتا نکته برای این کار وجود داره:

  • کانستراکتور های default, copy, move ارث بری نمیشن.
  • اگر توی کلاس فرزند کانستراکتوری باشه که ورودی هاش با ورودی های یک کانستراکتور توی کلاس والد یکسان باشه، اون کانستراکتور به ارث برده نمیشه.
  • اگه کانستراکتور دارای default argument باشه، به همون صورت به ارث نمیرسه بلکه به صورت چند کانستراکتور overload شده به ارث میرسه.
  • اگه یه کانستراکتور توی کلاس والد حذف بشه، توی کلاس فرزند هم حذف میشه.

در آخر

فصل بعدی درباره‌ی چند ریختی یا همون polymorphism عه که دانش ابتدایی ما برای برنامه نویسی شئ‌گرا رو کامل میکنه.

نکاتِ جدیدی که از فصل ۱۰ سی++ دایتل یادگرفتم

خب فصل ۱۰ ام از کتاب برنامه نویسی سی++ دایتل رو تموم کردم. اینجا خلاصه ای از چیزای جدیدی که یادگرفتم رو می‌نویسم.

string-object literal

توی سی++ ۱۴ میشه با اضافه کردن یک s به انتهای لیترال رشته‌ای‌مون(بعد از دابل کوتیشن، نه قبلش) اون رشته رو تبدیل به یه شئ از کلاس string کرد. مثال:

"Hello, World"s

Operator Overloading؛ به عنوان عضو کلاس یا نه؟

با اینکه من قبلا این بخش هارو توی کتاب C How To Program خونده بودم اما چون یادداشت نکرده بودم و خیلی وقت هم شده بود که حوصله‌ی برنامه نویسی رو نداشتم، یادم رفته بود.

مسئله اینه که چه وقتی باید تابع اوپراتوری که overload میشه رو جزئی از member function ها بذاریم و چه وقتی جزئی از non-member function. ساده‌ست، تنها زمانی میتونیم تابع اوپراتور رو به عنوان عضوی از کلاس قرار بدیم که شئِ کلاس ما به عنوان پارامتر در سمت چپ قرار بگیره. این کد رو ببینید:

istream& operator>>(istream& input, MyClass& object);

وقتی مینویسیم cin >> myobject درواقع انگار تابع رو به شکل operator>>(input, myobject) فراخوانی کردیم.

حالا از اونجایی که شئ ما توی پارامتر سمت چپ قرار نمیگیره، نمیتونیم این اوپراتور رو داخل کلاس overload کنیم و اگر اینکارو انجام دادیم، باید اینطوری بنویسیمش: myobject << cin

از طرف دیگه، برای اینکه بتونیم اوپراتور هامون رو به صورت commutative(جابجایی پذیر) تعریف کنیم، به دلایلی که بالاتر توضیح داده شد باید حداقل یکبار اوپراتورمون رو به عنوان non-member function تعریف کنیم.

نکته درباره‌ی Dynamic Allocation در عضو های کلاس

وقتی یک حافظه ای رو به صورت پویا برای یکی از اعضای کلاس‌مون (data-member) در نظر می‌گیریم باید حواسمون باشه که
Default memberwise assignment و Default copy constructor رو به حال خودشون رها نکنیم چون مشکلاتی مثل double free رو بوجود میارن. پس یادمون باشه که کپی کانستراکتور خودمون + علامت مساوی(=) خودمون رو تعریف کنیم.

حذف یه تابع از کلاس

یه تابع از کلاس (function member) رو میشه حذف کرد. قبلا برای اینکار، اون تابع رو جزء بخش private کلاس قرار می‌دادند اما الان به عنوان مثال میشه اینطوری نوشت:

const Array* operator=(const Array*) = delete;

کاربردش چیه؟ معمولا برای غیرفعال کردن توابعی که توسط کامپایلر به صورت خودکار ساخته میشن (auto generate)، مثل سازنده‌ی پیشفرض(Default constructor)، کپی کانستراکتور، اوپراتور تخصیص (=) و … استفاده می‌شن.

تعریف شئ از کلاس با initializer list

برای اینکه بتونیم یه شئ از کلاسمون رو با initializer list بسازیم، باید یه کانستراکتور داشته باشیم که ورودی‌ش از نوع initializer_listباشه. مثال:

MyClass::MyClass(initializer_list list);

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

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

برای اینکه یه کانستراکتور تبدیل کننده داشته باشیم نیاز داریم که کانستراکتور ما بتونه با یک آرگومان صدا زده بشه.(این مسئله برای کپی کانستراکتور ها صادق نیست) مثال :

MyClass:MyClass(int a);

حالا با استفاده از این تابع میشه هم به صورت ضمنی و هم به صورت مستقیم یک int رو به شئ ای از کلاس خودمون تبدیل کنیم.

برای تعریف یک Conversion Operator میتونیم اینطوری عمل کنیم:

MyClass::operator string() const;

حالا اگر فرض کنیم a اسم شئ ما باشه، وقتی بنویسیم static_cast<string> (a) درواقع کامپایلر تابع a.operator string() رو صدا میزنه.

چرا توابع سازنده‌مون رو به شکل explicit تعریف کنیم؟

توابع سازنده ای که میتونن فقط با یک آرگومان صدا زده بشن ممکنه توسط کامپایلر اشتباها به عنوان Conversion Constructor تلقی بشن(چون این تابع ها هم با یک ورودی صدا زده میشن) و به صورت ضمنی عمل cast رو انجام بدن. درکل ما برای اینکه از تبدیل ضمنی چه توی کانستراکتور(اونایی که میتونن با یه آرگومان صدا زده بشن) و چه توی اوپراتور های تبدیلی( conversion operator) جلوگیری کنیم، اون توابع رو به صورت explicit تعریف می‌کنیم.

فصل بعدی درباره‌ی ارث‌بری هست 🙂