Язык программирования C++ для профессионалов

       

Указатели и массивы


Указатели и массивы в языке Си++ тесно связаны. Имя массива можно использовать как указатель на его первый элемент, поэтому пример с массивом alpha можно записать так:

int main() { char alpha[] = "abcdefghijklmnopqrstuvwxyz"; char* p = alpha; char ch;

while (ch = *p++) cout << ch << " = " << int (ch) << " = 0" << oct(ch) << '\n'; }

Можно также задать описание p следующим образом:

char* p = &alpha[0];

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

void f() { extern "C" int strlen(const char*); // из <string.h> char v[] = "Annemarie"; char* p = v; strlen(p); strlen(v); }

Но в том и загвоздка, что обойти это нельзя: не существует способа так описать функцию, чтобы при ее вызове массив v копировался. Результат применения к указателям арифметических операций +, -, ++ или -- зависит от типа указуемых объектов. Если такая операция применяется к указателю p типа T*, то считается, что p указывает на массив объектов типа T. Тогда p+1 обозначает следующий элемент этого массива, а p-1 - предыдущий элемент. Отсюда следует, что значение (адрес) p+1 будет на sizeof(T) байтов больше, чем значение p. Поэтому в следующей программе

main() { char cv[10]; int iv[10];

char* pc = cv; int* pi = iv;

cout << "char* " << long(pc+1)-long(pc) << '\n'; cout << "int* " << long(pi+1)-long(pi) << '\n'; }

с учетом того, что на машине автора (Macintosh) символ занимает один байт, а целое - четыре байта, получим:

char* 1 int* 4

Перед вычитанием указатели были явной операцией преобразованы к типу long. Он использовался для преобразования вместо "очевидного" типа int, поскольку в некоторых реализациях языка С++ указатель может не поместиться в тип int (т.е. sizeof(int)<(sizeof(char*)). Вычитание указателей определено только в том случае, когда они оба указывают на один и тот же массив (хотя в языке нет возможностей гарантировать этот факт). Результат вычитания одного указателя из другого равен числу (целое) элементов массива, находящихся между этими указателями. Можно складывать с указателем или вычитать из него значение целого типа; в обоих случаях результатом будет указатель. Если получится значение, не являющееся указателем на элемент того же массива, на который был настроен исходный указатель (или указателем на следующий за массивом элемент), то результат использования такого значения неопределен. Приведем пример:

void f() { int v1[10]; int v2[10];

int i = &v1[5]-&v1[3]; // 2 i = &v1[5]-&v2[3]; // неопределенный результат

int* p = v2+2; // p == &v2[2] p = v2-2; // *p неопределено }

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



Содержание раздела