- •Отличия между доступом по имени массива и по указателю
- •Int const *(cptr_a);
- •Int main()
- •Int* V[10]; // массив указателей
- •Int (*p)[10]; // указатель массива
- •Int I: // целая переменная
- •Int * pi: // указатель на целую переменную
- •Void* ptr;
- •Int main(){
- •Классы памяти и динамическое выделение памяти
- •Динамические объекты в с
- •Рекомендации по использованию указателей и динамического распределения памяти
- •Void main()
- •Int n; // Порядок матрицы
- •Vveditre poriadok matrizi: 5
- •Int *pmin, I; // Рабочий указатель, содержащий результат
- •Void main() {
Int *pmin, I; // Рабочий указатель, содержащий результат
for (i = 1, pmin=A; i<n; i ++)
if (A[i] < *pmin) pmin = &A[i];
return(pmin); } // В операторе return - значение указателя
Void main() {
int B[5]={3,6,1,7,2}; printf("min = %d\n",*min(B,5)); }
Прежде всего обратим внимание на синтаксис. Заголовок функции написан таким образом, как будто имя функции является указателем на int. Этим способом и обозначается, что ее результат - указатель. Оператор return возвращает значение переменной-указателя pmin, то есть адрес. Вообще в нем может стоять любое выражение, значение которого является указателем, например:
return &A[k]; return pmin + i; return A+k;
Указатель - результат функции - может ссылаться не только на отдельную переменную, но и на массив. В этом смысле он не отличается ничем от других указателей.
В Си возможна также передача ссылки в качестве результата функции. Ее следует понимать как отображение (синоним) на переменную, которая возвращается оператором return. Требования к объекту - источнику ссылки, на который она отображается, еще более строгие - это либо глобальная переменная, либо формальный параметр функции, передаваемый в нее по ссылке или по указателю. При обращении к результату функции - ссылке производится действие с переменной-прототипом.
// Функция возвращает ссылку на минимальный элемент массива
int &ref_min(int A[], int n){
for (int j=0,k=0; i<n; i++) if (A[i]<A[k]) k = i;
return A[k];}
void main(){
int В[5]={4,8,2|6,4};
ref_min(B,5)++;
for (int i=0; i<5; i++) printf("%d *',B[i]);
Указатели на функции
Можно определить указатель на любой тип, в том числе на функцию или метод класса. Если имеется несколько функций одного и того же типа:
int foo(long x);
int bar(long x);
можно определить переменную типа указатель на функцию и вызывать эти функции не напрямую, а косвенно, через указатель:
int (*functptr)(long x);
functptr = &foo;
(*functptr)(2);
functptr = &bar;
(*functptr)(4);
Начнем с краткого обзора простых указателей на функции. В С, и С++ в частности, указатель на функцию с именем my_func_ptr , указывающий на функцию, принимающую в качестве аргументов int и char*, и возвращающую float, объявляется так:
float (*my_func_ptr)(int, char*); // Для большей удобочитаемости, рекомендую использовать typedef. // Особенно можно запутаться, когда указатель на функцию является аргументом // функции. // Тогда бы объявление выглядело так: typedef float (*MyFuncPtrType)(int, char*); MyFuncPtrType my_func_ptr; |
Заметьте, что различным комбинациям аргументов соответствуют различные типы указателей на функцию. В MSVC, кроме этого, указатели на функцию различаются в зависимости от типа соглашения о вызове (calling conventions): __cdecl, __stdcall, __fastcall. Для того чтобы указатель на функции указывал на вашу функцию, необходимо выполнить следующую конструкцию:
my_func_ptr = some_func; |
Для вызова функции через указатель:
(*my_func_ptr)(7, "Arbitrary string"); |
Разрешается приводить один тип указателя на функцию к другому. Но не разрешается приводить указатель на функцию к указателю на void. Остальные разрешенные операции тривиальны. Указателю на функцию можно присвоить 0 для обозначения нулевого указателя. Доступны многочисленные операторы сравнения (==, !=, <, >, <=, >=), можно также проверить на равенство 0 либо неявным преобразованием к bool. Кроме того, указатель на функцию может быть использован в качестве нетипизированного параметра шаблона. Он в корне отличается от типизированного параметра, а также отличается от интегрального нетипизированного параметра шаблона. При инстанцировании используется имя, а не тип или значение. Именные параметры шаблонов поддерживаются не всеми компиляторами, даже не всеми из тех, которые поддерживают частичную специализацию шаблонов.
Наиболее распространенное применение указателей на функции в С – это использование библиотечных функций, таких как qsort, и обратных (callback) функций в Windows. Кроме того, есть еще много вариантов их применения. Реализация указателей на функции проста: это всего лишь «указатели на код», в них содержится начальный адрес участка ассемблерного кода. Различные типы указателей существуют лишь для уверенности в корректности применяемого соглашения о вызове.
В качестве последнего примера рассмотрим динамическое распределение памяти для массива указателей на функции, имеющие один входной параметр типа double и возвращающие значение типа double.
Пример:
#include
#include
double cos(double);
double sin(double);
double tan(double);
int main()
{ double (*(*masfun))(double);
double x=0.5, y;
int i;
masfun=(double(*(*))(double))
calloc(3,sizeof(double(*(*))(double)));
masfun[0]=cos;
masfun[1]=sin;
masfun[2]=tan;
for (i=0; i<3; i++); { y="masfun[i](x);" printf("\n x="%g" y="%g",x,y);" } return 0; }
Указатели на функции как параметры
Указатели на функции - незаменимое средство языков С и С++, когда объектами обработки должны служить функции. Например, создавая процедуры для вычисления определенного интеграла задаваемой пользователем функции или для построения таблицы значений произвольной функции, нужно иметь возможность передавать в программы функции. Удобнее всего организовать связь между функцией, реализующей метод обработки (например, численный метод интегрирования), и той функцией, для которой этот метод нужно применить, через аппарат параметров, в число которых входят указатели на функции.
Рассмотрим задачу вычисления корня функции f(x) на интервале локализации [А; В] для иллюстрации особенностей применения указателя функции в качестве параметра. Численный метод (деление пополам интервала локализации) оформляется в виде функции со следующим заголовком:
float root(указатель_на_функцию, float A, float В, float EPS);
Здесь А - нижняя граница интервала локализации корня; B - верхняя граница того же интервала; EPS - требуемая точность определения корня. Введем тип "указатель на функцию", для которой нужно определить корень:
typedef float(*pointPunc)(float);
Теперь можно определить функцию для вычисления корня заданной функции с указателем pointPunc. Прототип функции будет таким:
float root(pointPunc F, float A, float B, float EPS);
Приведем тестовую программу для определения с помощью root() корня функции у = х2 - 1 на интервале [0, 2]:
//RAZN3_1.СРР - указатель функции как параметр функции.
#include <iostream.h>
#include <stdlib.h> // Для функции exit().
// Определение типа указателя на функцию:
typedef float (*pointFunc)(float);
// Определение функции для вычисления корня:
float root(pointFunc F, float A, float B, float EPS)
{
float x, y, c, Fx, Fy, Fc;
x = A; y = B;
Fx = (*F)(x); // Значение функции на левой границе.
Fy = (*F)(y); // Значение функции на правой границе.
if (Fx * Fy > 0.0)
{
cout << "\nНеверен интервал локализации";
exit(1); // Аварийное завершение программы.
}
do
{
c = (y - x)/2; // Центр интервала локализации.
Fc = (*F)(c); // Значение функции в с.
if (Fc * Fx > 0) { x = c; Fx = Fc; }
else { y = c; Fy = Fc; }
} while (Fc != 0 && y - x > EPS);
return c;
}
#include <math.h>
// Определение тестовой функции у = х * х - 1:
float testfunc(float x)
{ return x * x - 1; }
void main()
{
float root(pointFunc, float, float, float); // Прототип.
float result;
result = root(testfunc, 0.0, 2.0, 1e-5);
cout << "\nКорень тестовой функции: " << result;
}
Текст этой программы можно взять здесь.
Результат выполнения программы:
Корень тестовой функции: 1
Заметим, что, помимо использования в качестве параметра, указатель на функцию может быть еще и типом возвращаемого значения. Например:
int (*ff(int))(int*, int);
ff() объявляется как функция, имеющая один параметр типа int и возвращающая указатель на функцию типа
int (*)(int*, int);
И здесь использование директивы typedef делает объявление понятнее. Объявив PF с помощью typedef, мы видим, что ff() возвращает указатель на функцию:
// Использование директивы typedef делает
// объявления более понятными
typedef int (*PF)(int*, int);
PF ff(int);
Типом возвращаемого значения функции не может быть тип функции. В этом случае выдается ошибка компиляции. Например, нельзя объявить ff() таким образом:
// typedef представляет собой тип функции
typedef int func(int*, int);
func ff(int); // ошибка: тип возврата ff() - функция
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&