• Добро пожаловать на компьютерный форум Tehnari.ru. Здесь разбираемся с проблемами ПК и ноутбуков: Windows, драйверы, «железо», сборка и апгрейд, софт и безопасность. Форум работает много лет, сейчас он переехал на новый движок, но старые темы и аккаунты мы постарались сохранить максимально аккуратно.

    Форум не связан с магазинами и сервисами – мы ничего не продаём и не даём «рекламу под видом совета». Отвечают обычные участники и модераторы, которые следят за порядком и качеством подсказок.

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

    Не знаете, с чего начать? Создайте тему с описанием проблемы – подскажем и при необходимости перенесём её в подходящий раздел.
    Задать вопрос Новые сообщения Как правильно спросить
    Если пришли по ссылке со старого Tehnari.ru – вы на нужном месте, просто продолжайте обсуждение.

Уроки по С++ для начинающих

  • Автор темы Автор темы Gruvi
  • Дата начала Дата начала

Нужна ли публикация уроков по C++ ?

  • Да

    Голосов: 29 93.5%
  • Нет

    Голосов: 2 6.5%

  • Всего проголосовало
    31

Gruvi

Ученик
Поддержка
Регистрация
10 Мар 2011
Сообщения
765
Реакции
33
Баллы
0
Уроки по С++ для начинающих

Первая программа на C++ — урок 1

В качестве инструмента разработки под Windows, я советую использовать Visual Studio 2008 или Dev C++ 5. Пользователям unix-подобных систем, потребуется компилятор gcc и любой текстовый редактор, например vim После того, как вы установили все необходимое, приступим к написанию первой программы.

Если вы пользуетесь MS Visual Studio, откройте меню «Файл → Создать → Проект». Перейдите на вкладку «Общие» и выберите «Пустой проект». Придумайте ему любое название, например lesson1 и нажмите OK. В окне обозревателя решений (обычно он находится в левом верхнем углу) щелкните правой кнопкой на папке файлы исходного кода, в диалоговом окне выберите пункт меню Добавить → Создать элемент. Введите название для нового файла — main.cpp и нажмите кнопку Добавить

Если вы используете gcc, создайте пустой файл и откройте его любимым текстовым редактором. Наберите следующий код:
#include <iostream>
using namespace std;

int main()
{
cout << "Hello, world!" << endl;
system("pause"); // Только для тех, у кого MS Visual Studio
return 0;
}

Директива #include используется для подключения других файлов в код. Строка #include <iostream>, будет заменена содержимым файла iostream.h, который находится в стандартной библиотеке языка и отвечает за ввод и вывод данных на экран.

Содержимое второй строки — using namespace std; указывает на то, что мы используем по умолчанию пространство имен, с названием std. Все то, что находится внутри фигурных скобок функции int main() {} будет автоматически выполняться после запуска программы.

Строка cout << "Hello, world!" << endl; говорит программе выводить сообщение с текстом «Hello, world» на экран.

Оператор cout предназначен для вывода текста на экран командной строки. После него ставятся две угловые кавычки (<<). Далее идет текст, который должен выводиться. Он помещается в двойные кавычки. Оператор endl переводит строку на уровень ниже.

Если в процессе выполнения произойдет какой-либо сбой, то будет сгенерирован код ошибки, отличный от нуля. Если же работа программы завершилась без сбоев, то код ошибки будет равен нулю. Команда return 0 необходима для того, чтобы передать операционной системе сообщение об удачном завершении программы.

В конце каждой команды ставится точка с запятой.

Теперь скомпилируйте и запустите программу. Тем, кто пользуется MS Visual Studio, нужно нажать сочетание клавиш Ctrl+F5. Пользователям gcc нужно выполнить следующую команду.

c++ имя_файла.cpp -o имя_выходного_бинарника

Если программа собралась с первого раза, то хорошо. Если компилятор говорит о наличии ошибок, значит вы что-то сделали неправильно. Прочитайте текст ошибки и попробуйте ее исправить своими силами. Если не получится, напишите о вашей проблеме в комментариях.
 
Переменные и типы данных в C++ — урок 2

Из школьного курса математики мы все знаем, что такое переменные. В программировании принципы довольно схожи. Переменная — это «ячейка» оперативной памяти компьютера, в которой может храниться какая-либо информация.

В программировании переменная, как и в математике может иметь название, состоящее из одной латинской буквы, но также может состоять из нескольких символов, целого слова или нескольких слов.

В языке С++ все переменные имеют определенный тип данных. Например, переменная, имеющая целочисленный тип не может содержать ничего кроме целых чисел, а переменная с плавающей точкой — только дробные числа.

Тип данных присваивается переменной при ее объявлении или инициализации. Ниже приведены основные типы данных языка C++, которые нам понадобятся.

int — целочисленный тип данных.
float — тип данных с плавающей запятой.
double — тип данных с плавающей запятой двойной точности.
char — символьный тип данных.
bool — логический тип данных.

Объявление переменной в C++ происходит таким образом: сначала указывается тип данных для этой переменной а затем название этой переменной.

int a; // объявление переменной a целого типа.
float b; // объявление переменной b типа данных с плавающей запятой.
double c = 14.2; // инициализация переменной типа double.
char d = 's'; // инициализация переменной типа char.
bool k = true; // инициализация логической переменной k.



Заметьте, что в C++ оператор присваивания (=) — не является знаком равенства и не может использоваться для сравнения значений. Оператор равенства записывается как «двойное равно» — ==.

Присваивание используется для сохранения определенного значение в переменной. Например, запись вида a = 10 задает переменной a значение числа 10.

Сейчас мы напишем простую программу-калькулятор, которая будет принимать от пользователя два целых числа, а затем определять их сумму:

#include <iostream>
using namespace std;

int main()
{
setlocale(0, "");
/*7*/ int a, b; // объявление двух переменных a и b целого типа данных.
cout << "Введите первое число: ";
cin >> a; // пользователь присваивает переменной a какое-либо значение.
cout << "Введите второе число: ";
cin >> b;
/*12*/ int c = a + b; // новой переменной c присваиваем значение суммы введенных пользователем данных.
cout << "Сумма чисел = " << c << endl; // вывод ответа.
return 0;
}

В 7-й строке кода программы мы объявляем переменные «a» и «b» целого типа int. В следующей строке кода выводится сообщение пользователю, чтобы он ввел с клавиатуры первое число.

В 9-й строке стоит еще незнакомый вам оператор — cin. Этот оператор просит пользователя ввести значение переменной «c» с клавиатуры. Аналогичным образом задается значение переменной «b».

В 12-й строке мы производим инициализацию переменной «c» суммой переменных «a» и «b». Далее находится уже знакомый вам оператор cout, который выводит на экран строку и значение переменной «c».

При выводе переменных, они не заключаются в кавычки, в отличие от строк.
 
Алгоритм ветвления в C++ — урок 3

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

К примеру, мы вводим с клавиатуры целое число. Если это число больше десяти, то программа должна выполнить одно действие, иначе — другое. Реализуем этот алгоритм на C++ с помощью конструкции ветвления.

Оператор if
Пример конструкции ветвления

#include <iostream>
using namespace std;

int main()
{
setlocale(0, "");
double num;

cout << "Введите произвольное число: ";
cin >> num;

if (num < 10) { // Если введенное число меньше 10.
cout << "Это число меньше 10." << endl;
} else { // иначе
cout << "Это число больше либо равно 10." << endl;
}
return 0;
}

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

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

Оператор if

Оператор if служит для того, чтобы выполнить какую-либо операцию в том случае, когда условие является верным. Условная конструкция в С++ всегда записывается в круглых скобках после оператора if.

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

Пример конструкции ветвления

if (num < 10) { // Если введенное число меньше 10.
cout << "Это число меньше 10." << endl;
} else { // иначе
cout << "Это число больше либо равно 10." << endl;
}

Здесь говорится: «Если переменная num меньше 10 — вывести соответствующее сообщение. Иначе, вывести другое сообщение».

Усовершенствуем программу так, чтобы она выводила сообщение, о том, что переменная num равна десяти:

if (num < 10) { // Если введенное число меньше 10.
cout << "Это число меньше 10." << endl;
} else if (num == 10) {
cout << "Это число равно 10." << endl;
} else { // иначе
cout << "Это число больше 10." << endl;
}

Здесь мы проверяем три условия:

Первое — когда введенное число меньше 10-ти
Второе — когда число равно 10-ти
И третье — когда число больше десяти

Заметьте, что во втором условии, при проверке равенства, мы используем оператор равенства — ==, а не оператор присваивания, потому что мы не изменяем значение переменной при проверке, а сравниваем ее текущее значение с числом 10.

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

Каждому оператору if соответствует только один оператор else. Совокупность этих операторов — else if означает, что если не выполнилось предыдущее условие, то проверить данное. Если ни одно из условий не верно, то выполняется тело оператора else.

Если после оператора if, else или их связки else if должна выполняться только одна команда, то фигурные скобки можно не ставить. Предыдущую программу можно записать следующим образом:

#include <iostream>
using namespace std;

int main()
{
setlocale(0, "");
double num;

cout << "Введите произвольное число: ";
cin >> num;

if (num < 10) // Если введенное число меньше 10.
cout << "Это число меньше 10." << endl;
else if (num == 10)
cout << "Это число равно 10." << endl;
else // иначе
cout << "Это число больше 10." << endl;

return 0;
}

Такой метод записи выглядит более компактно. Если при выполнении условия нам требуется выполнить более одной команды, то фигурные скобки необходимы. Например:

#include <iostream>
using namespace std;

int main()
{
setlocale(0, "");
double num;
int k;

cout << "Введите произвольное число: ";
cin >> num;

if (num < 10) { // Если введенное число меньше 10.
cout << "Это число меньше 10." << endl;
k = 1;
} else if (num == 10) {
cout << "Это число равно 10." << endl;
k = 2;
} else { // иначе
cout << "Это число больше 10." << endl;
k = 3;
}

cout << "k = " << k << endl;
return 0;
}

Данная программа проверяет значение переменной num. Если она меньше 10, то присваивает переменной k значение единицы. Если переменная num равна десяти, то присваивает переменной k значение двойки. В противном случае — значение тройки. После выполнения ветвления, значение переменной k выводится на экран.
 
Спасибо, за исправление.
 
Циклы в С++ — урок 4

Иногда необходимо повторять одно и то же действие несколько раз подряд. Для этого используют циклы. В этом уроке мы научимся программировать циклы на C++, после чего посчитаем сумму всех чисел от 1 до 1000.

Цикл for
Описание синтаксиса
Пример кода
Цикл while
Цикл do while

Цикл for

Если мы знаем точное количество действий (итераций) цикла, то можем использовать цикл for. Синтаксис его выглядит примерно так:
for (действие до начала цикла;
условие продолжения цикла;
действия в конце каждой итерации цикла) {
инструкция цикла;
инструкция цикла 2;
инструкция цикла N;
}
Итерацией цикла называется один проход этого цикла

Существует частный случай этой записи, который мы сегодня и разберем:

for (счетчик = значение; счетчик < значение; шаг цикла) {
тело цикла;
}

Счетчик цикла — это переменная, в которой хранится количество проходов данного цикла.

Описание синтаксиса

Сначала присваивается первоначальное значение счетчику, после чего ставится точка с запятой.

Зачем задается конечное конечное значение счетчика цикла. После того, как значение счетчика достигнет указанного предела, цикл завершится. Снова ставим точку с запятой.

Задаем шаг цикла. Шаг цикла — это значение, на которое будет увеличиваться или уменьшаться счетчик цикла при каждом проходе.

Пример кода

Напишем программу, которая будет считать сумму всех чисел от 1 до 1000.

#include <iostream>
using namespace std;

int main()
{
int i; // счетчик цикла
int sum = 0; // сумма чисел от 1 до 1000.
setlocale(0, "");
for (i = 1; i <= 1000; i++) // задаем начальное значение 1, конечное 1000 и задаем шаг цикла - 1.
{
sum = sum + i;
}
cout << "Сумма чисел от 1 до 1000 = " << sum << endl;
return 0;
}

Если мы скомпилируем этот код и запустим программу, то она покажет нам ответ: 500500. Это и есть сумма всех целых чисел от 1 до 1000. Если считать это вручную, понадобится очень много времени и сил. Цикл выполнил всю рутинную работу за нас.

Заметьте, что конечное значение счетчика я задал нестрогим неравенством ( <= — меньше либо равно), поскольку, если бы я поставил знак меньше, то цикл произвел бы 999 итераций, т.е. на одну меньше, чем требуется. Это довольно важный момент, т.к. здесь новички часто допускают ошибки, особенно при работе с массивами (о них будет рассказано в следующем уроке). Значение шага цикла я задал равное единице. i++ — это тоже самое, что и i = i + 1.

В теле цикла, при каждом проходе программа увеличивает значение переменной sum на единицу. Еще один очень важный момент — в начале программы я присвоил переменной sum значение нуля. Если бы я этого не сделал, программа вылетела вы в сегфолт. При объявлении переменной без ее инициализации что эта переменная будет хранить «мусор».

Естественно к мусору мы ничего прибавить не можем. Некоторые компиляторы, такие как gcc, инициализирует переменную нулем при ее объявлении.
Цикл while

Когда мы не знаем, сколько итераций должен произвести цикл, нам понадобится цикл while или do...while. Синтаксис цикла while в C++ выглядит следующим образом.

while (Условие) {
Тело цикла;
}

Данный цикл будет выполняться, пока условие, указанное в круглых скобках является истиной. Решим ту же задачу с помощью цикла while. Хотя здесь мы точно знаем, сколько итераций должен выполнить цикл, очень часто бывают ситуации, когда это значение неизвестно.

Ниже приведен исходный код программы, считающей сумму всех целых чисел от 1 до 1000.

#include <iostream>
using namespace std;

int main()
{
setlocale(0, "");
int i = 0; // инициализируем счетчик цикла.
int sum = 0; // инициализируем счетчик суммы.
while (i < 1000)
{
i++;
sum += i;
}
cout << "Сумма чисел от 1 до 1000 = " << sum << endl;
return 0;
}

После компиляции программа выдаст результат, аналогичный результату работы предыдущей программы. Но поясним несколько важных моментов. Я задал строгое неравенство в условии цикла и инициализировал счетчик i нулем, так как в цикле while происходит на одну итерацию больше, потому он будет выполняться, до тех пор, пока значение счетчика перестает удовлетворять условию, но данная итерация все равно выполнится. Если бы мы поставили нестрогое неравенство, то цикл бы закончился, когда переменная i стала бы равна 1001 и выполнилось бы на одну итерацию больше.

Теперь давайте рассмотрим по порядку исходный код нашей программы. Сначала мы инициализируем счетчик цикла и переменную, хранящую сумму чисел.

В данном случае мы обязательно должны присвоить счетчику цикла какое-либо значение, т.к. в предыдущей программе мы это значение присваивали внутри цикла for, здесь же, если мы не инициализируем счетчик цикла, то в него попадет «мусор» и компилятор в лучшем случае выдаст нам ошибку, а в худшем, если программа соберется — сегфолт практически неизбежен.

Затем мы описываем условие цикла — «пока переменная i меньше 1000 — выполняй цикл». При каждой итерации цикла значение переменной-счетчика i увеличивается на единицу внутри цикла.

Когда выполнится 1000 итераций цикла, счетчик станет равным 999 и следующая итерация уже не выполнится, поскольку 1000 не меньше 1000. Выражение sum += i является укороченной записью sum = sum + i.

После окончания выполнения цикла, выводим сообщение с ответом.

Цикл do while

Цикл do while очень похож на цикл while. Единственное их различие в том, что при выполнении цикла do while один проход цикла будет выполнен независимо от условия. Решение задачи на поиск суммы чисел от 1 до 1000, с применением цикла do while.

#include <iostream>
using namespace std;

int main ()
{
setlocale(0, "");
int i = 0; // инициализируем счетчик цикла.
int sum = 0; // инициализируем счетчик суммы.
do {// выполняем цикл.
i++;
sum += i;
} while (i < 1000); // пока выполняется условие.
cout << "Сумма чисел от 1 до 1000 = " << sum << endl;
return 0;
}
Принципиального отличия нет, но если присвоить переменной i значение, большее, чем 1000, то цикл все равно выполнит хотя бы один проход.
 
Массивы в C++ — урок 5

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

Возьмем группу студентов из десяти человек. У каждого из них есть фамилия. Создавать отдельную переменную для каждого студента — не рационально. Создадим массив, в котором будут храниться фамилии всех студентов.

Пример инициализации массива
Описание синтаксиса
Вывод элементов массива через цикл
Объявление массива без инициализации
Заполнение массива с клавиатуры

Пример инициализации массива

string students[10] = {
"Иванов", "Петров", "Сидоров",
"Ахмедов", "Ерошкин", "Выхин",
"Андеев", "Вин Дизель", "Картошкин", "Чубайс"
};

Описание синтаксиса

Массив создается почти так же, как и обычная переменная. Для хранения десяти фамилий нам нужен массив, состоящий из 10 элементов. Количество элементов массива задается при его объявлении и заключается в квадратные скобки.

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

Попробуем вывести наш массив на экран с помощью оператора cout.

#include <iostream>
#include <string>

int main()
{
std::string students[10] = {
"Иванов", "Петров", "Сидоров",
"Ахмедов", "Ерошкин", "Выхин",
"Андеев", "Вин Дизель", "Картошкин", "Чубайс"
};
std::cout << students << std::endl; // Пытаемся вывести весь массив непосредственно
return 0;
}
Скомпилируйте этот код и посмотрите, на результат работы программы. Готово? А теперь запустите программу еще раз и сравните с предыдущим результатом. В моей операционной системе вывод был следующим:

Первый вывод: 0x7ffff8b85820
Второй вывод: 0x7fff7a335f90
Третий вывод: 0x7ffff847eb40

Мы видим, что выводится адрес этого массива в оперативной памяти, а никакие не «Иванов» и «Петров».

Дело в том, что при создании переменной, ей выделяется определенное место в памяти. Если мы объявляем переменную типа int, то на машинном уровне она описывается двумя параметрами — ее адресом и размером хранимых данных.

Массивы в памяти хранятся таким же образом. Массив типа int из 10 элементов описывается с помощью адреса его первого элемента и количества байт, которое может вместить этот массив. Если для хранения одного целого числа выделяется 4 байта, то для массива из десяти целых чисел будет выделено 40 байт.

Так почему же, при повторном запуске программы, адреса различаются? Это сделано для защиты от атак переполнения буфера. Такая технология называется рандомизацией адресного пространства и реализована в большинстве популярных ОС.

Попробуем вывести первый элемент массива — фамилию студента Иванова.
#include <iostream>
#include <string>

int main()
{
std::string students[10] = {
"Иванов", "Петров", "Сидоров",
"Ахмедов", "Ерошкин", "Выхин",
"Андеев", "Вин Дизель", "Картошкин", "Чубайс"
};
std::cout << students[0] << std::endl;
return 0;
}

Смотрим, компилируем, запускаем. Убедились, что вывелся именно «Иванов». Заметьте, что нумерация элементов массива в C++ начинается с нуля. Следовательно, фамилия первого студента находится в students[0], а фамилия последнего — в students[9].

В большинстве языков программирования нумерация элементов массива также начинается с нуля.

Попробуем вывести список всех студентов. Но сначала подумаем, а что если бы вместо группы из десяти студентов, была бы кафедра их ста, факультет из тысячи, или даже весь университет? Ну не будем же мы писать десятки тысяч строк с cout?

Конечно же нет! Мы возьмем на вооружение циклы, о которых был написан предыдущий урок.
Вывод элементов массива через цикл

#include <iostream>
#include <string>

int main()
{
std::string students[10] = {
"Иванов", "Петров", "Сидоров",
"Ахмедов", "Ерошкин", "Выхин",
"Андеев", "Вин Дизель", "Картошкин", "Чубайс"
};
for (int i = 0; i < 10; i++) {
std::cout << students << std::endl;
}

return 0;
}

Если бы нам пришлось выводить массив из нескольких тысяч фамилий, то мы бы просто увеличили конечное значение счетчика цикла — строку for (...; i < 10; ...) заменили на for (...; i < 10000; ...).

Заметьте что счетчик нашего цикла начинается с нуля, а заканчивается девяткой. Если вместо оператора строгого неравенства — i < 10 использовать оператор «меньше, либо равно» — i <= 10, то на последней итерации программа обратится к несуществующему элементу массива — students[10]. Это может привести к ошибкам сегментации и аварийному завершению программы. Будьте внимательны — подобные ошибки бывает сложно отловить.

Массив, как и любую переменную можно не заполнять значениями при объявлении.
Объявление массива без инициализации

string students[10];
// или
string teachers[5];

Элементы такого массива обычно содержат в себе «мусор» из выделенной, но еще не инициализированной, памяти. Некоторые компиляторы, такие как GCC, заполняют все элементы массива нулями при его создании.

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

int n;
cin >> n;
string students[n]; /* Неверно */

Выделение памяти в процессе выполнения возможно при работе с динамическими массивами. Но о них немного позже.

Заполним с клавиатуры пустой массив из 10 элементов.
Заполнение массива с клавиатуры

#include <iostream>
#include <string>

using std::cout;
using std::cin;
using std::endl;

int main()
{
int arr[10];

// Заполняем массив с клавиатуры
for (int i = 0; i < 10; i++) {
cout << "[" << i + 1 << "]" << ": ";
cin >> arr;
}

// И выводим заполненный массив.
cout << "\nВаш массив: ";

for (int i = 0; i < 10; ++i) {
cout << arr << " ";
}

cout << endl;

return 0;
}


Скомпилируем эту программу и проверим ее работу.
 
Функции в C++ — урок 6

Сегодня мы поговорим о функциях в C++. Очень часто в программировании необходимо выполнять одни и те же действия. Например, мы хотим выводить пользователю сообщения об ошибке в разных местах программы, если он ввел неверное значение. без функций это выглядело бы так:

#include <iostream>
#include <string>

using namespace std;

int main()
{
string valid_pass = "qwerty123";
string user_pass;
cout << "Введите пароль: ";
getline(cin, user_pass);
if (user_pass == valid_pass) {
cout << "Доступ разрешен." << endl;
} else {
cout << "Неверный пароль!" << endl;
}
return 0;
}

А вот аналогичный пример с функцией:

#include <iostream>
#include <string>

using namespace std;

void check_pass (string password)
{
string valid_pass = "qwerty123";
if (password == valid_pass) {
cout << "Доступ разрешен." << endl;
} else {
cout << "Неверный пароль!" << endl;
}
}

int main()
{
string user_pass;
cout << "Введите пароль: ";
getline (cin, user_pass);
check_pass (user_pass);
return 0;
}

По сути, после компиляции не будет никакой разницы для процессора, как для первого кода, так и для второго. Но ведь такую проверку пароля мы может делать в нашей программе довольно много раз. И тогда получается копипаста и код становится нечитаемым. Функции — одна из самых важных компонентов языка C++.

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

Если функция не возвращает никакого значения, то она должна иметь тип void (такие функции иногда называют процедурами)

При объявлении функции, после ее типа должно находиться имя функции и две круглые скобки — открывающая и закрывающая, внутри которых могут находиться один или несколько аргументов функции, которых также может не быть вообще.
после списка аргументов функции ставится открывающая фигурная скобка, после которой находится само тело функции.
В конце тела функции обязательно ставится закрывающая фигурная скобка.

Пример построения функции

#include <iostream>
using namespace std;

void function_name ()
{
std::cout << "Hello, world" << std::endl;
}

int main()
{
function_name(); // Вызов функции
return 0;
}
Перед вами тривиальная программа, Hello, world, только реализованная с использованием функций.

Если мы хотим вывести «Hello, world» где-то еще, нам просто нужно вызвать соответствующую функцию. В данном случае это делается так: function_name();. Вызов функции имеет вид имени функции с последующими круглыми скобками. Эти скобки могут быть пустыми, если функция не имеет аргументов. Если же аргументы в самой функции есть, их необходимо указать в круглых скобках. Также существует такое понятие, как дефолтные параметры или параметры функции по умолчанию. Такие параметры можно не указывать при вызове функции, т.к. они примут значение по умолчанию, указанно после знака присваивания после данного параметра и списке всех параметров функции

В предыдущих примерах мы использовали функции типа void, которые не возвращают никакого значения. Как многие уже догадались, оператор return используется для возвращения вычисляемого функцией значения. Рассмотрим пример функции, возвращающей значение на примере проверки пароля.

#include <iostream>
#include <string>

using namespace std;

string check_pass (string password)
{
string valid_pass = "qwerty123";
string error_message;
if (password == valid_pass) {
error_message = "Доступ разрешен.";
} else {
error_message = "Неверный пароль!";
}
return error_message;
}

int main()
{
string user_pass;
cout << "Введите пароль: ";
getline (cin, user_pass);
string error_msg = check_pass (user_pass);
cout << error_msg << endl;
return 0;
}

В данном случае функция check_pass имеет тип string, следовательно она будет возвращать только значение типа string, иными словами говоря строку. Давайте рассмотрим алгоритм работы этой программы.

Самой первой выполняется функция main(), которая должна присутствовать в каждой программе. теперь мы объявляем переменную user_pass типа string, затем выводим пользователю сообщение «Введите пароль», который после ввода попадает в строку user_pass. А вот дальше начинает работать наша собственная функция check_pass().

В качестве аргумента этой функции передается строка, введенная пользователем. аргумент функции — это, если сказать простым языком переменные или константы вызывающей функции, которые будет использовать вызываемая функция. При объявлении функций создается формальный параметр, имя которого может отличаться от параметра, передаваемого при вызове этой функции. Но типы формальных параметров и передаваемых функии аргументов в большинстве случаев должны быть аналогичны.

Собственно, я отвлекся, перейдем к алгоритму. После того, как произошел вызов функции check_pass, начинает работать данная функция. Если функцию нигде не вызвать, то этот код будет проигнорирован программой. Итак, мы передали в качестве аргумента строку, которую ввел пользователь. Теперь эта строка в полном распоряжении функции (хочу обратить Ваше внимание на то, что переменные и константы, объявленные в разных функциях независимы друг от друга, они даже могут иметь одинаковые имена. В следующих уроках я расскажу о том, что такое область видимости, локальные и глобальные переменные).

Теперь мы проверяем, правильный ли пароль ввел пользователь или нет. если пользователь ввел правильный пароль, присваиваем переменной error_message Соответствующее значение. если нет, то сообщение об ошибке.

После этой проверки мы возвращаем переменную error_message. На этом работа нашей функции закончена. А теперь, в функции main(), то значение, которое возвратила наша функция мы присваиваем переменной error_msg и выводим это значение (строку) на экран терминала.

Также, можно организовать повторный ввод пароля с помощью рекурсии (о ней мы еще поговорим). Если объяснять вкратце, рекурсия — это когда функция вызывает сама себя. Смотрите еще один пример:

#include <iostream>
#include <string>

using namespace std;

bool password_is_valid (string password)
{
string valid_pass = "qwerty123";
if (valid_pass == password)
return true;
else
return false;
}

void get_pass ()
{
string user_pass;
cout << "Введите пароль: ";
getline(cin, user_pass);
if (!password_is_valid(user_pass)) {
cout << "Неверный пароль!" << endl;
get_pass (); // Здесь делаем рекурсию
} else {
cout << "Доступ разрешен." << endl;
}
}

int main()
{
get_pass ();
return 0;
}

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

Не расстраивайтесь, если не сразу поймете все аспекты функций в C++, т.к. это довольно сложная тема и мы еще будем разбирать примеры с функциями в следующих уроках.
 
Указатели в C++ — урок 7

Сегодня мы поговорим об адресации памяти. Советую уделить особое внимание данному уроку, так как, это, пожалуй, одна из самых важных вещей в написании программного кода.

При выполнении любой программы, все необходимые для ее работы данные должны быть загружены в оперативную память компьютера. Для обращения к переменным, находящимся в памяти, используются специальные адреса, которые записываются в шестнадцатеричном виде, например 0x100 или 0x200. Логично предположить, что если переменных в памяти, необходимых для выполнения программы потребуется слишком большое количество, которое не сможет вместить в себя сама аппаратная часть, произойдет перегрузка системы или ее отказ, или попросту говоря зависание.

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

Такой подход может быть приемлем в простых примерах и несложных программах, которые не требуют большого количества ресурсов. Если же наш проект является огромным программным комплексом с высоким функционалом, объявлять таким образом переменные, естественно, было бы довольно не умно.

Можете себе представить, если бы небезызвестная Battlefield 3 использовала такой метод работы с данными? В таком случае, самым заядлым геймерам пришлось бы перезагружать свои высоконагруженные системы кнопкой reset после нескольких секунд работы игры.

Дело в том, что играя в тот же Battlefield, геймер в каждый новый момент времени видит различные объекты на экране монитора, например сейчас я стреляю во врага, а через долю секунды он уже падает убитым, создавая вокруг себя множество спецэффектов, таких как пыль, тени, и т.п.

Естественно, все это занимает какое-то место в оперативной памяти компьютера. Если не уничтожать неиспользуемые объекты, очень скоро они заполнят весь объем ресурсов ПК.

По этим причинам, в большинстве языков, в том числе и C/C++, имеется понятие указателя. Указатель — это переменная, хранящая в себе адрес ячейки оперативной памяти, например 0x100.

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

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

Ниже приведен конкретный пример обращения к переменным через указатель и напрямую.
Пример использования статических переменных
#include <iostream>
using namespace std;

int main()
{
int a; // Объявление статической переменной
int b = 5; // Инициализация статической переменной b

a = 10;
b = a + b;
cout << "b is " << b << endl;
return 0;
}
Пример использования динамических переменных

#include <iostream>
using namespace std;

int main()
{
int *a = new int; // Объявление указателя для переменной типа int
int *b = new int(5); // Инициализация указателя

*a = 10;
*b = *a + *b;

cout << "b is " << *b << endl;

delete b;
delete a;

return 0;
}

Синтаксис первого примера вам уже должен быть знаком. Мы объявляем/инициализируем статичные переменные a и b, после чего выполняем различные операции напрямую с ними.

Во втором примере мы оперируем динамическими переменными посредством указателей. Рассмотрим общий синтаксис указателей в C++.

Выделение памяти осуществляется с помощью оператора new и имеет вид: тип_данных *имя_указателя = new тип_данных;, например int *a = new int;. После удачного выполнения такой операции, в оперативной памяти компьютера происходит выделение диапазона ячеек, необходимого для хранения переменной типа int.

Логично предположить, что для разных типов данных выделяется разное количество памяти. Следует быть особенно осторожным при работе с памятью, потому что именно ошибки программы, вызванные утечкой памяти, являются одними из самых трудно находимых. На отладку программы в поисках одной ничтожной ошибки, может уйти час, день, неделя, в зависимости от упорности разработчика и объема кода.

Инициализация значения, находящегося по адресу указателя выполняется схожим образом, только в конце ставятся круглые скобки с нужным значением: тип данных *имя_указателя = new тип_данных(значение). В нашем примере это int *b = new int(5).

Для того, чтобы получить адрес в памяти, на который ссылается указатель, используется имя переменной-указателя с префиксом &. перед ним (не путать со знаком ссылки в C++).

Например, чтобы вывести на экран адрес ячейки памяти, на который ссылается указатель b во втором примере, мы пишем cout << "Address of b is " << &b << endl;. В моей системе, я получил значение 0x1aba030. У вас оно может быть другим, потому что адреса в оперативной памяти распределяются таким образом, чтобы максимально уменьшить фрагментацию. Поскольку, в любой системе список запущенных процессов, а также объем и разрядность памяти могут отличаться, система сама распределяет данные для обеспечения минимальной фрагментации.

Для того, чтобы получить значение, которое находится по адресу, на который ссылается указатель, используется префикс *. Данная операция называется разыменованием указателя.

Во втором примере мы выводим на экран значение, которое находится в ячейке памяти (у меня это 0x1aba030): cout << "b is " << *b << endl; . В этом случае необходимо использовать знак *.

Чтобы изменить значение, находящееся по адресу, на который ссылается указатель, нужно также использовать звездочку, например, как во втором примере —*b = *a + *b;.

Когда мы оперируем данными, то используем знак *
Когда мы оперируем адресами, то используем знак &

В этих вещах очень часто возникают недопонимания, и кстати, не только у новичков. Многие из тех, кто начинал программировать с того же php, также часто испытывают подобную путаницу при работе с памятью.

Для того, чтобы освободить память, выделенную оператором new, используется оператор delete.

Освобождение памяти производится в зеркальном порядке по отношению к ее выделению. Иными словами, если указатель a был объявлен первым, он должен быть удален из памяти последним. Это и есть принцип кучи — первым вошел, последним вышел, и соответственно — последним вошел, первым вышел.
Пример освобождения памяти

#include <iostream>
using namespace std;

int main()
{
// Выделение памяти
int *a = new int;
int *b = new int;
float *c = new float;

// ... Любые действия программы

// Освобождение выделенной памяти
delete c;
delete b;
delete a;

return 0;
}

При использовании оператора delete указателя, знак * не используется.

Это очень важная и громоздкая тема. Пожалуй, наиболее важная, особенно для начинающих программистов. Вам следует с особым вниманием подойти к ней, поскольку когда вы научитесь рационально работать с памятью, можете уже считать себя хорошим программистом.
 
Динамические массивы в C++ — урок 8

В пятом уроке мы разобрали понятие массива. При объявлении, мы задавали массиву определенный постоянный размер. Возможно, кто-то из читателей пробовал делать так:

Но, как уже было сказано — при объявлении статического массива, его размером должна являться числовая константа, а не переменная. В большинстве случаев, целесообразно выделять определенное количество памяти для массива, значение которого изначально неизвестно.

Например, необходимо создать динамический массив из N элементов, где значение N задается пользователем. В предыдущем уроке мы учились выделять память для переменных, используя указатели. Выделение памяти для динамического массива имеет аналогичный принцип.
Создание динамического массива
#include <iostream>
using namespace std;

int main()
{
int num; // размер массива
cout << "Enter integer value: ";
cin >> num; // получение от пользователя размера массива

int *p_darr = new int[num]; // Выделение памяти для массива
for (int i = 0; i < num; i++) {
// Заполнение массива и вывод значений его элементов
p_darr = i;
cout << "Value of " << i << " element is " << p_darr << endl;
}
delete [] p_darr; // очистка памяти
return 0;
}

Синтаксис выделения памяти для массива имеет вид указатель = new тип[размер]. В качестве размера массива может выступать любое целое положительное значение.
 
Параметры командной строки в C++ — урок 9

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


Будет отправлять пакеты на адрес google.com с интервалом в 5 секунд. Здесь мы передали программе ping два параметра — это задержка между запросами и адрес хоста для обмена пакетами.

Эти параметры описываются, как аргументы функции main(). Первый аргумент — это количество параметров, которые были переданы программе. В качестве первого аргумента всегда передается название самого файла программы. Второй аргумент — это массив, хранящий все остальные параметры.
Пример 1.1
#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
for (int i = 0; i < argc; i++) {
// Выводим список аргументов в цикле
cout << "Argument " << i << " : " << argv << endl;
}
return 0;
}


Откройте командную строку и запустите оттуда скомпилированную программу.
Для получения числовых данных из входных параметров, можно использовать функции atoi и atof.
 
Классы в C++ — урок 10

Весь реальный мир состоит из объектов. Города состоят из районов, в каждом районе есть свои названия улиц, на каждой улице находятся жилые дома, которые также состоят из объектов.

Практически любой материальный предмет можно представить в виде совокупности объектов, из которых он состоит. Допустим, что нам нужно написать программу для учета успеваемости студентов. Можно представить группу студентов, как класс языка C++. Назовем его Students.

Основные понятия
Модификаторы доступа public и private
Программа учета успеваемости студентов
Отделение данных от логики
Создание объекта через указатель
Конструктор и деструктор класса

class Students {
// Имя студента
std::string name;
// Фамилия
std::string last_name;
// Пять промежуточных оценок студента
int scores[5];
// Итоговая оценка за семестр
float average_ball;
};

Основные понятия

Классы в программировании состоят из свойств и методов. Свойства — это любые данные, которыми можно характеризовать объект класса. В нашем случае, объектом класса является студент, а его свойствами — имя, фамилия, оценки и средний балл.

У каждого студента есть имя — name и фамилия last_name . Также, у него есть промежуточные оценки за весь семестр. Эти оценки мы будем записывать в целочисленный массив из пяти элементов. После того, как все пять оценок будут проставлены, определим средний балл успеваемости студента за весь семестр — свойство average_ball.

Методы — это функции, которые могут выполнять какие-либо действия над данными (свойствами) класса. Добавим в наш класс функцию calculate_average_ball(), которая будет определять средний балл успеваемости ученика.

Методы класса — это его функции.
Свойства класса — его переменные.
class Students {
public:
// Функция, считающая средний балл
void calculate_average_ball()
{
int sum = 0; // Сумма всех оценок
for (int i = 0; i < 5; ++i) {
sum += scores;
}
// считаем среднее арифметическое
average_ball = sum / 5.0;
}

// Имя студента
std::string name;
// Фамилия
std::string last_name;
// Пять промежуточных оценок студента
int scores[5];

private:
// Итоговая оценка за семестр
float average_ball;
};

Функция calculate_average_ball() просто делит сумму всех промежуточных оценок на их количество.
Модификаторы доступа public и private

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

Закрытые данные класса размещаются после модификатора доступа private:. Если отсутствует модификатор public, то все функции и переменные, по умолчанию являются закрытыми (как в первом примере).

Обычно, приватными делают все свойства класса, а публичными — его методы. Все действия с закрытыми свойствами класса реализуются через его методы. Рассмотрим следующий код.

class Students {
public:
// Установка среднего балла
void set_average_ball(float ball)
{
average_ball = ball;
}
// Получение среднего балла
float get_average_ball()
{
return average_ball;
}
std::string name;
std::string last_name;
int scores[5];

private:
float average_ball;
};

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

Функция set_average_ball() принимает средний балл в качестве параметра и присваивает его значение закрытой переменной average_ball. Функция get_average_ball() просто возвращает значение этой переменной.
Программа учета успеваемости студентов

Создадим программу, которая будет заниматься учетом успеваемости студентов в группе. Создайте заголовочный файл students.h, в котором будет находиться класс Students.

/* students.h */
#include <string>

class Students {
public:
// Установка имени студента
void set_name(std::string student_name)
{
name = student_name;
}
// Получение имени студента
std::string get_name()
{
return name;
}
// Установка фамилии студента
void set_last_name(std::string student_last_name)
{
last_name = student_last_name;
}
// Получение фамилии студента
std::string get_last_name()
{
return last_name;
}
// Установка промежуточных оценок
void set_scores(int student_scores[])
{
for (int i = 0; i < 5; ++i) {
scores = student_scores;
}
}
// Установка среднего балла
void set_average_ball(float ball)
{
average_ball = ball;
}
// Получение среднего балла
float get_average_ball()
{
return average_ball;
}

private:
// Промежуточные оценки
int scores[5];
// Средний балл
float average_ball;
// Имя
std::string name;
// Фамилия
std::string last_name;
};

Мы добавили в наш класс новые методы, а также сделали приватными все его свойства. Функция set_name() сохраняет имя студента в переменной name, а get_name() возвращает значение этой переменной. Принцип работы функций set_last_name() и get_last_name() аналогичен.

Функция set_scores() принимает массив с промежуточными оценками и сохраняет их в приватную переменную int scores[5].

Теперь создайте файл main.cpp со следующим содержимым.
/* main.cpp */
#include <iostream>
#include "students.h"

int main()
{
// Создание объекта класса Student
Students student;

std::string name;
std::string last_name;

// Ввод имени с клавиатуры
std::cout << "Name: ";
getline(std::cin, name);

// Ввод фамилии
std::cout << "Last name: ";
getline(std::cin, last_name);

// Сохранение имени и фамилии в объект класса Students
student.set_name(name);
student.set_last_name(last_name);

// Оценки
int scores[5];
// Сумма всех оценок
int sum = 0;

// Ввод промежуточных оценок
for (int i = 0; i < 5; ++i) {
std::cout << "Score " << i+1 << ": ";
std::cin >> scores;
// суммирование
sum += scores;
}

// Сохраняем промежуточные оценки в объект класса Student
student.set_scores(scores);
// Считаем средний балл
float average_ball = sum / 5.0;
// Сохраняем средний балл в объект класса Students
student.set_average_ball(average_ball);
// Выводим данные по студенту
std::cout << "Average ball for " << student.get_name() << " "
<< student.get_last_name() << " is "
<< student.get_average_ball() << std::endl;

return 0;
}

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

Объект класса Students характеризует конкретного студента. Если мы захотим выставить оценки всем ученикам в группе, то будем создавать новый объект для каждого из них. Использование классов очень хорошо подходит для описания объектов реального мира.

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

Введенные данные мы передаем set-функциям, которые присваивают их закрытым переменным класса. После того, как были введены промежуточные оценки, мы высчитываем средний балл на основе этих оценок, а затем сохраняем это значение в закрытом свойстве average_ball, с помощью функции set_average_ball().

Скомпилируйте и запустите программу.

Отделение данных от логики

Вынесем реализацию всех методов класса в отдельный файл students.cpp.
/* students.cpp */
#include <string>
#include "students.h"

// Установка имени студента
void Students::set_name(std::string student_name)
{
Students::name = student_name;
}

// Получение имени студента
std::string Students::get_name()
{
return Students::name;
}

// Установка фамилии студента
void Students::set_last_name(std::string student_last_name)
{
Students::last_name = student_last_name;
}

// Получение фамилии студента
std::string Students::get_last_name()
{
return Students::last_name;
}

// Установка промежуточных оценок
void Students::set_scores(int scores[])
{
for (int i = 0; i < 5; ++i) {
Students::scores = scores;
}
}

// Установка среднего балла
void Students::set_average_ball(float ball)
{
Students::average_ball = ball;
}

// Получение среднего балла
float Students::get_average_ball()
{
return Students::average_ball;
}

А в заголовочном файле students.h оставим только прототипы этих методов.

/* students.h */
#pragma once /* Защита от двойного подключения заголовочного файла */
#include <string>

class Students {
public:
// Установка имени студента
void set_name(std::string);
// Получение имени студента
std::string get_name();
// Установка фамилии студента
void set_last_name(std::string);
// Получение фамилии студента
std::string get_last_name();
// Установка промежуточных оценок
void set_scores(int []);
// Установка среднего балла
void set_average_ball(float);
// Получение среднего балла
float get_average_ball();

private:
// Промежуточные оценки
int scores[5];
// Средний балл
float average_ball;
// Имя
std::string name;
// Фамилия
std::string last_name;
};


Такой подход называется абстракцией данных — одного из фундаментальных принципов объектно-ориентированного программирования. К примеру, если кто-то другой захочет использовать наш класс в своем коде, ему не обязательно знать, как именно высчитывается средний балл. Он просто будет использовать функцию calculate_average_ball() из второго примера, не вникая в алгоритм ее работы.

Над крупными проектами обычно работает несколько программистов. Каждый из них занимается написанием определенной части продукта. В таких масштабах кода, одному человеку практически нереально запомнить, как работает каждая из внутренних функций проекта. В нашей программе, мы используем оператор потокового вывода cout, не задумываясь о том, как он реализован на низком уровне. Кроме того, отделение данных от логики является хорошим тоном программирования.

В начале обучения мы говорили о пространствах имен (namespaces). Каждый класс в C++ использует свое пространство имен. Это сделано для того, чтобы избежать конфликтов при именовании переменных и функций. В файле students.cpp мы используем оператор принадлежности :: перед именем каждой функции. Это делается для того, чтобы указать компилятору, что эти функции принадлежат классу Students.
Создание объекта через указатель

При создании объекта, лучше не копировать память для него, а выделять ее в в куче с помощью указателя. И освобождать ее после того, как мы закончили работу с объектом. Реализуем это в нашей программе, немного изменив содержимое файла main.cpp.
/* main.cpp */
#include <iostream>
#include "students.h"

int main()
{
// Выделение памяти для объекта Students
Students *student = new Students;

std::string name;
std::string last_name;

// Ввод имени с клавиатуры
std::cout << "Name: ";
getline(std::cin, name);

// Ввод фамилии
std::cout << "Last name: ";
getline(std::cin, last_name);

// Сохранение имени и фамилии в объект класса Students
student->set_name(name);
student->set_last_name(last_name);

// Оценки
int scores[5];
// Сумма всех оценок
int sum = 0;

// Ввод промежуточных оценок
for (int i = 0; i < 5; ++i) {
std::cout << "Score " << i+1 << ": ";
std::cin >> scores;
// суммирование
sum += scores;
}
// Сохраняем промежуточные оценки в объект класса Student
student->set_scores(scores);

// Считаем средний балл
float average_ball = sum / 5.0;
// Сохраняем средний балл в объект класса Students
student->set_average_ball(average_ball);
// Выводим данные по студенту
std::cout << "Average ball for " << student->get_name() << " "
<< student->get_last_name() << " is "
<< student->get_average_ball() << std::endl;
// Удаление объекта student из памяти
delete student;
return 0;
}


При создании статического объекта, для доступа к его методам и свойствам, используют операция прямого обращения — «.» (символ точки). Если же память для объекта выделяется посредством указателя, то для доступа к его методам и свойствам используется оператор косвенного обращения — «->».
Конструктор и деструктор класса

Конструктор класса — это специальная функция, которая автоматически вызывается сразу после создания объекта этого класса. Он не имеет типа возвращаемого значения и должен называться также, как класс, в котором он находится. По умолчанию, заполним двойками массив с промежуточными оценками студента.
class Students {
public:
// Конструктор класса Students
Students(int default_score)
{
for (int i = 0; i < 5; ++i) {
scores = default_score;
}
}

private:
int scores[5];
};

int main()
{
// Передаем двойку в конструктор
Students *student = new Students(2);
return 0;
}


Мы можем исправить двойки, если ученик будет хорошо себя вести, и вовремя сдавать домашние задания. А на «нет» и суда нет :-)

Деструктор класса вызывается при уничтожении объекта. Имя деструктора аналогично имени конструктора, только в начале ставится знак тильды ~. Деструктор не имеет входных параметров.
#include <iostream>

class Students {
public:
// Деструктор
~Students()
{
std::cout << "Memory has been cleaned. Good bye." << std::endl;
}
};

int main()
{
Students *student = new Students;
// Уничтожение объекта
delete student;
return 0;
}
 
Рекурсия в С++ - Урок 11
recursive.webp


Рекурсия это повторение элемента, подобного самому себе.

Рассмотрим на примере вычисления факториала.
Факториал - произведение чисел от 1 до n, включая n.
| 1 , n = 0;
n = |
| n * (n - 1), n > 0;

Пример:
6! = 1 * 1 * 2 * 3 * 4 * 5 * 6 = 720;

Напишем итеративный алгоритм для вычисления факториала:
Код:
    int factorial_iterative (int n)
	{
		if (n == 0)						// Если 0!
			return 1;					// то факториал = 1, возвращаем ее
		for (int i = n-1; i > 0; --i) 	// Декрименирующий цикл от (n - 1) до 0, не включая ноль
			n *= i;						// Производим умножение n и (n-1) членов последовательности

		return n;						// Возвращаем значение факториала
	}
Функция целочисленного типа. В качестве входного аргумента принимается целое число, возвращается значение факториала.
Вычисление факториала идет с декриминацией множителя: 6! = 6 * 5 * 4 * 3 * 2 * 1;
Данную функцию можно написать рекурсивно заменив цикл на рекурсию:
Код:
    int factorial_recursive (int n)
	{	
		if (n == 0) 							// Условие выхода: если n равно 0
			return 1;
	
		return n * factorial_recursive (n-1); 	// Возвращаем значение факториала
	}

Рекурсивная функция вызывает сама себя, пока условие выхода из нее не будет истинным.
В данном случае, функция в 5-ой строке будет вызывать сама себя с декрименацией аргумента, пока последний не станет нулем.
Рассмотрим работу алгоритма на примере 6!:
Код:
 	factorial_recursive (6):
	{	
	 if (6 == 0) 							
	  return 1;
	
	 return 6 * factorial_recursive (6-1)	
	   		    {	
				 if (5 == 0) 							
				  return 1;
				 return 5 * factorial_recursive (5-1)
				  			{	
							 if (4 == 0) 							
							  return 1;
							 return 4 * factorial_recursive (4-1)
							    		{	
										 if (3 == 0) 							
										  return 1;
							  			 return 3 * factorial_recursive (3-1)
										 	        {	
													 if (2 == 0) 							
													  return 1;
													 return 2 * factorial_recursive (2-1) 	
				   									  		    {	
																 if (1 == 0) 							
																  return 1;
																 return 1 * factorial_recursive (1-1)
							   											    {	
																			 if (0 == 0) 							
																			  return 1; // выход 	
				   									 					    } 	
				   												}
				   									} 	
				   						}	 	
				   			}
				 }	 	
	}

При написании рекурсивных функций сначала пишем условие выхода, а потом уже основной алгоритм.

Любой рекурсивный алгоритм можно реализовать итеративно.
Глубина рекурсии - количество вложеных вызовов самой себя в глубину. В нашем примере глубина рекурсии равна 5.
Рекурсивные алгоритмы короче, красивее, удобнее своих итерационных аналогов. Рекурсия не дает выиграша вы быстродействии
и даже дает небольшой проигрыш в использовании памяти. Для огромных объемов данных все же применяют итеративные алгоритмы.
Особо удачно использование рекурсии и рекурсивно-ориентироваными структурам, к примеру - деревья. Также удачно рекурсия
ложится на быструю сортировку методом Ч.Хоара (о ней будет статья в цикле "сортировки").

Код:
#include <iostream>
using namespace std;

long long int factorial_recursive (long long int n)
{
	if (n == 0) 
		return 1;
	
	return n * factorial_recursive (n-1);
}

long long int factorial_iterative (long long int n)
{
	if (n == 0)
		return 1;
	for (long long int i = n-1; i > 0; --i) 
		n *= i;

	return n;
}

int main ()
{
	long long int x;
	cout<<"Enter x: ";
	cin>>x;
	if (x < 0) {
		cout<<endl<<"Error! x must be non-negative!";
		return 0;		
	}
        if (x > 16) {
                cout<<endl<<"Error! n overflow!";
                return 0;



	cout<<endl<<"recursive factorial "<<x<<" = "<<factorial_recursive (x);
	cout<<endl<<"iterative factorial "<<x<<" = "<<factorial_iterative (x);
	return 0;
}
Снимок экрана от 2013-05-31 14:51:04.webp
Примечание: В данном варианте максимальное значение x = 16, в противном случае - переполнение переменной n. Если изменить тип на long int предел x = 19, при long long int - 25. Дальнейшее увеличение верхней границы x требует использования длинной арифметики.
Компилятор: g++
 
Так я не понял где продолжение?
 
Можете в ютубе канал по с++ порекомендовать?
 
Назад
Сверху