14. Обзор типов

В этом разделе кратко собрано описание действий, которые могут совершаться над объектами различных типов.

14.1 Классы

Классовые объекты могут присваиваться, передаваться функциям как параметры и возвращаться функциями. Другие возможные операции, как, например, проверка равенства, могут быть определены пользователем; см. #8.5.10.

14.2 Функции

Есть только две вещи, которые можно проделывать с функцией: вызывать ее и брать ее адрес. Если в выражении имя функции возникает не в положении имени функции в вызове, то генерируется указатель на функцию. Так, для передачи одной функции другой можно написать

	typedef int (*PF) ();
	extern g (PF);
	extern f ();
	...
	g (f);
Тогда определение g может иметь следующий вид:
	g (PF funcp)
	  {
	    ...
	    (*funcp) ();
	    ...
	  }

Заметьте, что f должна быть описана явно в вызывающей программе, поскольку ее появление в g(f) не сопровождалось (.

14.3 Массивы, указатели и индексирование

Всякий раз, когда в выражении появляется идентификатор типа массива, он преобразуется в указатель на первый член массива. Из-за преобразований массивы не являются адресами. По определению операция индексирования [] интерпретируется таким образом, что E1[E2] идентично *((E1)+(E2)). В силу правил преобразования, применяемых к +, если E1 массив и E2 целое, то E1[E2] относится к E2-ому члену E1. Поэтому, несмотря на такое проявление асимметрии, индексирование является коммутативной операцией.

Это правило сообразным образом применяется в случае многомерного массива. Если E является n-мерным массивом ранга i*j*...*k, то возникающее в выражении E преобразуется в указатель на (n-1)-мерный массив ранга j*...*k. Если к этому указателю, явно или неявно, как результат индексирования, применяется операция *, ее результатом является (n-1)-мерный массив, на который указывалось, который сам тут же преобразуется в указатель.

Рассмотрим, например,

	int x[3][5];

Здесь x - массив целых размером 3*5. Когда x возникает в выражении, он преобразуется в указатель на (первый из трех) массив из 5 целых. В выражении x[i], которое эквивалентно *(x+1), x сначала преобразуется, как описано, в указатель, затем 1 преобразуется к типу x, что включает в себя умножение 1 на длину объекта, на который указывает указатель, а именно объект из 5 целых. Результаты складываются, и используется косвенная адресация для получения массива (из 5 целых), который в свою очередь преобразуется в указатель на первое из целых. Если есть еще один индекс, снова используется тот же параметр; на этот раз результат является целым.

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

14.4 Явные преобразования указателей

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

Указатель иожет быть преобразован к любому из целых типов, достаточно больших для его хранения. То, какой из int и long требуется, является машинно-зависимым. Преобразующая функция также является машинно-зависимой, но предполагается, что она не содержит сюрпризов для того, кто знает структуру адресации в машине. Подробности для некоторых конкретных машин были даны в #2.6.

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

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

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

	extern void* alloc ();
	double* dp;
	dp = (double*) alloc (sizeof (double));
	*dp= 22.0 / 7.0;
alloc должна обеспечивать (машинно-зависимым образом) то, что возвращаемое ею значение подходит для преобразования в указатель на double; в этом случае использование функции мобильно. Различные машины различаются по числу бит в указателях и требованиям к выравниванию объектов. Составные объекты выравниваются по самой строгой границе, требуемой каким-либо из его составляющих.

15. Константные выражения

В нескольких местах C++ требует выражения, вычисление которых дает константу: в качестве границы массива (#8.3), в case выражениях (#9.7), в качестве значений параметров функции, присваиваемых по умолчанию, (#8.3), и в инициализаторах (#8.6). В первом случае выражение может включать только целые константы, символьные константы, константы, описанные как имена, и sizeof выражения, возможно, связанные бинарными операциями

	 + - * / % & | ^ << >> == != < > <= >= && ||
или унарными операциями
	- ~ !
или тернарными операциями
	? :

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

Большая широта допустима для остальных трех случаев использования; помимо константных выражений, обсуждавшихся выше, допускаются константы с плавающей точкой, и можно также применять унарную операцию & к внешним или статическим объектам, или к внешним или статическим массивам, индексированным константным выражением. Унарная операция & может также быть применена неявно с помощью употребления неиндексированных массивов и функций. Основное правило состоит в том, что инициализаторы должны при вычислении давать константу или адрес ранее описанного внешнего или статического объекта плюс или минус константа.

Меньшая широта допустима для константных выражений после #if: константы, описанные как имена, sizeof выражения и перечислимые константы недопустимы.

16. Соображения мобильности

Определенные части C++ являются машинно-зависимыми по своей сути. Следующий ниже список мест возможных затруднений не претендует на полноту, но может указать на основные из них.

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

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

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

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

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

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

Назад | Содержание | Вперед