Основы программирования на C++

Материал из Олимпиадное программирование в УлГТУ
Перейти к навигации Перейти к поиску

Подготовка к работе

Установка среды разработки

Выберите только вариант «Разработка классических приложений на C++»
  • Visual Studio 2010 (для Windows XP, но также рекомендуется, если вы не хотите выкачивать большие установочные файлы)

Создание проекта в Visual Studio

Создаём проект:

Меню Файл (File) → Создать (New) → Проект... (Project...) → слева выбираем Visual C++ → справа выбираем Пустой проект (Empty Project) → вводим имя проекта → OK.

Добавляем в проект новый файл:

Меню Проект (Project) → Добавить новый элемент... (Add New Item...) → Файл C++ (C++ File) → вводим имя файла → Добавить (Add).

Решение проблем

  • Visual Studio не позволяет мне использовать scanf
Меню Проект (Project) → Свойства (Properties) → C/C++ → Проверки SDL (SDL Checks) → выбрать значение Нет (No) → OK.
Другой способ: скопировать в начало программы строку #define _CRT_SECURE_NO_WARNINGS
  • При запуске появляется окно Следующий проект устарел. Выполнить его сборку? (This project is out of date. Would you like to build it?)
Поставьте галочку Больше не выводить это окно (Do not show this dialog again) и нажмите кнопку Да (Yes).
  • Если программа содержит ошибки, при запуске появляется окно Возникли ошибки сборки. Продолжить и запустить последний успешно построенный вариант? (There were build errors. Would you like to continue and run the last successful build?)
Поставьте галочку Больше не выводить это окно (Do not show this dialog again) и нажмите кнопку Нет (No).
  • Где посмотреть, какие у меня ошибки?
Меню Вид (View) → Список ошибок (Error List). В более старых версиях: View → Other Windows → Error List.

Вспомогательные материалы к занятиям

Введение. Переменные. Ввод-вывод

Минимальная программа на C++

int main() {
    
}

В любой программе всегда должна быть функция main. Фигурные скобки обозначают начало и конец функции, все команды записываются внутри.

До функции main, в самом начале файла, обычно указываются директивы #include (например #include <stdio.h>).

Использование переменных

Чтобы добавить в программу переменную, укажите её тип, название и, если необходимо, начальное значение.

int radius = 100;                           //целое число radius со значением 100
double circleArea = 3.14 * radius * radius; //дробное число circleArea, значение вычисляется по формуле

Переменные могут менять своё значение:

radius = radius * 2; //радиус увеличился в 2 раза

Часто используемые типы данных:

int целые числа от -231 до 231-1 (примерно от -2·109 до 2·109) 4 байта
long long большие целые числа от -263 до 263-1 (примерно от -9·1018 до 9·1018) 8 байт
double дробные числа точность 15-16 десятичных цифр 8 байт

Вывод при помощи функции printf

Функция printf используется для печати на экране. Для использования printf нужно подключить stdio.h (#include <stdio.h>).

printf выводит только то, что указано в кавычках. Для вывода перевода строки используется специальный символ \n.

printf("Hello World!\n");     //выводит Hello World!
printf("My name\nis Vasya."); //выводит My name, а затем на новой строке is Vasya.

printf может выводить переменные и значения выражений, но для этого в строке с кавычками нужно указать места («окошечки»), куда следует подставлять значения. Места указываются при помощи спецификаторов.

int salary = 100;
printf("Your salary is %d $.\n", salary);               //выводит Your salary is 100 $.
printf("Next year you'll have %.2lf $.", salary * 1.5); //выводит Next year you'll have 150.00 $.

Часто используемые спецификаторы:

%d int
%lld long long
%lf double
%.Xlf double, X знаков после точки (например, %.2lf — 2 знака после точки)

printf может выводить несколько значений:

printf("P:%d S:%d", 2 * (3 + 4), 3 * 4); //выводит P:14 S:12

Ввод при помощи функции scanf

Функция scanf используется для ввода с клавиатуры. Для использования scanf нужно подключить stdio.h (#include <stdio.h>).

scanf похож на printf, но перед каждой переменной следует ставить амперсанд &:

int age;
scanf("%d", &age); //значение переменной age вводится с клавиатуры

scanf может вводить несколько значений:

int time;
double speed;
scanf("%d%lf", &time, &speed); //значения переменных time и speed вводятся с клавиатуры

Условия

Простые условия

Конструкция if позволяет выполнять в программе различные действия в зависимости от условий.

if (password == 12345) {        //если значение переменной password равняется 12345,
    printf("Welcome!");         //вывести Welcome!
} else {                        //в противном случае
    printf("Access denied!");   //вывести Access denied!
}

Если ветка else не используется, её можно не писать:

if (lives <= 0) {               //если количество жизней меньше или равно нулю,
    printf("Game over!");       //вывести Game over!
}                               //иначе ничего не делать

Можно скомбинировать несколько условий при помощи else if:

if (mark == 5) {
    printf("Excellent!");
} else if (mark == 4) {
    printf("Good!");
} else if (mark == 3) {
    printf("Satisfactory");
} else {
    printf("Bad");
}

Операции сравнения

< меньше
<= меньше или равно
> больше
>= больше или равно
== равно (не путать с =)
!= не равно

Условия со связками И, ИЛИ

Связка И (&&) позволяет проверить, что обе части условия одновременно являются истинными:

if (20 <= age && age <= 30)  //если возраст от 20 до 30 лет включительно

Связка ИЛИ (||) позволяет проверить, что хотя бы одна из частей условия является истинной:

if (age < 20 || 30 < age)    //если возраст меньше 20 или больше 30 лет

Поиск максимума или минимума

Чтобы найти наибольшее из значений нескольких переменных, следует сделать следующие шаги:

  • Создать новую переменную, в которой в итоге будет находиться значение максимума, и положить туда значение первой переменной;
  • Сравнить максимум со второй переменной, и если вторая переменная больше, положить в максимум значение второй переменной;
  • Сравнить максимум с третьей переменной, и если третья переменная больше, положить в максимум значение третьей переменной;
  • Повторить для оставшихся переменных.
int maxValue = a;
if (b > maxValue) {
    maxValue = b;
}
if (c > maxValue) {
    maxValue = c;
}
if (d > maxValue) {
    maxValue = d;
}

Минимум определяется аналогичным образом (знаки > следует изменить на <).

Циклы

Цикл while

Циклы — это конструкции языка C++, позволяющие несколько раз выполнять однотипные действия.

Общий вид цикла while:

while (<условие>) {
    <действия>
}

Перед началом цикла будет выполнена проверка, является ли <условие> истинным. Если это так, то выполняются действия, указанные внутри цикла (пока всё очень похоже на if). Однако, в отличие от if, при достижении закрывающей скобки цикла while мы перемещаемся обратно на его начало, где вновь будет проверено <условие>, и если оно всё ещё истинно, то снова выполняются действия внутри цикла, и так далее. Это продолжается до тех пор, пока <условие> не перестанет быть истинным (или пока не встретится оператор break; см. далее).

Цикл while удобно использовать тогда, когда заранее неизвестно, сколько именно повторов требуется сделать.

Примеры:

//В банке лежит n рублей, каждый месяц сумма увеличивается на 10%.
//Сколько месяцев потребуется, чтобы скопить миллион рублей?
int money = n, months = 0; //изначально денег в банке - n, прошло месяцев - 0
while (money < 1000000) {  //пока не накопится миллион или больше,
    money *= 1.1;          //сумма увеличивается на 10% (дробная часть отбрасывается)
    months++;              //количество месяцев увеличивается на 1
}                          //цикл закончится в тот момент, когда money будет больше или равно миллиону
printf("%d", months);      //выводим количество прошедших месяцев
//поиск минимального делителя (большего единицы) числа n
int divisor = 2;           //считаем, что минимальный делитель — это двойка
while (n % divisor != 0) { //пока n не делится на divisor,
    n++;                   //увеличиваем divisor
}                          //цикл закончится в тот момент, когда n будет делиться на divisor
printf("%d", divisor);     //выводим полученный делитель

Цикл for

Цикл for удобно использовать тогда, когда количество повторений известно заранее.

Пусть, например, нужно вывести числа от 0 до 9. Это можно сделать при помощи цикла while:

int i = 0;
while (i < 10) {
    printf("%d ", i);
    i++;
}

Однако гораздо проще в данном случае использовать цикл for:

for (int i = 0; i < 10; i++) {
    printf("%d ", i);
}

Запись цикла for всегда состоит из трёх частей, разделённых точками с запятой (именно точками с запятой, это важно!):

for (<инициализация>; <условие>; <изменение>) {
    <действия>
}
  • Как правило, внутри цикла for используется некоторая переменная (счётчик). Первая часть отвечает за создание счётчика и запись в него начального значения;
  • Вторая часть содержит условие, при истинности которого цикл продолжает работу (оно имеет точно такой же смысл, как условие в скобках у while). Как правило, здесь проверяется, что счётчик ещё не дошёл до некоторого финального значения;
  • Третья часть показывает, как должен изменяться счётчик от итерации к итерации.

Примеры:

for (int i = 5; i <= 15; i++) {
    printf("%d ", i);              //Вывод: 5 6 7 8 9 10 11 12 13 14 15
}
for (int i = 30; i >= 20; i--) {
    printf("%d ", i);              //Вывод: 30 29 28 27 26 25 24 23 22 21 20
}
for (int i = 0; i <= 40; i += 5) {
    printf("%d ", i);              //Вывод: 0 5 10 15 20 25 30 35 40
}
for (int i = 1; i < 100; i *= 3) {
    printf("%d ", i);              //Вывод: 1 3 9 27 81 243 729
}

Операторы break и continue

Оператор break досрочно завершает цикл while или for (если при выполнении программы мы встречаем break, мы сразу же перемещаемся за конец цикла):

for (int i = 0; i < 10; i++) { //цикл от 0 до 9
    if (i % 4 == 0) {          //если счётчик делится на 4,
        break;                 //прервать цикл
    }
    printf("%d ", i);          //вывести счётчик
}
//Вывод: 0 1 2 3

Оператор continue досрочно переходит к следующей итерации цикла while или for (если при выполнении программы мы встречаем continue, мы сразу же перемещаемся к началу цикла; если есть счётчик, то он будет изменён):

for (int i = 0; i < 10; i++) { //цикл от 0 до 9
    if (i % 4 == 0) {          //если счётчик делится на 4,
        continue;              //перейти к следующей итерации
    }
    printf("%d ", i);          //вывести счётчик
}
//Вывод: 0 1 2 3 5 6 7 9

Массивы

Объявление и использование массивов

Массив — несколько переменных одного типа, имеющих общее имя и различные индексы. Для обращения к ячейке массива требуется написать имя массива, а затем — индекс ячейки в квадратных скобках.

int x; //одна переменная типа int, 4 байта
int a[5]; //массив из 5 ячеек типа int, 20 байт

Ячейки массива имеют индексы от 0 до (SIZE - 1), где SIZE — размер массива.

int a[10]; //объявлены 10 ячеек: a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9]
int b[100]; //объявлены 100 ячеек: от a[0] до a[99]

С ячейками массива можно работать как с обычными переменными:

int a[5];
a[0] = 100;
a[1] = a[0] * 2;
printf("%d", a[1]); //вывод: 200

При создании массива можно сразу задать ячейкам начальные значения (как и в случае с отдельными переменными). Если этого не сделать, массив изначально будет содержать случайные значения (мусор).

int x;                      //x — отдельная переменная, содержит случайное значение (мусор)
int y = 100;                //y — отдельная переменная, содержит значение 100
int a[3];                   //a — массив из 3 ячеек, которые содержат случайные значения (мусор)
int b[3] = {100, 200, 300}; //b — массив из 3 ячеек, которые содержат значения 100, 200 и 300

Считывание и вывод массивов

Если определён массив из 5 ячеек, и значения ячеек вводятся с клавиатуры, можно использовать 5 операций scanf:

int a[5];
scanf("%d", &a[0]);
scanf("%d", &a[1]);
scanf("%d", &a[2]);
scanf("%d", &a[3]);
scanf("%d", &a[4]);

Но проще и правильнее в данном случае использовать цикл for:

int a[5];
for (int i = 0; i < 5; i++) {
    scanf("%d", &a[i]);
}

Часто в задачах размер массива неизвестен заранее, и его также нужно вводить с клавиатуры. В этом случае нужно объявить массив на такое количество ячеек, которого заведомо хватит во всех случаях (как правило, узнать это количество можно из ограничений в разделе «Входные данные»), а затем сделать отдельную переменную size и использовать её.

int size;                          //объявляем переменную для фактического размера массива
scanf("%d", &size);                //считываем размер массива

int a[100010];                     //объявляем массив такой величины, чтобы его хватило для всех тестов
for (int i = 0; i < size; i++) {   //считываем столько ячеек массива, сколько указано в size
    scanf("%d", &a[i]);
}

Аналогичным образом выполняется вывод ячеек массива на печать:

for (int i = 0; i < size; i++) {
    printf("%d ", a[i]);
}

Символы и строки

Символы в языке С

Язык С использует кодировочную таблицу ASCII, в которой различным знакам сопоставлены коды от 0 до 127. В языке C отсутствует отдельный тип для хранения символов, а маленькие целые числа (от 0 до 127) всегда могут интерпретироваться как символы и наоборот.

Для хранения кодов удобно использовать 8-битный целый тип char (может представлять целые числа от -128 до 127, чего как раз хватает для кодов символов).

char a = 33;       //a - это число 33, или, что то же самое, код символа '!'
printf("%d\n", a); //вывод: 33
printf("%c\n", a); //вывод: !

char b = '@';      //b - это код символа '@', или, что то же самое, число 64
printf("%d\n", b); //вывод: 64
printf("%c\n", b); //вывод: @

Запись, ввод и вывод символов

Если в программе нужно использовать символьную константу, она заключается в одинарные кавычки.

char x = '$'; //в переменную x записывается код знака доллара - 36

Для ввода одного символа используют scanf() со спецификатором %с.

Важно: спецификаторы %d и %lf при вводе игнорируют пробелы, переводы строки и т. п. Если в программе записано scanf("%d%d", &a, &b), и вводится «12 34», то a и b будут считаны правильно (a == 12, b == 34).

Однако %c может считывать пробелы и переводы строк. Если в программе записано scanf("%c%c", &a, &b), и вводится «X Y», то в b будет записан пробел (a == 'X', b == ' ').

Если этого нужно избежать, перед %c нужно ставить пробел. Это указание для scanf: пропустить все пробельные символы (если они есть) и прочитать первый непробельный.

char x;
scanf(" %c", &x);

Для вывода одного символа используется printf() со спецификатором %c. Если нужно вывести не символ, а его код, следует использовать спецификатор %d.

char x = '?'
printf("Code of %c is %d"); //выведет Code of ? is 63

Распространённые действия с символами

Чтобы проверить, является ли символ цифрой, можно использовать тот факт, что коды символов от '0' до '9' в таблице ASCII расположены последовательно. Следовательно, если символ — цифра, то его код должен находиться между '0' и '9'.

if ('0' <= x && x <= '9') //проверка, является ли x цифрой

Аналогично производятся проверки, является ли символ строчной (заглавной) латинской буквой.

if ('a' <= x && x <= 'z') //проверка, является ли x строчной буквой
if ('A' <= x && x <= 'Z') //проверка, является ли x заглавной буквой

Чтобы сделать из строчной буквы заглавную, нужно добавить к коду строчной буквы смещение до блока заглавных букв (то есть дистанцию от символа 'a' до символа 'A')

x += ('A' - 'a'); //преобразование строчной буквы в заглавную (x должен быть строчной буквой!)
x += ('a' - 'A'); //преобразование заглавной буквы в строчную (x должен быть заглавной буквой!)

Работу с символами облегчает библиотека <ctype.h>, которая содержит множество удобных функций:

  • isdigit(x) — проверка, является ли x цифрой ( if (isdigit(x)) )
  • islower(x) — проверка, является ли x строчной буквой
  • isupper(x) — проверка, является ли x заглавной буквой
  • isalpha(x) — проверка, является ли x буквой
  • tolower(x) — возвращает строчную букву, соответствующую x ( x = tolower(x); )
  • toupper(x) — возвращает заглавную букву, соответствующую x


Строки в языке C

Строки — это массивы символов.

char name[6] = "Vasya";

Для обозначения конца строки используется специальный символ — завершающий ноль ('\n', ASCII-код 0). Поэтому для хранения строки из N символов требуется массив из не менее чем (N + 1) ячеек (например, для строки "Vasya" нужно не менее 6 ячеек).

Если завершающий ноль будет утерян, строку не получится обработать стандартными функциями (например, print).

Запись, ввод и вывод строк

Строковые константы записываются в двойных кавычках.

char city[20] = "Ulyanovsk";

Для ввода строки до пробела используется scanf() со спецификатором %s.

char name[100];
scanf("%s", &name); //при вводе "Vasily Pupkin" в name будет записано "Vasily"

Для ввода строки целиком (до перевода строки) используется scanf() со спецификатором %[^\n].

char name[100];
scanf("%[^\n]", &name); //при вводе "Vasily Pupkin" в name будет записано "Vasily Pupkin"

Для вывода строк используется printf() со спецификатором %s.

char city[20] = "Ulyanovsk";
printf("Welcome to %s", city); //выведет Welcome to Ulyanovsk

Распространённые действия со строками

Для обхода всех символов строки используется цикл for, продолжающийся до тех пор, пока текущим символом не окажется завершающий ноль.

char s[20] = "Ulyanovsk";
for (int i = 0; s[i] != 0; i++) {
    printf("%c ", s[i]);           //выведет U l y a n o v s k
}

Полезные функции для работы со строками содержатся в заголовочном файле <string.h>. Наиболее часто используемые из них:

  • strlen(s) — возвращает длину строки s (без учёта завершающего нуля);
  • strcpy(s1, s2) — копирует строку s2 в s1 (аналог несуществующей операции s1 = s2);
  • strcat(s1, s2) — дописывает строку s2 к s1 (аналог несуществующей операции s1 += s2);
  • strcmp(s1, s2) — возвращает 0, если строки s1 и s2 равны, отрицательное число, если s1 лексикографически меньше s2, либо положительное число, если s2 лексикографически больше s2 (аналог несуществующих операций s1 == s2, s1 < s2, s1 > s2);
  • strchr(s, x) — возвращает указатель на первое вхождение символа x в строку s либо 0, если x не содержится в s. Чтобы получить индекс символа x в строке s, нужно записать int index = strchr(s, x) - s;
  • strstr(s1, s2) — возвращает указатель на первое вхождение строки s2 в строку s1 либо 0, если s2 не содержится в s1. Чтобы получить индекс строки s2 в строке s1, нужно записать int index = strstr(s1, s2x) - s1;

Строки в стиле C++

В языке C++ для хранения строк используется специальный тип string. Чтобы получить к нему доступ, нужно подключить заголовочный файл <string> (не путать со <string.h>!) и пространство имён std.

#include <string>
using namespace std;

int main() {
    string s = "Hello!";
}

Строки типа string нельзя вводить при помощи scanf. Для ввода следует использовать одну из двух возможностей:

#include <stdio.h>
#include <string>
using namespace std;

int main() {
    char buffer[100];
    scanf(" %s", &buffer);
    string s = buffer;
}
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s;
    cin >> s;
}

Для вывода строки типа string с помощью printf необходимо предварительно вызвать у строки метод .c_str(), возвращающий аналог строки в старом стиле.

string s = "Hello";
printf("%s", s.c_str());

Возможности строк типа string:

  • Индексация отдельных символов через квадратные скобки, как у массивов;
  • Получение размера строки вызовом метода .size();
  • Присваивание строк оператором =;
  • Конкатенация строк операторами + и +=;
  • Сравнение строк операторами ==, !=, <, >;
  • Поиск символов и подстрок в строке вызовом метода .find() (возвращает индекс вхождения либо -1, если вхождение не найдено);
  • Получение подстрок вызовом метода .substr().

Функции

Определение функций

Определение функции в языке C++ имеет следующий вид (количество параметров может быть иным):

типВозвращаемогоЗначения имяФункции(типПараметра1 имяПараметра1, типПараметра2 имяПараметра2) {
    
}

Для возврата значения из функции используется ключевое слово return. При достижении строки с return функция немедленно завершается, а в место её вызова подставляется значение, записанное справа от return.

Пример: функции, вычисляющие площадь различных геометрических фигур:

double rectangleArea(double width, double height) {
    return width * height;
}

double triangleArea(double a, double b, double c) {
    double p = (a + b + c) / 2;
    return sqrt(p * (p - a) * (p - b) * (p - c));
}

double circleArea(double radius) {
    double pi = acos(-1.0);
    return pi * radius * radius;
} 

Вызвать функцию может любая другая функция, расположенная ниже:

double segmentLength(double ax, double ay, double dx, double by) {
    double dx = ax - bx;
    double dy = ay - by;
    return sqrt(dx * dx + dy * dy);
}

double triangleArea(double ax, double ay, double dx, double by, double cx, double cy) {
    double ab = segmentLength(ax, ay, bx, by);
    double ac = segmentLength(ax, ay, cx, cy);
    double bc = segmentLength(bx, by, cx, cy);
    double p = (ab + ac + bc) / 2;
    return sqrt(p * (p - ab) * (p - ac) * (p - bc));
}

int main() {
    printf("%lf", triangleArea(0, 0, 10, 0, 0, 10));
}

Функция может не иметь возвращаемого значения. Для обозначения такой функции используется ключевое слово void.

//функция, n раз выводящая символ c
void repeatSymbol(char c, int n) {
    for (int i = 0; i < n; i++)
        printf("%c", c);
}

Передача параметров в функцию

По умолчанию используется передача параметров по значению. Её особенности:

  • Внутри функции создаются копии переданных переменных;
  • Изменения, произошедшие с параметрами внутри функции, не сохранятся вне функции.
void swap(int a, int b) {
    int t = a;
    a = b;
    b = t;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);
    printf("x=%d y=%d"); //будет выведено x=10 y=20
}

Старый способ сохранения сделанных внутри функции изменений — передача по указателю (вместо самих переменных передаются их адреса, см. функцию scanf).

Современный способ сохранения сделанных внутри функции изменений — передача по ссылке.

Добавление знака & перед именем параметра означает передачу по ссылке. Её особенности:

  • Внутри функции используются непосредственно сами переданные переменные;
  • Изменения, произошедшие с параметрами внутри функции, сохранятся вне функции;
  • По ссылке невозможно передать константы.
void swap(int &a, int &b) {
    int t = a;
    a = b;
    b = t;
}

int main() {
    int x = 10, y = 20;
    swap(x, y);
    printf("x=%d y=%d"); //будет выведено x=20 y=10
}

Массивы всегда передаются по ссылке. При передаче массива в функцию он теряет возможность узнать свой размер, поэтому часто вместе с массивом передаётся количество элементов в нём.

int sum(int a[], int size) {
    int res = 0;
    for (int i = 0; i < size; i++)
        res += a[i];
    return res;
}