Методы доступа к элементам массивов

В языке С++ между указателями и массивами существует тесная связь. Например, когда объявляется массив в виде int array[25], то этим определяется не только выделение памяти для двадцати пяти элементов массива, но и для указателя с именем array, значение которого равно адресу первого по счету (нулевого) элемента массива, т.е. сам массив остается безымянным, а доступ к элементам массива осуществляется через указатель с именем array. С точки зрения синтаксиса языка указатель array является константой, значение которой можно использовать в выражениях, но изменить это значение нельзя.

Поскольку имя массива является указателем допустимо, например, такое присваивание:

int array[25]; int *ptr; ptr=array;

Здесь указатель ptr устанавливается на адрес первого элемента масcива с индексом 0, причем присваивание ptr=array можно записать в эквивалентной форме ptr=&array[0].

Теперь присваивание x = *ptr будет копировать содержимое array[0] в x. Если рtr указывает на некоторый определенный элемент массива array, то по определению ptr+1 указывает на следующий элемент, и вообще ptr-i указывает на элемент, стоящий на i позиций до элемента, указываемого ptr, а ptr+i на элемент, стоящий на i позиций после. Таким образом, если ptr указывает на array[0], то *(ptr+1) ссылается на содержимое array[1], ptr+i - адрес array[i], а *(ptr+i) - содержимое array[i].

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

Первый способ связан с использованием обычных индексных выражений в квадратных скобках, например, array[16]=3 или array[i+2]=7. При таком способе доступа записываются два выражения, причем второе выражение заключается в квадратные скобки. Одно из этих выражений должно быть указателем, а второе - выражением целого типа. Последовательность записи этих выражений может быть любой, но в квадратных скобках записывается выражение следующее вторым. Поэтому записи array[16] и 16[array] будут эквивалентными и обозначают элемент массива с номером шестнадцать. Указатель, используемый в индексном выражении, не обязательно должен быть константой, указывающей на какой-либо массив, это может быть и переменная. В частности после выполнения присваивания ptr=array доступ к шестнадцатому элементу массива можно получить с помощью указателя ptr в форме ptr[16] или 16[ptr].

Второй способ доступа к элементам массива связан с использованием адресных выражений и операции разадресации в форме *(array+16)=3 или *(array+i+2)=7. При таком способе доступа адресное выражение равное адресу шестнадцатого элемента массива тоже может быть записано разными способами *(array+16) или *(16+array).

При реализации на компьютере первый способ приводится ко второму, т.е. индексное выражение преобразуется к адресному. Для приведенных примеров array[16] и 16[array] преобразуются в *(array+16).

Для доступа к начальному элементу массива (т.е. к элементу с нулевым индексом) можно использовать просто значение указателя array или ptr. Любое из присваиваний

*array = 2; array[0] = 2; *(array+0) = 2; *ptr = 2; ptr[0] = 2; *(ptr+0) = 2;

присваивает начальному элементу массива значение 2, но быстрее всего выполнятся присваивания *array=2 и *ptr=2, так как в них не требуется выполнять операции сложения.

Пример:

Дана последовательность чисел (способ ее формирования выбрать самостоятельно). Найти до первого ноля количество элементов равных 6 и равных 3 после него.

#include<iostream.h>

#include<iomanip.h>

#include<stdlib.h>

const n=10;

int *p,*p1,kol=0,kol1=0;

void kol_6_3();

void main()

{

kol_6_3();

cout<<endl<<" "<<*p;

cout<<endl<<" "<<*p1;

}

void kol_6_3()

{

int a;

p=&a;

int i=1;

do

{ cin>>*p;

if(*p==6) kol++;

i++;}

while (*p!=0 && i<=n);

p1=&a;

if (i<=n)

do

{ cin>>*p;

if(*p==3) kol1++;

i++;}

while (*p!=0 && i<=n);

p=&kol;

p1=&kol1;

}

Строчная константа, как, например, "i am a string" является массивом символов. Компилятор завершает внутреннее представление такого массива символом \0, так что программы могут находить его конец. Таким образом, длина массива в памяти оказывается на единицу больше числа символов между двойными кавычками.

По-видимому, чаще всего строчные константы появляются в качестве аргументов функций, как, например, в

printf ("hello, world\n");

Когда символьная строка, подобная этой, появляется в программе, то доступ к ней осуществляется с помощью указателя символов; функция printf фактически получает указатель символьного массива.

Конечно, символьные массивы не обязаны быть только аргументами функций. Если описать message как

char *message;

то в результате оператора

message = "now is the time";

переменная message станет указателем на фактический массив символов. Это не копирование строки; здесь участвуют только указатели. В языке "C" не предусмотрены какиe-либо операции для обработки всей строки символов как целого.

Мы проиллюстрируем другие аспекты указателей и массивов, разбирая две полезные функции из стандартной библиотеки ввода-вывода, которая будет рассмотрена в главе 7.

Первая функция - это strcpy(s,t), которая копирует строку t в строку s. Аргументы написаны именно в этом порядке по аналогии с операцией присваивания, когда для того, чтобы присвоить t к s обычно пишут

s = t;

сначала приведем версию с массивами:

strcpy(s, t) /* copy t to s */ char s[], t[]; { int i; i = 0; while ((s[i] = t[i])!= '\0') i++; }

Для сопоставления ниже дается вариант strcpy с указателями.

strcpy(s, t) /* copy t to s; pointer version 1 */ char *s, *t; { while ((*s = *t)!= '\0') { s++; t++; } }

Так как аргументы передаются по значению, функция strcpy может использовать s и t так, как она пожелает. Здесь они с удобством полагаются указателями, которые передвигаются вдоль массивов, по одному символу за шаг, пока не будет скопирован в s завершающий в t символ \0.

На практике функция strcpy была бы записана не так, как мы показали выше. Вот вторая возможность:

strcpy(s, t) /* copy t to s; pointer version 2 */ char *s, *t; { while ((*s++ = *t++)!= '\0'); }

Здесь увеличение s и t внесено в проверочную часть. Значением *t++ является символ, на который указывал t до увеличения; постфиксная операция ++ не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ помещается в старую позицию s, до того как s будет увеличено. Конечный результат заключается в том, что все символы, включая завершающий \0, копируются из t в s. И как последнее сокращение мы опять отметим, что сравнение с \0 является излишним, так что функцию можно записать в виде

strcpy(s, t) /* copy t to s; pointer version 3 */ char *s, *t; { while (*s++ = *t++); }

Хотя с первого взгляда эта запись может показаться загадочной, она дает значительное удобство. Этой идиомой следует овладеть уже хотя бы потому, что вы с ней будете часто встречаться в C++-программах.

Вторая функция - strcmp(s, t), которая сравнивает символьные строки s и t, возвращая отрицательное, нулевое или положительное значение в соответствии с тем, меньше, равно или больше лексикографически s, чем t. Возвращаемое значение получается в результате вычитания символов из первой позиции, в которой s и t не совпадают.

strcmp(s, t) /* return <0 if s<t, 0 if s==t, >0 if s>t */ char s[], t[]; { int i; i = 0; while (s[i] == t[i]) if (s[i++] == '\0') return(0); return(s[i]-t[i]); }

Вот версия strcmp с указателями: strcmp(s, t) /* return <0 if s<t, 0 if s==t, >0 if s>t */ char *s, *t; { for (; *s == *t; s++, t++) if (*s == '\0') return(0); return(*s-*t); }

Рассмотрим еще один пример работы со строками, как с элементами массива:strlen(s) /* return length of string s */ char *s; { int n; for (n = 0; *s!= '\0'; s++) n++; return(n); }

Операция увеличения s совершенно законна, поскольку эта переменная является указателем; s++ никак не влияет на символьную строку в обратившейся к strlen функции, а только увеличивает локальную для функции strlen копию адреса.

Описания формальных параметров в определении функции в виде

char s[];

и

char *s;

совершенно эквивалентны; какой вид описания следует предпочесть, определяется в значительной степени тем, какие выражения будут использованы при написании функции. Если функции передается имя массива, то в зависимости от того, что удобнее, можно полагать, что функция оперирует либо с массивом, либо с указателем, и действовать далее соответствующим образом. Можно даже использовать оба вида операций, если это кажется уместным и ясным.

Можно передать функции часть массива, если задать в качестве аргумента указатель начала подмассива. Например, если а - массив, то как

f(&array[2])

так и

f(array+2)

передают функции f адрес элемента array[2], потому что и &array[2], и array+2 являются указательными выражениями, ссылающимися на третий элемент аrray. Внутри функции f описания аргументов могут присутствовать в виде:

f(arr) int arr[]; {... }

или

f(arr) int *arr; {... }

Что касается функции f, то тот факт, что ее аргумент в действительности ссылается к части большего массива,не имеет для нее никаких последствий.


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: