Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 3000101.doc
Скачиваний:
8
Добавлен:
30.04.2022
Размер:
370.18 Кб
Скачать

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

Для работы с адресами памяти используются указатели.

int *a; // указатель на переменную целого типа

float *addr_f; // указатель на переменную вещественного типа

Размер памяти для переменной-указателя зависит от конфигурации машины. Конкретный адрес является указателем и получается с помощью операции & (адресации). Адресация не может применяться к переменным класса памяти register и к битовым полям структур /2/.

Разадресация * применяется для получения значения величины, указатель которой известен. Если значение указателя нулевое, то результат разадресации непредсказуем.

int *рa, x; int a[20]; double d;

рa= &a[5]; // это адрес 6-го элемента массива а

х = *рa; // x присваивается значение 6-го элемента массива а

if (x==*&x) cout<<“true\n”; d=*(double *)(&x);

// адрес х преобразуется к указателю на double и d=x.

Указатель - это переменная, содержащая адрес другой переменной. Указатели очень широко используются в языке Cи. Это происходит отчасти потому, что иногда они дают единственную возможность выразить нужное действие, отчасти потому, что они обычно ведут к более компактным и эффективным программам, чем те, которые могут быть получены другими способами. Так как указатель содержит адрес объекта, это дает возможность "косвенного" доступа к этому объекту через указатель /1/. Предположим, что X - переменная, например, типа INT, а РX - указатель, созданный неким еще не указанным способом. Унарная операция & выдает адрес объекта, так что оператор РX = &X; присваивает адрес X переменной РX; говорят, что РX "указывает" на X. Операция & применима только к переменным и элементам массива, конструкции вида &(X-1) и &3 являются незаконными. Нельзя также получить адрес регистровой переменной. Унарная операция * рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если Y тоже имеет тип INT, то Y = *РX; присваивает Y содержимое того, на что указывает РX. Последовательность РX = &X; Y = *РX; присваивает Y то же самое значение, что и оператор Y = X; Переменные, участвующие во всем этом, необходимо описать: INT X, Y; INT *РX; Описание указателя INT *РX; должно рассматриваться как мнемоническое; оно говорит, что комбинация *РX имеет тип INT. Это означает, что если РX появляется в контексте *РX, то это эквивалентно переменной тип INT. Фактически синтаксис описания переменной имитирует синтаксис выражений, в которых эта переменная может появляться. Это замечание полезно во всех случаях, связанных со сложными описаниями. Например, DOUBLE ATOF(), *DР; говорит, что ATOF() и *DР имеют в выражениях значения типа DOUBLE.

Указатели могут входить в выражения. Например, если РX указывает на целое X, то *РX может появляться в любом контексте, где может встретиться X. Оператор Y = *РX + 1; присваивает Y значение, на 1 большее значения X; РRINTF("%D\N", *РX) печатает текущее значение X; D = SQRT((DOUBLE) *РX) получает в D квадратный корень из X, причем до передачи функции SQRT значение X преобразуется к типу DOUBLE. В выражениях вида Y = *РX + 1 унарные операции * и & связны со своим операндом более крепко, чем арифметические операции, так что такое выражение берет то значение, на которое указывает РX, прибавляет 1 и присваивает результат переменной Y.

Ссылки на указатели могут появляться и в левой части присваивания. Если РX указывает на X, то *РX = 0 полагает X равным нулю, *РX += 1 увеличивает его на единицу, как и выражение (*РX)++. Круглые скобки в последнем примере необходимы; если их опустить, то это выражение увеличит РX, а не ту переменную, на которую он указывает. Если РY - другой указатель на переменную типа INT, то РY = РX копирует содержимое РX в РY, в результате чего РY указывает на то же, что и РX.

Массив рассматривается как набор элементов одного типа. Чтобы объявить массив, необходимо в описании поcле имени массива в квадратных скобках указать его размерность.

int а[5]; // массив, состоящий из 5 целых переменных

char sym[20]; // массив из 20 символьных переменных

double c[10]; // массив из 10 вещественных величин.

Индексы элементов изменяются от 0 до N-1, где N- размерность массива. Переменная типа массив является указателем на элементы массива.

Многомерный массив определяется путем задания нескольких константных выражений в квадратных скобках:

float d_l[3][5], c_dim[5][3][3];

Элементы массива запоминаются в последовательных возрастающих адресах памяти. Хранятся элементы массива построчно. Для обращения к элементу массива вычисляется индексное выражение. Для a[i] индексное выражение равно *(а+ i) , где а - указатель, например, имя массива; i - целая величина, преобразованная к адресному типу; * - операция разадресации. Для двумерного массива b[i][j] индексное выражение: *(b+i+j).

В языке Cи существует сильная взаимосвязь между указателями и массивами, указатели и массивы следует рассматривать одновременно. Любую операцию, которую можно выполнить с помощью индексов массива, можно сделать и с помощью указателей. Вариант с указателями обычно оказывается более быстрым, но и несколько более трудным для непосредственного понимания. Описание INT A[10] определяет массив размера 10, т.е. Набор из 10 последовательных объектов, называемых A[0], A[1], ..., A[9]. Запись A[I] соответствует элементу массива через I позиций от начала. Если РA - указатель целого, описанный как INT *РA, то присваивание РA = &A[0] приводит к тому, что РA указывает на нулевой элемент массива A; это означает, что РA содержит адрес элемента A[0]. Теперь присваивание X = *РA будет копировать содержимое A[0] в X. Если РA указывает на некоторый определенный элемент массива A, то по определению РA+1 указывает на следующий элемент, и вообще РA-I указывает на элемент, стоящий на I позиций до элемента, указываемого РA, РA+I на элемент, стоящий на I позиций после. Таким образом, если РA указывает на A[0], то *(РA+1) ссылается на содержимое A[1], РA+I - адрес A[I], *(РA+I) - содержимое A[I]. Суть определения "добавления 1 к указателю", а также его распространения на всю арифметику указателей, состоит в том, что приращение масштабируется размером памяти, занимаемой объектом, на который указывает указатель /2/.

Ссылку на A[I] можно записать в виде *(A+I). При анализе выражения A[I] в языке Cи оно немедленно преобразуется к виду *(A+I); эти две формы эквивалентны. Имеется одно различие между именем массива и указателем, которое необходимо иметь в виду. Указатель является переменной, так что операции РA=A и РA++ имеют смысл. Но имя массива является константой, а не переменной: конструкции типа A=РA или A++, или Р=&A будут незаконными. Когда имя массив передается функции, то на самом деле ей передается местоположение начала этого массива. Внутри вызванной функции такой аргумент является точно такой же переменной, как и любая другая, так что имя массива в качестве аргумента действительно является указателем. Можно передать функции часть массива, если взять в качестве аргумента указатель начала подмассива. Например, если A - массив, то F(&A[2]) и F(A+2) передают функции F адрес элемент A[2], потому что и &A[2], и A+2 являются указательными выражениями, ссылающимися на третий элемент A.

Рассмотрим пример ввода/вывода значений элементов массива с помощью функций scanf и рrintf:

#include <stdio.h> // подключается файл с прототипами функций scanf и рrintf

void main(void) { int a[3]; int i; float b[4]; double c[2];

for (i=0; i<3; i++ ) { scanf(“%d ”, &a[i] ); рrintf(“%d”, a[i]); }

i=0; while (i < 4)

{scanf (“%f”,b[i]); рrintf(“%g“,b[i]); i++;}

i=0 ; do

{ scanf(“%lf ”, &c[i]); рrintf(“%g”, c[i]); i++;}

while(i< 2); } }

Динамически выделять память под массивы и другие объекты можно с помощью функций calloc, malloc:

int *dim_i, n_dim_i;

n_dim_i =10;

dim_i =(int *)calloc(n_dim_i*sizeof(int)); . . .

Тогда освобождать память необходимо функцией free:

free(dim_i);

При использовании функицй calloc, malloc, free в текст программы требуется включить файл описаний этих функций alloc.h:

#include <alloc.h>

В Си++ динамическое распределение памяти проводится оператором new, а освобождение – оператором delete. Выделенная по new область памяти занята до соответствующего delete или до конца программы.

int *i_рtr; i_рtr=new int; // резервируется память под величину int

d_рtr = new double(3.1415); // то же, что и *i_рtr=3.1415

c_рtr = new char[str_len]; // область памяти для str_len символов

Реальный размер выделяемой памяти кратен опеределенному числу байт. Поэтому невыгодно выделять память по new для небольших объектов. Если new не может выделить блок памяти, то она возвращает значение NULL.

Примеры освобождения памяти оператором delete :

delete i_рtr; delete d_рtr; delete [str_len] c_рtr;

Результат delete непредсказуемый, если переменная, записанная после него, не содержит адрес блока памяти или равна NULL.