مسیرها
/
C++
C++
/
تمرین‌ها
/
بنیاد اسپیدی‌واگن
بنیاد اسپیدی‌واگن

بنیاد اسپیدی‌واگن

تمرین یادگیری

مقدمه

مانند بسیاری از زبان‌های دیگر، C++ هم «اشاره‌گر» دارد. شما پیش‌تر می‌دانید که «مرجع» و اشاره‌گر به هم شبیه‌اند، اما آن‌ها را یک پله نزدیک‌تر به نحوه‌ی کار درونی رایانه‌ی خودتان در نظر بگیرید. اشاره‌گرها متغیرهایی‌اند که آدرس اشیاء را در خود نگه می‌دارند. از آن‌ها برای تعامل مستقیم با اشیاء استفاده می‌شود، که تخصیص حافظه‌ی پویا و دستکاری کارآمد داده را در C++ ممکن می‌کند.

اگر تازه با اشاره‌گرها آشنا شده‌اید، ممکن است کمی رمزآلود به نظر برسند، اما وقتی به آن‌ها عادت کنید، کاملاً سرراست‌اند.

آن‌ها بخش حیاتی از C++ هستند، پس وقت بگذارید و واقعاً درکشان کنید. نسخه‌ی ابتدایی آن‌ها در این مفهوم، «اشاره‌گر ساده» یا «اشاره‌گر خام» نامیده می‌شود. در C++ مدرن «اشاره‌گر هوشمند» هم وجود دارد؛ نوع پایه اصلاً هوشمند نیست و باید همه‌ی کارها را خودتان به‌صورت دستی انجام دهید.

پیش از پرداختن به جزئیات، بهتر است کاربرد اشاره‌گرها را درک کنید. اشاره‌گرها روشی برای به اشتراک گذاشتن آدرس یک شیء با بخش‌های دیگر برنامه‌ی ما هستند، که به دو دلیل عمده مفید است:

  1. مانند مرجع‌ها، اشاره‌گرها از کپی کردن پرهیز می‌کنند و به کاهش مصرف منابع برنامه‌ی شما کمک می‌کنند.
  2. برخلاف مرجع‌ها، می‌توان اشاره‌گرها را به اشیاء دیگری دوباره نسبت داد.
  3. اشاره‌گرها همچنین می‌توانند به یک مقدار تهی اشاره کنند تا نشان دهند که در حال حاضر به هیچ شیء‌ای اشاره نمی‌کنند.

نحوه‌ی نگارش کلی

اعلان یک اشاره‌گر در C++ شامل مشخص کردن نوع داده‌ای است که اشاره‌گر به آن اشاره می‌کند و پس از آن یک ستاره (*) و اسم اشاره‌گر می‌آید. وقتی اشاره‌گرها اعلان می‌شوند، به‌طور خودکار مقداردهی اولیه نمی‌شوند. بدون انتساب صریح، یک اشاره‌گر معمولاً مقداری نامعین در خود نگه می‌دارد که اغلب از آن به «آدرس زباله» یاد می‌شود. گرچه برخی کامپایلرها ممکن است اشاره‌گرها را با nullptr مقداردهی اولیه کنند، این رفتار در همه‌ی کامپایلرها تضمین‌شده نیست، پس ضروری است که به آن تکیه نکنید. بهترین روش این است که اشاره‌گرهای خام را به‌طور صریح مقداردهی اولیه کنید و پیش از استفاده، تهی نبودن آن‌ها را بررسی کنید تا از بروز مشکلات احتمالی جلوگیری شود.

int* ptr{nullptr}; // Declares a pointer and makes sure it is not invalid

برای انتساب آدرس یک متغیر به یک اشاره‌گر، از «عملگر آدرس‌دهی» (&) استفاده می‌کنید. ارجاع‌زدایی از یک اشاره‌گر با «عملگر ارجاع» (*) انجام می‌شود.

std::string opponent{"Solomon Lane"};
// 'ethan' points to the address of the string opponent
std::string* ethan{&opponent};
// Instead of ethan's, the opponent's name address is given to the passPort
std::string passportName{*ethan};

توجه: ارجاع‌زدایی باید به‌طور صریح انجام شود، در حالی که مرجع‌ها صرفاً مثل یک نام مستعار کار می‌کردند.

حساب اشاره‌گر

«حساب اشاره‌گر» به شما امکان می‌دهد عملیات حسابی را روی اشاره‌گرها انجام دهید، که به‌ویژه هنگام کار با آرایه‌ها مفید است. افزودن یک عدد صحیح به یک اشاره‌گر باعث می‌شود به عنصر دیگری اشاره کند.

// Stargate Coordinate Code
int gateCode[] = {462, 753, 218, 611, 977};
// 'ptr' points to the first element of 'gateCode'
int* ptr{&gateCode[0]};
// Accesses the third Stargate address through pointer arithmetic
int dialedAddress{*(ptr + 2)};
// Chevron encoded! Dialing Stargate address:
openStarGate(dialedAddress);
Caution

حساب اشاره‌گر در C++ اگر با دقت مدیریت نشود، به‌راحتی می‌تواند به رفتار تعریف‌نشده منجر شود. رفتار تعریف‌نشده می‌تواند خود را در نتایج غیرمنتظره‌ی برنامه، از کار افتادن‌ها یا حتی آسیب‌پذیری‌های امنیتی نشان دهد. یکی از نمونه‌های بدنام پیامدهای رفتار تعریف‌نشده در سال ۱۹۹۶ رخ داد، در انفجار موشک آریان ۵، جایی که یک استثنای نرم‌افزاری ناشی از تبدیل یک عدد ممیز شناور ۶۴ بیتی به یک عدد صحیح علامت‌دار ۱۶ بیتی به یک شکست فاجعه‌بار انجامید.

دسترسی به متغیرهای عضو

در C++، از عملگر -> برای دسترسی به اعضای یک شیء از طریق اشاره‌گر به آن شیء استفاده می‌شود. این شکل کوتاه‌تری است که دسترسی به اعضای اشیائی را که اشاره‌گرها به آن‌ها اشاره می‌کنند ساده‌تر می‌کند. برای مثال، اگر ptr اشاره‌گری به شیئی با متغیر عضو x باشد، به‌جای استفاده از (*ptr).x می‌توانید مستقیماً از ptr->x استفاده کنید. این عملگر هنگام کار با اشاره‌گرها به اشیاء، خوانایی کد را افزایش می‌دهد و پرگویی را کاهش می‌دهد.

در اینجا یک مثال کوتاه داریم، با یک struct به اسم Superhero که متغیر عضوی به اسم superpower دارد. تابع main اشاره‌گری به اسم dianaPrince به یک شیء Superhero می‌سازد (که نماینده‌ی Wonder Woman است). از عملگر -> برای دسترسی به متغیر عضو superpower استفاده می‌شود که کمند معروف Wonder Woman یعنی «Lasso of Truth» را به نمایش می‌گذارد.

struct Superhero {
    std::string superpower;
};
Superhero wonder_woman{};
Superhero* dianaPrince = &wonder_woman;
dianaPrince->superpower = "Lasso of Truth";
// Using the -> operator to access member variable superpower:
std::cout << "Wonder Woman, possesses the mighty " << dianaPrince->superpower;

اشاره‌گر در برابر مرجع

اشاره‌گرها و مرجع‌ها هر دو دسترسی غیرمستقیم به اشیاء را ممکن می‌کنند، اما در توانایی‌ها و ملاحظات ایمنی‌شان با هم تفاوت دارند. اشاره‌گرها انعطاف تغییر شیء هدف خود را می‌دهند و می‌توانند مقدار تهی بگیرند. با این حال، این انعطاف ریسک‌هایی به همراه دارد، مثل ارجاع‌زدایی از اشاره‌گرهای تهی یا ساختن اشاره‌گرهای معلق. در مقابل، مرجع‌ها نمی‌توانند تهی باشند و هنگام ساخت به اشیاء معتبر گره می‌خورند و از این ریسک‌ها در امان می‌مانند. با توجه به ماهیت ایمن‌ترشان، بهتر است مرجع‌ها را به اشاره‌گرها ترجیح دهید، مگر آنکه کارکردهای افزوده‌ای که اشاره‌گرها فراهم می‌کنند ضروری باشند.

دستورالعمل‌ها

خوش آمدید، مهندس! شما یکی از آخرین کهنه‌کاران بنیاد Speedywagon هستید، سازمانی سری که دهه‌ها با تهدیدهای باستانی مانند Pillar Men جنگیده است. در جریان این تلاش، سال‌ها صرف نگهداری سامانه‌های فناورانه‌ی بنیاد کرده‌اید؛ سامانه‌هایی که با ترکیبی از فناوری پیشرفته و کتابخانه‌های قدیمی ساخته شده‌اند.

اما در زمان‌های اخیر، حسگرهایی که فعالیت‌های Pillar Men را ردیابی می‌کنند از کار افتاده‌اند. سامانه‌های بنیاد قدیمی‌اند و این کد با یک کتابخانه‌ی C++ قدیمی در تعامل است که نمی‌توان آن را به‌روزرسانی کرد. وظیفه‌ی شما پیاده‌سازی چهار تابع اصلی است که فعالیت حسگرهای Pillar Men را با استفاده از کتابخانه‌ای قدیمی مبتنی بر «اشاره‌گر» پایش می‌کنند.

به عنوان یک مهندس C++ مدرن، ترجیح می‌دهید از اشاره‌گرهای هوشمند استفاده کنید، اما افسوس که کد قدیمی احترام به روش‌های قدیمی را می‌طلبد. سرنوشت بشریت ممکن است به این اشاره‌گرها بستگی داشته باشد، پس با احتیاط پیش بروید و امید که انرژی Hamon راهنمای شما باشد.

Note

از آنجا که خوانش‌های حسگر می‌توانند بسیار بزرگ باشند، یک struct نمونه در اختیار شما قرار می‌دهیم که در کتابخانه‌ی واقعی استفاده می‌شود. کد آن پیشاپیش در فایل هدر برای شما پیاده‌سازی شده است.

struct pillar_men_sensor {
    int activity{};
    std::string location{};
    std::vector<int> data{};
};

1. بررسی اتصال حسگر (connection_check)

وظیفه‌ی اول شما این است که مطمئن شوید حسگر Pillar Men به‌درستی متصل است. نمی‌توانیم بگذاریم حسگرهای قطع‌شده هشدارهای کاذب ایجاد کنند. تابعی به نام connection_check خواهید نوشت که بررسی می‌کند آیا اشاره‌گر حسگر معتبر است یا نه، با بررسی nullptr.

  • تابعی تعریف کنید که یک اشاره‌گر به struct pillar_men_sensor را می‌پذیرد.
  • تابع باید اگر اشاره‌گر حسگر null نباشد true و در غیر این صورت false برگرداند.
pillar_men_sensor* sensor{nullptr};
bool isConnected = connection_check(sensor);
// isConnected => false

2. شمارش فعالیت حسگرها (activity_counter)

Pillar Men در سایه‌ها کمین کرده‌اند و باید بدانیم آیا حسگرها فعالیتی شناسایی کرده‌اند یا نه. تابع activity_counter را خواهید نوشت که یک آرایه از حسگرها و یک ظرفیت را می‌گیرد که تعداد حسگرهای آرایه را نشان می‌دهد.

  • تابعی تعریف کنید که یک اشاره‌گر به اولین عنصر یک آرایه و ظرفیت آن آرایه را می‌پذیرد.
  • با حساب اشاره‌گر روی آرایه‌ی حسگرها حلقه بزنید و خوانش‌های فعالیت را جمع کنید.
  • فعالیت جمع‌شده را برگردانید.
pillar_men_sensor sensor_array[3] = {{0}, {101}, {22}};
int totalActivity = activity_counter(sensor_array, 3);
// totalActivity => 123

3. کنترل هشدار (alarm_control)

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

  • تابعی تعریف کنید که اشاره‌گری به یک pillar_men_sensor را می‌پذیرد.
  • تابع ابتدا باید حسگر را از نظر nullptr بررسی کند. اگر حسگر nullptr باشد، false برگردانید.
  • اگر حسگر معتبر باشد و فعالیتش بزرگ‌تر از ۰ باشد، true و در غیر این صورت false برگردانید.
pillar_men_sensor db{9008, "songokunoie", {7, 7, 7}};
bool alarm = alarm_control(&db);
// alarm => true

4. بررسی ناهنجاری‌ها در داده‌ها با تابع uv_alarm

در این وظیفه، تابع uv_alarm را پیاده‌سازی می‌کنید تا مشخص کنید آیا بر اساس سطح قرارگیری در معرض نور UV و فعالیت حسگر باید هشداری ایجاد شود یا نه. تابع uv_alarm باید از تابع uv_light_heuristic که در اختیارتان قرار گرفته استفاده کند؛ تابعی که روی یک بردار از داده‌ها کار می‌کند و بر اساس آستانه‌های مشخصی مقداری برمی‌گرداند. این نسخه‌ای نمونه از کد پیچیده‌ای است که در محیط عملیاتی اجرا خواهد شد؛ لطفاً رابط آن را تغییر ندهید.

تابع uv_alarm را در فضای‌نام speedywagon تعریف کنید. این تابع باید:

  • یک اشاره‌گر به struct pillar_men_sensor را به عنوان پارامتر بگیرد.
  • اگر اشاره‌گر حسگر null باشد، false برگرداند.
  • تابع uv_light_heuristic را فراخوانی کند و آدرس آرایه‌ی data حسگر را به آن بدهد.
  • اگر مقدار برگردانده‌شده توسط uv_light_heuristic بزرگ‌تر از سطح sensor->activity باشد true و در غیر این صورت false برگرداند.
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
C++ Exercism

آماده‌اید بنیاد اسپیدی‌واگن را شروع کنید؟

در Exercism ثبت‌نام کنید تا C++ را همراه با 19 مفهوم100 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.