پوشش جامع حوزههای آزمون
این بانک تستهای جامع به صورت سیستماتیک سازماندهی شده تا ساختار، سختگیری و عمق ارزیابیهای فنی حرفهای و مراحل استخدام در شرکتهای بزرگ را شبیهسازی کند:
ساختارهای داده و الگوریتمها (۲۵٪)
مباحث پوشش داده شده: دستکاری آرایهها، مزایای حافظه متوالی، لیستهای پیوندی یکطرفه و دوطرفه، پیادهسازی پشته و صف، درختهای باینری، درختهای متوازن و بهینهسازی پیچیدگی زمانی/فضایی.
برنامهنویسی شیگرا - OOP (۲۰٪)
مباحث پوشش داده شده: چیدمان کلاس، چرخه حیات نمونهسازی اشیاء، ارثبری تکگانه و چندگانه، چندریختی زمان اجرا از طریق جداول مجازی، چندریختی زمان کامپایل و استراتژیهای کپسولهسازی سختگیرانه.
مدیریت حافظه (۱۵٪)
مباحث پوشش داده شده: اشارهگرهای خام، مکانیسمهای ارجاع (Reference)، تخصیص حافظه Stack در مقابل Heap، ایمنی تخصیص حافظه دینامیک، RAII و اشارهگرهای هوشمند (std::unique_ptr, std::shared_ptr, std::weak_ptr).
تمپلیتها و جنریکها (۱۰٪)
مباحث پوشش داده شده: سینتکس تمپلیت، پارامترهای نوع، نمونهسازی صریح و جزئی تمپلیت، مبانی متاپروگرمینگ تمپلیت، SFINAE و ارزیابی زمان کامپایل.
الگوهای طراحی نرمافزار (۱۰٪)
مباحث پوشش داده شده: پیادهسازیهای خاص C++ برای الگوهای Singleton، Factory Method، Observer، Strategy و Decorator با تأکید بر Thread-safety و چرخه حیات اشیاء.
همروندی و موازیسازی (۱۰٪)
مباحث پوشش داده شده: مدیریت مدرن رشتهها (std::thread, std::jthread)، Mutexها، قفلهای مشترک، متغیرهای شرطی، عملیات Atomic، مفاهیم برنامهنویسی Lock-free و جلوگیری از رقابت دادهها.
مدیریت خطا و استثناها (۵٪)
مباحث پوشش داده شده: سربار بلوکهای try-catch، تضمینهای کد ایمن در برابر استثنا (basic, strong, noexcept)، انواع استثناهای سفارشی و الگوهای طراحی کد خطا.
کتابخانه استاندارد (STL) (۵٪)
مباحث پوشش داده شده: کانتینرهای متوالی و انجمنی، قوانین ابطال Iterator، بهینهسازی الگوریتمهای استاندارد، فانکتورهای سفارشی و تطبیقدهندههای کانتینر.
توضیحات دوره
موفقیت در مصاحبههای فنی برای محاسبات با کارایی بالا (HPC)، برنامهنویسی سیستم یا توسعه بازی، مستلزم تسلط بر مکانیسمهای سطح پایین زبان، معماری حافظه و استانداردهای مدرن طراحی C++ است. شرکتهایی که به دنبال مهندسان C++ هستند، تنها توانایی شما در نوشتن سینتکس را تست نمیکنند؛ بلکه درک شما از رفتارهای زمان اجرا، سربار حافظه و بهرهوری منابع را ارزیابی میکنند. من این منبع تستهای تمرینی را طراحی کردم تا شما را با سبک دقیق مسائل فنی، چالشهای معماری و سوالات بهینهسازی که توسط تیمهای مهندسی برتر در مراحل واقعی مصاحبه استفاده میشود، آشنا کنم.
با ۵۵۰ سوال دقیقاً طراحی شده، این دوره به عنوان یک پلتفرم آمادهسازی متمرکز برای نقشهای متوسط تا پیشرفته عمل میکند. مفاهیم حیاتی از جمله دستکاری اشارهگرها، مکانیسم داخلی جداول مجازی، پیادهسازیهای ایمن RAII و بدنیههای مدرن همروندی را پوشش میدهد. به جای ارائه تکههای کد پیشپاافتاده، این سوالات شما را در سناریوهای مهندسی واقعی قرار میدهند، جایی که انتخاب الگوی غلط، کانتینر نامناسب یا نوع ارجاع اشتباه منجر به نشت حافظه، رفتار تعریف نشده (Undefined Behavior) یا گلوگاههای عملکردی میشود.
هر سوال در این بانک دارای یک تحلیل معماری گسترده است. من واقعیت مهندسی پشت پاسخ صحیح را توضیح میدهم و تکتک گزینههای نادرست را تحلیل میکنم تا یاد بگیرید تلههای ظریف سینتکسی و ضدالگوهای ساختاری را شناسایی کنید. با تکمیل این ارزیابیهای جامع، شفافیت فنی لازم برای توضیح با اعتماد به نفس انتخابهای کدنویسی خود، تحلیل لحظهای عملکرد زمان اجرا و قبولی در مصاحبههای پیش رو در اولین تلاش را به دست خواهید آورد.
نمونه پیشنمایش سوالات تمرینی
سوال ۱: برنامهنویسی شیگرا و مدیریت حافظه
سناریویی را در نظر بگیرید که در آن یک اشارهگر از کلاس Base به یک شیء از کلاس Derived که به صورت دینامیک تخصیص یافته، اشاره میکند. تخریبکننده (Destructor) کلاس Base با کلمه کلیدی virtual تعریف نشده است. هنگامی که عملگر delete روی اشارهگر کلاس Base فراخوانی میشود، کدام یک از گزارههای زیر رفتار زمان اجرا و مدیریت منابع حاصل را به درستی توصیف میکند؟
الف) محیط زمان اجرا به طور خودکار نوع کلاس Derived را از طریق RTTI تشخیص داده و هر دو تخریبکننده Derived و Base را به درستی فراخوانی میکند.
دلیل نادرست بودن: اطلاعات نوع زمان اجرا (RTTI) توسط عملگر استاندارد delete برای اصلاح تخریبکنندههای مجازی گمشده استفاده نمیشود. بدون کلمه کلیدی virtual در تعریف کلاس Base، کامپایلر در هنگام کامپایل کاملاً به نوع استاتیک اشارهگر متکی است.
ب) کامپایلر یک خطای اجباری در زمان کامپایل صادر میکند و از بیلد شدن برنامه به دلیل تخریب چندریختی ناایمن جلوگیری میکند.
دلیل نادرست بودن: این یک سینتکس کاملاً قانونی در C++ است، بنابراین کامپایلر آن را رد نمیکند. این یک نقص منطقی و ساختاری است که منجر به رفتار تعریف نشده در زمان اجرا میشود، نه شکست در کامپایل.
ج) فقط تخریبکننده کلاس Derived اجرا میشود و زیر-شیء کلاس Base در حافظه Heap باقی میماند و باعث نشت جزئی حافظه میشود.
دلیل نادرست بودن: نوع استاتیک اشارهگر Base است. بنابراین، کامپایلر تخریبکننده کلاس Base را فراخوانی میکند، نه تخریبکننده کلاس Derived را.
د) این عملیات منجر به رفتار تعریف نشده (Undefined Behavior) میشود که معمولاً تنها تخریبکننده کلاس Base را اجرا کرده و هرگونه منبع منحصر به شیء کلاس Derived را نشت میدهد.
دلیل درست بودن: طبق استاندارد ISO C++، حذف یک شیء مشتق شده از طریق اشارهگر به کلاس پایه که فاقد تخریبکننده مجازی است، منجر به رفتار تعریف نشده میشود. در بیشتر پیادهسازیهای کامپایلر، این امر به صورت فراخوانی تنها تخریبکننده کلاس Base ظاهر میشود. اجزای کلاس Derived هرگز پاکسازی نمیشوند که منجر به نشت منابع و وضعیت فاسد برنامه میگردد.
ه) مکانیسمهای static cast در عملگر delete تضمین میکنند که تخصیصدهنده حافظه، کل بایتهای تخصیص یافته کلاس Derived را بدون اجرای هیچ تخریبکنندهای به صورت ایمن آزاد کند.
دلیل نادرست بودن: در حالی که ردیابی تخصیص Heap ممکن است در برخی مدیریتهای حافظه بایتهای خام را بازیابی کند، نادیده گرفتن تخریبکننده کلاس Derived به این معنی است که منابع غیر حافظهای (مانند دستگیرههای فایل باز، اتصالات دیتابیس یا اشارهگرهای هوشمند داخلی) برای همیشه نشت میکنند.
و) برنامه بلافاصله در زمان اجرا پیش از شروع اجرای هر تخریبکنندهای، با خطای Segmentation Fault مواجه میشود.
دلیل نادرست بودن: خطای Segmentation Fault هنگام دسترسی به مکانهای غیرقانونی حافظه رخ میدهد. فراخوانی یک تخریبکننده پایه غیرمجازی روی یک اشارهگر معتبر بلافاصله باعث کرش برنامه نمیشود؛ بلکه به طور بیصدا منابع را نشت داده و تخریبهای ناقصی را اجرا میکند.
سوال ۲: مدیریت حافظه و معماری اشارهگرهای هوشمند
شما در حال توسعه یک سیستم با کارایی بالا هستید که شامل دو جزء است که باید ارجاعاتی متقابل به یکدیگر داشته باشند. اگر یک رابطه چرخشی (Cyclic Relationship) را با استفاده از std::shared_ptr در هر دو کلاس تعریف کنید، اثر دقیق آن بر چرخه حیات حافظه برنامه چیست و طبق اصول RAII چگونه باید به درستی حل شود؟
الف) ارجاع چرخشی به دلیل بهروزرسانیهای بازگشتی شمارش ارجاع در هنگام ایجاد شیء، باعث سرریز پشته (Stack Overflow) فوری میشود.
دلیل نادرست بودن: اشیاء با موفقیت در Heap تخصیص مییابند و بهروزرسانیهای شمارش ارجاع در هنگام ایجاد، عملیات خطی هستند، بنابراین سرریز پشته رخ نمیدهد. مشکل تنها بعداً در مرحله تخریب ظاهر میشود.
ب) وابستگیهای چرخشی به طور ایمن توسط جمعآورنده زباله داخلی STL مدیریت میشوند که به طور خودکار حلقهها را در دورههای بیکاری زمان اجرا میشکند.
دلیل نادرست بودن: C++ دارای Garbage Collector زمان اجرا یا مکانیسم ردیابی برای تشخیص و شکستن چرخههای ارجاع ایزوله نیست. حافظه تا زمانی که پروسه کاملاً متوقف شود، مسدود باقی میماند.
ج) شمارش ارجاع هر دو شیء هرگز به صفر نمیرسد و باعث نشت دائمی حافظه میشود که باید با جایگزینی یکی از ارجاعات با std::weak_ptr حل شود.
دلیل درست بودن: وقتی دو شیء ارجاعات std::shared_ptr به یکدیگر دارند، شمارش ارجاع قوی آنها هرگز نمیتواند به زیر ۱ برسد، حتی اگر تمام اشارهگرهای خارجی به این چرخه تخریب شوند. این یک حلقه حذفناپذیر در حافظه ایجاد میکند. جایگزینی یک طرف رابطه با std::weak_ptr اجازه دسترسی غیرمالکانه بدون افزایش شمارش ارجاع قوی را میدهد و تخریب صحیح را هنگام خروج مالک اصلی از محدوده (Scope) ممکن میسازد.
د) جایگزینی یک ارجاع با اشارهگر خام (Raw Pointer)، تنها راه سازگار با استاندارد برای ردیابی دینامیک والد-فرزند بدون ایجاد رفتار تعریف نشده است.
دلیل نادرست بودن: در حالی که اشارهگر خام از حلقه ارجاع جلوگیری میکند، ریسکهای ایمنی قابل توجهی مانند اشارهگرهای معلق ایجاد میکند اگر شیء والد ابتدا تخریب شود. استفاده از std::weak_ptr روشی ایمن برای بررسی اعتبار شیء قبل از دسترسی با استفاده از متد .lock() فراهم میکند.
ه) استفاده از std::unique_ptr در هر دو طرف رابطه، چرخه را حل کرده و در عین حال ویژگیهای مدرن اشارهگر هوشمند را حفظ میکند.
دلیل نادرست بودن: دو شیء نمیتوانند ارجاعات متقابل std::unique_ptr به یکدیگر داشته باشند، زیرا std::unique_ptr مالکیت انحصاری را مدل میکند. کد به دلیل تکرار معناشناسی مالکیت منحصر به فرد، در کامپایل شکست میخورد.
و) اگر هر دو تعریف کلاس از std::enable_shared_from_this ارثبری کنند، حلقه ارجاع به طور خودکار حل میشود.
دلیل نادرست بودن: کلاس کمکی std::enable_shared_from_this به یک شیء اجازه میدهد تا به طور ایمن یک std::shared_ptr به خودش از طریق یک متد داخلی ایجاد کند. این کلاس مسائل مالکیت چرخشی بین دو شیء مستقل را تغییر داده یا حل نمیکند.
سوال ۳: همروندی، موازیسازی و همگامسازی رشتهها
یک توسعهدهنده نیاز دارد از یک منبع مشترک که توسط چندین رشته Worker به طور همزمان دسترسی دارند، محافظت کند. توسعهدهنده به جای استفاده از مکانیسمهای قفل مبتنی بر RAII، به صورت دستی mutex.lock() را در ابتدای بخش بحرانی و mutex.unlock() را در انتهای تابع فراخوانی میکند. اگر یک استثنای غیرمنتظره در زمان اجرا در میانه پردازش بخش بحرانی رخ دهد، چه اتفاقی برای وضعیت همگامسازی برنامه میافتد؟
الف) هسته سیستمعامل استثنا را رهگیری کرده، تمام قفلهای نگه داشته شده توسط رشته را آزاد میکند و پروسه را همگام نگه میدارد.
دلیل نادرست بودن: هسته سیستمعامل استثناهای سطح برنامه C++ را مدیریت نمیکند و باز کردن قفلهای Mutex برای قفلهای خام در فضای کاربر را خودکار نمیسازد.
ب) Mutex برای همیشه قفل میماند و باعث ایجاد بنبست (Deadlock) برای هر رشته بعدی میشود که سعی در تصاحب همان منبع دارد.
دلیل درست بودن: هنگامی که یک استثنا پرتاب میشود، جریان متوالی اجرای عادی متوقف شده و پشته شروع به بازگشت (Unwind) میکند. از آنجایی که فراخوانی دستی mutex.unlock() در انتهای تابع قرار دارد، در هنگام بازگشت پشته کاملاً نادیده گرفته میشود. Mutex برای همیشه قفل میماند و باعث بنبست میشود.
ج) موتور زمان اجرا به طور خودکار نزدیکترین دستور mutex.unlock() کشف شده در واحد ترجمه فعلی را از طریق stack walking اجرا میکند.
دلیل نادرست بودن: زمان اجرای C++ در هنگام بازگشت پشته تنها اشیائی را که مدت زمان ذخیرهسازی خودکار دارند (اشیاء پشته) پاکسازی میکند. این موتور دستورات دستوری تابع را ردیابی نکرده و کدهای دلخواه مانند فراخوانی صریح unlock را اجرا نمیکند.
د) برنامه استثنا را به یک کد خطای داخلی تبدیل کرده و رشته را مجبور میکند از مرز قفل متوالی بعدی ادامه دهد.
دلیل نادرست بودن: استثناها به طور خودکار به کدهای خطا تبدیل نمیشوند. استثنا به انتشار در بالای پشته فراخوانی ادامه میدهد و تمام منطق باقیمانده تابع را نادیده میگیرد.
ه) Mutex دچار فساد ساختاری شده و به وضعیتی تعریف نشده منتقل میشود که به رشتههای همزمان اجازه میدهد به طور همزمان وارد بخش بحرانی شوند.
دلیل نادرست بودن: وضعیت Mutex کاملاً معتبر باقی میماند، اما قفل شده میماند. Mutex خودش را فاسد نمیکند تا اجازه رقابت دادهها را بدهد؛ بلکه دسترسی را کاملاً مسدود میکند.
و) اگر Mutex قبل از مرحله کامپایل با wrapper نوع std::atomic تعریف شده باشد، وضعیت کاملاً ایمن است.
دلیل نادرست بودن: Mutexها نمیتوانند به عنوان انواع atomic تعریف شوند و بدنیههای atomic نیز آزاد کردن قفلها هنگام بازگشت پشته را خودکار نمیکنند. راه حل صحیح استفاده از Wrapperهای قفل RAII مانند std::lock_guard یا std::unique_lock است.
به تستهای سوالات مصاحبه خوش آمدید تا شما را برای ارزیابی سوالات مصاحبه C++ آماده کنیم.
شما میتوانید هر تعداد بار که بخواهید در آزمونها شرکت کنید.
این یک بانک سوالات اختصاصی و بسیار حجیم است.
در صورت داشتن سوال، از پشتیبانی مدرسان بهرهمند میشوید.
هر سوال دارای یک توضیح مفصل است.
سازگار با موبایل از طریق اپلیکیشن Udemy.
امیدوارم تا الان متقاعد شده باشید! و سوالات بسیار بیشتری در داخل دوره وجود دارد.
Interview Questions Tests
مربی در Udemy
نمایش نظرات