Опубликован: 05.11.2013 | Доступ: свободный | Студентов: 542 / 46 | Длительность: 11:51:00
Лекция 4:

Язык программирования Си

< Лекция 3 || Лекция 4: 123456 || Лекция 5 >

3.4. Управление вычислениями

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

Условные выражения и условные операторы

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

if (Check For Connection() == TRUE)
  {
    Transmit = 1;
  }
  else
  {
    Transmit = 0;
  }  
      

То же самое будет, если мы вспомним, что функция Check_For_Connection ( ) сама имеет логическое значение.

if (Check For Connection())
  {
    Transmit = 1;
  }
  else
  {
    Transmit = 0;
  }
      

Или, используя условное присваивание:

Transmit = Check For Connection()?1:0;  
      

и наконец:

Transmit = Check For Connection();  
      

В последнем случае мы "вспомнили", что логическое значение "истинно" представляется в языке Си единицей, а "ложно" - нулем. Мы специально не воспользовались средствами приведения (преобразования) типа, чтобы добиться максимальной лаконичности записи.

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

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

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

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

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

Давайте посмотрим на фрагмент программы, определяющий, есть ли в строке, хранящейся в массиве char Str_In [20], стоящие подряд повторяющиеся символы. Напомним, что признаком конца строки в языке Си является символ с кодом '\0'.

int i, Yes; 
char Ch;
i = 0; Ch = Str In[ i] ; Yes = 0; 
while ( Ch != '\0' )
  { if ( Ch == Str_In[ ++i]  )
    Yes = 1;
    Ch  = Str In[ i] ;
  }  
      

Такой цикл будет работать до тех пор, пока не "наткнется" на конец строки. И значение переменной Yes, равное единице, покажет нам, встречались ли в строке стоящие подряд одинаковые символы. Но цикл можно прекратить и раньше, если в инвариант включить проверку значения переменной Yes:

int i, Yes; 
char Ch;
i = 0; Ch = Str In[ i] ; Yes = 0; 
while ( Ch != '\0' && Yes == 0 )
  {
    Yes = ( Ch == Str_In[ ++i] ) ;
    Ch  = Str In[ i] ;
  }
      

Теперь цикл будет прекращаться, как только в строке встретится пара одинаковых символов. В этом случае и тело удалось упростить, так как если соседние символы равны, то переменная Yes становится истинной и цикл прекращается.

Используя форму цикла do-while или, как его еще называют, цикл с постусловием, можно еще сократить текст подобного фрагмента программы:

int i, Yes; 
char Ch;   
i = 0; Ch = Str In[ i] ; 
do
  { Yes = ( Ch == Str_In[ ++i]  )  ;
  Ch = Str In[ i] ;
  }
while ( Ch != '\0' && Yes == 0 );
      

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

Еще один вид цикла имеет заголовок, определяющий более сложные правила выполнения тела. Это цикл for. В заголовке этого цикла в круглых скобках записываются три блока операторов. Первый (инициализация цикла) выполняется один раз перед началом работы. Второй - инвариант цикла, определяющий предусловие выполнения тела. Третий оператор выполняется в цикле сразу после тела. Фактически он является завершением тела - его последним оператором.

Проще всего проиллюстрировать этот вид цикла на примере обнуления первых 20 элементов некоторого массива My_Array:

int i;
for (i = 0;i<20; My Array[ i++] = 0 ) ;
      

В данном примере собственно определено пустое тело. Все его полезные действия (обнуление очередного элемента массива и увеличение индекса - параметра цикла) уложились в третий элемент заголовка. Более сложный и более "классический" пример использования цикла for приведен ниже.

Пусть мы имеем некоторый 8-битовый код в переменной

unsigned char Input Byte  
      

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

unsigned char Reversed Byte; 
unsigned char i;
Reversed Byte = 0;
for (i=0; i < 8; i++)
{
  Reversed Byte <<= 1;  /*  Shift output byte left.  */
  if (Input Byte & 1)  /*  If input byte has 1 in the  first  */
  Reversed_Byte |= 1;  /*  pos. put 1 into the output  byte.  */
  Input Byte >>=1;  /*  Shift input byte right for the  */
}  /* next bit */  
      

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

Reversed_Byte |= Input_Byte & 1;  
      

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

С помощью цикла for можно записать и решение задачи по проверке наличия одинаковых символов в строке:

int i, Yes; 
char Ch;
for (i = 0, Ch = Str_In[ i] ; Ch != '\0' && Yes == 0;)
{
  Yes = ( Ch == Str_In[ ++i]  )  ;
  Ch = Str In[ i] ;
}  
      

За счет эквивалентности значений некоторых типов (символьных, целых и логических) можно найти более короткую запись самого цикла:

for (i = 0 ; Ch = Str_In[ i] && ! Yes = (Ch == Str_In[ i+1] ); ++i);  
      

В этой записи используется следующее свойство: условие инварианта цикла вычисляется до тех пор, пока не будет установлена его истинность или ложность. Так, если Ch = Str_In[ i] приводит к нулевому результату (текущему символу присвоен признак конца строки), то цикл будет прекращен и вторая часть условия вычисляться не будет. Если же был переслан из строки ненулевой символ, то будет вычисляться вторая часть условия Yes = (Ch == Str_In[ i+1] ). И если она истинна (символы равны), то отрицание этого приведет к выходу из цикла.

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

< Лекция 3 || Лекция 4: 123456 || Лекция 5 >