![]() Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Связь между указателями и массивами
Одной из наиболее распространенных конструкций с использованием указателей являются массивы. Результатом использования указателей для массивов является меньшее количество используемой памяти и высокая производительность. В языке С массивы – это упорядоченные данные (элементы) одного типа. Компилятор языка С рассматривает имя массива как адрес его первого элемента (в языке С нумерация элементов массива начинается с нуля). Например, если имя массива Arr с десятью элементами, то i -й элемент (0 ≤ i < 10) компилятор преобразует его по правилам работы с указателями с операцией разыменования: *(Arr + i). Здесь Arr как бы указатель, а i – целочисленная переменная. Сумма (Arr + i) указывает на i -й элемент массива, а операция разыменования (оператор раскрытия ссылки *) дает значение самого элемента, т.е. Arr[i] идентично *(Arr+i). Имя массива без индекса образует указатель на начало этого массива. Имеется одно различие между именем массива и указателем, которое необходимо иметь в виду. Указатель является переменной, так что операции ptr1=Arr и ptr1++ имеют смысл. Но имя массива является константой, а не переменной: конструкции типа Arr=ptr1 или Arr++, или Arr=& x будут незаконными. Т.е. имя массива всегда указывает на одно и то же место в памяти – на нулевой элемент. Пусть, например, массив Arr содержит 10 целочисленных переменных: int Arr[10]; Тогда можно объявить указатель ptr, который будет указывать на элементы массива Arr: int *ptr; Тип указателя (в примере это int) должен соответствовать типу объявленного массива. Для того, чтобы указатель ptr ссылался на первый элемент (с нулевым индексом) массива Arr, можно использовать утверждение: ptr = Arr; В то же время можно использовать прямую адресацию: ptr = & Arr[0]; Обе формы записи эквивалентны. Аналогичные утверждения будут справедливы для других типов массивов: char, float, double и пр. Если указатель ptr указывал на первый элемент (с нулевым индексом) массива Arr, то для обращения к следующему элементу массива допустимы следующие формы утверждений: ptr = & Arr[1]; ptr += 1; Соответственно, выражение *(ptr+1) будет ссылаться на значение, содержащееся в элементе Arr[1]. Утверждение ptr += n; заставит указатель *ptr ссылаться на элемент массива, находящийся на расстоянии n от того, на который ранее ссылался указатель, независимо от типа элементов, содержащихся в массиве. Разумеется, значение n должно быть в допустимых пределах для данного объема массива. При работе с указателями и массивами особенно удобны операторы инкремента " ++" и декремента " ––". Использование оператора инкремента с указателем аналогично операции суммирования с единицей, а операция декремента имеет тот же эффект, что и вычитание единицы из указателя. В языке программирования С вполне корректной операцией является сравнение указателей. К указателям применяются операции сравнения " > ", " > =", "! =", " ==", " < =", " < ". Сравнивать указатели допустимо только с другими указателями того же типа или с константой NULL, обозначающей значение условного нулевого адреса. Константа NULL – это особое значение переменной-указателя, присваиваемое ей в том случае, когда она не должна иметь никакого значения. Его можно присвоить переменной-указателю любого типа. Оно представляет собой целое число нуль. Особое значение имеет сравнение двух указателей, которые связаны с одним и тем же массивом данных. Рассмотрим инициализацию указателей типа char: char *ptr = " hello, world"; Переменная *ptr является указателем, а не массивом. Поэтому строковая константа " hello, world" не может храниться в указателе *ptr. Тогда возникает вопрос, где хранится строковая константа. Для этого следует знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк. В ней он сохраняет строковые константы, которые встречаются ему по ходу чтения текста программы. Следовательно, когда встречается объявление с инициализацией, то компилятор сохраняет " hello, world" в таблице строк, а указатель *ptr записывает ее адрес. Поскольку указатели сами по себе являются переменными, их можно хранить в массивах, как и переменные других типов. Получается массив указателей. Массив указателей фиксированных размеров вводится одним из следующих определений: тип *имя_массива [размер]; тип *имя_массива [ ] = инициализатор; тип *имя_массива [размер] = инициализатор; В данной инструкции тип может быть как одним из базовых типов, так и производным типом; имя_массива – идентификатор, определяемый пользователем по правилам языка С; размер – константное выражение, вычисляемое в процессе трансляции программы; инициализатор – список в фигурных скобках значений элементов заданного типа (т.е. тип). Рассмотрим примеры: int data[7]; // обычный массив int *pd[7]; // массив указателей int *pi[ ] = { & data[0], & data[4], & data[2] }; В приведенных примерах каждый элемент массивов pd и pi является указателем на объекты типа int. Значением каждого элемента pd[j] и pi[k] может быть адрес объекта типа int. Все 6 элементов массива pd указателей не инициализированы.В массиве pi три элемента, и они инициализированы адресами конкретных элементов массива data. В случае обработки строк текста они, как правило, имеют различную длину, и их нельзя сравнить или переместить одной элементарной операцией в отличие от целых чисел. В этом случае эффективным средством является массив указателей. Например, если сортируемые строки располагаются в одном длинном символьном массиве вплотную — начало одной к концу другой, то к каждой строке можно обращаться по указателю на ее первый символ. Сами же указатели можно поместить в массив, т.е. создать массив указателей. Две строки можно сравнить, рассмотрев указатели на них. Массивы указателей часто используются при работе со строками. Можно привести пример массива строк о студенте, задаваемый с помощью массива указателей. char *ptr[ ] = { " Surname", //Фамилия " Name", // Имя " group", // группа " ACOUY" // специальность }; С помощью массива указателей можно инициализировать строки различной длины. Каждый из указателей массива указателей указывает на одномерный массив символов (строку) независимо от других указателей. В языке программирования С предусматриваются ситуации, когда указатели указывают на указатели. Такие ситуации называются многоуровневой адресацией. Пример объявления указателя на указатель: int **ptr2; В приведенном объявлении **ptr2 – это указатель на указатель на число типа int. При этом наличие двух звездочек свидетельствует о том, что имеется двухуровневая адресация. Для получения значения конкретного числа следует выполнить следующие действия: int x = 88, *ptr, **ptr2; ptr = & x; ptr2 = & ptr; printf(" %d", **ptr2); В результате в выходной поток (на дисплей пользователя) будет выведено число 88. В приведенном фрагменте переменная *ptr объявлена как указатель на целое число, а **ptr2 – как указатель на указатель на целое. Значение, выводимое в выходной поток (число 88), осуществляется операцией разыменования указателя **ptr2. Для многомерных массивов указатели указывают на адреса элементов массива построчно. Рассмотрим пример двухмерного целочисленного массива М размера 3 5, т.е. состоящего из 3 строк и 5 столбцов, и определим указатель: int M[3][5]= {{1, 2, 3, 4, 5}, {–6, –7, –8, –9, –10}, {11, 12, 13, 14, 15}}; int *ptr; Элементы массива (по индексам) располагаются в ячейках памяти по строкам в следующем порядке: M[0][0], M[0][1], M[0][2], M[0][3], M[0][4], M[1][0], M[1][1], M[1][2], M[1][3], M[1][4], M[2][0], M[2][1], M[2][2], M[2][3], M[2][4]. Сначала запоминается первая строка, затем вторая, затем третья. В данном случае двухмерный массив – это массив трех одномерных массивов, состоящих из 5 элементов. Указатель указывает на адреса элементов в порядке расположения их в памяти. Поэтому тождественны равенства: ptr == & M[0][0]; //? 1-я строка, 1-й столбец ptr + 1 == & M[0][1]; // 1-я строка, 2-й столбец ptr + 2 == & M[0][2]; // 1-я строка, 3-й столбец ptr + 3 == & M[0][3]; // 1-я строка, 4-й столбец ptr + 4 == & M[0][4]; // 1-я строка, 5-й столбец ptr + 5 == & M[1][0]; // 2-я строка, 1-й столбец ptr + 6 == & M[1][1]; // 2-я строка, 2-й столбец ptr + 7 == & M[1][2]; // 2-я строка, 3-й столбец ptr + 8 == & M[1][3]; // 2-я строка, 4-й столбец ptr + 9 == & M[1][4]; // 2-я строка, 5-й столбец ptr + 10 == & M[2][0]; // 3-я строка, 1-й столбец ptr + 11 == & M[2][1]; // 3-я строка, 2-й столбец ptr + 12 == & M[2][2]; // 3-я строка, 3-й столбец ptr + 13 == & M[2][3]; // 3-я строка, 4-й столбец ptr + 14 == & M[2][4]; // 3-я строка, 5-й столбец Практически следует произвести инициализацию указателя, например, взяв адрес первого элемента матрицы, а затем – обращение к элементам матрицы, можно производить через указатель: ptr = & M[0][0]; *(ptr + i*n + j); где i – номер строки заданной матрицы, j – номер столбца, n – число столбцов в матрице.
|