Как объявить массив постоянных указателей на функции в C? - PullRequest
22 голосов
/ 03 декабря 2008

Мне нужно объявить массив указателей на функции следующим образом:

extern void function1(void);
extern void function2(void);
...

void (*MESSAGE_HANDLERS[])(void) = {
   function1,
   function2,
   ...
};

Однако я хочу, чтобы массив был объявлен как константа - как данные в массиве, так и указатель на данные. К сожалению, я не помню, где разместить ключевые слова const.

Я предполагаю, что фактический указатель, MESSAGE_HANDLERS в этом случае, уже постоянен, потому что он объявлен как массив. С другой стороны, нельзя ли изменить указатели функций в массиве во время выполнения, если он объявлен как показано?

Ответы [ 5 ]

56 голосов
/ 03 декабря 2008

Есть способ вспомнить, как построить такой тип. Сначала попробуйте прочитать указатели, начиная с их имени, и читайте справа налево.

Как объявить это без посторонней помощи?

Массивы

T t[5];

- это массив из 5 T . Чтобы сделать T типом функции, вы пишете тип возврата слева, а параметры справа:

void t[5](void);

будет массивом из 1015 * функций, возвращающих void и не имеющим параметров . Но сами функции не могут быть заполнены массивами! Они не объекты. Только указатели на них могут.

Как насчет

void * t[5](void);

Это по-прежнему неправильно, так как это просто изменило бы тип возвращаемого значения на указатель на void. Вы должны использовать скобки:

void (*t[5])(void);

и это на самом деле будет работать. t - это массив из 5 указателей на функции, которые возвращают void и не принимают параметров .

Отлично! А как насчет массива указателей на массивы? Это очень похоже. Тип элемента отображается слева, а размер - справа. Опять же, скобки нужны, потому что в противном случае массив стал бы многомерным массивом целочисленных указателей:

int (*t[5])[3];

Вот и все! массив из 5 указателей на массивы из 3 int .

А как насчет функций?

То, что мы только что узнали, верно и для функций. Давайте объявим функцию, принимающую int, которая возвращает указатель на другую функцию без параметра и возвращающую void:

void (*f(int))(void);

нам снова нужны скобки по той же причине, что и выше. Теперь мы можем вызвать его и снова вызвать возвращаемую функцию.

f(10)();

Возвращение указателя на функцию, возвращение другого указателя на функцию

А как насчет этого?

f(10)(true)(3.4);

? Другими словами, как бы выглядела функция , принимающая int, возвращающая указатель на функцию, принимающая bool, возвращающая указатель на функцию, принимающую double, и возвращающая void ? Ответ в том, что вы просто вкладываете их:

void (*(*f(int))(bool))(double);

Вы могли бы делать это бесконечные времена. Действительно, вы также можете вернуть указатель на массив так же, как вы можете указатель на функцию:

int (*(*f(int))(bool))[3];

Это функция, которая возвращает int, возвращая указатель на функцию, принимает bool, возвращая указатель на массив из 3 int

Какое это имеет отношение к const?

Теперь, когда выше объяснено, как создавать более сложные типы из фундаментальных типов, вы можете поместить const в места, где вы теперь знаете, где они принадлежат. Просто подумайте:

T c * c * c ... * c name;

T - это основной тип, на который мы в конечном итоге указываем. c обозначает либо const, либо не const. Например

int const * const * name;

объявит имя с указателем типа на постоянный указатель на константу int . Вы можете изменить name, но вы не можете изменить *name, который будет иметь тип

int const * const

и ни **name, что будет иметь тип

int const

Давайте применим это к указателю на функцию выше:

void (* const t[5])(void);

Это фактически объявляет массив, содержащий константные указатели. Таким образом, после создания (и инициализации) массива указатели являются постоянными, потому что const появился после звезды. Обратите внимание, что в этом случае мы не можем поставить const перед звездой, поскольку не имеет указателей на постоянные функции . Функции просто не могут быть постоянными, так как это не имеет смысла. Таким образом, следующее недействительно:

void (const * t[5])(void);

Заключение

Способ объявления функций и массивов в C ++ и C на самом деле немного сбивает с толку. Сначала вы должны разобраться с этим, но если вы понимаете это, вы можете написать очень компактные объявления функций, используя его.

15 голосов
/ 03 декабря 2008

cdecl говорит:

cdecl> explain void (* const foo[])(void)
declare foo as array of const pointer to function (void) returning void

Это то, что вам нужно?

15 голосов
/ 03 декабря 2008

В подобных ситуациях введите typedef для имени вашей сигнатуры функции, что значительно упрощает:

typedef void MESSAGE_HANDLER(void);

с этим на месте, это должно быть просто:

MESSAGE_HANDLER * const handlers[] = { function1, function2 };

Чтобы получить фактическое содержимое константы массива.

EDIT : Удалена часть указателя из typedef, это действительно лучше (живи и учись).

1 голос
/ 03 декабря 2008

Я не уверен, будет ли это работать в 'C'. он работает в 'C ++':

  • Сначала определите MESSAGE_HANDLERS как тип:

    typedef void (*MESSAGE_HANDLER)();

  • Затем используйте определение типа для объявления вашего массива константой:

    MESSAGE_HANDLER const handlers[] = {function1, function2};

Хитрость в typedef, если вы можете сделать то же самое семантически в 'C', это тоже должно работать

1 голос
/ 03 декабря 2008

С VisualStudio 2008 я получаю:

void (* const MESSAGE_HANDLERS[])(void) = {
   NULL,
   NULL
};

int main ()
{
    /* Gives error 
        '=' : left operand must be l-value
    */
    MESSAGE_HANDLERS = NULL;

    /* Gives error 
        l-value specifies const object
    */
    MESSAGE_HANDLERS[0] = NULL;
}
...