Logo GenDocs.ru

Поиск по сайту:  


Загрузка...

Лекции по C/С++ - файл Л7.doc


Загрузка...
Лекции по C/С++
скачать (357.8 kb.)

Доступные файлы (8):

Л1.doc318kb.06.09.2009 15:02скачать
Л2.doc250kb.06.09.2009 14:56скачать
Л3.doc127kb.07.12.2009 15:34скачать
Л4.doc250kb.06.09.2009 14:57скачать
Л5.doc151kb.06.09.2009 14:58скачать
Л6.doc165kb.06.09.2009 14:58скачать
Л7.doc150kb.06.09.2009 14:58скачать
Л8.doc269kb.13.01.2006 20:00скачать

Л7.doc

Реклама MarketGid:
Загрузка...

Лекция №7

10. Указатели и ссылки

10.1. Указатели


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

^ Форма записи:

Спецификатор-типа [ модификатор ] * описатель .

Спецификатор-типа задает тип объекта и может быть любого основного типа, типа структуры, смеси (об этом будет сказано ниже). Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа. Однако для того, чтобы можно было выполнить арифметические и логические операции над указателями или над объектами, на которые они указывают, необходимо при выполнении каждой операции явно определить тип объектов. Такие определения типов может быть выполнено с помощью операции приведения типов.
^

10.1.1. Указатели и адреса


Предположим, что х - переменная, например, типа int, а рх – указатель:

int x=-3;

int *px;
Унарная операция &(взятие адреса) выдает адрес объекта, так что оператор

px = &x;

присваивает адрес x переменной px; говорят, что px "указывает" на x. Операция & применима только к переменным и элементам массива.
Для

int *U;

register int reg1;

недопустимы конструкции вида:

U=&(х-1); /* результатом выражения является число, находящееся в программном

стеке, его адрес не может быть получен */

U= &3 ; /* с константой 3 не связана ни одна ячейка памяти */

U=&reg1; /* нельзя получить адрес регистровой переменной, т.к. располагается не в

памяти, а в регистрах процессора */

Унарная операция * (разыменования) рассматривает свой операнд как адрес конечной цели и обращается по этому адресу, чтобы извлечь содержимое. Следовательно, если y тоже имеет тип int, то

int x=-3, y;

int *px;

px=&x;

y = *px;

присваивает y содержимое того, на что указывает px, т.е. само значение переменной х.

Описание указателя говорит о том, что комбинация *px имеет тип int. Это означает, что если px появляется в контексте *px, то это эквивалентно переменной типа int. Фактически синтаксис описания переменной имитирует синтаксис выражений, в которых эта переменная может появляться. Это замечание полезно во всех случаях, связанных со сложными описаниями. Например,

double atof(), *dp;

говорит, что atof() и *dp имеют в выражениях значения типа double.

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

Перед использованием описанного указателя надо обязательно задать ему адрес переменной (постоянной), на которую он будет указывать.

Указатели могут входить в выражения. Например, если px указывает на целое x, то *px может появляться в любом контексте, где может встретиться x. Так оператор

y = *px + 1;

присваивает y значение, на 1 большее значения x;

printf("%d\n", *px)

печатает текущее значение x;

d = sqrt(double) *px)

получает в d квадратный корень из x, причем до передачи функции sqrt значение x преобразуется к типу double. (смотрите главу 2).

В выражениях вида

y = *px + 1;

унарные операции * и & связаны со своим операндом более крепко, чем арифметические операции, так что такое выражение берет то значение, на которое указывает рх, прибавляет 1 и присваивает результат переменной y. Мы вскоре вернемся к тому, что может означать выражение

y = *(px + 1);

Ссылки на указатели могут появляться и в левой части присваивания. Если px указывает на x, то

*px = 0;

полагает х равным нулю, а

*px += 1;

увеличивает его на единицу, как и выражение

(*px) + 1; или *px + 1;

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

И наконец, так как указатели являются переменными, то с ними можно обращаться, как и с остальными переменными. Если py - другой указатель на переменную типа int, то

py = px;

копирует содержимое рх в py, в результате чего py указывает на то же, что и px.



Самостоятельная проработка:



В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.

Примеры:

unsigned int * a; /* переменная а представляет собой указатель

на тип unsigned int (целые числа без знака) */

double * x; /* переменная х указывает на тип данных с

плавающей точкой удвоенной точности */

char * fuffer ; /* объявляется указатель с именем fuffer

который указывает на переменную типа char */

double nomer;

void *addres;

addres = & nomer;

(double *)addres ++;

/* Переменная addres объявлена как указатель на объект любого типа. Поэтому ей можно присвоить адрес любого объекта (& - операция вычисления адреса). Однако, как было отмечено выше, ни одна арифметическая операция не может быть выполнена над указателем, пока не будет явно определен тип данных, на которые он указывает. Это можно сделать, используя операцию приведения типа (double *) для преобразования addres к указателю на тип double, а затем увеличение адреса. */

const * dr;

/* Переменная dr объявлена как указатель на константное выражение, т.е. значение указателя может изменяться в процессе выполнения программы, а величина, на которую он указывает, нет. */

unsigned char * const w = &obj.

/* Переменная w объявлена как константный указатель на данные типа char unsigned. Это означает, что на протяжение всей программы w будет указывать на одну и ту же область памяти. Содержание же этой области может быть изменено. */


^

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


Над указателями можно выполнять унарные операции: инкремент и декремент. При выполнении операций ++ и -- значение указателя увеличивается или уменьшается на длину типа, на который ссылается используемый указатель.

Пример:

int *ptr, a[10];

ptr=&a[5];

ptr++; /* равно адресу элемента a[6] */

ptr--; /* равно адресу элемента a[5] */

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

Пример:

int *ptr1, *ptr2, a[10];

int i=2;

ptr1=a+(i+4); /* равно адресу элемента a[6] */

ptr2=ptr1-i; /* равно адресу элемента a[4] */

В операции вычитания могут участвовать два указателя на один и тот же тип. Результат такой операции имеет тип int и равен числу элементов исходного типа между уменьшаемым и вычитаемым, причем если первый адрес младше, то результат имеет отрицательное значение.

Пример:

int *ptr1, *ptr2, a[10];

int i;

ptr1=a+4;

ptr2=a+9;

i=ptr1-ptr2; /* равно -5 */

i=ptr2-ptr1; /* равно 5 */

Значения двух указателей на одинаковые типы можно сравнивать в операциях ==, !=, <, <=, >, >= при этом значения указателей рассматриваются просто как целые числа, а результат сравнения равен 0 (ложь) или 1 (истина).

Пример:

int *ptr1, *ptr2, a[10];

ptr1=a+5;

ptr2=a+7;

if (prt1>ptr2) a[3]=4;

В данном примере значение ptr1 меньше значения ptr2 и поэтому оператор a[3]=4 не будет выполнен.
^

10.1.3. Указатели и аргументы функций


Так как в С++ передача аргументов функциям осуществляется "по значению", вызванная процедура не имеет непосредственной возможности изменить переменную из вызывающей программы. Что же делать, если вам действительно надо изменить аргумент? Например, программа сортировки захотела бы поменять два нарушающих порядок элемента с помощью функции с именем swap. Для этого недостаточно написать

swap(a, b);

определив функцию swap при этом следующим образом:

void swap(int x,int y)

{

int t;

t= x;

x = y;

y = t;

}

Из-за вызова по значению swap не может воздействовать на аргументы а и b в вызывающей функции.

К счастью, все же имеется возможность получить желаемый эффект. Вызывающая программа передает указатели подлежащих изменению значений:

swap(&a, &b);

Так как операция & выдает адрес переменной, то &a является указателем на a. В самой swap аргументы описываются как указатели и доступ к фактическим операндам осуществляется через них.

void swap(int *px,int *py)

{ int t; t = *px; *px = *py; *py = t;

}

Указатели в качестве аргументов обычно используются в функциях, которые должны возвращать более одного значения. (можно сказать, что swap возвращает два значения, новые значения ее аргументов).



Самостоятельная проработка:


^

10.1.4. Указатели на функции


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

Сортировка часто состоит из трех частей - сравнения, которое определяет упорядочивание любой пары объектов, перестановки, изменяющей их порядок, и алгоритма сортировки, осуществляющего сравнения и перестановки до тех пор, пока объекты не расположатся в нужном порядке. Алгоритм сортировки не зависит от операций сравнения и перестановки, так что, передавая в него различные функции сравнения и перестановки, мы можем организовать сортировку по различным критериям. Именно такой подход используется в нашей новой программе сортировки.

Как и прежде, лексикографическое сравнение двух строк осуществляется функцией strcmp, а перестановка функцией swap; нам нужна еще функция numcmp, сравнивающая две строки на основе численного значения и возвращающая условное указание того же вида, что и strcmp. Эти три функции описываются в main и указатели на них передаются в sort. В свою очередь функция sort обращается к этим функциям через их указатели. Мы урезали обработку ошибок в аргументах с тем, чтобы сосредоточиться на главных вопросах.
#define lines 100 /* мax number of lines

tC be sorted */
main(argc, argv) /* sort input lines */

int argc;

char *argv[];

{

char *lineptr[lines]; /* pointers to text lines */

int nlines; /* number of input lines read */

int strcmp(), numcmp(); /* comparsion functions */

int swap(); /* exchange function */

int numeric = 0; /* 1 if numeric sort */

if(argc>1 && argv[1][0] == '-' && argv[1][1]=='n')

numeric = 1;

if(nlines = readlines(lineptr, lines)) >= 0) {

if (numeric)

sort(lineptr, nlines, numcmp, swap);

else

sort(lineptr, nlines, strcmp, swap);

writelines(lineptr, nlines);

}

else

printf("input too big to sort\n");

}

Здесь strcmp, nimcmp и swap - адреса функций; так как известно, что это функции, операция & здесь не нужна совершенно аналогично тому, как она не нужна и перед именем массива. Передача адресов функций организуется компилятором.

Второй шаг состоит в модификации sort:

sort(v, n, comp, exch) /* sort strings v[0] ... v[n-1] */

char *v[]; /* into increasing order */

int n;

int (*comp)(), (*exch)();

{

int gap, i, j;
for(gap = n/2; gap > 0; gap /= 2)

for(i = gap; i < n; i++)

for(j = i-gap; j >= 0; j -= gap) {

if((*comp)(v[j], v[j+gap]) <= 0)

break;

(*exch)(&v[j], &v[j+gap]);

}

}

Здесь следует обратить определенное внимание на описания. Описание int (*comp)() говорит, что сомр является указателем на функцию, которая возвращает значение типа int. Первые круглые скобки здесь необходимы; без них описание int *comp() говорило бы, что comp является функцией, возвращающей указатель на целые, что, конечно, совершенно другая вещь.

Использование comp в строке

if (*comp)(v[j], v[j+gap]) <= 0)

полностью согласуется с описанием: comp - указатель на функцию, *comp - сама функция, а (*comp)(v[j], v[j+gap]) - обращение к ней. Круглые скобки необходимы для правильного об'единения компонентов.

Мы уже приводили функцию strcmp, сравнивающую две строки по первому численному значению:

numcmp(s1, s2) /* compare s1 and s2 numerically */

char *s1, *s2;

{

double atof(), v1, v2;
v1 = atof(s1);

v2 = atof(s2);

if(v1 < v2)

return(-1);

else if(v1 > v2)

return(1);

else

return (0);

}

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

swap(px, py) /* interchange *px and *py */

char *px[], *py[];

{

char *temp;

temp = *px;

*px = *py;

*py = temp;

}



^

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


В языке С++ между указателями и массивами существует тесная связь. Например, когда объявляется массив в виде 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, то тот факт, что ее аргумент в действительности ссылается к части большего массива,не имеет для нее никаких последствий.
^

10.1.7. Указатели на многомерные массивы


Указатели на многомерные массивы в языке С++ - это массивы массивов, т.е. такие массивы, элементами которых являются массивы. При объявлении таких массивов в памяти компьютера создается несколько различных объектов. Например, при выполнении объявления двумерного массива int arr2[4][3] в памяти выделяется участок для хранения значения переменной arr, которая является указателем на массив из четырех указателей. Для этого массива из четырех указателей тоже выделяется память. Каждый из этих четырех указателей содержит адрес массива из трех элементов типа int, и, следовательно, в памяти компьютера выделяется четыре участка для хранения четырех массивов чисел типа int, каждый из которых состоит из трех элементов. Такое выделение памяти показано на схеме на рис.3.

Arr






arr[0]



arr[0][0]

arr[0][1]

arr[0][2]

arr[1]



arr[1][0]

arr[1][1]

arr[1][2]

arr[2]



arr[2][0]

arr[2][1]

arr[2][2]

arr[3]



arr[3][0]

arr[3][1]

arr[3][2]

^ Рис.3. Распределение памяти для двумерного массива.

Таким образом, объявление arr2[4][3] порождает в программе три разных объекта: указатель с идентификатором arr, безымянный массив из четырех указателей и безымянный массив из двенадцати чисел типа int. Для доступа к безымянным массивам используются адресные выражения с указателем arr. Доступ к элементам массива указателей осуществляется с указанием одного индексного выражения в форме arr2[2] или *(arr2+2). Для доступа к элементам двумерного массива чисел типа int должны быть использованы два индексных выражения в форме arr2[1][2] или эквивалентных ей *(*(arr2+1)+2) и (*(arr2+1))[2]. Следует учитывать, что с точки зрения синтаксиса языка С++ указатель arr и указатели arr[0], arr[1], arr[2], arr[3] являются константами и их значения нельзя изменять во время выполнения программы.

Размещение трехмерного массива происходит аналогично и объявление float arr3[3][4][5] порождает в программе кроме самого трехмерного массива из шестидесяти чисел типа float массив из четырех указателей на тип float, массив из трех указателей на массив указателей на float, и указатель на массив массивов указателей на float.

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

Например, обращение к элементу arr2[1][2] можно осуществить с помощью указателя ptr2, объявленного в форме int *ptr2=arr2[0] как обращение ptr2[1*4+2] (здесь 1 и 2 это индексы используемого элемента, а 4 это число элементов в строке) или как ptr2[6]. Заметим, что внешне похожее обращение arr2[6] выполнить невозможно так как указателя с индексом 6 не существует.

Для обращения к элементу arr3[2][3][4] из трехмерного массива тоже можнo использовать указатель, описанный как float *ptr3=arr3[0][0] с одним индексным выражением в форме ptr3[3*2+4*3+4] или ptr3[22].

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

struct INDEX { int i,

int j,

int k } min_index ;

struct INDEX * find_min (int *ptr1, int l, int m int n)

{ int min, i, j, k, ind;

min=*ptr1;

min_index.i=min_index.j=min_index.k=0;

for (i=0; i*(ptr1+ind)

{ min=*(ptr1+ind);

min_index.i=i;

min_index.j=j;

min_index.k=k;

}

}

return &min_index;

}
^

10.1.8. Массивы указателей


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

Следующие объявления переменных

int a[]={10,11,12,13,14,};

int *p[]={a, a+1, a+2, a+2, a+3, a+4};

int **pp=p;

порождают программные объекты, представленные на схеме на рис.4.




pp






p



.

.

.

.

.














a



11

12

13

14

15

^ Рис.4. Схема размещения переменных при объявлении.

При выполнении операции pp-p получим нулевое значение, так как ссылки pp и p равны и указывают на начальный элемент массива указателей, связанного с указателем p ( на элемент p[0]).

После выполнения операции pp+=2 схема изменится и примет вид, изображенный на рис.5.




pp






p



.

.

.

.

.


























a



10

11

12

13

14







^ Рис.5. Схема размещения переменных после выполнения операции pp+=2.







Результатом выполнения вычитания pp-p будет 2, так как значение pp есть адрес третьего элемента массива p. Ссылка *pp-a тоже дает значение 2, так как обращение *pp есть адрес третьего элемента массива a, а обращение a есть адрес начального элемента массива a. При обращении с помощью ссылки **pp получим 12 - это значение третьего элемента массива a. Ссылка *pp++ даст значение четвертого элемента массива p т.е. адрес четвертого элемента массива a.

Если считать, что pp=p, то обращение *++pp это значение первого элемента массива a (т.е. значение 11), операция ++*pp изменит содержимое указателя p[0], таким образом, что он станет равным значению адреса элемента a[1].

Сложные обращения раскрываются изнутри. Например, обращение *(++(*pp)) можно разбить на следующие действия: *pp дает значение начального элемента массива p[0], далее это значение инкременируется ++(*p) в результате чего указатель p[0] станет равен значению адреса элемента a[1], и последнее действие это выборка значения по полученному адресу, т.е. значение 11.

В предыдущих примерах был использован одномерный массив, рассмотрим теперь пример с многомерным массивом и указателями. Следующие объявления переменных

int a[3][3]={ { 11,12,13 },

{ 21,22,23 },

{ 31,32,33 } };

int *pa[3]={ a,a[1],a[2] };

int *p=a[0];

порождают в программе объекты представленные на схеме на рис.6.


^ Рис.6. Схема размещения указателей на двумерный массив.

Согласно этой схеме доступ к элементу a[0][0] получить по указателям a, p, pa при помощи следующих ссылок: a[0][0], *a, **a[0], *p, **pa, *p[0].

Рассмотрим теперь пример с использованием строк символов. Объявления переменных

char *c[]={ "abs", "dx", "yes", "no" };

char **cp[]={ c+3, c+2 , c+1 , c };

char ***cpp=cp;

можно изобразить схемой представленной на рис.7.


Рис.7. Схема размещения указателей на строки.



10. 1.9. Указатели и модификатор const
Допускается объявление указателей в виде:

const СпецификаторТипа *ИмяУказателя;

СпецификаторТипа *const ИмяУказателя=&ИмяПеременной;

В первом случае не допускается изменять значение переменной через указатель.

Пример:

int a,b;

const int *pa;

pa=&a; // допустимо

a=5; //допустимо

*pa=6; // недопустимо

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

Пример:

int a,b;

int* const pa=&a;

pa=&b; // недопустимо

a=5; //допустимо

*pa=6; // допустимо

10.2. Ссылки


Ссылка также как и указатель хранит адрес некоторого объекта.

^ Форма записи:

Спецификатор-типа & описатель=ИмяПеременной

Имя переменной должно существовать.

Отличие ссылки от указателя: однажды присвоенное ссылке значение не может быть изменено, т.е. ссылка будет связана только с той ячейкой, адрес которой был присвоен ссылке при инициализации. Значение адреса, хранимого указателем, может быть изменено. Значение переменной, на которую указывает ссылка или указатель можно изменять. Операция взятия адреса допустима к ссылке, а операция разыменования – нет, т.к. для получения содержимого ячейки достаточно обратиться к ссылке по ее имени. Из вышесказанного можно сделать вывод, что ссылка грубо говоря - это указатель с константным значением адреса.

Пример:

#include<iostream.h>

void main()

{

int a=2,b=5;

int &p_a=a;

p_a++; // аналогично a++

cout<<p_a<<endl;

p_a=b; // аналогично a=b

cout<<p_a;

cout<<&p_a; //вывод адреса

}







Скачать файл (357.8 kb.)

Поиск по сайту:  

© gendocs.ru
При копировании укажите ссылку.
обратиться к администрации
Рейтинг@Mail.ru