Это проще всего объяснить с помощью прототипов функций . Прототип функции объявляет функцию, но не определяет ее.
Одной из целей прототипов является то, что это возможно с разными модулями компиляции. Вы помещаете прототипы в заголовочный файл, а определение в исходный файл. Это позволяет скомпилировать объектный файл. Когда вы затем включаете заголовочный файл и связываетесь с объектным файлом, вам не нужно перекомпилировать функции.
Они также полезны, если вы по какой-то причине хотите, чтобы две функции вызывали друг друга. Рассмотрим этот пример:
void fun1(void) {
fun2();
}
void fun2(void) {
fun1();
}
Конечно, это будет бесконечный цикл, но дело в том, что это не скомпилируется. fun2
будет компилироваться, но когда мы подходим к fun1
, мы не знаем, что fun2
существует. Решением является использование прототипов функций.
void fun2(void);
void fun1(void) {
fun2();
}
void fun2(void) {
fun1();
}
Когда вы видите, что это и есть цель, совершенно очевидно, что прототипы функций - это просто объявление. Это не делает ничего. Объявление int f(float, char*);
просто говорит, что существует функция с именем f
. Он возвращает int
и принимает float
и char*
в качестве аргумента. Итак, на ваш вопрос, поскольку он никогда не работает с параметрами, ему не нужно иметь имя для ссылки на них. Только определение делает. Вот почему вы можете получить сообщение об ошибке компилятора error: parameter name omitted
, которое вы отправили в вопросе:
Ваш пример не функция, а указатель на функцию. Та же самая причина применяется там. Вы можете создать указатель на функцию, но только для определения функции нужны идентификаторы параметров. Подробнее о функциональных указателях здесь
Вы можете использовать разные имена для параметров в объявлении и определении, если хотите. Одним из возможных вариантов использования этого (я не говорю, хорошо это или плохо. Просто показываю, что это возможно) является использование описательных имен для переменных в прототипе, но более коротких в определении. Это хорошо компилируется, например:
void backwards(const char *inputString, char *outputString);
void backwards(const char *is, char *os) {
size_t l = strlen(is);
for(size_t n=0; n<l; n++)
os[l-n-1]=is[n];
os[l]='\0';
}
Одной из веских причин этого является то, что заголовочный файл обычно используется в качестве интерфейса, поэтому имеет смысл сказать, что идентификаторы должны быть более информативными. Опять же, я просто показываю, что это возможно, и не говорю, что вы должны или не должны этого делать.
Говоря о прототипах, стоит упомянуть тот факт, что многие люди не знают. Прототип void f();
НЕ объявляет функцию без аргументов. Он объявляет функцию, принимающую неопределенное количество аргументов. Правильный способ объявления функции без аргументов - void f(void);
. Это может быть важно, когда дело доходит до указателей на функции. Посмотрите на этот пример, который я скопировал из другого ответа, который я сделал:
$ cat main.c
int foo() { return 0; }
int bar(int a) { return a; }
int main(void)
{
int (*f)();
f=foo;
f=bar;
int(*g)(void);
g=foo;
g=bar;
}
Это генерирует это предупреждение:
$ gcc main.c
main.c: In function ‘main’:
main.c:11:3: warning: assignment to ‘int (*)(void)’ from incompatible pointer type ‘int (*)(int)’ [-Wincompatible-pointer-types]
g=bar;
^
Когда дело доходит до прототипов обычных функций, вы можете полностью пропустить аргументы, если хотите. Это компилируется и работает просто отлично:
void foo();
int main() {
foo(5,6);
}
void foo(int x, int y) {
printf("The sum is: %d\n", x+y);
}
Вышеуказанное не работает в C ++, поскольку C ++ не поддерживает прототипы с неопределенными аргументами. В C ++ void f();
- это то же самое, что и void f(void);
. По этой причине C не может поддерживать перегрузку функций, в то время как C ++ может.
Наконец, один пример компиляции с предоставленным вами фрагментом:
// Declaration of function pointer
int (*function)(int, float);
// Declaration of function
int foo(int, float);
// Definition of function
int foo(int x, float y) {
return x;
}
// Assign the function pointer
function = foo;
TL; DR
По сути, вы можете объявить прототип функции двумя (за исключением функций с переменными числами) способами:
<return type> <name>();
, который объявляет функцию с неопределенными аргументами и подходит для любого определения функции с правильным именем и типом возврата, независимо от аргументов.
<return type> <name>(<type> [<name>], <type> [<name>] ... );
, которые объявляют функцию с указанными типами аргументов. Имена не являются обязательными и могут отличаться от указанных в определении. Правильный способ объявления функции без аргументов: <return type> <name>(void);