ارائه TA برنامه نویسی پیشرفته - Git و Regex - جلسه 1

دروداین جلسه میخوایم مطالبی که سر کلاس حضوری صحبت کردیم رو دوباره بهشون اشاره کنیم که بتونید با تامل مطالب رو دنبال کنید.گیت - Gitچرا از گیت استفاده میکنیم؟1. انجام کار تیمی رو راحت تر میکنه و مدیریت پروژه بهتر انجام میشه2. مدیریت نسخه های برنامه برای برنامه نویس محیا میکنه (برای میتونید ببینید برای پیاده سازی ویژگی X چه تغییراتی اعمال شده)برای استفاده از Git باید این برنامه رو از سایت زیر دریافت کنید و نصب کنیدhttps://git-scm.com/downloadsگیت یک ابزار تحت کامند لاین هست که میتونید با اجرای دستوراتی از امکانات اون استفاده کنید.مهمقبل از اینکه وارد توضیحات گیت بشیم بگم که توی این جلسه صرفا سعی میکنیم با دستورات گیت و مفاهیم اون آشنا بشیم و در نهایت خیلی از این دستوراتی که استفاده میکنیم توسط IDE ها به صورت خودکار و به صورت گرافیکی انجام میشه، پس هدف ما یادگیری مفاهیم گیت هست که بدونیم چطور ازش باید استفاده کنیم.فرض کنید ما یک پروژه داریم که صرفا یک فایل main.cpp داخلش قرار دادیم و یک فانکشن ساده رو پیاده سازی کردیم به شکل زیر:main.cppint main() { return 0; }پس پوشه پروژه ما به صورت زیر میشه - MyProject - main.cppحالا میخوایم از امکانات گیت توی این پروژه استفاده کنیم، اولین کاری که باید انجام بدیم initialize کردن گیت در پوشه پروژه هست که دستور زیر رو در command prompt در پوشه مربوطه اجرا میکنیم.> git initبا اجرای این دستور فولدری در پوشه پروژتون درست میشه به نام .git که تنظیمات و اطلاعات تاریخچه پروژه شما داخلش با ساختار خاصی ذخیره میشه، به صورت زیرحالا باید با مفهومی آشنا بشیم به نام کامیت (Commit)، زمانی که ما تغییری را در پروژه ایجاد میکنیم باید این تغییرات رو ثبت کنیم و این ثبت تغییرات رو به صورت کامیت در گیت داریم.الان که گیت رو در پوشه پروژه تنظیم کردیم نوبت این هست که تغییرات رو ثبت کنیم، برای ثبت تغییرات از دستور زیر استفاده میکنیم> git commit -m &quotcommit message&quotدستور بالا تغییرات رو با نام خاصی که به هر مجموعه تغییرات میدیم ثبت میکنهفقط یه نکته ای، الان گیت از کجا میدونه من میخوام کدوم تغییرات رو ثبت کنم؟ شاید چندین فایل داشته باشیم و فقط بخوایم یکی از فایل ها تغییراتش ثبت بشه، اینجا مفهومی مطرح میشه به اسم Stage به این شکل که ما فایل هایی که میخوایم تغییراتش رو ثبت کنیم در Stage اضافه میکنیم و دستور commit به stage نگاه میکنه و فایل هایی که داخل Stage هستند رو ثبت میکنه، برای اضافه کردن فایل به stage از دستور زیر استفاده میکنیم> git add main.cppکه میتونی با دستور زیر هر فایل که در پوشه کنونی وجود داره رو به stage اضافه کنیم> git add .حالا میتونیم از طریق دستور زیر تغییراتمون رو ثبت کنیم> git commit -m &quotfirst commit&quotحالا بریم سراغ یه سری امکاناتی که در کنار هم دیگه به ما کمک میکنن تا با گیت بهتر کار کنیمدر گیت مفهومی وجود داره به نام برنچ (Branch) که یک لینک لیست از کامیت ها هست. یعنی شما میتونید یک مجموعه کامیت رو در کنار هم داشته باشید که به اون لیست نامی رو اختصاص بدید.به صورت پیش‌فرض شما در برنچ master قرار دارید و کامیت هایی که میزنید به این لیست اضافه میشه، حالا ممکنه شما نیاز داشتید باشید بعد از یک مدت یک برنچ A از برنچ master ایجاد کنید و از این به بعد تغییراتتون رو داخل برنچ A ثبت کنید.برای شروع بیاید یک برنچ به اسم test ایجاد کنیم و یک تغییر داخل فایل main.cpp بدیم و این تغییر رو ذخیره کنیم، برای ایجاد برنچ جدید باید از دستور زیر استفاده کنیم> git branch testحالا برای اینکه از این به بعد کامیت ها داخل این برنچ ثبت بشه باید داخلش checkout کنید که با دستور زیر میتونیم اینکارو انجام بدیم> git checkout testیه تغییر تو فایل main.cpp بدیم و بریم یه کامیت هم با دستور زیر ثبت کنیم> git add main.cpp > git commit -m &quotmake a change in test branch&quotحالا میتونید تاریخچه کامیتی که زدیم رو با دستور زیر ببینیم (آپشن های all و graph صرفا برای نمایش بهتر گراف هست)> git log --graph --allتوی این تصویر دوتا مورد هست که بهش اشاره میکنم1f8a715dfaab25b5061f8726ed699c4a534c9d79 1c01aa3da3bde22469dbcadee8aa480b84f5d589عبارت های بالا هش اطلاعات کامیتی هست که ثبت کردیم، در واقع اطلاعاتی شامل مسیج کامیت و تغییراتی که اون کامیت شامل بوده هست.عبارت HEAD نشان دهنده آخرین کامیت برنچ کنونی هست.سوالی که الان پیش میاد اینه که این تغییرات رو چطور با هم تیمی ها به اشتراک بزاریم؟ اینجا مفهومی به نام remote مطرح میشه که ما اعلام میکنیم در کجا ها میخوایم این تغییرات رو آپلود بکنیم.تو گام اول اینکه کجا میتونیم پروژه های گیت رو قرار بدیم باید بگم که برای قرار دادن پروژه میتونیم به پلتفرم هایی مثل github.com و یا gitlab.com مراجعه کنیم، اینها تعدادی از پلتفرم ها هستن، شما میتونید خودتون هم برای خودتون همچین git server هایی بالا بیارید. برای ایجاد remote از دستور زیر استفاده میکنیم، توجه داشته باشید که هر پروژه میتونه چندین remote داشته باشه و توی چندین remote مختلف آپلود بشه> git remote add origin https://github.com/AliRezaBeigy/Testعبارت origin صرفا یه اسم هست برای remote که میتونه هر چیزی باشهبرای شروع آپلود هم از دستور زیر استفاده میکنیم که کامیت ها ما رو اپلود کنه> git pushاگر بار اولی باشه که میخواید push کنید احتمالا باید یک برنج رو به عنوان برنچ پیش‌فرض معرفی کنید> git push --set-upstream origin masterاز اونجایی که ما دوتا برنچ داریم و میخوایم دوتاش رو push کنیم با دستور زیر برنچ test هم push میکنیم> git push origin testحالا پروژه ما داخل Repository ما آپلود شده.برای استفاده حرفه ای تر از گیت بهتره با ویژگی هایی هم آشنا بشیمفرض کنید در حال پیاده سازی تابع جستجو در لیست هستید و تصمیم میگیرید پیاده سازی این تابع رو به زمان دیگه ای موکول کنید، اینجا نیاز دارید تا تغییرات رو داخل فضایی ذخیره کنید، برای این کار میتونید از دستور زیر استفاده کنید> git stash push -m &quotsearch function&quotدستور بالا تغییرات کنونی رو داخل یک پشته ذخیره میکنه و با عملیات های push و pop میتونید تغییرات رو کنترل کنید، برای نمایش لیست stash ها میتونید از دستور زیر استفاده کنید> git stash listبرای برگدوندن تغییرات از stash میتونید از دستور زیر استفاده کنید> git stash pop 0همون طور که اشاره کردیم ما میتونیم تغییرات رو توی برنچ های مختلف ثبت کنیم، این به معنی این هست که فرض کنید شما و دوستتون رضا دارید روی پروژه کار میکنید و شما قراره صفحه A رو پیاده کنید و رضا قراره صفحه B رو پیاده سازی کنه، برای اینکه کار گروهی بهتری داشته باشید بهتره هر کدوم روی برنچ مخصوص اون ویژگی یعنی پیاده سازی صفحه A و صفحه B کار کنیدشما یک برنچ با دستور زیر درست میکنید> git branch page-Aروی اون برنچ checkout میکنید> git checkout page-Aحالا پیاده سازی خودتون رو انجام میدید و تغییراتون رو commit میکنید(بهتره تغییرات خودتون رو در چندین کامیت ثبت کنید)به این مرحله رسیدیم که شما و رضا هر دو تغییرات خودتون رو پیاده کردید و دو برنچ با مجموعه ای از تغییرات دارید، به شکل زیر توجه کنیداینجا ما یک برنچ master داریم که بعد از کامیت دوم دو برنچ page-A و page-B ساخته شده و ابتدا یک کامیت در page-A ثبت شده و بعد از آن یک کامیت در page-B ثبت شده و در نهایت یک کامیت در page-A ثبت شده.نمایی از log هم ببینیمحالا بعد از اینکه کار دو برنچ به پایان برسه باید برنچ ها رو با هم یکسان کنیم و تغییرات رو به برنچ master بیاریم، به همچین عملی مرج(merge) میگیم که میتونیم با دستور زیر اینکار رو انجام بدیم (توجه داشته باشید که باید ابتدا در برنچ master قرار بگیرید و بعد دستور زیر را بزنید تا برنچ مربوطه را با آن مرج کند)> git merge page-Aیه نگاهی دوباره به log بندازیمهمونطور که میبینید الان دو برنچ master و page-A به یک کامیت اشاره میکنندبرای برنچ page-B هم همین دستور رو ثبت میکنیم ولی به یک مشکل میخوریم، برای درک علت مشکل یک نگاهی به تغییرات مثال که مطرح کردم بکنیدpage-Apage-Bبنظرتون گیت توانایی تشخیص و مرج کردن این دو برنچ رو داره؟ چطور میتونه تشخیص بده که باید این دو فانکشن رو در کنار هم قرار بده؟ جواب این هست که نمیتونه و به اصطلاح به conflict میخوره و از شما میخواد که این conflict رو رفع کنید، یه نگاه به اتفاقی که بعد از اجرای دستور زیر میوفته بندازیم> git merge page-Bهمونطور که میبینید گیت یک سری علامت به کد ما اضافه کرده و اعلام کرده در این بخش مشکل در تشخیص داره، حالا نوبت ماست تا این مشکل رو حل کنیموقتی کانفلیکتی رخ میده گیت به فرمت زیر مشکل رو نمایش میده<<<<<<< HEAD تغییرات در برنچ کنونی ======= تغییرات در برنچ موردنظر >>>>>>> اسم برنچ موردنظرحالا خودمون مشکل رو حل میکنیم و این فرمت هم پاک میکنیم و در نهایت تغییرات رو commit میکنیم.توی مثال قبل شما و رضا روی پروژه کار میکردید اگر رضا تغییراتش رو push کنه شما برای اینکه به تغییرات اون دسترسی داشته باشید میتونید از دستور زیر استفاده کنید تا تغییرات و تاریخچه گیت بروز بشه> git pullعبارات با قاعده - Regexفرض کنید یک متن بزرگ بهتون دادند و گفتن از داخلش IP ها رو استخراج کنید به طور مثال به متن زیر توجه کنیدHello ALiReza LMS: address (https://lms.khu.ac.ir) IP: 127.0.0.1 IP: 127.0.0.2 IP: 192.5.0.3 IP: 127.0.0.4قاعدتا با سرچ کردن . توی یک متن بزرگ نمیشه به IP ها رسید، از اونجایی که از اینکه IP ها شامل چه اعدادی میشن هم اطلاعی نداریم نمیتونیم به راحتی IP ها رو بدست بیاریمراه حل استفاده از عبارات با قاعده یا Regex هست، به این معنی که یک عبارتی بنویسیم که برای کامپیوتر قابل درک باشهبرای شروع من یک عبارت با قاعده خیلی مبتدی مینویسم که IP های داخل متن بالا رو پیدا کنه\d\d\d\.\d\.\d\.\dعبارت بالا از \d استفاده شده که به معنی عدد یا digit هست \. به معنی کاراکتر . هست، عبارت بالا گفتید مواردی رو پیدا کن که سه عدد کنار هم و بعد از آن یک نقطه و یک عدد و یک نقطه و یک عدد و یک نقطه و یک عدد دیده بشهحالا اگه متن رو به صورت زیر تغییر بدیمHello ALiReza LMS: address (https://lms.khu.ac.ir) IP: 127.0.0.1 IP: 127.0.0.2 IP: 192.55.0.3 <- this line changed IP: 127.0.0.4توی متن بالا Regex قبل نمیتونه 192.55.0.3 رو پیدا کنه چون قسمت دوم از دو عدد تشکیل شده، پس بهتره Regex نوشته شده رو بهبود بدیم تا این دسته از IP ها رو هم پیدا کنه\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}در Regex بالا با استفاده از {1,3} اعلام میکنیم که کاراکتر میتونه 1 تا 3 بار تکرار بشهحالا بیاید استفاده از Regex در کد cpp هم ببینیمبرای استفاده از Regex باید regex را include کنید، بیاید یک نمونه کد ببینیم#include <regex> #include <string> #include <iostream> int main() { using namespace std; string input = &quotHello\n&quot &quotALiReza\n&quot &quotLMS: address (https://lms.khu.ac.ir)\n&quot &quotIP: 127.0.0.1\n&quot &quotIP: 127.0.0.2\n&quot &quotIP: 192.55.0.3\n&quot &quotIP: 127.0.0.4&quot cout << &quotList Of IP:&quot << endl; regex reg(R&quot(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})&quot); smatch matches; regex_search(input, matches, reg); cout << &quotMatched Position: &quot << matches.position() << endl; cout << &quotMatched: &quot << matches.str() << endl; return 0; }در اینجا ما یک شی از کلاس regex ساختیم و از cpp 11 به بعد میتونید آپشن R قبل از string استفاده کنید و راحت تر بدون دوبار \ گذاشتن عبارت با قاعده خودتون رو بنویسید.برای اینکه عبارتی رو از طریق regex پیدا کنید میتونید از متد regex_search استفاده کنید و خروجی آن یک bool برای اینکه متوجه بشید آیا این عبارت در متن پیدا شده یا خیراطلاعات مورد پیدا شده را در smatch ثبت میشه و میتونید از متد هایی مثل position یا str برای دسترسی به اطلاعات مورد پیدا شده استفاده کنیدخروجی کد بالا به صورت زیر میشود:List Of IP: Matched Position: 55 Matched: 127.0.0.1یک مثال دیگه بزنیم، میخوایم همه IP هایی که قبل اون ها IP: داره رو پیدا کنیمبرای اینکار کافیه Regex رو به صورت زیر تغییر بدیمIP: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}حالا خروجی کد بالا با Regex جدید به صورت زیر میشهList Of IP: Matched Position: 51 Matched: IP: 127.0.0.1همونطور که میبینید قسمت IP: هم به خروجی اضافه شد، اگر بخوایم فقط قسمت خاصی رو استفاده کنیم از مفهومی به اسم group استفاده میکنیم، به این صورت که قسمتی از عبارت رو که میخوایم داخل پرانتز قرار میدیم و از طریق تابع str و ورودی index میتونیم به group مورد نظر دسترسی داشته باشیم، به کد زیر توجه کنید#include <regex> #include <string> #include <iostream> int main() { using namespace std; string input = &quotHello\n&quot &quotALiReza\n&quot &quotLMS: address (https://lms.khu.ac.ir)\n&quot &quotIP: 127.0.0.1\n&quot &quotIP: 127.0.0.2\n&quot &quotIP: 192.55.0.3\n&quot &quotIP: 127.0.0.4&quot cout << &quotList Of IP:&quot << endl; regex reg(R&quot(IP: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))&quot); smatch matches; regex_search(input, matches, reg); cout << &quotMatched Position: &quot << matches.position() << endl; cout << &quotWhole Matched: &quot << matches.str(0) << endl; cout << &quotIP Matched: &quot << matches.str(1) << endl; return 0; }در دستور بالا قسمت IP عبارت رو داخل پرانتز گذاشتیم و با استفاده از ورودی str تونستید group مورد نظر رو انتخاب کنیم(توجه داشته باشید که index صفر به معنی کل عبارت و 1 به معنی گروه اول میباشد)خروجی کد بالا به صورت زیر میشودList Of IP: Matched Position: 51 Whole Matched: IP: 127.0.0.1 IP Matched: 127.0.0.1حالا اگر متن رو به صورت زیر تغییر دهیمHello ALiReza LMSIP: 45.56.53.56 <- this line changed LMS: address (https://lms.khu.ac.ir) IP: 127.0.0.1 IP: 127.0.0.2 IP: 192.55.0.3 <- this line changed IP: 127.0.0.4میخواهیم IP هایی رو پیدا کنیم که قبل از IP: کاراکتر S وجود نداشته باشد، Regex رو به صورت زیر تغییر میدیم[^S]IP: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))در اینجا از [^] استفاده شد که اعلام کردیم قبل از IP: کاراکتر S نباید دیده شود میتوانید عبارتی به صورت زیر بنویسید[^12345678]TESTبه معنی اینکه قبل از TEST هیچ کدام از کاراکتر های 1 تا 8 نباید دیده شود(حواستون باشه به معنی دیده نشدن 12345678 نیست)اگر بخواهیم همه موارد که پیدا میشود در متن پیدا کنیم بهتر از کلاس sregex_iterator استفاده کنیم، به کد زیر توجه کنید#include <regex> #include <string> #include <iostream> int main() { using namespace std; string input = &quotHello\n&quot &quotALiReza\n&quot &quotLMS: address (https://lms.khu.ac.ir)\n&quot &quotIP: 127.0.0.1\n&quot &quotIP: 127.0.0.2\n&quot &quotIP: 192.55.0.3\n&quot &quotIP: 127.0.0.4&quot cout << &quotList Of IP:&quot << endl; regex reg(R&quot(IP: (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}))&quot); for(sregex_iterator i = sregex_iterator(input.cbegin(), input.cend(), reg); i != sregex_iterator(); i++) { const smatch& match = *i; cout << match.str(1) << endl; } return 0; }خروجی کد بالا به صورت زیر هستList Of IP: 127.0.0.1 127.0.0.2 192.55.0.3 127.0.0.4حالا بریم یه Regex بنویسیم که اسم ALiReza رو پیدا کنه، برای اینکار اگر من از عبارت زیر استفاده کنم هیچ موردی پیدا نمیشهalirezaعلتش هم این هست که به صورت پیش‌فرض Regex به بزرگ و کوچیکی حساسه ولی ما میتونیم با فلگ ها(flag) به Regex بگیم که این عبارت به بزرگی و کوچیکی حساس نیست، به کد زیر توجه کنید#include <regex> #include <string> #include <iostream> int main() { using namespace std; string input = &quotHello\n&quot &quotALiReza\n&quot &quotLMS: address (https://lms.khu.ac.ir)\n&quot &quotIP: 127.0.0.1\n&quot &quotIP: 127.0.0.2\n&quot &quotIP: 192.55.0.3\n&quot &quotIP: 127.0.0.4&quot regex reg(R&quot(alireza)&quot, regex::icase); smatch matches; regex_search(input, matches, reg); cout << &quotMatched Position: &quot << matches.position() << endl; cout << &quotMatched: &quot << matches.str() << endl; return 0; }خروجی کد بالا به صورت زیر استMatched Position: 6 Matched: ALiReza