Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Адресная арифметика, типы указателей и операции над ними

Поиск

Операции, определенные над указателями:

операция разыменования или доступа по адресу (*);

преобразования типов или приведения типов (type *);

присваивания (=);

получения адреса (&);

сложения и вычитания (аддитивные операции);

инкремент (++);

декремент(--);

операции отношения (операции сравнения).

Операция разыменования или доступа по адресу (*)

В синтаксисе языка над указателями определена операция * (доступа по адресу, разыменования). Чтобы ею воспользоваться, необходимо перед именем указателя поставить звездочку (*имя_указателя). С помощью этой операции через указатель можно получить доступ к объекту в памяти, на который этот указатель настроен. То есть *имя_указателя является полным синонимом имени объекта; над ним определено то же самое множество операций, что и над объектом, и оно может использоваться в любых выражениях вместо объекта (переменной). Кроме этого, тип выражения *имя_указателя совпадает с типом объекта и, следовательно, все операции преобразования типа над *имя_указателя работают точно так же, как и над объектом.

Пример:

long a = 1, b = 2;

long * pi1=&b, *pi2 = &a; //настройка pi1и pi2 на a и b;

*pi2 = *pi2 + 2**pi2/*pi1; // Это эквивалентно выражению а=а+2*а/b

cout<<”\n a = “<<a<<”\t *pi2 = “<<*pi2;

 

Результаты работы программы

a = 2 *pi2 = 2

В примере определены и инициализированы две переменные a и b типа long и два указателя pi1 и pi2 типа long*. Поскольку указатели настроены на переменные a и b, то, следовательно, в них хранятся адреса этих переменных. И так как указатели имеют тип long*, то, следовательно, они, начиная с записанных в них адресов, будут трактовать информацию в следующих 4-х байтах как значения переменных типа long. Именно это обеспечивает правильность выполнения выражения.

Операция (=) присваивания.

Язык Си++ не поддерживает преобразование типов по умолчанию при выполнении операции (=) присваивания над указателями, в отличии от основных типов данных (за исключением указателя типа void*). То есть компилятор выдаст ошибку, если тип указателя не совпадет с типом объекта, на который его настраивают.

Пример:

int a = 2, b = 1;

double c = 1, *pd;

c = a; /* В этой строке происходит преобразование типа по умолчанию при выполнении операции присваивания. Создаётся неименованная переменная типа double, которая инициализируется значением переменной a, и только после этого происходит присваивание переменной c типа double значения неименованной переменной типа double.*/

pd = &a; /*Ошибка. Указатель pd определен как указатель на объект типа double, а не указатель на объект типа int. */

pd = &c; // Ошибки нет. Указатель pd настраивается на переменную c.

Пример можно переписать, исправив ошибку с помощью операции явного преобразования типов, записав

pd = (double*) &a; /*Указателю pd присваивается значение адреса переменной a*/

В этом случае создаётся неименованный указатель типа (double*), который инициализируется адресом переменной a, и только после этого указателю pd присваивается значение неименованного указателя. То есть с помощью явного преобразования типа удаётся обойти правило выполнения операции присваивания над указателями, которое гласит: Тип указателя и тип объекта, на который он указывает, должны совпадать. Обходя это правило, компилятор генерирует код, предполагая, что программист знает, что он делает и знает, какие негативные последствия могут быть, если далее в программе будет использовано выражение *pd.

Возможные негативные последствия использования *pd.

Поскольку переменная а – это переменная типа int, то следовательно, она занимает в памяти 2 байта. В соответствии с синтаксисом операции доступа по адресу *pd будет считаться, что, начиная с адреса содержащегося в pd, в следующих 8 байтах памяти размещён объект типа double. То есть содержимое этих 8 байт будет трактоваться как значение объекта double. Значение первых двух байт известно, но значение следующих 6 байт непредсказуемо, и следовательно, значение выражения *pd также не известно. Поэтому использование *pd в арифметических выражениях не имеет смысла.

Ещё хуже обстоит дело, если будет попытка выполнить операцию *pd = выражение. Поскольку при компиляции компилятор отвел только два байта под переменную a типа int, и что расположено в следующих 6 байтах (значение другой переменной этой же программы или вообще системные данные операционной системы) неизвестно, то перезапись этих 6 байт может привести либо к появлению труднообнаружимой ошибки, либо вообще к зависанию компьютера.

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

int *p; /* Указатель р определен, но не инициализирован, в нем находится случайное число */

cout<<*p; /* Это случайное число рассматривается как адрес в памяти объекта типа int. Что на самом деле находится по этому адресу не известно, поэтому будет выведено какое-то произвольное число. */

*p = 1; /*Компилятор не найдет ошибки в этом коде, но в этом месте программа скорей всего будет зависать или операционная система будет выдавать сообщение: «Программа выполнила некорректную операцию и будет закрыта» (произведена попытка записи 1 в память по адресу, находящемуся в р). */

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

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

int *p = NULL;

Синтаксис языка гарантирует, что пустой указатель не адресует никакого объекта. Однако синтаксис не гарантирует, что внутреннее значение пустого указателя будет совпадать с кодом целого числа 0.

Есть ситуации, когда применение явного преобразования типа над указателями абсолютно необходимо. В качестве примера можно привести следующие случаи:

Известно, например, что в системной памяти по адресу 0х0417 находится байт состояния клавиатуры. Для того чтобы иметь возможность прочитать или изменить его значение, необходимо написать код char* pc = (char*)0x0417; cout<<*pc; т.е. явно указать, что 0x0417 не шестнадцатеричная константа, а адрес в памяти на переменную типа char.

В языке Си++ есть функция void* malloc(unsigned s), которая возвращает указатель на блок динамически распределяемой памяти длиной s байт. При неудачном завершении возвращает значение NULL и функция void free(void* bl), которая освобождает ранее выделенный блок динамически распределяемой памяти с адресом первого байта bl.

Приведем пример программы с использованием явного преобразования типа, которая также не приводит к фатальным последствиям.

//Программа 5.1

#include "stdafx.h"

#include <iostream>

void main(){

unsigned long L = 0x12345678; /* определяется и инициализируется шестнадцатеричной /константой переменная L типа unsigned long*/

char* cp = (char *) &L;

int * ip = (int*) &L;

long * lp = (long*) &L; /*определяется указатели cp, ip, lp типа char*, int*, long* который инициализируется с помощью явного преобразования типа адресом переменной L */

std::cout<<std::hex; /* Шестнадцатеричное представление выводимых значений*/

std::cout<<"\n Address L, i.e. &L = "<<&L<< "\t L = "<<L;/*выводится адрес в памяти, начиная с которого размещены 4 байта отведенные для переменной L, и значение самой переменной L*/

std::cout<<"\n value of pointer cp = " << (void *) cp;

std::cout<<"\t and *cp = 0x"<<(int)*cp;

std::cout<<"\n value of pointer ip = " << ip;

std::cout<<"\t and *ip = 0x"<<*ip;

std::cout<<"\n value of pointer lp = " << lp;

std::cout<<"\t and *lp = 0x"<<*lp;/*вывод на экран значений указателей cp, ip, lp и результатов выполнения операции разыменования над этими указателями*/

getchar();

}

Результат выполнения программы:

Адрес L т. е. &L = 0x1Е290ffС L = 0x12345678

Значение указателя cp = 0x1Е290ffС и *cp = 0x78

Значение указателя ip = 0x1Е290ffС и *ip = 0x5678

Значение указателя lp = 0x1Е290ffС и *lp = 0x12345678

Из результатов выполнения программы видно, что все указатели имеют одно и то же значение, которое совпадает с адресом переменной L. При применении операции разыменования (*) над указателями cp, ip, lp в соответствии с правилом выполнения этой операции первый, первый - второй и первый - четвертый байты памяти, начиная с записанного в этих указателях адреса, воспринимаются как объекты типа char, int, long соответственно. В соответствии с принятой для IBM PC стандартом, размещение числовых кодов в памяти начинается с младшего адреса. За счет этого пары младших разрядов шестнадцатеричного числового кода размещаются в байтах памяти с меньшими адресами (рис. 5.1).

 

Рис. 5.1. Схема размещения в памяти IBM PC переменной L - типа unsigned long

 

Операция получения адреса операнда (&)

Унарная операция получения адреса операнда &операнд (не путать с бинарной операцией поразрядной конъюнкции операнд&операнд) позволяет получить адрес размещения объекта в памяти компьютера. Другими словами, эта операция возвращает число, которое является номером байта в памяти компьютера, начиная с которого в памяти выделено место под хранение значения операнда. По умолчанию результат выполнения операции &операнд (число) имеет тип тип_операнда*. Пример:

int s = 8;

int* ps=&s; /*Результатом выполнения операции &s будет являться число, являющееся адресом байта, начиная с которого в памяти компьютера размещена переменная s. Это число имеет тип int*. Далее выполняется операция присваивания переменной ps типа int* значение типа int*. Получается, что с обеих сторон от операции присваивания стоят операнды, имеющие одинаковый тип. Поэтому операция выполняется корректно, без преобразований типов. */

 

Сложения и вычитания (аддитивные операции над указателями)

Бинарная операция вычитания определена над указателями одного типа и над указателем и константой. Бинарная операция сложения определена только над указателем и константой. Пример:

int ar[5]; /* Пример определения массива, состоящего из пяти элементов типа int. Обращаться к элементам массива можно с помощью выражений ar[0], ar[1], …, ar[4]*/

int * pi1 = &ar[4], pi2 = &ar[0]; /* Определение и инициализация указателей. */

int raz = pi1 – pi2; /*Разность двух указателей одного типа присваивается переменной */

Чему равна эта разность? Она равна разности значений указателей, деленной на размер в байтах объекта того типа, к которым отнесен указатель. То есть если значение pi1 = 0x00fa (адрес ar[4]), а значение pi2 = 0х00f2 (адрес ar[0]), то значение разности будет (0x00fa - 0х00f2) / sizeof(int) = 4.

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

(long) pi1 – (long) pi2; /* Явное преобразование типа. Создаются неименованные переменные типа long, которые инициализируются значениями указателей, и только затем находится разность двух неименованных переменных типа long. Результат этой разности равен, естественно, 8. Безусловно, это же значение можно было найти, вычислив (pi1 – pi2) *sizeof (int), но в этом случае надо быть абсолютно уверенным, что между объектами, на которые указывают указатели, лежат объекты того же типа. В данном случае это так, так как указатели настроены на элементы одного и того же массива, а синтаксис языка гарантирует, что элементы массива располагаются в памяти последовательно друг за другом.*/

При вычитании или прибавлении константы k к указателю, значение указателя изменяется на величину k*sizeof(type), где type - тип объекта, к которому отнесен указатель. Продолжая предыдущий пример, можно записать:

pi1 = pi1+3; /* Значение pi1 увеличится не на три, а на величину, равную 3*sizeof(int)= 6 */

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

Операции инкремента и декремента над указателями

Операции инкремента и декремента определены над указателями. При инкременте или декременте указателя его значение, как и в случае с операциями сложения и вычитания указателя и константы, увеличивается/уменьшается не на 1, а на величину sizeof(type), где type - тип объекта, к которому отнесен указатель.

Работая с адресами и указателями, нужно внимательно относиться к последовательности выполнения унарных операций *, ++, --, &, так как они в выражениях могут употребляться в самых разнообразных сочетаниях. Напомним также, что ассоциативность операций *, ++, --, & направлена справа налево.

Пример:

//Программа 5.2

#include "stdafx.h"

#include <iostream>

void main(){

int i[3] = {10,20,30}; // Определение и инициализация массива.

int *p = &i[1]; // Определение и инициализация указателя.

std::cout<<"\n *&i[1] = " << *&i[1]; /* Сначала находим адрес i[1], а потом применяем операцию доступа по адресу и в результате печатается значение i[1]. */

std::cout<<"\n *&++i[1] = " << *&++i[1]; /*Значение i[1] увеличивается на 1 и печатается.*/

std::cout<<"\n *p = "<<*p; //Печатается значение i[1]

std::cout<<"\n *p++ = "<<*p++; /*Печатается значение i[1], p увеличивается на 1, т.е. настраивается на i[2]. В данном контексте ++ постфиксная операция, поэтому она выполняется, после того как p было использовано в выражении. */

std::cout<<"\n *p = "<<*p; //Печатается значение i[2]

std::cout<<"\n ++*p = "<<++*p; /*Печатается значение i[2] увеличенное на 1.*/

std::cout<<"\n *--p = "<<*--p; /*Сначала р уменьшается на 1 и настраивается на i[1], а затем печатается значение i[1].*/

std::cout<<"\n ++*--p = "<<++*--p; /*Сначала р уменьшается на 1 и настраивается на i[0], затем i[0] увеличивается на 1 и печатается. */

getchar();

}

Результаты выполнения программы:

*&i[1] = 20

*&++i[1] = 21

*p = 21

*p++ = 21

*p = 30

++*p = 31

*--p = 21

++*--p = 11

Операции отношения над указателями

Операции отношения над указателями не имеют никаких характерных особенностей по сравнению с операциями над переменными основных типов данных. Результат выполнения операции отношения над указателями всегда будет либо 0, либо 1, а операндами - значения указателей, т.е. числа.

Пример: Записывает строку Hellow world в обратном порядке.

char str[13] = “Hellow world”;

char *pch = & str[0];

char temp;

for(int i = 0; pch <= &str[11-i]; i++, pch++) { /* Цикл будет выполняться до тех пор, пока значение указателя pch меньше или равно адресу &str[12-i].*/

temp = str[11-i];

str[11-i] = *pch;

*pch = temp;

}

Операция sizeof над указателями

Указатели – это объекты в памяти состоящие из 4 байт, в которых содержится число, воспринимающееся компилятором как адрес другого объекта в памяти. Над указателем определена операция sizeof(имя_указателя) или sizeof(тип_указателя). Поскольку любой указатель занимает в памяти 4 байта, то, следовательно, результатом выполнения операции sizeof() над указателями всегда будет число 4. Пример:

cout<<”\n sizeof(void*) = “ << sizeof(void*);

cout<<”\n sizeof(long*) = “ << sizeof(long*);

cout<<”\n sizeof(char*) = “ << sizeof(char*);

cout<<”\n sizeof(long double*) = “ << sizeof(long double*);

void* pv; cout<<”\n sizeof(pv) = “ << sizeof(pv);

int* pi; cout<<”\n sizeof(pi) = “ << sizeof(pi);

long* pl; cout<<”\n sizeof(pl) = “ << sizeof(pl);

float* pf; cout<<”\n sizeof(pf) = “ << sizeof(pf);

Во всех случаях операция sizeof() от указателя или типа будет возвращать значение 4.

Раз указатель - это объект в памяти, то можно найти адрес этого объекта в памяти. Для хранения адреса указателя используется тип (type**) указатель на указатель, который в свою очередь, тоже является объектом в памяти и, следовательно, можно найти и его адрес и т.д. Пример:

//Программа 5.3

#include "stdafx.h"

#include <iostream>

void main(){

int i = 88;

int *pi = &i;

int **ppi = &pi;

int ***pppi = &ppi;

std::cout<<"\n ***pppi = "<<***pppi;

getchar();

}

Результат выполнения программы:

***pppi = 88;

 

5.3. Свойства указателя типа void*

Тип void (пустота, пробел) является основным типом языка программирования Си++, но в отличие от других основных типов с помощью ключевого слова void нельзя создать объект типа void. В основном ключевое слово void используется для того, чтобы указать на отсутствие параметров функции и/или возвращаемого функцией значения. Например:

void printStr(void){ cout<<“Здравствуй Мир!!”;}

Эта функция не принимает никаких параметров и не возвращает никакого значения.

Указатель типа void* - это четырехбайтовый объект, предназначенный для хранения адреса любого типа объектов или адреса фрагмента памяти, т.е. он может указывать на всё что угодно. С этим указателем не связывается информация о том, какой объект находится в памяти, начиная с записанного в нем адреса. Именно поэтому нельзя применить к указателю типа void* операцию доступа по адресу (*). У компилятора нет информации, сколько байт отведено под объект, на который указывает указатель типа void*, и как информацию в этих байтах интерпретировать. Указатель типа void* единственный тип указателя, для которого определено преобразование типа по умолчанию. Пример:

void* buf; //Определение указателя buf типа void*.

int a = 5;

double k = 5.5;

buf = &a; /*Преобразование типа по умолчанию. На самом деле выполняется выражение buf = (void*)&a; компилятор по умолчанию добавляет (void*). В buf записывается адрес переменной а. И теперь только программист знает, что buf указывает на объект типа int. */

cout<<*buf; /*ошибка, операция разыменования не определена над указателями типа void*/

cout<<(*(int*) buf); /*Явное преобразование типа. Создаётся неименованный указатель типа int, который инициализируется адресом, хранящимся в buf. К неименованному указателю int* применяется операция разыменования и на экране печатается значение переменной a.*/

buf = &k; /*Преобразование типа по умолчанию. В buf записывается адрес переменной k.*/

cout<<*(double*)buf; //На экране печатается значение переменной k.

cout<<*(int*) buf; /*На экране печатается неизвестно, что, так как buf в данный момент указывает на объект типа double, а не на объект типа int.*/

double * pd; // определение указателя pd типа double*

pd = buf; /*Ошибка. Операция неявного преобразования типа над указателями типа double* не определена. Компилятор выдаст сообщение «Не могу переконвертировать void* в double*.*/

pd = (double*) buf; //Ошибки нет. Явное преобразование типа.

cout<< *pd; //Печать значения переменной k

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

Таблица 5.1

Способы определения указателя

  type* имя_указателя = инициализатор; ни указатель, ни объект, на который он указывает, константами не являются, их можно изменять int i = 1; int* pi = &i; pi = 2; /* операция разрешена, pi указывает не на константу; pi = pi +1; операция разрешена, pi не константа */  
  type* const имя_указателя = инициализатор; указатель является константой т.е. его нельзя перестроить на другой объект; объект, на который настроен указатель, изменить можно int i = 1; int* const pi = &i; *pi = 2; ошибки нет, значение переменной i изменено с помощью указателя pi (i = 2); pi = pi +1; ошибка, значение указателя константа, его изменить нельзя
  type const* имя_указателя = инициализатор; указатель не является константой; объект, на который настроен указатель, изменить нельзя он константа int i = 1; int* const pi = &i; *pi = 2; ошибка, значение переменной i изменить с помощью указателя нельзя; pi = pi +1; операция разрешена pi не константа
  type const * const имя_указателя = инициализатор; указатель и объект, на который он указывает являются константами их изменить нельзя int i = 1; int const* const pi = &i; *pi = 2; ошибка, значение переменной i изменить с помощью указателя нельзя; pi = pi +1; ошибка, pi константа

 

 

Свойства объекта cout

С помощью объекта класса iostream cout и перегруженной операции << на экран монитора выводится значение выражения стоящего справа от <<, причем формат вывода на экран значения выражения зависит от его типа. Особо следует выделить случай, когда значение выражения имеет тип char, unsigned char, char*, unsigned char*.

Например

char st1 = ‘S’, st2 = 83; /*Определение и инициализация переменных st1 и st2. Причём st1 и st2 равны друг другу, так как символьная константа ‘S’ в соответствии с таблицей ASCII имеет код 83.*/

int i = 83;

cout<<”\n st1 = “<<st1<<”\t st2 = “<<st2<<”\t i = “<<i; /*Вывод на экран значений st1, st2 и i. Поскольку st1 и st2 имеют тип char, то печатается не их значение, а соответствующий этому значению по таблице ASCII символ, т е. будет напечатано st1 = S, st2 = S. Переменная i имеет тип int, поэтому печатается просто её значение i = 83.*/

char *pc = “Язык Си++”; /*Строковая константа, состоящая из 9 байт. Первые восемь байт содержат числовые коды соответствующих символов, а последний девятый байт содержит код 00 и является признаком конца строковой константы. Указатель pc инициализируется адресом первого байта. */

int i = 83, * pi;

pi = &i;

cout<<”\n pc = “<< pc<<”\t pi = “<<pi; /* На экране печатается pc = Язык Си++ pi = 532465*/

Объект cout по-разному ведет себя c указателями типов char*, unsigned char* и указателями других типов. Так, вместо значения pc (адреса), объект cout начиная с адреса, записанного в pc и до байта с кодом 00, воспринимает фрагмент памяти как фрагмент, содержащий строковую константу, и печатает её на экране. Если по каким-либо причинам строковая константа не была завершена кодом 00, то объект cout выведет строковую константу и будет выводить далее содержимое всех байтов в символьном виде до тех пор, пока не наткнется на байт с кодом 00, т.е. за строковой константой будет выведен какой-то мусор. При выводе значения указателя pi типа int на экран будет выведено просто значение pi, т. е. число являющееся адресом переменной i. Пример:

char p[5] = {‘М’, ‘и’, ‘р’}; /*определен массив и инициализированы первые три его элемента */

cout<<p; //Печатает Мир и далее мусор

p[3] = ‘\0’; //Управляющий символ ‘\0’, который записывает в p[3] код 0x00

cout<<p; //Печатает просто Мир без мусора

 

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

Определение массива

type имя_массива [ константное_выражение] инициализатор;

где type – тип элементов массива; имя_массива – идентификатор массива; инициализатор – не обязательное инициализирующее выражение; константное_выражение – выражение, значение которого вычисляется во время компиляции, а не во время выполнения программы. Это значит, что во время выполнения программы нельзя увеличивать размерность массива. Поэтому такие массивы называются статическими.

Инициализировать элементы массива можно следующим образом:

int f[] = {2, 5, ‘S’}; /*Вместо символа S компилятор подставит соответствующее ему по таблице ASCII число. Размер массива равен трем, задаётся неявно с помощью инициализатора.*/

char a[5] = {2, 5, ‘S’}; /* Размер массива равен 5 задаётся явно. Первые три элемента массива инициализируются соответствующими значениями. */

char a[2] = {2, 5, ‘S’};// Ошибка инициализации

Для массивов типа char принят ещё один способ инициализации:

char pc[] = “Привет всем”; /*Размер массива равен 12, задаётся неявно и определяется количеством символов, заключенных в кавычки, плюс невидимый, так называемый нулевой символ ‘\0’. Полным аналогом такого способа инициализации является строка char pc[] = {‘П’, ‘р ’, ’и’, ’в’, ’е’, ’т’,’ ’,’в’,’с’,’е’,’м’,’\0’};*/

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

extern type имя_массива[];

где type и имя_массива должны совпадать с типом и именем массива определенного где-то в программе. Type можно опустить, в этом случае по умолчанию будет предполагаться, что type имеет тип int. Пример:

extern f[]; //Описание массива типа int, определенного где-то в программе.

extern char pc[]; /*Описание массива типа char, определенного где-то в программе.*/

Если массив был описан, но не определен, то на этапе компоновки будет выдано сообщение об ошибке.

Над именем массива определена операция sizeof(имя_массива). Результатом этой операции является количество байт, выделенных под массив. Используя эту операцию можно определить, сколько есть элементов в массиве, например:

double k[5];

cout<<”\n Количество элементов в массиве k = “;

cout<<sizeof(k)/sizeof(double); //

Результат работы программы:

Количество элементов в массиве k = 5

Доступ к конкретному элементу массива при определении массива в виде type имя_массива[константное_выражение] можно выполнить с помощью операции имя_массива[ индекс], причём программа должна быть написана так, чтобы изменение индекса лежало в пределах интервала от 0 до константного_выражения – 1 включительно. При выходе индекса за пределы интервала программа начинает обращаться к памяти, которая не была выделена под массив и, следовательно, результаты работы такой программы непредсказуемы. Ни компилятор, ни компоновщик не проверяют диапазон изменения индекса, программист сам должен обеспечивать выполнение правила обращения к элементам массива.

Имя массива – это константный указатель, значение которого равно адресу первого элемента массива. Единственное отличие, которое есть, например, между именем массива ai и константным указателем pi, определенным ниже как

int ai[5];

int * const pi = &ai[0]; /* или int * const pi = ai; т.е. &ai[0] = = ai заключается в результате выполнения операции sizeof(), так при выполнении операции sizeof(ai) результат будет равен 10, а sizeof(pi) результат будет равен 4. Поскольку ai это имя массива, которое является константным указателем, а pi – константный указатель, настроенный на первый элемент массива. */

Во всем остальном они подобны. Это означает, что к указателям можно применять операцию [ ], а к именам массивов все правила адресной арифметики, связанной с указателями. Более того, запись имя_массива[ индекс] является выражением с двумя операндами. Первый из них, т.е. имя_массива – это константный указатель – адрес начала массива в основной памяти; индекс - это выражение целого типа, определяющее смещение от начала массива. Используя операцию обращения по адресу *, действие бинарной операции [] можно объяснить так: *(имя_массива + индекс). Поскольку операция сложения коммутативна, то можно записать и так *(индекс+ имя_массива), а, следовательно, операцию [], можно представить в виде индекс[имя_массива]. Например, обращение к элементам массива ai через имя массива или указатель pi, которые были определены выше, можно записать

ai[0] == pi[0] == *ai == *pi == 0[ai] == 0[pi],

ai[1] == pi[1] == *(ai+1) == *(pi+1)== 1[ai] == 1[pi],

ai[4] == pi[4] == *(ai+4) == *(pi+4)== 4[ai] == 4[pi].

Но ошибкой будет запись *(ai++), или *(pi++), или *(pi = pi+4), или *(ai = ai+4), так как ai и pi - константные указатели. Необходимо также помнить, что при прибавлении целого числа (индекса) к указателю на самом деле прибавляется число равное индекс*sizeof(type), где type - тип объекта, к которому отнесен указатель.

Использовать операцию [] c указателями очень удобно. Она коротка с точки зрения записи, интуитивно понятна, не изменяет значение указателя и может использоваться и с неконстантными указателями. Например, напишем функцию, которая в обратном порядке выводит на экран содержимое строки.

//Программа 5.4

#include "stdafx.h"

#include <iostream>

void fstr(char* pst){

int strlen = 0;

for(; pst[strlen]!= '\0'; strlen++); /*Определяем длину строки. Телом цикла является пустой оператор. */

for(int i = strlen-1; i >= 0; i--) std::cout<< pst[i]; /*Выводим строку посимвольно в обратном порядке на экран, используя указатель pst и операцию []. */

}

void main(){

char p [] = "Example work of operation []";

fstr(p);/*При вызове функции формальному параметру pst присваивается значение фактического параметра p. Таким образом, указатель pst настраивается на первый элемент массива р.*/

getchar();

}

 



Поделиться:


Последнее изменение этой страницы: 2016-08-10; просмотров: 303; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.135.204.43 (0.013 с.)