|
Программирование >> Процедурные приложения
инициализируется адресом переменной mytype). С другой стороны, содержимое ячейки, на которую ссылается такой указатель, может быть свободно изменено: pmytype 2 = &mytypel; недопустимая попытка изменить защищенный адрес *pmytype 2 = (MYTYPE)float value; допустимое изменение содержимого ячейки памяти А теперь подумайте, какой из двух вариантов применения ключевого слова const соответствует объявлению массива? Ответ: второй. Напомним, что имя массива представляет собой неизменяемый адрес первого его элемента. С точки зрения компилятора, ничего не изменилось бы, если бы массив объявлялся следующим образом: тип данных* const имя массива = адрес первого элемента; Другие операции над указателями Мы уже рассмотрели примеры, иллюстрирующие применение операторов ++ и -- к указателям, а также добавление целого значения к указателю. Ниже перечислены другие операции, которые могут быть выполнены над указателями: вычитание целого значения из указателя; вычитание одного указателя из другого (оба должны ссылаться на один и тот же массив); сравнение указателей с помощью операторов <=, = и >=. В результате вычитания целого числа указатель будет ссылаться на элемент, смещенный на указанную величину влево по отношению к текущей ячейке. Результатом вычитания указателя из указателя будет целое число, соответствующее числу элементов между ячейками, на которые они ссылались. Предполагается, что оба указателя одного типа и связаны с одним и тем же массивом. Если в выражении используются два разнотипных указателя или если они ссылаются на разные массивы, то результат операции невозможно предсказать. Независимо от того, в какой операции участвует.указателъ, компилятор не может проследить за тем, чтобы в результате операции адрес не выходил за пределы массива. Указатели одного типа можно сравнивать друг с другом. Возвращаемые значения true(! 0) или false(0) можно использовать в условных выражениях и присваивать переменным типа intтак же, как результаты любых других логических операций. Один указатель будет меньше другого, если он указывает на ячейку памяти с меньшим индексом. При этом предполагается, что оба указателя связаны с одним и тем же массивом. Наконец, указатели можно сравнивать с нулем. В данном случае можно выяснить только равенство/неравенство нулю, поскольку указатели не могут содержать отрицательные значения. Нулевое значение указателя означает, что он не связан ни с каким объектом. Ноль является единственным числовым значением, которое можно непосредственно присвоить указателю независимо от его типа. Кроме того, указатель можно сравнить с константным выражением, результатом которого является ноль, а также с другим указателем типа void(в последнем случае указатель следует предварительно привести к типу void). Все остальные операции с указателями запрещены. Например, нельзя складывать два указателя, а также умножать и делить их. Физическая реализация указателей В примерах данной главы адреса возвращались как целые числа. Исходя из этого, вы могли сделать заключение, что указатели являются переменными типа int. В действительности это не так. Указатель содержит адрес переменной определенного типа, но при этом свойства указателя не сводятся к свойствам рядовых переменных типа int или float. В некоторых системах значение указателя может быть скопировано в переменную типа int и наоборот, хотя стандарты языков C/C++ не гарантируют подобную возможность. Чтобы обеспечить правильную работу программы в разных системах, такую практику следует исключить. Указатели на функции Во всех рассмотренных до сих пор примерах указатели использовались для обращения к определенным значениям, содержащимся в памяти компьютера. Теперь мы познакомимся с указателями, связанными не с данными, а с программными кодами - функциями. Указатели на функции обеспечивают возможность косвенного вызова функций, точно так же как указатели на данные предоставляют косвенный доступ к ячейкам памяти. С помощью указателей на функции можно решать многие важные задачи. Рассмотрим, к примеру, функцию qsort(), осуществляющую сортировку массива. В числе ее параметров должен быть задан указатель на функцию, выполняющую сравнение двух элементов массива. Необходимость в функции-аргументе возникает в связи с тем, что алгоритм сравнения может быть достаточно сложным и многофакторным, работающим по-разному в зависимости от типа элементов. Код одной функции не может быть передан в другую функцию как значение аргумента, но в C/C++ допускается косвенное обращение к функции с помощью указателя. Концепция использования указателей на функции часто иллюстрируется на примере стандартной функции qsort(},прототип которой находится в файле STDLIB.H. В приводимом ниже примере программы на языке С мы создаем собственную функцию сравнения icompare funct () и передаем указатель на нее в функцию qsort(). * qsort.с * Эта программа на языке С демонстрирует передачу указателя на функцию * в качестве аргумента функции qsort(). #include <stdio.h> #include <stdlib.h> #define IMAXVALUES 10 int icompare funct(const void *iresult a, const void *iresult b); int (*ifunct ptr) (const void *, const void *); void main () int i ; int iarray[IMAXVALUES] ={0, 5, 3, 2, 8, 7, 9, 1, 4, 6); ifunct ptr = icompare funct; qsort(iarray,IMAXVALUES, sizeof(int),ifunct ptr); for(i=0;i < IMAXVALUES; i++) printf( %d , iarray[i]); } int icompare funct(const void *iresult a, const void *iresult b) f return({* (int*)iresult a) - (*(int*)iresult b)); } Функция icompare funct() (которую будем называть адресуемой) соответствует требованиям, накладываемым на нее функцией qsort() (которую будем называть вызывающей): она принимает два аргумента типа void* и возвращает целочисленное значение. Напомним, что ключевое слово const в списке аргументов накладывает запрет на изменение данных, на которые указывают аргументы. Благодаря этому вызывающая функция в худшем случае неправильно отсортирует данные, но она не сможет их изменить! Теперь, когда синтаксис использования функции icompare funct() стал понятен, уделим внимание ее телу. Если адресуемая функция возвращает отрицательное число, значит, первый ее аргумент меньше второго. Ноль означает равенство аргументов, а положительное значение - что первый аргумент больше второго. Все эти вычисления реализуются единственной строкой, составляющей тело функции icompare funct () : return((*(int *)iresult a) - (*(int *)iresult b)); Поскольку оба указателя переданы в функцию как void*, они приводятся к соответствующему им типу int и раскрываются (*). Результат вычитания содержимого второго указателя из содержимого первого возвращается в функцию qsort() в качестве критерия сортировки. Важная часть программы - объявление указателя на адресуемую функцию, расположенное сразу вслед за ее прототипом: int (*ifunct ptr)(const void *, const void *); Это выражение определяет указатель ifunct ptr на некую функцию, принимающую два константных аргумента типа void* и возвращающую значение типа int. Обратите особое внимание на скобки вокруг имени указателя. Выражение вида int *ifunct ptr(const void *, const void *); воспринимается не как объявление указателя, а как прототип функции, поскольку оператор вызова функции, (), имеет более высокий приоритет, чем оператор раскрытия указателя *. В результате последний будет отнесен к спецификатору типа, а не к идентификатору ifunct ptr. Функция qsort() принимает следующие параметры: адрес массива, который нужно отсортировать (массив iarray), число элементов массива (константа imaxvalues), размер в байтах элемента таблицы (sizeof(int)) и указатель на функцию сравнения (ifunct ptr() ) Рассмотрим еще несколько интересных примеров: int * (* (*ifunct ptr) (int) ) [5]; float (*(*ffunct ptr)(int,int))(float); typedef double (* (* (*dfunct ptr) () ) [5]) () ; dfunct ptr A dfunct ptr; (* (*function array ptrs () ) [5]) () ; Первая строка описывает указатель ifunct ptr на функцию, принимающую один целочисленный аргумент и возвращающую указатель на массив из пяти указателей типа int. Вторая строка описывает указатель ffunct ptr на функцию, принимающую два целочисленных аргумента и возвращающую указатель на другую функцию с одним аргументом типа float и таким же типом результата. Создавая с помощью ключевого слова typedef новый тип данных (более подробно эта тема раскрывается в главе Дополнительные типы данных ), можно избежать повторения сложных деклараций. Третья строка читается следующим образом: тип dfunct ptr определен как указатель на функцию без аргументов, возвращающую указатель на массив из пяти указателей, которые, в свою очередь, ссылаются на функции без аргументов, возвращающие значения типа double. В четвертой строке создается экземпляр такого указателя. Последняя строка содержит объявление функции, а не переменной. Создаваемая функция function array ptrs() не имеет параметров и возвращает указатель на массив из пяти указателей, которые ссылаются на функции без аргументов, возвращающие значения типа int(последнее подразумевается по умолчанию, если не указано иное). Динамическая память Во время компиляции программ на языках C/C++ память компьютера разделяется на четыре области: программного кода, глобальных данных, стек и динамическую область ( куча ). Последняя отводится для хранения временных данных и управляется функциями распределения памяти, такими как malloc() и free(). Функция mallос () резервирует непрерывный блок ячеек для хранения указанного объекта и возвращает указатель на первую ячейку этого блока. Функция free() освобождает ранее зарезервированный блок и возвращает эти ячейки в динамическую область для последующего резервирования. В качестве аргумента в функцию malloc() передается целочисленное значение, указывающее количество байтов, которое необходимо зарезервировать. Если резервирование прошло успешно, функция malloc() возвращает переменную типа void*, которую можно привести к любому необходимому типу указателя. Концепция использования указателей типа void описана в стандарте ANSI С. Этот спецификатор предназначен для создания обобщенных указателей неопределенного типа, которые впоследствии можно преобразовывать к
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |