Вывод текста, первая программа



ПОСОБИЕ ПО ОСНОВАМ ПРОГРАММИРОВАНИЯ НА ЯЗЫКЕ С++ ДЛЯ НЕПРОГРАММИСТОВ !!! Автор: Васька Мырт ( vk.com/id176208527) Данная книга не является учебником по программированию для программистов. Это лишь сборник самых важных вещей из опыта любительского программирования. Цель книги- систематизировать свои знания в голове, отбросить всё это и смело идти дальше, а также дать возможность любому человеку освоить азы с нуля простым языком. ________________________________________________ РАЗДЕЛ 1

ОСНОВЫ И ПРОЦЕДУРНОЕ ПРОГРАММИРОВАНИЕ

 

НАЧАЛО РАБОТЫ (ТЕХНИЧЕСКИЕ МОМЕНТЫ)

Для того, чтобы писать программы на языке программирования с++, нам понадобится т.н. среда программирования (IDE)- это программа, в которой пишутся программы. На самом деле можно обойтись и без IDE, нужен лишь компилятор. Компилятор- это программа, которая переводит код, написанный на высокоуровневом языке программирования (в данном случае с++), в машинный. Компьютер понимает только машинный код, для человека же это очень очень сложно, поэтому он пишет программы на более простом для человека языке. А потом компилятор переводит этот "человеческий" язык в "компьютерный". Это тема отдельного разговора, об этом м.б. будет позже, а мы идём дальше.

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

Главная и самая продвинутая IDE- это MICROSOFT VISUAL STUDIO. Есть бесплатные версии, которые можно скачать на официальном сайте майкрософт. Также можно поискать на торрентах. Я в своё время потратил 3 дня, чтобы найти рабочую версию, поэтому поделюсь ссылкой на свой яндекс диск:

Https://yadi.sk/d/xCsxw14azD9cf

Эта среда visual studio 2012 ultimate у меня работала и работает, поэтому я буду показывать всё на её примере. Тебе нужно скачать этот файл (он весит 1.5 гб) и установить как обычную программу. После установки можно будет начинать программировать. Подходит для windows 7, 8 и 10 точно, проверено!

После установки IDE перейдём к созданию проекта. Запускаем программу, далее слева находим Ultimate 2012, Start и "New Project". Либо нажимаем на FILE (слева вверху) и там тоже New Project. Нажимаем. Выбираем язык по умолчанию с++ (если среда это спрашивает) и пустой проект (empty project). Пишем имя проекта в поле Name, либо можно по умолчанию оставить Project1. И жмём ОК. После загрузки слева выбираем Solution Explorer --> Resource Files --> Add --> New Item. Выбираем c++ file (cpp) и имя можно оставить по умолчанию Source.cpp либо написать своё. Жмём Add. Всё, теперь весь код мы будем писать в созданном нами файле Source.cpp. Позже по мере развития твоего программирования ты будешь создавать и дополнительные файлы, но пока нам будет достаточно только одного.

Чтобы сохранить проект, ты можешь выбрать FILE -> Сохранить как. Или нажать CTRL + S, тут всё стандартно. Проекты сохраняются по умолчанию в папке "Мои документы"

...

У visual studio есть серьёзный недостаток: очень много весит и при этом нельзя установить на внешний жёсткий диск- только на системный. Поэтому если у тебя очень мало свободной памяти, например если у тебя ssd 32 гб, то visual studio у тебя не поместится. Либо придётся удалить всё и тогда только это и поместится- весит в итоге около 10 гб вся эта махина после установки. В таком случае придётся использовать другую среду программирования.

Я пробовал разное и заработала у меня только среда dev cpp (dev c++). Можешь скачать отсюда:

https://yadi.sk/d/BNJEhVz93JwnnZ

После установки всё это весит примерно 300 мб. Тут есть ряд недостатков, но зато можно компилировать на с++ и пользоваться программами. Как сделать чтобы это работало и на другом компьютере- я не разобрался и в интернете ответа на этот вопрос на данный момент так и не нашёл. Эксперименты оказались неудачными. Но можно компилировать и пользоваться на своём компьютере.

Ещё тут есть проблема со шрифтами- при копировании кода и вставки в среду могут получиться каракули вместо русских букв- нестыковка кодировок. Придётся вручную всё русское переписывать (комментарии, выводимые текста)

Также тут нужно писать название проекта на латинице- иначе будут проблемы с компиляцией и запуском. Т.е. не получится скомпилировать и выполнить одновременно. Можно будет только скомпилировать отдельно, а потом найти этот exe файл и уже запустить.

Ты скачиваешь установщик по ссылке выше и устанавливаешь как обычную программу. Теперь как создать проект: открыть среду -> файл -> создать -> проект. Далее по умолчанию наверху стоит Basic, выбираем ниже Console Application, с++ проект и пишешь имя проекта на латинице. Далее выбираешь место где всё это будет сохраняться. Для удобства можно в папке "мои документы". У тебя создался и открылся файл main.cpp. Стираешь то что там уже есть и можешь там писать свою программу.

 

Вывод текста, первая программа

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

В главном файле cpp ты пишешь следующее:

#include <iostream>

using namespace std;

int main() {

cout << "Privet" << endl;

system("pause");

}

 

Вывод в консоль производится с помощью команды cout. Чтобы вывести слово "Privet", тебе нужно написать:

cout << "Privet";

Вместо "Privet" может быть любой другой текст.

Команда endl переводит тебя на новую строку.

Ты также можешь писать это отдельно: cout << endl;

После операции ставится в конце точка с запятой.

 

Однако, если ты просто напишешь cout << "Privet" << endl; одной строчкой программу, то она не скомпилируется. Команду cout компилятор не поймёт. Поэтому мы вначале пишем #include <iostream>, что значит подключить библиотеку iostream (input output stream- поток ввода вывода), т.е. благодаря этой строчке ты можешь в программе что-то вводить и выводить. Но есть ещё одна мелочь- вторая строчка, которую тоже нужно написать: using namespace std; Что значит- использовать пространство имён std. Команды ввода вывода (наподобие cout, endl и т.п) принадлежат некой более обобщённой структуре, которая называется "пространство имён". И если ты не напишешь вторую строчку с подключением пространства имён, тебе придётся везде в программе вместо cout писать std::cout, а вместо endl писать std::endl. Это, согласись, неудобно. Также можно писать вместо using namespace std отдельно using std::cout, using std::endl и т.д.

Далее- все ключевые операции происходят в главной функции, которая записывается как int main() { содержимое }

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

Главная функция пишется самой последней функцией, т.е. в самом низу программы.

После того как ты написал программу, в visual studio ты нажимаешь наверху зелёную кнопочку Local Windows Debugger. После этого программа либо скомпилируется и запустится- вылезет консольное чёрное окно (это exe формат, сама программа) - либо выдаст ошибку. Внизу будет на английском написано, в какой строке ошибка и что именно за ошибка. Из-за этой ошибки проект не компилируется. Чтобы программа скомпилировалась и работала, тебе нужно исправить все ошибки. Часто они могут быть совершены по невнимательности- например, ты забыл поставить точку с запятой.

В среде devcpp ты нажимаешь F11 (скомпилировать и выполнить). Просто скомпилировать F9, просто выполнить F10. Все эти 3 кнопки можно найти наверху в виде цветных квадратиков чуть ниже справки, там даже есть подписи при наведении на них курсора. При вылезании окон просто жмёшь "сохранить"- это .cpp и .h файлы проекта.

Чтобы программа не вылетала после завершения, мы пишем после всех действий system("pause"); Это значит, у тебя вылезет в программе сообщение "Для продолжения нажмите любую клавишу" и благодаря этому ты увидишь то что вывелось или выполнилось в программе. И после нажатия клавиши программа вылетает, если заканчивается. Либо выполняются следующие действия по программе.

Чтобы ты мог выводить русские символы, нужно в начале главной функции написать setlocale(0,""); На самом деле это значит что используется язык системы. В нашем случае это русский. Но при этом ты не сможешь вводить русские символы.

Для полной русификации (чтобы ты ещё и вводить русское мог) нужно вместо setlocale(0,""); написать в начале главной функции 2 строчки (команды): SetConsoleCP(1251); и SetConsoleOutputCP(1251); При этом помимо подключения iostream нужно подключить ещё одну библиотеку: windows.h. Всё это выглядит таким образом:

#include <iostream>

#include <Windows.h>

using namespace std;

int main() {

SetConsoleCP(1251);

SetConsoleOutputCP(1251);

cout << "Привет!!! :)" << endl;

system("pause");

}

При этом в настройках консоли нужно поставить шрифт "Lucida Console". Для этого при запуске exe программы ты правой кнопкой мыши нажимаешь на левый верхний угол (значок), выбираешь свойства (properties) и там уже можешь поменять шрифт. Возможно, lucida console у тебя уже стоит по умолчанию.

...

Теперь мы можем рассмотреть пример посложнее. Вывести уже несколько строк:

ВСЕМ ПРИВЕТ

Я ИЗУЧАЮ С++

Если мы только выводим русские символы, нам будет достаточно setlocale. Программа для вывода этих 2 строк будет иметь такой вид:

#include <iostream>

using namespace std;

int main() {

setlocale(0,"");

cout << "ВСЕМ ПРИВЕТ" << endl;

           cout << "Я ИЗУЧАЮ С++" << endl;

system("pause");

}

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

Пробелы можно ставить между разными элементами команд. Например, можно написать так: cout << "ВСЕМ ПРИВЕТ" << endl; А можно и так: cout<<"ВСЕМ ПРИВЕТ"<< endl; Во втором случае мы убрали промежуточные пробелы между командами, но при этом код будет рабочим. Только нам самим тяжелее разобрать что в нём происходит. Поэтому рекомендую для удобства, дизайна и понятности кода ставить пробелы, делать отступы, пропуски строк и т.п. Это считается хорошим тоном. Позже ты к этому привыкнешь и вообще сам поймёшь, на сколько это важно. Потому что часто, открыв самим же написанный код полгода назад, ты не можешь его понять- слишком много всего, каша из слов, команд и символов.

Текст у нас всегда употребляется в двойных кавычках. Числа можно выводить без кавычек, например: cout << 2 << endl;

Вместо endl ты можешь использовать для перехода на новую строку команду символов \n после текста в самом тексте. Это выглядит так: cout << "ВСЕМ ПРИВЕТ\n";

Можно ставить \n и endl несколько раз для перехода на несколько строк. Пропуск строки имеет вид cout << endl; или cout << "\n"; Чтобы пропустить 2 строки, можно написать cout << endl << endl; или cout << "\n\n"; или даже так: cout << "\n" << endl; или cout << endl << "\n";

Для вывода табуляции (клавиша TAB на клавиатуре) используется команда \t в тексте. Табуляция позволяет выровнять текст на разных строках. Можно использовать табуляцию несколько раз.

Чтобы вывести сами кавычки, мы ставим перед ними символ  \. Например, я хочу вывести просто двойные кавычки и всё. Для этого я пишу: cout << "\"";

Чтобы вывести двойные кавычки 2 раза, я пишу: cout << "\"\""; Тут вместо просто кавычек, как ты понял, мы пишем \"

Одинарные кавычки выводятся аналогично. В с++ двойные кавычки предназначаются для текста, а одинарные- для одного символа.

Для вывода символа \ мы используем аналогичный приём: cout << "\\"; Т.е. мы ставим в начале такого неоднозначного для компилятора символа ещё раз этот символ, чтобы показать что это будет просто текст.

Например, нужно вывести такую последовательность символов: "\'

Тогда это будет выглядеть так: cout << "\"\\\'";

Разберём по кускам: " \" \\ \' "

Для вывода пробелов мы так и пишем: cout << " " << endl;

...

Теперь по поводу главной функции main. Правильно писать именно int main, т.к. главная функция должна возвращать 0 в случае успешного завершения программы. По хорошему, в конце главной функции писать бы ещё return 0; Но компиляторы у devcpp и visual studio автоматически это проставляют, так что здесь эту строчку писать необязательно.

Компилятор visual studio поймёт и переварит и void main, но другие компиляторы, в т.ч. и dev cpp (тут обычно встроен g++ или minGW) не поймут, потому что по стандартам языка это неправильно. Т.е. void main у тебя в средах, отличных от visual studio, может выдать ошибку и не скомпилироваться.

Загвоздка с void main в том, что когда-то так писали на заре зарождения языков Си и С++, поэтому такое написание дублируется в учебной литературе- обычно вузовской. Т.е. студенты приучаются писать void main, т.к. препод так пишет на доске и в учебниках так написано, но по факту это работает только в visual studio, а в других средах нет, и вообще так писать неправильно оказывается. В высших учебных заведениях часто устаревшая и редко проверяемая литература. Это нужно иметь в виду. Я тоже начинал учиться с void main по причинам выше, а потом пришлось переучиваться на int main. Так что.. Пиши int main, так должно быть правильно в с++

...

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

Например: cout << "привет"; // вывели текст "привет"

Многострочный комментарий делается так:

/*

пишем что-нибудь

опять пишем

*/

Также можно оставлять комментарий в середине строки: cout << /* вывод */ "привет";

 

Переменные и ввод текста

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

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

int- целые числа

double- вещественные числа (т.е. могут быть как целые, так и дробные)

char- символы

bool- логическая переменная (принимает 2 значения- истина или ложь)

Также есть float- это тоже вещественные числа, но занимают меньше битов памяти. double- это вещественные числа двойной точности.

Это были примитивные типы данных. Также мы можем создавать свои типы данных, это называется структура или класс, но про это будет далеко внизу позже. Но один класс также часто употребим: string- это строки. Для того чтобы мы могли использовать их, нужно в начале программы написать #include <string>

...

Наш калькулятор будет выглядеть следующим образом:

#include <iostream> // подключение библиотеки ввода-вывода

using namespace std;

int main() {

           setlocale(0,""); // чтобы выводились русские символы

           cout << "Введите первое слагаемое: ";

           int a; // создание переменной (ячейки памяти для хранения 1-го слагаемого)

           cin >> a; // ввод числа с клавиатуры

           cout << "Введите второе слагаемое: "; int b; cin >> b;

           cout << "Сумма: " << a+b << endl;

           system("pause");

}

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

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

...

После ввода чего-либо нас автоматически перебрасывает на новую строку, т.е. endl (читается как "ендлайн") происходит автоматически.

Мы можем по очереди сразу и 2 числа вводить: cin >> a >> b; После ввода числа нажимаем ENTER.

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

Дробные числа вводятся через точку, т.е. например 7.15- это 7 целых и 15 сотых. Для таких чисел используем тип double. Если ты введёшь их в тип int, то дробная часть просто обрежется и проигнорируется, т.е. вместо 7.15 у тебя будет просто 7.

Для арифметических действий используются стандартные символы: + сложить, - вычесть, * умножить, / поделить. Так же есть остаток (%). Например, 13%4- это значит что будет вычислен остаток от деления 13 на 4. Работает только для целых чисел. В данном случае 13:4= 3 целых и 1 в остатке, поэтому 13%4=1.

Можно записывать целые выражения, например: cout << (3+5)*8.7 - 2/3 << endl; Всё это выражение автоматически вычислится и выведется. Порядок действий такой же как в обычной математике: сначала в скобках, потом умножение или деление, потом сложение или вычитание.

Или можно записывать выражения с переменными, например так:

double a= 3.7; double b= -2.81;

cout << (a+b)*(a-b) + 11.6/2.7 + a*a - b/(b-3) << endl;

 

Задание 1

Написать программу, которая будет вычислять площадь и периметр квадрата с данной стороной. Например, я ввожу значение стороны: 5. А программа выводит: площадь равна тому-то, а периметр вот тому-то. Для вычисления площади квадрата нужно сторону умножить саму на себя, а для периметра- на 4.

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

...

Перейдём к вводу текстовых данных. Для 1 символа мы используем char; Например, так: char c; cout << "Введите + чтобы продолжить\n-->"; cin >> c;

Для ввода целого слова (больше 1 символа) используем string. Например, ты вводишь имя, а программа приветствует тебя. Это будет выглядеть так:

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           cout << "Введи своё имя: "; string name; cin >> name;

           cout << "Приветствуем тебя, " << name << endl;

           system("pause");

}

Тут name- это имя переменной. Ты мог написать любое другое слово на латинице или даже буквы с цифрами типа b24, главное чтобы начиналось с буквы. Или даже так: imya_dlya_programmy - символы нижнего подчёркивания можно использовать для замены пробела, но при этом самого пробела быть не должно. Имя переменной должно быть желательно в тему и быть понятным, потому что в большой программе таких имён (идентификаторов) может быть много и в них можно легко запутаться. Поэтому очень важно как ты называешь свои переменные. Поменьше безликих a, b, c, a2, x, yz и т.п. Лучше писать целым словом на латинице.

Пустая строка выглядит так: "". Пустой символ: '\0'

К строкам применима операция сложения. Например:

string s1= "Cat "; string s2= "Musya"; string s= s1+s2; cout << s; Тогда выведется "Cat Musya";

Если у нас есть слово, мы можем обращаться к отдельному символу по номеру. Например, string s= "Musya"; Тогда мы можем написать cout << s[0] << endl; И выведется символ M. Нумерация символов начинается с нуля. s[1]=='u', s[2]== 's', s[3]== 'y', s[4]== 'a'.

Регистр букв имеет важное значение. Cat и cat это разные переменные. Так же и с символами: 'S' и 's' это разные символы.

Для ввода нескольких слов с пробелами используем команду getline:

string s; getline(cin,s);

Например, можно ввести целое предложение. После нажатия ENTER ввод в переменную s будет закончен. Если же просто написать cin >> s; тогда после первого пробела ввод в переменную закончится. Т.е. все дальнейшие слова после пробела проигнорируются и пропадут.

 

Задание 2

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

...

В с++ есть упрощение для переприсвоения переменных. Например, мы пишем так:

int a= 5, b=3;

a= a+b;

В данном случае в переменную a теперь занесётся значение 5+3=8. И будет a=8, b=3;

Мы могли бы записать это короче: a+=b;

Аналогично и с другими действиями. Например, x*=7 это то же самое что и x= x*7. Можно это использовать и с остатком: a%=5 (то же самое что a= a%5)

Два раза подряд такая операция недопустима. Т.е. нельзя писать a+=5*=7, нужно писать отдельно: a+=5; a*=7; Т.е. сначала мы к переменной a прибавляем 5, а потом умножаем её на 7.

...

Также за имя переменной ты можешь написать это символ $, это тоже будет работать, но не рекомендуется. Можно также начинать имя переменной с подчёркивания или вообще подчёркиванием имя и назвать, например: string _; Или вообще так: int $_$;

...

РЕШЕНИЯ задач ранее

// Первая.

#include <iostream>

using namespace std;

int main() {

           setlocale(0,"");

           cout << "Введите сторону квадрата: "; double side; cin >> side;

           cout << "Площадь: " << side*side << endl;

           cout << "Периметр: " << side*4 << endl;

           system("pause");

}

// Вторая.

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           cout << "Введите имя: "; string name; cin >> name;

           cout << "Введите фамилию: "; string surname; cin >> surname;

           cout << name << " " << surname[0] << endl;

           system("pause");

}

 

Условия и разветвления

Бывает, что какие-то операции должны выполняться только при каком-то условии. Или программа делится на несколько веток в зависимости от того что мы ввели и от того что получилось. Для этого существуют условия.

Например, мы вводим целое число. И если оно чётное, то выводится текст "число чётное". А иначе нечётное. Условие реализуется через оператор if. На самом деле его достаточно, хоть существуют и другие операторы для удобства.

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

int a; cin >> a;

if(a%2==0) cout << "Число чётное" << endl;

if(a%2==1) cout << "Число нечётное" << endl;

Если число чётное, то оно делится на 2 без остатка. Это значит что остаток при делении на 2 равен нулю. Это мы и записали в условии. Ну а у нечётного числа остаток при делении на 2 равен 1.

Можно было сделать и так:

if(a%2==0) cout << "Число чётное" << endl;

else cout << "Число нечётное" << endl;

...

Теперь чтобы лучше закрепить условия, напишем программу, которая будет решать квадратное уравнение. Квадратное уравнение имеет вид a*x*x + b*x + c = 0, где a,b,c- известные числа (коэффициенты), х- неизвестное число. Всё это у нас будет в типе double (который может принимать как целые, так и дробные числа).

Решение квадратного уравнения (сокращённо- квур) зависит от дискриминанта. Если он меньше нуля, то уравнение не имеет вещественных корней. Если равен нулю, то тогда 1 корень. Если больше нуля, то 2 корня.

Т.е. как выглядит программа: вводим a,b,c, а затем их обрабатываем.

double a,b,c; cin >> a >> b >> c;

Теперь результат будет зависеть от значения дискриминанта d= b*b - 4*a*c. Можно эту новую переменную даже не вводить.

if (b*b - 4*a*c < 0) cout << "Корней нет" << endl;

if (b*b - 4*a*c == 0) cout << "x= " << -b / (2*a) << endl;

if (b*b - 4*a*c > 0) {

           cout << "x1= " << ( -b + sqrt(b*b - 4*a*c) ) / (2*a) << endl;

           cout << "x2= " << ( -b - sqrt(b*b - 4*a*c) ) / (2*a) << endl;

}

Вот и всё. Теперь разберём что тут есть непонятного.

Знаки больше и меньше как в математике, ничего нового. Но проверка равенства делается через 2 знака равно подряд. Например, если a равно b тогда выводим "да": if (a==b) cout << "да"; Это нужно чтобы компилятор не путал с присваиванием. При присваивании переменной значение у нас один знак равно, а при проверке равенства- два.

Если в условии у нас больше чем 1 действие, мы должны заключить всё в фигурные скобки как это в случае когда дискриминант больше нуля. Действие определяется точкой с запятой в конце. Фигурные скобки можно ставить всегда, но когда одно действие то можно и опускать. Если не поставить фигурные скобки, то при выполнении условия будет восприниматься только первое действие.

Общий синтаксис такой: if ( условие ) { набор действий, который соблюдается при истинности условия }

Можно использовать также команду else (иначе):

if ( условие ) { набор действий если условие истинно } else { набор действий если условие ложно, т.е. во всех других случаях }

В нашей программе мы могли поступить ещё так:

double d= b*b-4*a*c;

if (d < 0) cout << "Корней нет" << endl;

else if (d == 0) cout << "x= " << -b / (2*a) << endl;

else {

           cout << "x1= " << ( -b + sqrt(b*b - 4*a*c) ) / (2*a) << endl;

           cout << "x2= " << ( -b - sqrt(b*b - 4*a*c) ) / (2*a) << endl;

}

Если дискриминант меньше нуля, делаем одно. Иначе если дискриминант равен нулю, делаем такое-то. Во всех остальных случаях (т.е. тут дискриминант будет больше нуля) делаем уже вот это.

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

Можно сделать так:

if (a==0) cout << "Уравнение не квадратное! Ошибка!" << endl;

else {

if (b*b - 4*a*c < 0) cout << "Корней нет" << endl;

if (b*b - 4*a*c == 0) cout << "x= " << -b / (2*a) << endl;

if (b*b - 4*a*c > 0) {

           cout << "x1= " << ( -b + sqrt(b*b - 4*a*c) ) / (2*a) << endl;

           cout << "x2= " << ( -b - sqrt(b*b - 4*a*c) ) / (2*a) << endl;

}

}

Отступы делаются чисто для удобства самого программиста- на работоспособность программы это никак не влияет. Просто без отступов получается каша, которую зачастую очень трудно разобрать. Даже тому, кто её и написал.

Для арифметического корня мы используем функцию sqrt. Чтобы она работала, надо прописать в начале кода программы ещё заголовочный файл cmath или math.h, т.е. #include <cmath>

Например, корень 16 мы вычислим так: sqrt(16). Если хотим вывести результат, то cout << sqrt (16) << endl;

Можно было в нашей программе в случае положительного дискриминанта сделать 1 действие таким образом:

cout << "x1= " << ( -b + sqrt(b*b - 4*a*c) ) / (2*a) << endl << "x2= " << ( -b - sqrt(b*b - 4*a*c) ) / (2*a) << endl;

Как видишь, одни и те же вещи можно реализовывать разными способами- это всё вопрос удобства.

В качестве тренировки предлагаю усовершенствовать программу, чтобы она выводила ещё и комплексные числа. Т.е. мы корень из минус единицы считаем мнимой единицей. Например, решение sqrt(-4) мы выведем как: cout << "2*i << endl; Тут буквой i обозначается мнимая единица, это всё выводится как текст. Например, корень из отрицательного числа T выведется так: cout << sqrt (-T) << " * i" << endl; Если T= -9, то -T= 9 и из него уже можно сосчитать корень, а далее просто дописывается текст " * i", т.е. умножить на мнимую единицу. Буква i взята математиками не случайно: i- imaginary (мнимая)

Полная программа приведена в конце главы.

...

Помимо стандартных знаков "больше", "меньше", "равно", есть ещё другие:

<= меньше или равно

>= больше или равно

!= не равно

Также можно использовать объединения условий:

&& и

|| или

! не

Пример: если число x чётное и при этом больше 10, тогда выводим его:

if ( (x % 2 ==0) && (x > 10) ) cout << x << endl;

Все скобки тут обязательны. Каждое отдельное условие в скобках. Также могут быть скобки как в арифметических выражениях, например: (не А или B ) или (B и С), где А, B, С- некие условия. Их можно заключать в логические переменные. Например:

bool A= m>7; bool B= m%3 == 1; bool C= m!= 0; // переменная m уже была объявлена где-то ранее

if ( (!A || B) || (B && C) ) { ... что-то делается }

Больше и подробнее про логические переменные и все эти переключения условий ты узнаешь дальше по ходу обучения программирования. А в качестве тренировки вот тебе задача: вводятся 3 стороны треугольника. И надо вычислить площадь по формуле герона. Если треугольник с такими сторонами не существует, тогда вывести что он не существует.

Справка

Формула Герона: S= sqrt (p*(p-a)*(p-b)*(p-c)) , где a,b,c- стороны треугольника, p- половина его периметра. Треугольник не существует если какие-то 2 его стороны в сумме меньше чем третья.

...

Есть в с++ также и более компактное и сложное условие, которое удобно применять если у тебя 1 действие. Например в случае с чётностью числа.

int x; cin >> x;

x%2==0 ? cout << "Чётное" << endl : cout << "Нечётное" << endl;

Т.е. как бы спрашивается: число x чётное? Если да, то выводим это. А иначе тогда выводим нечётное.

Синтаксис такой:

условие ? делаем это если оно истинно : делаем это если оно ложно

Работает только для 1 действия.

...

Также есть ещё достаточно удобная в некоторых случаях конструкция, если у тебя проверяются значения только 1 переменной. Например, вводим цифру, а нам выводится день недели порядковый по счёту.

int day; cin >> day;

switch (day) {

case 1:

           cout << "Понедельник" << endl;

           break;

case 2:

           cout << "Вторник" << endl;

           break;

case 3:

           cout << "Среда" << endl;

           break;

case 4:

           cout << "Четверг" << endl;

           break;

case 5:

           cout << "Пятница" << endl;

           break;

case 6:

           cout << "Суббота" << endl;

           break;

case 7:

           cout << "Воскресенье" << endl;

           break;

default:

           cout << "Нет такого дня недели!" << endl;

           break;

}

На этом примере ты можешь лицезреть синтаксис. default- это все остальные значения, эта вещь может отсутствовать, т.е. она необязательна.

Мы можем то же самое сделать и c операторами if, else:

if (day==1)                    cout << "Понедельник" << endl;

else if (day==2)          cout << "Вторник" << endl;

else if (day==3)          cout << "Среда" << endl;

else if (day==4)          cout << "Четверг" << endl;

else if (day==5)          cout << "Пятница" << endl;       

else if (day==6)            cout << "Суббота" << endl;

else if (day==7)          cout << "Воскресенье" << endl;

else                            cout << "Нет такого дня недели!" << endl;

 

Задача

Предположим, мы продаём яблоки по цене 70 руб за кг. Если стоимость покупки превышает 500 руб, тогда даём 5% скидку. Если превышает 2000 руб, то скидка уже 10%. И программа такова: вводим сколько кг мы хотим купить, а нам выводится цена. Один из вариантов решения в конце главы.

...

Теперь посмотрим как поделиться той программой, которую ты написал. Например, ты написал решатель квадратных уравнений, назвал "Реквур 1.0" и хочешь чтобы на других компьютерах люди тоже могли им пользоваться. В среде visual studio 2012 это происходит так:

1. Само создание приложения: нужно зайти в Project -> Properties (свойства) -> Configuration Properties -> General -> Project Defaults -> Use of MFC -> и выбрать использовать в статической библиотеке (Use MFC in a Static Library)

И сохранить сделанные изменения. Далее обычная отладка. В результате создастся exe файл, но весит он не 70-200кб как раньше, а 1 мегабайт, примерно в 10 раз больше.

Если этого не сделать, то будет выходить ошибка 0xc000007b

Всё, это все действия со стороны программиста.

+ ещё если не компилируется (а лучше всегда так делать) - выбираем в свойствах опять же: Linker -> System -> Subsystem -> и выбрать CONSOLE что-то там (это первая вроде вкладка)

2. Со стороны пользователя (на новом компьютере)

+ установить Microsofi Visual c++ redistributable 2012 года, это нужно для работы всех программ написанных на с++ и в этой среде. можно и 64 бит и все битные версии какие есть на всякий случай. Установщик скачивается с официального сайта майкрософт и весит около 10мб.

+ скачать требуемые dll файлы (часто msvcp110d.dll и msvcr110d.dll - это было у меня и у других видел) - скачать просто поиск в яндексе или гугле с сайта какого-нибудь, можно опять же и 32 бит и 64 на всякий случай. Весят они обычно очень мало, меньше мегабайта.

Далее скачанные 32-битные dll файлы помещаются в папку system32 (она находится в папке windows на диске С), а 64-битные- в аналогичную папку sysWOW64.

Все эти вспомогательные файлы можно также скачать тут:

https://yadi.sk/d/RtS_BKjv3GnDo4

С devcpp вопрос тоже был решён- я проверял на старом компьютере и ещё одном чужом компьютере, где не установлено никаких сред программирования вообще: всё работает. Тут не нужно вообще проводить каких-то дополнительных действий: просто обычная компиляция. Возможно потребуются какие-то dll файлы, но их можно докачать- я с таким на текущий момент написания этих строк пока не сталкивался. Хотя на компьютере ещё другого парня не работало то что скомпилировано в devcpp, но работало то что через visual studio 2012 и он мне не смог прислать скриншот из-за своей некомпетентности, так что тут точно до конца ещё не понятно что именно должно быть установлено на компьютере чтобы работали программы, скомпилированные на devcpp. Но два других опыта показали положительный результат- поэтому решусь сделать вывод что чтобы программы, скомпилированные в devcpp, работали на других компьютерах- программисту не нужно делать никаких ухищрений, т.е. сам файл 1800-1900 кб который получается на выходе- должен запускаться и работать сам по себе.

..... ..... ..... .....

РЕШЕНИЯ ЗАДАЧ (ПРОГРАММЫ) ИЗ ГЛАВЫ

// Квур с комплексными числами

#include <iostream>

#include <cmath>

using namespace std;

int main () {

           setlocale(0,"");

           double a,b,c,d,A,B,x1,x2;

           cout << "Введите старший член: "; cin>>a;

           cout << "Введите средний член: "; cin>>b;

           cout << "Введите свободный член: "; cin>>c;

           if(a==0) cout << "Уравнение не квадратное!" << endl;

           else {

                           cout << endl;

                           d= b*b-4*a*c;

                           if (d<0) {

                                           A= -b/(2*a);

                                           B= sqrt(-d)/(2*a);

                                           cout << "x1= " << A << " + " << B << " * i" << endl;

                                           cout << "x2= " << A << " - " << B << " * i" << endl;

                           }

                           if (d==0) cout << "x= " << (0-b)/(2*a) << endl;

                           if (d>0) {

                                           x1= (-b - sqrt(d))/(2*a);

                                           x2= (-b + sqrt(d))/(2*a);

                                           // эти усложнения нужны чтобы не выводилось -0 (минус ноль)

                                           if (x1==0) cout << "x1= " << 0 << endl;

                                           else cout << "x1= " << x1 << endl;

                                           if (x2==0) cout << "x2= " << 0 << endl;

                                           else cout << "x2= " << x2 << endl;

                           }

           }

           cout << endl;

           system("pause");

}

 

// С формулой герона

#include <iostream>

#include <cmath>

using namespace std;

int main () {

           setlocale(0,"");

           cout << "Введите стороны треугольника:" << endl;

           double a,b,c; cin>>a>>b>>c;

           if ( (a+b<c) || (a+c<b) || (b+c<a) ) cout << "Треугольник с такими сторонами не существует!" << endl;

           else {

                           double p= (a+b+c)/2;

                           cout << "Площадь: " << sqrt(p*(p-a)*(p-b)*(p-c)) << endl;

           }

           system("pause");

}

 

// Со скидками

#include <iostream>

using namespace std;

int main () {

           cout << "kilos: "; double q; cin >> q;

           if (70*q < 500) cout << "Price: " << 70*q << endl;

           if ( (70*q >= 500) && (70*q<2000) ) cout << "Price: " << 70*q*0.95 << endl;

           if (70*q >= 2000) cout << "Price: " << 70*q*0.9 << endl;

           system("pause");

}

 

Циклы и повторения

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

Например, мы хотим сосчитать такую сумму: 1*1 + 2*2 + 3*3 + ... + 100*100. Есть ручные методы вычисления таких сумм, но программирование и современные компьютеры позволяют нам всё упрощать и считать миллионы однотипных операций в секунду.

Стандартно это считается так:

1. Создаётся переменная-накопитель с начальным значением. В случае суммы это 0, в случае произведения- 1.

2. В цикле к ней добавляются значения.

3. Полученный результат используется там где нужно.

Ниже мы сосчитаем эту сумму таким кодом:

int sum= 0;

for (int i=1; i<=100; i++) sum+= i*i;

cout << sum << endl;

Каждая строчка тут- это каждый шаг. Создали переменную sum и присвоили ей 0. Далее в неё мы будем прибавлять в цикле i*i, где i принимает поочерёдно значения от 1 до 100. Цикл реализуется через оператор for. Для от i=1 при i<=100 с прибавлением к i единицы делаем sum= sum + i*i; i++ это всё равно что i= i+1 или i+=1. Также есть уменьшение на единицу: i--

Далее на примерах будет ещё более понятно:

1. Сосчитать степень a^n, где n- натуральное число. Например, 2^4= 2*2*2*2.

double res= 1;

for (int i=1; i<=n; i++) res*= a;

cout << res << endl;

2. Повторить вывод слова "Привет" 20 раз подряд.

for(int i=1; i<=20; i++) cout << "Привет";

3. Сосчитать n! (факториал). Например, 5!= 1*2*3*4*5.

int res=1;

for (int i=1; i<=n; i++) res*= i;

cout << res << endl;

Ещё раз посмотри эти примеры выше и прокрути в голове алгоритм. Посмотрим на примере факториала. Сначала res=1. Далее res= res*1=1, тут i=1. Второй проход цикла: res= res*2= 2, тут i=2. i- это итератор, переменная которая показывает сколько раз нужно сделать типовое действие.

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

cout << "Сколько чисел перемножаем: "; int n; cin >> n;

double number, res= 1;

for (int i=1; i<=n; i++) {

           cin >> number;

           res*= number;

}

cout << res << endl;

Оператор for мы используем, когда точно знаем количество итераций. Если количество итераций неизвестно или бесконечно, то используем цикл while.

Можно переписать вычисление факториала с использованием цикла while:

int res= 1; int i=1;

while (i<=n) {

           res*= i;

           i++;

}

Пока i меньше или равно n, делаем- и в фигурных скобках описывается что нужно делать. Умножать результат на итератор, а потом этот итератор инкрементировать, т.е. прибавлять к нему единицу. Как только условие i<=n перестанет выполняться, цикл прекратится.

Рассмотрим для примера алгоритм Евклида- нахождение наибольшего общего делителя (НОД). Предположим, есть 2 числа: 108 и 48. Ищется остаток 108%48= 12. Далее 48%12= 0. И вот как мы только получаем 0, значит последнее число (12)- и будет НОД.

int x, y; cin >> x >> y; // исходные числа

int z; // вспомогательная переменная чтобы не терялись промежуточные значения

while (y!=0) {

           z= x; x= y; y= z%y;

}

cout << x << endl;

На примере выше: x=108, y=48. Т.к. 48!=0, то входим в цикл. z=108, x=48, y= 108%48=12. Теперь x= 48, y=12. 12!=0, поэтому цикл продолжается. z= 48, x= 12, y= 48%12=0. Теперь x=12, y=0. Т.к. 0!=0 уже не соблюдается, то цикл прекращается. И мы выводим 12.

...

Ниже будут примеры задач для тренировки:

1. Сосчитать сумму 3*4 + 4*5 + 5*6 + ... + 100*101

2. Вывести таблицу квадратов до 100 в таком виде:

1 --> 1

2 --> 4

3 --> 9

и так далее..

3. Сосчитать n-е число фибоначчи. Эти числа задаются так: каждое следующее число является суммой двух предыдущих. Начинается всё с 1 и 1. Т.е. 3-е число будет 1+1= 2, четвёртое 1+2= 3 и так далее.

4. Вводятся числа. Когда ввели 0, то ввод заканчивается. Нужно сосчитать сумму и произведение всех полученных чисел.

5. Вводятся слова "муся", "дуся". Если мы ввели "муся", то прибавляется 1 балл в некую переменную для муси, для дуси аналогично. Окончание ввода происходит, когда у кого-нибудь набралось больше 10 баллов. В конце выводится положительная разность этих баллов.

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

...

В с++ также есть третий вид цикла: do while. Здесь процесс выполняется минимум 1 раз, а затем, если некое условие соблюдается, то цикл продолжается. Это можно использовать для выдачи ошибок. Например, мы вводим количество рабочих часов и программа выводит процент этого рабочего времени от суток. Если мы введём больше 24 часов, программа попросит ввести опять и продолжится дальше только если мы введём правильное время. Это может выглядеть так:

double hours;

do {

           cout << "Кол-во рабочих часов: "; cin >> hours;

           if ( (hours > 24) || (hours < 0) ) cout << "Ошибка! Неправильный ввод времени!\n";

} while ( (hours > 24) || (hours < 0) );

cout << hours*100/24 << "%\n";

Это называется цикл с постусловием. Обычный while- с предусловием.

...

Также есть бесконечный цикл- его можно использовать чтобы программа повторялась всегда, чтобы заново не запускать. Например, квадратное уравнение. Можно всё вовлечь в бесконечный цикл и после одного решённого уравнения программа предложит решить другое вместо того чтобы закрываться. Реализуется это через while(true), т.е. пока истина, т.е. всегда.

while(true) { ПРОГРАММА }

Также бесконечный цикл можно использовать для тестирования определённых кусков программы. Вместо while(true) можно написать while(1). В начале цикла (программы) можно сделать очистку экрана командой system("CLS");

Можно сделать цикл не бесконечный, а просить нажать определённую цифру чтобы был повтор. Например так:

int k=0;

while(k==0) {

           ПРОГРАММА

           cout << "Повторить программу? Жми 0 для этого. Иначе- любое другое число\n";

           cin >> k;

}

Либо можно реализовать это через цикл do while:

int k;

do {

           ПРОГРАММА

           cout << "(0) Повтор\nВыйти- любое другое число\n";

           cin >> k;

} while (k==0);

 

Для более сжатой записи существуют операции инкремента (увеличения переменной на единицу) и декремента (уменьшения на единицу)

Есть 2 вариации: до и после.

i++ тут сначала делается действие с этой переменной, а потом она увеличивается

++i сначала прибавление, а потом действие

Например: int i=3; i= i++ + ++i. Что тут будет?

Сначала i=3. Вместо ++i будет 4, т.к. при такой записи сначала происходит инкремент. Далее 3+4=7. А теперь и инкрементируется из-за плюсов после первого слагаемого: 8. С минусами всё аналогично: есть --i и i--

Можно использовать эти плюсы даже в самих операциях, например возьмём факториал:

int res= 1; int i=1;

while (i<=n) {

           res*= i;

           i++;

}

Можно записать это короче:

int res= 1; int i=1;

while (i <= n) {

           res*= i++;

}

или так:

int res= 1; int i=1;

while (++i <= n) {

           res*= i;

}

 

И напоследок посмотрим ещё раз for. Там есть 3 компонента- начальная точка, конечная и на сколько прибавляется.

Например, для вычисления суммы 3+5+7+...+103 можно использовать цикл:

int sum= 0;

for (int i=3; i<=103; i+=2) sum+= i;

Тут начинается с 3 и дальше по +2 меняется i. Т.е. при первом проходе i=3, далее i=5, 7, 9 и т.д. до тех пор пока не достигнет 103. Точнее, цикл выполняется пока i<=103, как и прописано. А само изменение i прописано в i+=2.

Для пропуска каких-то отдельных значений можно использовать оператор continue. Например, мы складываем 1+2+4+5+7+8+...+100, т.е. пропускаем числа, которые делятся на 3. Тогда это запишется так:

int sum= 0;

for (int i=1; i<=100; i++) {

           if (i%3==0) continue;

           sum+= i;

}

Для преждевременного выхода из цикла используется оператор break. Это мы уже видели в условиях switch-case. Здесь мне трудно подобрать пример, т.к. используется реже остального. Например, мы складываем 1+2+3...+1000 и если суммарное число будет заканчиваться на 45, тогда мы выйдем из цикла. Либо такого не произойдёт и мы просуммируем все 1000. Последняя цифра числа x находится как остаток при делении на 10, последние 2 цифры- остаток при делении на 100 и так далее.

int sum=0;

for (int i=1; i<=1000; i++) {

           if (sum%100==45) break;

           sum+= i;

}

 

...

Решения всех задач в главе (полные программы)

// сумма

#include <iostream>

using namespace std;

int main() {

           int sum= 0;

           for (int i=3; i<=100; i++) sum+= i*(i+1);

           cout << sum << endl;

           system("pause");

}

 

// таблица квадратов

#include <iostream>

using namespace std;

int main() {

           for (int i=1; i<=100; i++) cout << i << " --> " << i*i << endl;

           system("pause");

}

 

// фибоначчи

#include <iostream>

using namespace std;

int main() {

           int n; cin >> n;

           int a= 1, b= 1, c;

           if( (n==1) || (n==2) ) cout << 1 << endl;

           if (n >= 3) {

                           for (int i=3; i<=n; i++) {

                                           c= a; a= b; b= c+a;

                           }

                           cout << b << endl;

}

           system("pause");

}

 

// сумма и произведение (задача 4)

#include <iostream>

using namespace std;

int main() {

           setlocale(0,"");

           double sum= 0, prod= 1, number=2; // вместо 2 м.б. любое число кроме нуля

           while(number!=0) {

                           cin >> number;

                           sum+= number;

                           if(number!=0) prod*= number;

           }

           cout << "Сумма: " << sum << "\nПроизведение: " << prod << endl;

           system("pause");

}

 

// муся и дуся

#include <iostream>

#include <string>

#include <Windows.h>

#include <cmath>

using namespace std;

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           int musya=0, dusya= 0; string s;

           bool pass= true;

           while (pass) {

                           cin >> s;

                           if (s=="муся") musya++;

                           if (s=="дуся") dusya++;

                           if ( (musya>=10) || (dusya>=10) ) pass= false;

           }

           cout << abs (musya-dusya) << endl;

           system("pause");

}

Эту последнюю задачу можно было решить иначе, но я специально добавил тут новые ранее не упоминавшиеся в книге инструменты программирования, чтобы расширить твой запас эрудиции. Тут ты можешь видеть как можно использовать логические переменные (bool) , а также функцию abs- это модуль числа. Для её работы нужно подключить библиотеку cmath. Например, abs(5)= 5, abs(-8)= 8, abs(0)= 0.

Ниже пример с уже упоминавшимися в книге инструментами:

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           int musya=0, dusya= 0; string s;

           while ( (musya<10) && (dusya<10) ) {

                           cin >> s;

                           if (s=="муся") musya++;

                           if (s=="дуся") dusya++;

           }

           if(musya-dusya>=0) cout << (musya-dusya) << endl;

           if(musya-dusya<0) cout << (dusya-musya) << endl;

           system("pause");

}

 

Случайные числа

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

Чтобы использовать случайные числа в языке с++ нам нужно подключить библиотеки <cstdlib> и <ctime>. Для генерации случайного целого числа от 0 до n-1 используем команду: rand()%n;

Функция rand() создаёт случайное число от 0 до некого максимального, которое не может быть меньше 32767. Обычно максимальное и равно 32767, но может быть и больше. Чтобы узнать чему оно равно, можно вывести cout << RAND_MAX << endl; Мы же считаем остаток от этого числа. Если нам нужно 3 случайных возможных числа (0, 1, 2), то могут быть такие 3 остатка при делении на 3 некого большого случайного числа.

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

Делая некое отступление, скажу что чтобы число было случайным, а не псевдослучайным, нужен источник энтропии (хаоса)- т.е. что-то практически точно не предсказуемое в конкретной точке: колебания токов, тепловой шум, радиоактивный распад и т.д. и т.п.

Например, нам нужно случайное число от 0 до 100. Это запишется так:

#include <iostream>

#include <cstdlib>

#include <ctime>

using namespace std;

int main() {

           srand(time(NULL)); // обнуление времени для того чтобы числа были разными при каждом новом запуске программы

           cout << rand()%101 << endl;

           system("pause");

}

Обнуление времени достаточно использовать 1 раз в программе в самом начале. Т.е. строчка srand(time(NULL)); пишется 1 раз и всё, больше её писать не нужно- можно спокойно генерировать сколько угодно случайных чисел через функцию rand().

Чтобы сгенерировать случайные числа от a до b включительно, пишем так: rand()%(b-a+1) + a. Т.е. мы генерируем случайное число от 0 до b-a, а далее прибавляем a для смещения промежутка в от a до b. Тут b-a+1 это количество всех целых чисел в промежутке от a до b.

Например, нам нужно случайное трёхзначное число. Трёхзначное- это от 100 до 999, поэтому запишем так: int x= rand()%900 + 100;

Ах да, все случайные числа генерируются в формате int, т.е. целое число.

Можно сгенерировать и дробное число особым приёмом. Например, нам нужно число от 1 до 2 с точностью 3 знака после запятой. Тогда у нас будут числа от 1000 до 2000, а потом мы просто поделим на 1000. Таким образом:

int x= rand()%1001 + 1000; double y= x; y/=1000;

Нужно помнить, что если мы делим целое число на целое, то получим целое, просто дробная часть обрежется. Поэтому если нам нужна дробь, то числитель нужно перевести в формат double.

...

Для тренировки будут следующие задачи:

Задача 1

Бросается игральный кубик (на нём 6 граней от 1 до 6- мы делаем его программную численную имитацию). Мы делаем ставку. Если выпало 1 или 2, то вся ставка сгорает. Если 3 или 4, то ставка либо растёт на 10%, либо падает на 10%. Если выпало 5 или 6, то ставка удваивается. Ставка вводится, потом показывается сколько выпало и какая стала ставка.

Задача 2

Есть 3 кошки- вбиваются их имена. Затем программа выдаёт случайным образом имя какой-нибудь кошки.

Задача 3

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

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

...

Посмотрим теперь как генерировать несколько случайных чисел. Например, мы бросаем 10 кубиков, где грань от 1 до 6. И выведем номера всех кубиков:

int kub;

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

           kub= rand()%6 + 1;

           cout << kub << " ";

}

cout << endl;

Можно также посчитать сумму баллов случайно брошенных 10 кубиков:

int kub, sum=0, i;

for (i=1; i<=10; i++) {

           kub= rand()%6 + 1;

           sum+= kub;

}

cout << sum << endl;

Но что делать, если нам нужны случайные числа, которые не будут повторяться?Например при имитации вытягивания игральных карт, определения трёх победителей или сортировки данных в случайном порядке. Тут алгоритм будет чуточку сложнее. Для его рассмотрения нужно ещё изучить массивы и функции, поэтому мы вернёмся к нему позже.

Случайные числа могут быть и отрицательными. Например, нужны случайные числа от -9 до +9. Тогда у нас a= -9, b= 9 и всё как обычно: rand()%19 - 9;

...

Решения задач выше

// Первая

#include <iostream>

#include <cstdlib>

#include <ctime>

using namespace std;

int main() {

           setlocale(0,"");

           srand(time(NULL));

           int kub= rand()%6 + 1; double stavka; cin >> stavka;

           if ( (kub==1) || (kub==2) ) {

                           stavka= 0;

                           cout << "Ставка сгорела!" << endl;

           }

           if ( (kub==3) || (kub==4) ) {

                           int y= rand()%2;

                           if (y==0) {

                                            stavka*= 1.1;

                                           cout << "Ставка увеличилась на 10%" << endl;

                           }

                           if (y==1) {

                                           stavka*= 0.9;

                                           cout << "Ставка уменьшилась на 10%" << endl;

                           }

           }

           if ( (kub==5) || (kub==6) ) {

                           stavka*= 2;

                           cout << "Ставка удвоилась!" << endl;

           }

           cout << "Ставка стала равна: " << stavka << endl;

           system("pause");

}

// вторая

#include <iostream>

#include <cstdlib>

#include <ctime>

#include <Windows.h>

#include <string>

using namespace std;

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           srand(time(NULL));

           string a,b,c;

           for(int i=1; i<=3; i++) {

                           cout << "Введите имя кошки: ";

                           if (i==1) cin >> a;

if (i==2) cin >> b;

                           if (i==3) cin >> c;

           }

           int r= rand()%3;

           if (r==0) cout << a << endl;

           if (r==1) cout << b << endl;

           if (r==2) cout << c << endl;

           system("pause");

}

// третья

#include <iostream>

#include <cstdlib>

#include <ctime>

using namespace std;

int main() {

           srand(time(NULL));

           int x;

           do {

                           x= rand()%90 + 10;

           } while ( (x%10==5) || (x%10==7) );

           cout << x;

           system("pause");

}

 

 

Дополнения про ввод-вывод

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

Для считывания символа используется cin.get(); Иногда эту команду используют вместо system("pause"); Считывание символа запрашивается, чтобы программа не вылетала. Но работает не всегда. В devcpp испытание прошло успешно- после вывода текста не вылетает. Если вылет всё равно происходит, некоторые авторы советуют использовать cin.get несколько раз подряд, но это уже муть получается.

Также есть более убогий инструмент, который советуют некоторые авторы, но про который тоже стоит упомянуть: подключить <conio.h> и использовать _getch(); Если cin.get требует нажатия enter, то в _getch вылет происходит если ты нажмёшь хотя бы 1 клавишу (введёшь хотя бы 1 символ)

"\b" перемещение указателя ввода назад на 1 символ. Можешь попробовать ввести cout << "Privet\b"; и посмотреть. Указатель ввода будет после Prive. И если ввести что-то ещё, то символ t (последний) затрётся. Чтобы сдвинуть указатель (каретку) в самое начало, вместо \b используется \r (эти команды типа \b, \r, \n и др. называются escape-последовательностями)

"\a" короткий звуковой сигнал, может использоваться для привлечения внимания к чему-то конкретному.

Команды типа endl называются манипуляторами потока. Есть и другие. Например, перевод в другую систему счисления: oct- в восьмеричную, hex- в шестнадцатеричную.

Пример: cout << oct << 19; Выведется 23 (2*8+3, т.е. это 19 в восьмеричной системе)

Чтобы записать число в 16-ричную систему, мы сначала должны написать 0х. Например, число 1F (31 в десятичной) нужно записать как 0х1F, тогда оно будет считываться потоком вывода. Для перевода в десятичную делаем так: cout << dec << 0x1F;

Аналогично, для восприятия восьмеричного числа сначала нужно поставить 0. Например, то же 23 мы переводим в десятичную из восьмеричной так: cout << dec << 023;

Для вывода этих 0 и 0x при переводе в hex и oct используем showbase: cout << showbase << hex << 42; Выведется 0х2a.

Чтобы наоборот не выводить 0 и 0x там где они выводятся, используем noshowbase.

...

При выводе вещественных чисел обычно используется 6 знаков после запятой. Например:

double x; cout << x/3; Выведется 0.333333.

Мы можем увеличить или уменьшить эту точность с помощью манипулятора setprecision(). Для его работы необходимо подключить <iomanip>

Пример: double x; cout << setprecision(8) << x/3; Выведется уже 8 троек после запятой. Если вместо 8 указать число гораздо больше, например 76, то будет уже погрешность: 0.333333333333333314829616256247390992939472198486328125. Точнее, это даже не погрешность. Цитата из справочника: "Если число слишком велико, оно автоматически отображается в научном формате, и тогда точность задаёт количество цифр в мантиссе".

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

 

Математические функции

Благодаря подключению библиотеки <cmath> мы можем использовать встроенные математические функции, такие как степень, логарифм, синус и прочие. Мы уже встречались с этим ранее для вычисления корня (sqrt) и модуля (abs).

1. Арифметический корень: sqrt(x)

2. Модуль (абсолютное значение): abs(x)

3. Степень a^n: pow(a,n). Работает и для дробных степеней. Отрицательные числа в дробную степень не возводятся.

4. Кубический корень: cbrt(x)

5. Синус, косинус, тангенс: sin(x), cos(x), tan(x). Значения аргумента указываются в радианах.

Дополнительные тригонометрические функции можно считать через эти основные. Например, котангенс это 1/tan(x), секанс- 1/cos(x), косеканс- 1/sin(x).

6. Обратные тригонометрические функции- арксинус, арккосинус, арктангенс: asin(x), acos(x), atan(x). Ответ вычисляется в радианах.

7. Экспоненциальная степень e^x: exp(x). Тут e= 2.71828.. (число е). Само число е можно найти так: exp(1). Есть также с вычетом единицы- expm1(x). И 2 в степени х: exp2(x).

8. Натуральный логарифм: log(x). Натуральный от 1+x: log1p(x).

9. Десятичный логарифм: log10(x).

Произвольные логарифмы можно считать через деление двух одинаковых. Например, логарифм 7 по основанию 3 можно сосчитать так: log(7)/log(3). Либо через десятичный: log10(7)/log10(3). По основанию 2 есть: log2(x).

10. Гиперболические функции: sinh(x), cosh(x), tanh(x) и обратные гиперболические: asinh(x), acosh(x), atanh(x)

11. Гипотенуза: hypot(x,y), где x,y- катеты. Точнее, hypot(x,y)= sqrt(x*x+y*y).

12. Округление до ближайшего целого и возвращение в типе double: round(x). Отбрасывание дробной части: trunc(x).

13. Минимальное и максимальное: fmin(x,y), fmax(x,y).

 

Массивы

Для хранения некого числа однотипных данных вводятся т.н. массивы. Они нужны чуть ли не в любой достаточно крупной программе. С их необходимостью мы уже сталкивались, когда нужно было создать несколько случайных чисел без повтора. Также они могут быть нужны при обработке и сортировке данных, более компактном вводе-выводе и т.д. Суть массива в том, что в отличие от переменной мы можем хранить не одно значение, а сколько угодно- и всё это под одним идентификатором (именем массива). И в цикле легко обращаться последовательно к каждому элементу и что-то с ним делать.

Массивы бывают статические и динамические. Начнём со статических- в них число элементов заранее известно.

Объявляется так: int a[5]; Тут создаётся массив целых чисел из 5 элементов. a- это имя массива. Массив 7 вещественных чисел можно создать так: double mass[7]; По сути это просто резервирование памяти.

Можно задать начальные значения, например: int arr[4]= {1,2,3,4};

Либо мы можем занести значения в цикле:

int arr[5]; for (int i=0; i<=4; i++) cin >> arr[i];

Нумерация элементов массива начинается с нуля.

Можно менять отдельные ячейки: arr[3]=5; arr[2]= arr[3]; и т.д. и т.п.

...

Пример: игральный кубик подбрасывается 10 раз. Нужно вывести сколько очков было у кубика с номером N. Мы сохраним все значения в массив:

int kub[10]; srand(time(NULL));

for (int i=0; i<10; i++) kub[i]= rand()%6 + 1;

cout << "Очки какого кубика по счёту вывести: "; int N; cin >> N;

if ( (N>=1) && (N<=10) ) cout << kub[N-1];

Мы вывели N-1-й элемент, потому что в массиве нумерация с нуля. Т.е. 1-й кубик будет kub[0], второй- kub[1] и т.д. до десятого- kub[9]. А сам массив обозначается как kub[10].

Можно сложить все очки во всех кубиках:

int sum= 0;

for (int i=0; i<10; i++) sum+= kub[i];

cout << sum;

Либо можно сложить, например, только от 4-го до 8-го (в массиве это будет от 3-го до 7-го): for (int i=3; i<=7; i++) sum+= kub[i];

Можно количество элементов вписать как константу, например:

const int CubesNumber= 10;

int kub[CubesNumber];

Значения константы уже никак не будут меняться в программе. Константы могут быть и дробные типа const double pi= 3.14159265357989;

Массивы мы можем создавать не только для чисел, но и для символов, строк, логических переменных и вообще любых типов данных, которые впоследствии мы научимся создавать. Например, массив строк: string s[3]= {"строка1", "строка2", "строка3"}; Сама строка может быть представлена как массив символов, это мы уже ранее рассматривали.

Выводить сразу весь массив так: cout << m[10]; нельзя. Просто не будет работать или будет выводить белиберду вроде адресов ячеек в оперативной памяти. Нужно выводить так: for (int i=0; i<10; i++) cout << m[i] << " "; Пробелы оставляем чтобы значения элементов массива не слипались. Можно выводить каждое на новую строку: for (int i=0; i<10; i++) cout << m[i] << endl;

Копировать элементы, опять же нельзя так: int m[5]= {1,4,5,9,0}; int n[5]= m[5]; Такое тоже работать не будет. Нужно делать так: for (int i=0; i<5; i++) n[i]= m[i]; Т.е. в массивах мы делаем всё поэлементно.

...

Для тренировки ниже будут даны задачи:

Задача 1

Создаётся массив 10 вещественных чисел. Числа вводятся с консоли. Нужно вывести первое число, которое окажется больше 5. Если таких не будет, то вывести "такого числа нет"

Задача 2

Создать массив 20 случайных целых чисел от -7 до +7. Далее вывести количество положительных, отрицательных и нулевых чисел.

Задача 3

Создать массив 50 случайных целых трёхзначных чисел. Сосчитать сумму первых 25 чисел и произведение последних 25 чисел. После этого обнулить все элементы массива.

Задача 4

В некотором магазине есть товары: молоко- 55 руб, хлеб- 30 руб, гречка- 48 руб, огурцы- 37 руб, сахар- 42 руб. Нужно внести все эти товары в программу через массивы и далее вывести случайный товар.

Задача 5

Внесите в массив последовательность чисел 1,4,7... и т.д. 100 элементов через реккурентное соотношения. Далее вывести кумулятивные суммы этих элементов, т.е. вывести 100 элементов нового массива: 1, 1+4, 1+4+7 и т.д.

...

Рассмотрим теперь динамические массивы- в них количество переменных изначально неизвестно и задаётся переменной. Пример объявления массива целых чисел с n элементами: int *m= new int [n]; Тут m-имя массива. Звёздочка обязательна при объявлении. Далее она не нужна. На самом деле это называется указатель, но об этом потом. Ненужный уже массив можно удалять, чтобы он не занимал лишнюю память: delete []m;

Пример: рассмотрим задачу сортировки массива- это типовая задача. На самом деле есть много различных алгоритмов сортировки, но мы рассмотрим самый интуитивно понятный. Есть массив целых (или вещественных) чисел с n элементами. Можно это сделать так:

int n= rand()%90 + 10; int *mass= new int [n]; for (int i=0; i<n; i++) mass[i]= rand()%90+10;

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

Вначале создаётся переменная, где у нас будет максимальный. Условно занесём туда первый элемент массива. Далее пойдём по элементам массива в цикле и если встретим какой-то элемент который больше нашего условного максимального, то теперь этот элемент станет новым максимальным:

int max= mass[0];

for (int i=1; i<n; i++) {

           if (mass[i] > max) max= mass[i];

}

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

Позиция ищется просто:

int max= mass[0]; int poz= 0;

for (int i=1; i<n; i++) {

           if (mass[i] > max) {

                           max= mass[i];

                           poz= i;

           }

}

А теперь всё в цикле, т.е. полная сортировка массива:

int max, poz, buff;

for (int K=0; K< n-1; K++) {

max= mass[K]; int poz= K;

for (int i=K+1; i<n; i++) {

           if (mass[i] > max) {

                           max= mass[i];

                           poz= i;

           }

}

buff= mass[K];

mass[K]= max;

mass[poz]= buff;

}

И всё, теперь массив mass у нас отсортированный по убыванию. Для закрепления попробуй сам отсортировать произвольный массив по возрастанию, т.е. от меньшего к большему.

...

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

Пример объявления двумерного массива 2х3 целых чисел: int mass[2][3];

Вводить и выводить элементы придётся в двойном цикле:

for(int i=0; i<2; i++) for (int j=0; j<3; j++) cin >> mass[i][j];

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

           for(int j=0; j<3; j++) cout << mass[i][j] << " ";

           cout << endl;

}

Тут можно понять, что у нас на каждой строчке 3 элемента и всего 2 строчки. Т.е. у нас в массиве 2х3 имеется 2 строки и 3 столбца. Нумерация как в обычной векторной алгебре, только вместо первого элемента нулевой и т.п. сдвиг. Для удобства можно делать на 1 расширение больше, чтобы нумерация была с первого, а нулевые по номеру элементы просто будут нетронуто пустовать. Т.е. создать вместо int mass[2][3] массив mass[3][4] и в цикле вбивать с 1 по 2 и с 1 по 3.

Двумерный массив также называют матрицей.

Ввод-вывод можно производить и без циклов, например: cin >> mass[1][2]; cout << mass[0][2]; и т.д. и т.п.

Также можно внести значения при объявлении: int mass[2][3]= { {5,-6,0}, {2,1,2} };

Аналогично можно использовать трёхмерные, четырёхмерные и т.д. массивы. Вот пример объявления пятимерного массива: double fiver[4][3][5][3][2]; Но обычно дальше третьего измерения на практике не используется, ибо незачем.

Пример задачи: есть двумерный массив 5х5. Нужно посчитать сумму всех элементов на главной диагонали. Главная идёт с левого верхнего угла в правый нижний. Это значит что у нас будут элементы 00, 11, 22 и т.д. Это можно понять, если нарисовать таблицу.

Итак, пусть у нас есть mass[5][5] с заполненными элементами. Тогда нужную суму мы посчитаем так:

int sum=0;

for(int i=0; i<5; i++) for(int j=0; j<5; j++) {

           if (i==j) sum+= mass[i][j];

}

Вот и всё. Для побочной диагонали меняется лишь условие: if (i == 4-j)

Перевод двумерного массива matrix[m][n] в одномерный arr[m*n]:

for (int i=0; i<m*n; i++) arr[i]= mass[i/n][i%n];

...

Решения

// Первая

#include <iostream>

using namespace std;

int main() {

           setlocale(0,"");

           double numbers[10];

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

                           cout << "Введите " << i+1 << "-е число: ";

                           cin >> numbers[i];

           }

           double x=4; // м.б. любое число меньше 5

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

                           if (numbers[i] > 5) {

                                           x= numbers[i];

                                           break;

                           }

           }

          if (x==4) cout << "такого числа нет" << endl;

           else cout << x << endl;

           system("pause");

}

 

// Вторая

#include <iostream>

#include <cstdlib>

#include <ctime>

#include <string>

using namespace std;

int main() {

           setlocale(0,"");

           srand(time(NULL));

           int a[20]; int sign[3]= {0,0,0};

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

                           a[i]= rand()%15 - 7;

                           if (a[i]<0) sign[0]++;

                           if (a[i]==0) sign[1]++;

                           if (a[i]>0) sign[2]++;

           }

           string signname[3]= {"(-)","(0)","(+)"};

           for (int i=0; i<3; i++) cout << signname[i] << " " << sign[i] << endl;

           system("pause");

}

// Третья

#include <iostream>

#include <cstdlib>

#include <ctime>

#include <string>

using namespace std;

int main() {

           setlocale(0,"");

           srand(time(NULL));

           int x[50];

           for (int i=0; i<50; i++) x[i]= rand()%900 + 100;

           int res[2]= {0, 1};

           string Res[2]= {"Сумма: ", "Произведение: "};

           for(int i=0; i<=24; i++) res[0]+= x[i];

           for(int i=25; i<=49; i++) res[1]*= x[i];

           for(int i=0; i<2; i++) cout << Res[i] << res[i] << endl;

           system("pause");

}

// Четвёртая

#include <iostream>

#include <cstdlib>

#include <ctime>

#include <string>

using namespace std;

int main() {

           setlocale(0,"");

           srand(time(NULL));

           string name[5]= {"молоко", "хлеб", "гречка", "огурцы", "сахар"};

           int price[5]= {55,30,48,37,42};

           int i= rand()%5;

           cout << "Случайный товар: " << name[i] << " (" << price[i] << " руб)" << endl;

           system("pause");

}

 

// Пятая

#include <iostream>

using namespace std;

int main() {

           setlocale(0,"");

           int src[100], sum[100];

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

                           sum[i]= 0;

                           src[i]= 1 + 3*i;

                           for (int j=0; j<=i; j++) sum[i]+= src[j];

                           cout << sum [i] << " ";

           }

           for (int i=0; i<100; i++) src[i]= 0;

           system("pause");

}

 

Функции

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

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

Это понятие аналогично тому что в математике. Функция может иметь один, несколько аргументов или не иметь вообще. При этом функция возвращает одно значение.

Пример функции, которая считает куб числа:

double cube (double x) {

           return x*x*x;

}

double x- это аргумент, который функция принимает. return- значение, которое функция выдаст в ответе (возвращает). double cube- значит что значение куба будет возвращено в типе double.

Далее в программе мы можем много раз использовать эту функцию и просто писать cube(3), cube (a+b) и т.д. и т.п.

Все функции пишутся (объявляются) до главной main. В самой main уже чисто вызовы этих функций.

Функция может ничего не возвращать, тогда она записывается типом void. Такие функции также называются процедурами. Пример функции, которая переводит на новую строку:

void _ () { cout << endl; }

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

void _ (int k) {

           for (int i=0; i<k; i++) _();

}

Выше был продемонстрирован приём перегрузки функций. Мы использовали всё то же имя (подчёркивание). Эти 2 функции различаются количеством аргументов: первая их не имеет, вторая имеет один аргумен типа int.

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

double min (double a, double b) {

           if (a<b) return a;

           else return b;

}

double min (double a, double b, double c) {

           if ( a < min (b,c) ) return a;

           else return min(b,c);

}

double min (double a, double b, double c, double d) {

           if ( a < min (b,c,d) ) return a;

           else return min(b,c,d);

}

Можно прописать аналогичные функции для типа int. Но есть также другие численные типы: float, long long int и другие малоизвестные и редкоиспользуемые. Даже для строк можно использовать или новых созданных типов. Чтобы функция работала для любого типа, вводятся шаблоны.

template <typename Type>

// здесь Type- имя шаблонного типа данных

И далее написать функцию:

Type min (Type a, Type b) {

           if (a<b) return a;

           else return b;

}

И можно использовать дальше перегрузку. Для каждой новой функции нужно создавать новый шаблон. Называть его можно всё тем же именем.

...

Пример задачи: даны 3 стороны треугольника. Найти все углы в этом треугольнике и выразить в градусах.

Для нахождения углов по 3 сторонам мы воспользуемся теоремой косинусов:

c^2= a^2 + b^2 - 2*a*b*cos(C), где a,b,c- стороны треугольника, C- угол между сторонами a,b.

Понятно что эту теорему косинусов можно запрограммировать, а точнее- нахождение угла C через неё:

double AngleCos (double a, double b, double c) {

           return acos ( (a*a + b*b - c*c) / (2*a*b) );

}

Понятно что саму функцию умножения самого на себя (квадрат числа) тоже можно объявить:

double q2 (double x) {

           return x*x;

}

double AngleCos (double a, double b, double c) {

           return acos ( (q2(a)+q2(b)-q2(c) ) / (2*a*b) );

}

На первый взгляд это бессмысленно, но функция квадрата может быть полезна при умножения самого на себя какого-нибудь крупного выражения типа (a+b+c). Короче и красивее писать q2(a+b+c) чем (a+b+c)*(a+b+c). Или функции могут даже включать сами себя, например: q2( q2(a)+q2(b)+q2(c) ); Причём это использование друг в друге может быть многократно.

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

const double pi= 3.14159265357989;

double AngleCos (double a, double b, double c) {

           return ( acos ( (a*a + b*b - c*c) / (2*a*b) ) ) * 180 / pi;

}

Теперь получим всю программу:

#include <iostream>

#include <cmath>

using namespace std;

const double pi= 3.14159265357989;

double AngleCos (double a, double b, double c) {

           return ( acos ( (a*a + b*b - c*c) / (2*a*b) ) ) * 180 / pi;

}

int main () {

           setlocale(0,"");

           double a,b,c;

           cout << "Сторона а: "; cin >> a;

           cout << "Сторона b: "; cin >> b;

           cout << "Сторона c: "; cin >> c;

           if ( (a+b<c) || (a+c<b) || (b+c<a) ) cout << "Такого треугольника не существует!\n";

           else {

                           cout << "\nУгол А: " << AngleCos(b,c,a) << " гр";

                           cout << "\nУгол B: " << AngleCos(a,c,b) << " гр";

                           cout << "\nУгол C: " << AngleCos(a,b,c) << " гр";

           }

           cout << endl; system("pause");

}

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

Также функции позволяют легко вносить правки. Возьмём даже пример выше- если мы хотим выводить углы в радианах, нам надо лишь исправить выражение в самой функции. В функции main всё останется верно. Если б мы писали без функции, пришлось бы исправлять 3 раза и при этом тут гораздо легче ошибиться и запутаться. А если б мы использовали эту функцию не 3 раза, а 30 раз? Я об этом.

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

string chet (int x) {

           if(x%2==0) return "чётное";

           else return "нечётное";

}

Далее в главной функции можно просто писать: cout << chet(x);

...

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

Пример рекурсивного нахождения факториала:

int fact (int x) {

           if (x==0) return 1;

           else return x*fact(x-1);

}

Т.е. как он будет искать 4!= 4 * 3! = 4*3*2!= 4*3*2*1!= 4*3*2*1*0!= 4*3*2*1*1= 24. Вот пример использования рекурсии.

Следует избегать случаев, когда рекурсия используется 2 раза. Например в числах фибоначчи:

int fib (int x) {

           if ( (x==1) || (x==2) ) return 1;

           else return fib(x-1) + fib (x-2);

}

Для маленьких чисел алгоритм работает неплохо. Но если тебе нужно будет вычислить, скажем fib(50), то программа заглохнет. Нужно вычислить fib(49) и fib(48). Для каждого из них надо вычислить ещё 2 раза, т.е. уже 4. И время работы алгоритма растёт по экспоненте, т.е. пока сумма дойдёт чисто до кусков из fib(2) и fib(1), нужно будет выполнить 2^48 операций как минимум. Поэтому в таких случаях склоняются к нерекурсивным алгоритмам.

Ещё пример: рекурсивный алгоритм нахождения НОД:

int nod (int x, int y) {

           if (y==0) return x;

           else return nod (y, x%y);

}

Тут по умолчанию принято что оба числа вначале отличны от нуля.

...

Как занести массив в аргумент функции? Например, нам нужно вычислить скалярное произведение двух произвольной, но одинаковой размерности векторов. Скалярное произведение- это почленное умножение координат и затем сложение всех произведений. Например, для 2 векторов (1,2,3) и (4,5,6) скалярным произведением будет 1*4 + 2*5 + 3*6 = 32.

Массив в аргумент записывается так mass[], где mass- имя массива. При этом нужно отдельным аргументом указать его размерность.

template < typename Type >

Type scalar (Type a[], Type b[], int size) {

           Type res= 0;

           for (int i=0; i<size; i++) res+= a[i]*b[i];

           return res;
}

А вот пример функции, которая вычисляет модуль вектора:

template < typename Type >

double module (Type a[], int size) {

           double res= 0;

           for (int i=0; i<size; i++) res+= a[i]*a[i];

           return sqrt(res);
}

А что делать, если мы хотим возвращать сам массив. Например, сумму 2 векторов. Тогда мы пишем со звёздочкой. Пример:

template < typename Type >

Type *ArraySum (Type a[], Type b[], int size) {

           Type *c= new Type [size]; // массив для суммы

           for (int i=0; i<size; i++) c[i]= a[i] + b[i];

           return c;

}

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

Далее в главной функции мы можем создавать уже массив проще: предположим, что массивы целых чисел x, y и размерностью 10 у нас уже есть. Тогда: int *z= ArraySum(x,y,10); вот уже у нас и есть массив из суммы. Мы можем сделать даже так:

int *z= ArraySum ( ArraySum(x,y,10), ArraySum(x,x,10), 10 ); т.е. более сложные комбинированные варианты.

Для сортировки массива мы можем создать тупо процедуру. Пример:

void ArraySorte (int mass[], int n) {

           int max, poz, buff;

for (int K=0; K< n-1; K++) {

max= mass[K]; int poz= K;

for (int i=K+1; i<n; i++) {

           if (mass[i] > max) {

                           max= mass[i];

                           poz= i;

           }

}

buff= mass[K];

mass[K]= max;

mass[poz]= buff;

}

}

Т.е. исходный массив заменился на новый, отсортированный- под тем же именем mass.

...

Иногда нам нужны функции, которые будут изменять сами переменные, а не их копии. Например, у нас игра и происходит сражение, где отнимаются очки жизни (hit points, hp). Пусть нас ударяют на 10 hp с вероятностью 33% (1/3). Тогда аргумент нужно писать со знаком амперсент (&) запишем функцию:

void battle (int &hp) {

           int r= rand()%3;

           if (r==0) hp-=10;

}

И в главной функции далее мы уже можем использовать вызов: battle(hp); Вместо hp может быть другое имя- запоминаются всё равно прототипы. Например, у нас в главной функции будет hitpoints. Тогда battle(hitpoints) тоже будет работать.

Для массива амперсент не нужен- он так и меняется автоматически.

Функции могут быть логическими: пример- является ли число положительным?

template <typename Number>

bool pozitive (Number x) {

           if (x>0) return true;

           else return false;

}

В главной функции можно уже это использовать в условиях, например: if (pozitive(x)) или с отрицанием: if (!pozitive(x)). Или даже в составе выражений: if ( (pozitive(x)) && (x<2) ).

...

Ну и напоследок- функция, которая выдаёт случайные числа без повтора:

int Rand(int a, int b, int k, int i) {

           srand(time(NULL));

           int *x= new int [k];

           x[0]= rand()%(b-a+1)+a;

           if(k>1) {

                           for(int i=1;i<k;i++) {

                                           int K=0;

                                           do {

                                                           K=0;

                                                           x[i]= rand()%(b-a+1)+a;

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

                                                                           if(x[i]==x[j]) K++;

                                                           }

                                           } while(K>0);

                           }

           }

           return x[i];

}

Тут мы ищем k случайных чисел на промежутке от a до b включительно. В главной функции вызов происходит так:

for(int i=0;i<k;i++) cout<<Rand(a,b,k,i)<<" ";

Благодаря srand(time(NULL)) в начале функции, мы получаем действительно единый массив случайных без повтора чисел, т.к. всё выровнено по единому времени.

...

Задача 1

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

Задача 2

Написать функцию, которая увеличивает число в 2 раза. Далее применить это 6 раз в программе, вывести начальное значение для типов int и double. Использовать шаблоны.

Задача 3

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

Задача 4

Написать функцию, которая нормирует массив. Т.е. вместо исходного массива будет массив, где каждый элемент поделен на модуль массива (вектора)

Задача 5

Написать функцию, которая синтезирует массив с элементами 1,2,3,..., n-1, n. Также добавить перегруженные функции, которые синтезируют только среднию часть, например от k1 до k2 или только правую часть где k- граничный элемент слева.

Задача 6

Написать функцию, которая выводит n-ю букву в слове. Размер слова пока не учитывать.

Задача 7

Написать программу, которая проверяет правильность введённого пароля. Встроенный пароль "муся". Если пароль верный, вывести секретное число: 123454321. Если нет, то вывод что пароль неверный. Использовать функцию проверки пароля и предустановленные как константы пароль и секретное число. Пароли вводятся до тех пор, пока не будет правильный. После ввода правильного пароля и вывода секретного числа программа завершается.

...

Бонус: дизайновые функции

// переход на новую строку

void _() { cout << endl; }

// переход на новую строку k раз

void _(int k) { for(int I=0; I<k; I++) cout<<endl; }

// пауза или очистка экрана; 0- очистка, 1- пауза, 2- пауза+очистка

void __(int k) {

           if(k==0) system("CLS");

           if(k==1) {

                           _(); system("pause");

           }

           if(k==2) {

                           __(1); __(0);

           }

}

Для windows 8 консоль имеет фиксированную ширину, в соответствии с чем разработаны также функции ниже. Консоль в windows 10 уже можно растягивать, делая уже или шире.

// подчёркнутая линия на всю длину консоли

void ___() {

           _(); cout << "________________________________________________________________________________"; _();

}

// подчёркнутая линия на всю длину консоли n раз

void ___(int n) {

           _(); for(int i=0;i<n;i++) cout << "________________________________________________________________________________" << endl;

}

 

Решение 1

template <typename Type>

double average (Type a[], int size) {

           double sum= 0;

           for (int i=0; i<size; i++) sum+= a[i];

           return sum/size;

}

Решение 2

#include <iostream>

using namespace std;

template <typename Type>

void up2 (Type &x) { x*=2; }

int main() {

           int x; cin >> x;

           double y; cin >> y;

           for (int i=0; i<6; i++) up2(x);

           for (int i=0; i<6; i++) up2(y);

           cout << x << " " << y << endl;

           system("pause");

}

Решение 3

#include <iostream>

#include <cmath>

using namespace std;

template <typename Type>

Type amplitude (Type data[], int size) {

           Type min= data[0], max= data[0];

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

                           if (data[i]>max) max= data[i];

                           if (data[i]<min) min= data[i];

           }

           return abs(min-max);

}

int main() {

           setlocale(0,"");

           cout << "Размерность массива целых чисел: "; int n; cin >> n;

           cout << "Размерность массива вещественных чисел: "; int m; cin >> m;

           int *arr1= new int [n];

           double *arr2= new double [m];

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

                           cout << "Элемент " << i+1 << " целых: "; cin >> arr1[i];

           }

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

                           cout << "Элемент " << i+1 << " вещественных: "; cin >> arr2[i];

           }

           cout << "Амплитуда целых: " << amplitude(arr1,n) << endl;

           cout << "Амплитуда вещественных: " << amplitude(arr2,m) << endl;

           system("pause");

}

Решение 4

void Norm (double arr[], int n) {

           double sum= 0;

           for(int i=0; i<n; i++) sum+= arr[i]*arr[i];

           sum= sqrt(sum);

           for(int i=0; i<n; i++) arr[i]/=sum;

}

Решение 5

int *synpos (int n, int k1, int k2) {

           int N= k2-k1+1;

           int *x= new int [N];

           for (int i=0; i<N; i++) x[i]= k1 + i;

           return x;

}

int *synpos (int n, int k) {

           int *x= synpos(n,k,n);

           return x;

}

int *synpos (int n) {

           int *x= synpos(n,1,n);

           return x;

}

Решение 6

char haref (string s, int n) { return s[n-1]; }

Решение 7

#include <iostream>

#include <string>

#include <Windows.h>

using namespace std;

const string password= "муся";

const int secret= 123454321;

bool pass (const string password) {

           string s;

           system("CLS");

           cout << "Введите пароль: "; cin >> s;

           if (s!=password) {

                           cout << "Неверный пароль!" << endl;

                           return false;

           }

           if (s==password) return true;

}

int main() {

           SetConsoleCP(1251);

           SetConsoleOutputCP(1251);

           bool check=true;

           while(check) {

                           if(pass(password)) {

                                           cout << secret << endl;

                                           check= false;

                           }

                           system("pause");

           }

}

 

Переводы между типами данных

Теперь пришло время подробнее изучить типы данных. Мы уже знаем, что для целых чисел используется тип int, для любых- double, для символов- char, для логических переменных (да или нет- истина или ложь)- bool, для слов и строк (с подключением <string>)- string. И есть ещё пустой тип- void.

Но есть и другие типы данных, которые тоже могут использоваться. Например, для дробных наряду с double мы можем также использовать тип float. Эти типы различаются количеством памяти, которое они занимают в компьютере. Чтобы узнать, сколько байт занимает тот или иной тип данных, нужно вывести sizeof(тип_данных). Например, если мы напишем команду cout << sizeof(float) << endl; то получим число 4- это значит что число, объявленное в типе float, будет занимать 4 байт памяти. Для double- 8 байт (потому так и названо- тип данных двойной точности, 4*2=8). И есть ещё long double- 16 байт.

Что же касается типа данных int, то его занимаемая память зависит от разрядности операционной системы. 32-битная система значит что тут число типа int будет занимать 32 бит, т.е. 4 байт. По крайней мере, так пишут в справочниках- но на моей 64-разрядной операционной системе int всё равно показывает 4 байта. Тем не менее, int зависит от самой системы, что не всегда хорошо, поэтому есть более жёсткие типы данных, количество байт у которых закреплено у любой операционной системы: short int (2 байт) и long int (4 байт). Также есть long long int (8 байт)- его мы можем использовать, когда нам нужны числа больше чем 2 млрд. Максимальное число типа int- 2 в 31 степени минус 1, т.е. 2147483647. Минимальное: -2147483648. Т.е. всего в типа int может храниться 2^32 чисел. 32- потому что 4 байта это 32 бит. И в компьютере все числа представлены в виде последовательностей нулей и единиц.

Чтобы было понятнее, представим что мы можем хранить только числа от 1 до 8. И чтобы представить эти числа в двоичной системе, нам понадобится 3 цифры: 001, 010, 011, 100, 101, 110, 111, 000. Т.е. всего возможно 8 вариантов. Таким образом, из N цифр в двоичной системе возможно 2^N различных чисел.

Вместо short int можно писать просто short. Этот тип принимает значения целых чисел только от -32768 до +32767. Тип long long int вмещает числа до 2^63 - 1, т.е. примерно до 19-циферных чисел.

Также мы можем объявлять целые типы только беззнаковыми словом unsigned. Например, unsigned int x, unsigned short y, unsigned long long int number= 2; и т.п. Беззнаковый тип имеет такую же "байтовость" как и соответствующий ему знаковый, но при этом память отрицательных чисел высвобождается для новых положительных. И unsigned int уже может хранить числа от 0 до 4294967295. Если ты попытаешься присвоить ему отрицательные числа, он выведет положительное- то что у него в ячейке памяти. Например, -1 будет равносильно 4294967295, -2 равносильно 4294967294 и т.д. Для типа short, если попробуем присвоить число больше 32767, то всё равно выведется то что в ячейке памяти. Например, 32769 будет равносильно -32767, 32770- равносильно -32766 и т.д. Также эту хитрость можно использовать для получения максимального числа: например, переменной типа unsigned long long int присвоить -1: и ты получишь число 2^64 - 1. А именно, 18446744073709551615.

Демонстрация: unsigned long long int x= -1; cout << x << endl;

Тип bool занимает всего 1 бит- 0 или 1. На более глубоком уровне 0 это значит нет намагничивания, 1- есть намагничивание. Так компьютер и различает.

Символьный тип char занимает 1 байт (8 бит). Т.е. всего существует 2^8=256 различных символов. Т.е. в памяти символ представлен в виде последовательности 8 цифр, среди которых могут быть только 0 и 1. Каждой последовательности соответствует свой символ по определённому стандарту, который называется ASCII (асци, аски). Ты можешь найти таблицу в интернете, либо сам понять это соответствие через команды ниже.

Например, ты хочешь понять, какой номер имеет символ # по таблице ASCII. Это делается очень просто: cout << (int)'#' << endl; т.е. мы переводим символ в тип int и выводим. Программа вывела число 35.

Ты можешь даже вывести всю таблицу таким кодом:

int x; char c= '\0';             

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

           cout << i << " --> " << c << endl;

           x= (int)c; x++;

           c= (char)x;

}

Здесь '\0' - нулевой символ, он имеет соответствующий номер в таблице ASCII: 0. Нулевой символ- значит пустой символ. Его можно использовать, чтобы проверять- есть дальше в слове символ или нет, чтобы сосчитать в нём количество символов. Цифры занимают последовательно номера от 48 до 57. Это можно использовать, чтобы переводить цифры из символьного типа в численный и на основе этого- строковый тип в численный. Английские заглавные буквы занимают номера от 65 до 90, английские прописные- от 97 до 122, русские заглавные- от 192 до 223, русские прописные- от 224 до 255. Некоторые символы консоль даже не выводит из-за особенности этих символов и консоли. Например, есть символ перевода на новую строку '\n' и прочие такие "неявные" символы. Напоминаю, в с++ символ обозначается одинарными кавычками и имеет тип char.

Есть также более расширенная таблица, где каждый символ занимает уже 2 байта, а поэтому сюда можно вместить уже 65536 различных символов- эта таблица называются Unicode (юникод). Эта таблица позволяет хранить редкие символы, символы локальных языков и даже смайлики вроде улыбок и сердечек. Для расширенного типа символов используется тип данных wchar_t.

При этом стоит отметить, что мы можем переводить число в символ, например:

cout << (char)46 << endl; И нам выведется символ, которому соответствует число 46 в таблице ASCII. Но с wchar_t такое не прокатывает.

Как ты заметил, мы можем переводить один тип в другой через тип со скобками сзади. Но это работает на уровне организации нулей и единиц, это нужно учитывать. Однако, численные типы можно переводить легко и без проблем, например:

int x= 6, y=7; cout << x/y; тут проблема в том что дробная часть обрежется и выведется 0. Поэтому числа должны быть изначально в double, например можно сделать так:

cout << (double)x / y; тут мы сделали вещественным лишь числитель, но нам это будет достаточно. Или можно было сделать: cout << x / (double)y;

Запомни: int/int= int, double/int= double, int/double= double, double/double= double. Но это так только в с++, в других языках программирования может отличаться, это тоже нужно помнить.

Чтобы число было типом float или long double, делаем аналогично- только в скобках вместо double пишем соответствующий тип.

Аналогично можно переводить из double в int. Например, чтобы взять остаток- он работает только для типа int.

double x= 25, y=3; cout << (int)x % (int)y << endl;

Или можно делать целочисленное деление: (int)x / (int)y; Либо (int)(x/y);

...

Теперь рассмотрим переводы из текстовой информации в численную и наоборот. Например, как из текста "41237" сделать число 41237. И наоборот.

// перевести символ в цифру

int CharToInt (char c) {

           return (int)c - (int)'0';

}

Тут должно быть всё понятно: все цифры в таблице ASCII стоят друг за другом. Т.е. для 0 будет 48-48, для 1: 49-48 и т.д. Можно было вместо (int)'0' сразу написать 48, но говорят что бывают отличающиеся таблицы ASCII, поэтому лучше оставлять так для большей универсальности.

Пример использования функции: int x= CharToInt('2'). В переменную x занесётся 2.

// перевод цифры в символ

string DigitToString (int x) {

           if (x==0) return "0";

           if (x==1) return "1";

           if (x==2) return "2";

           if (x==3) return "3";

           if (x==4) return "4";

           if (x==5) return "5";

           if (x==6) return "6";

           if (x==7) return "7";

           if (x==8) return "8";

           if (x==9) return "9";

}

Формат string тут для того чтобы потом из этих цифр слагать слово с помощью другой функции. Но можно сделать такую же функцию и для char:

char DigitToChar (int x) {

           return (char)(48+x);

}

Или более универсально:

char DigitToChar (int x) {

           return (char)((int)'0'+x);

}

Далее изучим функции для количества цифр в числе и количества символов в слове. Количество цифр в числе легко находится через десятичный логарифм:

int qq (int x) {

           if (x<1) return 1;

           else return log10(x)+1;

}

Либо через шаблон, чтобы можно было иметь аргумент других численных типов:

template <typename Num>

int qq (Num x) {

           if (x<1) return 1;

           else return log10(x)+1;

}

log10(1)= 0. Для аргумента от 0 до 1 невключительно логарифм будет отрицательным- поэтому тут мы ставим значение 1- т.е. 1 цифра. Можно было написать просто if(x==0), но оставили меньше 1 на случай если будем подставлять дробные. Здесь мы учитываем только количество цифр в целой части числа. Т.к. значение возвращается целочисленное, то дробная часть, получившаяся в результате вычисления логарифма, обрезается. От 1 до 9 будет значение логарифма с обрезанием дробей 0, от 10 до 99: 1. И так далее. И прибавляем единицу, чтобы было количество цифр.

Теперь научимся разбивать число на цифры. Заметим, что последняя цифра- остаток при делении на 10, последние 2- остаток при делении на 100. И так далее. Это мы и можем использовать.

Например, число x= 51672. Как мы можем получить цифру 6? Таким хитрым приёмом: 6= (672-72) / 100= (x%1000 - x%100) / 100. И это можно обобщить в функцию.

// n-я цифра слева для числа х

int qn (int x, int n) {

           int dlg = pow(10, qq(x)+1-n);

           int dlg1= pow(10, qq(x)-n);

           return (x%dlg - x%dlg1) / dlg1;

}

// разбить число на массив из цифр

int *DigitArray (int x) {

           int *m= new int [qq(x)];

           for(int i=0; i<qq(x); i++) m[i]= qn(x,i+1);

           return m;

}

В обратную сторону:

// сложить число из массива цифр как он записан

int Construct (int m[], int size) {

           int res=0;

           for(int i=1; i<=size; i++) res+= m[i-1]*pow(10,size-i);

           return res;

}

Теперь разберёмся со словами и символами. Как посчитать количество символов в слове? Создадим переменную-счётчик и будем приплюсовывать в неё единицу, пока не встретим нулевой (пустой символ)

// сколько символов в слове / строке

int StringSize (string s) {

           char c= s[0]; int I=0; int stringsize=0;

           while ((int)c != 0) {

                           I++;

                           c=s[I];

                           stringsize++;

           }

           return stringsize;

}

Далее мы это уже используем: переводим каждую текстовую цифру в интовую и производим поразрядное сложение, например: 417= 4*(10^2) + 1*(10^1) + 7*(10^0).

// перевести строку в число

int StringToInt (string s) {

           int sum=0;

           for (int I=0; I<StringSize(s); I++) sum+=CharToInt(s[I])*pow(10,StringSize(s)-I-1);

           return sum;

}

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

// перевод числа в строку

string IntToString (int x) {

           string res="";

           for(int I=1; I<=qq(x); I++) res+=DigitToString(qn(x,I));

           return res;

}

Также добавлю сюда функции на проверку- является ли данный текст числовой информацией.

// является ли символ цифрой

bool isDigit (char c) {

           if(((int)c>=48)&&((int)c<=57)) return true;

           else return false;

}

// является ли строка числом

bool isNum (string s) {

           bool *Digit = new bool [StringSize(s)];

           for(int I=0;I<StringSize(s);I++) Digit[I]=false;

           for(int I=0;I<StringSize(s);I++) {

                           if(isDigit(s[I])) Digit[I]=true;

           }

           bool Rez=true;

           for(int I=0;I<StringSize(s);I++) Rez*=Digit[I];

           return Rez;

}

 

Работа с файлами

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

Для работы с файлами необходимо подключить библиотеку <fstream>

Далее- чтобы создать новый файл, мы должны создать объект класса (типа) ofstream и применить метод open. Конкретнее:

ofstream fout; // м.б. любое имя, fout обычно берётся для удобства т.к. похоже на cout.

fout.open("name.txt");

Тут вместо name.txt может быть другое имя файла.

Далее мы что-то можем занести в этот файл. Например, последовательность квадратов чисел: 1, 4, 9, 16 и т.д. Каждое число будем писать на новой строке.

for (int i=1; i<=1000; i++) fout << i*i << endl;

После того как мы что-то делали в файле, его нужно закрыть:

fout.close();

Всё, работа с файлом завершена.

Файл создастся в той же папке, где находится сама exe программа. Также мы можем сами указать путь- но работает только для внешних дисков, т.е. в локальном диске С файл создать таким образом не получится.

fout.open("E:\\testfile.txt");

Либо можно даже создавать в каком-нибудь подкаталоге:

fout.open("E:\\files\\work\\testfile.txt");

Только при этом папки files и work должны быть уже созданы, т.е. сами подкаталоги (папки) эта функция open не создаёт.

Если мы заново откроем этот файл через fout, то всё содержимое сотрётся. Тип ofstream пригоден только именно для записи информации в файл. Для чтения из файла используется уже тип ifstream. Синтаксис аналогичен.

ifstream fin; // опять же- fin для удобства, переменная может называться любым другим допустимым именем

fin.open("name.txt");

Можно далее получить информацию с файла:

int x; fin >> x;

И вывести: cout << x << endl;

Файл ищется в той же папке, где и сама exe программа. Можно сделать чтобы выводилась ошибка, если файл не найден. И вообще чтобы все действия с файлом выполнялись только если он найден:

ifstream fin;

fin.open("name.txt");

if (!fin) cout << "Ошибка! Файл не найден" << endl;

if (fin) {

ЧТО-ТО ДЕЛАЕМ С ИНФОРМАЦИЕЙ ИЗ ФАЙЛА

}

fin.close();

...

Можно сканировать информацию из файла до тех пор пока не достигнем конца. Это делается так: while (!fin.eof()) { ДЕЛАЕМ ЧТО-ТО }

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

Пример: у нас есть файл name.txt со значениями квадратов. Сделаем так чтобы после последнего значения не было перехода на новую строку:

int N; cin >> N;

for (int i=1; i<=N; i++) {
           fout << i*i;

           if (i!=N) fout << endl;

}

Теперь мы можем прочесть эти данные и вывести на консоль:

int a;

while (!fin.eof()) {

           fin >> a;

           cout << a << endl;

}

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

int a, count=0;

while (!fin.eof()) {

           fin >> a; count++;

}

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

fin.close(); fin.open("name.txt");

int *m= new int [count];

for (int i=0; i<count; i++) fin >> m[i];

...

Научимся теперь сортировать данные- это одна из ключевых задач. Например, у нас в текстовом файле есть данные вида:

петя 817

вася 1244

муся 267

киса 312

И нам нужно чтобы всё было в порядке убывания чисел:

вася 1244

петя 817

киса 312

муся 267

Т.е. тут нужно не просто отсортировать массив чисел, но ещё и учитывать слова, которые соответствуют каждому числу. Это чуть усложняет задачу, но всё равно это вполне решаемо.

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

Итак, полная программа:

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

int main() {

           setlocale(0,"");

           ifstream fin; fin.open("name.txt");

           if(!fin) cout << "Файл не найден!" << endl;

           if(fin) {

                           // подсчёт количества строк

                           string s; int n=0;

                           while(!fin.eof()) {

                                           fin >> s >> s; n++;

                           }

                           fin.close();

                               

                           // массивы для хранения данных

                           string *Data= new string [n];

                           int *Numbers= new int [n];

 

                           // занесение всей информации из файла в массивы

                           fin.open("name.txt");

                           for(int i=0; i<n; i++) fin >> Data[i] >> Numbers[i];

 

                           // новые массивы для будущих отсортированных данных

                           string *NewData= new string [n];

                           int *NewNumbers= new int [n];

                               

                           // нахождение минимального (пригодится в сортировке по убыванию)

                           int min= Numbers[0];

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

                                           if(Numbers[i]<min) min= Numbers[i];

                           }

                           // можно вместо минимального тупо 0 ставить, если в процессе участвуют только положительные числа

                               

                           // сортировка данных в новые массивы

                           int max, poz;

                           for (int K=0; K< n; K++) {

                                      max= Numbers[K]; int poz= K;

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

                                                           if (Numbers[i] > max) {

                                                                           max= Numbers[i];

                                                                           poz= i;

                                                           }

                                           }

                                           NewData[K]= Data[poz];

                                           NewNumbers[K]= Numbers[poz];

                                           Numbers[poz]= --min;

                           }

 

                           // удаление ненужных массивов

                           delete []Data; delete []Numbers;

 

                           // вывод отсортированного в новый файл

                           ofstream fout; fout.open("sorte.txt");

                           for (int i=0; i<n; i++) fout << NewData[i] << " " << NewNumbers[i] << endl;

                           fout.close();

                           cout << "Данные отсортированы в sorte.txt" << endl;

           }

           fin.close();         

           cout << endl; system("pause");

}

...

Файлы можно также переименовывать и удалять. Например, чтобы удалить файл name.txt (он находится в той же папке, где и программа), пишем:

remove("name.txt");

Чтобы переименовать name.txt в newname.txt пишем такую команду:

rename("name.txt","newname.txt");

...

Теперь по поводу автогенерации файлов с циферными именами и вообще про переменное имя файла. Оно работает только в Visual Studio. В devcpp такое не компилируется. Например, можно написать:

string filename;

cout << "Введите имя файла: "; cin >> filename;

ifstream f; f.open(filename);

Можно также синтезировать имя файла на основе имени игрока/пользователя. Есть у нас, скажем, имя игрока name. Можно сделать так:

string filename= name + ".txt";

ofstream f; f.open(filename);

Что касается генерации имён файлов типа res_1.txt, res_2.txt и т.д. Т.к. у нас используется инкремент, который доступен только для числовых данных, а имя файла- текст, то придётся переводить числа в текст. Благо, функции для перевода в предыдущей главе мы уже написали.

int namecount= 0;

string filename= "res_";

while(true) {

           namecount++;

           filename+= IntToString(namecount);

           filename+= ".txt";

           ofstream f; f.open(filename);

           // ЧТО-ТО ПРОИСХОДИТ

}

Повторюсь, такое работает только в среде (и соответственно для компилятора) visual studio. В devcpp такой трюк не работает. Там имя файла может быть только фиксированное. Для функций rename и remove переменное имя файла не работает даже в visual studio.

Иногда может потребоваться обратный трюк- по некому числовому имени файла сделать что-то с числом. Например, файлы названы 1.txt, 2.txt и т.д. а нам надо работать с этими числами 1, 2 и т.д.. Это можно сделать через перевод 1, 2 и т.д. в числовую информацию аналогичной функцией StringToint(). Также понадобятся функции StringSize() и CharToInt(). Все эти функции были в предыдущей главе. Вот пример извлечения числа 415 из строки 415.txt и умножения затем этого числа на 2 в качестве доказательства того что число всё же извлеклось из текста и теперь мы можем делать с ним всё что хотим:

string x= "415.txt"; string X="";

for (int i=0; i<StringSize(x)-4; i++) X+= x[i];

int y= StringToInt(X); cout << y*2;

...

Проблема с переменным именем файла была решена даже в devcpp. Нужно писать вот так:

string fname= "musya.txt";

ifstream fin( fname.c_str() );

Функция c_str() переводит формат string в формат char* (указатель на символ). Для неё ничего не нужно отдельно подключать в данном случае.

И далее уже можно работать с информацией в файле. Например, если в файле лежит число, то можно его вывести в консоль: int x; fin >> x; cout << x;

Эксперты также пишут, что в стандарте с++ 11 функция c_str() не требуется и работает даже тип string, как это и было в visual studio 2012.

...

Теперь- как работать с файлом в функции. Например, у нас будет функция, которая сохраняет число в нужном файле. На примере:

void Save (int x, string fname) {

           ofstream f (fname.c_str()); f << x; f.close();

}

Далее можно вызывать эту функцию: Save(145, "kisa.txt");

...

Мы можем также создавать любые файлы, не только текстовые. Например: ofstream f ("petya.cpp"); или даже ofstream f ("mrrow.jpg"); Естественно, файл будет пустым.

Вот пример мы создаём файл формата doc и заносим туда число 157:             

ofstream f ("mrrow.doc");

f << 157 << endl;

f.close();

Можно далее открыть этот файл и вывести тамошнее число:

ifstream F ("mrrow.doc");

int a; F >> a; cout << a << endl;

F.close();

Внесение-вынесение текстовой информации тоже работает обычным образом. Можешь попробовать другие форматы: docx, rtf и т.п.

Работает также формат xls: информация помещается в ячейку А1. Переход на новую строку сдвинет нас на нижнюю ячейку, в данном случае- в А2. Если, находясь в А2, мы пропишем табуляцию ("\t"), то мы переместимся направо, т.е. в B2. Таким образом, мы теперь можем даже заполнять таблицы. Можно представить, что у нас нет никаких ячеек и чисто белый непрерывный лист- так будет также понятно как мы можем вытаскивать информацию- как из обычного текстового файла.

Чтобы вытащить строку s из файла, можно использовать знакомую уже функцию getline:

getline(fin,s); Здесь fin- объект типа ifstream.

...

Ну и напоследок стоит рассказать про некоторые режимы. Мы можем открывать файлы немного иначе, чем обычно. Например, можно открыть файл для дозаписи- т.е. не очищать файл и что-то писать, а просто открыть существующий файл и что-то туда дозаписать. Это можно сделать таким образом:

ofstream f ("kisa.txt", ios_base::app);

f << 33;

При этом старая информация в файле kisa.txt не стирается. Мы просто добавили туда 33.

Можно использовать также универсальный тип fstream. Тогда для режима чтения можно использовать ios_base::in, а для режима записи в файл- ios_base::out. Например, ifstream f ("name.txt"); всё равно что можно записывать так: fstream f ("name.txt", ios_base::in);

 

Заголовочные файлы

Чем дальше ты заходишь в программирование, тем крупнее и сложнее становятся программы. Да, ты научился уже выносить части кода в отдельные функции, массивы, даже константные переменные ввёл. Но функций становится так много, что уже становится трудно во всём этом разобраться. Можно ли все или часть функций вынести куда-то в отдельный файл? Да, можно. Для этого мы можем создать заголовочные файлы- один или несколько.

Тебе нужно навести мышку на вкладку с проектом: в visual studio это там где resource files- и выбрать add new item как мы создавали source.cpp. Только теперь мы будем создавать уже файл с расширением .h (можешь назвать как угодно- я привык называть Supply.h)

Теперь в этот файл мы можем перенести наши функции. И все включения (include) тоже- тем более если функции используют какой-либо из инклудов.

В devcpp заголовочный файл создаётся так: слева есть вкладка "проект", там ещё правее написано "классы". Например, у меня проект называется "test" и там ещё файл main.cpp. Нужно правой кнопкой нажать на имя проекта (в моём случае "test")- и выбрать New File. Файл тут же создатся с именем "безымянный1". Далее нужно правой кнопкой (там же рядом) нажать на имя этого нового файла и выбрать "переименовать файл". И вписываем уже имя с расширением, например Supply.h (прям так и пишем с расширением!)

Например, программа которая считает углы в треугольнике по теореме косинусов (мы её уже рассматривали ранее). Переместим все функции в отдельный заголовочный файл Supply.h. Просто пишем в этом файле все инклуды и функции. А в главном cpp файле пишем только #include "Supply.h"

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

Это выглядит так:

#ifndef SUPPLY_H

#define SUPPLY_H

ВСЕ ФУНКЦИИ И ПРОЧИЙ ИНСТРУМЕНТАРИЙ

#endif

Вместо SUPPLY_H можно написать другое имя переменной, но принято писать имя файла и при этом заглавными буквами. Можно писать просто SUPP или вообще supp- это дело удобства.

Теперь ниже покажем как будет выглядеть вся программа. Код будет уже находиться в 2 файлах.

// Файл Supply.h

#ifndef SUPP

#define SUPP

#include <iostream>

#include <cmath>

#include <string>

using namespace std;

const double pi= 3.14159265357989;

double AngleCos (double a, double b, double c) {

           return ( acos ( (a*a + b*b - c*c) / (2*a*b) ) ) * 180 / pi;

}

double InputTaraf () {

           cout << "Сторона а: "; double a; cin >> a; return a;

}

bool NoTring (double a[]) {

           if ( (a[0]+a[1]<a[2]) || (a[0]+a[2]<a[1]) || (a[1]+a[2]<a[0]) ) return true;

           else return false;

}

void AngleRes (double a, double b, double c, string s) {

           cout << "\nУгол " <<  s << AngleCos(a,b,c) << " гр";

}

string S[3]= { "A: ", "B: ", "C: "};

void __() { cout << endl; system("pause"); }

#endif

 

// Теперь файл Source.cpp или main.cpp

#include "Supply.h"

int main () {

           setlocale(0,"");

           double a[3]; for (int i=0; i<=2; i++) a[i]= InputTaraf();

           if (NoTring(a)) cout << "Такого треугольника не существует!\n";

           else for (int i=0; i<=2; i++) AngleRes( a[(i+1)%3], a[(i+2)%3] , a[i], S[i]); __();

}

Как видишь, в главном cpp файле чистота и порядок, а весь функционал мы вынесли в отдельный h файл. Для больших проектов это очень важно, т.к. мы строим программу так, чтобы:

1. Всё было удобно и расположено. Ориентироваться в коде как можно легче и быстрее.

2. В случае ошибки или доработки исправлять приходилось бы как можно меньше компонентов.

3. Имена переменных и прочих объектов были понятны и ассоциировались с самими объектами, которые мы так назвали.

Для общемировой международной универсальности пишут идентификаторы обычно на английском языке- чтобы было понятно как можно большему количеству людей. Т.к. если ты ещё не заметил- всё программирование по сути состоит из английских слов и их сокращений: include (включить), if (если), for (для), while (пока), int (integer- целый), cout (c out- си вывод), iostream (input output stream- поток ввода вывода) и т.д. Это просто команды, понятные для человека. А компилятор уже переводит всё это на машинный язык, понятный для компьютера. Это касается не только языка с++, но и 99% остальных.

 

Измерение времени

В некоторых случаях нужно или просто интересно знать, сколько времени работал тот или иной алгоритм. Так можно тестировать, какой алгоритм быстрее. Или вести статистику времени, если мы, например, пишем игру. Замер времени производится через функцию clock(). Она считает миллисекунды, т.е. количество тысячных долей секунды. И возвращает в формате int.

Пример:

int t1= clock();

КАКОЙ-ТО УЧАСТОК КОДА

int t2= clock();

cout << t2-t1 << endl;

Если мы хотим вывести в секундах, тогда вносим в double и потом делим на 1000:

double t1= clock();

ПРОГРАММА

double t2= clock();

cout << (t2-t1)/1000 << " секунд" << endl;

В случае минут мы делим на 60000. Тут уже обычная математика, ничего особо сверхъестественного.

Можно сократить программу так:

double t= clock();

ПРОГРАММА

cout << ( (double)clock() - t ) / 1000 << " сек" << endl;

Тут мы использовали лишь одну переменную для замера времени.

Ах да, для работы clock нужно подключить <ctime>

Пример подсчёта времени алгоритма простой сортировки массива из 10 тысяч целых чисел:

#include <iostream>

#include <ctime>

#include <cmath>

using namespace std;

int main() {

           const int n= 10000;

           int mass[n];

           for (int i=0; i<n; i++) mass[i]= pow(-1,i)*sin(i) ;

           double t= clock();

           int max, poz, buff;

           for (int K=0; K< n-1; K++) {

           max= mass[K]; int poz= K;

           for (int i=K+1; i<n; i++) {

                           if (mass[i] > max) {

                                           max= mass[i];

                                           poz= i;

                           }

           }

           buff= mass[K];

           mass[K]= max;

           mass[poz]= buff;

           }

           cout << ( (double)clock() - t ) / 1000 << " sec" << endl;

           system("pause");

}

Мой компьютер сделал это за 0.3 сек. Если заменить число элементов на 100 тысяч, т.е. в 10 раз, то уже заметно как увеличивается время сортировки: 31 секунда, т.е. примерно в 100 раз дольше. Поэтому придумываются более быстрые алгоритмы сортировки, один из которых будет выдан в следующей главе.

 


Дата добавления: 2018-09-22; просмотров: 161; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!