Объяснение указателей функций
У меня проблема с пониманием синтаксиса С++ в сочетании с указателями функций и декларациями функций, то есть:
Обычно, когда мы хотим объявить тип функции, мы делаем что-то вроде:
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.
Ответы
Ответ 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;
}
Вы можете использовать его таким образом, я проверил.
В этом вопросе действительно сложный вопрос.