В этом разделе кратко собрано описание действий, которые могут
совершаться над объектами различных типов.
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
указатель и просмотр памяти, на которую указывает указатель), но
должны приниматься во внимание при согласовании внешне предписанных
форматов памяти.
|