Упаковка и распаковка информации — КиберПедия 

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...

Упаковка и распаковка информации

2017-09-28 301
Упаковка и распаковка информации 0.00 из 5.00 0 оценок
Заказать работу

 

В реальных задачах возникает необходимость работать с числами из определённого диапазона. Например, из условия задачи известно, что числа находятся на промежутке от нуля до трёх. Тогда для их представления в памяти достаточно двух бит, так как 310=112. Для экономии памяти возникает необходимость в одну ячейку поместить несколько чисел.

Пример 11 (упаковка, или сжатие информации). В двухбайтную переменную unsigned short r (16 бит) запишем 16/2=8 чисел из диапазона 0..3.

Существует несколько алгоритмов решения подобной задачи. Предлагается заполнять ячейку r справа налево. Первое число помещаем в последние два бита, сдвигаем его влево на два разряда. На освободившиеся после сдвига два бита засылаем второе число, снова сдвигаем влево на два разряда, записываем третье число, сдвигаем и так далее.

unsigned short P (unsigned short mymax,unsigned short shift, unsigned short n)

{ randomize(); unsigned short num, r=0;

for (int j=1; j<=n; j++)

{ r<<=shift; // или r=r<<shift;

num=random(mymax+1); cout<<num<<" ";

r |= num; // или r+= num;

} return r;

}

void main()

{ unsigned short R= P(3, 2, 8); printf(" \n % x % d",R, R);

getch();

}

Так как после сдвига влево ячейки r два последних бита будут нулевыми, а в переменной num ненулевые только два последних бита, то r |= num и r += num выполняются одинаково. Заметим, что в общем случае эти две операции различаются.

В функции printf формат %x используется для вывода целого числа в шестнадцатеричной системе счисления с малыми латинскими буквами a — f.

В таких задачах важно уметь проанализировать результат. Пусть случайным образом сформированы следующие восемь чисел в таком порядке: 3, 0, 2, 1, 3, 0, 1, 1. Тогда будет выведено c9c5 — шестнадцатеричное представление числа по формату %x. Почему? Каждое число от 0 до 3 в ячейке r занимает два бита. В двоичном виде имеем 1100100111000101. Разбив на тетрады и учитывая, что это положительное число (unsigned), получим c9c5. Затем будет выведено 51653 — это же число в десятичной системе счисления по формату %d.

Пример 12 (распаковка). Как из такого числа r (см функцию в примере 11), в котором хранятся восемь чисел из диапазона 0..3, “взять” каждое из них и, например, вывести?

Алгоритм 1. Выполнить распаковку можно так, как выводили целое число в двоичной системе счисления (пример 10). Будем получать числа справа налево. При этом операция & выполняется с числом 3 (11 в двоичной системе счисления), а сдвигать надо вправо на 2, 4, 6, … разрядов.

Алгоритм 2. Числа будем получать слева направо в том же порядке, как они формировались. Для выделения первой левой цифры (в нашем примере 3)используем операции (r & 0xC000)>>14, так как

С00016 = 11000000000000002. Для выделения второй цифры (0) из числа 11000000000000002 получаем 00110000000000002= 300016, используя сдвиг на два разряда и выполняем операции (r & 0x3000)>>12.

На каждом последующем шаге, переменную, с которой выполняется операция &, сдвигаем вправо на два разряда (обозначим Maska), а результат этой операции сдвигаем вправо на 10, 8, … разрядов (переменная k).

void UnPark (unsigned short r,

unsigned short shift, unsigned short n)

{ unsigned Maska=0xC000, k=14;

for (int j=1; j<=n; j++,k –=2, Maska>>=shift)

{ short num= (r & Maska)>>k;

cout<<num<<" "; }

}

void main()

{ unsigned short R= P(3, 2, 8); /* Функцию P см. в примере 11 */

printf(" \n % x % d\n",R, R); UnPark(R,2,8);

getch();

}

В результате будут получены и выведены те же числа, которые были сформированы функцией P случайным образом, т. е. 3, 0, 2, 1, 3, 0, 1, 1.

Упаковку эффективно использовать, если в памяти необходимо хранить массив целых чисел из указанного диапазона большой размерности. Например, если надо сохранить 400 чисел из диапазона 0..3, то вместо массива unsigned short a[400] достаточно объявить массив unsigned short a[50 ]. Этим самым память экономим в 8 раз. Эффективность этой методики с точки зрения экономии памяти тем выше, чем меньше диапазон чисел. Но время выполнения таких программ увеличивается.

 

 

Логический тип

 

В классическом “старом” языке С такого типа не было. Вместо него использовался целый тип. Логический или булевый тип появился в языке С++. Переменные такого типа объявляются, например, следующим, образом: bool B1, B2, B3, B4. Переменная данного типа может принимать одно из двух значений — true или false, которыеявляются логическими константами. Кроме этих переменных дальше в тексте будем использовать вещественные переменные x и y, определяющие координаты точки на плоскости в декартовой системе координат.

Логические переменные можно использовать:

· в операторах присваивания, например,

B1=x>0; B1=true; B2=y>0 && x<0;

B1=B2 || x*x+y*y<=4;

· в операторе if, например, if (B1), что равносильно if (B1= = true) …;

· в операторах while и do … while,

например, while (B1) { …}, что равносильно while(B1= = true) {…};

· в заголовке оператора for. Чаще всегобулевский тип используется во второй части заголовка. Например, for (x=1; B1; x+=0.5) {…}; что равносильно for (x=1; B1==true; x+=0.5) {…}; Теоретически переменные булевского типа могут быть в первой и в третьей частях заголовка этого оператора.

Для логических переменных и констант определены логические операции &&, ||,! и операции сравнения >, <, >=, <=, = =,!= (см. 3.3 гл. 1). Например, присваивание B3 = y>x*x && fabs(x)<2 можно записать так:

B1= y>x*x; B2= fabs(x)<2; B3=B1 && B2;

При сравнении логических величин учитывается, что результаты следующих сравнений истинны: true>false; true = = true; false = = false.

Пример 13. Пусть B1=x>0; B2=y>0;

a) Выражение B1>B2 истинно тогда и только тогда, когда x>0 && y<=0 истинно, т. е. когда B1==true, а B2==false.

b) Выражение B1== B2 истинно тогда и только тогда, когда x>0 && y>0 || x<=0 && y<=0, т. е. когда B1==true и B2==true или обе переменные принимают значение false.

c) Выражение!(B1<B2) равносильно

x>0 && y<=0|| x>0 && y>0 || x<=0 && y<=0.

Существует связь между логическими операциями и оператором if.

Пример 14. Присваивание B3=B1 && B2 с помощью if без логических операций равносильно if (B1) B3 = B2; else B3 = false;

Пример 15. Дан вложенный if

if (B2) B3=true;

else if (B1) B3 = false;

else B3 = true;

Это можно записать короче: B3=!B1 || B2;

Мы уже знаем, что целый тип совместим с булевским типом. Любое ненулевое число играет роль true, а нуль равносилен false. Поэтому если int flag, то оператор if (flag) cout<<”Yes”; else cout<<”No”; равносилен следующему:

if (flag!=0) cout<<”Yes”; else cout<<”No”.

Надо различать логические и битовые операции.

Если логические операции используются для целых чисел, то любое ненулевое число играет роль true, а нуль равносилен false.

Пример 16. В результате выполнения операторов

int b1=10<0xA, b2=0x10, b3; b3=b1 || b2 && 10; cout<<b3;

выведем единицу. Почему? Так как 10 и 0xA — одно и то же число в разных системах счисления, то результат сравнения ложный и b1=0. Значением переменной b2 является ненулевое число 16 в шестнадцатеричной системе счисления, а это соответствует истине. Поэтому b2 && 10 даст в результате истину, и b3 также будет истинным.

Если объявить bool b1=10<0xA, b2=0x10, b3; то результат будет тот же самый, т. е. будет выведена единица (а не true).

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

Пример 17. Рассмотрим похожий на предыдущий пример с битовыми операциями: bool b1=10<0xA, b2=0x10, b3; b3=b1 | b2 & 10; cout<<b3;

В результате операции b2 & 10 получим 0, потому что битовая операция применяется к каждой паре битов, т. е. 12 & 10102=0. Значением b1 является false, т. е. 0. Поэтому в результате будет выведен 0. Необходимо обратить внимание, что значением переменной b2 будет 1, а не 100002. По этой же причине, если bool b2=10, то в результате получим 0.

Но если int b1=10<0xA, b2=10, b3; то получим 10, так как 10&10=10, b1=0, 0 | 10 =10.

 

Символьный тип

 

Мы уже знаем, как объявить переменную символьного типа, правила записи символьных констант. Каждый символ занимает один байт.

Для ввода одного символа можно использовать функции getchar, getch или getche. От их выбора зависит, надо ли при вводе символа нажимать не только на клавишу вводимого символа, но и дополнительно на “Ввод” (да для getchar, нет для getch, и нетдля getche). Во-вторых, эти стандартные функции влияют на отображение набираемого на клавиатуре символа на экране (да, нет, да).

Для вывода символа вместо cout удобнее использовать функции printf или cprintf, так как с помощью форматов (“ %c ” или “ %d ”) можно указать, что мы выводим, символ или его код (см. пример 19).

Среди символов есть такие, которые нельзя ввести обычным способом, так как их нет на алфавитно–цифровой клавиатуре. Они называются символами псевдографики. Для их ввода курсор подводим в нужное место и нажимаем “ Alt ”. При включённом цифровом режиме (клавиша NumLock) код символа набираем справа на клавиатуре. Во время набора ничего не отображается, но когда отпустим клавишу “ Alt ”, в соответствующем месте появится символ псевдографики с набранным кодом. Такие символы в функцию можно передать, как и обычные символы, двумя способами: с помощью его числового кода или вышеописанным способом набрать такой символ.

Пример 18. Составить и проверить функцию, которая рисует границу прямоугольника. Параметры функции: x0, y0 — текстовые координаты левого верхнего угла; n1,n2 — размеры прямоугольника.

void ramka (int x0, int y0, int n1, int n2)

{ int x,y,i; x=x0; y=y0;

gotoxy(x,y++); printf("%c",218);

for (i=0;i<n1-2;i++) { gotoxy(x,y++); printf("%c", 179); }

gotoxy(x++,y); printf("%c",192);

for (i=0;i<n2-2;i++) { gotoxy(x++,y); printf("%c",196); }

gotoxy(x,y--); printf("%c",217);

for(i=0;i<n1-2;i++) { gotoxy(x,y--); printf("%c",179); }

gotoxy(x--,y); printf("%c",191);

for(i=0;i<n2-2;i++) { gotoxy(x--,y); printf("%c",196); }

}

int main ()

{ ramka (2,5, 10, 30); getch(); return 0; }

В программе использовались кодыследующих символов псевдографики:

218 для левого верхнего угла;

179 — для вертикальной линии;

192 — для левого нижнего угла;

196 — для горизонтальной линии;

217 — для правого нижнего угла;

191 — для правого верхнего угла.

Пример 19. Другие особенности символов можно увидеть, выполнив программу для вывода кодовой таблицы. то есть всехсимволов, в том числе и символов псевдографики, и их кодов:

main()

{ clrscr(); textcolor(10);

for (int i=1; i<256; i++)

{ cprintf("%4d",i); printf("%c%c", i, i%12?' ':'\n'); }

getch(); return 0;

}

В цикле по формату "%4d” выводим код символа указанным в textcolor 10-м цветом, а по формату "%c” — символ с этим кодом цветом по умолчанию. Если i не кратно 12, т.е. i%12!= 0, то выводим пробел. В противном случае с помощью '\n' переходим на следующую строку экрана.

Анализ кодовой таблицыпозволяет отметить следующие особенности символов:

· цифры в таблице располагаются подряд, цифра 0 имеет код 48, у цифры 1 код 49, и т. д., цифра 9 имеет код 57;

· как прописные, так и строчные латинские буквы располагаются непрерывно в алфавитном порядке с кодами 65—90 для больших и 97—122 для маленьких букв;

· русские прописные буквы располагаются также подряд (коды 128—159);

· маленькие русские буквы терпят разрыв, буквы от ‘а’ (код 160) до ‘п’ (175) следуют подряд. После буквы ‘п’ размещаются символы псевдографики, а затем русский алфавит продолжается с буквы ‘р’ (224) до ‘я’(239).

Рассмотрим совместимость символьного и целого типов, что характерно для языка С++. В переменной типа char можно хранить как символы (точнее, их коды), так и целые однобайтные числа. Для переменных типа char определены те же операции, что и для типа int, например, получение остатка от целочисленного деления (%), сложение и другие.

Пример 20. В языке С++, в отличие от Pascal, можно выполнить следующий фрагмент: char a='5', b=5; cout<<a<<b << a%b<<" "<<a/b;

В результате выведем символ 5, символ с кодом 5 (см. кодовую таблицу), 3, 10. Почему? В переменной a хранится код символа ‘5’, т. е. целое число 53 (48+5, где 48 — код нуля), а в переменной b — обычное целое число 5, а 53 % 5 = 3, 53 / 5 = 10. Заметим, что если объявим int a=53, b=5; то выведем 53, 5, 3, 10. Если, что интереснее, int a=’5’, b=5; то получим то же, что и в предыдущем варианте, т. е. 53, 5, 3, 10, так как значением целочисленной переменной a будет код символа 5 — число 53.

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

Пример 21. Пусть char a, b; cin>>a>>b;

a) При выполнении введём 5 и 3. Тогда с помощью того же cout получим 5, 3, 2, 1. Здесь число 2 получено не как остаток от деления 5 на 3, а как результат операции % для кодов этих символов, т. е. 53 % 51 = 2. Для этих же чисел получено и число 1. В этом можно убедиться, если вместо частного вывести произведение cout << a*b; На экране мы увидим не 15 (5*3), а 2703, т. е. произведение кодов символов 5 и 3 (51* 53 = 2703).

b) Пусть по–прежнему char a, b; cin>>a>>b; При выполнении наберем два символа, например, a и с, коды которых 97 и 99. Тогда по той же причине будет выведено: a, c, 97, 9603, где 97=97%99, а 9603=97*99. “Числа”, которые больше, чем 9, при таком объявлении ввести не сможем. Если введём, например, 123, то a примет значение символа ‘1’, b= ‘2’, а символ 3 игнорируется. Получим 1, 2, 49, 2450, где 49=49 % 50, 2450 = 49*50.

с) Если объявлено int a, b; и вводим целые числа, то значениями этих переменных будут введённые числа. При вводе символов ничего хорошего не получим, хотя программа будет выполняться.

 

Вещественный тип

 

Вещественное число (число с плавающей запятой) состоит из двух частей: мантиссы и порядка. Например, число в десятичной системе счисления 0,000123 можно записать одним из следующих способов: 0.0000123*10; 0,123*10-3; 1,23*10-4 и т.д. Аналогично 78900=0,789*105=7,89*104=78,9*103 и т.д. Термин “число с плавающей запятой” и связан с тем, что десятичная запятая в различных записях числа перемещается (плывёт) по числу. Нас будет интересовать нормализованное число (соответственно 0,123*10-3 и 0,789*105 ), в котором в левом числе после запятой значащая цифра, а не нуль. Первая его часть называется мантиссой (0,123 и 0,789), а числа - 3 и 5 – порядком.

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

Пример 22. Рассмотрим десятичное число 12,375. Для его перевода в двоичную систему счисления отдельно переводим целую часть аналогично как было показано в первом параграфе и отдельно дробную часть. В качестве вспомогательной системы счисления можно использовать шестнадцатеричную. Для перевода дробной части из десятичной системы счисления в шестнадцатеричную выполняем следующее:

дробную часть числа умножаем на 16;

полученную целую часть результата (число от 0 до 15) переводим в шестнадцатеричную систему счисления и берём в качестве первой после запятой 16-й цифры результата;

дробную часть результата, если она не равна нулю, повторно умножаем на число 16;

полученную целую часть переводим в шестнадцатеричную систему счисления и берём в качестве следующей шестнадцатеричной цифры;

дробную часть результата снова умножаем на 16.

Этот процесс продолжаем, пока не наступит одна из следующих ситуаций:

a) на некотором шаге, не обязательно в самом начале, получим в дробной части нуль. В этом случае перевод выполнили точно. Это имеет место в нашем примере: 0,375*16=6.0;

b) получим в дробной части число, которое было раньше. Например, 0,15*16=2,4; 0,4*16=6,4. Если продолжать умножение 0,4*16, будем получать одно и то же, т. е 6,4. В таком случае получаем следующий результат: 0,1510= 0,2666…16=0,2(6)16. Круглые скобки означают, что записанное в них одно или несколько разных чисел будут повторяться бесконечное число раз. Говорят, что это число в периоде, т.е. 6 в периоде;

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

Для перевода дробной части из шестнадцатеричной системы счисления в десятичную записываем каждую шестнадцатеричную (но не десятичную!) цифру в виде тетрады, т.е. четырёх двоичных цифр. Получим 12.37510=С.616=1100,0110=1100,011. При этом последнюю цифру ‘0’ можем не писать. Как и в десятичной системе счисления, этот нуль незначащий. Остальные нули рядом с запятой как слева, так и справа, обязательны! Это двоичное число, как и в десятичной системе счисления, записать можно по-разному: 11,00011*22; 1100011*2-3; 1.100011*23 и т. д. Из приведенных вариантов нас будет интересовать последняя нормализованная запись, в которой в целой части записана одна первая значащая единица. Получим: m=0.100011; p=310=112, где m — мантисса, p — порядок в двоичной системе счисления.

Пусть переменная объявлена как float. Тогда 4 байта или 32 бита распределяются так.

Один самый “левый” бит отводится под знак мантиссы, или, что то же самое, под знак всего числа. Записывается 0, если мантисса, а, значит и само вещественное число, положительное, и 1 в противном случае. Никакого дополнительного кода для отрицательного вещественного числа, как это было для целых чисел, получать не надо.

Следующие 8 разрядов (бит) занимает изменённый порядок записи числа в двоичной системе счисления, который называется характеристикой числа (обозначим её x). Что это такое? Знак порядка нигде не хранится. Чтобы он всегда был неотрицательным, порядок увеличивается на 12710, т. е. x=p+12710=p+7F16. (1) Для нашего примера здесь будет храниться число x=310+12710= 13010=8216=100000102.

Это же можно вычислить и так: x=316+7F16=8216 =100000102;

Последние 32-1-8=23 разряда занимает мантисса. При этом старшая её цифра, равная 1, в памяти не хранится, но учитывается при вычислениях. Если дробная часть числа переведена в шестнадцатеричную, а, значит и в двоичную системы счисления не точно, т. е. имели место два последних варианта (см. выше перевод), последняя двоичная цифра округляется по обычным правилам. Если первая отбрасываемая двоичная цифра равна 1, то прибавляем двоичную единицу, в противном случае оставляем без изменения.

Таким образом, число 12,375 в формате float будет представлено следующим образом: 01000001010001100000000000000000. В литературе можно встретить шестнадцатеричную запись результата: 4146000016.

Пример 23. Представить число -0.01 как число с плавающей точкой в формате float.

Переводим дробную часть модуля числа в двоичную систему счисления: 0.01=0.028F5С28F5C…16=0.0(28F5С)16=

0.0000001010001111010111000010100…2. Так как под мантиссу отводится 23 разряда, то должны получить 25 двоичных цифр, не считая первых после десятичной точки подряд идущих нулей. Почему? По правилу нормализации самая первая значащая единица (в примере в десятичной цифре 2) в память не записывается, а ещё одна дополнительная двоичная цифра нужна для того, чтобы определить, как округлять число. Так как первая отбрасываемая двоичная цифра = 0, то получаем

m=0.010001111010111000010102. Если число “маленькое”, т.е. целая часть = 0, а в дробной части после запятой несколько подряд идущих нулей, то получим отрицательный порядок. Так как 0.0000001010001111010111000010102= 1.01000111101011100001010*2-7,

то p=-710=-716, x= p+7F16=7F16-716=7816=011110002. В результате получим ответ: 10111100001000111101011100001010.

Пример 24. Рассмотрим обратную задачу. Пусть в ячейке размером 4 байта хранится следующая последовательность нулей и единиц, шестнадцатеричное представление которой такое: С215999A16. Известно, что здесь хранится вещественное число, т.е. в программе записано объявление: float a. Что это за число в 10-й системе счисления?

Для ответа на этот вопрос в обратном порядке выполняем действия, описанные выше.

1) Запишем двоичное представление содержимого ячейки: 11000010000101011001100110011010.

2) Единица в старшем бите (самая “левая”) означает, что всё вещественное число отрицательное.

3) В следующих 8 битах находится характеристика числа, т.е. x=100001002=8416. Из формулы (1) получаем двоичный порядок числа:

p=x-7F16=8416-7F16 =516=510.

4) Из последних 23 разрядов получаем

m=0.001010110011001100110102.

5) Поэтому искомое число a=1.00101011001100110011010*25=

100101.0110011001100110102@25.(6) 16@37.410.

Перевод дробной части из шестнадцатеричной в десятичную систему счисления выполняли следующим образом: 0.(6) 16 = 0.6666616 = 6*16-1+6*16-2+6*16-3+6*16-4+6*16-5@0.410.

Т.к. в самом “левом” бите была единица, то получаем ответ: - 37.410.

 

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

 

5.1. Преобразование типов в выражениях

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

переменные типа char и short int преобразуются к типу int;

переменные типа float преобразуются к типу double;

если один из пары операндов имеет тип double, другой операнд также преобразуется к double;

если один из пары операндов имеет тип unsigned, другой операнд также преобразуется к unsigned;

Пример 25. Пусть char c; int k; float f; double d, res; … res=(f+d) * (c-k);

Последовательно выполняется преобразование внутри каждой пары операндов до выполнения соответствующей операции. Перед выполнением сложения переменная f преобразуется к типу double и затем суммируются переменные одинаковых типов double. Перед вычитанием с преобразуется к типу int и результат вычитания будет целым. Так как (f+d) имеет тип double, то перед умножением целое (c-i) преобразуется к типу double, и всё выражение будет иметь тип double. Так как переменная слева имеет такой же тип double, то во время присваивания преобразование типов в этом примере не выполняется (см. 5.2).

Есть возможность “заставить” выражение принять определённый тип с помощью операции принудительного преобразования, имеющей следующий вид: (тип) выражение. Здесь в скобках можно записать стандартный или определённый пользователем тип.

Например, часто можно встретить следующую ошибку. Пусть переменная Sum используется для вычисления суммы элементов целочисленного массива размерности n, где n, конечно, целое. Тогда если объявить int Sum, то среднее значение по формуле Aver=Sum/n; будет найдено неправильно, даже если float Aver. Почему? Несмотря на то, что Aver вещественное, мы имеем дело с целочисленным делением, так как и Sum, и n целые. Поэтому при делении будет отброшена дробная часть результата,. Полученное целое значение будет преобразовано к типу float без восстановления отброшенной дробной части и присвоено переменной Aver.

Один из способов преодолеть это — объявить float Sum. А если нам надо, чтобы в других операторах этого же блока или функции переменная Sum была целой? Тогда, оставив int Sum, записываем Aver=(float)Sum/n. В таком случае при вычислении частного произойдёт “временное” преобразование Sum из int во float, а в остальных операторах как до, так и после этого присваивания Sum будет иметь тот тип, который записан при объявлении, т. е. целый. Поэтому если, например, записать cout << Sum; то сумма будет выведена как целая величина.

Пример 26. Дан код: float f=100.5; cout<<(int)f/6<<” “ << int)f/6.<< " "<<f/6<< " "<<f/6.; Что будет выведено?

Ответ: 16 16.6667 16.75 16.75

Объяснить результат.

 

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

Что получится, если бы в предыдущем примере res=(f+d) * (c-i); переменная res имела тип, отличный от double, например, целый? Тогда используется следующее правило преобразования типов при присваивании: значение справа от оператора присваивания (значение выражения) преобразуется к типу переменной, стоящей слева. В результате получилась бы целая часть выражения справа.

Пример 27. Пусть char c; int k; float f; double d;

a) При присваивании c=k в переменной c остаются младшие 8 битов переменной k. Когда происходит преобразование из целого числа int или short к символу char, из целого int — к короткому целому short, то старшие биты теряются.

b) В результате присваиваний f=c; f=k; получается целое число, преобразованное к формату с плавающей точкой.

При преобразовании из int в float или из float в double точность не увеличивается, а всего лишь меняется формат представления данных.

Пример 28. Дан код:

float f2 =100.5, r1=(int)f2/6, r2=(int)f2/6., r3=f2/6, r4=f2/6.;

cout<<r1<< " "<<r2<<" "<<r3<< " "<<r4;

Что будет выведено?

Ответ: 16 16.6667 16.75 16.75

Объяснить, какие преобразования выполнены.

Пример 29. Дан код:

int f2 =100.5, r21=(int)f2/6, r22=(int)f2/6., r23=f2/6, r24=f2/6;

cout<<r21<<" "<<r22<<" "<<r23<<" "<<r24;

Что будет выведено?.

Ответ: 16 16 16 16

Объяснить, какие преобразования выполнены.

 

Упражнения и тесты

 

Целый тип. Битовые операции.

1. Что будет выведено? int a=170, r = a & 15; cout<<r;

r = a | 8; cout<<endl<<r<<endl;

if ((a & 8) == 8) cout<<"Yes"; else cout<<"No";

Решение. Число 170 переводим в шестнадцатеричную систему счисления делением на 16. Получим 17010=AA16. Каждую шестнадцатеричную цифру запишем в виде двоичной тетрады и добавим необходимое количество незначащих нулей. Получим 00000000000000000000000010101010.

1510 = 11112. Учитывая правила выполнения операции &, выведем десятичное число 10, т. е. с помощью этой операции всегда выделяется последняя шестнадцатеричная цифра числа.

Так как 810=10002 и принимая во внимание правила выполнения операции | (битовое или), получим то же число 170.

Так как в двоичном представлении числа 8 есть всего одна единица, то результатом операции a & 8 будет либо 0, либо 8 =10002 в зависимости от того, что находится в 3–м справа бите числа a (нумерация битов с нуля). В нашем примере получим 8, и будет выведено “YES”, что означает, что в 3–м справа бите исходного числа a была единица.

2. Определить результат:

unsigned short a; cin>>a; a=a & 0xFFF7; cout<<a;

Решение. Введем, например, число 26. В двоичной системе счисления это 0000000000011010. Так как FFF716=11111111111101112, то операция &(битовое и) оставит без изменения все биты, кроме третьего справа. Независимо от того, что было в этом бите (0 или 1), получится нуль, т. е. мы “выключили” третий справа бит. В результате получим 00000000000100102=18, т. е. будет выведено число 18. Если введем, например, число 23, то это число не изменится, так как в третьем бите был 0.

3. Пусть int a=-8, b=23. Что получится в результате выполнения следующей части программы: printf (“%d %X %d %X”, a & b,a | b,a ^ b,~a);

4. Найти значение переменной r, объявленной как int r, если:

а) r= ~(100>>2) & ~(–10) | 0x10; b ) r=162 | 0x10A & 111;

c) r= –10 & 1|4; d) r=(123<<4)& 123.

5. Найти значение этого же выражения из предыдущего упражнения, если переменная объявлена как unsigned short r.

6. Найти r3=30 ^ –1707, если:

a) short r3, b) unsigned short r3.

7a. Пусть в ячейке, объявленной short a, хранится следующая двоичная последовательность:

а) 0000001001001001; b) 1100000000001000;

c) 1111111111111000; d) 1000000000000000.

Что это за число в десятичной системе счисления? шестнадцатеричной системе счисления?

7b. Выполнить предыдущее упражнение, если переменная объявлена как unsigned short a.

8. Как включить 4-й справа бит, т. е. поместить туда единицу независимо от того, какое там было значение? Нумерация битов с нуля.

9. Как заменить значение 2–го справа бита на противоположное значение, т. е. нуль на единицу, а единицу на нуль? Нумерация битов с нуля.

 

Логический тип.

10. Пусть bool b1, b2, b3. Записать оператор if и операторы присваивания, которые выполняют те же действия, что и оператор

a) b1= b2 || b3; b) b1=b2 || b3 && b4;

c) b1=(b2 || b3) && b4; d) b1=!(b2 && b3 || b4).

Решение: a) if (b2) b1=true; else b1=b3;

11. Записать с помощью логических операций и оператора присваивания,не используя оператор if:

bool b1, b2, b3,r; if (!b1) r=false; else if (!b2) r=false; else r=b3;

12. Нарисовать область плоскости, в которой и только в которой для вещественных величин x и y следующее логическое выражение истинно:

а) (fabs(x) < 1) > (fabs(y) < 1);

b)! (fabs(x) < 1) = = (fabs(y) < 1);

c)! ((fabs(x) < 1) = = (fabs(y) < 1)).

Решение. a) Надо найти область плоскости, в которой и только в которой одновременно первое неравенство истинно, а второе ложно, т. е. точки, для которых выражение (fabs(x)<1) && (fabs(y)>=1) истинно.

b) !(fabs(x) < 1) равносильно неравенству (fabs(x) >= 1). Поэтому надо найти область плоскости, в которой и только в которой выражение

(fabs(x)>=1) &&(fabs(y)<1) || (fabs(x)<1) &&(fabs(y)>=1) истинно.

c) Сначала определяем область, в которой и только в которой выражение (fabs(x)<1) && (fabs(y)<1) || (fabs(x)>=1) && (fabs(y)>=1) истиннo. Это был бы ответ, если бы не было операции отрицания. Операция отрицания в качестве ответа оставит точки, не вошедшие в эту область.

13. Записать следующее логическое выражение, используя операции логического умножения и сложения (&&, ||) и не используя сравнение логических величин: a) (y>x) = = (x>0); b) (y > x)!= (x > 0);

c) !((y > x) > (x>0)); d )! (y > x) > (x > 0).

Нарисовать область плоскости, в которой и только в которой записанное логическое выражение истинно.

14. Записать логическое выражениес помощью сравнения логических величин, не используя операций && и ||:

a) x*x+y*y > 1 && y <= x;

b) x*x+y* y> 1 && y <= x || x*x+y*y <= 1 && y > x;

c) x>0 && y > 0 || x <= 0 && y <= 0.

Нарисовать область плоскости, в которой и только в которой записанное логическое выражение истинно.

15. Не используя логических операций, записать выражение, принимающее значение true тогда и только тогда, когда точка плоскости с координатами (x, y) принадлежит первой четверти, включая и оси координат, или третьей четверти, не включая оси координат.

16. Объяснить работу следующей программы:

float x,y; bool lg; cin>>x>>y;

while (!(x==100 && y==100))

{

lg=x>0; cout<<" "<<lg<<" "<<(y>0)<<" "<<(lg > (y>0));

if (lg > (y>0)) cout<<" YES "; else cout<<" NO";

cin>>x>>y;

}

17. Операторы

int x=8, y=4;

cout<<(x&y)<<" "<<(x&&y)<<" " <<(x|y)

<<" "<<(x||y) <<" "<<(y<x<1)<<" "<<(x<y<1);

выведут 0 1 12 1 0 1. Объяснить результат.

 

Символьный тип.

18. Объяснить выполнение printf("%c %d", a, a);

Вариант 1. Если int a; и вводим число 98, то выведем b 98, т. е. по формату %c выводится символ, код которого хранится в переменной a, а по формату %d — целое число, код этого символа.

Вариант 2. Этот же результат получим, если объявим char a; и введём символ ‘ b’.

19. Что будет выведено и почему?

1) char ch; cin>>ch; cout<< (ch*2);

если введём 1) 1? 2) d?:

2) char CH=1; cout<<” “<<(CH*2);

3) char ch2=’1’; cout<<” “<<(ch2*2);

4) printf (“ \n %c %d %c %d %c %d”, ch, ch, CH, CH, ch2, ch2);

Объявление и инициализация, как в 1), 2), 3).

5) int ci; cin>>ci; printf(“\n %c %d”, ci,ci);

если введём 50?

Вещественный тип

20. a) float f1=1., f2=0.1, f3=-1., f4=-0.1;

b) float f1=10., f2=10.1, f3=-10., f4=-10.1;

Как эти числа представляются в памяти компьютера?

 

Задачи

Уровень B

1. С помощью битовых операций вывести на экран значение k –го справа бита, где k вводим с контролем.

2. С помощью битовых операций вывести на экран i –ю справа шестнадцатеричную цифру, где i вводим с контролем.

3. Составить функцию, которая с помощью битовых операций, выводит шестнадцатеричное представление беззнакового двухбайтного целого числа. В main для последовательности введенных чисел вывести их шестнадцатеричный код, полученный с помощью функции и с помощью формата “ %X ”.

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

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

6 — 11. Решить задачу упаковки и распаковки, если диапазон чисел следующий: a) 0..1; b) 0..7; c) 0..15.

Г л а в а 7
ВВЕДЕНИЕ В УКАЗАТЕЛИ

Одной из основных причин, почему язык С++ объективно более сложный по сравнению с другими языками (например, Pascal), является широкое использование переменных-указателей.

 

§ 1. Понятие указателя.
Операции разыменования и разадресации

 

Указатель — это специальная переменная, которая хранит адрес другой переменной. Указатель объявляется следующим образом: тип* переменная; где тип — любой допустимый как простой, так и составной базовый тип указателя.

Например, пусть объявлена обычная переменная int t; Объявление и инициализация int* p= &t; означают следующее. В переменной p будетхраниться не обрабатываемое программой целое число (оценка студента, количество выпущенной продукции и т. п.), а адрес ячейки, в которой будет находиться информация указанного типа (целое число). Под адресом будем понимать номер первого байта выделенного для переменной участка оперативной памяти. Для переменных, не являющихся указателями, без дополнительного объявления адрес также запоминается системой, и его можно получить с помощью операции & (разадресации), например, &t. Эта унарная операция, которую иногда называют “взятие адреса”, ничего не делает со значением переменной t.

До первого использования переменная-указатель обязательно должна быть проинициализирована. До тех пор, пока не определим значение указателя, он относится к чему-то случайному в памяти, и его использование может привести к непредсказуемым результатам. Один из способов показан выше и означает, что в переменную p помещается адрес ячейки t. Важно понять, что int* p= &t; равносильно int* p; p=&t; а не *p=&t; В этом заключается одна из трудностей начального этапа изучения указателей. Эта тема усложняется ещё и тем, что такой же символ “ & ” используется при объявлении переменной ссылочного типа. Здесь этот символ определяет операцию взятия адреса для переменной и никакого отношения к ссылочному типу не имеет.

Заметим, что расстановка пробелов при объявлении указателей свободная. Допустимы также следующие записи: int * p= & t; int *p= &t;


Поделиться с друзьями:

Индивидуальные очистные сооружения: К классу индивидуальных очистных сооружений относят сооружения, пропускная способность которых...

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

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

Кормораздатчик мобильный электрифицированный: схема и процесс работы устройства...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.213 с.