Как все начиналось
Мое программирование началось осенью 80-го года в рамках семестрового курса состоящего из одних практических занятий. Пожилая дама призвав нас не быть обезьянками, а думать, кратко ознакомила с необходимым набором языковых конструкций и предложила решить небольшую вычислительную задачу. Как оказалось, такой путь весьма эффективен. Вот и мы им воспользуемся.
Основы
Все программы работают с переменными. Переменная всегда хранит значение и может иметь имя и тип, который ограничивает возможные значения переменной. Например, переменная может хранить только числа или только строки. "Всегда хранит значение" следует понимать буквально -- даже, если вы ничего не записывали в переменную, там будет какое-то значение.
Для изменения значений переменных используется оператор присваивания:
a = 5
Он означает, что новым значением переменной a будет 5.
Оператор альтернативы позволяет в зависимости от условия выполнить тот или иной блок операторов:
if (d > 0) {
. . a = 10
}
else {
. . a = 15
}
В данном примере, если значение переменной d будет больше 0, то переменной a будет присвоено значение 10. В ином случае переменной a будет присвоено значение 15. Упомянутые блоки операторов выделены фигурными скобками и конечно же могут содержать более одного оператора.
Оператор вызова функции позволяет выполнить именованную последовательность операторов (функцию), возможно передав ей один или несколько параметров. Функция может возвращать значение. Нам потребуется функция вывода для демонстрации результата работы программы. Обычно она имеет вид:
Write("Корень: ") или WriteLn(x)
При этом после использования Write точка вывода следующего символа остается на той же строке, а после WriteLn переносится в начало следующей. Как правило такие функции могут принимать столько параметров, сколько вам нужно:
WriteLn("Корни: ", x1, " ", x2)
В этом случае значения переменных выводятся друг за другом, и, если вы хотите их разделить, то о пробелах придется позаботиться самостоятельно.
Задача
Любая программа вычисляет результат для какой-либо задачи. Я умышленно не использую слово решает в отношении программы, поскольку решение задачи всегда лежит на программисте. Это он должен найти решение поставленной задачи. Программа -- это лишь запись вашего решения на языке, понятном компьютеру.
В качестве задачи для нашей программы возьмем решение квадратного уравнения:
ax² + bx + c = 0, a≠0
Нам нужно будет найти и напечатать его действительные корни, если они есть.
Для определенности решать будем вот это уравнение:
3x² + 7x − 6 = 0
Решение
Для квадратного уравнения решение состоит в том, что сначала нам нужно вычислить дискриминант по формуле:
D = b² - 4ac
Далее сравниваем дискриминант с нулем и решаем, сколько корней у нас будет 0, 1 или 2, если D < 0, D = 0 или D > 0 соответственно. Определившись, можем вычислить существующие корни по формуле:
x₁₂ = (-b ± √D)/(2a)
Исходные данные для a, b, и c равны 3, 7 и -6.
Как видно из формул, нам понадобятся функции для вычисления квадрата и квадратного корня. Пусть это будут Sqr(x) и Sqrt(x). Я не упомянул их в "Основах", но так часто бывает, что анализ и решение задачи выявляют новые объекты для изучения. Вы должны быть к этому готовы.
При решении задачи важно, чтобы все исходные данные были формально определены, а все промежуточные данные и результат были через них вычислимы.
Программа
Имея решение, не сложно записать нашу программу:
a = 3
b = 7
c = -6
D = Sqr(b) - 4*a*c
if (D < 0) {
. . WriteLn("Нет действительных корней.")
}
else {
. . if (D > 0) {
. . . . x1 = (-b + Sqrt(D))/(2*a)
. . . . x2 = (-b - Sqrt(D))/(2*a)
. . . . WriteLn("Существует два действительных корня:")
. . . . WrireLn("x1: ", x1, " x2: ", x2)
. . }
. . else {
. . . . x = -b/(2*a)
. . . . WriteLn("Существует один действительный корень:")
. . . . WriteLn("x: ", x)
. . }
}
Развитие
Так бывает, что создав программу, нам хочется, чтобы она выполняла еще что-нибудь. Рассмотренная программа решает только одно квадратное уравнение с коэффициентами 3, 7 и -6. Изменим ее, чтобы она позволяла вводить коэффициенты для разных квадратных уравнений. Нам для этого понадобится функция ввода значений: ReadLn(). Она позволяет приостановить выполнение программы, дождаться ввода значения и вернуть его. Сигналом завершения ввода для нее служит нажатие клавиши Enter. ReadLn вернет нам строку, а число мы получим, воспользовавшись функцией ToNumber(str). Перед вводом, полезно вывести строку-приглашения, описывающую, что программа ожидает. В этом нам поможет уже знакомая Write.
Вот пример организации ожидания ввода значения для переменной a:
Write("Введите значение a: ")
a = ToNumber(ReadLn());
Выполнив то же для переменных b и c, мы получим программу, позволяющую решать любые квадратные уравнения.
Массовые вычисления
Программа из предыдущего раздела решает уравнения по одному, используя вас для ввода значений коэффициентов. Для нескольких уравнений это нормально, но для большого числа становится утомительным. Поэтому запишем наборы коэффициентов в файл, а программу изменим так, чтобы она читала коэффициенты из этого файла, а результат записывала в другой файл. Для этого нам понадобится цикл и функции для работы с файлами.
Есть несколько форм циклов. Все они обеспечивают циклическое выполнение группы операторов. Мы будем использовать цикл по условию:
while(условие) {
. . тело цикла
}
Этот цикл выполняет операторы из тела цикла до тех пор, пока верно условие. Важно помнить, что тело цикла должно влиять на условие, таким образом, чтобы в конце концов условие стало неверным.
Для работы с файлами будем использовать следующие функции
- Open("имя файла") -- открывает файл для чтения.
- Create("имя файла") -- создает новый файл, позволяя записывать в него.
- EndOfFile(file) -- проверяет положение позиции чтения и возвращает true, если достигнут конец файла.
- ReadLn(file) -- читает из файла строку.
- WriteLn(file) -- записывает в файл строку.
- Close(file) -- закрывает файл. Закрывать файлы важно, поскольку обычно к файлу может иметь доступ только одна программа. Закрытие файла разрывает связь файла с программой, позволяя другим программам получить доступ к нему. Другой причиной необходимости закрытия является оптимизация записи данных файл. Данные пишутся в файл не сразу, а сначала накапливаются в буфере, по заполнении которого, переносятся на носитель. Одним из последствий операции Close является запись на носитель не до конца заполненного буфера.
Для достижения нашей цели, понадобится два файла: входной с коэффициентами, и выходной для результатов. Во входном файле коэффициенты для одного уравнения будут занимать одну строку и разделяться с помощью точки с запятой. В выходной файл для каждого уравнения записываются его коэффициенты, а, следом за ними -- корни. Это позволит не считать строчки, для выяснения, к какому уравнению относятся корни.
Замысел программы следующий. Читаем входной файл построчно до конца файла. Для каждой строки получаем коэффициенты уравнения и находим его корни, которые записываем вместе с коэффициентами в выходной файл.
input = Open("equations.sqe")
output = Create("solutions.sqe")
while(not EndOfFile(input)) {
. . coefficients = Split(ReadLn(input), ";")
. . a = ToNumber(coefficients[0])
. . b = ToNumber(coefficients[1])
. . c = ToNumber(coefficients[2])
. . D = Sqr(b) - 4*a*c
. . if (D < 0) {
. . . .WriteLn(output, a, ";", b, ";", c, ": Нет действительных корней.")
. . }
. . else {
. . . . if (D > 0) {
. . . . . . x1 = (-b + Sqrt(D))/(2*a)
. . . . . . x2 = (-b - Sqrt(D))/(2*a)
. . . . . . WriteLn(output, a, ";", b, ";", c, ": Существует два действительных корня:", "x1: ", x1, " x2: ", x2)
. . . . }
. . . . else {
. . . . . . x = -b/(2*a)
. . . . . . WriteLn(output, a, ";", b, ";", c, ": Существует один действительный корень:", "x: ", x)
. . . . }
. . }
}
Close(input)
Close(output)
Не описанная ранее функция Split(строка, разделитель), делит передаваемую строку на части по разделителю, возвращая части как массив строк. Массив представляет собой набор значений, для доступа к которым используется индекс, определяющий положение элемента в массиве. Здесь мы предполагаем, что коэффициенты в строке расположены в порядке a, b, c.
Отмечу еще один момент. В файле результатов значения a, b и c записываются в том же формате, что и во входном файле с коэффициентами. Эта мелочь сильно упростит поиск нужного результата. Ведь можно скопировать набор коэффициентов из исходного файла и подставить его в окне поиска по файлу результатов. Наблюдательность часто позволяет найти решение, которое не сложно реализовать, а пользы получить целый вагон.
Какой язык выбрать
Любой, для которого у вас есть среда, в которой вы можете написать код, собрать и запустить программу. У меня первым был Фортран, а средой служил студенческий вычислительный центр, в который я относил программный код на листочках в клеточку, и забирал перфокарты и распечатки с программой и результатами. Но у меня тогда и выбора не было, а у вас есть. Так что экспериментируйте. Возможно вам захочется сделать приведенную программу на нескольких языках, и отрыть для себя, что все они одинаковые (ну, может быть, кроме LISP'а и Prolog'а).
И еще. Язык программирования это лишь инструмент для коммуникации с компьютером. Вы должны научиться формулировать то, что делает ваша программа или ее часть на своем родном языке, на котором вы мыслите.
Сложность
Сложность программирования таится не в изучении языков и многочисленных библиотек. Число языковых конструкций невелико, а число функций в библиотеках хоть и значительно, но конечно. К тому же, все сразу они не нужны. Их можно изучать постепенно. Вряд вам сразу придется разрабатывать большой проект с нуля -- скорее всего вы попадете в команду уже что-то создавшую. А это доступ к примеру, который поможет вам определиться, что изучать. Так что наблюдательность и любопытство вам в помощь. Наблюдательность поможет вам выявлять объекты для исследования, а любопытство -- подстегивать в их изучении.
Так где сложность. Она в поиске решений тех задач, что встанут перед вами. Каждая новая компания, в которую вы устроитесь будет иметь свою предметную область, которую нужно будет изучить, чтобы научится решать задачи из нее. Поэтому навык анализа сложных систем вам сильно поможет. А чтобы собирать информацию о предметной области вам не обойтись без навыка общения с носителями этой информации. И про наблюдательность с любопытством не забудьте.
Другая сторона сложности программирования -- работа с ошибками. Да, они будут. И их надо будет искать, что в большом проекте нетривиальная задача. Помните про наблюдательность и любопытство? А когда найдете, то настанет момент исправления. И вы будете искать, на что же повлияли ваши изменения, что тоже может оказаться тем еще квестом.
Еще одна грань сложности программирования открывается при столкновении с большими проектами. Если у вас кодовая база в 1000000 строк, то вряд ли вы ее выучите наизусть. Тут могу посоветовать: при каждой работе делайте или пополняйте заметки о том, что вы нашли или сделали. Позднее, они вас здорово выручат. И опять -- наблюдательность с любопытством вам в помощь.