Лекция 3. Операторы и выражения

Архитектура программы

  • символы
  • лексемы
  • операторы и выражения
  • функции
  • модули
_images/pagoda.png

Выражения и операторы

Определение

Операторы и выражения - запись действия в программе.

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

Понятие оператора

Существуют операторы преобразования данных и операторы управления работой программы.

Операции

Характеристики операций

  • Ассоциативность (левоассоциативные, правоассоциативные)
  • Количество аргументов (унарные, бинарные, тернарные)
  • Приоритет
  • Префиксная и постфиксная форма (для ++, — —)

Ассоциативность

a=b=c=d=e

a+b+c+d+e

Аргументы

a+b

!a

(a>b)?c:d

Приоритет

a+b*c/d-e

a=b==c

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

Результат операций

В языке Си у операций могут быть следующие результаты:

  • Числовая константа, означающая результат присвоения, математического выражения, адрес ячейки памяти.
  • Числовая константа, означающая логическое значение (0 - ложь, 1 - истина).
  • Ссылка на область памяти.

В Си не имеется встроенного логического типа со значениями true и false, но любое ненулевое целое значение означает истину, а нулевое - ложь.

Арифметические операции

Знак Операция
    *
Умножение
/ Деление и целочисленное деление нацело
% Деление по модулю и остаток от деления
    +
Сложение
    -
Вычитание

Знак / всегда означает деление. Однако если с обеих сторон от этого знака стоят целые величины (константы, переменные или их комбинации), он означает целочисленное деление. Если в результате такого деления получается остаток, С++ его отбрасывает.

Знак % означает выделение остатка при целочисленном делении. Эта операция требует, чтобы с обеих сторон от ее знака стояли целые величины

a = 10/2;       // 5 (остатка нет)
b = 300/100;    // 3 (остатка нет)
c = 10/3;       // 3 (остаток отброшен)
d = 300/165;    // 1 (остаток отброшен)
e = 10%3;       // 1 (остаток)
f = 20/4;       // 5 (остатка нет)

Присваивание

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

a=b=c=d=10;
value=15*(a=9-b);

Существуют два класса выражений:

l-value (левостороннее выражение. Может стоять слева от присваивания)

r-value (правостороннее выражение. Может стоять справа от присваивания)

Составное присваивание

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

Операция Пример Эквивалент
  • +=
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;

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

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

int a=10,b=20;
float c=7.9,d;

d=c+(float)a;        // a приводится к float
b=(int)(d+(float)b); // двойное приведение

Сравнение

Операции сравнения являются бинарными и используются для сравнения двух значений

Операции Описание
== Равно
> Больше
< Меньше
>= Больше или равно
<= Меньше или равно
!= Не равно

Результатом операции сравнения выступают целые числа 0 и 1. Первое означает ложь, второе - истину.

Не следует путать присваивание = и равенство ==

Логические операции

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

Для логических умножения и сложения действует принцип: если результат первого подвыражения определяет результат всего выражения, то второе и последующие подвыражения не выполняются.
d = (a==b) && printf("Hello\n");

В приведенном примере, если \(a\) равно \(b\) , строка Hello появляется на экране, иначе не появляется.

Значения операций

Пример выражения Результат
-4+6 2
c=3+8 11
5>3 1
6+(c=3+8) 17
6+c=3+8 Ошибка!
z=(x>y)?1:0;
max=(a>b)?a:b;

Инкремент и декремент

Данные операции могум использоваться в префиксной и постфиксной формах. основная разница - изменение приоритета.

Код

int a=0,b=0;
a=++b;

будет эквивалентен следующему

int a=0,b=0;
b=b+1;
a=b;

Код

int a=0,b=0;
a=b++;

будет эквивалентен следующему

int a=0,b=0;
a=b;
b=b+1;

Правила вычислений

  1. Постфиксная форма
  2. СНАЧАЛА переменная изменяется на 1;
  3. и только после этого используется в выражении.
  4. Префиксная форма
  5. старое значение переменной сохраняется для использования в дальнейшем выражении, в котором встретилась эта переменная;
  6. и только ПОСЛЕ этого ее значение СРАЗУ ЖЕ изменяется на 1.

Операции инкремента/декремента

Корректные выражения

int x=10;
int y=2;
int z=x++ + --y;
// вывод 11
printf("%d",z);

Некорректные выражения

int y=2,n=3;
// результат может быть разным
int nextnum=(y+n++)*n;
// вывод 4 15 в MS VC++ 2005
printf("%d %d",n,nextnum);
z=++x + x++; // результат не определён
++x++;       // попытка изменить l-value

Что можно сказать о следующей программе?

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

Неопределенное поведение

Определение (см. Википедию)

Неопределённое поведение (англ. undefined behaviour) — свойство некоторых языков программирования (наиболее заметно в Си), программных библиотек и аппаратного обеспечения в определённых маргинальных ситуациях выдавать результат, зависящий от реализации компилятора (библиотеки, микросхемы) и случайных параметров

Пример:

int i = 5;
i = ++i + ++i;

При его выполнении переменная i может принять значения 13 или 14 для C/C++, 13 для Java, PHP и C#, 12 при реализации на LISP.

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

Операторы

Основные алгоритмические конструкции

Основная теорема структурного программирования

Программа для решения любой задачи может быть составлена из комбинации следования, ветвления и цикла (Бойм-Якопини, 1966).

  • следование
  • ветвление
  • цикл
_images/sledovanie.png _images/vetvlenie.png _images/povtorenie.png

Классификация операторов

  • простые
  • составные
  • прочие (оператор-выражение)

Оператор if

Оператор if

Простые формы

if(exp1)
   exp2;

if(exp1)
   exp2;
else
   exp3;

if(exp1)
   exp2;
else if(exp3)
   exp4;

Составные формы

if(exp1)
{
  ...
  ...
}
else if (expr2)
{
  ...
  ...
}
else
{
  ...
  ...
}

Примеры if

Несколько версий одной и той же программы

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;
}
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;
}
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;
}
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;
}

Удачные/неудачные конструкции

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;
}
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

Сложные выражения

Замечание

С позволяет конструировать очень сложные выражения. Эта сложность должна быть оправдана.

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

switch(exp1)
{
   case const1:
     exp2;
   case const2:
     exp3;
   ...
   default:
     expN;
}
switch(exp1)
{
   case const1:
      exp2;
      break;
   case const2:
      exp3;
      break;
   default:
      expN;
}
int count=0;
int count_a=0;
int count_b=0;
...
switch(ch)
{
   case 'a':
      count_a++;
   case 'b':
      count_b++;
   default:
      count++;
}
switch(ch)
{
   case 'a':
      count_a++;
      break;
   case 'b':
      count_b++;
      break;
   default:
      count++;
}

Вопрос: Чему будут равны значения переменных, если строка: babah!?

Оператор while

Операторы цикла

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

  • Циклы с предусловием: while(), for()
  • Циклы с постусловием: do..while()

Оператор while

while (exp1)
  exp2;

while(exp1)
{
   expr2;
   expr3;
   ...
}
int count=10;
while(count>=0)
{
   puts("Hello!");
   count--;
}
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]!=0)
   putchar(str[i++]);


char str[]="A simple string";
int i=0;

while(str[i])
   putchar(str[i++]);
char str[]="A simple string";
int i=0,count=0;

while(str[i])
{
   if(str[i]>='A'&&str[i]<='Z')
      count++;
   i++;
}

Найдите ошибку:

char str[]="A simple string";
int i=0,count=0;

while(str[i])
   if(str[i]>='A'&& str[i++]<='Z')
      count++;
char str[]="A simple string";
int i=0;

while(str[i])
   if(str[i]>='A'&&str[i]<='Z')
      putchar(str[i]+'a'-'A');
char str[]="A simple string";
int len=0;

while(str[len++]);
   printf("String length:%d\n",
           len-1);

Ошибки при организации while

В чем состоит ошибка?

int main(){ /* печать фразы 10 раз */
{
   int i;
   while(i < 10){
      printf("%d\n", i+1);
      i++;
   }
}

Оператор do while

Оператор do while

Это цикл с постусловием. Тело цикла выполняется как минимум 1 раз

do
   exp1;
while(exp2);

do
{
   exp1;
   exp2;
   ...
}
while(exp1);
int val;
do
{
   fflush(stdin);
   printf("Enter a positive number\n");
   scanf("%d",&val);
}
while(val<=0);

Оператор for

Оператор for

Формат

for(exp1;exp2;exp3)
   exp4;

for(exp1;exp2;exp3)
{
   exprs;
}

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

exp1
exp2  ->
exp4
exp3
exp2  ->
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;
}
int main()
{
   const int ROWS=6;
   const int CHARS=6;
   int row;
   char ch;
   for(row=0;row<ROWS;row++)
   {
      for(ch=('A'+row);ch<('A'+CHARS);ch++)
          putchar(ch);
      putchar('\n');
   }
   return 0;
}

Оператор for

ABCDEF
BCDEF
CDEF
DEF
EF
F

Операторы управления

  • goto - безусловный переход на метку.
  • return - возвращение из функции.
  • break - прерывание тела цикла и switch
  • continue - переход на следующую итерацию цикла.

Оператор goto усложняет отладку программы и сильно портит стиль разработчика (‘’спагетти-код’‘)!

Спагетти-код

Пример спагетти кода на языке BASIC:

10 i = 0
20 i = i + 1
30 IF i <= 10 THEN GOTO 70
40 IF i > 10 THEN GOTO 50
50 PRINT "Программа завершена."
60 END
70 PRINT i; " квадрат = "; i * i
80 GOTO 20

Тоже самое, но без goto:

FOR i = 1 TO 10
    PRINT i; " квадрат = "; i * i
NEXT i
PRINT "Программа завершена."

Оператор break

Назначение break - прерывать цикл или тело switch:

/* Распечатываем числа с 1 по 15 и выходим */
int i=1;
for(;;)
{
   printf("%d\n",i);
   if(i==15)
      break;
}

Оператор continue

/* Распечатываем нечётные числа  с 1 по 100 */
for(int i=1;i<=100;i++)
{
   if(i%2==0)
      continue;
   printf("%d\n",i);
}

Дополнительная информация

Определение

Точка следования (англ. 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 инвертировать символы в строке?
  • Перечислите операторы управления ходом выполнения программы.
  • Что такое спагетти-код?
  • Что такое точки следования?