Опубликован: 10.10.2006 | Уровень: специалист | Доступ: свободно
Лекция 2:

Описания и константы

2.3.2 Неявное преобразование типа

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

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

int i1 = 256+255;
char ch = i1        // ch == 255
int i2 = ch;        // i2 == ?

В присваивании ch=i1 теряется один разряд (и самый важный!), а когда мы присваиваем значение переменной i2, у переменной ch значение "все единицы", т.е. 8 единичных разрядов. Но какое значение примет i2? На машине DEC VAX, в которой char представляет знаковые значения, это будет -1, а на машине Motorola 68K, в которой char - беззнаковый, это будет 255. В С++ нет динамических средств контроля подобных ситуаций, а контроль на этапе трансляции вообще слишком сложен, поэтому надо быть осторожными.

2.3.3 Производные типы

Исходя из основных (и определенных пользователем) типов, можно с помощью следующих операций описания:

*     указатель
&     ссылка
[]    массив
()    функция

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

int* a;
float v[10];
char* p[20];   // массив из 20 символьных указателей
void f(int);
struct str { short length; char* p; };

Ключевая идея состоит в том, что описание объекта производного типа должно отражать его использование, например:

int v[10];     // описание вектора
i = v[3];      // использование элемента вектора

int* p;        // описание указателя
i = *p;        // использование указуемого объекта

Обозначения, используемые для производных типов, достаточно трудны для понимания лишь потому, что операции * и & являются префиксными, а [] и () - постфиксными. Поэтому в задании типов, если приоритеты операций не отвечают цели, надо ставить скобки. Например, приоритет операции [] выше, чем у *, и мы имеем:

int* v[10];        // массив указателей
int (*p)[10];      // указатель массива

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

int x, y;    // int x; int y;

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

int* p, y;        // int* p; int y; НО НЕ int* y;
int x, *p;        // int x; int* p;
int v[10], *p;    // int v[10]; int* p;

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

2.3.4 Тип void

Тип void синтаксически эквивалентен основным типам, но использовать его можно только в производном типе. Объектов типа void не существует. С его помощью задаются указатели на объекты неизвестного типа или функции, не возвращающие значение.

void f();    // f не возвращает значения
void* pv;    // указатель на объект неизвестного типа

Указатель произвольного типа можно присваивать переменной типа void*. На первый взгляд этому трудно найти применение, поскольку для void* недопустимо косвенное обращение (разыменование). Однако, именно на этом ограничении основывается использование типа void*. Он приписывается параметрам функций, которые не должны знать истинного типа этих параметров. Тип void* имеют также бестиповые объекты, возвращаемые функциями.

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

void* malloc(unsigned size);
void free(void*);

void f()          // распределение памяти в стиле Си
{
  int* pi = (int*)malloc(10*sizeof(int));
  char* pc = (char*)malloc(10);
  //...
  free(pi);
  free(pc);
}

Обозначение: (тип) выражение - используется для задания операции преобразования выражения к типу, поэтому перед присваиванием pi тип void*, возвращаемый в первом вызове malloc(), преобразуется в тип int. Пример записан в архаичном стиле.

Равиль Ярупов
Равиль Ярупов
Привет !
Федор Антонов
Федор Антонов
Оплата и обучение
Роман Островский
Роман Островский
Украина
Оксана Пагина
Оксана Пагина
Россия, Москва