Создание нового проекта консольного типа, страница 3

float *p;           // Объявление указателя p

p = new float[n];   // Присвоение указателю p

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

Указатели, объявленные вне функций или объявленные с описателем static, автоматически инициализируются нулями, как и все глобальные переменные в C++. Указатели, объявленные внутри функции (автоматические переменные), не инициализированы вовсе. Использование такого указателя до присвоения ему осмысленного значения является серьезной ошибкой. Нулевой адрес (NULL или 0) не может быть присвоен указателю никакой из функций или операций динамического выделения памяти (new, malloc и т. д.). В связи с этим он обычно служит признаком того, что указатель еще не был инициализирован программистом. NULL — это символическая константа, определенная в stdio.h для нулевого указателя. Символической константой называется константа, определенная с помощью макроподстановки #define. Например, #define NULL (void*)0 определяет символическую константу NULL, которая до компиляции везде будет заменена препроцессором на выражение (void*)0. Константа NULL определена как целый ноль, явно приведенный к типу void*, то есть указателю на произвольный, неопределенный тип.

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

При объявлении указателя задается тип переменных, на которые он может указывать. Это кажется лишним, так как указатель любого типа в Win32 — 4 байта, содержащие адрес какого-либо объекта в памяти. Но все дело в том, что с указателями связана адресная арифметика, правила которой различны для разных типов указателей. Так, если к указателю на тип int прибавить единицу, то его значение изменится на 4 байта или sizeof(int). Это же действие в Win16 приведет к увеличению указателя на 2 байта. Забегая вперед, скажем, что при увеличении указателя на массив объектов какого-либо класса (например, класса Man) указатель сдвинется в памяти ровно на один объект этого класса, независимо от того, сколько памяти он занимает.

Имя массива в языке С фактически является указателем на первый его элемент, который соответствует нулевому значению индекса (или индексов в случае многомерных массивов). Если объявлен массив float a[16];, то справедливо равенство а==&a[0]. Переменные типа указатель могут быть использованы и часто используются для доступа к элементам массива. Рассмотрим пример, в котором совместно используются массив переменных вещественного типа и указатель на переменные этого же типа. Имеют смысл следующие присвоения:

float a[16],*p;       // Объявление массива и указателя

for (int i=0;  i<16;  i++)

  a[i] = float(i*i);   // Заполнение массива

p = &a[6];    // p указывает на а[6]

*p = 3.14f;   // Равносильно a[6] = 3.14;

p++;           // Теперь p указывает на а[7]. Произошел сдвиг на 4 байта

a[1] = *(p+3); // Равносильно a[1] = a[10]; В a[1] попадает 100.

a[2] = ++*p;  // Равносильно a[2] = ++a[7]; В a[2] попадает 50.

a[3] = *++p;  // Равносильно a[3] = a[8]; В a[3] попадает 64.

Адресная арифметика, например p+3 или ++p, осуществляется в единицах объявленного базового типа данных float. Если в конкретной вычислительной системе число типа float занимает 4 байта, то результатом операции р+3 будет адрес, отстоящий от р на 12 байт. Значение ++*p вычисляется так: сначала выбирается (*p) — содержимое по адресу p, то есть a[7], так как в данный момент указатель содержит &a[7]. Затем выполняется приращение (increment) a[7], то есть увеличение a[7] на единицу. При вычислении *++p, наоборот, сначала производится изменение указателя (++p), потом выборка содержимого по адресу, на который он указывает.

Тесную связь между указателями и массивами проиллюстрируем еще одним примером, в котором суммируются члены сходящегося ряда, предварительно размещенные в динамическом массиве. Так как для вычисления квадрата мы пользуемся функцией pow(a,b), позволяющей возвести a в степень b, то следует подключить файл math.h, в котором определены математические функции.

double sum (float*, float*);    // Прототип функции

void main()

{                        // Сумма сходящегося ряда

int size;

puts ("\n Enter an array size:");

size = in (1, 100000);         // Наша функция ввода

float *first = new float[size]; // Динамический массив

float *last = first + size - 1; // Адрес последнего элемента

printf ("\n\t Starting address of the array = %p"

    "\n\t Ending address of the array   = %p", first,last);

for (int i=0; i<size; i++)   // Заполнение массива (члены ряда)

  first[i] = 1./pow(i+1, 2);

printf ("\n\t Current sum value = %8.4f", sum (first, last));

}

      //===== Вычисление суммы элементов массива =====//

double sum (float *start, float *end)

{

double sum=0.;

while (start <= end)  // Пробег по массиву с помощью указателей

  sum += *start++;     // Вклады в сумму

return sum;

}