Лекция 3. Операторы и выражения ####################################################################################### Архитектура программы =========================================================== * символы * лексемы * операторы и выражения * функции * модули .. image:: _static/03/pagoda.png Выражения и операторы `````````````````````````````````````````````````````````` .. admonition:: Определение **Операторы и выражения** - запись действия в программе. * Программа строится из **операторов**. * Операторы включают в себя **выражения**. * **Выражение** представляет собой комбинацию **операций** и **операндов**. * **Выражение** имеет некоторое значение (которое можно сохранить в памяти), а **оператор** значения не имеет. * У **оператора** есть некоторый результат (побочный эффект), например повторение действия. Понятие оператора `````````````````````````````````````````````````````````` Существуют **операторы преобразования** данных и **операторы управления** работой программы. .. admonition:: Примеры выражений .. admonition:: Примеры операторов Операции =========================================================== .. list-table:: * - **Тип** - **Обозначения** * - Арифметические - +,-,*,/,% * - Присваивание - = * - Сравнение - ==,!=,>,<,>=,<= * - Арифметические с присваиванием - +=,-=,/=,*=,%= * - Инкремент, декремент - ++,--- --- * - Логические - !,\ - \ - ,|| * - Адресные - *,\ - * - Побитовые (поразрядные) - \ - , |, :math:`\widehat{}` , >>, <<, ~ * - Побитовые с присваиванием - \ - =, |=, :math:`\widehat{}` =, >>=, <<=, ~= * - Доступ к полям структур и объединений - ., -> * - Обращение к элементу массива - [] * - Изменение знака - +,- * - Приведение типа - (тип) * - Прочие - ? : , sizeof(), запятая Характеристики операций `````````````````````````````````````````````````````````` * Ассоциативность (левоассоциативные, правоассоциативные) * Количество аргументов (унарные, бинарные, тернарные) * Приоритет * Префиксная и постфиксная форма (для ++, --- ---) .. admonition:: Ассоциативность a=b=c=d=e a+b+c+d+e .. admonition:: Аргументы a+b !a (a>b)?c:d .. admonition:: Приоритет a+b*c/d-e a=b==c Приоритет операций `````````````````````````````````````````````````````````` .. list-table:: * - **Ранг операции** - **Операции** * - 1 - **() [] -> .** * - 2 - **! ~** унарные \alert{+ - * \ - sizeof (type)} * - 3 - бинарные арифметические: *** / %** * - 4 - бинарные арифметические **+ --** * - 5 - поразрядные сдвига: ** << >> ** * - 6 - сравнение: **< <= > >=** * - 7 - сравнение: **== !=** * - 8 - побитовые: \alert{\ - :math:`\widehat{}` |} * - 9 - логические: \alert{\ - \ - ||} * - 10 - тернарная условная: **?:** * - 11 - арифметические и побитовые с присваиванием * - 12 - запятая **,** Результат операций `````````````````````````````````````````````````````````` В языке Си у операций могут быть следующие результаты: * Числовая константа, означающая результат присвоения, математического выражения, адрес ячейки памяти. * Числовая константа, означающая логическое значение (0 - ложь, 1 - истина). * Ссылка на область памяти. В Си не имеется встроенного логического типа со значениями **true** и **false**, но любое *ненулевое* целое значение означает **истину**, а *нулевое* - **ложь**. Арифметические операции `````````````````````````````````````````````````````````` .. list-table:: * - **Знак** - **Операция** * - * - Умножение * - / - Деление и целочисленное деление нацело * - % - Деление по модулю и остаток от деления * - + - Сложение * - - - Вычитание Знак **/** всегда означает деление. Однако если с обеих сторон от этого знака стоят целые величины (константы, переменные или их комбинации), он означает целочисленное деление. Если в результате такого деления получается остаток, С++ его отбрасывает. Знак **%** означает выделение остатка при целочисленном делении. Эта операция требует, чтобы с обеих сторон от ее знака стояли целые величины .. code-block:: c a = 10/2; // 5 (остатка нет) b = 300/100; // 3 (остатка нет) c = 10/3; // 3 (остаток отброшен) d = 300/165; // 1 (остаток отброшен) e = 10%3; // 1 (остаток) f = 20/4; // 5 (остатка нет) Присваивание `````````````````````````````````````````````````````````` Особенностью Си является возможность комбинирования операции присваивания с другими операциями, а также выполнение множественного присваивания. .. admonition:: * .. code-block:: c a=b=c=d=10; value=15*(a=9-b); Существуют два класса выражений: **l-value** (левостороннее выражение. Может стоять слева от присваивания) **r-value** (правостороннее выражение. Может стоять справа от присваивания) Составное присваивание `````````````````````````````````````````````````````````` Эта группа операций позволяет совместить арифметику и присваивание, что дает выразительность и удобочитаемость программам. .. list-table:: * - **Операция** - **Пример** - **Эквивалент** * - + = - B+ = 500; - B= b+ 500; * - -- = - С-- = 50; - C = с -- 50; * - * = - D* = 1.2; - D = d*1.2; * - / = - F/ = 50; - f = f/.50; * - % = - m% = 7; - M = m% 7; Преобразование типов `````````````````````````````````````````````````````````` В различных выражениях могут встречаться данные как одного, так и разных типов. Компилятор может выполнять операцию приведения типов по-умолчанию, действуя согласно простого правила: *короткие типы приводятся к длинным*. Если необходимо изменить стандартное преобразование, то вводят явное приведение типов. .. code-block:: c int a=10,b=20; float c=7.9,d; d=c+(float)a; // a приводится к float b=(int)(d+(float)b); // двойное приведение Сравнение `````````````````````````````````````````````````````````` Операции сравнения являются *бинарными* и используются для сравнения двух значений .. list-table:: * - *Операции* - *Описание* * - == - Равно * - > - Больше * - < - Меньше * - >= - Больше или равно * - <= - Меньше или равно * - != - Не равно Результатом операции сравнения выступают целые числа 0 и 1. Первое означает ложь, второе - истину. Не следует путать **присваивание** **=** и **равенство** **==** Логические операции `````````````````````````````````````````````````````````` Для создания более сложных выражений из простых существуют логические операции, которые реализуют логическое отрицание, умножение и сложение. Для логических умножения и сложения действует принцип: если результат первого подвыражения определяет результат всего выражения, то второе и последующие подвыражения не выполняются. .. admonition:: .. code-block:: c d = (a==b) && printf("Hello\n"); В приведенном примере, если :math:`a` равно :math:`b` , строка *Hello* появляется на экране, иначе не появляется. Значения операций `````````````````````````````````````````````````````````` .. list-table:: * - **Пример выражения** - **Результат** * - -4+6 - 2 * - c=3+8 - 11 * - 5>3 - 1 * - 6+(c=3+8) - 17 * - 6+c=3+8 - Ошибка! .. admonition:: Условная операция .. code-block:: c z=(x>y)?1:0; max=(a>b)?a:b; Инкремент и декремент `````````````````````````````````````````````````````````` Данные операции могум использоваться в **префиксной** и **постфиксной** формах. основная разница - изменение приоритета. *Код* .. code-block:: c int a=0,b=0; a=++b; будет эквивалентен следующему .. code-block:: c int a=0,b=0; b=b+1; a=b; *Код* .. code-block:: c int a=0,b=0; a=b++; будет эквивалентен следующему .. code-block:: c int a=0,b=0; a=b; b=b+1; Правила вычислений #. Постфиксная форма #. СНАЧАЛА переменная изменяется на 1; #. и только после этого используется в выражении. #. Префиксная форма #. старое значение переменной сохраняется для использования в дальнейшем выражении, в котором встретилась эта переменная; #. и только ПОСЛЕ этого ее значение СРАЗУ ЖЕ изменяется на 1. Операции инкремента/декремента `````````````````````````````````````````````````````````` Корректные выражения .. code-block:: c int x=10; int y=2; int z=x++ + --y; // вывод 11 printf("%d",z); Некорректные выражения .. code-block:: c int y=2,n=3; // результат может быть разным int nextnum=(y+n++)*n; // вывод 4 15 в MS VC++ 2005 printf("%d %d",n,nextnum); .. code-block:: c z=++x + x++; // результат не определён ++x++; // попытка изменить l-value Что можно сказать о следующей программе? .. code-block:: c int main() { int x=10,y=20; x++=--y; printf("%d %d\n",x,y); return 0; } \pause **Ошибка компиляции:** *l-value required as left operand of assignment* Неопределенное поведение `````````````````````````````````````````````````````````` .. admonition:: Определение (см. Википедию) **Неопределённое поведение** (англ. undefined behaviour) — свойство некоторых языков программирования (наиболее заметно в Си), программных библиотек и аппаратного обеспечения в определённых маргинальных ситуациях выдавать результат, зависящий от реализации компилятора (библиотеки, микросхемы) и случайных параметров Пример: .. code-block:: c int i = 5; i = ++i + ++i; При его выполнении переменная **i** может принять значения 13 или 14 для C/C++, 13 для Java, PHP и C#, 12 при реализации на LISP. Неопределенность в языке C/C++ связана с тем, что согласно стандартам С и С++ побочные эффекты (то есть инкремент в данном случае) могут быть применены в любой удобный для компилятора момент между двумя **точками следования** (см. раздел *Дополнительная информация*). Операторы =========================================================== Основные алгоритмические конструкции `````````````````````````````````````````````````````````` .. admonition:: Основная теорема структурного программирования Программа для решения любой задачи может быть составлена из комбинации следования, ветвления и цикла (Бойм-Якопини, 1966). .. admonition:: Базовые структуры * следование * ветвление * цикл .. image:: _static/03/sledovanie.png .. image:: _static/03/vetvlenie.png .. image:: _static/03/povtorenie.png Классификация операторов `````````````````````````````````````````````````````````` .. admonition:: По строению * простые * составные .. admonition:: По назначению * прочие (оператор-выражение) Оператор if ----------------------------------------------------------- Оператор **if** `````````````````````````````````````````````````````````` ``Простые формы`` .. code-block:: c if(exp1) exp2; if(exp1) exp2; else exp3; if(exp1) exp2; else if(exp3) exp4; ``Составные формы`` .. code-block:: c if(exp1) { ... ... } else if (expr2) { ... ... } else { ... ... } Примеры if `````````````````````````````````````````````````````````` Несколько версий одной и той же программы .. admonition:: Версия 1 .. code-block:: c int main() { int val; printf("Enter a number: "); scanf("%d",&val); if(val>0) puts("You entered a positive number!"); else if(val<0) puts("You entered a negative number!"); else puts("You entered ZERO!"); return 0; } .. admonition:: Версия 2 .. code-block:: c int main() { char mes[]="You entered"; char numbers[][20]={"a positive number","a negative number","ZERO"}; int val; printf("Enter a number: "); scanf("%d",&val); if(val>0) printf("%s %s!\n",mes,numbers[0]); else if(val<0) printf("%s %s!\n",mes,numbers[1]); else printf("%s %s!\n",mes,numbers[2]); return 0; } .. admonition:: Версия 3 .. code-block:: c int main() { char mes[]="You entered"; char numbers[][20]={"a positive number", "a negative number","ZERO"}; int val,choice; printf("Enter a number: "); scanf("%d",&val); if(val>0) choice=0; else if(val<0) choice=1; else choice=2; printf("%s %s!\n",mes,numbers[choice]); return 0; } .. admonition:: Версия 4 .. code-block:: c int main() { char mes[]="You entered"; char numbers[][20]={"a positive number", "a negative number","ZERO"}; int val; printf("Enter a number: "); scanf("%d",&val); printf("%s %s!\n",mes,numbers[val>0?0:(val<0?1:2)]); return 0; } Удачные/неудачные конструкции `````````````````````````````````````````````````````````` .. code-block:: c if(coord_x>20) if(coord_x<30) flag=0; else flag=1; if(coord_x>20) { if(coord_x<30) flag=0; else flag=1; } .. code-block:: c if(coord_x>20) if(coord_x<30) if(coord_y<40) ; else flag=1; if(coord_x>20 && coord_x<30) flag=0; else flag=1; Удачные и неудачные конструкции `````````````````````````````````````````````````````````` Среди всех приведенных конструкций наиболее удачной следует признать последнюю, так как она лишена нагромождений и воспринимается однозначно. Если необходимо сравнить значение переменной с набором констант, то лучше отказаться от **if** в пользу **switch** Сложные выражения `````````````````````````````````````````````````````````` .. admonition:: Замечание С позволяет конструировать очень сложные выражения. Эта сложность должна быть оправдана. .. code-block:: c int coord_x; int ret,flag; if((ret=scanf("%d",&coord_x))!=1||(coord_x<0||coord_x>40)) printf("Error!\n"); else printf("flag=%d\n",(coord_x%10>0)?flag=1:flag=0); Оператор switch ----------------------------------------------------------- Оператор **switch** `````````````````````````````````````````````````````````` .. code-block:: c switch(exp1) { case const1: exp2; case const2: exp3; ... default: expN; } .. code-block:: c switch(exp1) { case const1: exp2; break; case const2: exp3; break; default: expN; } .. code-block:: c int count=0; int count_a=0; int count_b=0; ... switch(ch) { case 'a': count_a++; case 'b': count_b++; default: count++; } .. code-block:: c switch(ch) { case 'a': count_a++; break; case 'b': count_b++; break; default: count++; } *Вопрос:* Чему будут равны значения переменных, если строка: **babah!**? Оператор while ----------------------------------------------------------- Операторы цикла `````````````````````````````````````````````````````````` В Си существуют операторы с *предусловием* и *постусловием*. Особенность первых состоит в том, что условное выражение находится у них перед телом, так что возможно выполнить тело один раз, а возможно - ни разу. Циклы с постусловием обеспечивают выполнение тела хотя бы один раз, проверочное выражение находится у них после описания тела. * Циклы с предусловием: **while(), for()** * Циклы с постусловием: **do..while()** Оператор **while** `````````````````````````````````````````````````````````` .. code-block:: c while (exp1) exp2; while(exp1) { expr2; expr3; ... } .. code-block:: c int count=10; while(count>=0) { puts("Hello!"); count--; } .. code-block:: c char str[]="A simple string"; int i=0; while(str[i]!='\0') putchar(str[i++]); .. code-block:: c char str[]="A simple string"; int i=0; while(str[i]!=0) putchar(str[i++]); char str[]="A simple string"; int i=0; while(str[i]) putchar(str[i++]); .. code-block:: c char str[]="A simple string"; int i=0,count=0; while(str[i]) { if(str[i]>='A'&&str[i]<='Z') count++; i++; } Найдите ошибку: .. code-block:: c char str[]="A simple string"; int i=0,count=0; while(str[i]) if(str[i]>='A'&& str[i++]<='Z') count++; .. code-block:: c char str[]="A simple string"; int i=0; while(str[i]) if(str[i]>='A'&&str[i]<='Z') putchar(str[i]+'a'-'A'); .. code-block:: c char str[]="A simple string"; int len=0; while(str[len++]); printf("String length:%d\n", len-1); Ошибки при организации while `````````````````````````````````````````````````````````` В чем состоит ошибка? .. admonition:: * .. code-block:: c int main(){ /* печать фразы 10 раз */ { int i; while(i < 10){ printf("%d\n", i+1); i++; } } Оператор do while ----------------------------------------------------------- Оператор **do while** `````````````````````````````````````````````````````````` Это цикл с **постусловием**. Тело цикла выполняется как минимум 1 раз .. code-block:: c do exp1; while(exp2); do { exp1; exp2; ... } while(exp1); .. code-block:: c int val; do { fflush(stdin); printf("Enter a positive number\n"); scanf("%d",&val); } while(val<=0); Оператор for ----------------------------------------------------------- Оператор **for** `````````````````````````````````````````````````````````` Формат .. code-block:: c for(exp1;exp2;exp3) exp4; for(exp1;exp2;exp3) { exprs; } Порядок выполнения .. code-block:: c exp1 exp2 -> exp4 exp3 exp2 -> .. admonition:: Программа инвертирования введённой строки .. code-block:: c int main() { int top=0, bot=0, len; char str[256],temp; fgets(str,256,stdin); len=strlen(str); // определяем длину введенной строки if(str[len-1]=='\n') // удаляем хвостовой \n str[len-1]=0; while(str[bot++]); // bot указывает на символ после \0 for ( bot-=2; top < bot ; top++, bot--) { temp=str[top]; str[top]=str[bot]; str[bot]=temp; } puts(str); return 0; } .. admonition:: Что напечатает программа? .. code-block:: c int main() { const int ROWS=6; const int CHARS=6; int row; char ch; for(row=0;row 10 THEN GOTO 50 50 PRINT "Программа завершена." 60 END 70 PRINT i; " квадрат = "; i * i 80 GOTO 20 Тоже самое, но без **goto**: .. code-block:: none FOR i = 1 TO 10 PRINT i; " квадрат = "; i * i NEXT i PRINT "Программа завершена." Оператор break `````````````````````````````````````````````````````````` Назначение **break** - прерывать цикл или тело **switch**: .. code-block:: c /* Распечатываем числа с 1 по 15 и выходим */ int i=1; for(;;) { printf("%d\n",i); if(i==15) break; } Оператор continue `````````````````````````````````````````````````````````` .. code-block:: c /* Распечатываем нечётные числа с 1 по 100 */ for(int i=1;i<=100;i++) { if(i%2==0) continue; printf("%d\n",i); } Дополнительная информация =========================================================== Точки следования ----------------------------------------------------------- .. admonition:: Определение **Точка следования** (англ. Sequence point) — в программировании любая точка программы, в которой гарантируется, что все побочные эффекты предыдущих вычислений уже проявились, а побочные эффекты последующих еще отсутствуют. В C и C++ определены следующие точки следования: * Между вычислением левого и правого операндов в операторах **&&** (логическом И), **||** (логическом ИЛИ) и операторах-запятых. Например, в выражении ***p++ != 0 && *q++ != 0** все побочные эффекты левого операнда \texbf{*p++ != 0} проявятся до начала каких либо действий в правом. * Между вычислением первого и второго или третьего операндов в операторе условия. В строке **a = (*p++) ? (*p++) : 0** точка находится после первого операнда ***p++**, при выполнении второго **p** уже увеличена на 1. * В конце всего выражения. Эта категория включает в себя инструкции-выражения **a=b;**, выражения в инструкциях **return**, управляющие выражения в круглых скобках инструкций ветвления **if** или **switch** и циклов **while** или **do-while** и все три выражения в круглых скобках цикла **for**. * Перед входом в вызываемую функцию. Порядок, в котором вычисляются аргументы не определен, но эта точка следования гарантирует, что все ее побочные эффекты проявятся на момент входа в функцию. В выражении **f(i++) + g(j++) + h(k++)** каждая из трёх переменных: **i, j** и **k**, принимает новое значение перед входом в **f, g** и **h** соответственно. Однако, порядок вызова функций **f(), g(), h()** неопределён, следовательно неопределён и порядок инкремента **i, j, k**. Значения **j** и **k** в теле функции **f** оказываются неопределенными. Следует заметить, вызов функции нескольких аргументов **f(a,b,c)** не является случаем применения оператора-запятой и не определяет порядок вычисления значений аргументов. * При возврате из функции, на момент когда возвращаемое значение будет скопировано в вызывающий контекст.(Явно описана только в стандарте С++, в отличие от С.) * В объявлении с инициализацией на момент завершения вычисления инициализирующего значения, например, на момент завершения вычисления **(1+i++)** в **int a = (1+i++);** Вопросы для самоконтроля ============================================= * Какие уровни образуют архитектуру программы на С? * Что такое оператор? Выражение? * Чем оператор отличается от выражения? * Какие бывают операторы? * Приведите примеры операторов и выражений. * Приведите пример классификации операций. * Перечислите основные характеристики операций. * Чем левоассоциативные операции отличаются от правоассоциативных? * Как называются операции в зависимости от количества аргументов? * Какие операции относятся к высоко приоритетным? К низко приоритетным? * Что представляет собой истинное значение в Си? Ложное значение? * Чем отличается l-value-выражение от r-value? * В чём заключаются особенности присваивания в языке Си? * Как осуществляется преобразование типов? * Что такое явное и неявное преобразование типов? * Как работает механизм логических операций? * Какие бывают операции инкремента/декремента? * Как можно выразить операцию инкремента в разных формах? * Как вычисляется выражение с инкрементом в префиксной форме? * Как вычисляется выражение с инкрементом в постфиксной форме? * Какими особенностями обладают операции инкремента/декремента? * Что означает термин ''неопределённое поведение''? * Какие алгоритмические конструкции мы относим к основным при структурном программировании? * Как можно классифицировать операторы? * Опишите варианты оператора if. Для чего используется этот оператор? * На что влияет неудачно выбранная конструкция оператора? * Какой должна быть сложность конструкций языка С? * Опишите формат оператора switch. * В чем назначение оператора switch? * Опишите формат оператора while. Для чего используется этот оператор? * Как можно использовать while при обработке строк? * Какие ошибки могут возникать при использовании while? * Опишите формат оператора do..while. * Чем цикл do while отличается от цикла while? * Опишите формат оператора for. * Чем оператор for отличается от других операторов цикла? * Как с помощью for инвертировать символы в строке? * Перечислите операторы управления ходом выполнения программы. * Что такое спагетти-код? * Что такое точки следования?