یادآوری مباحث مهم از C++

1395/9/14 آرام درخشانی 3578

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

آماده اید بریم سراغ درس و مقشمون؟! جلسه ی قبل یک ناخنک مختصر به کیوت زدیم و یه برنامه ی کوچیک و ساده باهاش نوشتیم و ترسمون ازش ریخت. مطمئنا اگه این دفعه ی اول بود که سراغ کیوت می‌آمدید ساختار کیوت و کدهاش براتون کمی جای سوال داشت و گنگ بود. غصه نخورید اوایل هر کاری این حالت طبیعیه.
امروز میخوایم بیشتر با جد کیوت که همان سی++ است آشنا شویم و دریابیم که آنچه در کیوت میبینیم خیلی تفاوت با سی++ نداره. هرچند که کیوت خودش قالب ها و الگوهای جدیدی را به این زبان اضافه کرده اما در کل در چارچوب همان سی++ جای میگیرن.
مطالعه‌ی این فصل برای آن دسته از کاربرانی که آشنایی قبلی با سی++ دارند زیاد ضروری نیست اما خالی از لطف هم نیست.

سی++ از روزنه‌ی کیوت
حداقل های لازم برای فهم یک برنامه ی کیوت به شرح زیر می باشد:
1-بحث کامپایل(کار make  و qmake)
2-سی++ مقدماتی (شامل متغییر و شرط و حلقه و ...)
3-ساختار پروژه ی کیوت و تقسیمات فایل
4-شی گرایی (کلاس، خواص، متدها)
5-پوینترها یا اشاره‌گرها

کامپایل:
همانطور که میدانیم بطور کلی برای ساخت فایل خروجی(exe) از یک برنامه ی سی++  
-ابتدا باید کدهایمان را در یک فایل .cpp  بنویسیم.
-سپس باید فایل .makefile را  بسازیم که شامل حداقل 5 مورد زیر است:
•Rules: قوانین لازم برای ساخت فایل های خاص (مثلا: برای ساخت فایل .o از یک فایل .cppباید دستور gcc –c  را روی فایل .cpp تون اجرا کنید)
•Sources and Headers lists: لیست تمام فایل های .cpp و .h که در برنامه استفاده شده(بعدا بیشتر میشکافیم این رو)
•Targets: مشخص میکند که کدام یک از کتابخانه‌ها یا خروجی ها(فایل‌های اجرایی) باید ساخته شود.
•Dependencies: بخاطر می سپارد که وقتی یک فایل خاص تغییر میکند کدام Target مرتبط باید بازسازی شوند(به فرض مثال بعد از تغییرات کد).
•Comments: توضیحات که با علامت #  می شوند و در اصل پروسه ی کامپایل دخالتی ندارند و برای فهم بیشتر هستند.
-سپس این فایل .makefile را میدیم به ابزار make
-ابزار make (مثل MinGW) پروسه ی compiling  و سپس linking (یعنی تولید کد اسمبلی از روی کد تولید کد ماشین از روی کد اسمبلی و سپس اضافه کردن Static Libraries) را اجرا می کند تا در نهایت فایل خروجی .exe تولید و سر میز تقدیم شما شود ؛)  
(تصویر شماره 1)
چند نکته سمی:
1-مزیت استفاده از این .makefile این است که هر بار که تغییرات در کد برنامه اعمال می شود و مجددا دستور ساخت به برنامه داده می شود، ابزار make از روی فایل .makefile تنها فایل هایی را بررسی میکند که تغییر کرده اند و این زمان صرف شده برای کامپایل را بسیار کاهش میدهد.
2-لابد شما هم فایل .pro رو توی پوشه ی پروژه ی کیوتتون دیدید و نیز لابد کلمه ی qmake به گوش مبارکتان خورده است اما تا کنون هیچ وقت اسم فایل .makefile وmake  و ... رو ندیدید و نشنیدید توی محیط کیوت. خب من الان میخوام سرِّ درون کیوتی رو بشکافم و این راز رو برملا کنم؛ خانمها و آقایون: اون مارمولکی که کار تولید .makefile رو انجام میده کسی نیست جز qmake !!  و غذای این مارمولک نیز چیزی نیست جز اون فایل ساده ی .pro !
3-نتیجه گیری۱: به نام خدا. فایل .pro  لیست فایل های .h و .cpp و نیز عکس ها و منابع و ... + برخی توضیحات و محل قرار گیری برخی فایل ها و نیز برخی تنظیمات مهم رو توی خودش نگه میداره. سپس این qmake از روی فایل .pro فایل .makefile رو میسازه و تحویل MinGW میده (که ابزار make رو داره) برای ساخت.
4-نتیجه گیری۲: qmake تنها یکی از کارهاش اینه. بقیه ش چیه؟ خودتون زحمت پیدا کردنش رو بکشید تا لذت تحقیق رو بچشید(معلم دوم ابتداییم هم همیشه میگفت شم ادبیم قویه).
5-نتیجه گیری ۳: qmake بیشتر تنظیمات رو در خودش نگهداری میکنه ومغز متفکر پروسه ی ساخت(build) هست. اما خودش چیزی نمی سازه جز .makefile
تصویر شماره 2 در راستای تبیین این بحث راهگشاست.
 سی++ مقدماتی:
این مواردی رو که عرض میکنم فقط من باب «یاد آر که چه بَلْدی یاد آر» است و به آن نمی پردازیم:
1-متغییرها: که برای اختصاص یا گرفتن یک جای مخصوص و شناسنامه دار از حافظه ی کامپیوتر است تا موارد موقتی رو توی آن ذخیره کنیم و در مواقع لزوم به آن مراجعه کنیم.
2-شرط‌ها: برای کنترل جریان برنامه و دادن قوه ی منطق به برنامه. بعبارتی تغییر جهت دادن بنا به شرایط خاص و پارامترهای خاص.
3-حلقه‌ها: برای اجرای یک/چند فرمان طبق یک الگوی تکراری‌ و با تعداد دفعات مشخص برای کاهش تعداد خط-کد های نوشته شده و نیز کاهش زمان و بالا بردن خوانایی برنامه.
4-ماکروها یا Preprocessor ها: چون ماکروها قبل از هر چیز و هر کد دیگری توسط کامپایلر خوانده می شوند، از ماکروها برای وارد کردن فایل های لازم (#include) در برنامه و نیز تعریف ثابت ها و نیز شرط های کلی و اولیه(مثل: اگر سیستم کاربر ویندوز بود متغییر پلتفرم رو برابر ویندوز قرار بده یا ...) مثالی از این شرط های کلی رو در پایین آورده ام:

#if defined _WIN32
  #define PLATFORM "Windows"
#elif defined __linux
  #define PLATFORM "Linux"
#elif defined __macosx
  #define PLATFORM "MacOSX"
#else
  #define PLATFORM "Others"
#endif


برای مطالعه ی بیشتر به ارجاع شماره 1 مراجعه فرمایید.

ساختار پروژه ها در کیوت:
دیده اید که در یک پروژه‌ی ساده‌ی کیوتی به چند نوع فایل و پوشه برمیخوریم:
-فایل .pro که تفصیل آن در بالا آمد. کارش لیست کردن تنظیمات، وارد کردن نیازمندی‌ها(مثلا اگر بخواهید از پایگاه داده استفاده کنید باید در این فایل بنویسید: Qt + = sql) و نیز برخی مسیرها و اسم پروژه(Target)و... است. این فایل بعدا ماده ی اولیه برای ساخت .makefile‌توسط qmake خواهد شد.
-پوشه headers که دارای فایل‌های .h هستند: این پوشه در واقع دسته بندی‌ای است که کیوت انجام میدهد و فایل های سرآیند را ذیل آن قرار میدهد. فایل سرآیند با پسوند .h‌ کارش Prototyping یا خلاصه سازی است. یعنی فقط تعاریف کلاس‌ها و توابع را در بر میگیرد (Interface Declaration) و بدنه ی توابع و کلاس‌ها در فایل های .cpp که source نامیده می شوند قرار میگیرد(Implementation Definition).
یعنی چی؟ یعنی دیگه زشته که شما هم تعریف کلاس و هم بدنه ی توابع و متغییرها و خلاصه همه چیز رو داخل یک فایل cpp بیچاره بریزید!  
یعنی اگر بعد این همه مدت یه فایل اینجوری تحویل ما بدهید حسابی ازتون دلخور میشیم:

main.cpp
#include <iostream>
using namespace std;
#include <string>
class greetings{
    public:
    void sayHello(){
        cout << "Hi " << this->myname << " !!" << endl;
    }

    private:
    string myname = "YOUR_NAME";
};
int main(int argc, char *argv[])
{
    greetings g1;
    g1.sayHello();
    return 0;
}

بلکه از پروگرامر متشخصی چون شخص شخیص شما انتظار میره که این کلاس رو اینطوری توی دو تا فایل بنویسه و بعد توی فایل اصلی برنامه که main.cpp است از اون دو تا فایل با فراواخوانی .h استفاده کنه:

myfile.h
#ifndef MYFILE_H
#define MYFILE_H
#include <iostream>
using namespace std;
#include <string>
class greetings{
    public:
    void sayHello();

    private:
    string myname = "YOUR_NAME";
};
#endif // MYFILE_H

myfile.cpp
#include <myfile.h>

void greetings::sayHello(){
    cout << "Hi " << this->myname << " !!" << endl;
}

main.cpp
#include <iostream>
#include <myfile.h>
using namespace std;

int main(int argc, char *argv[])
{
    greetings g1;
    g1.sayHello();
    return 0;
    cout << "Hello World!" << endl;
    return 0;
}

برنامه ی پیوستی شماره 1 رو دانلود کنید تا ببنید نتیجه چی میشه.
پس بعبارتی دیگر: فایل های .h نقشه ی کلی برنامه ی ما رو ترسیم میکنه(متغییرهای بکار رفته و توابع و پارامترهای مورد نیاز و ...) و فایل های .cpp پیاده سازی شده ی همان نقشه است با جزئیات کامل. و هر وقت نیاز به این کلاس داشتید فقط کافی است فایل .h آن را include  کنید. تا به تمام قابلیت های تعریف شده ی آن کلاس دسترسی پیدا کنید.
درست مثل کلاس iostream که هر وقت لازمتون میشه include میکنید و فایل هدرش به برنامه تون اضافه میشه.

-پوشه ی Resources : که توسط فایل .qrc داخلش، تمام منابع خارجی از جمله عکس و فیلم و پس زمینه ها و فونت و... رو داخل اونجا میذارید و توسط فایل .qrc آنها را مدیریت میکنید.
-پوشه ی qml : که حاوی کلیه ی اسکریپت های qml است که بکار میبرید در برنامه تون.
-نکته: این جداسازی باعث خوانایی پروژه و آسان تر شدن مدیریت آن می شود. همچنین قابلیت نگهداری(Maintenance) و استفاده‌ی مجدد(Refactoring) را نیز بطور قابل توجهی افزایش میدهد.
 شی گرایی:
خب میرسیم به اصل کاری که تقریبا همه ی کارهامون توی کیوتی با کلاس ها انجام میشه چون فریمورک کیوت کاملا بصورت شی گرا پیاده سازی شده.
بعلت کثرت منابع این قسمت رو ساده و مختصر توضیح میدهم و رد می شوم.
شی گرایی چی است؟ شی گرایی الگوی نجیبی است که از آن جهت ساده سازی و نیز مدیریت بهتر کدهایمان استفاده میکنیم. در بیانی مهندسانه تر(!): الگو(Paradigm)ی است برای تجزیه، تحلیل، طراحی و پیاده سازی یک سیستم.  ایده ی پشت این الگو، همان شبیه سازی جهان واقعی ماست که همه چیز شیء است و دارای خواص(Property/Attribute) و کارکرد(Function/Method). مثلا یک تلوزیون دارای خاصیت اندازه است و نیز کارکرد روشن شدن. یا یک آبمیوه گیری که دارای خاصیت مدل، نام کارخانه و حجم است و کارکرد روشن شدن و خاموش شدن و نیز کارکرد چرخش ممتد(همان دکمه های 1 تا 4 روی آن)(چرخش ممتد!!! عجب اصطلاح من درآوردی‌ای!).

کلاس چی است؟ کلاس عبارت است از الگو. آیا تا کنون پارچه برده‌اید پیش خیاط تا برایتان مانتو بدوزد؟ خیاط یک آلبوم به شما نشان میدهد و شما مدل مورد علاقه ی خود را انتخاب میکنید. هر مدل دارای یک الگو است که شیوه‌ی برش پارچه را برای تولید مانتویی از یک مدل خاص بطور دقیق نشان میدهد. از این پس می توانیم الگو را کلاس بنامیم و مانتویی که از روی آن ساخته می شود را شیء.
یا در مثالی دیگر: قالب کیک = کلاس و  خود کیک= شیء
کلاس را در سی++ بصورت زیر تعریف میکنند:

class mySampleClass{
    …
    …
};


شی گرا بودن یک کد، یا یک مدل شامل 4 جزء اصلی است:
1-اصل انتزاع (Abstraction) : بدین معنی می باشد که در مدل سازی سیستم(که بعدا آنرا تبدیل به کدهای شی گرا می کنیم) فقط و فقط آن خصوصیات و کارکردهایی لحاظ شود و به حساب آید که مرتبط با هدف آن سیستم باشند.
مثلا وقتی میخواهید برای یک مغازه ی میوه و تربار فروشی برنامه بنویسید، اگرچه تعداد دانه‌های داخل یک گوجه فرنگی جزو خصوصیات گوجه فرنگی است اما چون تاثیری در هدف آن مغازه(یعنی فروش) ندارد لذا آن را لحاظ نمی کنیم. یا مثلا شجره ی نژادی آن نیز نباید لحاظ شود. اما میزان کود مصرفی(میزان ارگانیک بودن) برای آن یا تاریخ برداشت آن جزو خصوصیاتی است که تاثیر مستقیم در قیمت دارد لذا باید لحاظ شود.
این اصل مصداق عملی ندارد تا کد نشان دهیم فقط یک نصیحت است.
2-ارث بری(Inheritance): در شی گرایی قابلیت ایجاد سلسله مراتب را دارید. یک شجره نامه را در نظر بگیرید: من فرزند پدرم هستم و پدر من هم فرزند پدرش و ...
من یک سری از خصوصیات و کارکردها را از پدرم به ارث برده ام(مثل زبان و رنگ پوست و حرف زدن و...) و ایشان نیز از پر خود یکسری خصوصیات وکارکردها را تا.... می رسد به حضرت آدم! وقتی شما میخواهید این سلسه مراتب را مدل سازی بکنید در شی گرایی، خصوصیات مشترک را از طریق ارث‌بری به اشتراک میگذارید و تنها و تنها خصوصیات منحر بفرد را بطور مجزا برای کلاس‌ها تعریف میکنید.
در سی++ برای ارث بری کلاس B از کلاس A باید بصورت زیر عمل کرد:

class B:A{
…
}

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

class Adam{
public:
    void walking();
    void speacking();
    void sleeping();
    void engaging();
    void annoing();
    
private:
    int height;
    int weight;
    string name;
    string family;
};

class Aram: Adam{
public:
    studing();
    telegramming();
    clashing();
    
private:
    int graduatedYear;
};

همانطور که ملاحظه می فرمایید من بعنوان یک آدم تمام خصوصیات و کارکردهای حضرت آدم را با آن دو نقطه( : ) به ارث بردم و فقط آن خصوصیات و کارکردهایی که منحصر به من بود و در حضرت آدم نبود را جداگانه ذکر کردم. مثل مطالعه کردن و کارکردن با تلگرام و کلش بازی کردن(که نکردم تا حالا)  و سال فارغ التحصیلی. به این میگن ارث بری و کارکردش هم ساده سازی و کم کردن از حجم برنامه و بالا بردن خوانایی کدمون هست.
•نکته: اگر یک خصوصیت در دو کلاس(والد و فرزند) اسماً یکی باشند اما ماهیتشان یکی نباشد چه؟ یعنی فرض کنید حضرت آدم روی زمین می خوابیدند حالا تختخوابی ساخته شده که میتوان ایستاده خوابید! راهکار این معضل چیزی است به اسم overriding .
3-کپسوله سازی(Encasulation)  :
مطمئنا شما هم به عبارت‌های public و private در تعریف کلاس ها دقیق شده اید. این ها چه هستند؟ اینها ما را قادر به کپسوله سازی می کنند. یعنی چی؟ یعنی در واقع داریم با این کلمات دست بیگانگان را از محتویات گاوصندوق کلاسمان کوتاه میکنیم(مرگ بر استکبار!).
تمامی اعضای public  توسط کلاس های دیگر قابل فراخوانی هستند. لذا به هرکسی که آنها را فرابخواند سرویس دهی میکنند. به این دسته از اعضا، interface یا رابط میگوییم. مثل دکمه ی on/off تلوزیون.
شرکت سامسونگ تمام سیم ها را مخفی(کپسوله) کرده و شما اجازه ی دستکاری آنها را ندارید بلکه تنها و تنها قادرید توسط ریموت خود آن شرکت و از طریق دکمه ی تعریف شده این فرمان رو صادر کنید.
تمامی اعضای private فقط و فقط، تکرار میکنم:‌ فقط و فقط توسط همان کلاس قابل فراخوانی، مقداردهی و تغییر دادن هستند.
کاربرد: با اینکار هم امنیت برنامه را مدیریت میکنیم و هم خود را مقید به الگویی میکنیم نظم خوبی را در برنامه حاکم میکند.
یک اصل کلی می گوید: همواره متغییرهایتان را private تعریف کنید و توابع عمومی تان را public . برای دسترسی و تغییر هر متغییرprivate  هم یک تابع public  تعریف کنید که واسطه بشود(Setter/Getter) با اینکار میتوانید از صحت اطلاعاتی که درون متغییرها قرار میگیرد اطمینان حاصل کنید.

4-چند شکلی(Polymorphism) : این قابلیت در واقع به هوشمندی برنامه ی شما می افزاید. بدین معنی است که شما چند تا تابع از یک/چند نوع مختلف «به یک اسم» داشته باشید که هرکدام یک/چند تا پارامتر مختلف میگیرد. برنامه بر اساس نوع و تعداد متغییرها و نیز نوع خروجی درخواستی، تابع درست را فراخوانی میکند. مثال؟
 

http://bit.ly/1omj0IU

** اشاره گرها: این مبحث بماند برای ابتدای جلسه بعد.

خب خسته نباشید. امیدوارم این مرور براتون مفید بوده باشه. بابت تاخیری که در آموزش‌ها بوجود میاد نیز پوزش میخوام. مشغله‌هامون یک کم زیاد شده و کار تولید آموزش هم بسیار زمان بر هست. امیدوارم با یک صبر و تحمل بیشتر بتونیم باهم تو این مسیر همراه هم باشیم.
خیلی موفق باشید!


ارجاعات
ـــــــــــــــــ

 

(۱) http://www.bogotobogo.com/cplusplus/preprocessor_macro.php

 

آموزش کیوت

تصویرـشمارهـ1

آموزش کیوت


تصویرـشمارهـ2