Выражения
Как уже было упомянуто в предыдущих главах (в истории ООП), одним из наиболее значимых нововведений языков программрования высокого уровня, стала возможность записи арифметических выражений в естественной для человека форме, с использованием операторов и функций. Для того чтобы посчитать значение выражения, теперь программисту требуется лишь правильно записать его, не заботясь о том, как оно будет вычисляться. Всю работу по разбору такого выражения, определению очередности выполнения операций берет на себя компилятор языка.
В этой главе мы узнаем, что такое выражения с точки зрения языка К++, как они формируются и на что нужно обращать внимание при их записи. Кратко будет затронута тема операторов, будут даны основные сведения о них. Более подробно, операторы как элемент языка К++, будут рассмотрены в конце книги, в отдельном разделе.
Содержание |
Арифметические операции
Наиболее простой и понятный тип выражений — это самые обычные арифметические выражения:
<source lang="kpp"> var x = 2 + 3 * 4; var y = (2 + 3) * 4; </source>
Для простоты мы используем хорошо нам известную конструкцию объявления переменной, но на этот раз в инициализаторе мы указываем не одну константу, вроде строки "hello world", а целое выражение. Выражением считается все, что записано между знаком "=" и точкой с запятой ";", которая завершает конструкцию.
При вычислении значения выражения применяются те же самые правила что и в обычной математике, то есть операции имеют приоритет и выполняются строго в порядке убывания приоритета. В первом случае, сначала будет вычислено подвыражение 3 * 4, к значению которого будет добавлено число 2; сумма будет установлена как значение переменной. Во втором случае, приоритет операций был изменен введением круглых скобок, которые имеют тот же смысл, что и в математике: сначала вычисляется значение в скобках, а затем остальные операции, опять же в порядке их приоритета. Скобок в выражение может быть сколько угодно, допускается вложенность.
...Таким образом, переменной x будет присвоено значение 14, а переменная y будет равна 20.
В выражениях могут указываться не только числовые константы. Так же как и в математике, в выражениях можно использовать переменные.
При вычислении выражения вмсето имени переменной подставляется ее значение. Таким образом, если в ходе рассчетов выражения, переменная изменит свое значение, то вместе следующего вхождения переменной может быть подставлено уже новое значение. А чтобы с уверенностью сказать "может быть подставлено" или "будет подставлено" нужно смотреть, какой именно код записан в выражении. С этим моментом связана известная проблема неоднозначности выражений. Забежав вперед, можно рассмотреть связанный с данным вопросом пример применения операторов в выражении:
<source lang="kpp"> var i = 5; var x = ++i + ++i; </source>
Как вы думаете, какое значение будет иметь переменная x после выполнения вышеприведенного кода? Программист, знающий только Паскаль при виде этого примера скорее всего впадет в ступор, поскольку этот язык не имеет оператора ++, либо он сочтет что выражение записано ошибочно.
Новичок, изучающий язык C++, но уже кое-что знающий о нем, может рассуждать так: «Оператор ++ — это оператор инкремента. В зависимости от того где он расположен в выражении, будет зависеть то как он изменяет переменную; если оператор находится слева он переменной, то сначала будет произведена операция инкремента, а затем новое значение будет использоваться при расчете выражения. В нашем случае это именно так. Стало быть, при рассчете выражения, в обоих случаях значение переменной i будет увеличено на единицу и будет использовано в рассчете. В результате мы получим выражение x = 6 + 7 = 13»
Опытный программист на C++, первым делом спросит как реализованы операторы ++ и +, и происходит ли копирование аргументов. Если копирование происходит, то значение будет таким же как у новичка, то есть 13. Если же оператор работает с самой переменной, то произойдет следующее:
Первый оператор ++ увеличит значение переменной i на единицу, которое теперь будет равно 6. Поскольку мы имеем дело со ссылкой на переменную то это значение не будет нигде сохраняться. Далее, при вычислении значения второго слагаемого сработает уже второй оператор ++, который так же увеличит (уже увеличенное значение!) переменную на 1, и вернет результат 6 + 1, то есть 7. При вычислении итогового значения мы получаем выражение 7 + 7, то есть 14.
Получается что сколько человек, столько и мнений. А самое интересное, что каждый из них прав. Их рассуждения логичны с учетом того, с какой позиции смотреть на проблему. В такой неразберихе не мудрено запутаться. Если читатель еще не сбит с толку окончательно, то можно продолжить и рассмотреть эту задачу с точки зрения языка К++.
В случае с языком К++ вычисление значения будет происходить по второму сценарию. То есть, результат такого выражения будет равен 14.
Вызов функций
Вызов функций в выражениях осуществляется путем записи имени функции, следом за которым в круглых скобках перечисляются ее аргументы. При этом, при вычислении выражения, вместо самой функции будет подставлено ее значение:
<source lang="kpp"> const pi = 3.1415926; var x = 10; var y = x * sin(pi); </source>
Доступ к полям
Доступ к полям и свойствам классов осуществляется путем записи имени объекта, следом закоторым ставится оператор точка ".", а затем идентификатор поля либо метода. Если ссылаемое поле само имеет свойства или поля, то к ним так же можно ссылаться, поставив вторую точку и указав идентификатор и т. д.
<source lang="kpp" line="1"> class MyClass {
var m_Field = "hello world"; property field read m_Field write m_Field; public function SetField(const string what = "world") { m_Field = "hello " + what; }
}
function caller() {
var object = new MyClass; println(object.field); object.field = "hello everyone!"; object.SetField("universe"); object.SetField();
} </source>
- 1-7
- Мы объявляем некоторый класс MyClass, который имеет поле m_Field и свойство field, связанное с полем. Так же имеется метод SetField(), который устанавливает новое значение свойства, которое определяется выражением в строке 5. Значение свойства складывается из строковой константы "hello ", к которой добавляется значение параметра what.
- 9-15
- В этой функции приводится пример применения вышеописанного класса. Сначала мы создаем инстанцию класса MyClass с помощью оператора new. Затем вызывается функция println(), которая печатает значение свойства объекта. В третьей строке мы присваиваем свойству field (а значит и полю m_Field) новое значение. Последние две строки показывают пример вызова метода с явным аргументом и с аргументом по умолчанию.
Операторы
С точки зрения языка К++, оператор — это специальная функция, имеющая строго определенное название, принимающая определенный набор параметров и возвращающая определенное значение. Фактически, операторы являются частью самого языка и обрабатываются отдельно от обычных функций еще на этапе синтаксического разбора программы, а следовательно, могут иметь любое имя (то есть, на них не распространяются правила именования идентификаторов). Так и происходит. К примеру, все арифметические выражения что мы писали ранее, содержат наборы числовых констант, переменных и операторов, реализующих арифметические операции. То есть, +, -, *, /, ++ и многие другие — это операторы. Например, оператор +, объявленный в реализации стандартной библиотеки для класса int, принимает в качестве параметра объект того же типа и выполняет операцию сложения.
Существует большое количество разнообразных операторов, которые разделяются на два больших класса: унарные и бинарные операторы. Первые работают с единственным объектом (то есть применяются для него же самого), вторые — оперируют двумя объектами. Таким образом, оператор + является бинарным оператором, в то время как ++ — унарный. В языках программирования, операторы применяются для самых различных задач, а не только для арифметики. Например, в книге уже был рассмотрен оператор as, служащий для явного приведения типов.
Однако, то что операторы являются частью языка программирования, не означает что программист не может их использовать для собственных нужд в своих классах. Операторы, подобно обычным функциям и методам, позволяют произвоить перегрузку и переобъявление. Приведем простой пример, иллюстрирующий этот механизм:
<source lang="kpp" line="1"> extend MyClass { public:
operator MyClass += (const MyClass mc) { m_Field += mc.field; return this; } operator MyClass += (const string s) { m_Field += s; return this; } const operator string () { return m_Field; }
}
function f() {
var x = new MyClass; var y = new MyClass; x.field = "X"; y.field = "Y"; println(x += y); println(x += "Z");
} </source>
- 1-6
- Используя механизм расширений, мы дополнили функциональность ранее описанного класса MyClass, добавив в него три оператора: две реализации бинарного оператора += и оператор приведения типа. В блоке описания параметров для бинарных операторов мы указываем, с объектами какого типа должна работать данная конкретная реализация оператора. В первом случае, принимается объект нашего же класса MyClass, во втором случае — объект стандартного класса string. Оба оператора делают одно и то же: добавляют к текущему значению поля m_Field, значение содержащееся в аргументе, то есть, в другом объекте, расположенном справа от оператора. Обратите внимание на тела операторов: для конкатенации строк применяется тот же самый оператор +=, однако следует понимать, что это уже применяется реализация оператора, объявленная в классе строки.
- Оператор приведения типов служит для преобразования объекта нашего класса MyClass к классу строки. Это может быть необходимо, например, для удобного вывода информации об обекте. При этом, инстанция класса указывается в выражении так, как будто она является строкой. Соответственно, ее можно будет передавать в качестве параметра функциям, принимающим строки — сперва будет выполнена операция приведения типа (с помощью нашего оператора), а затем результат будет передан как параметр. Стоит отметить, что эта операция выполняется компилятором автоматически. От нас требуется только реализовать сам оператор приведения типа.
- 8-12
- Для иллюстрирования вышесказанного, мы создаем два экземпляра нашего класса и присваиваем свойству field новые значения: "X" и "Y" соответственно.
- 13
- Эта строка показывает наш оператор в действии. Как видите, мы выполняем операцию += для двух инстанций нашего класса. При этом, компилятор языка проверит, поддерживает ли наш класс такой оператор для аргумента типа MyClass. В нашем случае это так. Следовательно, при вычислении выражения он будет задействован. Результат выполнения операции x += y так же является объектом класса MyClass (на самом деле, это будет все та же инстанция x, поскольку в реализации оператора применена конструкция "return this;"). Но функция println() принимает в качестве параметра строку, значит необходимо выполнить операцию приведения типов. Компилятор языка К++ вторично обратится за помощью к самому классу и проверит наличие оператора приведения к типу string. К счастью, такой оператор тоже имеется, поэтому компилятор заключает что типы приводимы, и генерирует вызов оператора приведения. В заключение, результат выполнения оператора приведения (в нашем случае это будет строка "XY") будет передан функции println(), которая уже распечатает его.
- 14
- Смысл этой конструкции мало чем отличается от предыдущей, за исключением того, что вторым операндом указан не объект класса MyClass, а строка. Соответственно, при компиляции будет задействована вторая реализация оператора +=; в остальном все просиходит точно так же.
Примечание: Здесь мы затронули лишь самые общие сведения об операторах и их применении. На самом деле, это очень обширная тема, требующая отдельного рассмотрения. Более подробно, про операторы можно почитать в отдельном разделе.