распределение функциональных свойств между разными частями программы. Мы не можем просто сидеть и фантазировать, как получше разбить программу на части; мы должны учитывать, какие библиотеки находятся в нашем распоряжении и как их можно использовать. Пока вы находитесь в начале пути, но вскоре увидите, что использование существующих библиотек, таких как стандартная библиотека языка С++, позволяет сэкономить много сил не только на этапе программирования, но и на этапах тестирования и документации. Например, потоки ввода-вывода позволяют нам не вникать в детали устройства аппаратных портов ввода-вывода. Это первый пример разделения программы на части с помощью абстракции. В следующих главах мы приведем новые примеры.
Обратите внимание на то, какое значение мы придаем структуре и организации программы: вы не сможете написать хорошую программу, просто перечислив множество инструкций. Почему мы упоминаем об этом сейчас? На текущем этапе вы (или, по крайней мере, многие читатели) слабо представляете себе, что такое программа, и лишь через несколько месяцев будете готовы написать программу, от которой может зависеть жизнь или благосостояние других людей. Мы упоминаем об этом, чтобы помочь вам правильно спланировать свое обучение. Существует большой соблазн набросать примерный план курса по программированию — похожего на изложенный в оставшейся части книги, — выделив темы, которые имеют очевидное полезное применение и проигнорировав более “тонкие” вопросы разработки программного обеспечения. Однако хорошие программисты и проектировщики систем знают (и это знание часто приобретается тяжелой ценой), что вопросы структуры лежат в основе хорошего программного обеспечения и пренебрежение ими порождает массу проблем. Не обеспечив хорошей структуры программы, вы, образно говоря, лепите ее из глины. Это вполне возможно, но таким образом никогда нельзя построить пятиэтажный дом (глина просто не выдержит). Если хотите построить не времянку, а солидное здание, то следует уделить внимание структуре и правильной организации кода, а не возвращаться к этим вопросам, совершив множество ошибок.
4.3. Выражения
Основными строительными конструкциями программ являются выражения. Выражение вычисляет некое значение на основе определенного количества операндов. Простейшее выражение представляет собой обычную литеральную константу, например 'a', 3.14 или "Norah".
Имена переменных также являются выражениями. Переменная — это объект, имеющий имя. Рассмотрим пример.
// вычисление площади:
int length = 20; // литеральное целое значение
// (используется для инициализации переменной)
int width = 40;
int area = length*width; // умножение
Здесь литералы 20 и 40 используются для инициализации переменных, соответствующих длине и ширине. После этого длина и ширина перемножаются; иначе говоря, мы перемножаем значения length и width. Здесь выражение “значение length” представляет собой сокращение выражения “значение, хранящееся в объекте с именем length”. Рассмотрим еще один пример.
length = 99; // присваиваем length значение 99
Здесь слово length, обозначающее левый операнд оператора присваивания, означает “объект с именем length”, поэтому это выражение читается так: “записать число 99 в объект с именем length”. Следует различать имя length, стоящее в левой части оператора присваивания или инициализации (оно называется “lvalue переменной length”) и в правой части этих операторов (в этом случае оно называется “rvalue переменной length”, “значением объекта с именем length”, или просто “значением length”). В этом контексте полезно представить переменную в виде ящика, помеченного именем.
Иначе говоря, length — это имя объекта типа int, содержащего значение 99. Иногда (в качестве lvalue) имя length относится к ящику (объекту), а иногда (в качестве rvalue) — к самому значению, хранящемуся в этом ящике.
Комбинируя выражения с помощью операторов, таких как + и *, мы можем создавать более сложные выражения, так, как показано ниже. При необходимости для группировки выражения можно использовать скобки.
int perimeter = (length+width)*2; // сложить и умножить
Без скобок это выражение пришлось бы записать следующим образом:
int perimeter = length*2+width*2;
что слишком громоздко и провоцирует ошибки.
int perimeter = length+width*2; // сложить width*2 с length
Последняя ошибка является логической, и компилятор не может ее обнаружить. Компилятор просто видит переменную с именем perimeter, инициализированную корректным выражением. Если результат выражения не имеет смысла, то это ваши проблемы. Вы знаете математическое определение периметра, а компилятор нет.
В программах применяются обычные математические правила, регламентирующие порядок выполнения операторов, поэтому length+width*2 означает length+(width*2). Аналогично выражение a*b+c/d означает (a*b)+(c/d), а не a*(b+c)/d. Таблица приоритетов операторов приведена в разделе A.5.
Первое правило использования скобок гласит: “Если сомневаешься, используй скобки”. И все же программист должен научиться правильно формировать выражения, чтобы не сомневаться в значении формулы a*b+c/d. Слишком широкое использование операторов, например (a*b)+(c/d), снижает читабельность программы.
Почему мы заботимся о читабельности? Потому что ваш код будете читать не только вы, но и, возможно, другие программисты, а запутанный код замедляет чтение и препятствует его анализу. Неуклюжий код не просто сложно читать, но и трудно исправлять. Плохо написанный код часто скрывает логические ошибки. Чем больше усилий требуется при его чтении, тем сложнее будет убедить себя и других, что он является правильным. Не пишите слишком сложных выражений вроде
a*b+c/d*(e–f/g)/h+7 // слишком сложно
и всегда старайтесь выбирать осмысленные имена.
4.3.1. Константные выражения
В программах, как правило, используется множество констант. Например, в программе для геометрических вычислений может использоваться число “пи”, а в программе для пересчета дюймов в сантиметры — множитель 2.54. Очевидно, что этим константам следует приписывать осмысленные имена (например, pi, а не 3.14159). Аналогично, константы не должны изменяться случайным образом. По этой причине в языке С++ предусмотрено понятие символической константы, т.е. именованного объекта, которому после его инициализации невозможно присвоить новое значение. Рассмотрим пример.
const double pi = 3.14159;
pi = 7; // ошибка: присваивание значения константе
double c = 2*pi/r; // OK: мы просто используем переменную pi,
// а не изменяем ее
Такие константы полезны для повышения читабельности программ. Увидев фрагмент кода, вы, конечно, сможете догадаться о том, что константа 3.14159 является приближением числа “пи”, но что вы скажете о числе 299792458? Кроме того, если вас попросят изменить программу так, чтобы число “пи” было записано с