С++: Теоретические сведения: Массивы и указатели

Теоретические сведения <br/>Массивы и указатели <br/><br/>В языке Си существует два способа доступа к переменной: ссылка на переменную по имени и использование механизма указателей. Указатель-переменная (или просто указатель) – это переменная, предназначенная для хранения адреса в памяти. Указатель-константа – это значение адреса оперативной памяти. В языке Си определены две специальные операции для доступа к переменным через указатели: операция &amp; и операция *. Результатом операции &amp; является адрес объекта, к которому операция применяется. Например, &amp;varl дает адрес, по которому varl хранится в памяти (точнее, адрес первого байта varl). Операция * – это операция обращения к содержимому памяти по адресу, хранимому в переменной-указателе или равному указателю-константе. <br/>П...
27 апреля 2004, вторник 02:40
4upuk4ek для раздела Блоги
Теоретические сведения
Массивы и указатели


В языке Си существует два способа доступа к переменной: ссылка на переменную по имени и использование механизма указателей. Указатель-переменная (или просто указатель) – это переменная, предназначенная для хранения адреса в памяти. Указатель-константа – это значение адреса оперативной памяти. В языке Си определены две специальные операции для доступа к переменным через указатели: операция & и операция *. Результатом операции & является адрес объекта, к которому операция применяется. Например, &varl дает адрес, по которому varl хранится в памяти (точнее, адрес первого байта varl). Операция * – это операция обращения к содержимому памяти по адресу, хранимому в переменной-указателе или равному указателю-константе.
Признаком переменной-указателя для компилятора является наличие в описании переменной двух компонентов:
1) типа объекта данных, для доступа к которому используется указатель (или, как часто говорят, на который ссылается указатель);
2) символа * перед именем переменной.
В совокупности тип и * воспринимаются компилятором как особый тип данных – "указатель на что-либо". Таким образом, описание
int var1, *ptr;
приводит к появлению переменной varl и указателя-пере-менной ptr.
Переменная varl будет занимать два байта памяти. Указатель ptr имеет тип int *, т. е. тип "указатель на целое". Место, выделяемое под такой тип компилятором, зависит от модели памяти. Указатели при их описании могут, как и обычные переменные, получать начальное значение. Например:
int var1, *ptr1 = (int *) 200, *ptr2 = &var1;
Здесь описаны две переменные-указателя ptrl и ptr2; ptrl получает начальное значение 200, а ptr2 в качестве начального значения - адрес, по которому в памяти хранится varl. При инициализации указателя ptrl константой выполняется явное приведение к типу "указатель на целое". Это избавляет от предупреждения о немобильности инициализации.
Операцию *, пытаясь выразить словами смысл выражения, можно заменить фразой "взять содержимое по адресу, равному значению указателя". Например, оператор присваивания
*ptrl=*ptr2+4;
можно интерпретировать так: взять содержимое памяти по адресу, равному значению указателя ptr2, прибавить к этому содержимому 4, а результат поместить по адресу, равному значению указателя ptrl. Число байтов, извлекаемых из памяти и участвующих в операции, определяется компилятором исходя из типа, на который указатель "указывает". В то же время будет ошибкой запись типа
*234=var1;
так как в левой части оператора присваивания записывается адрес константы. Но константа в Си не имеет адреса в памяти.
Существуют ограничения и на использование операции взятия адреса &:
1) нельзя определить адрес константы, например, некорректным является выражение
var1=&0xff00;
2) нельзя определить адрес значения, получаемого при выполнении арифметического выражения, включающего знаки +, -, /, * и т. п. Например, некорректным является выражение
int var1, *ptr; 

pt=&( var1 *3);
3) нельзя определить адрес переменной, описанной как register. Например, будет ошибкой попытка определить адрес varl:
unsigned register var1; 

unsigned int * ptr;
ptr=&var1;[code]Сам указатель-переменная тоже имеет адрес. Поэтому, например, корректным будет такой фрагмент:[code]int var1, *ptr1,*ptr2=&var1;
ptr1=(int*)&ptr2;
Здесь описываются два указателя-переменные и ptr2 инициализируется значением адреса переменной varl. Затем ptrl присваивается значение адреса, по которому в памяти располагается ptr2.
Особым типом указателя является указатель типа void *, называемый часто родовым (generic). Ключевое слово void говорит об отсутствии данных о размере объекта в памяти. Но компилятору для корректной интерпретации ссылки на память через указатели нужна информация о числе байтов, участвующих в операции. Поэтому во всех случаях использования указателя, описанного как void *, необходимо выполнить операцию явного приведения типа указателя. Например:
unsigned long block = OxffeeddccL;

void* ptr=&block;
char ch;
unsigned two bytes;
long int four bytes;
ch = * (char*) ptr;
two_bytes = *( unsigned*) ptr;
fowbytes = * (long int *) ptr;
Указатель-переменная ptr инициализируется значением адреса первого байта переменной block. При выполнении присваивания компилятору необходимо знать размер объекта справа от знака =. Эту информацию сообщают компилятору операции приведения типа void * к типу (char *), (unsigned *), (long int *). В первой операции присваивания операнд слева – это первый байт переменной block. Поэтому переменная ch становится равной 0хСС. (Напомним, что младший байт всегда располагается в памяти по меньшему адресу.) Во второй операции = операнд слева – это младшее слово переменной block и, следовательно, переменная two_bytes станет равна 0xDDCC. После третьей операции присваивания four_bytes станет равной 0xFFEEDDCC.
Для указателей-переменных разрешены некоторые операции: присваивание; инкремент или декремент; сложение или вычитание; сравнение.
Язык Си разрешает операцию сравнения указателей одинакового типа. При выполнении присваивания значение указателя в правой части выражения пересылается в ячейку памяти, отведенную для указателя в левой части.
Если к указателю, описанному как type * ptr; , прибавляется или отнимается константа N, значение ptr изменяется на N * sizeof(type). Разность двух указателей type * ptrl, * ptr2 – это разность их значений, поделенная на sizeof(type).
Использование механизма указателей является одним из способов доступа к элементам массива. Массив - это расположенные вплотную друг за другом в памяти элементы одного и того же типа. Каждый массив имеет имя, оно является указателем-константой, равной адресу начала массива (первого байта первого элемента массива). Доступ к отдельным элементам массива возможен по имени массива и индексу (порядковому номеру) элемента, рассматривая имя как указатель и используя операцию *, можно выполнить доступ к любому из элементов. Будут полностью эквивалентными ссылки на i-й элемент массива array (независимо от типа элемента) array [i] и * (array + i). Следующие выражения являются тождествами:
аrrау==&аrrау[0],

((array + i) == &аггау [i]).
Использование указателей дает несколько более короткий код программы и в ряде случаев позволяет обойтись без дополнительных переменных, используемых в качестве индексов.
Имя двухмерного массива является указателем-константой на массив указателей-констант. Элементами массива указателей являются указатели-константы на начало каждой из строк массива. Например, для двухмерного массива matrix[2][2] указателями-константами на нулевую, первую и вторую строки будут matrix[0], matrix[1] и matrix[2], а следующие выражения будут тождественными:
matrix[0]= =&matrix[0][0],

matrix[1]= =&matrix[1][0],
matrix[2]= =&matrix[2][0].
Как и для одномерных массивов, доступ к элементам двухмерного массива может осуществляться по индексу или с помощью механизма указателей. В последнем случае "точкой отсчета" может быть как самый первый элемент массива, так и первый элемент каждой из строк, т. е. могут использоваться как указатель-константа, задаваемый именем массива, так и указатели на строки массива. Например, для двухмерного массива m правомерны следующие способы индексации для доступа к ij-му элементу массива:
m[i][j],

*(m[i]+j)),
*(*(т + i) +j)).
Элементы массива могут иметь любой тип, в том числе и быть указателями-переменными. Наиболее часто массивы указателей используются для компактного расположения в памяти строк текста, структурных переменных и других "протяженных" объектов данных. Особенно удобны массивы указателей при динамическом управлении памятью.
Как и любой другой массив, массив указателей должен описываться. При описании может выполняться инициализация отдельных элементов. Например, так описывается массив указателей из 20 элементов:
char * messages[20];
Каждый элемент массива messages имеет тип char *. Число байтов памяти, занимаемых элементом, зависит от модели памяти (2 или 4 байта). Приведем пример описания массива указателей с его инициализацией:
char * messages[ ] = {"Не открыт файл",

"Ошибка ввода",
"Диск",
"Тайм-аут",};
Компилятор резервирует место для четырех указателей. Они получают начальное значение, равное адресу начала в памяти соответствующих строковых литералов. Существует принципиальное различие в расположении в памяти массива указателей и, на первый взгляд, подобного ему двухмерного массива типа char, например, такого:
char array [ ][16] = { "Не открыт файл",

"Ошибка ввода",
"Диск",
"Тайм-аут", };
Преимущества использования массивов указателей в том, что появляется возможность манипулировать не самими объектами, а только их адресами, что дает значительный выигрыш в скорости выполнения программы. Объекты, с которыми работает программа, могут располагаться в выделяемой динамически области памяти.
Имя массива указателей является указателем-константой на первый элемент массива. Так как первый элемент массива сам является указателем, то имя массива указателей является указателем на указатель. В Си можно описать и переменную, имеющую тип "указатель на указатель". Признаком такого типа является повторение символа * при описании переменной. Число звездочек определяет число "уровней" указателя. Фактически указатель на указатель – это ячейка памяти, хранящая адрес указателя. Размер области памяти, выделяемой для такой переменной, зависит от модели памяти (2 или 4 байта). При описании указатель на указатель может инициализироваться. Например:
int data = 5;                                          / * переменная типа int */

int * ptr= &data; / * указатель на переменную типа int */
int ** ptr_ptr = &ptr; /* указатель на указатель */
int*** ptr_ptr_ptr=&ptr_ptr; /* указатель на указатель */
Для доступа к переменной data теперь может использоваться операция взятия содержимого по адресу. Например, все приведенные ниже выражения присваивают переменной data значение 10:
data                  =10;

*ptr =10;
** ptr_ptr = 10;
*** ptr_ptr_ptr= 10;
Для доступа к памяти через указатели на указатели можно использовать и индексы. Например, эквивалентны следующие ссылки на переменную data:
ptr[0] и *ptr,

ptr_ptr[0][0] и **ptr_ptr,
ptr_ptr_ptr[0][0][0] и ***ptr_ptr_ptr.

----
По материалам ОмГУПС, все права защищены...


[Содержание ПС]