Основы программирования на C++
Подготовка к работе
Установка среды разработки
- Visual Studio 2017 Community (для Windows 7 — Windows 10)
- Выберите только вариант «Разработка классических приложений на 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; }