- •История изменений
- •Благодарности
- •Основы
- •Привет, Мир!
- •Ввод-вывод
- •Целые числа
- •Символы и строки
- •String
- •Перевод строки в целое число
- •Перевод целого числа в строку
- •Случайные числа
- •Профилирование
- •Массивы и матрицы
- •Объявление, размещение и инициализация массивов
- •Ввод массива
- •Вывод массива
- •Valarray
- •Vector
- •Матрицы
- •Элементарные алгоритмы
- •Абсолютное значение целого числа
- •Минимум и максимум среди двух чисел
- •Минимум и максимум среди трёх чисел
- •Сортировка массива из трёх чисел
- •Циклический сдвиг массива из трёх элементов
- •Разложение целого числа на его цифры
- •Линейный поиск
- •Рекурсия
- •Более сложные алгоритмы
- •Бинарный поиск
- •Циклический сдвиг массива
- •Подводные камни
- •Диграфы и триграфы
Симоненко Евгений А. Олимпиадная подготовка по программированию |
5 |
БЛАГОДАРНОСТИ
На данный труд подтолкнули меня мои ученики, которых я готовил к олимпиадам. Однажды мне надоело объяснять и повторять одно и тоже разным участникам моего кружка спортивного программирования, и так в ноябре 2010 года появилась первая редакция этого руководства. В процессе совершенствования знаний и навыков моих учеников, они сами стали подсказывать мне и друг другу различные приёмы кодирования решений. Некоторые из них были внесены в это руководство. В числе этих учеников хочу назвать прежде всего Егора Литвиненко, Сергея Косивченко и Александра Гончарова.
ОСНОВЫ
В этом разделе рассматриваются вопросы, относящиеся к самым азам не только спортивного программирования, но и вообще программирования на языке C++, без знания которых не стоит даже приближаться к местам, где проходят соревнования. :)
ПРИВЕТ, МИР!
Как же без приветствия Миру?
Ниже представлен код программы “Hello, World!” в стиле C++ и C:
C++ |
C |
|
#include <iostream> |
#include <cstdio> |
|
using namespace std; |
int main(int argc, char* arv[]) { |
|
int main(int argc, char* argv[]) { |
puts("Hello, World!"); |
|
return 0; |
||
cout << "Hello, World!" << endl; |
||
return 0; |
} |
|
|
||
} |
|
|
|
|
В решениях олимпиадных задач список аргументов функции main() можно опускать.
(На самом деле пример “Hello, World!” это не дань традиции, а способ показать примерный вид программы на C++, написанной в двух разных стилях.)
ВВОД-ВЫВОД
Ввод-вывод на C++ можно осуществлять в объектно-ориентированном стиле и в процедурном стиле, унаследованном из C. Объектно-ориентированный стиль как-правило более удобен и требует набора меньшего количества букв более простой в использовании, но второй потребляет меньше процессорного времени при интенсивном вводе-выводе.
При программировании в стиле C++ средства ввода-вывода подключаются заголовком iostream, за ввод отвечает объект cin, за вывод – cout. А в стиле C средства ввода-вывода подключаются заголовком cstdio, За ввод-вывод отвечает большое количество функций, но универсальными являются две: функция scanf() для ввода и printf() для вывода. Первым аргументом этих функций является так называемая строка формата. Строка формата для целых чисел должна содержать %d или %i, для символов – %c и для C-строк – %s. Следует также знать и не забывать, что функция printf() не делает автоматического переноса на новую строку. Для осуществления переноса включайте в конец строки формата символ \n.
Для вывода строк можно использовать функцию puts(), специально для этого предназначенную. Причём puts() в отличие от printf() осуществляет после вывода данных переход на новую строку.
Ниже представлен примеры программ в объектно-ориентированном и процедурном стилях, читающие со стандартного ввода два целых числа и выводящие их сумму.
6 |
Симоненко Евгений А. Олимпиадная подготовка по программированию |
|
|
|
|
C++ |
|
C |
|
|
|
#include <iostream> |
|
#include <cstdio> |
using namespace std; |
|
int main(int argc, char* argv[]) { |
int main(int argc, char* argv[]) |
int a = 0, b = 0; |
|
scanf("%d", &a); |
||
{ |
|
scanf("%d", &b); |
int a = 0, b = 0; |
|
int c = a + b; |
cin >> a >> b; |
|
printf("%d\n", c); |
int c = a + b; |
|
return 0; |
cout << c << endl; |
|
|
return 0; |
|
} |
|
|
|
} |
|
|
|
|
|
Если нужно данные читать из файла и результат также выводить в файл, то вместо непосредственной работы с файлами, можно просто перенаправить на них стандартные потоки ввода (stdin) и вывода (stdout) с помощью функции C freopen(). В обоих примерах ниже из файла input.txt читаются два числа, а результат их сложения выводится в файл output.txt.
C++ |
C |
#include <iostream> |
#include <cstdio> |
using namespace std; |
int main(int argc, char* argv[]) { |
int main(int argc, char* argv[]) |
freopen("input.txt", "r", stdin); |
freopen("output.txt", "w", stdout); |
|
{ |
int a = 0, b = 0; |
freopen("input.txt", "r", stdin); |
|
freopen("output.txt", "w", stdout); |
scanf("%d", &a); |
int a = 0, b = 0; |
scanf("%d", &b); |
int c = a + b; |
|
cin >> a >> b; |
printf("%d\n", c); |
int c = a + b; |
return 0; |
cout << c << endl; |
|
return 0; |
} |
|
|
} |
|
|
|
Другие вопросы ввода-вывода рассматриваются в других разделах.
ЦЕЛЫЕ ЧИСЛА
В таблице ниже представлены все основные целочисленные типы C++. Так как в C++ размеры целочисленных типов относительны, то есть зависят от архитектуры, то здесь указаны размеры для современных 32 и 64 разрядных архитектур.
Обозначение |
Размер |
Комментарий |
|
|
|
char |
1 |
По совместительству символьный тип (для 8 разрядных кодировок). |
|
|
|
short int |
2 |
Можно использовать более короткое обозначение short. |
|
|
|
int |
4 |
«Родной» тип для 32 разрядных архитектур. |
long int |
4 |
Эквивалентен int. Можно использовать более короткое обозначение |
|
|
long. |
long long int |
8 |
«Родной» тип для 64 разрядных архитектур. На 32 разрядных |
|
|
эмулируется. Можно использовать более короткое обозначение |
|
|
long long. |
Все перечисленные типы являются знаковыми, то есть допускают как положительные числа, так и отрицательные. В некоторых случаях целесообразно использование беззнаковых целых переменных и констант. В таком нужно к обозначению типа добавлять слово unsigned. Например, unsigned int. Не следует применять unsigned для расширения диапазона. Беззнаковость применяется в
Симоненко Евгений А. Олимпиадная подготовка по программированию |
7 |
тех случаях, когда отрицательные числа являются недопустимыми. Например, размер объектов класса vector или длина строки являются такими случаями. Типовая ошибка: объявляется в операторе for индексная переменная с типом int, то есть знаковая, которая в нём сравнивается с результатом метода size() или length(), который является беззнаковым. В этом случае следовало бы объявить индексную переменную с типом size_t, который для этого как раз и предназначен, или воспользоваться синонимом типа для размера из соответствующего класса. (На 32 разрядных компьютерах тип size_t фактически является синонимом для unsigned int и его диапазон 0 .. 4 294 967 295.)
В таблице ниже представлены диапазоны основных целочисленных типов 32 и 64 разрядных компьютеров.
Тип |
|
Диапазон |
|
|
|
char |
–128 .. 127 |
|
unsigned char |
0 |
.. 255 |
|
|
|
short int |
–32 768 .. 32 767 |
|
|
|
|
unsigned short |
0 |
.. 65 535 |
|
|
|
int |
–2 147 483 648 .. 2 147 483 647 |
|
|
|
|
unsigned int |
0 |
.. 4 294 967 295 |
long int |
–2 147 483 648 .. 2 147 483 647 |
|
|
|
|
unsigned long int |
0 |
.. 4 294 967 295 |
|
|
|
long long int |
–9 223 372 036 854 775 808 .. 9 223 372 036 854 775 807 |
|
|
|
|
unsigned long long int |
0..18 446 744 073 709 551 615 |
|
|
|
|
Следующая программа позволит читателю проверить на своём компьютере диапазон значений основных целых типов:
#include <iostream> #include <limits>
using namespace std;
int main(int argc, char* argv[]) { cout << "char: "
<<(int)numeric_limits<char>::min() << ".."
<<(int)numeric_limits<char>::max() << endl;
cout << "unsigned char: "
<<(int)numeric_limits<unsigned char>::min() << ".."
<<(int)numeric_limits<unsigned char>::max() << endl; cout << "wchar_t: "
<<numeric_limits<wchar_t>::min() << ".."
<<numeric_limits<wchar_t>::max() << endl;
cout << "short: "
<<numeric_limits<short>::min() << ".."
<<numeric_limits<short>::max() << endl; cout << "unsigned short: "
<<numeric_limits<unsigned short>::min() << ".."
<<numeric_limits<unsigned short>::max() << endl;
cout << "int: "
<<numeric_limits<int>::min() << ".."
<<numeric_limits<int>::max() << endl; cout << "unsigned int: "
<<numeric_limits<unsigned int>::min() << ".."
<<numeric_limits<unsigned int>::max() << endl;
cout << "long: "
<<numeric_limits<long>::min() << ".."
<<numeric_limits<long>::max() << endl; cout << "unsigned long: "
<<numeric_limits<unsigned long>::min() << ".."
8 |
Симоненко Евгений А. Олимпиадная подготовка по программированию |
<<numeric_limits<unsigned long>::max() << endl; cout << "long long: "
<<numeric_limits<long long>::min() << ".."
<<numeric_limits<long long>::max() << endl;
cout << "unsigned long long: "
<<numeric_limits<unsigned long long>::min() << ".."
<<numeric_limits<unsigned long long>::max() << endl; cout << "__int64: "
<<numeric_limits<__int64>::min() << ".."
<<numeric_limits<__int64>::max() << endl;
cout << "size_t: "
<<numeric_limits<size_t>::min() << ".."
<<numeric_limits<size_t>::max() << endl;
return 0;
}
В этом коде используется шаблонный класс numeric_limits<> из <limits>.
Чаще всего подходящим типом целых чисел является int. Но часто встречает такая ситуация: исходные значения прекрасно помещаются в int, но в процессе вычислений результат выходит за пределы диапазона int. Это одна из типовых ошибок начинающих. Выходом является использование целого типа long long int, который является 64-разрядным.
Другая типовая ошибка возникает в ситуации, которую мы рассмотрим на простом примере:
int x = 1000000000, y = 1000000000; long long int z = x * y;
cout << z << endl;
Результат неожиданный: –1486618624. Оказывается, механизм приведения типов языка C++ таков, что типом (размером) результата является тип выражения. И действительно, типом выражения в нашем примере является int, так как оба множителя имеют тип int.
Проблема решается принудительным приведением типов переменных, составляющих выражение:
int x = 1000000000, y = 1000000000; long long int z = (long long int) x * y; cout << z << endl;
Теперь мы получаем ожидаемый результат: 1000000000000000000.
Несмотря на огромный диапазон значений типа long long, в решении некоторых задач придётся прибегнуть к реализации так называемой длинной арифметики. С этим связана другая типовая ошибка (которою иногда удаётся избежать), когда вместо реализации длинной арифметики используют вещественный тип long double, диапазона значений которого может оказаться недостаточным. Есть и другие побочные эффекты от такого приёма.
Некоторые спортсмены вместо long long используют __int64.
Для того, чтобы компиляторы правильно обрабатывали длинные целочисленные литералы со знаком нужно использовать суффикс LL, например, –2000000000LL. Использование вместо этого приведение типа (long long) не приводит к ожидаемому результату.
СИМВОЛЫ И СТРОКИ
В C++ базовым символьным типом является тип char. Данные этого типа занимают 1 байт и предназначены для хранения отдельных символов в 8-разрядной кодировке, обычно ASCII. Тип является знаковым и в числовом представлении его диапазон от –128 до 127. Константы этого типа, то есть символы, должны заключаться в одинарные кавычки. Символы упорядочены в соответствие с кодировкой и их можно сравнивать между собой.
Для чтения отдельных символов со стандартного ввода можно использовать функцию scanf() с форматом %c, но лучше – getchar():
Симоненко Евгений А. Олимпиадная подготовка по программированию |
9 |
char c; scanf("%c", &c); c = getchar();
Для вывода отдельных символов на стандартный вывод можно использовать функцию printf() с форматом %c, но лучше – putchar():
char c; printf("%c", c); putchar(c);
Строки в C++ могут быть двух видов. Первый вид – это так называемые C-строки, унаследованные из языка C. Второй вид строк – это объектно-ориентированные строки типа string.
C-строка представляет из себя массив символов типа char с дополнительным элементом в конце для хранения ограничителя строки, в качестве которого выбрано число 0 (оно же символ '\0'). Недостатком C-строк является то, что часто мы не знаем заранее длину строки (количество её символов) и место под неё нужно резервировать по максимуму, а это не всегда подходит.
Для ввода C-строк используются функции scanf() с форматом %s и gets(). Вторая должна работать быстрее, что может оказаться критически важным при чтении большого количества строк:
char s[5]; scanf("%s", &s); gets(s);
Для вывода C-строки можно использовать функцию printf() с форматом %s, но лучше – puts(), только нужно не забывать о том, что puts() после вывода строки делает переход на новую строку. Если это не то, что нам нужно, следует использовать printf().
char s[5]; printf("%s\n", s); printf(s); puts(s);
Включив заголовок cstring мы получаем доступ к функциям библиотеки C для C-строк:
Название |
Назначение |
Сигнатура |
Возвращаемое значение |
||||
|
|
|
|
||||
strcat() |
Конкатена- |
char* strcat(char* dst, const char* src); |
Указатель на строку-приёмник dst. |
||||
|
ция |
двух |
|
|
|
|
|
|
строк. |
|
|
|
|
|
|
strcmp() |
Сравнение |
int strcmp(const char* str1, const char* str2); |
Число 0, если строки посимвольно |
||||
|
двух строк. |
|
совпадают; число большее 0, если |
||||
|
|
|
|
в первой строке обнаружен символ |
|||
|
|
|
|
больший |
|
соответствующего |
|
|
|
|
|
символа второй; и меньшее 0 в |
|||
|
|
|
|
противном случае. |
|
||
strcpy() |
Копирова- |
char* strcpy(char* dst, const char* src); |
Указатель на строку-приёмник dst. |
||||
|
ние строки. |
|
|
|
|
|
|
strlen() |
Длина стро- |
size_t strlen(const char* str); |
Длина |
строки |
(количество |
||
|
ки (количе- |
|
символов) |
без учёта символа |
|||
|
ство |
симво- |
|
ограничителя (\0). |
|
||
|
лов). |
|
|
|
|
|
|
strstr() |
Поиск |
char* strstr(const char* str1, const char* str2); |
Указатель |
на |
первое |
вхождение |
|
|
подстроки. |
|
str2 в str1. Если str2 не найдена в |
||||
|
|
|
|
str1, то возвращается нулевой |
|||
|
|
|
|
указатель. |
|
|
|
strtok() |
Разбиение |
char* strtok(char* str, const char* delimiters); |
Указатель на подстроку из str, |
||||
|
строки. |
|
обрамлённую |
одним |
или более |
||
