Python 3 функциональное программирование. Функциональное программирование на Python. Функции высшего порядка

Viber OUT 19.03.2019
Viber OUT

Аннотация: Рассматривается синтаксис и семантика построения выражений языка С#. Рассматриваются все возможные операции языка, их приоритеты. При рассмотрении логических операций обсуждается работа со шкалами. Рассмотрен лямбда оператор и лямбда выражение. Большое внимание уделяется преобразованиям типа данных при вычислении выражений. Обсуждаются вопросы эффективного вычисления выражений. Предлагаются задачи на эту тему.

Проект к данной лекции Вы можете скачать .

Выражения

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

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

Приоритет и порядок выполнения операций

Большинство операций в языке C#, их приоритет и порядок наследованы из языка C++. Однако имеются и различия: например, нет операции " , " , позволяющей вычислять список выражений; добавлены операции checked и unchecked , применимые к выражениям.

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

Таблица 3.1. Приоритеты операций языка C#
Приоритет Категория Операции Порядок
0 Первичные (expr), x.y, x->y, f(x), a[x], x++, x--, new, typeof(t), checked(expr), unchecked(expr) Слева направо
1 Унарные +, -, !, ~, ++x, --x, (T)x, sizeof(t) Слева направо
2 Мультипликативные (Умножение) *, /, % Слева направо
3 Аддитивные (Сложение) +, - Слева направо
4 Сдвиг << ,>> Слева направо
5 Отношения, проверка типов <, >, <=, >=, is, as Слева направо
6 Эквивалентность ==, != Слева направо
7 Логическое И (AND) & Слева направо
8 Логическое исключающее ИЛИ (XOR) ^ Слева направо
9 Логическое ИЛИ (OR) | Слева направо
10 Условное логическое И && Слева направо
11 Условное логическое ИЛИ || Слева направо
12 Условное выражение ? : Справа налево
13 Присваивание

Склеивание с null

=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= Справа налево
14 Лямбда-оператор => Справа налево

Перегрузка операций и методов

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

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

Большинство операций языка C# перегружены - одна и та же операция может применяться к операндам различных типов. Поэтому прежде чем выполнять операцию, проводится поиск реализации, подходящей для данных типов операндов. Замечу, что операции, как правило, выполняются над операндами одного типа. Если же операнды разных типов, то предварительно происходит неявное преобразование типа одного из операндов. Оба операнда могут быть одного типа, но преобразование типов может все равно происходить - по той причине, что для заданных типов нет соответствующей перегруженной операции. Такая ситуация достаточно часто возникает на практике, поскольку, например, операция сложения не определена для младших подтипов арифметического типа. Если для данных типов операндов нет подходящей реализации операции и невозможно неявное приведение типов операндов, то, как правило, эта ошибка обнаруживается еще на этапе компиляции.

Преобразования типов

Каждый объект (переменная), каждый операнд при вычислении выражения, само выражение характеризуется парой , задающей значение выражения и его тип. В процессе вычислений зачастую возникает необходимость преобразования типов - необходимость преобразовать пару к паре . Исходная пара называется источником преобразования, заключительная - целью преобразования.

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

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

Преобразование, для которого не выполняется хотя бы одно из этих условий, называется опасным. Достаточным условием существования безопасного преобразования является, например, условие того, что тип является подтипом типа . Действительно, в этом случае любое значение источника является одновременно и допустимым значением цели. Так, преобразование от типа int к типу double является безопасным. Обратное преобразование, естественно, будет опасным.

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

Существуют разные способы выполнения явных преобразований - операция кастинга (приведение к типу), методы специального класса Convert , специальные методы ToString , Parse . Все эти способы будут рассмотрены в данной лекции.

Поясним, как выполняются неявные преобразования при вычислении выражения. Пусть при вычислении некоторого выражения необходимо выполнить сложение , где имеет тип double , а - int . Среди многочисленных реализаций сложения есть операции, выполняющие сложение операндов типа int и сложение операндов типа double, так что при выборе любой из этих реализаций сложения потребуется преобразование типа одного из операндов. Поскольку преобразование типа от int к double является безопасным, а в другую сторону это преобразование опасно, то выбирается безопасное преобразование, выполняемое автоматически, второй операнд неявно преобразуется к типу double, выполняется сложение операндов этого типа, и результат сложения будет иметь тип double .

Организация программного проекта ConsoleExpressions

Как обычно, все примеры программного кода, появляющиеся в тексте, являются частью программного проекта. Опишу структуру используемого в этой лекции консольного проекта, названного ConsoleExpressions. Помимо созданного по умолчанию класса Program , в проект добавлены два класса с именами TestingExpressions и Scales . Каждый из методов класса TestingExpressions представляет тест, который позволяет анализировать особенности операций, используемых при построении выражений, так что этот класс представляет собой сборник тестов. Класс Scale носит содержательный характер, демонстрируя работу со шкалами, о которых пойдет речь в этой лекции. Чтобы иметь возможность вызывать методы этих классов, в процедуре Main класса Program объявляются и создаются объекты этих классов. Затем эти объекты используются в качестве цели вызова соответствующих методов. Общая схема процедуры Main и вызова методов класса такова:

static void Main(string args) { string answer = "Да"; do { try { TestingExpressions test = new TestingExpressions(); test.Casting(); //Вызов других методов … } catch (Exception e) { Console.WriteLine("Невозможно нормально продолжить работу!"); Console.WriteLine(e.Message); } Console.WriteLine("Продолжим работу? (Да/нет)"); answer = Console.ReadLine(); } while (answer == "Да" || answer == "да" || answer == "yes"); }

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

C++ для начинающих

4.13. Приоритеты

Приоритеты операций задают последовательность вычислений в сложном выражении. Например, какое значение получит ival?

Int ival = 6 + 3 * 4 / 2 + 2;

Если вычислять операции слева направо, получится 20. Среди других возможных результатов будут 9, 14 и 36. Правильный ответ: 14.
В С++ умножение и деление имеют более высокий приоритет, чем сложение, поэтому они будут вычислены раньше. Их собственные приоритеты равны, поэтому умножение и деление будут вычисляться слева направо. Таким образом, порядок вычисления данного выражения таков:

1. 3 * 4 => 12 2. 12 / 2 => 6 3. 6 + 6 => 12 4. 12 + 2 => 14

Следующая конструкция ведет себя не так, как можно было бы ожидать. Приоритет операции присваивания меньше, чем операции сравнения:

While (ch = nextChar() != "\n")

Программист хотел присвоить переменной ch значение, а затем проверить, равно ли оно символу новой строки. Однако на самом деле выражение сначала сравнивает значение, полученное от nextChar(), с "\n", и результат – true или false – присваивает переменной ch.
Приоритеты операций можно изменить с помощью скобок. Выражения в скобках вычисляются в первую очередь. Например:

4 * 5 + 7 * 2 ==> 34 4 * (5 + 7 * 2) ==> 76 4 * ((5 + 7) * 2) ==> 96

Вот как с помощью скобок исправить поведение предыдущего примера:

While ((ch = nextChar()) != "\n")

Операторы обладают и приоритетом , и ассоциативностью . Оператор присваивания правоассоциативен, поэтому вычисляется справа налево:

Ival = jval = kva1 = lval

Сначала kval получает значение lval, затем jval – значение результата этого присваивания, и в конце концов ival получает значение jval.
Арифметические операции, наоборот, левоассоциативны. Следовательно, в выражении

Ival + jval + kva1 + 1va1

сначала складываются ival и jval, потом к результату прибавляется kval, а затем и lval.
В таблице 4.4 приведен полный список операторов С++ в порядке уменьшения их приоритета. Операторы внутри одной секции таблицы имеют равные приоритеты. Все операторы некоторой секции имеют более высокий приоритет, чем операторы из секций, следующих за ней. Так, операции умножения и деления имеют одинаковый приоритет, и он выше приоритета любой из операций сравнения.

Упражнение 4.18

Каков порядок вычисления следующих выражений? При ответе используйте таблицу 4.4.

(a) ! ptr == ptr->next (b) ~ uc ^ 0377 & ui << 4 (c) ch = buf[ bp++ ] != "\n"

Упражнение 4.19

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

Упражнение 4.20

Следующие выражения вызывают ошибку компиляции из-за неправильно понятого приоритета операций. Объясните, как их исправить, используя таблицу 4.4.

(a) int i = doSomething(), 0; (b) cout << ival % 2 ? "odd" : "even";

Таблица 4.4. Приоритеты операций

Оператор Значение Использование
:: Глобальная область видимости ::name
:: Область видимости класса class::name
:: Область видимости пространства имен namespace::name
. Доступ к члену object.member
-> Доступ к члену по указателю pointer->member
Взятие индекса variable
() Вызов функции name(expr_list)
() Построение значения type(expr_list)
++ постфиксный инкремент lvalue++
постфиксный декремент lvalue--
typeid идентификатор типа typeid(type)
typeid идентификатор типа выражения typeid(expr)
преобразование типа const_cast(expr)
преобразование типа dynamic_cast(expr)
reinterpret_cast приведение типа reinterpret_cast (expr)
static_cast приведение типа static_cast(expr)
sizeof размер объекта sizeof expr
sizeof размер типа sizeof(type)
++ префиксный инкремент ++lvalue
-- префиксный декремент --lvalue
~ побитовое НЕ ~expr
! логическое НЕ !expr
- унарный минус -expr
+ унарный плюс +expr
* разыменование *expr
& адрес &expr
() приведение типа (type)expr
new выделение памяти new type
new выделение памяти и инициализация new type(exprlist)
new Выделение памяти под массив все формы
delete освобождение памяти все формы
delete освобождение памяти из-под массива все формы
->* доступ к члену классу по указателю pointer-> *pointer_to_member
.* доступ к члену класса по указателю object.*pointer_to_member
* Умножение expr * expr
/ Деление expr / expr
% деление по модулю expr % expr
+ сложение expr + expr
- вычитание expr - expr
<< сдвиг влево expr << expr
>> сдвиг вправо expr >> expr
< меньше expr < expr
<= меньше или равно expr <= expr
> больше expr > expr
>= больше или равно expr >= expr
== равно expr == expr
!= не равно expr != expr
& побитовое И expr & expr
^ побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ expr ^ expr
| побитовое ИЛИ expr | expr
&& логическое И expr && expr
|| логическое ИЛИ expr || expr
?: условный оператор expr ? expr * expr
= присваивание l-значение = expr
=, *=, /=, %=, +=, -=, <<=, >>=, &=, |=, ^= составное присваивание l-значение += expr и т.д.
throw возбуждение исключения throw expr
, запятая expr, expr

Чтобы правильно вычислять выражения (например, 4 + 2 * 3), мы должны знать, какие операторы что значат и в каком порядке их следует применять. Эта последовательность, в которой они выполняются, называется приоритетом операций . Следуя обычным правилам математики (в которой умножение следует выполнять перед сложением), выражение выше решается так — 4 + (2 * 3), результат — значение 10.

В C++ все операторы (операции) имеют свой уровень приоритета. Те, в которых он выше – выполняются первыми. В таблице ниже можно видеть, что приоритет операций умножения и деления (5) выше, чем в операциях сложения и вычитания (6). Компилятор использует это для определения порядка обработки выражений.

А что делать, если у двух операторов в выражении одинаковый уровень приоритета и они размещены рядом? Какую операцию выполнять первой? А здесь уже компилятор будет пользоваться правилами ассоциативности, которые указывают направление выполнения операций: слева направо или справа налево. Например, в 3 * 4 / 2, операции умножения и деления имеют одинаковый уровень приоритета — 5. А ассоциативность 5 уровня — слева направо, так что решать будем так: (3 * 4) / 2 = 6.

Таблица приоритета операций

Примечания :

1 – это самый высокий уровень приоритета, а 17 – самый низкий. Операции с более высоким уровнем приоритета выполняются первыми.

L -> R означает слева направо.

R -> L означает справа налево.

Ассоциативность Оператор Описание Пример
1. Нет :: Глобальная область видимости (унарный) ::name
:: Область видимости класса (бинарный) class_name::member_name
2. L->R () Круглые скобки (expression)
() Вызов функции function_name(parameters)
() Инициализация type name(expression)
{} Uniform инициализация (C++11) type name{expression}
type() Functional cast new_type(expression)
type{} Functional cast (C++11) new_type{expression}
Индекс массива pointer
. Доступ к члену объекта object.member_name
-> Доступ к члену объекта через указатель object_pointer->member_name
++ Пост-инкремент lvalue++
–– Пост-декремент lvalue––
typeid Информация о типе во время выполнения typeid(type) or typeid(expression)
const_cast Cast away const const_cast(expression)
dynamic_cast Run-time type-checked cast dynamic_cast(expression)
reinterpret_cast Cast one type to another reinterpret_cast(expression)
static_cast Compile-time type-checked cast static_cast(expression)
3. R->L + Унарный плюс +expression
Унарный минус -expression
++ Пре-инкремент ++lvalue
–– Пре-декремент ––lvalue
! Логическое НЕ (NOT) !expression
~ Побитовое НЕ (NOT) ~expression
(type) C-style cast (new_type)expression
sizeof Размер в байтах sizeof(type) or sizeof(expression)
& Адрес &lvalue
* Dereference *expression
new Динамическое выделение памяти new type
new Динамическое распределение массива new type
delete Динамическое удаление памяти delete pointer
delete Динамическое удаление массива delete pointer
4. L->R ->* Member pointer selector object_pointer->*pointer_to_member
.* Member object selector object.*pointer_to_member
5. L->R * Умножение expression * expression
/ Деление expression / expression
% Остаток expression % expression
6. L->R + Сложение expression + expression
Вычитание expression — expression
7. L->R << Побитовый сдвиг влево expression << expression
>> Побитовый сдвиг вправо expression >> expression
8. L->R < Сравнение. Меньше чем expression < expression
<= Сравнение. Меньше чем или равно expression <= expression
> Сравнение. Больше чем expression > expression
>= Сравнение. Больше чем или равно expression >= expression
9. L->R == Равно expression == expression
!= Не равно expression != expression
10. L->R & Побитовое И (AND) expression & expression
11. L->R ^ Побитовое исключающее ИЛИ (XOR) expression ^ expression
12. L->R | Побитовое ИЛИ (OR) expression | expression
13. L->R && Логическое И (AND) expression && expression
14. L->R || Логическое ИЛИ (OR) expression || expression
15. R->L ?: Тернарный условный оператор (см. примечание ниже) expression ? expression: expression
= Присваивание lvalue = expression
*= Умножение с присваиванием lvalue *= expression
/= Деление с присваиванием lvalue /= expression
%= Деление с остатком с присваиванием lvalue %= expression
+= Сложение с присваиванием lvalue += expression
-= Вычитание с присваиванием lvalue -= expression
<<= Присваивание с побитовым сдвигом влево lvalue <<= expression
>>= Присваивание с побитовым сдвигом вправо lvalue >>= expression
&= Присваивание с побитовой операцией И (AND) lvalue &= expression
|= Присваивание с побитовой операцией ИЛИ (OR) lvalue |= expression
^= Присваивание с побитовой операцией «исключающее ИЛИ» (XOR) lvalue ^= expression
16. R->L throw Создание исключения вручную throw expression
17. L->R , Оператор Comma (запятая) expression, expression

Примечание : Выражение в середине условного оператора?: выполняется как если бы оно находилось в круглых скобках.

Некоторые операторы вы уже знаете: +, -, *, /, (), =, <,>, <= и >=. Их значения одинаковы как в математике, так и в C++.

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

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

Как возводить в степень в C++?

Вы уже должны были заметить, что оператор ^, который обычно используется для обозначения возведения в степень в обычной математике, не является таковым в C++. В С++ это побитовая операция XOR. Так что же тогда вместо ^? А вместо этого – функция pow(), которая находится в заголовочном файле :

#include double x = pow(3.0, 4.0); // 3 в степени 4

#include

double x = pow (3.0 , 4.0 ) ; // 3 в степени 4

Обратите внимание, параметры и возвращаемые значения функции pow() — типа double. А поскольку типы с плавающей точкой известны ошибками округления, то результаты pow() могут быть слегка неточными (чуть меньше или чуть больше).

Если вам нужно возвести в степень целое число, то лучше использовать собственную функцию, например:

// примечание: экспонент не должен быть отрицательным int pow(int base, int exp) { int result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; base *= base; } return result; }

// примечание: экспонент не должен быть отрицательным

int pow (int base , int exp )

int result = 1 ;

while (exp )

if (exp & 1 )

result * = base ;

exp >> = 1 ;

base * = base ;

return result ;

Здесь используется алгоритм «Возведения в степень путем возведения в квадрат».

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

Тест

1) Из школьной математики вы знаете, что выражения внутри скобок выполняются первыми. Например, в (2 + 3) * 4 , часть (2 + 3) будет выполнятся первой.

В этом задании есть 4 выражения, в которых отсутствуют какие-либо скобки. Используя приоритет операций и правила ассоциативности в таблице выше, добавьте скобки в каждое выражение так, как если бы их обрабатывал компилятор.

Подсказка : Используйте колонку Пример в таблице выше, чтобы определить, является ли оператор унарным (имеет один операнд) или бинарным (два операнда). Если забыли, что такое унарный или бинарный — смотрите урок 17 .

Пример решения: х = 2 + 3 % 4

Бинарный оператор % имеет более высокий приоритет, чем оператор + или =, поэтому он применяется первым: х = 2 + (3 % 4);

Бинарный оператор + имеет более высокий приоритет, чем =, поэтому следующим применяется он.

Задания

а) x = 3 + 4 + 5;
б) x = y = z;
в) z *= ++y + 5;
г) a || b && c || d;

Ответ

а) Уровень приоритета бинарного оператора + выше, чем в =:

х = (3 + 4 + 5);

Ассоциативность бинарного оператора + слева направо:

Ответ: х = ((3 + 4) + 5).

б) Ассоциативность бинарного оператора = справа налево:

Ответ: x = (y = z).

в) Унарный оператор ++ имеет наивысший приоритет:

Бинарный оператор + имеет второй наивысший приоритет:

Ответ: z *= ((++y) + 5).

г) Бинарный оператор && имеет более высокий приоритет, чем ||:

a || (b && c) || d;

Ассоциативность бинарного оператора || слева направо:

Ответ: (a || (b && c)) || d.

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

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

В таблице 4.1 приведены операции в порядке убывания приоритета. Операции, расположенные в одной строке таблицы, или объединенные в одну группу, имеют одинаковый приоритет и одинаковую ассоциативность.

Таблица 4.1.

Приоритет и ассоциативность операций в языке Си

Знак операции Наименование Ассоциативность
() . -> Первичные Слева направо
+ - ~ ! * & ++ -- sizeof приведение типа Унарные Справа налево
* / % Мультипликативные Слева направо
+ - Аддитивные Слева направо
>> << Сдвиг Слева направо
< > <= >= Отношение Слева направо
== != Отношение Слева направо
& Поразрядное И Слева направо
^ Поразрядное исключающее ИЛИ Слева направо
| Поразрядное включающее ИЛИ Слева направо
&& Логическое И Слева направо
|| Логическое ИЛИ Слева направо
?: Условная Справа налево
= *= /= %= += -= <<= >>= &= |= ^= Простое и составное присваивание Справа налево
, Последовательное вычисление Слева направо

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

Выражение может содержать несколько операций одного приоритета. Когда несколько операций одного и того же уровня приоритета появляются в выражении, то они применяются в соответствии с их ассоциативностью - либо справа налево, либо слева направо.

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

а = b & 0xFF + 5

вычисляется как

а = b & (0xFF + 5),

а выражение

а +с >> 1

вычисляется как

(а + с) >> 1

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

В СП ТС реализована операция унарного плюса, позволяющая гарантировать порядок вычисления выражений в скобках.

Операция последовательного вычисления, логические операции И и ИЛИ, условная операция и операция вызова функции гарантируют определенный порядок вычисления своих операндов. Операция последовательного вычисления обеспечивает вычисление своих операндов по очереди, слева направо (запятая, разделяющая аргументы в вызове функции, не является операцией последовательного вычисления и не обеспечивает таких гарантий). Гарантируется лишь то, что к моменту вызова функции все аргументы уже вычислены.

Условная операция вычисляет сначала свой первый операнд, а затем, в зависимости от его значения, либо второй, либо третий.

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

int х, у, z, f();

z = х > у || f(x, у);

Сначала вычисляется выражение х>у. Если оно истинно, то переменной z присваивается значение 1, а функция f не вызывается. Если же значение х не больше у, то вычисляется выражение f(x,y). Если функция f возвращает ненулевое значение, то переменной z присваивается 1, иначе 0. Отметим также, что при вызове функции f гарантируется, что значение ее первого аргумента больше второго.

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

if(!feof(pf)) && (с = getc(pf)) …

Здесь feof - функция проверки на конец файла, getc - функция чтения символа из файла (см. раздел 12).

В-третьих, можно гарантировать, что в выражении f(x)&&g(y) функция f будет вызвана раньше, чем функция g . Для выражения f(x)+g(y) этого утверждать нельзя.

В последующих примерах показано группирование операндов для различных выражений.

Выражение Группирование операндов
a & b || c (a & b) || c
a = b || c a = (b || c)
q && r || s-- (q && r) || (s--)
p == 0 ? p += 1: p += 2 (p == 0 ? p += 1: p) += 2

В первом примере поразрядная операция И (&) имеет больший приоритет, чем -логическая операция ИЛИ (||), поэтому выражение а&b является первым операндом логической операции ИЛИ.

Во втором примере логическая операция ИЛИ (||) имеет больший приоритет, чем операция простого присваивания, поэтому выражение b||с образует правый операнд операции присваивания. (Обратите внимание на то, что значение, присваиваемое а , есть нуль или единица.)

The precedence and associativity of C operators affect the grouping and evaluation of operands in expressions. An operator"s precedence is meaningful only if other operators with higher or lower precedence are present. Expressions with higher-precedence operators are evaluated first. Precedence can also be described by the word "binding." Operators with a higher precedence are said to have tighter binding.

The following table summarizes the precedence and associativity (the order in which the operands are evaluated) of C operators, listing them in order of precedence from highest to lowest. Where several operators appear together, they have equal precedence and are evaluated according to their associativity. The operators in the table are described in the sections beginning with Postfix Operators . The rest of this section gives general information about precedence and associativity.

Precedence and Associativity of C Operators

Symbol 1 Type of Operation Associativity
() . ->

++ -- (postfix)

Expression Left to right
sizeof & * + - ~ !

++ -- (prefix)

Unary Right to left
typecasts Unary Right to left
* / % Multiplicative Left to right
+ - Additive Left to right
<< >> Bitwise shift Left to right
< > <= >= Relational Left to right
== != Equality Left to right
& Bitwise-AND Left to right
^ Bitwise-exclusive-OR Left to right
| Bitwise-inclusive-OR Left to right
&& Logical-AND Left to right
|| Logical-OR Left to right
? : Conditional-expression Right to left
= *= /= %=

+= -= <<= >>= &=

^= |=

Simple and compound assignment 2 Right to left
, Sequential evaluation Left to right

    Operators are listed in descending order of precedence. If several operators appear on the same line or in a group, they have equal precedence.

    All simple and compound-assignment operators have equal precedence.

An expression can contain several operators with equal precedence. When several such operators appear at the same level in an expression, evaluation proceeds according to the associativity of the operator, either from right to left or from left to right. The direction of evaluation does not affect the results of expressions that include more than one multiplication (* ), addition (+ ), or binary-bitwise (& , | , or ^ ) operator at the same level. Order of operations is not defined by the language. The compiler is free to evaluate such expressions in any order, if the compiler can guarantee a consistent result.

Only the sequential-evaluation (, ), logical-AND (&& ), logical-OR (|| ), conditional-expression (? : ), and function-call operators constitute sequence points and therefore guarantee a particular order of evaluation for their operands. The function-call operator is the set of parentheses following the function identifier. The sequential-evaluation operator (, ) is guaranteed to evaluate its operands from left to right. (Note that the comma operator in a function call is not the same as the sequential-evaluation operator and does not provide any such guarantee.) For more information, see Sequence Points .

Logical operators also guarantee evaluation of their operands from left to right. However, they evaluate the smallest number of operands needed to determine the result of the expression. This is called "short-circuit" evaluation. Thus, some operands of the expression may not be evaluated. For example, in the expression

the second operand, y++ , is evaluated only if x is true (nonzero). Thus, y is not incremented if x is false (0).

Examples

The following list shows how the compiler automatically binds several sample expressions:

Expression Automatic Binding
a & b || c (a & b) || c
a = b || c a = (b || c)
q && r || s-- (q && r) || s--

In the first expression, the bitwise-AND operator (& || ), so a & b forms the first operand of the logical-OR operation.

In the second expression, the logical-OR operator (|| ) has higher precedence than the simple-assignment operator (= ), so b || c is grouped as the right-hand operand in the assignment. Note that the value assigned to a is either 0 or 1.

The third expression shows a correctly formed expression that may produce an unexpected result. The logical-AND operator (&& ) has higher precedence than the logical-OR operator (|| ), so q && r is grouped as an operand. Since the logical operators guarantee evaluation of operands from left to right, q && r is evaluated before s-- . However, if q && r evaluates to a nonzero value, s-- is not evaluated, and s is not decremented. If not decrementing s would cause a problem in your program, s-- should appear as the first operand of the expression, or s should be decremented in a separate operation.

The following expression is illegal and produces a diagnostic message at compile time:

Illegal Expression Default Grouping
p == 0 ? p += 1: p += 2 (p == 0 ? p += 1: p) += 2

In this expression, the equality operator (== ) has the highest precedence, so p == 0 is grouped as an operand. The conditional-expression operator (? : ) has the next-highest precedence. Its first operand is p == 0 , and its second operand is p += 1 . However, the last operand of the conditional-expression operator is considered to be p rather than p += 2 , since this occurrence of p binds more closely to the conditional-expression operator than it does to the compound-assignment operator. A syntax error occurs because += 2 does not have a left-hand operand. You should use parentheses to prevent errors of this kind and produce more readable code. For example, you could use parentheses as shown below to correct and clarify the preceding example.



Рекомендуем почитать

Наверх