Подтвердить что ты не робот

Объяснение указателей функций

У меня проблема с пониманием синтаксиса С++ в сочетании с указателями функций и декларациями функций, то есть:

Обычно, когда мы хотим объявить тип функции, мы делаем что-то вроде:

typedef void(*functionPtr)(int);

и это прекрасно для меня. С этого момента functionPtr является типом, который представляет указатель на функцию, которая возвращает void и принимает значение int в качестве аргумента.

Мы можем использовать его следующим образом:

typedef void(*functionPtr)(int);

void function(int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr fun = function;
    fun(5);

    return 0;
}

И мы получаем 5, напечатанную на экране.

у нас есть указатель на функцию fun, мы назначаем некоторый существующий указатель на функцию - function, и мы выполняем эту функцию указателем. Круто.

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

int main() {

    functionPtr fun = function;
    fun(5);
    (*fun)(5);
    (*function)(5);
    function(5);

    return 0;
}

Итак, пока я могу себе представить, что указатели на функции и функции почти одинаковы, тогда это как-то прекрасно для меня.

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

typedef void(functionPtr)(int); //removed *

void function(int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr fun = function;
    fun(5);

    return 0;
}

Это дает мне следующую ошибку:

prog.cpp: 12: 14: warning: Объявление 'void fun (int)' имеет 'extern' и инициализируется   functionPtr fun = function;

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

int main() {

    functionPtr fun;
    fun(5);

    return 0;
}

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

typedef void(functionPtr)(int);

void function(int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr function;
    function(5);

    return 0;
}

Итак, теперь функция main name используется в основной тени, поэтому function(5) используется из объявления functionPtr function;. Он отлично работает и печатает 5 на экране.

Итак, теперь я в шоке. Почему это случилось? Также вводит в заблуждение, что когда указатель функции объявляется следующим образом:

typedef void(*functionPtr)(int);

Я могу создать функцию типа functionPtr следующим образом:

functionPtr function(int a){
    std::cout << a << std::endl;
}

тогда как при объявлении чего-то вроде:

typedef void(functionPtr)(int);

делает следующее:

functionPtr function(int a){
    std::cout << a << std::endl;
}

интерпретируется компилятором как функция, возвращающая функцию. Если это так, то почему предыдущее объявление (typedef void(functionPtr)(int);) знало, что это функция, возвращающая void и не возвращающая функцию functionPtr?

Может ли кто-нибудь объяснить мне, что на самом деле происходит для меня?

Я использую g++ С++-компилятор с включенной опцией С++ 14.

4b9b3361

Ответ 1

Ну, это немного запутывает.

Тип функции и указатель на тип функции действительно два разных типа (не более, чем int и указатель на int). Однако существует правило, что тип функции распадается на указатель на тип функции почти во всех контекстах. Здесь разлагающееся свободно означает преобразованное (существует разница между преобразованием типов и распадом, но вы, вероятно, сейчас не заинтересованы в нем).

Важно то, что почти каждый раз, когда вы используете тип функции, вы получаете указатель на тип функции. Обратите внимание, что почти, однако, почти каждый раз не всегда!

И вы попадаете в некоторые случаи, когда этого не происходит.

typedef void(functionPtr)(int);
functionPtr fun = function;

Этот код пытается скопировать одну функцию (а не указатель! function!) в другую. Но, конечно, это невозможно - вы не можете копировать функции на С++. Компилятор не допускает этого, и я не могу поверить, что вы его скомпилировали (вы говорите, что у вас есть ошибки компоновщика?)

Теперь этот код:

typedef void(functionPtr)(int);
functionPtr function;
function(5);

function ничего не теневое. Компилятор знает, что это не указатель на функцию, который можно вызвать, и просто вызывает ваш оригинальный function.

Ответ 2

Самый интересный из ваших примеров - этот, воспроизведенный здесь без использования typedef:

void function(int a) { // declaration and definition of 'function'
    std::cout << a << std::endl;
}

int main() {
    void function(int); // declaration of 'function'
    function(5);
}

В большинстве контекстов на С++ повторное объявление function в локальной области будет затенять глобальный ::function. Поэтому, ожидая ошибки компоновщика, имеет смысл - main()::function не имеет права определения?

Кроме того, специальные функции в этом отношении. Из примечания в [basic.scope.pdel]:

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

Итак, пример кода в точности эквивалентен:

void function(int a) { /* ... */ }
void function(int ); // just redeclaring it again, which is ok

int main() {
    function(5);
}

Вы также можете проверить это, поместив глобальный function в некоторое пространство имен, N. На этом этапе локальная декларация области добавит имя в ::, но у нее не будет определения - значит, вы получите ошибку компоновщика.


Другая интересная вещь, которую вы затронули, - это понятие преобразования функции в указатель, [conv.func]:

Значение l типа функции T может быть преобразовано в prvalue типа "указатель на T". Результатом является указатель на функция.

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

fun(5);         // OK, call function pointed to by 'fun'
(*fun)(5);      // OK, first convert *fun back to 'fun'
function(5);    // OK, first convert to pointer to 'function'
(*function)(5); // OK, unary* makes function get converted to a pointer
                // which then gets dereferenced back to function-type
                // which then gets converted back to a pointer

Ответ 3

Посмотрите свои примеры один за другим и что они на самом деле означают.

typedef void(functionPtr)(int);

void function(int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr fun = function;
    fun(5);

    return 0;
}

Здесь вы создаете typedef functionPtr для функций, которые принимают и int, и не возвращают значения. functionPtr на самом деле не является typedef для указателя функции, а фактической функции.

Затем вы пытаетесь объявить новую функцию fun и назначьте ей function. К сожалению, вы не можете назначать функции, поэтому это не работает.

int main() {

    functionPtr fun;
    fun(5);

    return 0;
}

Опять же, вы объявляете функцию fun с указанной вами сигнатурой. Но вы не определяете его, поэтому по праву вы не выполняете этап связывания.

typedef void(functionPtr)(int);

void function(int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr function;
    function(5);

    return 0;
}

Что здесь происходит? Вы определяете typedef, а в основном пишете functionPtr function;. Это в основном просто прототип для функции, которую вы уже написали, function. Он повторяет, что эта функция существует, но в противном случае она ничего не делает. Фактически вы можете написать:

typedef void(functionPtr)(int);

void function(int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr function;
    functionPtr function;
    functionPtr function;
    void function(int);

    function(5);

    return 0;
}

Сколько раз вы хотите, это ничего не изменит. function(5), который вы вызываете, всегда одно и то же.

Одна вещь, которую вы можете сделать на С++, - объявить прототип функции с помощью такого типа typedef, но вы не можете определить ее таким образом.

typedef void(*functionPtr)(int);

functionPtr function(int a){
    std::cout << a << std::endl;
}

Здесь вы определяете функцию, которая возвращает указатель на функцию, но затем вы ее не возвращаете. Компилятор, в зависимости от ваших настроек, может жаловаться или не жаловаться. Но снова function полностью отделен от functionPtr. Вы по существу записали

void (*)(int)   function(int a) {
    ...
}

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

Ответ 4

typedef void functionPtr (int);

void function (int a){
    std::cout << a << std::endl;
}

int main() {

    functionPtr *func;
    func = function;
    func(5);
    return 0;
}

Вы можете использовать его таким образом, я проверил. В этом вопросе действительно сложный вопрос.