Итак, я слышал разные мнения по этому вопросу и просто хочу убедиться, что я правильно понимаю.
Объявления void f();
а также void f(void);
означают точно то же самое, функция f
не принимает никаких параметров. То же самое для определений.
декларация void f(void);
Значит это f
не принимает никаких параметров.
декларация void f();
означает, что функция f
может иметь или не иметь параметры, и если это так, мы не знаем, что это за параметры или сколько их. Обратите внимание, что это не то же самое, что многоточие, мы не можем использовать va_list
,
Теперь здесь все становится интереснее.
Декларация:
void f();
Определение:
void f(int a, int b, float c)
{
//...
}
Декларация:
void f();
Определение:
void f()
{
//...
}
Что происходит во время компиляции в случаях 1 и 2, когда мы вызываем f
с правильными аргументами, неправильными аргументами и без аргументов вообще? Что происходит во время выполнения?
Если я заявляю f
с аргументами, но определите это без них, это будет иметь значение? Должен ли я иметь возможность обращаться к аргументам из тела функции?
Больше терминологии (C, а не C ++): прототип функции объявляет типы ее аргументов. В противном случае функция не имеет прототипа.
void f(); // Declaration, but not a prototype
void f(void); // Declaration and prototype
void f(int a, int b, float c); // Declaration and prototype
Декларации, которые не являются прототипами, являются пережитками от pre-ANSI C, со времен K&R C. Единственная причина использовать объявление старого стиля — поддерживать двоичную совместимость со старым кодом. Например, в Gtk 2 есть объявление функции без прототипа — оно там случайно, но не может быть удалено без разрушения двоичных файлов. Комментарии стандарта C99:
6.11.6 Объявление функций
Использование деклараторов функций с пустыми скобками (не параметр prototype-format
объявления типов) является устаревшей функцией.
Рекомендация: Я предлагаю компилировать весь код C в GCC / Clang с -Wstrict-prototypes
а также -Wmissing-prototypes
кроме обычного -Wall -Wextra
,
void f(); // declaration
void f(int a, int b, float c) { } // ERROR
Объявление не соответствует телу функции! Это на самом деле время компиляции ошибка, и это потому, что вы не можете иметь float
аргумент в функции без прототипа. Причина, по которой вы не можете использовать float
в непрототипированной функции, потому что когда вы вызываете такую функцию, все аргументы продвигаются с использованием определенных продвижений по умолчанию. Вот фиксированный пример:
void f();
void g()
{
char a;
int b;
float c;
f(a, b, c);
}
В этой программе a
повышен до int
1 а также c
повышен до double
, Так что определение для f()
должно быть:
void f(int a, int b, double c)
{
...
}
См. C99 6.7.6, параграф 15,
Если один тип имеет список типов параметров, а другой тип указан
декларатор функции, который не является частью определения функции и который содержит пустой
список идентификаторов, список параметров не должен иметь терминатор с многоточием и тип каждого
Параметр должен быть совместим с типом, который является результатом применения
продвижение аргумента по умолчанию.
Что происходит во время компиляции в случаях 1 и 2, когда мы вызываем
f
с правильными аргументами, неправильными аргументами и без аргументов вообще? Что происходит во время выполнения?
Когда вы звоните f()
, параметры продвигаются с использованием рекламных акций по умолчанию. Если продвигаемые типы соответствуют фактическим типам параметров для f()
тогда все хорошо. Если они не совпадают, это будет наверное компилировать, но вы определенно получите неопределенное поведение.
«Неопределенное поведение» означает «мы не даем никаких гарантий о том, что произойдет». Может быть, ваша программа потерпит крах, возможно, она будет работать нормально, может быть, она пригласит ваших родственников на ужин.
Есть два способа получить диагностику во время компиляции. Если у вас есть сложный компилятор с возможностями межмодульного статического анализа, вы, вероятно, получите сообщение об ошибке. Вы также можете получать сообщения об объявлении непрототипированных функций с помощью GCC, используя -Wstrict-prototypes
— который я рекомендую включить во всех ваших проектах (кроме файлов, которые используют Gtk 2).
Если я заявляю
f
с аргументами, но определите это без них, это будет иметь значение? Должен ли я иметь возможность обращаться к аргументам из тела функции?
Это не должно компилироваться.
На самом деле есть два случая, когда аргументы функции могут не соглашаться с определением функции.
Это нормально, чтобы пройти char *
к функции, которая ожидает void *
, и наоборот.
Можно передавать целочисленный тип со знаком в функцию, которая ожидает беззнаковую версию этого типа, или наоборот, при условии, что значение представимо в обоих типах (т. Е. Оно не отрицательно и не выходит за пределы диапазона подписанный тип).
1: Это возможный тот char
способствует unsigned int
, но это очень редко.
Все это действительно спорный вопрос, если вы используете C99 или более позднюю версию (и, если вы не застряли на старой встроенной системе или чем-то в этом роде, вы, вероятно, должен использовать что-то более современное).
Секция C99 / C11 6.11.6 Future language directions, Function declarators
состояния:
Использование деклараторов функций с пустыми скобками (не деклараторы типов параметров в формате prototype) является устаревшей функцией.
Следовательно, вы должны избегать использования таких вещей, как void f();
в целом.
Если он принимает параметры, перечислите их, формируя надлежащий прототип. Если нет, мы void
окончательно указать, что он не принимает никаких параметров.
В C ++
f () и f (void) одинаковы
В С,
они отличаются, и любое количество аргументов может быть передано при вызове функции f (), но аргумент не может быть передан в f (void)
В чистом C это приводит к ошибке: error C2084: function 'void __cdecl f(void )' already has a body
void f(void);
void f( );
int main() {
f(10);
f(10.10);
f("ten");
return 0;
}
void f(void) {
}
void f( ) {
}
.
fvoid.c line(19) : error C2084: function 'void __cdecl f(void )' already has a body
Но в Pure C ++ он будет компилироваться без ошибок.
Функции перегрузки (только C ++, C не имеет перегрузки)
Вы перегружаете имя функции f, объявляя более одной функции с именем f в одной и той же области видимости. Объявления f должны отличаться друг от друга типами и / или количеством аргументов в списке аргументов. Когда вы вызываете перегруженную функцию с именем f, правильная функция выбирается путем сравнения списка аргументов вызова функции со списком параметров каждой из перегруженных функций-кандидатов с именем f.
пример:
#include <iostream>
using namespace std;
void f(int i);
void f(double f);
void f(char* c);int main() {
f(10);
f(10.10);
f("ten");
return 0;
}
void f(int i) {
cout << " Here is int " << i << endl;
}
void f(double f) {
cout << " Here is float " << f << endl;
}
void f(char* c) {
cout << " Here is char* " << c << endl;
}
выход:
Here is int 10
Here is float 10.1
Here is char* ten