Как параметр может иметь тип, но без имени? - PullRequest
5 голосов
/ 10 июля 2019

Я видел вопрос, который был помечен как обман, но одна часть вопроса не получила ответа, и я не нашел подходящего, чтобы исправить это.Итак, вот так.

Однажды я увидел такое заявление:

int (*function)(int, float);

Я не совсем понимаю.Требуется два аргумента, но у них нет имени.Как это работает?Я имею в виду, когда объявляю такую ​​функцию:

int f(int x, int y) {
    return x+y;
}

Как это вообще возможно без идентификаторов?Я заметил, что это не работает, и даже выдает ошибку компилятора для первой строки, говоря, что

int f(int, int) {
    return /* What should I even write here? */ ;
}

Я получаю две ошибки:

f.c:1:7: error: parameter name omitted
 int f(int, int)
       ^~~
f.c:1:7: error: parameter name omitted
 int f(int, int)
            ^~~

1 Ответ

5 голосов
/ 10 июля 2019

Это проще всего объяснить с помощью прототипов функций . Прототип функции объявляет функцию, но не определяет ее.

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

Они также полезны, если вы по какой-то причине хотите, чтобы две функции вызывали друг друга. Рассмотрим этот пример:

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

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

  1. <return type> <name>();, который объявляет функцию с неопределенными аргументами и подходит для любого определения функции с правильным именем и типом возврата, независимо от аргументов.
  2. <return type> <name>(<type> [<name>], <type> [<name>] ... );, которые объявляют функцию с указанными типами аргументов. Имена не являются обязательными и могут отличаться от указанных в определении. Правильный способ объявления функции без аргументов: <return type> <name>(void);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...