Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Рацеев С.М. Программирование на языке Си.pdf
Скачиваний:
366
Добавлен:
23.03.2016
Размер:
1.65 Mб
Скачать

}

int main( )

{

int a[] = {1, 1, 2, 2, 2, 3, 3}; int i, size;

size = sizeof(a) / sizeof(*a); Zamena(a, &size);

for(i = 0; i < size; i++) printf("%d ", a[i]);

return 0;

}

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

Для начала заметим, что, как и у всякой переменной, у переменной типа массив должно быть некоторое значение. Значением переменной типа массив является адрес первого элемента массива:

Поэтому допустимо следующее присвоение:

int a[10], *pa; pa = a;

В данном случае переменная-указатель pa будет иметь в качестве своего значения адрес первого элемента массива a. Заметим, что присвоение pa = a можно также записать и в такой эквивалентной форме: pa = &a[0].

Запись или считывание данных в массив происходят следующим образом. Для этого компилятор вычисляет адрес соответствующего элемента, учитывая размер элементов, из которых массив состоит, и величину сдвига относительно первого элемента.

89

Например, чтобы записать или считать i-й элемент массива double a[10], компилятор умножит значение сдвига i на размер элемента типа double (то есть на 8) и получит 8*i байт. Затем компилятор добавит к адресу первого элемента массива 8*i байт сдвига и тем самым вычислит адрес i-го элемента данного массива. После этого по данному адресу i-й элемент можно считать, либо некоторое значение по данному адресу можно записать.

Компилятор не контролирует выход за границы массива. Если, например, при записи или считывании данных будет указан адрес a[20], то компилятор запишет или считает в ячейку по адресу, сдвинутому на 160 байт относительно адреса первого элемента массива, некоторое значение. В данном случае выбранная область памяти может принадлежать другой переменной, и результат программы может быть непредсказуемым.

Существует две формы записи для доступа к элементом массива. Первый способ (индексный) связан с использованием операции [], например a[0], a[5].

Второй способ связан с использованием адресных выражений. Заметим, что при прибавлении единицы к целой переменной ее значение увеличится на 1. Но если прибавить единицу к пере- менной-указателю, то ее значение увеличится на число байт того типа, на который она указывает. Например, если единицу прибавить к указателю на float, то значение данного указателя увеличится на 4. Поэтому значение a[i] можно записать с помощью адресного выражения: *(a+i). То есть значение a+i является адресом i-го элемента массива a, а *(a+i) является значением i-го элемента данного массива. Заметим также, что записи &a[i] и a+i являются эквивалентными. Любое выражение вида a[i] всегда приводится к виду *(a+i), то есть индексное выражение преобразуется к адресному.

Ниже приведены две эквивалентные программы, причем первая программа всегда преобразуется компилятором ко второму варианту.

#include <stdio.h>

#include <stdio.h>

#include <stdlib.h>

#include <stdlib.h>

#define N 10

 

90

int main( )

 

 

int main( )

 

 

{

 

 

{

 

 

int a[N],

/* массив a размером N */

int a[10], /* массив a размером N */

i;

/* счетчик

*/

i;

/* счетчик

*/

for (i = 0; i < N; i++)

 

for (i = 0; i < 10; i++)

 

{

 

 

{

 

 

a[i] = rand()%10;

 

*(a+i) = rand()%10;

 

printf("%d ", a[i]);

 

printf("%d ", *(a+i));

 

}

 

 

}

 

 

return 0;

 

 

return 0;

 

 

}

 

 

}

 

 

Еще раз подчеркнем, что имя массива не является переменной, поэтому нельзя, например, записать a++, хотя можно записать pa++.

Имеется три варианта передачи массива в качестве параметра функции:

1. Параметр задается как массив с указанием его размера, например

имя_функции (int a[100])

2. Параметр задается как массив без указания его размера, например

имя_функции (int a[])

3.Параметр задается как указатель, например имя_функции (int *a)

Когда компилятор встречает в качестве параметра одномерный массив, записанный в форме int a[100] или int a[], то он преобразует этот параметр к виду int *a.

Исходя из сказанного выше, приведем пример вычисления суммы элементов массива через указатели:

/* использование арифметики указателей */ int Sum (int *begin, int *end)

{

int sum = 0, *pa;

91

for(pa = begin; pa < end; pa++) sum += *pa;

return sum;

}

int main()

{

int sum, size, a[] = {2, 3, 5, 10, 20};

size = sizeof(a)/sizeof(*a); /* количество элементов в массиве */ sum = Sum(a, a + size);

printf("sum = %d\n", sum); return 0;

}

7.4.Операции с указателями

Вязыке Си имеются базовые операции, которые можно выполнять с указателями. Рассмотрим эти операции.

Операция присваивания. Указателям можно присваивать адреса ячеек памяти. Чаше всего указателю присваивается адрес первого элемента массива (когда массив передается функции в качестве параметра) или адрес переменной с помощью унарной операции &. Например:

int a[N], b, *pa, *pb;

pa = a; /* pa содержит адрес первого элемента массива a */ pb = &b; /* pb содержит адрес переменной b */

Также можно к значению одного указателя присвоить значение другого, например

char *pc; int *pa;

pa = (char *)pc;

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

92

Определение значения (разыменование). Унарная операция

* возвращает значение переменной, на которую указывает указатель. Например,

int a = 0, *pa;

pa = &a; /* указатель pa содержит адрес переменной a */ *pa = 1; /* теперь значение переменной a равно 1 */

Заметим, что исходя из определения операций & и *, можно с переменной оперировать следующим образом:

int a;

*&a = 123; /* в переменную a записано число 123 */

Адрес указателя. Как и любая переменная, переменнаяуказатель имеет адрес и значение, поэтому с помощью унарной операции & можно определять адрес самой переменной-указателя:

int *pa; printf("%p\n", &pa);

Прибавление к указателю целого числа. С помощью опера-

ции + можно к указателю прибавить целое число. При этом это целое число сначала умножается на количество байт типа данных, на который указывает указатель (тип указателя), а затем это значение прибавляется к значению указателя (то есть к адресу, который является значением указателя). Приведем пример.

int a[N], *pa;

pa = a; /* pa содержит адрес первого элемента массива a */ pa += 2; /*теперь pa содержит адрес элемента a[2] массива a*/

Инкремент значения указателя. К указателю можно приме-

нять операцию инкрементирования (++) значения, при этом значение указателя увеличивается на количество байт типа указателя.

Вычитание целочисленных значений. С помощью операции

– можно вычитать целое число от значения указателя. При этом это целое число сначала умножается на количество байт типа данных, на который указывает указатель, а затем от значения указателя вычитается полученное число.

Декремент значения указателя. К указателю можно приме-

нять операцию декрементирования (--).

93