Таблица приоритетов операций c. Выражения и операции

22.03.2019

Приоритет операций.

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

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

Операции в скобках

Функции (типа sqrt, cos и другие)

Умножение, деление, остаток от деления (слева направо, как в обычной жизни)

Сложение вычитание (слева направо, как в обычной жизни)

Для примера рассмотрим следующее выражение записанное на языке Си:

Приоритет арифметических операций

Над каждым действием вы видите каким оно выполняется по счету.

Для изменения порядка выполнения операций используются круглые скобки. Например, если записать:

Листинг 4.10

Компьютер сначала поделит а на b, и потом умножит полученный результат на с. Но, если нам надо посчитать чему равно a деленное на произведение b*c, тогда нужно использовать скобки.

Листинг 4.11

15. Элементарный ввод-вывод в языке Си.

Функция форматного вывода printf()

printf(форматная_строка, список_вывода)

Список вывода содержит перечисленные через запятую имена выводимых переменных, т. е. показывает, что выводить. В список вывода можно включать не только переменные, но и произвольные выражения (в частном случае константы).

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

произвольный текст (обычно он поясняет значения выводимых переменных); среди символов этого текста могут быть и управляющие; в несложных программах этого пособия часто будет использоваться управляющий символ "\n" ;

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

Спецификация формата вывода или ввода

В простейшем виде спецификация формата имеет вид:

%ширина_поля.точность спецификатор

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

Точность - положительное целое число, указывается в основном для вещественных чисел, означая в этом случае количество выводимых после десятичной точки цифр.

На первых этапах обучения программированию достаточно следующих спецификаторов:

d - для целых значений (типов int, char, unsigned);

f - для вещественных значений (типов float, double);

e - для вещественных значений (типов float, double)с указанием порядка, т. е. в виде:

Другие примеры оператора printf() представлены на рис.1.

Функция форматного ввода scanf()

Оператор вызова этой функции имеет вид:

scanf(форматная_строка, список_ввода)

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

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

Пример функции scanf:

printf("Введите i и a\n"); /* вывод приглашения к вводу */

scanf("%d%f", &i, &a);...

Форма ввода:

Введите i и a

В фигурные скобки принято заключать альтернативные фрагменты формы ввода или вывода. В данном примере значения i и a можно располагать на одной строке экрана, разделяя их пробелами, а можно на разных строках, разделяя их нажатием клавиши Enter.

Заметим также, что при вводе строк символов с помощью функции scanf() действуют более сложные правила. Так, в буфер устройства ввода считываются все символы до нажатия Enter, а в вводимую строковую переменную передаются символы до первого пробела. Такой принцип работы scanf() имеет свои преимущества, но они слишком трудны при начальном освоении языка Си. Поэтому для ввода и вывода строк лучше пользоваться функциями gets() и puts().

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

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

В таблице 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||с образует правый операнд операции присваивания. (Обратите внимание на то, что значение, присваиваемое а , есть нуль или единица.)

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

В четвертом примере показано неверное выражение, которое приведет к ошибке при компиляции. Операция равенства (==) имеет наибольший приоритет, поэтому p==0 группируется в операнд. Тернарная операция ?: имеет следующий приоритет. Ее первым операндом является выражение p==0 , вторым операндом - выражение p+=1 . Однако последним операндом тернарной операции будет считаться p , а не p+=2 . так как в данном случае идентификатор p по приоритету операций связан более тесно с тернарной операцией, чем с составной операцией сложения с присваиванием. В результате возникает синтаксическая ошибка, поскольку левый операнд составной операции присваивания не является L-выражением.

Чтобы предупредить ошибки подобного рода и сделать программу более наглядной, рекомендуется использовать скобки. Предыдущий пример может быть корректно оформлен следующим образом:

(р == 0) ? (р += 1) : (р += 2)

Из книги ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ВСТРОЕННЫХ СИСТЕМ. Общие требования к разработке и документированию автора Госстандарт России

Из книги Delphi. Учимся на примерах автора Парижский Сергей Михайлович

Порядок выполнения операторов В сложных выражениях операторы выполняются в следующем порядке:1. ().2. not.3. *, /, div, mod, and, shl, shr, as.4. +, –, or, xor.5. =, <>, <, >, <=, >=, in,

Из книги Windows Script Host для Windows 2000/XP автора Попов Андрей Владимирович

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

Из книги UNIX: взаимодействие процессов автора Стивенс Уильям Ричард

9.6. Приоритет чтения и записи В нашей реализации блокировок чтения-записи в разделе 8.4 приоритет предоставлялся ожидающим записи процессам. Теперь мы изучим детали возможного решения задачи читателей и писателей с помощью блокировки записей fcntl. Хочется узнать, как

Из книги TCP/IP Архитектура, протоколы, реализация (включая IP версии 6 и IP Security) автора Фейт Сидни М

6.13.4 Приоритет и тип обслуживания Первоначальным спонсором набора протоколов TCP/IP было Министерство обороны США, для которого было важно задание приоритетов датаграмм. Приоритеты мало используются вне военных и правительственных организаций. Для приоритета

Из книги HTML 5, CSS 3 и Web 2.0. Разработка современных Web-сайтов. автора Дронов Владимир

22.6.1 Приоритет Поле Priority выполняет две функции. При управлении нагрузкой для трафика TCP большим номерам соответствуют управляющие пакеты и интерактивный трафик, а меньшим номерам - обычный трафик. Определены следующие значения:0 Трафик не специфицирован1 Заполняющий

Из книги HTML 5, CSS 3 и Web 2.0. Разработка современных Web-сайтов автора Дронов Владимир

Из книги QNX/UNIX [Анатомия параллелизма] автора Цилюрик Олег Иванович

Приоритет операторов Последний вопрос, который мы здесь разберем, - приоритет операторов. Как мы помним, приоритет влияет на порядок, в котором выполняются операторы в выражении.Пусть имеется следующее выражение:a = b + c - 10;В этом случае сначала к значению переменной b

Из книги Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ автора Борри Хелен

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

Из книги Язык Си - руководство для начинающих автора Прата Стивен

Приоритет операторов Приоритет определяет порядок, в котором операторы и создаваемые ими значения вычисляются в выражении.Когда выражение содержит несколько операторов одного и того же типа, операторы вычисляются слева направо, если только не существует конфликта,

Из книги C++ для начинающих автора Липпман Стенли

Порядок выполнения операций Рассмотрим следующую строку: butter = 25.0 + 60.0 * n / SCALE; В этом операторе имеются операции сложения, умножения и деления. Какая операция будет выполнена первой? Будет ли 25.0 складываться с 60.0, затем результат 85.0 умножаться на n, а произведение

Из книги Разработка ядра Linux автора Лав Роберт

1.2.1. Порядок выполнения инструкций По умолчанию инструкции программы выполняются одна за другой, последовательно. В программеint main(){readIn();sort();compact();print();return 0;}первой будет выполнена инструкция readIn(), за ней sort(), compact() и наконец print().Однако представим себе ситуацию, когда

Из книги Описание языка PascalABC.NET автора Коллектив РуБоард

Приоритет процесса Наиболее широко распространенным типом алгоритмов планирования является планирование с управлением по приоритетам (priority-based). Идея состоит в том, чтобы расположить процессы по порядку в соответствии с их важностью и необходимостью использования

Из книги автора

Барьеры и порядок выполнения В случае, когда необходимо иметь дело с синхронизацией между разными процессорами или разными аппаратными устройствами, иногда возникает требование, чтобы чтение памяти (load) или запись в память (save) выполнялись в том же порядке, как это

Из книги автора

Порядок выполнения операций процессором Вспомните из материала главы 9, "Средства синхронизации в ядре", что для различных аппаратных платформ процессоры в разной степени изменяют порядок выполнения программных инструкций. Для некоторых процессоров порядок выполнения

Из книги автора

Приоритет операций Приоритет определяет порядок выполнения операций в выражении. Первыми выполняются операции, имеющие высший приоритет. Операции, имеющие одинаковый приоритет, выполняются слева направо.Таблица приоритетов операций @, not, ^, +, - (унарные), new 1

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

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

Выражения

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

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

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

Большинство операций в языке 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 , либо отдельный фрагмент метода.