ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 30.03.2021

Просмотров: 345

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.


enum apple {Jonathan, Golden_Del, Red_Del, Winesap=10, Cortland, McIntosh};


Часто в отношении перечислений ошибочно предполагается, что символы перечисления можно вводить и выводить как строки. Например, следующий фрагмент кода выполнен не будет.


// Слово "McIntosh" на экран таким образом не попадет.


fruit = McIntosh;


cout << fruit;


He забывайте, что символ McIntosh — это просто имя для некоторого целочисленного значения, а не строка. Следовательно, при выполнении предыдущего кода на экране отобразится числовое значение константы McIntosh, а не строка "McIntosh". Конечно, можно создать код ввода и вывода символов перечисления в виде строк, но он выходит несколько громоздким. Вот, например, как можно отобразить на экране названия сортов яблок, связанных с переменной fruit.


switch(fruit) {

 case Jonathan: cout << "Jonathan";

  break;

 case Golden_Del: cout << "Golden Delicious";

  break;

 case Red_Del: cout << "Red Delicious";

  break;

 case Winesap: cout << "Winesap";

  break;

 case Cortland: cout << "Cortland";

  break;

 case McIntosh: cout << "McIntosh";

  break;

}


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


#include <iostream>

using namespace std;

enum apple {Jonathan, Golden_Del, Red_Del, Winesap, Cortland, McIntosh};

// Массив строк, связанных с перечислением apple.

char name[][20] = {

 "Jonathan",

 "Golden Delicious",

 "Red Delicious",

 "Winesap",

 "Cortland",

 "McIntosh",

};

int main()

{

 apple fruit;

 fruit = Jonathan;

 cout << name[fruit] << '\n';

 fruit = Winesap;

 cout << name[fruit] << '\n';

 fruit = McIntosh;

 cout << name[fruit] << '\n';

 return 0;

}


Результаты выполнения этой программы таковы.


Jonathan


Winesap


McIntosh


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

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


Ключевое слово typedef

Ключевое слово typedef позволяет создать новое имя для существующего типа данных.

В C++ разрешается определять новые имена типов данных с помощью ключевого слова typedef. При использовании typedef-имени новый тип данных не создается, а лишь определяется новое имя для уже существующего типа. Благодаря typedef-именам можно сделать машинозависимые программы более переносимыми: для этого иногда достаточно изменить typedef-инструкции. Это средство также позволяет улучшить читабельность кода, поскольку для стандартных типов данных с его помощью можно использовать описательные имена.



Общий формат записи инструкции typedef таков,


typedef тип новое_имя;


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

Например, с помощью следующей инструкции можно создать новое имя для типа float,


typedef float balance;

Эта инструкция является предписанием компилятору распознавать идентификатор balance как еще одно имя для типа float. После этой инструкции можно создавать float-переменные с использованием имени balance.


balance over_due;

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



Еще об операторах 

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


Поразрядные операторы 


Поразрядные операторы обрабатывают отдельные биты.

Поскольку C++ нацелен на то, чтобы позволить полный доступ к аппаратным средствам компьютера, важно, чтобы он имел возможность непосредственно воздействовать на отдельные биты в рамках байта или машинного слова. Именно поэтому C++ и содержит поразрядные операторы. Поразрядные операторы предназначены для тестирования, установки или сдвига реальных битов в байтах или словах, которые соответствуют символьным или целочисленным С++-типам. Поразрядные операторы не используются для операндов типа bool, float, double, long double, void или других еще более сложных типов данных. Поразрядные операторы (они перечислены в табл. 9.1) очень часто используются для решения широкого круга задач программирования системного уровня, например, при опросе информации о состоянии устройства или ее формировании. Теперь рассмотрим каждый оператор этой группы в отдельности.


Поразрядные операторы И, ИЛИ, исключающее ИЛИ и НЕ

Поразрядные операторы И, ИЛИ, исключающее ИЛИ и НЕ (обозначаемые символами &, |, ^ и ~ соответственно) выполняют те же операции, что и их логические эквиваленты (т.е. они действуют согласно той же таблице истинности). Различие состоит лишь в том, что поразрядные операции работают на побитовой основе. В следующей таблице показан результат выполнения каждой поразрядной операции для всех возможных сочетаний операндов (нулей и единиц).


Как видно из таблицы, результат применения оператора XOR (исключающее ИЛИ) будет равен значению ИСТИНА (1) только в том случае, если истинен (равен значению 1) лишь один из операндов; в противном случае результат принимает значение ЛОЖЬ (0).

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


 1101 0011

& 1010 1010

 1000 0010

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


// Получение прописных букв.

#include <iostream>

using namespace std;

int main()

{

 char ch;

 do {

  cin >> ch;

  // Эта инструкция обнуляет 6-й бит.

  ch = ch & 223; // В переменной ch теперь прописная буква.

  cout << ch;

 }while(ch! = 'Q');

 return 0;

}


Значение 223, используемое в инструкции поразрядного И, является десятичным представлением двоичного числа 1101 1111. Следовательно, эта операция И оставляет все биты в переменной ch нетронутыми, за исключением шестого (он сбрасывается в нуль).

Оператор И также полезно использовать, если нужно определить, установлен ли интересующий вас бит (т.е. равен ли он значению 1) или нет. Например, при выполнении следующей инструкции вы узнаете, установлен ли 4-й бит в переменной status,


if(status & 8) cout << "Бит 4 установлен";


Чтобы понять, почему для тестирования четвертого бита используется число 8, вспомните, что в двоичной системе счисления число 8 представляется как 0000 1000, т.е. в числе 8 установлен только четвертый разряд. Поэтому условное выражение инструкции if даст значение ИСТИНА только в том случае, если четвертый бит переменной status также установлен (равен 1).


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


// Отображение конфигурации битов в байте.

void disp_binary(unsigned u)

{

 register int t;

 for(t=128; t>0; t=t/2)

  if(u & t) cout << "1";

  else cout << "0 ";

 cout << "\n";

}


Функция disp_binary(), используя поразрядный оператор И, последовательно тестирует каждый бит младшего байта переменной u, чтобы определить, установлен он или сброшен. Если он установлен, отображается цифра 1, в противном случае — цифра 0. Интереса ради попробуйте расширить эту функцию так, чтобы она отображала все биты переменной u, а не только ее младший байт.



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

 1101 0011

1010 1010

 1111 1011

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



// Получение строчных букв.

#include <iostream>

using namespace std;

int main()

{

 char ch;

 do {

  cin >> ch;

  /* Эта инструкция делает букву строчной, устанавливая ее 6-й бит.*/

  ch = ch | 32;

  cout << ch;

 }while(ch != 'q');

 return 0;

}


Установка шестого бита превращает прописную букву в ее строчный эквивалент.


Поразрядное исключающее ИЛИ (XOR) устанавливает в единицу бит результата только в том случае, если соответствующие биты операндов отличаются один от другого, т.е. не равны. Вот пример:

 0111 1111

^ 1011 1001

 1100 0110


Унарный оператор НЕ (или оператор дополнения до 1) инвертирует состояние всех битов своего операнда. Например, если целочисленное значение (хранимое в переменной А), представляет собой двоичный код 1001 0110, то в результате операции получим двоичный код 0110 1001.

В следующей программе демонстрируется использование оператора НЕ посредством отображения некоторого числа и его дополнения до 1 в двоичном коде с помощью приведенной выше функции disp_binary().


#include <iostream>

using namespace std;

void disp_binary(unsigned u);

int main()

{

 unsigned u;

 cout << "Введите число между 0 и 255: ";

  cin >> u;

 cout << "Исходное число в двоичном коде: ";

 disp_binary(u);

 cout << "Его дополнение до единицы: ";

 disp_binary(~u);

 return 0;

}

// Отображение битов, составляющих байт.

void disp_binary(unsigned u)

{

 register int t;

 for(t=128; t>0; t=t/2)

  if(u & t) cout << "1";

  else cout << "0";

 cout << "\n";

}


Вот как выглядят результаты выполнения этой программы.


Введите число между 0 и 255: 99


Исходное число в двоичном коде: 01100011


Его дополнение до единицы: 10011100



Операторы сдвига

Операторы сдвига, ">>" и "<<" сдвигают все биты в значении вправо или влево.

Общий формат использования оператора сдвига вправо выглядит так.


значение >> число_битов

А оператор сдвига влево используется так.


значение << число_битов

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

Здесь элемент число_битов указывает, на сколько позиций должно быть сдвинуто значение. При каждом сдвиге влево все биты, составляющее значение, сдвигаются влево на одну позицию, а в младший разряд записывается нуль. При каждом сдвиге вправо все биты сдвигаются, соответственно, вправо. Если сдвигу вправо подвергается значение без знака, в старший разряд записывается нуль. Если же сдвигу вправо подвергается значение со знаком, значение знакового разряда сохраняется. Как вы помните, отрицательные целые числа представляются установкой старшего разряда числа равным единице. Таким образом, если сдвигаемое значение отрицательно, при каждом сдвиге вправо в старший разряд записывается единица, а если положительно — нуль. Не забывайте, сдвиг, выполняемый операторами сдвига, не является циклическим, т.е. при сдвиге как вправо, так и влево крайние биты теряются, и содержимое потерянного бита узнать невозможно.

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


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

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


// Демонстрация выполнения поразрядного сдвига.


#include <iostream>

using namespace std;

void disp_binary(unsigned u);

int main()

{

 int i=1, t;

 for(t=0; t<8; t++) {

  disp_binary(i);

  i = i << 1;

 }

 cout << "\n";

 for(t=0; t<8; t++) {

  i = i >> 1;

  disp_binary(i);

 }

 return 0;

}

// Отображение битов, составляющих байт.

void disp_binary(unsigned u)

{

 register int t;

 for(t=128; t>0; t=t/2)

  if(u & t) cout << "1";

  else cout << "0 ";

 cout << "\n";

}


Результаты выполнения этой программы таковы.


0 0 0 0 0 0 0 1


0 0 0 0 0 0 1 0


0 0 0 0 0 1 0 0


0 0 0 0 1 0 0 0


0 0 0 1 0 0 0 0


0 0 1 0 0 0 0 0


0 1 0 0 0 0 0 0


1 0 0 0 0 0 0 0


1 0 0 0 0 0 0 0


0 1 0 0 0 0 0 0


0 0 1 0 0 0 0 0


0 0 0 1 0 0 0 0


0 0 0 0 1 0 0 0


0 0 0 0 0 1 0 0


0 0 0 0 0 0 1 0


0 0 0 0 0 0 0 1


Оператор "знак вопроса"

Одним из самых замечательных операторов C++ является оператор "?". Оператор "?" можно использовать в качестве замены if-else-инструкций, употребляемых в следующем общем формате.


if(условие)


 переменная = выражение 1;


else


 переменная = выражение 2;

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

Оператор "?" называется тернарным, поскольку он работает с тремя операндами. Вот его общий формат записи:


Выражение1? Выражение2 : Выражение3;

Все элементы здесь являются выражениями. Обратите внимание на использование и расположение двоеточия.

Значение ?-выражения определяется следующим образом. Вычисляется Выражение1. Если оно оказывается истинным, вычисляется Выражение2, и результат его вычисления становится значением всего ?-выражения. Если результат вычисления элемента Выражение1 оказывается ложным, значением всего ?-выражения становится результат вычисления элемента Выражение3. Рассмотрим следующий пример.


while(something) {


 х = count > 0 ? 0 : 1;


 // ...


}

Здесь переменной x будет присваиваться значение 0 до тех пор, пока значение переменной count не станет меньше или равно нулю. Аналогичный код (но с использованием if-else-инструкции) выглядел бы так.


while(something) {


 if(count >0) x = 0;


 else x = 1;


 // ...


}

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


/* Эта программа использует оператор ? для предотвращения деления на нуль.


*/


#include <iostream>

using namespace std

int div_zero();

int main()

{

 int i, j, result;

 cout << "Введите делимое и делитель: ";

  cin >> i >> j;

 // Эта инструкция не допустит возникновения ошибки деления на нуль.

 result = j ? i/j : div_zero();

 cout << "Результат: " << result;