Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основы разработки программного обеспечения на примере языка С. Учебное пособие для СПО

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
2 Мб
Скачать
☆
union Big Word { long Bg long; char *Bg chart [4] ; }
А переменная X_Var такого типа определяется следующим образом:
union Dig Word X_Var;
Данные типа union Big_Word занимают память, необходимую для размещения наибольшего из своих элементов, и могут рассматриваться как аналог вариантной записи в языке Паскаль.
Константы
Константы в языке Си могут иметь одинаковые значения при различных формах своего представления. В особенности это относится к числовым значениям.
10, 012 и 0xA в языке Си будут соответствовать одному и тому же значению 10 или в двоичной форме 1010. Просто числовые константы, начинающиеся с цифры 0, считаются в Си заданными в восьмеричной форме представления, а с префиксом 0х - шестнадцатеричными.
Вещественные числа также можно записывать в различных формах
12.5, 1.2e1, 125e-1, где символ "е" или "Е" используется для отделения мантиссы числа от порядка. Предполагается, что вещественная константа всегда положительна, а предшествующий ей знак "минус", если указан, рассматривается как унарная операция смены знака.
Еще следует обратить внимание на символьные константы. Они записываются в одиночных кавычках: 'A', 'a', ' + ', '-' и т.п. В случае необходимости можно определить символ его кодом в таблице '\12' или '\xA', задавая после обратной наклонной черты номер символа в восьмеричной или шестнадцатеричной системе.
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
71
Ранее говорилось, что следует различать символы и строки символов, которые задаются двойными кавычками. Так, константе "A" будет соответствовать строка из двух символов: собственно 'A' и '\0', т.е. к байту символа будет добавлен второй байт с нулевым значением для представления признака конца строки.
3.2. Унарные операторы
Кроме бинарных операторов, в языке Си существует достаточно большое число унарных операторов.
Например, допустима следующая запись:
х=9; —х++++;
После ее выполнения х будет равен 10. Два минуса слева означают уменьшение значения х на единицу до использования значения переменной в выражении, два раза по два плюса после х означают два раза увеличить х на единицу после использования значения х в расчете выражения.
Попробуйте, например, рассчитать, чему будет равен х после выполнения следующих строчек на языке Си:
int x = 1; x += x++ + ++x;
(запись х + = 5 эквивалента записи х = х + 5).
Любопытно, что некоторые компиляторы не умеют правильно интерпретировать подобную "мешанину" из унарных и бинарных операторов в выражении с одной переменной. Тем более, стоит избегать подобных форм записи, сложных для интерпретации. Это не только дополнительная потеря времени, но и потенциальный источник ошибок.
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
72
3.3. Средства низкого уровня
Как уже было сказано, адрес любой переменной получается при помощи операции "&", указанной перед именем этой переменной. С результатом такой операции можно производить все арифметические действия, доступные для целых чисел.
Фактически полученный с помощью операции вычисления адреса объект является указателем. При арифметических операциях с ними Си руководствуется информацией о типе адресуемого объекта.
Как и в большинстве языков программирования, Си допускает переопределение типов, в ходе которого никаких преобразований внутреннего представления не происходит, однако появляется возможность работать с одним типом как с другим. Для этого достаточно перед переменной поставить в скобках название нового типа:
int iv; char cv; cv=(char)iv;
Кроме работы с адресами и преобразования типов, в языке Си предусмотрен набор битовых операций:
& - поразрядная конъюнкция;
^ - поразрядное исключающее или;
| - поразрядная дизъюнкция;
<< - поразрядный сдвиг влево;
>> - поразрядный сдвиг вправо.
Не стоит их путать с операциями логическими:
&& - логическая конъюнкция;
! - отрицание;
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
73
|| - логическая дизъюнкция.
Так, 2&1 дадут результат 0 (ложно), 2|1 дадут результат 3 (истинно), а
2&&1 дадут 1 (истинно) и 2||1 тоже дадут 1 (истинно).
Поразрядное кодирование
В ряде случаев для представления кодовых значений удобно давать наименования группам разрядов машинного слова или даже отдельным разрядам. Обычно такие значения трактуются как беззнаковые (unsigned) числа. Подобные описания в Си описываются как структуры с битовыми полями. Например:
struct STATUS_FLAG { unsined Overflow :1; unsined Start_Up :1; unsined Source_Code :4; unsined Result_Id :2; } In_Out_Status;
Эта структура описывает 8-битовый байт, разделенный на четыре поля. Первые два из них являются отдельными битами со значениями 0 или
1. Третье поле Source_Code представляет собой нормальное 4­разрядное шестнадцатеричное число, а последнее поле (два разряда) может представлять коды от 0 до 3.
3.4. Управление вычислениями
В язык Си включен достаточно развитый набор средств, позволяющий управлять вычислительным процессом и производить те или иные действия в зависимости от обрабатываемых данных.
Условные выражения и условные операторы
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
74
Например, нам необходимо проверить, что устройство присоединено к системе, для чего проверяется значение логической функции
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();
В последнем случае мы "вспомнили", что логическое значение
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
75
"истинно" представляется в языке Си единицей, а "ложно" - нулем. Мы специально не воспользовались средствами приведения (преобразования) типа, чтобы добиться максимальной лаконичности записи.
Однако эта последняя запись и самая неудобная в смысле сопровождения программного кода. Он стал наименее читаемым и сложно изменяемым при изменении соглашений о связях. С другой стороны, хорошо оптимизирующий компилятор во всех четырех случаях может построить одинаковый код машинной программы.
С точки зрения понимания того, что делается в программе, первая запись является наиболее удачной. Она же наиболее пригодна для тестирования, так как ветви вычислений в ней явно разделены. Кроме того, даже возвращаемое функцией 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' )
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
76
{ 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 );
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
77
Как видим, удалось обойтись без начальной инициализации переменной 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++)
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
78
{ 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);
В этой записи используется следующее свойство: условие инварианта цикла вычисляется до тех пор, пока не будет установлена его
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
79
истинность или ложность. Так, если Ch = Str_In[ i] приводит к нулевому результату (текущему символу присвоен признак конца строки), то цикл будет прекращен и вторая часть условия вычисляться не будет. Если же был переслан из строки ненулевой символ, то будет вычисляться вторая часть условия Yes = (Ch == Str_In[ i+1]
). И если она истинна (символы равны), то отрицание этого приведет к
выходу из цикла.
Совсем не трудно увидеть, что последняя форма записи читается (понимается) сложнее, чем первая. Поэтому далеко не всегда нужно стремиться к подобной лаконичности. Помните: программы пишутся не только для компьютера, но и для людей, которые их будут проверять, сопровождать, вносить в них необходимые изменения.
3.5. Процедуры и функции
В языке Си все процедуры рассматриваются как функции, т.е. все они имеют результат. В редких случаях, когда требуется прямо указать, что функция не возвращает никакого значения, указывается тип void.
Следует различать в программе два контекста, каждый из которых по­своему влияет на выполнение вычислений функции: контекст определения (описания) и контекст вызова (использования). К сожалению, система программирования Си не гарантирует их обязательное соответствие. Забота об этом во многом лежит на программисте-разработчике программного кода.
При определении функции (процедуры) разработчик должен задать заголовок функции, в котором определяются тип возвращаемого значения, имя функции и список ее параметров, например:
void My Strcpy(char * Destination, char * Source)
Такой заголовок определяет процедуру, перемещающую значение входной строки символов Source в результирующую строку
Destination. В качестве формальных параметров рассматриваются
указатели на символы, что в языке Си также соответствует символьным
Основы разработки программного обеспечения на примере языка СС.В. Синицын, О.И. Хлытчиев
80
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]