AliReza Beigy
AliReza Beigy
خواندن ۸ دقیقه·۳ سال پیش

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

درود

این جلسه میخوایم مطالبی که سر کلاس حضوری صحبت کردیم رو دوباره بهشون اشاره کنیم که بتونید با تامل مطالب رو دنبال کنید.

گیت - Git

چرا از گیت استفاده میکنیم؟
1. انجام کار تیمی رو راحت تر میکنه و مدیریت پروژه بهتر انجام میشه
2. مدیریت نسخه های برنامه برای برنامه نویس محیا میکنه (برای میتونید ببینید برای پیاده سازی ویژگی X چه تغییراتی اعمال شده)

برای استفاده از Git باید این برنامه رو از سایت زیر دریافت کنید و نصب کنید

https://git-scm.com/downloads

گیت یک ابزار تحت کامند لاین هست که میتونید با اجرای دستوراتی از امکانات اون استفاده کنید.


مهم

قبل از اینکه وارد توضیحات گیت بشیم بگم که توی این جلسه صرفا سعی میکنیم با دستورات گیت و مفاهیم اون آشنا بشیم و در نهایت خیلی از این دستوراتی که استفاده میکنیم توسط IDE ها به صورت خودکار و به صورت گرافیکی انجام میشه، پس هدف ما یادگیری مفاهیم گیت هست که بدونیم چطور ازش باید استفاده کنیم.


فرض کنید ما یک پروژه داریم که صرفا یک فایل main.cpp داخلش قرار دادیم و یک فانکشن ساده رو پیاده سازی کردیم به شکل زیر:

main.cpp

int 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-A

page-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
regexgitبرنامه نویسی پیشرفته
شاید از این پست‌ها خوشتان بیاید