Особенности c++ и его IDE, или как не писать system("pause") и не получить TL

2.02.2020

У языка программирования C++, а также у популярных IDE для этого языка, есть ряд особенностей, которые надо бы знать, чтобы эффективно писать на нем программы. В этом посте я постараюсь описать основные такие особенности, в первую очередь относящиеся к написанию небольших программ типа олимпиадных; возможно, в дальнейшем буду постепенно добавлять сюда что-нибудь. Сначала опишу особенности IDE, потому что они полезны даже тем, кто только начинает писать на C++, а потом особенности самого языка, которые нужны уже чуть более продвинутым школьникам.

Особенности IDE

На олимпиадах наиболее популярны две IDE по C++: это Code::blocks (полностью свободно распространяемая и кроссплатформеная) и Microsoft Visual Studo (есть бесплатный вариант, только под Windows, не путайте с Visual Studio Code!). Еще последнее время набирает популярность CLion, но у него нет полностью бесплатной версии, и я его (применительно к олимпиадам) не очень хорошо знаю, поэтому упоминать его не буду.

Code::blocks

Это достаточно простая IDE без особых заморочек, очень напоминает простые IDE из других языков программирования, типа Wing IDE для питона и встроенной IDE для Pascal ABC. Все просто: создаете новый файл, пишете код, запускаете кнопкой с зеленой стрелочкой (точнее, кнопкой с шестеренкой и зеленой стрелочкой, потому что вам обычно надо скомпилировать, и только потом запускать код). После запуска и завершения программы code::blocks задерживает окошко программы на экране, чтобы вы смогли посмотреть, что вывела ваша программа, поэтому вам не надо писать в конце программы какой-либо код для приостановки программы (типа system("pause") или getch).

Особенностей тут две. Во-первых, когда вы сохраняете программу в первый раз, надо явно указать расширение файла .cpp (т.е. в диалоге сохранения файла написать не my_best_program, а my_best_program.cpp), иначе по умолчанию программа сохранится как .c и соответственно компилятор будет считать, что это программа на C, а не на C++.

Во-вторых, не так просто сделать, чтобы заработал дебаггер. Для этого, во-первых, надо, чтобы дебаггер был установлен на компьютере (code::blocks использует компилятор gcc и дебаггер gdb, надо, чтобы они были установлены), во-вторых, просто файлы, созданные по кнопке “new”, code::blocks не будет отлаживать. Чтобы дебаггер заработал, надо в code::blocks создать “проект”, и уже в “проект” добавить файл с исходным кодом (существующий или новый). Но это достаточно просто и прямолинейно.

MSVS

Заметно более продвинутая, профессиональная, IDE. В ней вы не можете просто так создать новый файл, в ней надо создавать “проект”. И тут есть две проблемы.

Во-первых, если вы просто так создадите проект, то студия не будет задерживать окошко программы на экране после завершения программы. Если вы хотите посмотреть, что ваша программа вывела на экран, вам надо будет добавлять в конец программы какую-нибудь задержку типа system("pause") или getch. А это очень плохо, в частности, не все тестирующие системы хорошо к этому относятся (еще бы — вашей программе уже пора заканчивать работу, а она никак не завершается).

Вторая проблема — по умолчанию студия создает проект с включенными так называемыми pre-compiled headers, и в начале кода появляется строчка #include "pch.h". Нам это не нужно, это только мешает, потому что код с такой строчкой не будет компилироваться в тестирующей системе, а без нее не будет компилироваться у вас локально в студии.

Обе проблемы решаются правильным созданием проекта. А именно, при создании проекта надо явно убедиться, что вы указали два параметра для проекта: во-первых, это должно быть console application (это повлияет на задержку программы, см. ниже), во-вторых, это должно быть empty project (а это повлияет на pch).

Как это сделать, зависит от версии Visual Studio. В старых версиях в окошке создания проекта надо было выбрать тип проекта а-ля Console application, и уже далее в следующем окошке поставить галочку Empty project. В последних версиях надо выбрать тип проекта “Windows Desktop Wizard”, по-русски “Мастер классических приложений Windows” (не “пустой проект”, не “консольное приложение”, а именно “wizard”/”мастер”, его всегда непросто найти), и в следующем окошке в выпадающем меню выбрать Console application и поставить галочку Empty project.

Так вы создадите полностью пустой проект, в котором даже не будет файла, в который надо писать код. После этого через меню Project — Add new items добавляете новый .cpp-файл (или там же рядом можно добавить уже существующий cpp-файл), и дальше все понятно. После этого студия будет делать задержку после запуска программы, при условии, что вы ее правильным образом запускаете (я не помню точно — надо запускать программу то ли в режиме отладки, то ли наоборот без нее, т.е. то ли F5, то ли Ctrl-F5; попробуйте по-разному и разберитесь), ну и pch не будет.

Общие особенности

(Этот раздел в основном скопирован из моего текста про областную олимпиаду.)

Быстрый ввод-вывод

Стандартный ввод/вывод через iostream (т.е. с использованием cin/cout) по умолчанию работает медленно на больших данных. Если вам надо ввести, допустим, 100000 чисел, то с использованием cin вы наверняка получите time limit; аналогично если вам надо выводить много данных. Это связано с двумя проблемами.

Во-первых, медленно работает endl (для тех, кто понимает — вывод в cout буферизуется, но endl каждый раз сбрасывает буфер, реально выводя данные на диск, а реальный доступ к диску работает медленно). Поэтому не используйте endl вообще, используйте '\n'.

Во-вторых, есть еще проблема синхронизации с stdio (не буду сейчас подробнее писать, что это значит). Чтобы эту проблему побороть, есть три способа:

  • Работать с файлами, а не с клавиатурой/экраном (когда это допустимо). У fstream таких проблем со скоростью работы нет.
  • Использовать для ввода/вывода конструкции из stdio.h (scanf и printf), а не из iostream.
  • Написать в самом начале программы две магические строчки (их надо выучить наизусть):
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(nullptr);
    

Лично я вам рекомендую использовать первый или последний вариант.

Еще раз: есть две проблемы: одна с endl, другая с синхронизацией stdio и iostream. Одна решается тем, что вы не используете endl, другая — вот одним из трех описанных выше способов. Вам надо решить обе проблемы, т.е. и не использовать endl, и как-то разобраться с синхронизацией. Типичная ошибка — написать в начале программы этот страшный код с sync_with_stdio, но выводить все равно через endl. Получите time limit exceeded все равно.

Установка стека

В популярных компиляторах C++ по умолчанию установлен очень маленький размер стека. Если в вашей программе глубокая рекурсия (например, если вы пишете поиск в глубину), то программа может упасть.

В GCC попросить большой размер стека из самой программы невозможно. Поэтому, чтобы ваш код работал на больших данных, надо сделать соответствующие настройки компиляции — в командную строку компилятора добавить параметр типа -Wl,stack,64000000. Если вы компилируете вручную через командную строку, то указывайте этот параметр. Если компилируете через Code::Blocks, то найдите размер стека в настройках компиляции. В тестирующей системе на нормальных олимпиадах размер стека настраивает жюри (добавляя параметр в командную строку) и указывает это в памятке в разделе со строками компиляции.

А вот в Visual Studio можно установить необходимый размер стека прямо из программы примерно следующей конструкцией: #pragma comment(linker, "/STACK:64000000").

Число 64000000 в обоих примерах выше — это необходимый вам размер стека в байтах (в примерах 64 миллиона байт, т.е. примерно 64 Мб). Размер стека можете посчитать в уме исходя из вашей программы (умножьте глубину рекурсии на размер памяти, требуемый на один уровень рекурсии — это примерно сколько памяти занимают все переменные на одном уровне, плюс 10-20 байт), а можете и подобрать опытным путем — 32-64 Мб обычно достаточно. Учитывайте еще, конечно, ограничение по памяти.

Поэтому если вы пишете на MSVS, то всегда явно устанавливайте размер стека. Если пишете на g++ в code::blocks, настройте в настройках code::blocks для локального запуска, и надейтесь, что в тестирующей системе жюри это нормально настроит (по-нормальному это должно быть видно в памятке участника). Если пишете на g++ и компилируете из командной строки вручную, то добавляйте нужный параметр и, опять-таки, надейтесь на жюри. Если вдруг жюри не настроило стек на g++, но при этом предоставляет возможность отправки под MSVS, то добавьте магическую MSVS-строчку и отправляйте под MSVS, даже если вы пишете под g++.

Стандарты языка

У языка c++ есть разные версии, называемые стандартами. Из распространенных сейчас это C++03, C++11 (давным-давно был известен как C++0x), C++14 и C++17. Они отличаются небольшими, но зачастую удобными вещами (например, auto и range-based loops типа for (auto x : v) появились только в C++11).

В g++ стандарт языка задается в командной строке: --std=c++14 и т.п.; соответственно, в code::blocks стандарт языка можно указать в настройках компиляции, и на олимпиаде жюри должно бы указывать стандарт языка в памятке (на нормальных олимпиадах даже предоставляют вам возможность выбрать стандарт при отправке программы). В MSVS стандарт языка зависит от версии самой студии, вы не можете его поменять.


Мой курс по алгоритмическому программированию (и подготовке к олимпиадам) для школьников, студентов и всех желающих — algoprog.ru