پرچمداران
-
در همه بخش ها
- همه بخش ها
- فایل
- دیدگاه فایل
- نقد و بررسی فایل
- مقالات
- مقاله دیدگاه
- مقاله نقد و بررسی
- صفحات استاتیک
- صفحه دیدگاه
- صفحه نقد و بررسی
- کتابخانهها
- کتابخانه دیدگاه
- کتابخانه نقد و بررسی
- رویداد
- دیدگاه های رویداد
- بازبینی رویدادها
- تصاویر
- دیدگاه های تصویر
- نقد های تصویر
- آلبوم ها
- نظر های آلبوم
- نقد های آلبوم
- پست ها
- نوشتههای وبلاگ
- دیدگاه های وبلاگ
- بروزرسانی وضعیت
- پاسخ های دیدگاه ها
-
تاریخ سفارشی
-
همه زمان ها
4 خرداد 1397 - 6 اردیبهشت 1403
-
سال
5 اردیبهشت 1402 - 6 اردیبهشت 1403
-
ماه
6 فروردین 1403 - 6 اردیبهشت 1403
-
هفته
30 فروردین 1403 - 6 اردیبهشت 1403
-
امروز
6 اردیبهشت 1403
-
تاریخ سفارشی
دوشنبه, 14 بهمن 1398 - دوشنبه, 14 بهمن 1398
-
همه زمان ها
مطالب محبوب
در حال نمایش مطالب دارای بیشترین امتیاز در دوشنبه, 14 بهمن 1398 در همه بخش ها
-
1 امتیازهنگامیکه شما برای اولین بار از C به CPP مهاجرت می کنید، یا اصلا برنامه نویسی را قصد دارید با CPP شروع کنید، با مفاهیم متعددی روبرو خواهید شد که شاید برای شما جالب باشند که بدانید، این ایده ها چطور شکل گرفتند، چطور به CPP افزوده شدند و اهمیت آن ها در عمل (هنگام برنامه نویسی و توسعه نرم افزار) چیست. در این پست وبلاگی IOStream، به این خواهیم پرداخت که ایده Overloading و Template و Auto Deduction چطور از CPP سر در آوردند. همانطور که شما ممکن است تجربه کرده باشید، هنگامیکه برنامه نویسی و توسعه نرم افزاری را با C شروع می کنید، برنامه شما چیزی بیش از یک مجموعه بی انتها از توابع و استراکچرها و متغیرها و اشاره گرها و ... نخواهند بود. از همین روی شما مجبور هستید مبتنی بر ایده مهندسی نرم افزار و پارادیم برنامه نویسی ساخت یافته، برای هر کاری یک تابع منحصربفرد پیاده سازی کنید. این تابع باید از هر لحاظی از قبیل نام، نوع ورودی ها، نوع خروجی و حتی نوع عملکرد منحصربفرد باشد تا بتواند یک کار را به شکل صحیح کنترل کند که همین مسئله می تواند در پیاده سازی برخی نرم افزارها، انسان را در جهنم داغ و سوزان قرار بدهد. مثلا پیاده سازی یک برنامه محاسباتی مانند ماشین حساب که ممکن است با انواع داده های محاسباتی مانند عدد صحیح (Integer) و عدد اعشاری (Float) رو به رو شود. از همین روی فرض کنید، ما قرار است یک عمل محاسباتی مانند جمع از برنامه ماشین حساب را پیاده سازی کنیم. برای اینکه برنامه به شکل صحیحی کار کند، باید عمل جمع یا همان Add برای انواع داده های موجود از قبیل عدد صحیح و اعشاری پیاده سازی شود. اگر شما این کار را انجام ندهید، برنامه شما به شکل صحیحی کار نخواهد کرد (یعنی نتایج اشتباه ممکن است برای ما تولید کند). در تصویر زیر، نمونه این برنامه و توابع مرتبط با آن پیاده سازی شده است: #include <stdio.h> int AddInt(int arg_a, int arg_b) { return arg_a + arg_b; } float AddFloat(float arg_a, float arg_b) { return arg_a + arg_b; } double AddDouble(double arg_a, double arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { int result_int = AddInt(1, 2); float result_float = AddFloat(10.02f, 21.23f); double result_double = AddDouble(9.0, 24.3); printf("Result Integer: %d", result_int); printf("Result Float: %f", result_float); printf("Result Double: %lf", result_double); return 0; } به برنامه بالا دقت کنید. ما سه تا تابع Add با نام های منحصربفرد داریم که سه نوع داده مجزا را به عنوان ورودی دریافت می کنند، سه نوع نتیجه مجزا بازگشت می دهند، اگرچه پیاده سازی آن ها کاملا مشابه هم دیگر است و تفاوتی در پیاده سازی این سه تابع وجود ندارد. ولی به هر صورت، اگر به خروجی دیزاسمبلی برنامه مشاهده کنید، دلیل این مسئله را متوجه خواهید شد که چرا هنگام برنامه نویسی با زبان C، به نام های منحصربفرد نیاز است، چون اگر توابع نام های مشابه با هم داشته باشند، لینکر نمی تواند به دلیل تداخل نام (Name Conflict)، آدرس آن ها را محاسبه یا اصطلاحا Resolve کند. همانطور که در تصویر بالا خروجی دیزاسمبلی برنامه Add را مشاهده می کنید، اگر توابع نام مشابه داشتند، در هنگام فراخوانی (Call) تابع Add تداخل رخ می داد، چون دینامیک لودر سیستم عامل دقیقا نمی داند که کدام تابع را باید فراخوانی کند. برای همین نیاز است وقتی برنامه نوشته می شود، نام توابع در سطح کدهای اسمبلی و ماشین منحصر بفرد باشد. به هر صورت، به نظر شما آیا راهی وجود دارد که ما پیاده سازی این نوع توابع را ساده تر کنیم یا حداقل بار نامگذاری آن ها را از روی دوش توسعه دهنده و برنامه نویس برداریم؟ بله امکان این کار وجود دارد. مهندسان CPP با افزودن ویژگی Overloading و Name Mangling یا همان بحث Decoration مشکل برنامه نویسان در پیاده سازی توابع با نام های منحصربفرد را حل کردند (البته کاربردهای دیگر هم دارد که فعلا برای بحث ما اهمیت ندارند). ویژگی اورلودینگ در CPP به ما اجازه خواهد داد یک تابع با عنوان Add پیاده سازی کنیم که تفاوت آن ها فقط در نوع ورودی و نوع خروجی است. به عنوان مثال، در قسمت زیر، کد برنامه Add را مشاهده می کنید که با قواعد CPP بازنویسی شده است. #include <iostream> int Add(int arg_a, int arg_b) { return arg_a + arg_b; } float Add(float arg_a, float arg_b) { return arg_a + arg_b; } double Add(double arg_a, double arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { int result_int = Add(1, 2); float result_float = Add(10.02f, 21.23f); double result_double = Add(9.0, 24.3); std::cout << "Result Integer: " << result_int << std::endl; std::cout << "Result Float: " << result_float << std::endl; std::cout << "Result Double: " << result_double << std::endl; return 0; } همانطور که مشاهده می کنید، ما اکنون سه تابع با نام Add داریم. ولی شاید سوال پرسیده شود که چطور لینکر متوجه تفاوت این توابع با یکدیگر می شود درحالیکه هر سه دارای یک نام واحد هستند. اینجاست که مسئله Name Mangling یا همان Decoration نام آبجکت ها در CPP مطرح می شود. اگر شما برنامه مذکور را دیزاسمبل کنید، متوجه تفاوت کد منبع (Source-code) و کد ماشین/اسمبلی (Machine/Assembly-code) خواهید شد. همانطور که در خروجی دیزاسمبلی برنامه اکنون مشاهده می کنید، توابع اگرچه در سطح کد منبع دارای نام مشابه با یکدیگر بودند، اما بعد کامپایل نام آن ها به شکل بالا تبدیل می شود. به این شیوه نام گذاری Name Mangling یا Decoration گویند که قواعد خاصی در هر کامپایلر برای آن وجود دارد. این ویژگی موجب می شود در ادامه لینکر بتواند تمیز بین انواع توابع Add شود. به عنوان مثال، تابع نامگذاری شده با عنوان j__?Add@YAHH@Z تابعی است که به نوعی از تابع Add اشاره دارد که ورودی هایی از نوع عدد صحیح دریافت می کند. این شیوه نامگذاری خلاصه موجب خواهد شد لینکر بتواند به سادگی بین توابع تمایز قائل شود. با این حال هنوز یک مشکل باقی است، و آن هم تکرار مجدد یک پیاده سازی برای هر تابع است. به نظر شما آیا راهی وجود دارد که ما از پیاده سازی مجدد توابعی که ساختار مشابه برای انواع ورودی ها دارند، جلوگیری کنیم؟ باید بگوییم، بله. این امکان برای شما به عنوان توسعه دهنده CPP در نظر گرفته شده است. ویژگی که اکنون به عنوان Templateها در مباحث Metaprogramming یا Generic Programming استفاده می شود، ایجاد شده است تا این مشکل را اساساً برای ما رفع کند. با استفاده از این ویژگی کافی است، طرح یا الگوی یک تابع را پیاده سازی کنید، تا در ادامه خود کامپایلر مبتنی بر ورودی هایی که به الگو عبور می دهید، در Backend، یک نمونه تابع Overload شده مبتنی بر آن الگو برای نوع داده شما ایجاد کند. #include <iostream> template <typename Type> Type Add(Type arg_a, Type arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { int result_int = Add(1, 2); float result_float = Add(10.02f, 21.23f); double result_double = Add(9.0, 24.3); std::cout << "Result Integer: " << result_int << std::endl; std::cout << "Result Float: " << result_float << std::endl; std::cout << "Result Double: " << result_double << std::endl; return 0; } به عنوان مثال، در بالا تابع Add را مشاهده می کنید که نوع داده ورودی این تابع و حتی نوع خروجی آن مشخص نشده است و در قالب Typename به کامپایلر معرفی شده است. این یک الگو برای تابع Add است. کامپایلر اکنون می تواند مبتنی بر ورودی هایی که به تابع هنگام فراخوانی یا اصطلاحا Initialization عبور می دهیم، یک نمونه تابع Overload شده از آن الگو ایجاد کند و در ادامه آن را برای استفاده در محیط Runtime فراخوانی کند. حال اگر برنامه بالا را دیزاسمبل کنید، مشاهده خواهید کرد که کامپایلر از همان قاعده Overloading استفاده کرده است تا نمونه ای از تابع Add متناسب با نوع ورودی هایش ایجاد کند. هنوز می توان برنامه نویسی با CPP را جذاب تر و البته ساده تر کرد، اما چطور؟ همانطور که در قطعه کد بالا مشاهده می کنید، هنوز ما باید خود تشخیص دهیم که نوع خروجی تابع قرار است به چه شکل باشد. این مورد خیلی مواقع مشکل ساز خواهد بود. برای حل این مسئله، در CPP مبحثی در نظر گرفته شده است که آن را به عنوان Auto Deduction می شناسیم که سطح هوشمندی کامپایلر CPP را بالاتر می برد. در این ویژگی خود کامپایلر است که مشخص می کند نوع یک متغیر مبتنی بر خروجی که به آن تخصیص داده می شود، چیست. به عنوان مثال، شما می توانید برنامه بالا را به شکل زیر بازنویسی کنید: #include <iostream> template <typename Type> auto Add(Type arg_a, Type arg_b) { return arg_a + arg_b; } int main(int argc, const char* argv[]) { auto result_int = Add(1, 2); auto result_float = Add(10.02f, 21.23f); auto result_double = Add(9.0, 24.3); std::cout << "Result Integer: " << result_int << std::endl; std::cout << "Result Float: " << result_float << std::endl; std::cout << "Result Double: " << result_double << std::endl; return 0; } با استفاده از ویژگی Auto Deduction و کلیدواژه Auto در برنامه، خود کامپایلر در ادامه مشخص خواهد کرد که تابع Add چه نوع خروجی دارد و همچنین نوع متغیرها برای ذخیره سازی خروجی Add چه باید باشد. به عبارتی اکنون تابع Add هم Value و هم Data type را مشخص می کند که این موجب می شود برنامه نویسی با CPP خیلی ساده تر از گذشته شود. حال اگر به نمونه برنامه آخر نگاه کنید و آن را با نمونه C مقایسه کنید، متوجه خواهید شد که CPP چقدر کار را برای ما ساده تر کرده است. در این پست به هر صورت، قصد داشتم به شما نشان دهم که نحوه تحول CPP به صورت گام به گام چطور بوده است و البته اینکه پشت هر ویژگی در CPP چه منطق کلی وجود دارد. امیدوارم این مقاله برای شما مفید بوده باشد. نمونه انگلیسی این مقاله را می توانید در این آدرس (لینک) مطالعه کنید. میلاد کهساری الهادی
-
1 امتیازآموزش زبان برنامهنویسی سوئیفت - جلسه اول مواردی که در این جلسه یاد خواهید گرفت: مقدمه زبان برنامهنویسی سوئیفت ، نوشتن اولین دستور و معرفی متغییرها با سلام و عرض ادب خدمت شما دوستان عزیز و همراهان خوب همیشگی وبسایت آیوٌ اِسترِیم. در خدمت شما هستیم با یک دورهٔ جذاب برنامهنویسی به زبان سوئیفت! علاقهمندان زیادی به توسعهٔ محصولات و نرمافزارهای شرکت اَپل وجود دارن و از این رو با مسائلی دست به گریبان هستند که یکی از آنها؛ نبود آموزش کامل و به بیانی ساده است! مورد دوم هم که خیلی واضح هست،تَحریمه! که به جز اینکه قِشر کمدرآمد جامعه از پس خرید آنها بر نمیآیند، شامل تحریمهای دیگر هم میشود. و خیلیها بخاطر علاقه به یادگیری نمیتوانند شروع کنند به توسعهٔ محصولات نرمافزاری شگفتاَنگیز! از این رو تنها یک راه وجود داره و آن هم استفاده از نسخهٔ هَکشدهٔ سیستم عامل مَک هستش که به شما امکان استفاده از امکانات یک سیستمعامل مَک واقعی را میدهد! البته هدف این نیست که بگوییم استفاده از این روش خوب هست! بلکه برای آن دسته از عزیزانی که توانایی خریدن محصولات اَپل رو ندارند گفتیم که در غیر اینصورت اگه توانایی خرید دارید که بهترین راهش همین است که بخرید و لذت یک سیستمعامل متفاوت و جدید را داشته باشید :). خُب، اصل مطلب! اینکه در این دورهٔ آموزشی چه چیزهایی را یاد خواهید گرفت، فقط و فقط سه چیز است؛ مقدمات یادگیری Syntax ( سِینتَکس) زبان و کدنویسی مقدماتی یادگیری رابطکاربری ( User Interface ) ایجاد یک پروژهٔ ساده ماشینحساب و بعد از این مباحث هم کُلیت کار دستتان میآید و به راحتی میتوانید از منابعی مُعتبر، دانش و مهارت خودتان را بالا ببرید. زبان برنامهنویسی سوئیفت ( Swift ) چیست ؟ سوئیفت یک زبان برنامهنویسی از نوع کامپایلری برای توسعه محصولات نرمافزاری macOS , iOS , watchOS و tvOS است که توسط شرکت اَپل ساخته شده. قبل از این از زبان برنامهنویسی Objective-C برای توسعه محصولات برای موارد ذِکر شده استفاده میشد که بعد از آن این زبان جایش را به سوئیفت داد اما همچنان از Objective-C هم استفاده میشود. هدف در اینجا آموزش زبان است و شما میتوانید برای توضیحات بیشتر به مرجع این زبان، اینجا مراجعه کنید. شروع کدنویسی برای شروع کدنویسی به زبانبرنامهنویسی سوئیفت میتونید از یک برنامه موبایل هم حتی استفاده کنید! البته تنها در بخش مقدماتی. نام این نرمافزار موبایلی Sedona Swift Compiler است که میتوانید از فروشگاه Play دانلود و نصب کنید. یا از نرمافزاری ساده بر روی ویندوز خود کُدنویسی را شروع کنید که این کار برای سیستمعام لینوکس هم صِدق میکند که با نصب یک بسته میتوانید در لینوکس هم کُدنویسی با این زبان شیرین و ساده را شروع کنید. یا در نهایت اگر سیستمعامل مک دارید که چه بهتر و اگر ندارید از نسخهٔ هکشدهٔ آن استفاده کنید که عرض کردیم در مواقعی که واقعا چارهای ندارید!( الخصوص برای بخش رابط کاربری ( User Interface ) ). اولین چیز در هر زبان برنامهنویسی که آموزش داده میشود؛ سلام جهان! ( !Hello World ) همیشگی است?!. پس این کُد ساده را ببینید که این پیغام را به راحتی در کنسول چاپ میکند: print("Hello World!") به همین سادگی که مشاهده کردید، با استفاده از متد print پیغام Hello World را چاپ کردیم. اگر این دستور رو اجرا کنید، با همین پیغام در کنسول ویرایشگر خود مواجه میشوید. پس در نتیجه، این تابع برای چاپ مقادیر در سوئیفت هست. شاید دقت کرده باشید که سِمیکالُن نذاشتیم! سوئیفت این اجازه رو میدهد که بدون گذاشتن سِمیکالُن برنامهی خودتان را اجرا کنید که البته بگذارید هم مسئلهای پیش نمیآید، مگر در موقعی که چندین دستور در یک خط داشته باشید که آن موقع واجب میشود و باید حتما بگذارید، وگرنه کامپایلر اخطار خواهد داد! تابع print یک تابع سراسری در سوئیفت است که چندین آرگومانت دریافت میکند و اساس کار آن، چاپ اطلاعاتی است که به آن میدهیم. و چندین آرگومانتهای پیشفرض هم دریافت میکند که میتوانیم بسته به نیاز از آنها استفاده کنیم. در اولین آرگومانت، ما میتوانیم تا هر چند مقدار یا همان آیتمها، به آن بدهیم و در خروجی نمایش دهیم. به این شکل که میبینید: print("www.iostream.ir", "www.fanoox.com", item3, item4, ...) در آرگومانت دوم، که separator است، میتوانیم مشخص کنیم که اگر اطلاعات زیادی میخواهیم به خروجی ارسال و یا همان نمایش دهیم، در بین هر کدام از این اطلاعات، چه نمادی/علامتی قرار گیرد؟. که ما میتونیم اون نماد/علامت رو در بین دو "" ( دابل کوتیشن ) مشخص کنیم. به عنوان مثال: print("www.iostream.ir", "www.fanoox.com", separator : " :)) ") // Output the ==> www.iostream.ir :)) www.fanoox.com همانطور که مشاهده کردید، میتوانیم از هر نماد/علامتی که نیاز داشتیم در بین انبوهی از دادهها استفاده کنیم. در آرگومانت سوم، میتوانیم مشخص کنیم که اطلاعات در خط بعدی ( New line ) چاپ شوند یا در همان خط فعلی نمایش داده شوند! که به صورت زیر است: print("www.iostream.ir", terminator : "") print("www.fanoox.com") /*Output the => www.iostream.ir www.fanoox.com that not of include is new line */ در حالت پیشفرض تابع به صورت "terminator : "\n است که به معنی " در در پایان چاپ اطلاعات، اطلاعات دیگر را که بعد از این مقادیر چاپشده میآیند را چاپ کن ". برای تعریف متغییر در سوئیفت به دو صورت میتوانید عمل کنید: تعریف بدون تعیین نوع تعریف با تعیین نوع همچنین ما دو نوع متغییر داریم: متغییری که مقدارش میتواند در ادامهٔ برنامه تغییر کند متغییری که مقدارش فقط در هنگام تعریف مشخص و قابل تغییر در سراسر برنامه نیست ( ثابتها ) برای تعریف متغییر بدون نوع به این صورت عمل میکنیم: var website_name = "www.iostream.ir" print(website_name) // or print("The website name is \(website_name)") همانطور که مشاهده میکنید تعریف یک متغییر که همواره مقدارش تغییر کنید با کلمهٔ کلید var تعریف میشود و بعد از آن نام و مقدار آن میآید. در این حالت کامپایلر به صورت ضمنی خودش از روی مقدار، نوع متغییر را متوجه میشود و اگر شما این دستور را بنویسید: var website_name = "www.iostream.ir" print(type(of : website_name )) // or print("Type is => \(type(of : website_name )") // Result => String به شما مقدار String یا همان رشتهای را نمایش میدهد. اما در حالت تعیین نوع برای متغییر به این صورت است: var website_name : String = "www.iostream.ir" print(webiste_name) // or print("The website_name is \(website_name)") که شما صراحتا ( دستی ) نوع را مشخص کردید و کامپایلر در اینجا و در ادامهٔ برنامهٔ مقدار غیری از String ( رشته ) را قبول نمیکند. همانطور که مشاهده میکنید، برای تعریف نوع برای متغییر باید از دو کالُن ( : ) استفاده کنید و سپس حرف اول نوع متغییر را بزرگ بنویسم ( البته در این زبان ) مانند؛ Int و سپس با گذاشتن علامت انتساب ( = ) مقدار مورد نظر خود را به آن اختصاص دهیم. نکتهای دیگری که وجود دارد در چاپ کردن مقادیر است که شما میتوانید با دو روش فوق که ذکر شده است، مقادیر را چاپ کنید که یکی بصورت آیتم به آیتم یعنی print( item1, item2, item3 , ...) و تا هر چند آیتم را که حاوی مقادیر هستند به خروجی بفرستید و نمایش دهید و اما در حالت دوم باید Syntax متفاوتی استفاده کنید و آن هم ادغام رشته با مقادیر متغییرهاست! که مثالش را بالا برای شما عزیزان زدهام و مقادیر متغییرها باید بین دو پرانتز و قبل پرانتز از یک بکاسلش رو به عقب استفاده کنید!. و اما برویم به سراغ ثابتها! از اسم این متغییرها واضح است که یک بار تعریف میشوند و مقدار ثابتی دریافت میکنند و در ادامهٔ برنامه و یا رَوند پروژه هیج تغییری نمیکنند و در طول برنامه یا پروژه مقادیرشان ثابت است! بیاید با یک مثال شروع کنیم؛ ثابتها در سوئیفت با کلمهٔ کلید let تعریف میشوند و همانند متغییرها شامل نام و نوع هم هستند: let _website_name = "www.iostream.ir" _webiste_name = "iostream.ir" // Error , beacuse it's a constant print(_website_name) // or print("The website name => \(_website_name)") همانطور که میبینید، تغییر دادن مقادیر ثابتها باعث بروز خطا میشود و اجازهٔ چنین کاری به شما داده نخواهد شد. برای تعریف با تعیین نوع هم به این شکل میتوایند عمل میکنید: let _number : Int = 20 _number = 40 // Error , beacuse it's a constant print(_number) // or print("The number => \(_number)") امیدواریم این جلسه مورد رضایت شما عزیزان قرار گرفته باشد.
-
1 امتیازآموزش زبان برنامهنویسی سوئیفت - جلسه ششم مواردی که در این جلسه یاد خواهید گرفت: آرایهها ( Arrays ) آرایهها چیستند؟ اگر شما یک بسته کِبریت را تصور کنید، حداقل تا 50 تا دانه کبریت داخل آن است! همهی آنها هم همه کبریت هستند نه چیز دیگر. البته میشود چیز دیگری هم گذاشت داخل آن اما در برنامهنویسی نمیتوانید همچین کَلکی را سوار کنید! ( در بعضی از زبانهای برنامهنویسی ). آرایهها در سوئیفت مجموعهای از مقادیر را داخل خودشان نگهداری میکنند و مانند یک متغییر میمانند با این تفاوت که یک متغییر معمولی یک مقدار رو میتواند ذخیره کند ولی متغییر آرایهای اینگونه نیست. به این دنبالهی عددی دقت کنید؛ 1,2,3,4,5,6,7,8,9,10 بهنظر شما میشود اینها را در یک متغییر معمولی ذخیره کرد؟ به طوری که هر کدام از عددها را که بخواهیم سریع به ما بدهد؟ مسلما خیر. تازه اگر شما بخواهید برای هر کدوم از این اعداد، یه متغییر جداگانه تعریف کنید؛استاندارد پروژهتان به شدن پایین میآید و زبانی هم که با آن برنامهنویسی میکنید به همان مقدار به شما واکنش نشان خواهد داد ( هر کُنشی واکنشی دارد...!? ) کاهش سرعت اجرا، زیادنویسی، اگه برای سایر توسعهدهندههای دیگر باشد، به احتمال زیاد طرف پروژهی شما نمیآیند! به عنوان مثال، تعریف متغییر بدون آرایه؛ let number_one : Int8 = 1 let number_two : Int8 = 2 let number_three : Int8 = 3 let number_four : Int8 = 4 let number_five : Int8 = 5 let number_six : Int8 = 6 let number_seven : Int8 = 7 let number_eight : Int8 = 8 let number_nine : Int8 = 9 let number_ten : Int8 = 10 خودتان باشید، حاضرید به این شکل پروژهای را به این شکل پیش ببرید؟! حالا فکر کنید ۱۰۰۰ دادهی مختلف بخواهید تعریف کنید! تعجب میکنید، اینطور نیست؟! اما خوشبختانه همیشه راه نجات هست... آن هم استفاده از آرایهها است! آرایهها به شما کمک میکنند تا دادههای زیادی که مورد نیاز پروژهی شما باشد، تعریف و استفاده کنید. نحوهی تعریف به سه روش میتوانید آرایههای خودتان را تعریف کنید؛ استفاده از نوع و سازنده ( Construct ) استفاده از کلاس Array، نوع متغییر و سازنده تعریف آرایه بدون هیچ واسطهای! نکته آرایههای سوئیفت از موقعیت ( Index ) صفر ( ۰ ) شروع خواهند شد. در ادامه متوجه این موضوع خواهید شد، اما همیشه این را به خاطر داشته باشید که اولین عضو در یک آرایه در موقعیت صفرم آن آرایه قرار میگیرد و به همین ترتیب، موقعیت اول، حاکی از مقدار دوم، موقعیت دوم، مقدار سوم و الی آخر... پس این مورد را در پروژههایتان حواستان باشد! چرا که با یک اشتباه، ساعتها زمان و انرژی خود را تَلف میکنید تا یک اشتباه کوچک را پیدا و رفع کنید. اولین مورد که در لیست بالا گفته شده، به این صورت است که شما یک متغییر تعریف میکنید ( از کلمات کلیدی var و یا let میتوانید استفاده کنید) و بعد یک نام شفاف و دلخواه خواهید نوشت و در نهایت از نوع و سازنده به این شکل استفاده میکنید؛ var first_array = [Int]() print(first_array) // Output is empty or [] در روش دوم میتوانید از کلاس Array استفاده کنید! به این شکل که مشاهده میکنید؛ var first_array = Array<Int>() print(first_array) // Output is empty or [] و در نهایت میتوانیم بدون هیچ واسطهای ( بدون استفاده روش اول و دوم )، آرایهمان را تعریف کنیم؛ var first_array = [1,2,3,4,5,6,7,8,9,10] print(first_array) // Output is [1,2,3,4,5,6,7,8,9,10] به نظر میآید که این برای شما راحتتر و قابلدرکتر باشد تا موارد قبل، اینطور نیست؟ اما خُب، آنها را هم برای دوستانی که عاشق پیچدگی هستن نوشتیم! شما میتوانید از هر روشی که مدنظرتان بود استفاده کنید. نوع دادهای هم که مشخص میکنید، بستگی به پروژهی شما دارد؛ var first_array = ["www.iostream.ir","www.fanoox.ir","www.ModernCpp.ir"] print(first_array) // Output is empty or ["www.iostream.ir", "www.fanoox.ir", "www.ModernCpp.ir"] همینطور شما میتوانید از مقادیری پیشفرض برای آرایههایتان ستفاده کنید! به عنوان مثال شما در همان موقعی که تعریف آرایهی خودتان را انجام میدهید، میخواهید تا ۱۰ خانه از آرایهتان دادههای تکراری داشته باشد. اینکار را میتوانید با استفاده از سازنده ( Construct ) انجام بدهید؛ var first_array = [Int](repeating: 20, count: 10) print(first_array) // Output is [20,20,20,20,20,20,20,20,20,20] در کد بالا گفته شدهکه عدد ۲۰ را ( repeating: 20 ) به تعداد ۱۰ بار ( count: 10 ) در آرایهی first_array قرار گیرد. خروجی را همانطور که مشاهده میکنید، مقدار ۲۰ به تعداد ۱۰ بار در first_array تکرار شده. ترکیب دو آرایه با هم برای اینکار فقط کافی است با استفاده از آپِریتر +، یک آرایهی جدید بدست بیاوریم؛ var first_array = [1,2,3,4,5] var two_array = [6,7,8,9,10] var result_array = first_array + two_array print(first_array) // Output is [1,2,3,4,5,6,7,8,9,10] دسترسی به کُل یا یک از دادههای آرایه برای اینکه بتوانید به کل آرایه دسترسی داشته باشید و آن رو نمایش بدهید، میتوانید به این صورت عمل کنید؛ var first_array = [1,2,3,4,5] print(first_array) // Ouput is [1,2,3,4,5] البته میتوانید در مواقعی که نیاز به آرایه کامل دارید استفاده کنید! به عنوان مثال پارامتر توابع و ... برای اینکه بتوانید به یک دادهی خاص که موردنظر شما است، دسترسی داشته باشید؛ باید از موقعیت ( Index ) آرایه استفاده کنید؛ var website_name = ["www.iostream.ir","www.fanoox.com","www.ModernCpp"] var langauge_name = ["C++","C","Swift"] print(website_name[0]) // Output is www.iostream.ir print(langauge_name[2]) // Ouput is Swift در قطعه کد بالا، ابتدا باید نام آرایه بیاورید و بعد از آن با استفاده از دو براکت ( [] ) و قرار دادن شماره موقعیت، به مقدار آن دسترسی داشته باشید. همانطور هم که مشاهده میکنید، گفتیم که آرایهها از موقعیت صفرم شروع میشوند که در بالا با آوردن website_name و سپس شماره موقعیت ( Index Number ) به اولین عضو آن یعنی www.iostream.ir دسترسی پیدا کرده و آن را نمایش میدهیم. اگر از ۲ برای آرایه استفاده کنید، به شما خروجی Swift را خواهد داد! چرا که عضو دوم آرایهی langauge_name مقدار Swift است. اضافه کردن داده به آرایه اگه بخواهیم یک عضو ( مقدار جدید ) به آرایه اضافه کنیم، از متُد append که مربوط به کلاس آرایهها است، استفاده میکنیم. در جلسههای بعد با کلاسها، و متدها آشنا خواهید شد. به این مثال توجه کنید؛ var first_array = [1,2,3,4,5] // Before print("Before \(first_array)") // Ouput is [1,2,3,4,5] first_array.append(6) // After print("After \(first_array)") // Output is [1,2,3,4,5,6] با استفاده از موقعیت ( Index ) میتوانیم یک مقدار را به یک موقعیت یا همان خانهای از آرایه که مشخص کردیم، اضافه کنیم؛ var first_array = [1,2,3,4,5] first_array[0] = [10] print(first_array) // Output is [10,2,3,4,5] یا میتوانیم از آپِریتر ترکیبی =+ استفاده کنیم برای اضافه کردن گروهی از دادهها؛ var first_array = [1,2,3,4,5] // Before print(first_array) // Ouput is [1,2,3,4,5] first_array += [6,7,8,9,10] // After print(first_array) // Output is [1,2,3,4,5,6,7,8,9,10] با استفاده از متد insert میتوانید یک عضو جدید را به موقعیتی ( Index ) که مدنظرتان است، اضافه کنید! به عنوان مثال در آرایهی زیر، میخواهید که عدد ۳، در خانه یا موقعیت آخر قرار بگیرد؛ var first_array = [1,2,3,4,5] // Before print("Before \(first_array)") // Ouput is [1,2,3,4,5] first_array.insert(6, at: 4) // After print("After \(first_array)") // Output is [1,2,3,4,5,6] این متد دو پارامتر دریافت میکند؛ یکی برای عضو جدید و یکی دیگر برای موقعیتی که میخواهید عضو جدید قرار بگیرد که همانطور که مشاهده میکنید، مقدار ۶ در موقعیت آخر قرار گرفته است که مقدار ۵ را دارد. حذف یک عضو برای حذف یک مقدار از آرایه، از متدهای remove و removeLast استفاده میکنیم. اولین متد؛ یک موقعیت مشخص برای حذف عضو میخواهد، در حالی که متد دوم؛ آخرین عضو را حذف میکند؛ var first_array = [1,2,3,4,5] // Before remove print("Before \(first_array)") // Ouput is [1,2,3,4,5] first_array.remove(at: 0) // After remove print("After \(first_array)") // Output is [2,3,4,5] // Using removeLast first_array.removeLast() print(first_array) // Output is [2,3,4] با استفاده از at: 0 در متد remove مشخص کردیم که میخواهیم عضو اول را حذف کنیم که در خروجی هم میتوانید ببینید و در متد removeLast آخرین عضو آرایه ( در این مثال 5 ) رو حذف کردیم. و در نهایت شما میتوانید در یک رِنج ( بازه ) مشخص؛ که تعیین میکنید، عضو اضافه کنید! چیزی شبیه به حلقهی for! این مثال را ببینید؛ var first_array = [1,2,3,4,5] first_array[0...4] = [6,7,8,9,10] print("Before \(first_array)") // Ouput is [6,7,8,9,10] گردش در آرایه گردش یعنی اینکه بتوانیم بین اعضای یک آرایه که یکی یکی آنها را انتخاب میکنیم، کارهایی را که مدنظرمان است را انجام دهیم! که با استفاده از یک حلقهی for میتوانیم در عضوهای آن این کار را انجام دهیم. به این مثال توجه کنید؛ var first_array = [1,2,3,4,5] for item in first_array { print("Item : \(item)") } /* Item : 1 Item : 2 Item : 3 Item : 4 Item : 5 */ اگه بخواهیم که هم موقعیت ( Index ) و هم مقدار آرایه رو داشته باشیم؛ از متد enumerated استفاده میکنیم که به ما یک تاپِل ( Tuple ) خواهد داد ( در جلسات بعد بعدا مفصلا دربارهی تاپل صحبت خواهیم کرد) و در حلقه استفاده میکنیم؛ var first_array = [1,2,3,4,5] for (index,item) in first_array.enumerated() { print("Index of : \(index) and Item : \(item)") } /* Index of : 0 and Item : 1 Index of : 1 and Item : 2 Index of : 2 and Item : 3 Index of : 3 and Item : 4 Index of : 4 and Item : 5 */ Count و isEmpty ( دو ویژگی از کلاس آرایه ) شاید بخواهید بدانید تعداد عضوهای یک آرایه به چه تعدا هستند؟ این مورد به خصوص در حلقهها و یا موقعیتهای دیگر بسیار موثر خواهد بود! برای اینکار از ویژگی ( Property ) به نام count استفاده میکنیم که فقط خواندنی ( read-only ) است و هیچ مقداری رو نمیتوانید به آن ضافه کنید! ( درمورد ویژگیها مفصلا در جلسات بعد توضیح خواهیم داد ) و فقط میتوانید از مقدار آن که تعداد اعضای آرایه است، استفاده کنید؛ var first_array = [1,2,3,4,5] print(first_array.count) // Output is 5 اگه هم میخواهید خالی بودن یا نبودن یک آرایه را بررسی کنید؛ متد isEmpty این امکان را در اختیار شما قرار داده است؛ var first_array = [1,2,3,4,5] if first_array.isEmpty { print("Empty") } else { print("Not Empty") } // Not Empty امیدواریم که این جلسه مورد رضایت شما عزیزان باشد.
-
1 امتیازبا سلام وقت بخیر, در این مطلب میخواهیم در مورد روش کارکرد پیام رسان ها بیشتر بدانیم و با یکدیگر کد یک پیام رسان ساده را پیاده و بررسی کنیم. طرز کار کرد پیام رسان در نظر داشته باشید که هر پیام رسانی که بر ساختار ها پیاده شده باشد از دو قسمت تشکیل شده است. نرم افزار اصلی برای مدیریت درخواست ها (سرور) نرم افزار برای کاربران (کاربر) به نرم افزار اولی سمت SERVER خواهیم گفت و به بعدی سمت CLIENT خواهیم گفت. روم یا تالار گفتگو ما تنها یک اتاق برای گفتگو در نظر میگیریم و هر کاربری که به سرور متصل شود را در همان تالار اضافه خواهیم کرد. تالار های گفتگو صرفا برای تقکیک سازی ارسال و دریافت ها و محدود کردن بازه ی کاربران مورد نظر... (ممکن است یک کاربر در چند اتاق بطور همزمان باشد.) سیستم های پیام رسان پیشرفته تر مانند تلگرام و ... تالار های زیادی را شامل می شوند. (هر کاربر خودش در کانال و گروه های مختلفی عضو است که هر کدام از آنها یک کانال متفاوت محسوب می شوند.) * دقت شود که منظور از کانال صرفا یک اتاق یا تالار گفتگو است و منظور کانال ارتباطی و پروتکل نیست. نرم افزار اصلی نرم افزار اصلی وظیفه دارد تا تمام کاربرانی که وارد تالار شده اند را به یاد داشته باشد و هر لحظه اماده دریافت درخواست هایی از طرف کاربرانش باشد. و پیام هایی را که از کاربران دریافت می کند برای تمامی کاربران دیگر هم ارسال کند که بسته به خلاقیت و نیاز می تواند هر یک از این بخش ها متفاوت طراحی شود. نرم افزار اصلی باید از قبل اجرا شده باشد. تا کاربران دیگر با استفاده از نرم افزار مخصوص به خودشان بتوانند به سرور متصل شوند و ارسال و دریافت داشته باشند. در نظر داشته باشید که اگر در نرم افزار اصلی اختلالی پیش بیایید و متوقف بشوند. قطا برای تمام کاربران مشکل پیش می آید. مگر اینکه از پایگاه های داده ی داخلی استفاده کرده باشند. (با خلاقیت می توان به گونه های متفاوتی چنین ساختاری را پیاده کرد) نرم افزار کاربران این نرم افزار جذاب ترین بخش است چرا تمام قابلیت هایی را که در اختیار کاربر قرار می دهیم را مستقیما طراحی می کنیم. در نظر داشته باشید که هر چیزی که در این نرم افزار طراحی می شود باید در نرم افزار اصلی پشتیبانی شوند... بنابراین اگر این دو بخش توسط دو فرد یا دو گروه مجزا طراحی می شوند آنها باید توسط داکیومنت ها و جلساتی به نظرات مشابه ای رسیده باشند. (اگرچه اینها تخصص و وظیفه ی تحلیلگر سیستم است! نه بطور همزمان وظیفه توسعه دهنده و برنامه نویس نرم افزار) پیاده سازی یک نمونه اکنون در نظر داریم تا با استفاده از ساختار کتابخانه BoostAsio پروژه ای را با نام BoostAsioChat ایجاد کنیم که در آن می خواهیم یک پیام رسان با حداقل ترین امکانات پایه طراحی کنیم که بیشتر جنبه شخصی و تفریحی دارد. زیرا از ساختار های استاندارد و ایمن و کاربری کاملا بدور است! (می توانید خودتان توسعه دهید و آنرا جالب تر بسازید) ساختار نرم افزار اصلی و سرور را به این صورت تعریف می کنیم : typedef deque<message> messageQueue; class participant { public: virtual ~participant() {} virtual void deliver(const message& messageItem) = 0; }; typedef shared_ptr<participant> participantPointer; class room { public: void join(participantPointer participant); void deliver(const message& messageItem); void leave(participantPointer participant); private: messageQueue messageRecents; enum { max = 200 }; set<participantPointer> participants; }; class session : public participant, public enable_shared_from_this<session> { public: session(tcp::socket socket, room& room) : socket(move(socket)), room_(room); void start(); void deliver(const message& messageItem); private: void readHeader(); void readBody(); void write(); tcp::socket socket; room& room_; message messageItem; messageQueue Messages; }; class server { public: server(boost::asio::io_context& io_context, const tcp::endpoint& endpoint) : acceptor(io_context, endpoint); private: void do_accept(); tcp::acceptor acceptor; room room_; }; int main(int argc, char* argv[]); ساختار نرم افزار کاربر را هم به این صورت تعریف می کنیم : typedef deque<message> messageQueue; class client { public: client(boost::asio::io_context& context, const tcp::resolver::results_type& endpoints) : context(context), socket(context); void write(const message& messageItem); void close(); private: void connect(const tcp::resolver::results_type& endpoints); void readHeader(); void readBody(); void write(); boost::asio::io_context& context; tcp::socket socket; message readMessage; messageQueue writeMessage; }; int main(int argc, char* argv[]); در نظر داریم تا در این پروژه از thread ها نیز استفاده کنیم... در مورد این مفهوم ها می توانید بصورت مجزا تحقیق کنید. بنابراین روش کامپایل این پروژه به این صورت خواهد بود : $ g++ client.cpp -lpthread -o client $ g++ Server.cpp -lpthread -o server آزمایش همانطور که گفته شد در ابتدا نرم افزار اصلی و سرور باید اجرا شود. در اینجا ما تمام ارتباطات شبکه را بر روی یک سیستم در شبکه داخلی برقرار خواهیم کرد... پس نگرانی در مورد ساختار های درونی شبکه و آی پی / دی ان اس / دامین نخواهیم داشت. بنابراین ای پی را می توانید ای پی داخلی یا localhost در نظر بگیرید. برای آزمایش پورت فرضی 4000 را در نظر میگیریم و نرم افزار اصلی را روی این پورت اجرا میکنیم : $ ./server 4000 در این مرحله متوجه می شوید که نرم افزار اصلی با موفقیت اجرا شده است و همچنان اجرا مانده است. بله درست است... نرم افزار اصلی هر لحظه باید منتظر دستور کاربران باشد. اگر لحظه ای برای نرم افزار اصلی اختلالی پیش آید نخواهد توانست دستورات کاربران را انجام یا پاسخ دهد. بنابراین این پردازش را قطع نکنید و اجازه دهید تا نرم افزار اصلی اجرا بماند. در محیط دیگری نرم افزار سمت کاربر را نیز اجرا کنید. این نرم افزار را می توانید به تعداد دلخواه وارد کنید. (همانطور که ممکن است 6 نفر همزمان به سرور متصل باشند / یا ممکن است هیچ فردی به سرور متصل نشوند) ابتدا یک کاربری را به سرور با پورت 4000 و شبکه داخلی وصل می کنیم : $ ./client localhost 4000 first user: you can type message here... حال در محیط دیگری با کاربر جدیدی نیز وارد می شویم : $ ./client localhost 4000 second user: you can type message here... در این پروژه نمونه از کاربران نام کاربری یا نام نمی پرسیم.. و صرفا وقتی وارد محیط گفتگو می شوند... یا زمانی که به سرور متصل می شوند منتظر هستیم تا انها پیامی را بنویسند... هر پیامی را که بنویسند به سرور ارسال می شود و سرور وظیفه دارد تا آنرا برای تمام کاربران بفرستد. و این روند درون یک حلقه بی نهایت تکرار می شوند. پس این ارتباط دو طرفه خواهد بود و هم کاربران برای سرور اطلاعات ارسال می کنند و هم سرور برای کاربران اطلاعات ارسال خواهد کرد. در نظر داشته باشید که کاربر اول می تواند پیامی را بنویسد و به کاربران دیگر ارسال شود. ممکن است کاربر سومی اصلا تصمیمی به نوشتن پیام نداشته باشد و صرفا تمایل به خواندن پیام دیگران داشته باشند. و این کاملا اختیاری است. و ما کاربران را اجباری نمیکنیم. اگرچه شما می توانید با خلاقیت خودتان اینها را با متغییر های کمکی و دستورات شرطی پیاده کنید. کد ها برای پیام ها یک ساختار در نظر میگیریم و بصورت مشترک در هر دو نرم افزار استفاده خواهیم کرد... بنابراین اینرا در هدر پیاده خواهیم کرد. هدر پیام : (message.hpp) #ifndef message_HPP #define message_HPP #include <cstdio> #include <cstdlib> #include <cstring> using namespace std; class message { public: enum { headerLength = 4 }; enum { maxBodyLength = 512 }; message() : bodyLength_(0) { } const char* data() const { return data_; } char* data() { return data_; } size_t length() const { return headerLength + bodyLength_; } const char* body() const { return data_ + headerLength; } char* body() { return data_ + headerLength; } size_t bodyLength() const { return bodyLength_; } void bodyLength(size_t new_length) { bodyLength_ = new_length; if(bodyLength_ > maxBodyLength) bodyLength_ = maxBodyLength; } bool decodeHeader() { char header[headerLength + 1] = ""; strncat(header, data_, headerLength); bodyLength_ = atoi(header); if(bodyLength_ > maxBodyLength) { bodyLength_ = 0; return false; } return true; } void encodeHeader() { char header[headerLength + 1] = ""; sprintf(header, "%4d", static_cast<int>(bodyLength_)); memcpy(data_, header, headerLength); } private: char data_[headerLength + maxBodyLength]; size_t bodyLength_; }; #endif نرم افزار اصلی و سرور : (server.cpp) #include <iostream> #include <cstdlib> #include <deque> #include <memory> #include <list> #include <set> #include <utility> #include <boost/asio.hpp> #include "message.hpp" using boost::asio::ip::tcp; using namespace std; typedef deque<message> messageQueue; class participant { public: virtual ~participant() {} virtual void deliver(const message& messageItem) = 0; }; typedef shared_ptr<participant> participantPointer; class room { public: void join(participantPointer participant) { participants.insert(participant); for(auto messageItem: messageRecents) participant->deliver(messageItem); } void deliver(const message& messageItem) { messageRecents.push_back(messageItem); while(messageRecents.size() > max) messageRecents.pop_front(); for(auto participant: participants) participant->deliver(messageItem); } void leave(participantPointer participant) { participants.erase(participant); } private: messageQueue messageRecents; enum { max = 200 }; set<participantPointer> participants; }; class session : public participant, public enable_shared_from_this<session> { public: session(tcp::socket socket, room& room) : socket(move(socket)), room_(room) { } void start() { room_.join(shared_from_this()); readHeader(); } void deliver(const message& messageItem) { bool write_in_progress = !Messages.empty(); Messages.push_back(messageItem); if(!write_in_progress) { write(); } } private: void readHeader() { auto self(shared_from_this()); boost::asio::async_read(socket, boost::asio::buffer(messageItem.data(), message::headerLength), [this, self](boost::system::error_code ec, size_t) { if(!ec && messageItem.decodeHeader()) { readBody(); } else { room_.leave(shared_from_this()); } }); } void readBody() { auto self(shared_from_this()); boost::asio::async_read(socket, boost::asio::buffer(messageItem.body(), messageItem.bodyLength()), [this, self](boost::system::error_code ec, size_t) { if(!ec) { room_.deliver(messageItem); readHeader(); } else { room_.leave(shared_from_this()); } }); } void write() { auto self(shared_from_this()); boost::asio::async_write(socket, boost::asio::buffer(Messages.front().data(), Messages.front().length()), [this, self](boost::system::error_code ec, size_t) { if(!ec) { Messages.pop_front(); if(!Messages.empty()) { write(); } } else { room_.leave(shared_from_this()); } }); } tcp::socket socket; room& room_; message messageItem; messageQueue Messages; }; class server { public: server(boost::asio::io_context& io_context, const tcp::endpoint& endpoint) : acceptor(io_context, endpoint) { do_accept(); } private: void do_accept() { acceptor.async_accept([this](boost::system::error_code ec, tcp::socket socket) { if(!ec) { make_shared<session>(move(socket), room_)->start(); } do_accept(); }); } tcp::acceptor acceptor; room room_; }; int main(int argc, char* argv[]) { try { if(argc < 2) { cerr << "Usage: server <port> [<port> ...]\n"; return 1; } boost::asio::io_context io_context; list<server> servers; for(int i = 1; i < argc; ++i) { tcp::endpoint endpoint(tcp::v4(), atoi(argv[i])); servers.emplace_back(io_context, endpoint); } io_context.run(); } catch (exception& e) { cerr << "Exception: " << e.what() << "\n"; } return 0; } نرم افزار دوم و سمت کاربر : (client.cpp) #include <iostream> #include <thread> #include <cstdlib> #include <deque> #include <boost/asio.hpp> #include "message.hpp" using boost::asio::ip::tcp; using namespace std; typedef deque<message> messageQueue; class client { public: client(boost::asio::io_context& context, const tcp::resolver::results_type& endpoints) : context(context), socket(context) { connect(endpoints); } void write(const message& messageItem) { boost::asio::post(context, [this, messageItem]() { bool write_in_progress = !writeMessage.empty(); writeMessage.push_back(messageItem); if(!write_in_progress) { write(); } }); } void close() { boost::asio::post(context, [this]() { socket.close(); }); } private: void connect(const tcp::resolver::results_type& endpoints) { boost::asio::async_connect(socket, endpoints, [this](boost::system::error_code ec, tcp::endpoint) { if(!ec) { readHeader(); } }); } void readHeader() { boost::asio::async_read(socket, boost::asio::buffer(readMessage.data(), message::headerLength), [this](boost::system::error_code ec, size_t) { if(!ec && readMessage.decodeHeader()) { readBody(); } else { socket.close(); } }); } void readBody() { boost::asio::async_read(socket, boost::asio::buffer(readMessage.body(), readMessage.bodyLength()), [this](boost::system::error_code ec, size_t) { if(!ec) { cout.write(readMessage.body(), readMessage.bodyLength()); cout << "\n"; readHeader(); } else { socket.close(); } }); } void write() { boost::asio::async_write(socket, boost::asio::buffer(writeMessage.front().data(), writeMessage.front().length()), [this](boost::system::error_code ec, size_t) { if(!ec) { writeMessage.pop_front(); if(!writeMessage.empty()) { write(); } } else { socket.close(); } }); } boost::asio::io_context& context; tcp::socket socket; message readMessage; messageQueue writeMessage; }; int main(int argc, char* argv[]) { try { if(argc != 3) { cerr << "Usage: client <host> <port>\n"; return 1; } boost::asio::io_context context; tcp::resolver resolver(context); auto endpoints = resolver.resolve(argv[1], argv[2]); client c(context, endpoints); thread t([&context](){ context.run(); }); char line[message::maxBodyLength + 1]; while(cin.getline(line, message::maxBodyLength + 1)) { message messageItem; messageItem.bodyLength(strlen(line)); memcpy(messageItem.body(), line, messageItem.bodyLength()); messageItem.encodeHeader(); c.write(messageItem); } c.close(); t.join(); } catch (exception& e) { cerr << "Exception: " << e.what() << "\n"; } return 0; } این پروژه آزمایشی بصورت رایگان و اوپن سورس در اینترنت بخصوص اینجا وجود دارد و می توانید آنرا مستقیما بصورت کامل دانلود کنید. با تشکر, Max Base / مکس بیس
-
1 امتیازآموزش زبان برنامهنویسی سوئیفت - جلسه پنجم مواردی که در این جلسه یاد خواهید گرفت: متغییر آپشِنال (Optionals Variable ) ، رشتهها و کارکترها (String & Character ) متغییرهای آپشِنال ( Optionals Variable ) چیستند؟ این نوع متغییرها، برای مدیریت دادهی شما استفاده میشوند! همانطور که از نام آنها هم مشخص است، به معنی اختیاری! یعنی یا یک مقدار وجود دارد یا ندارد! در سوئیفت، شما باید بعد از تعریف یک متغییر مقدار آن را هم تعیین کنید و نمیتوانید آن را بدون مقدار به حال خود رها کنید! به عنوان مثال کُد زیر را ببینید: var website_name print(website_name) // Error compiler در صورتی که این کد را اجرا کنید، با خطای کامپایلر مواجه میشوید. دلیلش هم آن است که در این زبان، باید مقدار هر متغییر را باید بلافاصله بعد از تعریف آن بدهید و حالت پیشفرض نخواهیم داشت ( در برخی از زبانها حالت پیشفرضی برای متغییرها در نظر گرفته میشود مانند ++C ) این موضوع با نوع هم بخواهید تعریف کنید باز هم به خطای کامپایلر خواهید خورد: var website_name : String print(website_name) // Error compiler اما اگر به آن مقدار خالی ( "" => دابل کوتیشن ) بدهید، خروجی را برای شما نمایش خواهد داد، اما فقط خالی!: var website_name : String = "" print(website_name) // Print empty این برای همهی نوعهای دادهی دیگر هم صدق میکند. اما هر دفعه در کدهای خودتان برای بررسی کردن آنها باید شرط بگذارید ( if,else,ifelse و .. ) که ببینید کداممتغییر مقدار دارد یا ندارد. از این گذشته، با اینکار کدهای تمیزی هم نخواهید داشت. پس اینجا باید از آپشنالها استفاده کنیم. به این قطعه کد دقت کنید: var website_name : String? // website_name is automatically set to nil if name == nil{ print("website_name has nil value") } در مثال بالا، شما تعریف متغییر آپشنال ( Optional ) را میبینید که با علامت سوال ( ? ) همراه است. ابتدا شما باید، کلمهی کلیدی var را همراه با یک اسم، بیاورید و بعد با استفاده از دو نقطه ( : ) که کالُن در برنامهنویسی خوانده میشود استفاده کنید. و درنهایت به همراه نوع داده و علامت سوال را در جهت انگلیسی ( ? ) قرار میدهید. متغییر شما در حال حاضر مقدار nil را دارد که به صورت اُتوماتیک، به متغییر شما داده شده است که خود شما هم میتوانید این مقدار را برای تمای متغییرهای آپشنال خودتان قرار دهید. nil به معنای هیچ یا هیچچیز است. یعنی الان هیچ دادهای ندارید. حالا شما میتوانید هر زمانی که خواستید، به متغییر خود مقدار بدهید. نکته متغییر آپشنال شما به اصطلاح ( Wrapper ) به معنی بستهبندی، روپوش شده است! یعنی اینکه دادههای متغییر شما بستهبندی شده و در هنگامی که نیاز به اطلاعات آن داشتید باید با استفاده از علامت تعجب ( ! ) آن را ( Unwrapper ) کنید و یا از حالت بستهبندی شده خارج و اطلاعات خودتان را دریافت کنید! به عبارتی دیگر، اطلاعاتی که شما در متغییر خودتان ذخیره کردید در حالت آپشنال، بین دو پرانتز قرار گرفتند و به همین خاطر به آن میگویند Wrapper یا محافظت شده است!. به این مثال دقت کنید: var website_name : String? = "www.iostream.ir" if name == nil{ print("website_name has nil value") } else{ print(website_name!) // Print www.iostream.ir } همانطور که در مثال بالا مشاهده میکنید، اگه متغییر ما مقداری نداشت ( nil )، شرط اول اجرا خواهد شد و در غیر اینصورت، مقدار www.iostream.ir نمایش داده میشود. شما میتوانید در هر زمانی که نیاز داشتید، متغییر خودتان را برابر با nil کنید: var website_name : String? = "www.iostream.ir" website_name = nil if name == nil{ print("website_name has nil value") } else{ print(website_name!) // Print www.iostream.ir } خروجی برابر است با؛ website_name has nil value. به خاطر داشته باشید که به هیج وجه نمیتوانید به متغییرهای عادی کلمهی کلیدی nil رو بدهید! این فقط برای متغییرهای آپشنال است. رِشتهها و کارِکترِها (String & Character ) رشتهها مجموعهای از کارکترها هستند که یک حرف یا یک متن کامل را درون خودشان ذخیره میکنند ( همان متن یا نوشته ). مانند "www.iostream.ir" و یا "Apple". و کارکترهایی که در نهایت میتوانند تا دو حرف را در خودشان ذخیره کنند، مثل؛ "AP" و یا "IO". بگذارید چند نمونه بیاوریم تا بهتر و شفافتر متوجه بشوید: let website_name : String = "www.iostream.ir" print(website_name) و یا اینکه یک کارکتر داشته باشیم؛ let company_name : Character = "A" print(company_name) دقت داشته باشید که در حال حاضر که این زبان در این نسخهی یعنی ( 5.1 ) به سر میبرد، تنها از یک کارکتر پشتیبانی میکند اما در مورد کارکترهای یونیکُد ( Unicode ) اینگونه رفتار نمیکند و هیچ اخطاری از سمت کامپایلر، داده نمیشود. به این مثال دقت کنید؛ let unicode_character : Character = "\u{1F1EE}\u{1F1F7}" print(unicode_character) // Ouput iran flag در خروجی میتوانید مشاهده کنید که پرچم کشورمان نمایش داده میشود و آن هم به دلیل استفاده از کارکترهای یونیکد است. بنابراین، در این مورد بدون اینکه نوع دادهی کارکتر ( Character ) به کارکترهای یونیکد، ایرادی بگیرد، معادل آن را که حاوی یک کارکتر است ( در این مثال پرچم ایران که خود یک کارکتر است ) ذخیره میکند. اما درمواردی دیگر به علت اینکه که کارکتر یونیکدی نخواهیم داشت، بیشتر از یک کارکتر بخواهیم ذخیره کنیم باید از نوع دادهی رشته ( String ) استفاده کنیم نه Character. تعریف رشته شما با تعریف یک متغییر یا ثابت، نام آن و در نهایت نوع و مقدار آن، میتوانید یک رشته یا همان دنبالهای از کارکترها داشته باشید. و یا از سازنده (در محبث کلاسها کامل دربارهی آن توضیح خواهیم داد) خود نوع String استفاده کنید؛ let website_name : String = String("www.iostream.ir") print(website_name) یا اینکه به صورت کوتاه شده، بدون تعریف صَریح نوع، آن را به این شکل بنویسید: let website_name = "www.iostream.ir" let platform_name = String("www.fanoox.com") print("Website name : \(website_name) and Platfrom name : \(platform_name)") خالی قرار دادن رشته ما میتوانیم به دو صورت به متغییرها رشتهای خود مقدار خالی بدهیم! یا با استفاده از دو دابِل کُوتِیشِن ( "" ) یا با استفاده از یک نمونه از نوع رشته ( ()String ). به مثالهای زیر دقت کنید: let website_name = "" let platform_name = String() print(website_name,platform_name) // Empty بررسی خالی بودن یا نبون رشته شما میتوانید با استفاده از ویژگی isEmpty از کلاس String، بررسی کنید که آیا متغییر شما خالی است یا نه؟ به مثال زیر دقت کنید: let website_name = "" if website_name.isEmpty { // True print("website_name is empty!") } متصل کردن دو رشته با استفاده از آپِریِتر + شما میتوانید دو یا چند رشته را به هم متصل کنید؛ let website_name : String = "www.iostream.ir" let platform_name : String = "www.fanoox.com" let addition : String = website_name + " and " + platform_name print(addition) // www.iostream.ir and www.fanoox.com یا اینکه از حالت کوتاه شدهی آن استفاده کنید؛ let website_name : String = "www.iostream.ir" let platform_name : String = "www.fanoox.com" website_name += " and " + platform_name print(addition) // www.iostream.ir and www.fanoox.com ترکیب اطلاعات با یک رشته همچنین شما میتوانید یک رشتهی حاوی اطلاعات داشته باشید و در عین حال آنها در خروجی برای ذخیره در متغییرهای دیگر استفاده کنید! چگونه؟ به این صورت؛ let website_name : String = "www.iostream.ir" let platform_name : String = "www.fanoox.com" let addition = "Website name : \(website_name) and Platfrom name \(platform_name)" print(addition) // Website name www.iostream.ir and Platform name www.fanoox.com با استفاده از این الگو ()\ شما میتوانید تا بینهایت اطلاعات در داخل یک رشته قرار بدهید! فقط کافی است متغییرها، ویژگیهای یک کلاس، توابعی که مقدار برگشتی دارند ( درمورد توابع در جلسات بعد کاملا صحبت خواهیم کرد ) و هر چیزی که حاوی داده یا همان اطلاعات باشد قرار بدهید. اول یک اِسلش رو به عقب ( Backward Slash ) و بعد از آن هر اطلاعاتی که میخواهید داخل رشته باشند را داخل دو پرانتز () قرار میدهید. بدست آوردن طول یک رشته برای به دست آوردن طول یک رشته از ویژگی count از کلاس String استفاده میکنیم؛ let website_name : Stirng = "www.iostream.ir" print(website_naem.count) // website_name length is 15 اگر تکتک کارکترهای www.iostream.ir را بشمارید، میبینید که مقدار 15 به دست میآید. پس در خروجی هم عدد 15 را خواهیم داشت. مقایسه دو رشته اگه بخواهید یک رشته را با یک رشتهی دیگه مقایسه کنید، میتوانیم بطور مستقیم یا با استفاده از شرطها این کار را انجام دهیم؛ let website_name : String = "www.iostream.ir" let platform_name : String = "www.fanoox.com" print(website_name == platform_name) // False if website_name == platfrom_name { // False print("Equal") } else { print("Not Equal") // Not Equal } با آپِریِتر == در شرط if بررسی میکنیم که آیا website_name و platform_name با هم مساوی هستند یا نه، که Not Equal یعنی برابر نیستند را در خروجی خواهیم داشت. بررسی شروع یا پایان یک رشته با دو متد ( در جلسات آینده با متدها کاملا آشنا خواهید شد ) hasPrefix و hasSuffix میتوانیم بررسی کنیم که آیا رشتهی ما با ورودی که میدهیم، مطابقت دارد یا نه. به عنوان مثال رشتهی www.iostrea.ir رو میخواهیم بررسی کنیم که با www شروع میشود یا نه. برای این کار از متد hasSuffix استفاده میکنیم: let website_name : String = "www.iostream.ir" print(website_name.hasSuffix("wwww")) // False // Or if website_name.hasSuffix("www") { // True print(website_name) // www.iostream.ir } خروجی اول False است! چرا که ورودی ما ( wwww ) است و با اول رشتهی www.iostream.ir مطابقت ندارد ولی در دستور شرطی ( if ) که گذاشتیم، مقدار True است. چرا که ورودی ما ( www ) با اول www.iostream.ir مطابقت دارد. حالا اَگه بخواهیم که آخر رشتهی خود را بررسی کنیم که آیا آن چیزی که میخواهیم، با آخر رشته تمام میشود یا نه، از متد hasPrefix استفاده میکنیم: let website_name : String = "www.iostream.ir" print(website_name.hasPrefix(".com")) // False // Or if website_name.hasPrefix(".ir") { // True print(website_name) // www.iostream.ir } خروجی False و www.iostream.ir است. آخر دامنه وبسایت www.iostream.ir با ( ir. ) تمام میشود اما ما دامنهی com. را قرار دادیم که در نتیجه False خواهد شد. ولی در دستور شرطی یعنی if این موضوع درست است! چرا که دامنهی وبسایت با ir. تمام خواهد شد و این متد هم همین را میخواهد! پس در نتیجه True و مقدار متغییر website_name نمایش داده میشود. امیدواریم که این جلسه مورد رضایت شما عزیزان قرار گرفته باشد.
-
1 امتیازخب ! Build System چیست ؟ تمام برنامههایی که مینویسیم، معمولاً یک main.c دارند که نقطهٔشروع (start point) برنامهٔما هست. آیا همیشه همین یک فایله ؟ آیا همیشه نیازه که به یکصورت برنامه را کامپایل کنیم ؟ خب مسلماً جواب "نه" هست. چرا که ممکنه برنامهٔ شما دارای دهها فایل داشتهباشه، و نیاز داشتهباشید که هر فایل رو به صورتخاصی با فلگهای خاصی کامپایلکنید. اینجاس که "بیلد سیستم"ها وارد کار میشوند. به احتمال زیاد نمونههای زیادی مشاهده کردید که وقتی یک سورسیرا (source) از مخازن آنلاین گیت، مثل گیتهاب یا گیتلب دریافت میکنید در فایل راهنما (README.md) در بخش Build نوشته که وارد دایرکتوری بشید و دستور make و بعد make install را وارد کنید، دقیقاً کاری که میکنید اینکه برنامهٔ GNU Make را صدا میزنید که فایل تنظیمات رو از دایرکتوری جاری بخواند و دستورات تعیین شده رو انجام بده، این دستورات در فایلی به نام Makefile نوشته میشود. نصب کردن GNU Make این برنامه معمولاً روی تمام سیستمعاملهای معقول مثل GNU/Linux یا اقوام BSD نصب هست، درصورتیکه نبود میتوانید با استفاده از مدیربستهٔ سیستمعاملتون اقدام به نصب کنید، مثلاً برای نصب روی سیستمعامل Debian - Ubuntu - Ubuntu Mint میتوانید به اینصورت عمل کنید : $> apt install make چه کنیم با GNU Make ؟ اوّل از همه باید یک برنامهای داشتهباشیم که بخوایم براش Build System تعیین کنیم و دستورات Makefileش رو بنویسیم. یک نمونهٔ ساده کد چند تکهای را میتوانید از اینقسمت دریافت کنید. ما سه فایل arg.c/arg.h و main.c را به اینصورت داریم (یک ساختار معقول) : . ├── build ├── obj └── src ├── arg.c ├── arg.h └── main.c خب حالا ما باید Makefile خودمان را داخل دایرکتوری ریشه درست کنیم، قبلاً هم گفتم : "برنامهٔ GNU Make به دنبال فایلی به اسم Makefile یا GNUmakefile یا makefile میگرده". در Makefile میتوانیمما قوانین (rule) برای ساخته شدن چیزی و متغیرهایی تعریف کنیم. اینجا من توضیحات خلاصهای را میگویم، باقیماندهٔ مطالب را باید از مستنداترسمی GNU Make یا راهنمای سریع دنبال کنید. هر قوانینای که تعریف میکنیم دارای این ساختار هست : نیازها : هدفها دستورات مثلاً ما میخواهیم که برنامهٔکامپایل شدهٔمان، با اسم args در دایرکتوری build/ قرار بگیره. اینجا "هدف"ما میشه build/args و نیازما هم فایلهای کامپایل شدهٔ arg.c و main.c هست. اوه ! یک هدف دیگههم پیدا شد؛ الآن هدف دوّمما فایلهای کامپایل شدهٔ obj/arg.o و obj/main.o هست و نیازمان هم سورسهای این فایلها یعنی src/arg.h و src/arg.c و src/main.c. خب خیلی زیاد شدن، بهتره که از آخر شروع کنیم و نیازهایمان را برطرف کنیم، اوّلین نیاز فایلهای کامپایلشده هستن : obj/main.o obj/arg.o : src/main.c src/arg.c src/arg.h gcc -c -o obj/main.o src/main.c gcc -c -o obj/arg.o src/arg.o * نکته : سعی نکنید دستورات Makefile را از منطقهٔ کد کپی نکنید، کمی تلاش کنید و بنویسید. خب قبول دارم خیلی زیاد و زشت شد، بیاید این قانون (rule) را به دو تیکه قسمت کنیم : obj/arg.o : src/arg.c src/arg.h gcc -c -o obj/arg.o src/arg.c obj/main.o : src/main.c gcc -c -o obj/main.o src/main.c اگر تا انتها متن ادامه بدید حتماً کوتاهترم خواهد شد :). خب؛ object fileها یا همان فایلهای کامپایل شدهیمان را به دستآوردیم. حالا باید قانون (rule) نیاز اوّلمان را بنویسیم، چه چیزی نیاز داشتیم ؟ فایل کامپایل شدهٔ build/args که نیاز به object fileها داشت، حالا object fileها را داریم و باید نیاز هدفمان را برطرف کنیم : build/args : obj/main.o obj/arg.o gcc -o build/args obj/main.o obj/arg.o obj/arg.o : src/arg.c src/arg.h gcc -c -o obj/arg.o src/arg.c obj/main.o : src/main.c gcc -c -o obj/main.o src/main.c تمام شد. ما دستورات Build System خودمان را به زبان برنامهٔ GNU Make نوشتیم؛ حالا کافیه که فقط وارد دایرکتوریای که فایل Makefile هست بشیم و از ترمینال برنامهٔ make را فراخوانی کنیم : $> make gcc -c -o obj/main.o src/main.c gcc -c -o obj/arg.o src/arg.c gcc -o build/args obj/main.o obj/arg.o حالا میتوانیم برنامهٔ خودمان را اجرا کنیم : $> build/args -name Ghasem -family Ramezani Input Name is [Ghasem] Input Family is [Ramezani] دقّت کرده باشید ما توی نوشتن Makefileمان نیازمندیهارو یکی بالاتر از دیگری نوشتیم. چرا ؟ به خاطر اینکه GNU Make میاد از اوّل فایل شروع میکنه و قوانین (rules)ها را اجرا میکنه. بزارید با یک مثال نشان بدم. Makefile زیر را مدنظرتون داشتهباشید : obj/arg.o : src/arg.c src/arg.h gcc -c -o obj/arg.o src/arg.c obj/main.o : src/main.c gcc -c -o obj/main.o src/main.c build/args : obj/main.o obj/arg.o gcc -o build/args obj/main.o obj/arg.o ما نیاز اصلی خودمان را آخرین قانون (rule) نوشتیم. حالا برنامهٔ make را اجرا میکنیم تا رفتارَش را بهتر متوجه بشیم : $> make gcc -c -o obj/arg.o src/arg.c دیدید ؟ خیلی ساده برخورد کرد، اوّلین قانون (rule) را نگاه کرد تنها نیازمندیش فایلهای src/arg.c و src/arg.v بودن که وابسته به چیزی نبودند و هدفشان را تأمین کردند. اگر بخواهیم باقی قوانین (rules) را فراخوانی کنیم، باید صراحتاً مشخص کنیم : $> make obj/main.o gcc -c -o obj/main.o src/main.c $> make build/args gcc -o build/args obj/main.o obj/arg.o خب دیگه امیدوارم دلیل اینکهما نیازمندی اصلیه خودمان را اوّلین قانون (rule) قرار دادیم را متوجه شده باشید. وقتی make به نیازمندیه obj/arg.o و obj/main.o برای تأمین build/args برمیخوره ادامهٔ قوانین را پیمایش میکنه تا نیازمندیها را برطرف کنه. (اگر گیج شدید احتمالاً، پیشنهاد میکنم همین موارد را روی کاغذ کشیده و قسمت : نیازمندیها و هدفها و دستورات هر قانون را مشخص کنید.) میتوانیم قوانینی (rules) تعریف کنیم برای کارهای خاصی، مثلاً همان make install، یعنی قانون install را فراخوانی کن؛ حالا ما قانون clean را برای حذف کردن فایلهای کامپایلشده مینویسیم : clean : yes | rm -vf build/* obj/* البته باید در اینجا نکتهای را هم حواسمان باشد، باید به GNU Make بگوییم که قانون clean ، یک قانون الکیهست، و با یک "هدف" اشتباه نشود. به اینصورت قانون را ویرایش میکنیم : .PHONY : clean clean : yes | rm -vf build/* obj/* نگرانیای هم دربارهٔ Wildcard ها نداشتهباشید، GNU Make دستتون را باز گذاشته :). متغیرها در GNU Make مسلماً هرجا سخنی از متغیراست، سر و کلهٔ راحتیکار (و تا حدودی پیچیدگی) پیدا میشود. ما میتوانیم متغیرهم داخل Makefile خودمان داشتهباشیم. مثلاً فرض کنید که نیاز دارید تمام سورسکدها با کامپایل clang و سطحبهینهسازیه 3 کامپایل بشند. نیازی نیستکه هربار اینارو تایپ کنیم. کافیه براشون متغیرتعریف کنیم : CC = clang OP = -O3 OBJECT = obj/main.o obj/arg.o ARGS = src/arg.c src/arg.h build/args : $(OBJECT) $(CC) $(OP) -o build/args $(OBJECT) obj/arg.o : $(ARGS) $(CC) $(OP) -c -o obj/arg.o src/arg.c obj/main.o : src/main.c $(CC) $(OP) -c -o obj/main.o src/main.c clean : yes | rm -vf build/* obj/* متغیرهای به خصوصی نیز در GNU Make تعریف شدهاند که میتوانند کار مارا بسیار راحتتر کنند، برای مثال میتوانیم قانون object fileها را به اینصورت بازنویسی کنیم : obj/%.o : src/%.c $(CC) $(OP) -c -o $@ $? برای اطلاعات بیشتر به راهنمایسریع GNU Make مراجعه کنید. یادداشتها یا Code Comments برای استفاده از قابلیت Comment گذاری در کد، کافیه که اوّل خط خودتون از کاراکتر # استفاده کنید. خب دوستان، سعی کردم کلیّات مبحث را بگم؛ ابزار Make قابلیتهای بسیار زیادی داره که حتماً باید خودتون مطالعه کنید. مثلاً خواستید Makefile شما یک Makefile دیگه را صدا بزنه، یا حتیٰ دستورات شرطی اجرا بکند و یا از همه مهمتر بر اساس معماری پلتفرم شما عملیات کامپایل را انجام بده و ... . - موفقوپیروز باشید. ?
-
1 امتیازآموزش زبان برنامهنویسی سوئیفت - جلسه چهارم مواردی که در این جلسه یاد خواهید گرفت: کامنتها، دو ویژگی نوعهای داده Int، آپِریِترها ( Operators )، کلمات کلیدی break,continue,fallthrough کامنتها در زبان برنامهنویسی سوئیفت مثل اکثر زبانهای برنامهنویسی دیگر، از کامنتها استفادهای زیادی میشود و معمولا برای اهدافی کامنتها استفاده میشوند و میتوانند شامل موارد زیر باشند: برای غیرفعال کردن موقتی یک قسمت یا بخشی از کُد برای توضیح دادن کدها برای دیگر برنامهنویسان یا توسعهدهندگان یا توضیح کد برای اینکه در مراجعات بعدی به پروژه کارکرد همان قسمتی که کامنت کردید را متوجه شوید انواع کامنتها در زبان برنامهنویسی سوئیفت؛ تَک خطی ( single-line ) چند خطی ( multi-line ) در کامنت تک خطی، با گذاشتن دو علامت // میتوانید توضیحات خود را فقط در همان خط درمورد کُد مورد نظر بنویسید و بعد از آن کامنت شما در واقع در حال تعریف دستور جدید هستید! این کامنت، به این صورت استفاده میشود؛ let _web_site_name : String = "www.iostream.ir" print(_web_site_name) // Ouput the string of www.iostream.ir در مثال بالا همانطور که مشاهده میکنید از کامنت تک خطی استفاده کردیم، و بعد از این کامنت شما نمیتوانید انتظار داشته باشید که در خط جدید دوباره حالت کامنتگذاری برای شما همچنان فعال باشد! چرا که در خط بعدی دستوری جدید داریم نه کامنتی ادامهی کامنت قبل!. و اما کامنت چند خطی که میتوانید در تعداد خطهای بیشتر، توضیحات خود را بنویسید؛ let _web_site_name : String = "www.iostream.ir" /* We can also use from of print("String.. and \(_web_site_name)") */ print(_web_site_name) در مثال بالا که مشاهده میکنید، شما هیچ محدودیتی برای اضافه کردن توضیحات بیشتر برای خود کدهای خود ندارید و تا هر چند خط توضیحات که مدنظرتان بود، میتوانید استفاده کنید. دو ویژگی نوع دادهی Int اگر بخواهیم کوچکترین و بزرگترین عدد موجود از نوع دادهی Int را بدست آوریم، از دو ویژگی max,min استفاده میکنیم. هر دو ویژگی به همراه مثال در زیر آورده شدهاند؛ print("Max => \(Int.max)") print("Min => \(Int.min)") با اجرای کدهای بالا، بزرگترین مقدار موجود نوع دادهی Int و همچنین کوچکترین آن به شما نمایش داده خواهد شد. آپِریِترها ( Operators ) آپریترها در هر زبان برنامهنویسی به عنوان پایه و اساس محاسبات و در بعضی موارد برای کارهای دیگر استفاده میشوند. محاسباتی مانند، جمع، تفریق،تقسیم،ضرب و باقی مانده و ... در سوئیفت این 8 دسته آپریتر وجود دارد: اِنتسابی ( Assigment Operator ) محاسباتی ( Arithmetic Oprerator ) باقیمانده ( Remainder Operator ) مُرکب ( Compound Assigment Operator ) مقایسهای ( Comparison Operators ) ترِنِری ( Ternary Conditonal Operators ) دامِنه ( Range Operator ) منطقی ( Logical Operators ) اینها، کل آپریترها در سوئیفت هستند که هر کدام عمل مخصوص به خود را انجام میدهند. در مثالهای زیر هر کدام از اینها را به همراه مثال برای شما آوردهایم. آپریتر اِنتسابی ( Assigment Operator ) اگر ما یک مقداری را به متغییری انتساب میدهیم، عملا داریم از آپِریِتر انتساب ( = ) استفاده میکنیم. چرا که کدنویسی از سمت چپ شروع میشود و این هم کاملا منطقی هم است که مقدار دهی به یک متغییر از سمت چپ صورت میگیرد و مقدار سمت راست را درون خود ذخیره میکند. به این مثال دقت کنید؛ let _web_site_name : String = "www.iostream.ir" // Assigment Operator ( = ) print("Type of \(type(of : _web_site_name ) and the website of name \(_web_site_name)") در مثال بالا مشاهده میکنید که مقدار www.iostream.ir که یک مقدار رشتهای/متنی است را در متغیر web_site_name ذخیره میکنیم. این کار با استفاده از این علامت ( = ) انجام شده است. در خط بعد هم نوع متغییر و مقدار موجود در متغییر web_site_name که ذخیره شده است را نمایش میدهیم. آپریترهای محاسباتی ( Arithmetic Oprerator ) در قسمتهایی از پروژه پیش آمده که ما باید محاسباتی را انجام بدهیم. این کار با استفاده از این آپریترها انجام میشود. که شامل: +،-،/،* و ٪ هستند. برای هر کدام از اینها، مثالهایی در زیر آورده شده است؛ جمع ( + ) var number_one, number_two : Int8 number_one = 50 number_two = 50 print("Result = > ", number_one + number_two) // Output the number 100 در مثال بالا همان طور که مشاهده میکنید، ابتدا دوم متغییر با نوع Int8 تعریف کردهایم و سپس در خطوط بعد به آنها مقادیر ۵۰ را دادهایم. در نهایت عمل جمع ( + ) را روی آنها به صورت مستقیم انجام داده و نمایش میدهیم. یا اگر سادهتر بخواهیم تعریف کنیم، آپریتر ( + ) عمل جمع کردن اعداد و یا متصل کردن دو رشته را بر عهده دارند؛ var web_site_name, platform_name : String web_site_name = " www.iostream.ir :)" platform_name = " www.fanoox.com ;)" print("Binding two string => ", web_site_name + platform_name) // Ouput the website name and platform name => www.iostream.ir :) www.fanoox.com ;) و به همین راحتی میتوانید تا بینهایت عمل جمع و متصل کردن رشتهها را انجام دهید. تنها نکتهای که باید توجه داشته باشید این است که سوئیفت در برخورد با اعداد و این آپریتر، آن عبارت را محاسباتی در نظر میگیرد و در برخورد دو یا چند رشته، آن عبارت را عمل متصل کردن و الحالق ( Concatentation ) در نظر میگیرد. تفریق ( - ) برای کم کردن دو مقدار عددی از هم استفاده میشود؛ var number_one, number_two : Int8 number_one = 80 number_two = 30 print("Result => ", number_one - number_two) // Output the number 50 مثال بالا به خوبی نشان میدهد که دو مقدار ۸۰ و ۳۰ از هم کم شده و در نتیجه، خروجی برابر ۵۰ خواهد بود. ضرب ( * ) عمل ضرب کردن دو عدد را انجام میدهد؛ var number_one, number_two : Int8 number_one = 50 number_two = 20 print("Result => ", number_one * number_two) // Output the number 1000 دقت کنید که نباید به حرف ( x ) که شبیه به ضرب در ریاضیات است اشتباه گرفته شود. تقسیم ( / ) عمل تقسیم کردن دو عدد را انجام میدهد؛ var number_one, number_two : Double number_one = 50.0 number_two = 20.0 print("Result => ", number_one / number_two) // Output the number 2.5 در محاسباتی که عمل تقسیم را انجام میدهیم باید به این نکته دقت کنیم که اگر پروژهی ما عملا برای محاسبات کار خاصی است باید از نوع دادهی Double یا Float استفاده کنیم که البته در محاسبات معمولی، نوع دادهی Float جوابگوی نیاز ما هم هست، اما در محاسباتی که نیاز به دقت بالایی دارند باید از نوع دادهی Double استفاده کنیم. باقیمانده ( ٪ ) توجه داشته باشید که این علامت را با درصد که شبیه همین است اشتباه نگیرید! چرا که در دنیای واقعی ما، علامت درصد برای نشان دادن مقداری از چیزی در یک محصول یا خدمات است ولی در دنیای کامپیوتر و برنامهنویسی این علامت، به معنای باقی ماندهی بین دو عدد است که بعد از تقسیمهای پیدرپی که صورت میگیرد، بدست میآید. این باقی مانده یا ۰ است یا ۱ ( حتما شما هم عاشق صفر و یکی هستید که اساس کار کامپیوتر و سیستم شما را تشکیل میدهد! ). این مثال را ببینید تا بهتر متوجه این موضوع شوید؛ var number_one, number_two : Int8 number_one = 10 number_two = 2 print("Result => ", number_one % number_two) // Output the number 0 اگر جزئیتر بخواهیم وارد شویم به این صورت است که ابتدا عدد ۱۰ بر ۲ تقسیم ( / ) شده و سپس حاصلی که بدست میآید ۵ است و سپس دوباره ۵ تقسیم بر ۲ شده و ۲ بدست میآید و در اینجا باقیمانده ۲ ٪ ۲ میشود ۰. آپِریِترهای مُرکب ( Compound Assigment Operator ) که شامل عبارتهای کوتاهشده یا به اصطلاح میانبری برای عمل انتساب و محاسبه را فراهم میکند که شامل: =+،=-،=*،=/،=٪ است. در زیر توضیح مختصر به همراه یک مثال آورده شده است. انتساب و جمع ( =+ ) در این حالت ما هم عمل انتساب را داریم و هم عمل جمع، با یک تیر دو نشان بزنید! به مثال زیر دقت کنید. var number_one : Int8 = 50 number_one += 50 print("Result =>", number_one) // Output the number 100 در خط دوم که ما با آن کار داریم، متغییر number_one مقدار ۵۰ را هم به آن اضافه به خودش اضافه کرده و در نهایت در خود متغییر number_one ذخیره و نتیجه ۱۰۰ نمایش داده میشود. که بدون استفاده از میانبر، به این شکل بود؛ var number_one : Int8 = 50 number_one = number_one + 50 print("Result =>", number_one) // Output the number 100 حتی میتوانیم برای اتصال یک رشته به رشتهی دیگر استفاده کنیم؛ var web_site_name : String = "www.iostream.ir :) " web_site_name += " www.fanoox.com ;) " print("Result =>" web_site_name) // Ouput the web site of name => www.iostream.ir :) www.fanoox.com ;) که در شکل ساده به این شکل نوشته میشد؛ var _web_site_name_and_platform_name : String = "www.iostream.ir :) " + " www.fanoox.com ;) " print("Result => ", _web_site_name_and_platform_name) // Ouput the web site of name and platform name => www.iostream.ir :) www.fanoox.com ;) انتساب و تفریق ( - ) مقدار سمت راست را از مقدار سمت چپ کم میکند و نتیجه در همان متغییر ذخیره خواهد شد؛ var number : Int8 = 80 number -= 30 print("Result =>", number) // Ouput the number 50 مقدار ۸۰ که مقدار فعلی متغییر number است، از مقدار ۳۰ که در سمت راست متغییر قرار دارد، کم میشود و در نهایت مقدار ۵۰ در همان متغییر یعنی number ذخیره خواهد شد. انتساب و ضرب ( =* ) ضرب مقدار سمت راست در مقدار فعلی متغییر سمت چپ را انجام میدهد؛ var number : Int8 = 80 number *= 30 print("Result =>", number) // Ouput the number 2400 انتساب و تقسیم ( / ) مشابه آپِریِتر تقسیم ( / )، تقسیم مقدار سمت راست را به متغییر سمت چپ انجام میدهد؛ var number : Int8 = 10 number /= 2 print("Result =>", number) // Ouput the number 5 انتساب و باقیمانده ( ٪ ) عمل باقیماندهی دو عدد که شامل مقدار فعلی متغییر سمت چپ و مقدار سمت را است را محاسبه کرده و در متغییر سمت چپ ذخیره میکند؛ var number : Int8 = 10 number ٪= 5 print("Result =>", number) // Ouput the number 0 آپِریِترهای مقایسهای ( Comparison Operators ) برای مقایسهی بین دو مقدار و در نتیجه به دست آوردن مقدار True یا False مورد استفاده قرار میگیرند. بیشترین استفادهی آنها در شرطها است، اما میتوان به صورت مستقیم هم از آنها هم استفاده کرد. این آپِریِترها شامل ==،=!،>،<،=<،=>،==!،=== هستند. برای هر کدام مثالی در زیر آورده شده است. مساوی ( == ) برای مقایسه دو مقدار استفاده میکنیم که در صورتی که مقادیر دو طرف مساوی باشند، مقدار True و در غیر اینصورت مقدار False برگشت داده خواهد شد؛ print("True and False => ", 2 == 2) // Output the true print("True and False => ", 2 == 3) // Output the false در مثال بالا به دلیل اینکه ۲ با ۲ برابر است، مقدار نمایش داده شده، true است. و در خط بعدی به این دلیل که مقدار ۲ برابر با ۳ نیست، مقدار false نمایش داده میشود.همچنین میتوانید در شرطها و حلقهها هم استفاده کنید: if 2 == 2 { print("Ok!") }else { print("NO!") } // Ouput the string Ok! نامساوی ( =! ) اگر مقدار برابر با مقدار مقابل خودش نباشد، نتیجه true و در غیر اینصورت نتیجه false خواهد بود. علامت ! ( نَقیض ) دقیقا معنا و مفهوم ( == ) را عوض میکند ( یعنی اگر مقداری true باشد، برعکس شده ( false ) و اگر false باشد ( true ) میشود. print("True and False => ", 2 != 3) // Output the true print("True and False => ", 2 != 2) // Output the false در مثال بالا، ۲ مساوی ۳ نیست و این درست است!. در خط بعدی ۲ برابر با ۲ است و این درست است! که در نتیجه، برعکس آن یعنی false نمایش داده میشود. کوچکتر ( > ) اگر مقدار سمت چپ کوچکتر از مقدار سمت راست بود، نتیجه true و در غیر اینصورت نتیجه false است؛ print("The operator ( < ) => ", 1 < 2) // Output the boolean true print("The operator ( < ) => ", 2 < 1) // Output the boolean false بزرگتر ( < ) اگر مقدار سمت چپ بزرگتر از مقدار سمت راست بود، نتیجه true و در غیر اینصورت نتیجه false خواهد بود؛ print("The operator ( > ) => ", 1 > 2) // Output the boolean false print("The operator ( > ) => ", 2 > 1) // Output the boolean true بزرگتر یا مساوی ( =< ) اگر مقدار سمت چپ، بزرگتر یا مساوی مقدار سمت راست بود، نتیجه true است، در غیر اینصورت، نتیجه false خواهد بود؛ print("The operator ( >= ) => ", 1 >= 1) // Output the boolean true print("The operator ( >= ) => ", 1 >= 2) // Output the boolean false کوچکتر یا مساوی ( => ) اگر مقدار سمت چپ، کوچکتر یا مساوی مقدار سمت راست بود، نتیجه true است، در غیر اینصورت، نتیجه false است؛ print("The operator ( <= ) => ", 1 <= 2) // Output the boolean false print("The operator ( <= ) => ", 1 <= 1) // Output the boolean true نکته: اولویت مساوی ( = ) در این مورد بالاتر از بزرگتر < یا کوچکتر > است و بنابراین در حلقهها اگر این آپِریِتر باشد، اولویت با آن است. مقایسهی دو شئ ( === ) برای مقایسهی دو شئ ( object ) استفاده میشود. اگر هر دو شئ از یک مرجع ( refrence ) باشند یا به عبارت دیگر شئهای ساخته شده در حافظهی به نام Heap با تمام ویژگیها و متغییرها نگهداری شوند و اگر شئ دیگر از همان حافظهای که یک شئ دیگر استفاده میکند وجود داشته باشد، این آپِریِتر درمورد آن دو شئ نتیجهی true و در غیر اینصورت نتیجهی false را نمایش میدهد. در مبحث شئگرایی بیشتر در این مورد صحبت خواهیم کرد، در حال حاضر فقط همین را که بدانید، کافیست. به مثال زیر دقت کنید: class WebsitePlatform { var website_name, platform_name : String init(web_name : String, platform : String) { self.website_name = web_name self.platform_name = platform } } let _web_site_and_platform_one = WebSitePlatform(web_name : "www.iostream.ir", platform : "www.fanoox.com") let _web_site_and_platform_two = WebSitePlatform(web_name : "www.iostream.ir", platform : "www.fanoox.com") if _website_and_platform_one === _website_and_platform_two { print("This is refrence!") }else { print("No,this is not refrence!") } اگر این قطعه کد را اجرا کنید، به شما پیغام NO, this is not refrence را میدهد! چرا که هر دو شئ به صورت جداگانه در حافظهی Heap ذخیره شدهاند و هیچکدام به دیگر ارجاعی ندارد و به اصطلاح با هم ارتباطی ندارند و اگر ما بیایم به این شکل عمل کنیم، آنوقت دیگر یک ارجاع داریم به یک شئ مشخص؛ class WebsiteAndPlatform { var website_name, platform_name : String init(web_name : String, platform : String) { self.website_name = web_name self.platform_name = platform } } let _website_and_platform_one = WebSiteAndPlatform(web_name : "www.iostream.ir", platform : "www.fanoox.com") let _website_and_platform_two = _website_and_platform_one if _website_and_platform_one === _website_and_platform_two { print("This is refrence!") }else { print("No,this is not refrence!") } به جای ایجاد شئ جدید، همان شئ اول را به متغییر دوم انتساب میدهیم که در واقع الان دو شئ داریم که شئ دومی به شئ اولی اشاره میکند. یعنی اینکه الان با هم در ارتباط هستند و شئ دومی ارجاعیست به شئ اول؛ نتیجه خروجی هم This is refrence است. نامساوی بودن دو شئ ( ==! ) اگر دو شئ از یک مرجع نبودند، و هر کدام در حافظهای جداگانه نگهداری میشوند، نتیجه به صورت true و اگر دو شئ از یک مرجع بودند، نتیجه false خواهد بود. کاملا برعکس علامت ( === ). به مثال زیر دقت کنید: class WebsiteAndPlatform { var website_name, platform_name : String init(web_name : String, platform : String) { self.website_name = web_name self.platform_name = platform } } let _website_and_platform_one = WebSiteAndPlatform(web_name : "www.iostream.ir", platform : "www.fanoox.com") let _website_and_platform_two = WebSiteAndPlatform(web_name : "www.iostream.ir", platform : "www.fanoox.com") if _website_and_platform_one !== _website_and_platform_two { print("This is refrence!") }else { print("No,this is not refrence!") } مثال بالا هم که کاملا واضح است! اینکه اگر دو شئ با هم ارجاعی نداشتند، پس نتیجه true است و در غیر اینصورت اگر ارجاع داشتند، نتیجه false و شرط آخر اجرا خواهد شد که No, this is not refrence است. آپریتر ترِنِری ( Ternary Conditonal Operators ) سه قسمت دارد؛ مسئله/شرط مقدار اول مقدار دوم ابتدا شرط قرار خواهد گرفت و سپس به دنبال آن آپِریِتر ? و بعد از آن جواب اول که در صورت درست بودن شرط، برگشت داده میشود و علامت کالُن ( : ) بعد از جواب اول قرار میگیرد که در صورتی که نتیجه نادرست یا false باشد، مقدار بعد ( : ) برگشت داده میشود. به مثال زیر توجه کنید؛ let _website_name_and_platform_name : String = 2 > 1? "www.iostream.ir" : "www.fanoox.com" print("Result =>", _website_name_and_platform_name) // Output the www.iostream.ir در مثال بالا همانطور که مشاهده میکنید، ابتدا شرط یا مسئله قرار میگیرد و سپس مقدار بعد آپِریِتر ? و مقدار اول و دوم که با ( : ) از هم جدا میشوند. در این مثال نتیجه، www.iostream.ir است، چرا که ۲ بزرگتر از ۱ است و این کاملا منطقی است که نتیجهی true دارد. آپریتر دامِنه ( Range Operator ) بیشتر در حلقهها استفاده میشود و نحوهی کار آن را در جلسهی قبل توضیح داده شده است؛ for index_number_one in 0...20 { print(index_number_one, separator : " ",terminator : "") // Output the number 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 } for index_number_two in 0..<20 { print(index_number_two, separator : " ",terminator : "") // Output the number 0 1 2 3 4 5 6 7 8 9 10 11 12 13 15 16 17 18 19 } آپریترهای منطقی ( Logical Operators ) شامل !،&&،|| هستند که بیشتر استفادهی آنها در حلقههاست، اما شما میتوانید در هر جایی از پروژهی خودتان که نیاز به آنها داشتید، استفاده کنید. نَقیض ( ! ) کار آن، برعکس کردن یا نقض کردن یک مقدار است که امکان دارد true یا false باشد. به عنوان مثال اگه مقدار متغییری true است و این آپِریِتر را در ابتدای آن مقدار قرار بدهید، خروجی برابر است با false. و همین مورد هم برعکس صدق میکند، یعنی اگر شما مقدار متغییری false داشتید، با گذاشتن این عبارت در ابتدای متغییر، نتیجه true خواهد شد؛ var isname_website_iostream : Bool = false print(!is_name_website_iostream) // Output the bool true isname_website_iostream = true print(!isname_website_iostream) // Output the bool false درست بودن دو یا چند شرط ( && ) اگر در شرط یا عبارتی بخواهیم دو شرط را بررسی کنیم که هر دو هم حتما باید درست ( true ) باشند از این آپِریِتر استفاده خواهیم کرد؛ let web_sitename, platform_name : String web_sitename = "www.iostream.ir" platform_name = "www.fanoox.com" if web_sitename == "www.iostream.ir" && platform_name == "www.fanoox.com" { print(web_sitename, platform_name) } else { print("None") } در قطعه کد بالا، هر دو شرط مبنی بر اینکه دو مقدار متغییر تعریف شده باید برابر با مقدار تعیین شده در شرط باشند تا مقادیر چاپ شوند. در غیر اینصورت با خروجی None روبرور خواهیم شد. درست بودن حداقل یک شرط ( || ) اگر یکی از شرطها در بین چندین شرط که برقرار است و به عبارتی نتیجه درست ( true ) داشته باشد، وارد بدنهی شرطه خواهد شد و دستورات را اجرا میکند. اگه تمامی شرطها نادرست ( false ) باشند، دستورات داخل بدنه اجرا نخواهند شد؛ var web_sitename, platform_name : String web_sitename = "www.iostream.ir" platform_name = "www.fanoox.com" if web_sitename == "www.iostream.ir" || platform_name == "www.fanoox.ir" { print(web_sitename, platform_name) } else { print("None") } در مثال بالا شرط دوم برقرار نیست! چرا که متغییر platform_name با مقدار سمت راست آن برابر نیست. اما از آنجایی که از ( || ) استفاده کردهایم، پس شرط اول درست بوده و دستورات داخل بدنهی شرط if اجرا خواهند شد. کلمات کلیدی continue,break,fallthrough کلمهی کلید continue اگر بخواهیم در یک حلقه در یک جایی به بعد دستورات اجرا نشوند و حلقه مقدار فعلی را نادیده بگیرد، از این کلمهی کلید کمک خواهیم گرفت؛ for index in 0...20 { if index == 5 { continue } print(index) } /* Output the number 0 1 2 3 4 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 */ همانطور که در مثال بالا مشاهده میکنید، در حلقهی for شرط داخل حلقه بر این مبنا است که هر زمانی که متغییر index مساوی مقدار ۵ شد، به ادامهی دستورات پایان بده ( continue ) و مجدد حلقه را از سر بگیر. به همین خاطر است که عدد ۵ نمایش داده نشده است. کلمهی کلید break با این کلمهی کلید به راحتی میتوانید هر جایی از حلقه که دیگر نیازی نداشتید ادامه پیدا کنید، حلقه را متوقف یا به اصطلاح بشکنید!. با این کار دیگر حلقه تکرار نشده و دستورات بعد از حلقه اجرا خواهند شد؛ for index in 0...5 { if index == 2 { break } print(index) } /* Output the number 0 1 */ کلمهی کلید fallthrough شاید در جایی از دستور شرطی switch لازم داشتید که caseهای بعدی هم اجرا شوند. با این کلمهی کلیدی میتوانید این کار را انجام دهید؛ let _number : Int8 = 1 switch _number { case 2: print(2) case 1: print(1) fallthrough case 3: print(3) fallthrough default: print("None") } بعد از هر caseی که میخواهید اجرا شود باید این کلمهی کلیدی را قرار دهید. امیدوایم این جلسه هم مورد رضایت شما عزیزان قرار گرفته باشد.
-
1 امتیازآموزش زبان برنامهنویسی سوئیفت - جلسه سوم مواردی که در این جلسه یاد خواهید گرفت: انواع داده و حلقهها در زبان برنامهنویسی سوئیفت ما با انواعی از دادهها روبرو هستیم! طبق دیگر زبانهای برنامهنویسی نوع دادههای پرکاربرد را معرفی خواهیم کرد و همچنین دیگر نوعهای داده را در ادامه بازگو میکنیم که شما با دقت بتوانید در پروژههای خودتان نوعهای دادهی مناسب پروژهی خودتان را انتخاب کنید. همچنین باید در نظر داشته باشید که در هر پروژهی نرمافزاری به کار بردن نوعهای دادهای که اصلا شاید مناسب قسمتی از پروژه یا نیازی به آن نباشد و با نوع دادهای دیگر مسئله بهتر و بهینهتر حل شود و استفاده کنید، استاندارد پروژهی شما کاهش پیدا خواهد کرد و مطمعن هستم که شما دوست ندارید نرمافزاری غیراستاندارد و بهینه داشته باشید! بنابراین در انتخاب نوعهای داده در پروژههای خودتان نهایت دقت را به خرج دهید! چه کسی دوست ندارد که پروژهای کارآمد و بهینه داشته باشد که هر توسعه دهندهای با دیدن نظم و بهینه بودن پروژهاش، آن را تحَسین کند؟!. درمورد حلقهها هم یک توضیح مختصر خواهیم داد و بعد به سراغ کُدنویسی خواهیم رفت که مطمئنن قسمت هیجانی برای هر برنامهنویسی است?. همهی ما در برنامهنویسی نیاز داریم که چندین خط کد رو بنویسم تا در نهایت برنامهی ما به خوبی اجرا شود، البته اگه بقیهی قسمتهای کدهایتان به درستی کار کند! اما همین چند خط کد رو مثلا چاپ اعداد ۰ تا ۱۰ در نظر بگیرید؛ print(0) print(1) print(2) print(3) print(4) print(5) print(6) print(7) print(8) print(9) print(10) احتمالا با خودتان میگوید چه برنامهنویسی داریم که چنینکاری میکند؟! این همه کد! حالا فکر کنید از ۰ تا ۱۰۰۰ باید به همین شکل بنویسید! کاری با خطوط کدها و حتی منطقی بودن آن هم نداریم! اما واقعا حوصله، زمان و انرژی باارزشتان چه میشود؟ پس راهحل چیست؟ درست حدس زدید! حلقهها! این مسئله را به سادگی و زیبایی هر چه تمامترحل کردند. پس مثال بالا را با یک حلقه مینویسیم؛ for index in 0...5 { print(index) // Output => 0,1,2,3,4,5 } فقط با چندین خط کد این مسئله برطرف شد! حال شما میتوانید به جای عدد ۵، عدد ۱۰۰۰ یا بیشتر را قرار بدهید. پس متوجه شدید که کاربرد حلقهها در برنامهنویسی چقدر مهم و حیاتی هستند! که برای تکرار دستوراتی هستند که دادههای زیادی به صورت مستقیم یا غیر مستقیم در برنامه مورد استفاده قرار میگیرد و عملا بدون حلقهها باید کدهای زیادی بنویسید تا این دادهها نمایش داده شوند!. انواع داده در سوئیفت، ما با انواعی از داده سر و کار داریم که عبارتاند از: انواع دادهی صحیح و اعشاری Int۸؛ نوع دادهی عددی ۸بیت است، به این صورت که فقط توانایی ذخیره ۱بایت را دارد. و مقادیر ۱۲۷ تا ۱۲۷- را پوشش میدهد. UInt8؛ این نوع داده عددی فقط و فقط یک مقدار از ۰ تا ۲۵۵ را پوشش خواهد داد و ۱بایت است. که در واقع کوتاه شدهی نام Unsigned Integer، است که به معنای نوع بدون علامت ( - ) است. Int۳۲؛ این نوع داده ۴بایتی ( ۴ * ۸ ) است، و مقداری از ۲۱۴۷۴۸۳۶۴۷ تا ۲۱۴۷۴۸۳۶۴۷- را پوشش میدهد. UInt32؛ مانند UInt8، است، با این تفاوت که فقط مقادیر صحیح بدون علامت ( - ) را قبول میکند و ۴بایت است. از مقدار ۰ تا ۴۲۹۴۹۶۷۲۹۵ را پوشش میدهد. Int۶۴؛ مقداری بیشتری در خود ذخیره میکند و در نتیجه تعداد بایتهای بیشتری را هم از حافظه اشغال میکند! که ۸بایت است. یعنی اینکه مقدار عددی تا ۲۰ رقم عدد صحیح با علامت ( + ) و بدون علامت ( - ) را ذخیره میکند. از مقدار ۹۲۲۳۳۷۲۰۳۳۶۸۵۴۷۷۵۸۰۸ تا ۹۲۲۳۳۷۲۰۳۳۶۸۵۴۷۷۵۸۰۸- را پوشش میدهد. UInt۶۴؛ مانند Int64 است که مقدار بیشتری ذخیره میکند که فقط شامل اعداد صحیح بدون علامت ( - ) است و ۸بایتی. از مقدار ۰ تا ۱۸۴۴۶۷۴۴۰۷۳۷۰۹۵۵۱۶۱۵ را پوشش میدهد ( دو برابر Int64 ). Float: این نوع دادهی اعشاری است و ۴بایتی. مقادیر اعشاری از ۱.۲E-38 تا ۳.۴E-38 را پوشش میدهد ( تا ۶ رقم اعشار ). البته این اعدا و این نوع نوشتاری، نمادهای علمی هستند!. Double: در این نوع اعشاری یک تفاوت وجود دارد و آن اینکه فضای بیشتری را در اختیارمان میگذارد و همینطور دقت بالایی را هم در محاسبات به همراه دارد. ۸بایتی است و از مقدار ۲.۳E-308 تا ۱.۷E+308 را پوشش میدهد ( تا ۱۵ رقم اعشار ). برای هر کدام یک مثال خواهیم زد؛ var int_8_byte : Int8 = -50 print(int_8_byte) var uint_8_byte : UInt8 = 50 print(uint_8_byte) var int_32_byte : Int32 = -560 print(int_32_byte) var uint_32_byte : UInt32 = 560 print(uint_32_byte) var int_64_byte : Int64 = -1567 print(int_64_byte) var uint_64_byte : UInt64 = 1567 var float : Float = 18.5 print(float) var double : Double = 19.75 print(double) این قطعه کدها را در اِدیتور ( Editor ) یا IDE خودتان وارد و اجرا کنید تا نتیجه را بصورت زنده مشاهد کنید ( لطفا کُپی و پیست نکنید! ) خودتان بنویسید تا هم لذت کدنویسی را بچشید و هم اینکه بهتر یاد بگیرید. در این میان، دقت کنید که نوع دادهی خودتان را به درستی در پروژههای نرمافزاری استفاده کنید و طبق نیاز نرمافزار خود از دادهی مورد نیاز استفاده کنید. به عنوان مثال، یک بخشی از برنامهی شما نیاز به دادهای به اندازهی ۸بایت دارد، ولی شما میآید و یک دادهی ۶۴بایتی تعریف میکنید برای آن! با اینکار حافظهی سیستم شما و آن کسی که نرمافزار شما را اجرا خواهد کرد، احتمالا صدای فَن آن در خواهد آمد! پس دقت کنید. نوع دادهی بُولیَن ( Boolean ) دو مقدار True یا False دریافت میکند. از این نوع داده بیشتر در دستورات شرطی استفاده میشود و بخشهایی از برنامه که نیاز به بررسی و درستی یا نادرستی یک عبارت دارد. نحوهی تعریف و استفاده از آن هم به این شکل است؛ let _boolean : Bool = true // Or false print(_boolean) در این تعریف شما میتوانید با تعریف نوع دادهی Bool و سپس مقدار به آن، از آن در قسمتهای مختلف برنامه استفاده کنید. نوع دادههای رشتهای و کارکتری String؛ یک دنبالهای از رشته ( همان متن ) را در خود ذخیره میکند؛ let _web_site_iostream : String = "www.iostream.ir" print(_web_site_iostream) Character؛ فقط و فقط یک کارکتر را قبول میکند( به جز کارکترهای کنترلی )؛ let _character : Character = "i" let _character_control : Character = "\n" print(_character) // Output => i print(_character_control) // Output => new line همانطور که مشاهده کردید، چندین نوع داده در زبان برنامهنویسی سوئیفت وجود دارد که شما باید با توجه به پروژهی خودتان از آنها در جای مناسب استفاده کنید. حلقهها و انواع آن حلقهها در سوئیفت به طور متفاوتی تعریف میشوند، اما همهی آنها کار یکسانی انجام میدهند! یعنی تکرار دستورات! حلقهی اول که قصد معرفی آن را داریم، for است؛ for index in 0...5 { print(index) } در این تعریف همانطور که مشاهده میکنید، با تعریف کلمهی کلید for و بعد از آن یک نام دلخواه و معنیدار، برای اینکه مقادیر هر بار در آن ذخیره شوند ( در واقع نقش یک متغییر را بازی میکند ) و بعد کلمهی کلید in قرار میگیرد و در نهایت باید یک رِنج ( بازه ) مشخص از یک عدد تا یک عدد دیگر انتخاب کنیم که بین اینها با سه نقط ( ... ) از هم جدا میشوند! این به این معنی است که در این تعریف عدد اولی که تعیین میشود، تا عدد دوم به صورت کامل در index ذخیره خواهند شد و هیچ گونه کم و زیادی صورت نمیگیرد!. اما اگر بخواهیم یک عدد کمتر و یا در واقع یک تکرار کمتر داشته باشیم از این تعریف استفاده میکنیم؛ for index in 0..<5 { print(index) } در این صورت با آپریتر > ( کوچکتر ) میتوانیم یک عدد و در واقع یک تکرار کمتر داشته باشیم. اگر بخواهیم درمورد آرایه که در جلسات بعد دربارهی آنها صحبت خواهیم کرد، یک مثال بزنیم، به این صورت است؛ let _array_number = [10,20,30,40] for index in _array_number { print(index) } همانطور که میبینید، تعریف و استفاده از حلقهی for ساده و آسان است. حلقهی while، حلقهی دومی است که به معرفی آن خواهیم پراخت. این حلقه به این صورت تعریف خواهد شد؛ var number : Int8 = 0 while (number < 10) { print(number) number += 1 } در حلقه و در قسمت شرط آن شما میتوانید هر شرطی را که باعث ورود به بدنهی حلقه ( {} ) شود، تعریف کنید. کلمهی کلید while در ابتدا تعریف میشود و سپس شرطی را که مدنظرتان است، تعریف میکنید ( در بین دو پرانتز ) و سپس در بدنه شرط، دستورات خود را مینویسید. در نهایت، حلقهی آخر، حلقهی repeat استکه کارکردآن دقیقا همانند حلقهی do, while در سایر زبانهای برنامهنویسی است! نحوهی تعریف و استفاده از آن هم به این شکل است؛ var repeat : Int8 = 0 repeat { print(repeat) repeat += 1 } while repeat < 1 در این حلقه، بدنهی حلقه که شامل دستورات هستند ابتدا اجرا میشود و سپس شرط بررسی میشود! درست همانند do, while. این حلقه با کلمهی کلید repeat تعریف شده و سپس در بین آکولادها ( {} ) دستورات نوشته خواهند شد و بعد از آخرین آکولاد ( { ) جلوی آن و یا بعد آن کلمهی کلید while و سپس شرط را مینویسم. فقط توجه داشته باشید که while در اینجا نیازی به پرانتز ندارد!. البته این دستور فقط یک بار اجرا خواهد شد! چرا که متغییر repeat کوچکتر از ۱ نیست، بزرگتر از آن هم نیست و در واقع مساوی است ( مقدار repeat در همان ابتدا اول یک بار افزایش ( repeat =+ 1 ) داده میشود و مقدار ۱ را درون خود ذخیره میکند ). پس شرط نادرست ااست و حلقه دیگر اجرا نخواهد شد. نکته؛ دقت کنید که در تمامی حلقهها نیاز به آکولاد باز و بسته برای تعریف دستورات است و شما نمیتوانید بدون تعریف این آکولادها کدهای خودتان را بنویسید! پس این مورد را دقت کنید. به جزء حلقهی while دیگر حلقهها نیازی به پرانتز () برای تعریف شروط ندارند. امیدواریم که این جلسه هم مورد رضایت شما عزیزان قرار گرفته باشد.
-
1 امتیازآموزش زبان برنامهنویسی سوئیفت - جلسه دوم مواردی که در این جلسه یاد خواهید گرفت: نحوهی نامگذاری متغییرها ، نامگذاری صحیح و مجاز متغییرها و دستورات شرطی در ای جلسه، شما با نحوه نامگذاری صحیح متغییرها، دستورات شرطی و حلقهها آشنا خواهید شد و بعد از آن باید تمرین کنید که ریز به ریز همهچیز را متوجه شوید و این را بدانید که هر مطلبی را که باید برای بهتر کردن مهارتهای خودتون مطالعه کنید و عمل نکنید، در واقع هم به زودی مطلب از ذهنتان پاک خواهد شد و همچنین اینکه وقت و انرژی شما گرفته میشود خُب، برویم که وقت شما عزیزان از این بیشتر تلف نشود و آموزش را شروع کنیم. نحوهی نامگذاری صحیح متغییرها نحوهی نامگذاری متغییرها در هر پروژهی نرمافزاری از اهمیت زیادی برخوردار است! چرا که هم پروژهی شما بر روی نظم و قانون پیش خواهد رفت و هم این که اگر روزی خودتان یا توسعهدهندهای دیگر قصد داشت در توسعهی پروژه با شما همکاری کند، با دیدن نامهای عجیب و غریب متغییرها روبرو نشود! و نداند هر کدام از این متغییرها کجا استفاده میشوند! حالا جدا از اینکه یکسری استانداردها دیگر هم در کدنویسی باید رعایت شود ( کُد تَمیز ) که دیگر بستگی به خود شما و تجربهای دارد که شما کسب کردید. نکتهها: نامگذاری کارکترها شامل تمامی کارکترهای یونیکد ( کارکترهای مجاز ) است. مانند ( ? یا ? و ایمُوجیهای دیگر ) میباشد. نام متغییرها و ثابتها نمیتوانند شامل فضای خالی ( " " )، نمادههای ریاضی ( +,-,/ و .. )، علامتها ( {}.(),&,^ و .. ) و کارکترهای غیرمجاز باشند. سه تا از معروفتترین و پرکاربردترین نگارشها در برنامهنویسی و نامگذاری متغییر: نگارش شُتری ( camelCase ) نگارش پاسکال ( PascalCase ) نگارش ماری ( snake_case ) این سه از پرکاربرترین سبکهای نگارش در زبان برنامهنویسی و نامگذاری آنها هستند. البته نگارشهای دیگری هم هستند که همونطور که گفتیم، این سه، جز پرکاربردترین و معروفتترینها هستند که البته به شَخصه نگارش ماری را بیشتر دوست خواهم داشت! برویم و یک مثال از هر کدام بزنیم؛ نگارش شُتری ( camelCase ) var webSite = "www.iostream.ir" // Or with type var webSite : String = "www.iostream.ir" print(webSite) // Also the constant let webName = "www.iostream.ir" // Or with type let webName : String = "www.iostream.ir" print(webName) دقت کنید که حرف اول کلمه باید کوچک باشد و حرف دوم از کلمهی دوم بزرگ و سومین کلمه بازم بزرگ و همینطور تا آخر... و فقط حرف اول مورد هدف برای کوچک بودن در نظر گرفته میشود. نگارش پاسکال ( PascalCase ) var WebSite = "www.iostream.ir" // Or with type var WebSite : String = "www.iostream.ir" print(WebSite) // Also the constant let WebName = "www.iostream.ir" // Or with type let WebName : String = "www.iostream.ir" print(WebName) در این نگارش هم همانطور که مشاهده میکنید، حرف اول دوم و الی آخر ... هر کلمه باید بزرگ باشد. نگارش کبابی ( snake_case ) کباب به سبک برنامهنویسی?! var web_site = "www.iostream.ir" // Or with type var web_site : String = "www.iostream.ir" print(web_site) // Also the constant let _web_name = "www.iostream.ir" // Or with type let web_name : String = "www.iostream.ir" print(_web_name) توی این نگارش، نام متغییرها اگر دارای چندین کلمه باشند باید توسط یک آندرِلاین ( ـ ) ( دقت کنید که با علامت ( - ) متفاوت است ) از یک دیگر جدا میشوند و همچنین باید با حروف کوچک نوشته شوند. اگر دقت کرده باشید، متوجه شدهاید که ما از نامهای بیربط و گیجکننده استفاده نکردهایم! بطور مثال هر کسی با دیدن متغییری با نام web_site متوجه خواهد شد که این متغییر مربوط به دادههای وبسایت است که میتوانست به این شکل باشد، w , x , we و اینگونه نامها که گیجکننده هستند. حتی مثال بالا ( web_site ) میتوانست بهتر از این نیز باشد، web_site_iostream که هر چقدر نامهای متغییر واضحتر و اصولیتر باشد، خوانایی کدهای شما و استاندارد پروژهی شما بالا میرود که البته کار اول را که درست انجام بدهید، باقی کارها هم بر روی استاندادهای اصولی پیش میرود! البته این نظر خود بنده هستش?. ( خِشت اول که نهَد معمار کَج، تا ثُریا رَود دیوار کج! ) دستورات شرطی اگر وجود دستورات شرطی نباشند فقط میتوانیم یه برنامهی کوچک بنویسیم! در واقع یه برنامهی ساده و در حد چاپ اطلاعات و دستوراتی در همین محور. اما در پروژههای بزرگ نبود دستورات شرطی یعنی عذاب برای یک برنامهنویس! یه توضیح کوتاه دربارهی این موضوع بدهیم و سپس شروع به کدنویسی میکنیم که البته این دوره بر این مبنا شروع شده که شما اطلاعات پایهای از برنامهنویسی دارید و نیازی به توضیحات زیاد درمورد دستورات پایه ندارید. دستورات شرطی رو یک دو راهه در نظر بگیرید! اینکه یه تابلو زده شده است به سمت راست و نوشته که راه موفقیت شما از این طرفه و طرف دیگه نوشته این راه منجر به موفق نشدن شما میشود! حالا نوبت ذهن شماست که تصمیم بگیره کدام طرفی برود ( ذهن شما هم بر اساس باورهای شما تصمیم میگیرد، یعنی از قبل برنامهریزی شده است! ) مطمعنن کسی نمیخواد موفق نشود، اینطور نیست؟! پس دو راهه نقش شرط و ذهن شما نقش مترجم یا کامپایلر را بازی خواهد کرد با این تفاوت که ما حق انتخاب داریم اما در برنامهنویسی ما حق انتخاب را مشخص خواهیم کرد و کامپایلر همان چیزی را که ما صحیح میدانستیم و نوشتیم اجرا میکند. با این تعریف احتمالا متوجه شده باشید، پس برویم سراغ شرط دوستداشتنی if! در زبانهای مختلف، تعریف و استفاده از دستورات شرطی و یا سایر دستورات پایه تفاوتهایی با هم دارند و درمورد سوئیفت هم همین قانون صدق میکند! اینکه سوئیفت از سینتَکس ساده و زیبایی برای تعریف دستورات استفاده میکند که با سایر زبانها تفاوتهای ریزی دارد! تعریف دستور if در سوئیفت به این شکل است؛ var boolean : Bool = true if boolean == true { print(" Okay , This is boolean => \(boolean)") } در این تعریف همانطور که مشاهده میکنید، شرط if نیازی به پرانتز ندارد و فقط شرط جلوی آن قرار میگیرد و در صورت درست بودن ( true ) دستورات داخل بدنه ( {} ) اجرا خواهند شد. این نکته را هم در نظر داشته باشید بر خلاف زبانهای دیگر، باید حتما اکولادها ( {} ) را بگذارید حتی اگه فقط یک دستور باشد چرا که تعریف آن به همین شکل است. اما اگر شرط برقرار نبود و بخواهیم دستور دیگری را اجرا کنیم، از else، دوست خوب if استفاده خواهیم کرد؛ var boolean : Bool = true if boolean == true { print(" Okay , This is boolean => \(boolean)") } else { print(" This is not boolean! => \(boolean)") } در این قسمت اگر شرط اول برقرار نشد، یعنی boolean مقدارش false بود، else اجرا و پیغام This is not boolean به همراه مقدار متغییر boolean نمایش داده خواهد شد. در مواقعی هم میخواهیم چندین شرط داشته باشیم! یعنی که اگر شرط اول برقرار نبود، شرط دوم و اگر شرط دوم برقرار نبود شرط سوم و الی آخر .. که به این صورت عمل خواهیم کرد؛ let ـweb_site_name = "www.iostream.ir" if web_site_name == "www.iostream.ir" { print("This is web site => \(ـweb_site_name)") } else if ـweb_site_name == "www.google.com" { print("This is not web site!") } else if ـweb_site_name == "www.microsoft.com" { print("This is not web site!") { else { print("None") } به راحتی میتوانید دستورات شرطی خودتان را تا بینهایت ( مواقعی که مورد نیاز اس) گسترش دهید. Switch یکی دیگر از دستورات شرطی است که به مراتب، تمیزتر و خواناتر از if و else است! این دستور یک مقدار قبول دریافت میکند و بعد از آن توسط کِیسهایش ( case ) به بررسی آنها و مطابقت بود یا نبودن مقدار اصلی با مقادیر موجود میپردازد. نحوهی تعریف آن به این شکل است که مشاهده میکنید؛ let web_site_name = "www.iostream.ir" switch web_site_name { case "www.iostream.ir": print("This is web site => \(web_site_name)") case "www.google.com": print("This is not web site!") . . . . default: print("None") } switch در اینجا نقش همان if را بازی میکند با این تفاوت که بررسیها در بدنه و توسط caseهایی که دارد صورت میگیرد که case اول میگوید؛ اگر متغییر web_site_name برابر مقدار www.iostream.ir بود، چاپ کن؛ This is web site => web_site_name و به همین صورت caseهای دیگر که در صورتی که case قبلی با مقدار موجود در switch برابر نبود، case بعدی اجرا خواهد شد و در آخر شما میتوانید مانند دستور شرطی else اگر هیچکدام از شرطها ( در اینجا caseها ) برقرار نبود، چاپ کند None یا هیچکدام! این کار با استفاده از کلمهی کلیدی default صورت میگیرد، چیزی خارج از موارد تعریف شده ( caseها ) این مورد را اجرا خواهد کرد. امیدواریم که این جلسه مورد رضایت شما عزیزان قرار گرفته باشد.
-
1 امتیازفایلها/تغییرات پروژه را چطوری کنترل کنیم ؟ در وهلهٔ اوّل شاید بگید چه نیازیه ؟ خب برنامه رو مینویسیم و میریم دیگه !. درسته برنامهاتون را مینویسید و میروید؛ امّا به کجا چنین شتابان ؟ آیا همیشه برنامهٔ شما کوچکخواهد بود ؟ آیا قراره برنامهٔ شما در صد خط تمام بشه ؟ یا اینکه کلاً قصد توسعهاش رو دیگه ندارید ؟ خب شاید یکی دیگه داشت :). فرض کنید برنامهٔتان را نوشتید : void parsing(int argc, char **argv){ top = 0; for (unsigned i=1; i < (unsigned)argc; i+=2){ listArgs[top].name = argv[i]; listArgs[top].value = argv[i+1]; top++; } } خب، برنامهکار میکنه و میرید و یک هفتهٔ دیگه میاید و مثلاً خط : top = 0; را حذف میکنید. و برنامه در اجرای اوّل درست کار میکنه؛ پیشخودتون میگید خب چه نیازی بود، الکی ماهم کد نوشتیم :). امّا بعداً در اجراهای متوالی برنامه شروع میکنه به دادن خروجیهای نامتعارف. اینجاس که باید ساعتها وقت بزارید و بگردید ببینید آخرینبار چه چیزی رو تغییر دادید و کدوم فایل را ویرایش کردید. کار مسخره و اعصابخورد کنی میشه، درسته ؟. امّا برای مدیریت این دَنگٌوفَنگها میتونید از سیستمهایمدیریتپروژه استفاده بکنید. مثل Git حالا اینکه چرا گیت ؟ به خاطر اینکه راحتترینه و بهترینه. چرا ؟ چون امتحانش را پس داده، پروژهٔ بزرگ "کرنللینوکس" را داره مدیریت میکنه. حالا بیاید ببینیم اگه ما ازت گیت (git) استفاده میکردیم، چطوری میتوانستیم بفهمیم که چه بلایی سر کد آمده : ۱- اوّل گزارشات را چک میکنم، تا ببینم آخرین گزارشی که از تغییرات ذخیره کردم چه بوده ؟: $> git log commit bb513a5f9ec429222de03afa690e7fa5d2fbdf6e (HEAD -> master) Author: Ghasem Ramezani <g1999ramezani@gmail.com> Date: Sun May 5 00:05:22 2019 +0430 create a bug commit ab176fa8a282a74e6badfc285c0986bc66ee6b7d (origin/master, origin/HEAD) Author: Ghasem Ramezani <g1999ramezani@gmail.com> Date: Sat May 4 10:40:32 2019 +0430 make `top` to be 0 at first of parsing() function and make class storage of listArgs to be `extern` and getOption() function return "NULL" on Failure. خب فهمیدم که آخرین تغییرم با عنوان create a bug ثبت شده، حالا باید از شناسهاش استفاده کنم. ۲- تغییراتی که در آن گزارش ثبت شده است را مشاهده میکنم. : $> git show bb513a5f9ec429222de03afa690e7fa5d2fbdf6e commit bb513a5f9ec429222de03afa690e7fa5d2fbdf6e (HEAD -> master) Author: Ghasem Ramezani <g1999ramezani@gmail.com> Date: Sun May 5 00:05:22 2019 +0430 create a bug diff --git a/source/arg.c b/source/arg.c index c776ff2..a75c91d 100644 --- a/source/arg.c +++ b/source/arg.c @@ -7,7 +7,6 @@ unsigned top=0; struct ARGS listArgs[MAX_ARG]; void parsing(int argc, char **argv){ - top = 0; for (unsigned i=1; i < (unsigned)argc; i+=2){ listArgs[top].name = argv[i]; listArgs[top].value = argv[i+1]; دیدی به چه سادگی توانستیم تغییری که دادیم را پیدا کنیم ؟ البته این انتهای ماجرا نیست ! الآن که متوجه شدیم در کدام گزارشما خرابکاری کردیم؛ کافیه که تغییرات را به گزارش قبل از خرابکاری برگردانیم : $> git reset --hard ab176fa8a282a74e6badfc285c0986bc66ee6b7d البته قابل ذکره که ما اینجا تنها داخل این گزارش فقط یک تغییر داشتیم، مسلماً کار میتونه کمی پیچیدهتر بشه اگه تغییرات زیاد باشن، که همیشه هستن ?. چگونه با گیت (git) کار کنیم ؟ بسیار ساده، مسلماً اوّل نیاز دارید که این برنامه را نصب کنید. این برنامه به طور پیشفرض در سیستمعاملتون نصب نیست. کافیه که از مدیربستهٔ سیستمعاملتون کمک بگیرید، مثلاً برای Debian - Ubuntu - Ubuntu Mint به اینصورت کار تمام میشود : $ apt install git حالا بعد از نصب، نیاز دارید که مشخصاتتان را ثبت کنید، دقت کنید که تمام توضیحاتی که بنده میدهم را میتوانید بهصورت کاملتر از سایت گیت (git) دنبال کنید. $> git config --global user.name "Ghasem Ramezani" $> git config --global user.email "g1999ramezani@gmail.com" $> git config --global core.editor emacs دو مورد اوّل که واضح هستن، امّا مورد آخر دلبخواه خودتان هست، زمانیکه نیاز باشه گیت (git) ویرایشگرمتنی را جهت ویرایشباز بکند باید بداند که کدام ویرایشگر مورد علاقهٔ شماست. میتوانید هر برنامهای را قرار بدهید. امّا دقت کنید که بهترین ویرایشگرها میتوانند Vim, Emacs, Notepad++ باشند؛ فایل این تنظیمات را میتوانید از این مسیرها دنبال کنید : User Space : ~/.gitconfig System Wide: /etc/gitconfig ساخت مخازن (repository) خب حالا که نصب/پیکربندی انجام شد، کافیه که مخزن (repository) خودمان را راهاندازی کنیم. یک پروژهٔ جدید درست کنید و گیت (git) را مقداردهی (Initialize) کنید : $> mkdir project ; cd project $> git init ما یک دایرکتوری به اسم project درست کردیم، و مخزن (repository) خودمان را با دستور git init راهاندازی کردیم، یک سری فایلهایی گیت (git) برای ما داخل آن دایرکتوری با اسم .git درست کرده. تغییراتیکه نیاز رو انجام میدیم، مثلاً در وهلهٔ اوّل دایرکتوریها و فایلهای پروژه را راهاندازی میکنیم : $> mkdir header source build object $> touch header/arg.h source/arg.c Makefile $> tree . ├── build ├── Makefile ├── header │ └── arg.c ├── object └── source └── arg.h 4 directories, 3 files $> اگه درمورد Makefile نمیدانید، میتوانید از اینجا با GNU Make و Makefile آشنا بشید. الآن بد نیست که خروجی دستور git status را ببینیم تا توضیحاتی در اینباره بدیم (این دستور، وضعیتجاری مخزنمان را نشان میدهد) : $> git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) Makefile header/ source/ nothing added to commit but untracked files present (use "git add" to track) عکس زیر را مشاهدهکنید تا توضیحبهتری بدم : فایلهای شما داخل گیت (git) دارای حالاتهای مختلفیهستن، به طورکلّی یا شناختهشدناند (tracked) یا ناشناختهاند (untracked)؛ فایلها/دایرکتوریهایی که ما بعد از مقداردهی مخزنمان ساختیم، در حالت ناشناخته (untracked) هستند. که خود گیت (git) هم همینرا به ما گفتهاست : Untracked files: (use "git add <file>..." to include in what will be committed) برای اینکه شناختهشده (tracked) بشند، باید آنها را به صحنه (stage) ببریم. برای اینکار خود گیت گفتهاست که باید چه کرد که میتوانیم به دوصورت انجام دهیم : $> git add Makefile source header $> git add -A خب حالا دوباره خروجی git status را نگاه میکنیم : $> git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: Makefile new file: header/arg.c new file: source/arg.h الآن فایلهای ما به stage رفتند، و آمادهٔ اینهستند که گزارش (commit) بشوند. دقت کنید که Git از خِیر دایرکتوریهای خالی میگذرد. حالا کافیهکه ما تغییراتی که دادیم را گزارش کنیم، که با دستور git commit به دوصورت انجام میشود : $> git commit $> git commit -m "My Message" در حالتاوّل، گیت ادیتور پیشفرضتان را باز میکند و از شما میخواهد که یک توضیحکوتاه درمورد تغییراتیکه دادهاید بنویسید، در حالتدوّم، شما مستقیم توضیحکوتاه خود را وارد میکنید. حال دوباره برگردیم و خروجی دستور git status را ببینیم : $> git status On branch master nothing to commit, working tree clean خیلیهم عالی، این نشان دهندهٔ ایناست که ماهیچ فایل ناشناخته (َUntracked) یا دستکاریشده (Modified) یا درصحنه (Stage) نداریم. هنگامیکه تغییراتی را در فایلهای شناختهشده (Tracked) بدید، آن فایل از حالت دستکارینشده (Unmodified) به حالت دستکاریشده (Modified) درمیاید، که نیاز است شما تغییرات را درصورتنیاز وارد صحنه (Stage) کنید و بعد گزارشکنید (Commit). حال تغییراتیاعمال میکنیم، و مراحلمورد نیاز تا درصحنه (Stage) بردنفایلها انجام میدهیم : $> git add -A $> git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: Makefile modified: header/arg.h modified: source/arg.c امّا شاید شما نخواید که مثلاً Makefile گزارشش با مابقیه فایلها یکی باشه، و نیاز دارید که از Stage بیرون بیاریدش؛ دقّت کنید خود Git هم راهنمایی کرده که باید چهکار کرد : $> git reset HEAD Makefile $> git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: header/arg.h modified: source/arg.c Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: Makefile و حالا میتوانید به راحتی گزارش فایلهای خودتان را برای header/arg.h و source/arg.c بنویسید : $> git commit -m "Done With Print() Function" فایل .gitignore بیاید تا make را اجرا کنیم (درصورتیکه با GNU Make آشنا نیستید، برای آشنایی اینقسمت را مطالعه کنید) : $> git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: Makefile Untracked files: (use "git add <file>..." to include in what will be committed) build/ object/ no changes added to commit (use "git add" and/or "git commit -a") اینجا دایرکتوریهای build و object هم اضافه شدند،امّا ما نیازی نداریم که Git این دایرکتوریها را مدیریت کند، پس کافیه که یک فایل به اسم .gitignore درست کنیم. و فایلها و دایرکتوریهایی که نمیخواهیم Git آنها را دنبال کند را در آن ذکر کنیم : $> echo -e "/build/*\n/object/*" > .gitignore $> cat .gitignore /build/* /object/ $> git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: Makefile Untracked files: (use "git add <file>..." to include in what will be committed) .gitignore no changes added to commit (use "git add" and/or "git commit -a") و مشاهده میکنید که دیگر Git اخطاری برای دایرکتوریهای build و object نداد. خب دوستان،امیدوارم دلیل اهمیّت Git و کلاً برنامههای کنترلورژن را درککرده باشید؛ امّا شرمنده، دنیای Git بزرگتر از آنچه هست که من بخوام خلاصهای از هر قسمت را در یک پست بازگو کنم؛ شدیداً پیشنهاد میکنم که اینکتاب را برای فراگیری هرچه بهتر Git بخوانید. - موفق و پیروز باشید. ?