Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь 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; просмотров: 303; Нарушение авторского права страницы; Мы поможем в написании вашей работы! infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.135.204.43 (0.013 с.) |