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

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
24
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

r2 equels 1

r3 equels 0 (r1=1,y>x1)?(r2=1,y>x2)?(r3=1,y>x3)?4:3:2:1 equals 2 r1 equels 1

r2 equels 1

r3 equels 0

До вычисления значений условных выражений флажки r1, r2, r3 равны 0. При проверке условия соответствующий флажок получает значение 1. Выполнение вычислений прекращается, как только встретится "безусловное выражение". В нашем примере точка (y) попадет в полуоткрытый промежуток x1<y!x2 с номером 2. В каждом из выражений проверяются два условия. Проверка второго (в обоих случаях) приводит к выбору значения 2. Флажок r3 остается нулевым.

4.6. Битовые представления целых и поразрядные операции

Для изучения поразрядных операций и операций сдвигов нужен "инструмент" для отображения внутренних кодов (поразрядных представлений) констант и переменных. Подходящие для этого средства в нашем Практикуме вводятся в §11.4, посвященном битовым полям структур и объединений. Сейчас нам рано изучать материал названной темы, поэтому будем использовать функцию для печати кода целого аргумента как "черный ящик". Для этого нужно знать, как "присоединить" (подключить) функцию к нашей программе и как обратиться к ней. Текст тела функции может быть не понятен читателю до знакомства с материалом темы 11, поэтому он здесь не приведен. Обращение к функции полностью определяет ее прототип:

void intBitPrint(int integer);

Текст функции размещен в файле intBitPrint.c. Для "присоединения" функции к программе будем включать ее текст в начало программы препроцессорной директивой #include

"intBitPrint.c".

111

Еще одно вспомогательное решение, которое мы используем в "экспериментах", состоит в том, что обращение к функции intBitPrint() можно разместить в строке замещения следующего макроопределения:

#define PRINT_CODE(EXPRESSION)\

{printf("\nThe code of "#EXPRESSION" is\n");\ intBitPrint(EXPRESSION);}

Понимая непонятность таких средств для неподготовленного читателя, просим его обратить внимание на удобство и простоту программирования, достигаемые с их помощью. Нам сейчас важно получить в наглядном виде битовые коды числовых значений и проанализировать результаты применения поразрядных операций. С битовыми полями структур и объединений читатель может познакомиться в [3, гл. 6]. Макросредства препроцессора описаны в [3, гл. 3]. Примеры использования макросов уже были приведены в теме 3. Более подробно макросы рассматриваются в последующих темах нашего Практикума (см. тему 10).

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

ЗАДАЧА 04-19. Выберите целочисленную константу (типа int) с десятичным основанием. Напечатайте ее внутренний код и коды результатов применения к ней унарных операций ~ (поразрядное инвертирование), – (изменение знака), ! (логическое отрицание). Напечатайте также и десятичные значения выражений с этими операциями.

/* 04_19.c - внутренние коды и унарные операции */ #include <stdio.h>

#include "intBitPrint.c" #define PRINT_CODE(EXPRESSION)\

{printf("\nThe code of "#EXPRESSION" is\n");\ intBitPrint(EXPRESSION);}

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

PRINT_CODE(4);

112

PRINT_CODE(-4); PRINT_CODE(~4); PRINT_EXPI(~4); PRINT_CODE(!4); PRINT_EXPI(!4); return 0;

}

Результат выполнения программы:

The code

of 4 is

00000000

00000100

00000000

00000000

The code

of -4 is

11111111

11111100

11111111

11111111

The code

of ~4 is

11111111

11111011

11111111

11111111

The value of ~4 is -5

 

The code

of !4 is

00000000

00000000

00000000

00000000

The value of !4 is 0

Обратите внимание на код (битовое представление) выражения –4. Он наглядно иллюстрирует принятое на уровне реализации соглашение о том, что знаковые числа хранятся в дополнительном коде. Для числа x дополнительный код:

 

 

x,

если

x >=

0

доп(x) =

 

 

 

 

 

 

 

 

 

 

 

 

k

 

x

 

,

если

x < 0,

 

 

2

 

 

 

 

 

 

 

 

 

 

 

 

 

где k – количество разрядов, отведенное для представления чисел.

Именно поэтому десятичное значение выражения ~4 равно –5, что не очевидно без рассмотрения битовых представлений. Действительно, при k=4, 25=10000. 2k-5=(10000-101)=1011. Полученный код 1011 – это код числа –5. Код числа 4: 0100, a ~4 равно 1011.

Отметьте различие между операцией логического отрицания (!) и поразрядного инвертирования (~). Значением выражения !x (где х – положительное целое) всегда является 0.

Печать поразрядных представлений делает наглядным результаты операций поразрядных сдвигов.

113

ЗАДАЧА 04-20. Напечатайте коды целочисленной константы и выражений с операциями >> (сдвиг вправо) и << (сдвиг влево), в которых используется эта константа.

/* 04_20.c - внутренние коды и операции сдвигов */ #include <stdio.h>

#include "intBitPrint.c" #define PRINT_CODE(EXPRESSION)\

{ printf("\nThe code of "#EXPRESSION" is\n);\ intBitPrint(EXPRESSION);}

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

PRINT_CODE(9); PRINT_CODE(9<<3); PRINT_EXPI(9<<3); PRINT_CODE(9>>2); PRINT_EXPI(9>>2); return 0;

}

Результат выполнения программы:

The code

of 9 is

00000000

00001001

00000000

00000000

The code

of 9<<3 is

01001000

00000000

00000000

00000000

The value of 9<<3

is 72

 

The code

of 9>>2 is

00000010

00000000

00000000

00000000

The value of 9>>2

is 2

 

Как подтверждают результаты, сдвиг влево (<<) удобно использовать для умножения целого числа на значение 2N, где N – положительное число. При сдвиге вправо (>>) значение уменьшается в 2N раз с отбрасыванием дробной части результата, что не всегда допустимо.

114

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

ЗАДАЧА 04-21. Напечатайте коды выражений с поразрядными операциями ^ (исключающее ИЛИ), | (поразрядное ИЛИ), & (поразрядное И) и коды логических выражений с теми же операндами, в которых используются операции || (дизъюнкция), && (конъюнкция).

/* 04_21.c - поразрядные логические операции и отношения */

#include <stdio.h> #include "intBitPrint.c"

#define PRINT_CODE(EXPRESSION)\

{ printf("\nThe code of "#EXPRESSION" is\n"); intBitPrint(EXPRESSION);}

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

PRINT_CODE(11);

PRINT_CODE(13); PRINT_CODE(11^13); PRINT_EXPI(11^13); PRINT_CODE(11|13); PRINT_EXPI(11|13); PRINT_CODE(11&13); PRINT_EXPI(11&13); PRINT_CODE(11||13); PRINT_EXPI(11||13); PRINT_CODE(11&&13); PRINT_EXPI(11&&13); return 0;

}

Результат выполнения программы:

The

code

of

11

is

00000000

00001011

00000000

00000000

The

code

of

13

is

 

 

115

00000000

00000000

00000000

00001101

The code

of 11^13

is

00000110

00000000

00000000

00000000

The value of 11^13 is 6

 

The code

of 11|13

is

00001111

00000000

00000000

00000000

The value of 11|13 is 15

 

The code

of 11&13

is

00001001

00000000

00000000

00000000

The value of 11&13 is 9

 

The code

of 11||13 is

00000001

00000000

00000000

00000000

The value of 11||13 is 1

 

The code

of 11&&13 is

00000001

00000000

00000000

00000000

The value of 11&&13 is 1

Выводы из полученных результатов оставляем сделать читателю.

В [6] приведена программа, вычисляющая предельные значения переменных типа char, short, int, long как со знаком (signed), так и беззнаковых (unsigned).

Вот как это там реализовано для знаковых чисел типа int:

printf(“signed int min=%d\n”, -(int)((unsigned int)~0>>1));

printf(“signed int max=%d\n”, (int)((unsigned int)~0>>1));

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

ЗАДАЧА 04-22. Напишите программу, позволяющую проследить преобразования битовых представлений, приводящие к получению из числа 0 максимального числа типа signed int.

/* 04_22.c - поразрядные преобразования

при оценке предельных значений */ #include <stdio.h>

#include "intBitPrint.c"

116

#define PRINT_CODE(EXPRESSION)\

{ printf("\nThe code of "#EXPRESSION" is \n"); intBiPrint(EXPRESSION);}

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

printf("signed int max = %d\n", (int)((unsigned int)~0>>1));

PRINT_CODE(0);

PRINT_CODE(~0);

PRINT_EXPI(~0); PRINT_CODE((unsigned int)~0); PRINT_EXPI((unsigned int)~0); PRINT_CODE((unsigned int)~0>>1); PRINT_EXPI((unsigned int)~0>>1);

PRINT_CODE((int)((unsigned int)~0>>1)); PRINT_EXPI((int)((unsigned int)~0>>1)); return 0;

}

Результат выполнения программы:

signed int max = 2147483647

 

The code

of 0 is

00000000

00000000

00000000

00000000

The code

of ~0 is

11111111

11111111

11111111

11111111

The value of ~0 is -1

 

The code

of (unsigned int)~0 is

11111111

11111111

11111111

11111111

The value of (unsigned int)~0 is -1 The code of (unsigned int)~0>>1 is 01111111 11111111 11111111 11111111

The value of (unsigned int)~0>>1 is 2147483647 The code of (int)((unsigned int)~0>>1) is 01111111 11111111 11111111 11111111

The value of (int)((unsigned int)~0>>1) is 2147483647

117

В программе выведены коды выражений в той последовательности, в которой выполняются операции. У числа 0 все разряды нулевые. Выражение ~0 преобразует в единицу каждый бит кода 0. Тем самым формируется код из одних единиц. Затем выполняется преобразование к беззнаковому типу unsigned int. Сдвиг вправо на 1 уменьшает значение в 2 раза. Нуль, добавляемый в левый разряд при сдвиге вправо, превращает число в положительное. Нужный результат (максимальное положительное целое) получен.

Не очевидна необходимость приведения промежуточного значения ~0 к типу unsigned int. Проиллюстрируем роль этой операции.

ЭКСПЕРИМЕНТ. Удалите указанную операцию приведения типов (unsigned int) и объясните результаты, т.е. проанализируйте последовательность преобразований в выражении (int) (~0>>1).

Соответствующую программу (04_22.1.c) приводить не станем. А вот результаты интересные:

signed int max = -1

 

The code

of 0 is

00000000

00000000

00000000

00000000

The code

of ~0 is

11111111

11111111

11111111

11111111

The value of ~0 is -1

 

The code

of ~0>>1

is

11111111

11111111

11111111

11111111

The value of ~0>>1 is -1

 

The code

of (int)(~0>>1) is

11111111

11111111

11111111

11111111

The value of (int)(~0>>1) is -1

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

118

4.7. Унарная операция &

ифункция scanf()

Соперацией получения адреса объекта (&) и средствами форматного ввода нужно много поработать, чтобы достигнуть необходимой уверенности и полного понимания их особенностей. В теме 3 (3.4) функция scanf() применялась в макроопределениях, но достаточных объяснений не было. Начнем постепенно осваивать возможность функции форматного ввода и познакомимся на практике с применением выражения

&имя_переменной (или более общее &имя_объекта).

Для чтения разнотипных данных, набираемых пользователем на клавиатуре, имеется библиотечная функция scanf(). В ней для интерпретации прочитанных кодов используется форматная строка – первый аргумент функции. Вслед за форматной строкой в списке аргументов размещаются адреса объектов (например, переменных), которым должны быть присвоены введенные значения. Следует обратить внимание на важность согласования "изображения" вводимого значения (последовательность символов, набираемых на клавиатуре), типа объекта (переменной), которому присваивается получаемое значение, и спецификации преобразования в форматной строке.

Основные спецификации преобразования, используемые в scanf():

!%d – для десятичного целого int;

!%c – для отдельного символа char;

!%f, %e – для вещественного типа float;

!%lf, %le – для вещественного типа double;

!%Lf, %Le – для вещественного типа long double;

!%s – для строки символов.

Соответствие между типом объекта (переменной) и специфика-

цией преобразования явно следует из приведенного списка спецификаций. Правильность набора на клавиатуре вводимого значения зависит от аккуратности пользователя. Но на ту особенность функции scanf(), что в качестве аргументов (отличных от первого) должны использоваться адреса объектов (переменных), – следует обратить особое внимание. Здесь источник многочисленных ошибок начинающих (и не только начинающих) программистов.

119

ЗАДАЧА 04-23. Определить в программе две переменные типа int, ввести с клавиатуры их значения и вывести (напечатать) их сумму.

Задачу решает следующая программа:

/* 04_23.c - функция scanf() для типа int */ #include <stdio.h>

int main ()

{

int z, x, sum; printf("z="); scanf("%d",&z); printf("x="); scanf("%d",&x); sum=z+x; printf("sum=%d",sum); return 0;

}

Результат выполнения программы:

z=46 <ENTER> x=-16 <ENTER> sum=30

Все корректно и как-будто невыразительно. Попытаемся нарушить приведенные выше требования соответствия.

ЭКСПЕРИМЕНТ. Запустите программу на выполнение и введите нецелое (вещественное) значение первой переменной. Возможный результат:

z=0.65 <ENTER> x=sum=3616740

ЭКСПЕРИМЕНТ. Введите символ вместо целого числа. Возможный результат:

z=b <ENTER> x=sum=3630652

120