Выражения — различия между версиями
(gxUSKrDIIzGs) |
Raw mat (обсуждение | вклад) |
||
(не показаны 6 промежуточных версий 5 участников) | |||
Строка 1: | Строка 1: | ||
− | + | Как уже было упомянуто в предыдущих главах ([[Классы и объекты#История развития ООП|в истории ООП]]), одним из наиболее значимых нововведений языков программрования высокого уровня, стала возможность записи арифметических выражений в естественной для человека форме, с использованием операторов и функций. Для того чтобы посчитать значение выражения, теперь программисту требуется лишь правильно записать его, не заботясь о том, как оно будет вычисляться. Всю работу по разбору такого выражения, определению очередности выполнения операций берет на себя компилятор языка. | |
+ | |||
+ | В этой главе мы узнаем, что такое выражения с точки зрения языка К++, как они формируются и на что нужно обращать внимание при их записи. Кратко будет затронута тема операторов, будут даны основные сведения о них. Более подробно, операторы как элемент языка К++, будут рассмотрены в конце книги, в [[Обзор операторов|отдельном разделе]]. | ||
+ | |||
+ | == Арифметические операции == | ||
+ | |||
+ | Наиболее простой и понятный тип выражений — это самые обычные арифметические выражения: | ||
+ | |||
+ | <source lang="kpp"> | ||
+ | var x = 2 + 3 * 4; | ||
+ | var y = (2 + 3) * 4; | ||
+ | </source> | ||
+ | |||
+ | Для простоты мы используем хорошо нам известную конструкцию объявления переменной, но на этот раз в инициализаторе мы указываем не одну константу, вроде строки <tt>"hello world"</tt>, а целое выражение. Выражением считается все, что записано между знаком "<tt>=</tt>" и точкой с запятой "<tt>;</tt>", которая завершает конструкцию. | ||
+ | |||
+ | При вычислении значения выражения применяются те же самые правила что и в обычной математике, то есть операции имеют ''приоритет'' и выполняются строго в порядке убывания приоритета. В первом случае, сначала будет вычислено ''подвыражение'' <tt>3 * 4</tt>, к значению которого будет добавлено число 2; сумма будет установлена как значение переменной. Во втором случае, приоритет операций был изменен введением круглых скобок, которые имеют тот же смысл, что и в математике: сначала вычисляется значение в скобках, а затем остальные операции, опять же в порядке их приоритета. Скобок в выражение может быть сколько угодно, допускается вложенность. | ||
+ | |||
+ | ...Таким образом, переменной ''x'' будет присвоено значение 14, а переменная ''y'' будет равна 20. | ||
+ | |||
+ | В выражениях могут указываться не только числовые константы. Так же как и в математике, в выражениях можно использовать переменные. | ||
+ | |||
+ | При вычислении выражения вмсето имени переменной подставляется ее значение. Таким образом, если в ходе рассчетов выражения, переменная изменит свое значение, то вместе следующего вхождения переменной может быть подставлено уже новое значение. А чтобы с уверенностью сказать "может быть подставлено" или "будет подставлено" нужно смотреть, какой именно код записан в выражении. С этим моментом связана известная проблема неоднозначности выражений. Забежав вперед, можно рассмотреть связанный с данным вопросом пример применения [[Обзор операторов|операторов]] в выражении: | ||
+ | |||
+ | <source lang="kpp"> | ||
+ | var i = 5; | ||
+ | var x = ++i + ++i; | ||
+ | </source> | ||
+ | |||
+ | Как вы думаете, какое значение будет иметь переменная ''x'' после выполнения вышеприведенного кода? Программист, знающий только Паскаль при виде этого примера скорее всего впадет в ступор, поскольку этот язык не имеет оператора <tt>++</tt>, либо он сочтет что выражение записано ошибочно. | ||
+ | |||
+ | Новичок, изучающий язык C++, но уже кое-что знающий о нем, может рассуждать так: ''«Оператор ++ — это оператор инкремента. В зависимости от того где он расположен в выражении, будет зависеть то как он изменяет переменную; если оператор находится слева он переменной, то сначала будет произведена операция инкремента, а затем новое значение будет использоваться при расчете выражения. В нашем случае это именно так. Стало быть, при рассчете выражения, в обоих случаях значение переменной i будет увеличено на единицу и будет использовано в рассчете. В результате мы получим выражение x = 6 + 7 = 13»'' | ||
+ | |||
+ | Опытный программист на C++, первым делом спросит как реализованы операторы <tt>++</tt> и <tt>+</tt>, и происходит ли копирование аргументов. Если копирование происходит, то значение будет таким же как у новичка, то есть 13. Если же оператор работает с самой переменной, то произойдет следующее: | ||
+ | |||
+ | Первый оператор <tt>++</tt> увеличит значение переменной ''i'' на единицу, которое теперь будет равно 6. Поскольку мы имеем дело со ссылкой на переменную то это значение не будет нигде сохраняться. Далее, при вычислении значения второго слагаемого сработает уже второй оператор <tt>++</tt>, который так же увеличит (уже увеличенное значение!) переменную на 1, и вернет результат 6 + 1, то есть 7. При вычислении итогового значения мы получаем выражение 7 + 7, то есть 14. | ||
+ | |||
+ | Получается что сколько человек, столько и мнений. А самое интересное, что каждый из них прав. Их рассуждения логичны с учетом того, с какой позиции смотреть на проблему. В такой неразберихе не мудрено запутаться. Если читатель еще не сбит с толку окончательно, то можно продолжить и рассмотреть эту задачу с точки зрения языка К++. | ||
+ | |||
+ | В случае с языком К++ вычисление значения будет происходить по второму сценарию. То есть, результат такого выражения будет равен 14. | ||
+ | |||
+ | == Вызов функций == | ||
+ | |||
+ | Вызов [[Функции|функций]] в выражениях осуществляется путем записи имени функции, следом за которым в круглых скобках перечисляются ее [[Функции#Аргументы|аргументы]]. При этом, при вычислении выражения, вместо самой функции будет подставлено ее значение: | ||
+ | |||
+ | <source lang="kpp"> | ||
+ | const pi = 3.1415926; | ||
+ | var x = 10; | ||
+ | var y = x * sin(pi); | ||
+ | </source> | ||
+ | |||
+ | == Доступ к полям == | ||
+ | |||
+ | Доступ к [[Классы и объекты#Поля|полям]] и [[Классы и объекты#Свойства|свойствам]] классов осуществляется путем записи имени объекта, следом закоторым ставится оператор точка "<tt>.</tt>", а затем [[идентификатор]] поля либо метода. Если ссылаемое поле само имеет свойства или поля, то к ним так же можно ссылаться, поставив вторую точку и указав идентификатор и т. д. | ||
+ | |||
+ | <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; | ||
+ | puts(object.field); | ||
+ | object.field = "hello everyone!"; | ||
+ | object.SetField("universe"); | ||
+ | object.SetField(); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | |||
+ | ;1-7: Мы объявляем некоторый класс <tt>MyClass</tt>, который имеет поле ''m_Field'' и свойство ''field'', связанное с полем. Так же имеется метод <tt>SetField()</tt>, который устанавливает новое значение свойства, которое определяется выражением в строке 5. Значение свойства складывается из строковой константы <tt>"hello "</tt>, к которой добавляется значение параметра ''what''. | ||
+ | |||
+ | ;9-15: В этой функции приводится пример применения вышеописанного класса. Сначала мы создаем инстанцию класса <tt>MyClass</tt> с помощью оператора <tt>'''new'''</tt>. Затем вызывается функция <tt>puts()</tt>, которая печатает значение свойства объекта. В третьей строке мы присваиваем свойству <tt>field</tt> (а значит и полю ''m_Field'') новое значение. Последние две строки показывают пример вызова метода с явным аргументом и с аргументом по умолчанию. | ||
+ | |||
+ | == Операторы == | ||
+ | |||
+ | С точки зрения языка К++, ''оператор'' — это специальная функция, имеющая строго определенное название, принимающая определенный набор параметров и возвращающая определенное значение. Фактически, операторы являются частью самого языка и обрабатываются отдельно от обычных функций еще на этапе синтаксического разбора программы, а следовательно, могут иметь любое имя (то есть, на них не распространяются правила именования [[Идентификатор|идентификаторов]]). Так и происходит. К примеру, все арифметические выражения что мы писали ранее, содержат наборы числовых констант, переменных и операторов, реализующих арифметические операции. То есть, <tt>+</tt>, <tt>-</tt>, <tt>*</tt>, <tt>/</tt>, <tt>++</tt> и многие другие — это операторы. Например, оператор <tt>+</tt>, объявленный в реализации [[Стандартная библиотека Gide|стандартной библиотеки]] для класса <tt>[[Стандартные типы данных#Целые числа|int]]</tt>, принимает в качестве параметра объект того же типа (<tt>int</tt>) и выполняет операцию сложения. | ||
+ | |||
+ | Существует большое количество разнообразных операторов, которые разделяются на два больших класса: ''унарные'' и ''бинарные'' операторы. Первые работают с единственным объектом (то есть, применяются для него же самого), вторые — оперируют двумя объектами. Таким образом, оператор <tt>+</tt> является бинарным оператором, в то время как <tt>++</tt> является унарным. В языках программирования, операторы применяются для самых различных задач, а не только для арифметики. Например, в книге уже был рассмотрен оператор <tt>'''as'''</tt>, служащий для явного [[Приведение типов|приведения типов]]. | ||
+ | |||
+ | Однако, то что операторы являются частью языка программирования, не означает что программист не может их использовать для собственных нужд в своих классах. Операторы, подобно обычным функциям и методам, позволяют производить ''перегрузку'' и переобъявление. Приведем простой пример, иллюстрирующий этот механизм: | ||
+ | |||
+ | <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"; | ||
+ | puts(x += y); | ||
+ | puts(x += "Z"); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | |||
+ | ;1-6: Используя [[Классы и объекты#Расширения|механизм расширений]], мы дополнили функциональность ранее описанного класса <tt>MyClass</tt>, добавив в него три оператора: две реализации бинарного оператора <tt>+=</tt> и оператор приведения типа. В блоке описания параметров для бинарных операторов мы указываем, с объектами какого типа должна работать данная конкретная реализация оператора. В первом случае, принимается объект нашего же класса <tt>MyClass</tt>, во втором случае — объект стандартного класса <tt>[[Стандартные типы данных#Строки|string]]</tt>. Оба оператора делают одно и то же: добавляют к текущему значению поля ''m_Field'', значение содержащееся в аргументе, то есть, в другом объекте, расположенном справа от оператора. Обратите внимание на тела операторов: для ''конкатенации'' строк применяется тот же самый оператор +=, однако следует понимать, что это уже применяется реализация оператора, объявленная в классе строки. | ||
+ | |||
+ | : Оператор приведения типов служит для преобразования объекта нашего класса <tt>MyClass</tt> к классу строки. Это может быть необходимо, например, для удобного вывода информации об обекте. При этом, инстанция класса указывается в выражении так, как будто она является строкой. Соответственно, ее можно будет передавать в качестве параметра функциям, принимающим строки — сперва будет выполнена операция приведения типа (с помощью нашего оператора), а затем результат будет передан как параметр. Стоит отметить, что эта операция выполняется компилятором автоматически. От нас требуется только реализовать сам оператор приведения типа. | ||
+ | |||
+ | ;8-12: Для иллюстрирования вышесказанного, мы создаем два экземпляра нашего класса и присваиваем свойству <tt>field</tt> новые значения: "X" и "Y" соответственно. | ||
+ | |||
+ | ;13: Эта строка показывает наш оператор в действии. Как видите, мы выполняем операцию += для двух инстанций нашего класса. При этом, компилятор языка проверит, поддерживает ли наш класс такой оператор для аргумента типа <tt>MyClass</tt>. В нашем случае это так. Следовательно, при вычислении выражения он будет задействован. Результат выполнения операции <tt>x += y</tt> так же является объектом класса <tt>MyClass</tt> (на самом деле, это будет все та же инстанция ''x'', поскольку в реализации оператора применена конструкция "<tt>'''return this''';</tt>"). Но функция <tt>puts()</tt> принимает в качестве параметра строку, значит необходимо выполнить операцию приведения типов. Компилятор языка К++ вторично обратится за помощью к самому классу и проверит наличие оператора приведения к типу <tt>string</tt>. К счастью, такой оператор тоже имеется, поэтому компилятор заключает что типы ''приводимы'', и генерирует вызов оператора приведения. В заключение, результат выполнения оператора приведения (в нашем случае это будет строка "XY") будет передан функции <tt>puts()</tt>, которая уже распечатает его. | ||
+ | |||
+ | ;14: Смысл этой конструкции мало чем отличается от предыдущей, за исключением того, что вторым ''операндом'' указан не объект класса <tt>MyClass</tt>, а строка. Соответственно, при компиляции будет задействована вторая реализация оператора <tt>+=</tt>; в остальном все просиходит точно так же. | ||
+ | |||
+ | |||
+ | '''Примечание:''' Здесь мы затронули лишь самые общие сведения об операторах и их применении. На самом деле, это очень обширная тема, требующая отдельного рассмотрения. Более подробно, про операторы можно почитать в [[K++#Операторы|специальном разделе]], целиком посвященном вопросам операторов. |
Текущая версия на 13:45, 13 июля 2013
Как уже было упомянуто в предыдущих главах (в истории ООП), одним из наиболее значимых нововведений языков программрования высокого уровня, стала возможность записи арифметических выражений в естественной для человека форме, с использованием операторов и функций. Для того чтобы посчитать значение выражения, теперь программисту требуется лишь правильно записать его, не заботясь о том, как оно будет вычисляться. Всю работу по разбору такого выражения, определению очередности выполнения операций берет на себя компилятор языка.
В этой главе мы узнаем, что такое выражения с точки зрения языка К++, как они формируются и на что нужно обращать внимание при их записи. Кратко будет затронута тема операторов, будут даны основные сведения о них. Более подробно, операторы как элемент языка К++, будут рассмотрены в конце книги, в отдельном разделе.
Содержание |
[править] Арифметические операции
Наиболее простой и понятный тип выражений — это самые обычные арифметические выражения:
<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; puts(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. Затем вызывается функция puts(), которая печатает значение свойства объекта. В третьей строке мы присваиваем свойству field (а значит и полю m_Field) новое значение. Последние две строки показывают пример вызова метода с явным аргументом и с аргументом по умолчанию.
[править] Операторы
С точки зрения языка К++, оператор — это специальная функция, имеющая строго определенное название, принимающая определенный набор параметров и возвращающая определенное значение. Фактически, операторы являются частью самого языка и обрабатываются отдельно от обычных функций еще на этапе синтаксического разбора программы, а следовательно, могут иметь любое имя (то есть, на них не распространяются правила именования идентификаторов). Так и происходит. К примеру, все арифметические выражения что мы писали ранее, содержат наборы числовых констант, переменных и операторов, реализующих арифметические операции. То есть, +, -, *, /, ++ и многие другие — это операторы. Например, оператор +, объявленный в реализации стандартной библиотеки для класса int, принимает в качестве параметра объект того же типа (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"; puts(x += y); puts(x += "Z");
} </source>
- 1-6
- Используя механизм расширений, мы дополнили функциональность ранее описанного класса MyClass, добавив в него три оператора: две реализации бинарного оператора += и оператор приведения типа. В блоке описания параметров для бинарных операторов мы указываем, с объектами какого типа должна работать данная конкретная реализация оператора. В первом случае, принимается объект нашего же класса MyClass, во втором случае — объект стандартного класса string. Оба оператора делают одно и то же: добавляют к текущему значению поля m_Field, значение содержащееся в аргументе, то есть, в другом объекте, расположенном справа от оператора. Обратите внимание на тела операторов: для конкатенации строк применяется тот же самый оператор +=, однако следует понимать, что это уже применяется реализация оператора, объявленная в классе строки.
- Оператор приведения типов служит для преобразования объекта нашего класса MyClass к классу строки. Это может быть необходимо, например, для удобного вывода информации об обекте. При этом, инстанция класса указывается в выражении так, как будто она является строкой. Соответственно, ее можно будет передавать в качестве параметра функциям, принимающим строки — сперва будет выполнена операция приведения типа (с помощью нашего оператора), а затем результат будет передан как параметр. Стоит отметить, что эта операция выполняется компилятором автоматически. От нас требуется только реализовать сам оператор приведения типа.
- 8-12
- Для иллюстрирования вышесказанного, мы создаем два экземпляра нашего класса и присваиваем свойству field новые значения: "X" и "Y" соответственно.
- 13
- Эта строка показывает наш оператор в действии. Как видите, мы выполняем операцию += для двух инстанций нашего класса. При этом, компилятор языка проверит, поддерживает ли наш класс такой оператор для аргумента типа MyClass. В нашем случае это так. Следовательно, при вычислении выражения он будет задействован. Результат выполнения операции x += y так же является объектом класса MyClass (на самом деле, это будет все та же инстанция x, поскольку в реализации оператора применена конструкция "return this;"). Но функция puts() принимает в качестве параметра строку, значит необходимо выполнить операцию приведения типов. Компилятор языка К++ вторично обратится за помощью к самому классу и проверит наличие оператора приведения к типу string. К счастью, такой оператор тоже имеется, поэтому компилятор заключает что типы приводимы, и генерирует вызов оператора приведения. В заключение, результат выполнения оператора приведения (в нашем случае это будет строка "XY") будет передан функции puts(), которая уже распечатает его.
- 14
- Смысл этой конструкции мало чем отличается от предыдущей, за исключением того, что вторым операндом указан не объект класса MyClass, а строка. Соответственно, при компиляции будет задействована вторая реализация оператора +=; в остальном все просиходит точно так же.
Примечание: Здесь мы затронули лишь самые общие сведения об операторах и их применении. На самом деле, это очень обширная тема, требующая отдельного рассмотрения. Более подробно, про операторы можно почитать в специальном разделе, целиком посвященном вопросам операторов.