Как работают указатели на функции в C?

Пожалуйста, посмотрите пример кода ниже:

function readURL(input) {

  if (input.files && input.files[0]) {
    var reader = new FileReader();

    reader.onload = function(e) {
      $('#blah').attr('src', e.target.result);
    }

    reader.readAsDataURL(input.files[0]);
  }
}

$("#imgInp").change(function() {
  readURL(this);
});

your image

Кроме того, вы можете попробовать этот образец здесь .

1134
задан Yuval Adam 8 May 2009 в 15:49
поделиться

5 ответов

Указатели функций в C

Начнем с базовой функции, которая будет указывать на :

int addInt(int n, int m) {
    return n+m;
}

Прежде всего, давайте определим указатель на функцию, которая получает 2 int s и возвращает int :

int (*functionPtr)(int,int);

Теперь мы можем безопасно указать на нашу функцию:

functionPtr = &addInt;

Теперь, когда у нас есть указатель на функцию, давайте воспользуемся им:

int sum = (*functionPtr)(2, 3); // sum == 5

Передача указателя на другую функцию в основном такая же:

int add2to3(int (*functionPtr)(int, int)) {
    return (*functionPtr)(2, 3);
}

Мы также можем использовать указатели функций в возвращаемых значениях (старайтесь не отставать, это становится беспорядочно):

// this is a function called functionFactory which receives parameter n
// and returns a pointer to another function which receives two ints
// and it returns another int
int (*functionFactory(int n))(int, int) {
    printf("Got parameter %d", n);
    int (*functionPtr)(int,int) = &addInt;
    return functionPtr;
}

Но гораздо лучше использовать ] typedef :

typedef int (*myFuncDef)(int, int);
// note that the typedef name is indeed myFuncDef

myFuncDef functionFactory(int n) {
    printf("Got parameter %d", n);
    myFuncDef functionPtr = &addInt;
    return functionPtr;
}
1419
ответ дан 19 December 2019 в 20:14
поделиться

Одно из моих любимых применений указателей на функции - это дешевые и простые итераторы -

#include <stdio.h>
#define MAX_COLORS  256

typedef struct {
    char* name;
    int red;
    int green;
    int blue;
} Color;

Color Colors[MAX_COLORS];


void eachColor (void (*fp)(Color *c)) {
    int i;
    for (i=0; i<MAX_COLORS; i++)
        (*fp)(&Colors[i]);
}

void printColor(Color* c) {
    if (c->name)
        printf("%s = %i,%i,%i\n", c->name, c->red, c->green, c->blue);
}

int main() {
    Colors[0].name="red";
    Colors[0].red=255;
    Colors[1].name="blue";
    Colors[1].blue=255;
    Colors[2].name="black";

    eachColor(printColor);
}
109
ответ дан 19 December 2019 в 20:14
поделиться

Указатели функций можно легко объявлять, если у вас есть основные деклараторы:

  • id: ID : ID - это
  • Указатель: * D : указатель D на
  • Функция: D (<параметры>) : функция D, принимающая < параметры > возвращающая

В то время как D - еще один декларатор, созданный с использованием тех же правил. В конце концов где-то он заканчивается ID (см. пример ниже), который является именем объявленного объекта. Давайте попробуем создать функцию, принимающую указатель на функцию, которая ничего не берет и возвращает int, и возвращает указатель на функцию, принимающую char и возвращающую int. С type-defs это примерно так

typedef int ReturnFunction(char);
typedef int ParameterFunction(void);
ReturnFunction *f(ParameterFunction *p);

Как видите, довольно легко создать его, используя typedefs. Без typedef это не сложно с последовательным применением вышеуказанных правил декларатора. Как видите, я пропустил ту часть, на которую указывает указатель, и то, что возвращает функция. Это то, что появляется в самом левом углу объявления и не представляет интереса: оно добавляется в конце, если декларатор уже создан. Давайте сделаем это. Построим его последовательно, сначала многословно - покажем структуру с помощью [ и ] :

function taking 
    [pointer to [function taking [void] returning [int]]] 
returning
    [pointer to [function taking [char] returning [int]]]

Как видите, можно полностью описать тип, добавляя деклараторы один за другим. Строительство можно вести двумя способами. Один - снизу вверх, начиная с самого правильного (уходит) и продвигаясь до идентификатора. Другой способ - сверху вниз, начиная с идентификатора и заканчивая листьями. Я покажу оба пути.

Bottom Up

Построение начинается с объекта справа: возвращаемого объекта - функции, принимающей char. Чтобы деклараторы были различны, я собираюсь пронумеровать их:

D1(char);

Параметр char вставлен напрямую, поскольку это тривиально. Добавление указателя на декларатор путем замены D1 на * D2 . Обратите внимание, что нам нужно заключить в круглые скобки * D2 . Это можно узнать, посмотрев приоритет оператора * - и оператора вызова функции () . Без скобок компилятор прочитал бы его как * (D2 (char p)) . Но это, конечно, уже не будет простой заменой D1 на * D2 . Круглые скобки вокруг деклараторов всегда разрешены. Так что вы не сделаете ничего плохого, если добавите их слишком много.

(*D2)(char);

Тип возврата завершен! Теперь давайте заменим D2 функцией декларатора функции , принимающей <параметры> , возвращающей , то есть D3 (<параметры>) , который мы сейчас.

(*D3(<parameters>))(char)

Обратите внимание, что скобки не нужны, поскольку мы хотим, чтобы D3 на этот раз был декларатором функции, а не указателем. Большой, осталось только его параметры. Параметр выполняется точно так же, как и возвращаемый тип, только с заменой char на void . Итак, я скопирую его:

(*D3(   (*ID1)(void)))(char)

Я заменил D2 на ID1 , так как мы закончили с этим параметром (это уже указатель на функцию - нет необходимости в другом декларатор). ID1 будет именем параметра. Теперь, как я сказал выше, в конце добавляется тип, который изменяют все эти деклараторы - тот, который появляется в самом левом углу каждого объявления. Для функций это становится возвращаемым типом. Для указателей, указывающих на тип и т.д. Интересно, когда тип записывается, он будет отображаться в обратном порядке, справа :) В любом случае, его подстановка дает полное объявление. Оба раза int конечно.

int (*ID0(int (*ID1)(void)))(char)

В этом примере я вызвал идентификатор функции ID0 .

Сверху вниз

Это начинается с идентификатора в самом левом углу описания типа, обертывая этот декларатор, когда мы идем через правый. Начните с функции , принимающей < параметров > , возвращающей

ID0(<parameters>)

Следующим в описании (после «возврата») был указатель на . Давайте добавим его:

*ID0(<parameters>)

Затем следующая функция , принимающая < параметры > , возвращающая . Параметр представляет собой простой символ, поэтому мы сразу же добавляем его, поскольку он действительно тривиален.

(*ID0(<parameters>))(char)

Обратите внимание на добавленные нами круглые скобки, так как мы снова хотим, чтобы сначала связывался * , и , затем (char) . В противном случае он прочитал бы функцию , принимающую < параметры > , возвращающую функцию ... . Нет, функции, возвращающие функции, даже не разрешены.

Теперь нам просто нужно указать < параметры > . Я покажу краткую версию вывода, так как я думаю, что вы уже знаете, как это сделать.

pointer to: *ID1
... function taking void returning: (*ID1)(void)

Просто поместите int перед деклараторами, как мы делали с восходящим потоком, и мы закончены

int (*ID0(int (*ID1)(void)))(char)

Самое приятное

Что лучше: снизу вверх или сверху вниз? Я привык работать снизу вверх, но некоторым людям удобнее работать сверху вниз. Думаю, это дело вкуса. Кстати, если вы примените все операторы в этом объявлении, вы получите int:

int v = (*ID0(some_function_pointer))(some_char);

Это хорошее свойство объявлений в C: объявление утверждает, что если эти операторы используются в выражении с использованием идентификатора, то оно дает тип, который находится слева. То же самое и с массивами.

Надеюсь, вам понравился этот небольшой урок! Теперь мы можем ссылаться на это, когда люди задаются вопросом о странном синтаксисе объявления функций. Я старался использовать как можно меньше внутренних компонентов C. Не стесняйтесь редактировать / исправлять что-то в нем.

Не стесняйтесь редактировать / исправлять что-то в нем.

Не стесняйтесь редактировать / исправлять что-то в нем.

24
ответ дан 19 December 2019 в 20:14
поделиться

Поскольку указатели на функции часто являются типизированными обратными вызовами, вы можете захотеть взглянуть на типобезопасные обратные вызовы . То же самое относится к точкам входа и т. Д. Функций, которые не являются обратными вызовами.

C довольно непостоянен и в то же время снисходителен :)

0
ответ дан 19 December 2019 в 20:14
поделиться

Указатели функций в C могут использоваться для выполнения объектно-ориентированного программирования в C.

Например, на C написаны следующие строки:

String s1 = newString();
s1->set(s1, "hello");

Да, - > и отсутствие оператора new бесполезны, но, похоже, это означает, что мы устанавливаем текст некоторого класса String равным ] "hello" .

Используя указатели функций, можно эмулировать методы в C .

Как это достигается?

String class на самом деле является структурой с кучей указателей на функции, которые действуют как способ имитации методов. Ниже приводится частичное объявление класса String :

typedef struct String_Struct* String;

struct String_Struct
{
    char* (*get)(const void* self);
    void (*set)(const void* self, char* value);
    int (*length)(const void* self);
};

char* getString(const void* self);
void setString(const void* self, char* value);
int lengthString(const void* self);

String newString();

Как можно видеть, методы класса String фактически являются указателями на объявленную функцию. При подготовке экземпляра String вызывается функция newString , чтобы установить указатели функций на соответствующие функции:

String newString()
{
    String self = (String)malloc(sizeof(struct String_Struct));

    self->get = &getString;
    self->set = &setString;
    self->length = &lengthString;

    self->set(self, "");

    return self;
}

Например, getString , которая вызывается при вызове метода get , определяется следующим образом:

char* getString(const void* self_obj)
{
    return ((String)self_obj)->internal->value;
}

Можно заметить одну вещь: не существует концепции экземпляра объекта и наличия методов, которые на самом деле часть объекта, поэтому "собственный объект" должен передаваться при каждом вызове. (И внутренняя - это просто скрытая структура , которая была опущена в листинге кода ранее - это способ скрыть информацию, но это не относится к указателям на функции. )

Таким образом, вместо возможности выполнить s1-> set ("hello"); , нужно передать объект для выполнения действия с s1-> set (s1, "Привет") .

С этим второстепенным объяснением, которое необходимо передать ссылкой на вас, мы перейдем к следующей части, которая является наследованием в C .

Допустим, мы хотим сделать подкласс String , скажем ImmutableString . Чтобы сделать строку неизменной, метод set будет недоступен, при этом будет сохранен доступ к get и length , а также заставить «конструктор» принять a char * :

typedef struct ImmutableString_Struct* ImmutableString;

struct ImmutableString_Struct
{
    String base;

    char* (*get)(const void* self);
    int (*length)(const void* self);
};

ImmutableString newImmutableString(const char* value);

По сути, для всех подклассов доступные методы снова являются указателями на функции. В это время,

298
ответ дан 19 December 2019 в 20:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: