Выражения языка С лекция 4
План лекции • Классы, приоритеты и ассоциативность операций языка Си • Выражения l-value • Порядок вычисления выражений, точки следования, побочные эффекты • Выражения и подвыражения • Неявные преобразования типов
Обзор операций языка Си 1/3 • Операции Си делятся на 5 классов по числу и расположению аргументов • Запись одной операции состоит из одной или нескольких лексем Класс Число аргументов Положение операции отн. аргументов Атомарные 0 Префиксные 1 Перед Постфиксные 1 После Бинарные 2 Между Тернарные 3 Между
Обзор операций языка Си 2/3 • Операции связываются с аргументами по возрастанию своих приоритетов • Приоритеты задаются целыми числами П(*) 13 12 12 П(+) 12 13 12 Возможная расстановка скобок в х*х+у*у (х*х)+(у*у) (х*(х+у))*у, х*((х+у)*у) ((х*х)+у)*у, (х*(х+у))*у, (х*х)+(у*у), х*(х+(у*у)), х*((х+у)*у)
Обзор операций языка Си 3/3 • Ассоциативность бинарных операций задает расстановку скобок в выражениях, содержащих операции одного приоритета • Левоассоциативные -- слева направо • Правоассоциативные -- справа налево • Операции языка Си одного приоритета имеют одинаковую ассоциативность – Иначе расстановка скобок неоднозначна А(-) А(+) Расстановка скобок в х-х+у-у л л ((х-х)+у)-у п п х-(х+(у-у))
Приоритеты операций в языке Си Лексемы Операция Класс Приор-т Ассоц-ность Переменные Константы Доступ к значению константы или переменной атомарный 16 нет a[k] Доступ к элементу массива постфиксный 16 слева направо f(…) Вызов функции постфиксный 16 слева направо . Доступ к полю структуры или объединения постфиксный 16 слева направо -> Доступ к полю структуры или объединения через указатель постфиксный 16 слева направо k++ k-- Доступ к значению k и послед. увеличение или уменьшение k на 1 постфиксный 16 слева направо ++k --k Увеличение или уменьшение k на 1 и послед. доступ к полученному значению k префиксный 15 справа налево sizeof Размер значения или типа в байтах префиксный 15 справа налево ~ Побитовое НЕ префиксный 15 справа налево ! Логическое НЕ префиксный 15 справа налево -+ Смена знака числа (-) или НОП (+) префиксный 15 справа налево & Взятие адреса префиксный 15 справа налево
Приоритеты операций в языке Си Лексемы Операция Класс Приор-т Ассоциативность * Доступ через указатель префиксный 15 справа налево (имя типа) Явное изменение (преобразование) типа префиксный 14 справа налево */% Умножение, деление, остаток от деления бинарный 13 слева направо +- Сложение, вычитание чисел бинарный 12 слева направо << >> Сдвиг влево или вправо в 2 с. с. бинарный 11 слева направо < > <= >= Сравнение чисел бинарный 10 слева направо == != Проверка равенства и различия бинарный 9 слева направо & Побитовое И бинарный 8 слева направо ^ Побитовое исключающее ИЛИ бинарный 7 слева направо | Побитовое ИЛИ бинарный 6 слева направо && Логическое И бинарный 5 слева направо || Логическое ИЛИ бинарный 4 слева направо с ? в 1 : в 2 в 1 (если с != 0) или в 2 (если с == 0) тернарный 3 справа налево
Приоритеты операций в языке Си Лексемы Операция Класс Приор-т Ассоц-ность = += -= *= /= %= <<= >>= &= ^= |= Вычисление правого аргумента и послед. запись полученного значения в ячеку памяти, определяемую левым аргументом (присваивание) бинарный 2 справа налево , Последовательное вычисление аргументов бинарный 1 слева направо
Первичные выражения • • Идентификатор переменной или функции Константа Строковый литерал (является lvalue) Выражение, взятое в круглые скобки
Доступ к элементу массива • A[k] • A
Выражения и подвыражения <выражение>: : = <выражение-присваивания> | <выражение> ', ' <выражение-присваивания> : : = <условное-выражение> | <унарное-выражение> <оператор-присваивания> <выражение-присваивания> <оператор-присваивания>: : = '=' | '*=' | '/=' | '%=' | '+=' | '-=' | '<<=' | '>>=' | '&=' | '^=' | '|='
Выражения и подвыражения <условное-выражение> : : = <логическое-ИЛИ-выражение> | <логическое-ИЛИ-выражение> '? ' <выражение> ': ' <условное-выражение> <константное-выражение> : : = <условное-выражение> <логическое-ИЛИ-выражение>: : = <логическое-И-выражение> | <логическое-ИЛИ-выражение> '||' <логическое-И-выражение>
Выражения и подвыражения <логическое-И-выражение>: : = <ИЛИ-выражение> | <логическое-И-выражение> '&&' <ИЛИ-выражение>: : = <исключающее-ИЛИ-выражение> | <ИЛИ-выражение> '|' <исключающее-ИЛИ-выражение>: : = <И-выражение> | <исключающее-ИЛИ-выражение> '^' <И-выражение>
Выражения и подвыражения <И-выражение>: : = <выражение-равенства> | <И-выражение> '&' <выражение-равенства>: : = <выражение-отношения> | <выражение-равенства> '==' <выражение-отношения> | <выражение-равенства> '!=' <выражение-отношения>
Выражения и подвыражения <выражение-отношения>: : = <сдвиговое-выражение> | <выражение-отношения> '<' <сдвиговое-выражение> | <выражение-отношения> '>' <сдвиговое-выражение> | <выражение-отношения> '<=' <сдвиговое-выражение> | <выражение-отношения> '>=' <сдвиговое-выражение> x >> 2 x < y < z << 2 (x < y) < (z << 2)
Выражения и подвыражения <сдвиговое-выражение>: : = x+y <аддитивное-выражение> x >> y+z | <сдвиговое-выражение> '>>' <аддитивное-выражение> x << (y+z) | <сдвиговое-выражение> '<<' <аддитивное-выражение>: : = x*y <мультипликативное-выражение> x+y+z | <аддитивное-выражение> '+' <мультипликативное-выражение> | <аддитивное-выражение> '-' <мультипликативное-выражение> x - y - z
Выражения и подвыражения <мультипликативное-выражение>: : = (double)x <выражение-приведенное-к-типу> x*y | <мультипликативное-выражение> '*' <выражение-приведенное-к-типу> (x / y) / z | <мультипликативное-выражение> '/' <выражение-приведенное-к-типу> x%y | <мулътипликативное-выражение> '%' <выражение-приведенное-к-типу>
Выражения и подвыражения <выражение-приведенное-к-типу>: : = <унарное-выражение> &x | '(' <имя-типа> ')' (int*) &x <выражение-приведенное-к-типу> <унарное-выражение>: : = x++ <постфиксное-выражение> ++(x--) | '++' <унарное-выражение> --(x++) | '--' <унарное-выражение> * (int*) &x | <унарный-оператор> <выражение-приведенное-к-типу> sizeof(x) | 'sizeof' <унарное-выражение> | 'sizeof' '(' <имя-типа> ')' sizeof(int) <унарный-оператор>: : = '&' | '*' | '+' | '-' | '~' | '!'
Выражения и подвыражения <постфиксное-выражение>: : = <первичное-выражение> | <постфиксное-выражение> '[' <выражение> ']' | <постфиксное-выражение> '(' [<список-аргументов-выражений>] ')' | <постфиксное-выражение> '. ' <идентификатор> | <постфиксное-выражение> '->' <идентификатор> | <постфиксное-выражение> '++' | <постфиксное-выражение> '--' x A[x] f (x, y) point. x point->x i++ i--
Выражения и подвыражения <первичное-выражение>: : = <идентификатор> | <константа> | <строка> | '(' <выражение> ')' x 1. 25 f "ABCDEF" (x+y)
Выражения и подвыражения <константа>: : = <целая-константа> | <символьная-константа> | <константа-с-плавающей-точкой> | <константа-перечисление>
Выражения и подвыражения <список-аргументов-выражений>: : = <выражение-присваивания> | <список-аргументов-выражений> ', ' <выражение-присваивания>
• Выражения и подвыражения • Классы, приоритеты и ассоциативность операций языка Си • Далее – Выражения l-value – Порядок вычисления выражений, точки следования, побочные эффекты – Неявные преобразования типов
Выражения l-value • Ошибка или нет: – (A[i] < A[j] ? A[i] : A[j]) = 1 // min(A[i], A[j]) заменить на 1 – A[ A[i] < A[j] ? i : j ] = 1 • Выражения языка Си, значениям которых гарантированно соответствует ячейка памяти, называются l-value • Значениям, которые хранятся только в регистрах процессора, не соответствует никакая ячейка памяти • Только 5 видов выражений в языке Си являются l-value – см. след. слайд – Остальные виды выражений – не l-value
Выражения l-value • l-value получаются при следующих операциях Доступ к значению переменной Доступ через указатель * Доступ к элементу массива a[k] Доступ к полю структуры или объединения student. name – Доступ к полю структуры или объединения через указатель pstudent->name – – • Все остальные операции дают выражения, не являющиеся l-value
Выражения l-value • Операции, требующие l-value – Левый аргумент во всех видах присваивания =, += и т. п. – Взятие адреса & – Префиксные и постфиксные ++ и --
Выражения l-value • Пример 1 int x; x = 2; // x – l-value int A[10]; A[5] = 5+x; // A[5] – l-value, 5+x – не l-value • Пример 2 int x, y; (x < y ? x : y) = 1; *(x < y ? &x : &y) = 1; // ошибка, т. к. (x < y ? x : y) не l-value // ОК, т. к. *(x < y ? &x : &y) – l-value • Пример 3 (A[i] < A[j] ? A[i] : A[j]) = 1; A[ A[i] < A[j] ? i : j ] = 1; // ошибка // ОК
Точки следования, побочные эффекты • Побочный эффект вычисления выражения – это факт изменения содержимого ячеек памяти в процессе вычисления выражения – Присваивание • x = 1; – Сложный побочный эффект • i = 0; A[i++] = i++; // чему равно i – 0 или 1? • В каком порядке выполнятся = и ++?
Точки следования, побочные эффекты • Точка следования (sequence point) -- точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих ещё отсутствуют
Точки следования, побочные эффекты 1. Между вычислением левого и правого аргументов в операциях &&, || и , (запятая) 2. Между вычислением первого и второго или третьего аргументов в операции ? : 3. В конце всего выражения 4. Перед входом в вызываемую функцию 5. В объявлении с инициализацией на момент завершения вычисления инициализирующего значения 6. В остальном порядок выполнения операций определяет компилятор
Точки следования, побочные эффекты • Пример 1 while (*p++ != 0 && *q++ != 0) *p = *q; • Все побочные эффекты *p++ != 0 проявятся до начала вычисления *q++ != 0 – Правило 1 • Все побочные эффекты *p++ != 0 и *q++ != 0 проявятся до начала вычисления *p = *q – Правило 3 • Что делает этот цикл while?
Точки следования, побочные эффекты • Пример 2 int A[3] = {1, 0, 2}, *p = A; int a = (*p++) ? (*p++) : 0; // чему равно a? • Точка следования находится после первого *p++ • p уже увеличена на 1 при вычислении второго *p++
Точки следования, побочные эффекты • Пример 3 int i = 0, j = i++, k = i++; int x = f(i++) + g(j++) + h(k++); • Каждая из переменных i, j и k принимает новое значение перед входом в f, g и h соответственно • Порядок вызова функций f(), g(), h() неопределён • Порядок инкремента i, j, k неопределён • Если i, j и k – глобальные переменные, то значения j и k неопределены внутри f, значения i и k неопределены внутри g, значения i и j неопределены внутри h
Неявные преобразования типов • Над числами • Над указателями • Других нет
Неявные преобразования чисел • • • Если какой-либо из аргументов имеет тип long double, то другой приводится к long double В противном случае, если какой-либо из аргументов имеет тип double, то другой приводится к double В противном случае, если какой-либо из аргументов имеет тип float, то другой приводится к float В противном случае для обоих аргументов осуществляется целочисленное повышение; затем, если один из аргументов имеет тип unsigned long int, той другой преобразуется в unsigned long int В противном случае, если один из аргументов принадлежит типу long int, а другой -unsigned int, то результат зависит от того, покрывает ли long int все значения unsigned int – Если это так, то unsigned int приводится к long int – Если нет, то оба аргумента преобразуются в unsigned long int • • В противном случае, если один из аргументов имеет тип long int, то другой приводится к long int В противном случае, если один из аргументов -- unsigned int, то другой приводится к unsigned int В противном случае оба аргумента имеют тип int Подробно о каждом преобразовании см. след. слайды
Неявные преобразования чисел • Целочисленное повышение • Значения типов enum, signed char, short int, unsigned char, unsigned short int автоматически преобразуются в int (если значение представимо как int) или в unsigned int (в противном случае) • Для всех известных компиляторов enum, char, short int представимы как int
Неявные преобразования чисел • Целочисленные преобразования из типа со знаком ST в тип без знака UT – Если sizeof(ST) == sizeof(UT), то битовое представление не меняется – Если sizeof(ST) < sizeof(UT), то битовое представление со знаком дополняется старшими нулями – Если sizeof(ST) > sizeof(UT), то старшие разряды отбрасываются • Целочисленные преобразования из типа без знака UT в тип со знаком ST – Если sizeof(ST) >= sizeof(UT), то битовое представление не меняется – Если sizeof(ST) < sizeof(UT), то результат зависит от компилятора
Неявные преобразования чисел • Преобразования целые <--> числа с плавающей точкой – С плавающей точкой --> целое • Дробная часть отбрасывается – int x = (int)1. 25; // x == 1 • Если полученное значение выходит из диапазона целого типа, то результат неопределен – int x = (int)1 e 20; // рез-т неопределён – Целое --> с плавающей точкой • Если целое входит в диапазон типа с плавающей, но представляется неточно, то одно из двух ближайших значений с плавающей • Если выходит из диапазона типа с плавающей, то результат неопределен
Неявные преобразования чисел • Преобразования чисел с плавающей точкой из типа F 1 в F 2 – Если sizeof(F 1) <= sizeof(F 2), то значение не меняется (но может измениться битовое представление) – Если sizeof(F 1) > sizeof(F 2) и входит в диапазон F 2, то одно из двух ближайших значений – Если sizeof(F 1) > sizeof(F 2) и не входит в диапазон F 2, то неопределено
Неявные преобразования указателей • В операциях присваивания и сравнения целочисленное константное выражение со значением 0 автоматически преобразуется в указатель любого типа • Результатом преобразования 0 в указатель является NULL, отличный от всех остальных указателей – NULL не соответствует ни одной ячейке памяти
Неявные преобразования указателей • В операциях присваивания и сравнения указатель на значение типа T автоматически преобразуется в указатель на значение типа const T и на значение типа volatile T
Неявные преобразования указателей • Указатель типа void * автоматически преобразуется к указателю любого типа – Если результат подвергнуть явному обратному преобразованию, то мы получим прежний указатель • Значение типа функция автоматически преобразуется к типу указатель на функцию • Значение типа указатель на функцию автоматически преобразуется к типу функция
Явные преобразования указателей • • • Для указателей допускаются и другие преобразования, но в связи с ними возникает проблема зависимости результата от реализации. Эти преобразования должны быть специфицированы явным оператором преобразования типа или оператором приведения Указатель можно привести к целочисленному типу, достаточно большому для его хранения; требуемый размер зависит от реализации. Функция преобразования также зависит от реализации Объект целочисленного типа можно явно преобразовать в указатель. Если целое получено из указателя и имеет достаточно большой размер, это преобразование даст тот же указатель; в противном случае результат зависит от реализации Указатель на один тип можно преобразовать в указатель на другой тип. Если исходный указатель ссылается на объект, должным образом не выровненный по границам слов памяти, то в результате может произойти ошибка адресации. Если требования на выравнивание у нового типа меньше или совпадают с требованиями на выравнивание первоначального типа, то гарантируется, что преобразование указателя в другой тип и обратно его не изменит; понятие "выравнивание" зависит от реализации, однако в любой реализации объекты типа char предъявляют минимальные требования на выравнивание. Указатель может быть преобразован в другой указатель того же типа с добавлением или удалением квалификаторов того типа объекта, на который этот указатель показывает. Новый указатель, полученный добавлением квалификатора, имеет то же значение, но с дополнительными ограничениями , внесенными новыми квалификаторами. Операция по удалению квалификатора у объекта приводит к тому, что восстанавливается действие его начальных квалификаторов, заданных в объявлении этого объекта. Наконец, указатель на функцию может быть преобразован в указатель на функцию другого типа. Вызов функции по преобразованному указателю зависит от реализации; однако, если указатель еще раз преобразовать к его исходному типу, результат будет идентичен вызову по первоначальному указателю.
Заключение • Выражения и подвыражения • Классы, приоритеты и ассоциативность операций языка Си • Выражения l-value • Порядок вычисления выражений, точки следования, побочные эффекты • Неявные преобразования типов


