![]() Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь FAQ Написать работу КАТЕГОРИИ: ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву ![]() Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Адресная арифметика, типы указателей и операции над нимиСодержание книги
Поиск на нашем сайте
Операции, определенные над указателями: операция разыменования или доступа по адресу (*); преобразования типов или приведения типов (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 = π 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 Способы определения указателя
Свойства объекта 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; просмотров: 310; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.188.118.154 (0.014 с.) |