4. 1. // The square root function takes a double and returns a double
2. double sqrt(double);
3.
4. class Vector
5. {
6. public:
7. Vector(int s);
8. double& operator[](int i);
9. int size();
10.
11. private:
12. double* elem; // elem points to an array of sz doubles
13. int sz;
14. };
5. 1. double sqrt(double d) // Definition of sqrt()
2. {
3. // ... algorithm as found in math textbook ...
4. }
5.
6. Vector::Vector(int s) //Definition of the constructor
7. : elem(new double[s]), sz(s) // Initialize members
8. {
9. }
10.
11. double& Vector::operator[](int i)// Definition of subscripting
12. {
13. return elem[i];
14. }
15.
16. int Vector::size() // Definition of size()
17. {
18. return sz;
19. }
در این ویدیو در مورد ماژولاریتی صحبت می کنیم و راههایی که C++ برای این کار در اختیار ما میذاره رو شرح میدم.
فضای نام یک از راههای ایجاد ماژولاریتی رو توضیح میدم
و مدل اداره کردن خطا در C++ رو می گم.
تا اینجا هر مثالی از تابع ها متغییر ها و کلاسها دیدیم Declaration و Definition همراه هم بودند. این مدل شبیه مدل زبانهای Java و C# هست.
اما C++ اجازه میده Declaration و Definition رو از هم جدا کنیم.
در واقع Declaration یا تعریف تابع؛ متغییر و یا کلاس رو بهش Interface یا واسط میگیم.
و Definition رو Implementation یا پیاده سازی می گیم. ماژولار کردن برنامه ها با جدا کردن Declaration از Definition انجام میشه.
مدلی که C++ برای توسعه برنامه ها ارائه میده به این صورته که برنامه ها رو به قسمتهای جدا جدا تقسیم کنیم و هر قسمت رو جداگانه توسعه بدیم. کلید اصلی برای این کار اینه که روابط بین این قسمتها رو دقیق مشخص کنیم. یک راه اینه که با استفاده از declarationها interface تعریف کنیم. Interface همون رابط یا واسطی هست که امکان میده برنامه ها رو به قسمت های مختلف تقسیم کنیم. یه مثال از declaration ببینیم.
خط 2 یک declaration از تابع sqrt میبینیم. تفاوتش با توابعی که قبلا دیدیم اینه که بدنه نداره و یک سمی کلون تهش گذاشتیم.
خط 4 تا 14 هم یک declaration از کلاس vector می بینید. تمام متدهای کلاس بدون بدنه هستند و فقط تعریفشون هست. به اصطلاح به این نوع declaration میگیم امضای تابع. در امضای تابع فقط اسم تابع , نوع ورودیهاش و نوع خروجی رو مشخص می کنیم و بدنه ی تابع رو اینجا تعریف نمی کنیم و یه جای دیگه اون رو نوشتیم.
بدنه ی تابع sqrt و متد های کلاس رو در این مثال می بینیم. به بدنه ی تابع ها و متد ها که یه جای دیگه تعریف شدن definition یا پیاده سازی می گیم.
برای مشخص کردن پیاده سازی تابع باید اسم اون تابع رو بنویسیم و ورودی خروجی هاش رو دقیقا مثل declaration اون بنویسیم و بدنه ی اون تابع رو بنویسیم و داخل اون عملیاتی که تابع انجام میده رو بنویسیم.
برای متدهای کلاس هم مشابه تابع عمل می کنیم با این فرق که باید نام کلاس رو هم مشخص کنیم و با یک :: به اسم متد وصل کنیم. این :: یک اپراتور هست به نام resolution و مشخص می کنه که اسکوپ ما چیه. این اپراتور رو قبلا دیده بودیم و برای cout ازش استفاده کرده بودیم.
قدم بعدی بعد از جدا کردن Declaration از Definition ؛ برای ماژولار سازی برنامه ها اینه که کامپایل شدن قسمتهای مختلف برنامه رو هم از هم جدا کنیم.
برای این کار باید Declarationها رو در Source fileهای جدا بذاریم و Definitionها رو هم در source file های جدا بنویسیم. در شکل می بینید که Decleration کلاس Vector رو در Vector.h گذاشتیم. به فایلهایی که پسوند .h دارن Header file میگیم و هر کسی که میخواد از Vector استفاده کنه باید Vector.h رو در ابتدای برنامش اونو Include کنه تا بتونه به Interface کلاس Vector دسترسی داشته باشه. Definition متدهای Vector رو در Vector.cpp گذاشتیم. و جایی که از کلاس Vector استفاده می شه رو هم در یک فایل جدا به نام User.cpp گذاشتیم.
در این اسلاید هم رابطه ی بین این فایلها یا همون Interface رو می بینید. فایل های User.cpp و Vector.cpp فایل Vector.h رو با Include به خودشون اضافه کردن. در واقع Interface کلاس Vector با Includeبین 2 تا فایل Vector.cpp و User.cpp به اشتراک گذاشته شده. و چون فایل های User.cpp و Vector.cpp از هم مستقل هستند پس میتونیم اونا رو جدا گانه کامپایل کنیم.
فضای نام یا namespace یک راه برای اینکه؛ مشخص کنیم یکسری declaration مربوط به هم هستند و جلوی ایجاد مشکل برای نامهای تکراری رو میگیره.
برای مثال فرض کنید که بخوایم اعداد مختلط رو پیاده سازی کنیم. برای اینکه با اعداد مختلط که جاهای دیگه نوشته شده قاطی نشه از namespace استفاده کردیم .
تمامی declarationهای خودم رو در فضای نام My_code گذاشتم
برای پیاده سازی هم مشابه قبل؛ با استفاده از اپراتور resolutionیا همون :: مشخص کریم که پیاده سازی مربوط به کدوم declaration هست.
و برای فراخوان هم باید Namespace اون تابع رو مشخص کنیم
یک از امکاناتی که زبان C++ برای پی بردن به خطاها در زمان اجرای برنامه در اختیار ما میذاره Exception یا استثناهاست. وقتی برنامه ها بزرگتر می شن اداره کردن خطاها هم مهمتر میشه، برای مثال ما از کلاس Vector که با هم دیده بودیم استفاده میکنیم. اگه یادتون باشه ما یک اپراتور Subscritp نوشته بودیم که به ما امکان می داد با استفاده از index به عنصر آرایه دسترسی پیدا کنیم. حالا فرض کنید که ما یک index غیر معتبر بدیم مثلا یک عدد منفی بدیم یا بخوایم به یک عنصری دسترسی پیدا کنیم که در آرایه نسیت.
برای اینکه جلوی این کار رو بگیریم می تونیم از Exception استفاده کنیم اول اونو باید include کنیم.
با استفاده از using می تونیم مختصر نویسی کنیم. بجای اینکه همه جا بنویسیم std:: و بعد بنویسیم مثلا COUT یک راست مینویسیم cout و از std:: فاکتور میگیریمusing namespace به کامپایلر میگه وقتی داری دنبال نام یک کلاس یا متغییر می گردی توی namespace ؛ std رو هم بگرد.
داخل شرط if چک میکنیم که index معتبره یانه؟ اگه منفی باشه یا از اندازه عناصر بزرگتر باشه
با استفاده از throw یک خطا از نوع out_of_range یا دسترسی خارج از محدوده بر میگردونیم. در واقع throw مثل retrun عمل میکنه با این تفاوت که یک خطا برمیگردونه ولی return مقدار خروجی برمی گردونه.
حالا برای اینکه بتونیم از اتفاق افتادن یک exception در برنامه مطلع بشیم باید از try catch استفاده کنیم.
به این صورت که هر جایی که داریم از subscriptاستفاده می کنیم بین یک بلاک try قرار میگیره و بعد از try یک بلاک catch میذاریم
اگه در بدنه ی try یک خطا رخ بده برنامه میپره به
بدنه ی catch و دستورات اونو اجرا میکنه.
یک مورد دیگه که ممکن ورودی نادرست مشکل ساز باشه زمان ساختن vector هست. ممکنه توی constructor یک عدد منفی بدیم. میشه جلوی اون رو هم با یک exception به نام length_error گرفت تا درصورتی که کسی خواست یک vector با طول منفی بسازه یک exception تولید بشه و جلوی اونو بگیره
در این مثال میبینید که میشه یک بلاک try تعریف کرد
و چند تا بلاک catch از انواع مختلف براش گذاشت. در اولین catch ما میتونیم exceptionهای از نوع length_error رو اداره کنیم و در دومین catch هم خطاهای bad_alloc رو اداره کنیم یعنی زمانی دیگه که حافظه در اختیار نداشته باشیم و نتونیم با new یک متغییر جدید بسازیم.
یک جمع بندی از این سری از ویدیو ها بکنیم که در مورد چه مباحثی صحبت کردیم:
ما در اولین ویدیو یک مقدمه و روند تکامل C++ رو گفتیم و یک برنامه Hello world نوشتیم
در ویدیو دوم در مورد Typeها و متغییر ها و عملیاتهای ریاضی و منطقی گفتیم
در ویدیوی سوم در مورد شرط ها و حلقه ها صحبت کردیم
در ویدیوی چهارم در مورد آرایه ها و اشاره گرها صحبت کردیم و reference رو هم معرفی کردیم
در ویدیوی پنجم در مورد استراکت و کلاس و enum صحبت کردیم
و در ویدیوی آخر هم در مورد ماژولار کردن برنامه ها و exception بحث کردیم
امید وارم مفید بوده باشه
با تشکر