Пример кода

Напишем программу, которая будет считать сумму всех чисел от 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 на i. Еще один очень важный момент — в начале программы я присвоил переменной 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, то цикл все равно выполнит хотя бы один проход.

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

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

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

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

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].

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

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

.

Вывод элементов массива через цикл

#include <iostream>#include <string>

int main()

{

std:: string students[10] = {

"Иванов", "Петров", "Сидоров",

"Ахмедов", "Ерошкин", "Выхин",

"Андеев", "Вин Дизель", "Картошкин", "Чубайс"

};

for (int i = 0; i < 10; i++) {

std:: cout << students[i] << 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[i];

}

// И выводим заполненный массив.

cout << "\nВаш массив: ";

for (int i = 0; i < 10; ++i) {

cout << arr[i] << " ";

}

cout << endl;

return 0;

}

Скомпилируем эту программу и проверим ее работу.

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

Сегодня мы поговорим о функциях в 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++, т. к. это довольно сложная тема и мы еще будем разбирать примеры с функциями в следующих уроках.

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

Если Вы найдете какие-либо ошибки в моем коде, обязательно напишите об этом в комментариях. здесь же можно задавать все вопросы.

При выполнении любой программы, все необходимые для ее работы данные должныбыть загружены в оперативную память компьютера. Для обращения к переменным, находящимся в памяти, используются специальные адреса, которые записываются в шестнадцатеричном виде, например 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).

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

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

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

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

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

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

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

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

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

Пример освобождения памяти

#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 для указателя, знак * не используется.

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

int n = 10;

int arr[n];

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

Например, необходимо создать динамический массив из 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] = i;

cout << "Value of " << i << " element is " << p_darr[i] << endl;

}

delete [] p_darr; // очистка памяти

return 0;

}

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

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

ping -t 5 google.com

Будет отправлять пакеты на адрес 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[i] << endl;

}

return 0;

}

Откройте командную строку и запустите оттуда скомпилированную программу.

Для получения числовых данных из входных параметров, можно использовать функции atoi и atof.

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

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

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[i];

}

// считаем среднее арифметическое

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[i] = student_scores[i];

}

}

// Установка среднего балла

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[i];

// суммирование

sum += scores[i];

}

// Сохраняем промежуточные оценки в объект класса 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[i] = scores[i];

}

}

// Установка среднего балла 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[i];

// суммирование

sum += scores[i];

}

// Сохраняем промежуточные оценки в объект класса 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[i] = 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;

}

2.1. Работа с динамической памятью
В языке C++ для работы с динамической памятью введены операции new и
delete, которыми можно пользоваться наряду с функцией стандартной библиотеки
С malloc.
Операция new используется как для выделения памяти для одного объекта
(при этом возможна инициализация выделенной памяти передаваемым значени-
ем), так и для массива однородных объектов. Операция new возвращает адрес
начала выделенной динамической памяти соответствующего типа.
Ее синтаксис: new тип;
new тип (выражение-инициализатор);
new тип [выражение_размерность_массива];
Пример:
int * p;
int * q;
p = new int(5); // выделение памяти и инициализация
// значением 5
q = new int[10]; // выделение памяти для массива из 10
// элементов

Операция delete освобождает распределенную операцией new память. Ее
синтаксис:
delete указатель_на_объект;
delete [ ] указатель_на_массив_объектов;
Первая форма используется, если операцией new размещался единичный
(скалярный) объект. Векторная форма используется, если операцией new создан
массив объектов, при удалении которого для каждого из объектов необходим
вызов деструктора (деструкторы описываются далее). Такими объектами явля-
ются объекты пользовательского типа.
2.2. Описания, значения параметров по умолчанию
В функциях, написанных на языке С++, в отличие от функций на С, описания
локальных объектов и операторы располагаются в произвольном, удобном для
программиста порядке. Остается лишь единственное ограничение: любое вво-
димое программистом имя должно быть описано до его первого использования.
В частности, в С++ можно описать переменную, используемую в первом
выражении заголовка цикла for непосредственно в этом выражении:
for (int i = 0, i < 10, i++) {…}
В С++ разрешено задавать априорные значения формальных параметров
функции. Для этого после описания формального параметра в заголовке функции
ставится знак = и соответствующее значение параметра по умолчанию.
Если в заголовке функции есть параметры с априорными значениями и без
них, то все параметры, значения которых необходимо задавать явно при обра-
щении к функции, располагаются в начале списка формальных параметров, а за-
тем описываются формальные параметры с априорными значениями.
Пример:
void f (int a, int b, int c = 1, int d = 2, int e = 3){…}
При этом обратиться к функции f можно как к функции с двумя, тремя,
четырьмя или пятью параметрами:
f(5, 5); // a = 5, b = 5, c = 1, d = 2, e = 3
f(1, 2, 3); // a = 1, b = 2, c = 3, d = 2, e = 3
f(1, 1, 1, 1); // a = 1, b = 1, c = 1, d = 1, e = 3
f(7, 7, 7, 7, 7); // a = 7, b = 7, c = 7, d = 7, e = 7
В С++ отменены правила определения типов по умолчанию. Например, тип
возвращаемого функцией main результата надо указывать явно: int main ()
{…}.
2.3. Тип bool
В С++ введен логический тип данных bool и введены в употребление логи-
ческие константы true и false.

2.4. Ссылки
В С++ введен ссылочный тип данных.
Описание ссылки вводит идентификатор, который будет псевдонимом (alias)
для объекта, указанного при инициализации. Ссылка с точки зрения реализации
является адресом объекта. Отличие ссылочного типа от типа «указатель» заклю-
чается во множестве операций, применимых для этого типа. Ссылке сопоставля-
ется некоторый адрес единственный раз при ее инициализации. Проинициали-
зированная ссылка в дальнейшем не может быть изменена. Так же как и указатель,
ссылка является типизированной. Синтаксис определения ссылки:
тип& идентификатор = инициализатор;
int a;
int & b = a;
Инициализация ссылки, как обычно, записывается с помощью знака опера-
ции присваивания. После инициализации идентификатор ссылки используется
так же, как и переменная–инициализатор ссылки. Таким образом, дальнейшее
присваивание значений переменной-ссылке приведет к изменению не ее значения,
а значения переменной-инициализатора. Ссылка, по сути, является синонимом
имени переменной-инициализатора.
Пример:
int a = 5;
int & aa = a; // ссылка обязана иметь начальное
// значение!
// a и aa ссылаются на одно и то же
// целое число
a = aa + 1;
cout << a << aa; // будет выведено “6 6”, поскольку
// a и aa – одна и та же сущность
if (& aa == & a) {... }; // адрес ссылки равен адресу
// самого объекта!
// можно инициировать ссылку
// константным значением, если
// сама ссылка тоже константная:
const int & aa1 = 1; // ссылка на константу 1
double b[10];
double & bb = b[9]; // псевдоним для b[9] – последнего
// элемента массива b
Ссылка может быть определена и для динамического объекта:
int & x = * new int;
В этом случае, несмотря на то, что, как было отмечено выше, ссылка с точки
зрения реализации является адресом объекта, формат операции delete будет
следующий:
delete & x;
При описании формального параметра функции с использованием ссылки
этот параметр передается по ссылке.
При передаче параметра по ссылке в функцию передается его адрес, и все
изменения, происходящие с формальным параметром, происходят и с соответ-
ствующим фактическим параметром.
При передаче параметра по значению создается локальная копия объек-
та-параметра, которая инициализируется значением соответствующего фактиче-
ского параметра, при этом соответствующий фактический параметр не может
изменить своего значения в теле функции.
Ссылка инициализируется при передаче параметров в функцию и при пере-
даче возвращаемого значения в виде псевдонима объекта, который продолжает
существовать после выхода из функции (например, ссылка на текущий объект при
выходе из метода).
Примечание. Текущий объект может быть возвращен при выходе из метода не
только в виде ссылки на текущий объект, но и в виде объекта. Однако, это менее эф-
фективно, так как в этом случае создается копия текущего объекта.
Пример:
int & imax(int * m){
int i;

return m[i];
}
int main () {
int A[10];

imax(A) = 0;

}
В данном примере в результате вызова функции imax() будет возвращено
значение ссылки на максимальный элемент массива А. По данной ссылке этому
элементу вектора может быть присвоено новое значение.
2.5. Стандартная библиотека С++, стандартный ввод-вывод
Для языка С++ создана своя стандартная библиотека, которая отличается от
стандартной библиотеки С. Изменения затронули, например, функции вво-
да-вывода, введен раздел, посвященный шаблонам (STL – стандартная библио-
тека шаблонов, описанная ниже).
Тем не менее, стандартная библиотека С целиком включена в стандартную
библиотеку С++.
В С++ введен новый формат задания заголовочных файлов стандартной
библиотеки: без.h., при этом все используемые в библиотеке имена погружены в
стандартное пространство имен std (см. раздел 2.6).
Например, в С++ файл заголовков ввода-вывода называется iostream, и
для его подключения необходимо использовать директиву
#include <iostream>
Чтобы подключить к программe на С++ заголовочный файл стандартной
библиотеки С в новом формате, надо добавить в начало названия соответствую-
щего файла букву с. Например,
#include <cstdio>
В файле стандартной библиотеки С++ <iostream> введены классы, со-
ответствующие стандартным (консольным) потокам ввода (класс istream) и
вывода (класс ostream), а также объекты этих классов: cin – объект класса
istream, cout, cerr – объекты класса оstream.Через эти объекты для
базовых типов данных доступны операции ввода >> из стандартного потока
ввода, например,
cin >> x;
и вывода << в стандартный поток вывода (cout) или стандартный файл
ошибок (cerr), например,
cout << “String” << S << endl;
При этом явно не указываются никакие форматирующие элементы.
endl – константа, обозначающая конец строки, она так же определена в
файле <iostream>.
Стандартная библиотека С++:
1) обеспечивает поддержку свойств языка, например, управление памятью
(функции, основанные на использовании операций new и delete)
2) предоставляет информацию о типах во время выполнения программы
(RTTI)
3) обеспечивает поддержку обработки исключений (стандартные исклю-
чения)
4) предоставляет информацию о зависящих от реализации аспектах языка
(например, максимальное значение float)
5) предоставляет программисту общеупотребительные математические и
некоторые другие функции (например, sqrt(), генератор случайных
чисел и т. д.)
6) предоставляет программисту некоторые нетривиальные и машин-
но-зависимые средства, что позволяет писать переносимые программы
(например, списки, функции сортировки, потоки ввода/вывода).
С подробным описанием стандартной библиотеки С++ можно познако-
миться в [12], часть III «Стандартная библиотека».
2.6. Пространства имен, пространство имен std. Операция '::'
В C++ так же, как и в языке программирования C, используется обычный
механизм разрешения видимости имен по принципу локализации.
Кроме того, в С++ введено понятие пространства имен. Пространство имен
является областью видимости и позволяет локализовать имена, описанные
внутри этого пространства, что бывает полезно, например, для устранения кол-
лизий имен, которые могут возникнуть при написании одного программного
продукта разными программистами.
Пространства имен задаются следующим образом:
namespace имя_пространства_имен {
... описания...
}
Пример:
namespace A {
int x;
void f ();

}
В С++ также введена операция разрешения области видимости
имен‘::’, которая расширяет имя, определяя из какого пространства имен (или
области видимости) данное имя выбирается.
Если используется имя из текущей области видимости или имя из объем-
лющей области видимости, не совпадающее ни с одним именем из текущей об-
ласти, оно записывается обычным образом, если же нужно совпадающее имя из
объемлющей области или имя из другой области видимости, это имя можно за-
писать следующим образом:
имя_области_видимости:: имя
Идентификатор области видимости слева от операции ‘::’ называется
квалификатором.
C++ унаследовал от языка программирования C единое глобальное про-
странство имен. Если необходимо использовать глобальную переменную, нахо-
дящуюся вне пространств имен, то для устранения неоднозначности используется
операция ‘::’ без квалификатора.
Пример:
char c;
namespace x{
char c;
void f(char e){
::c = c = e;
}
}

Файлы позволяют пользователю считывать большие объемы файла.

Поток ifstream служит для работы в режиме чтения

Дан файл содержащий текст на русском языке в предложениях некоторые из слов записныццц


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: