Возможно ли иметь переменные входные данные в функциях C

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

Я хочу ввести либо:

два целых числа : выведите одно целое число.

two double : вывести одно двойное число.

Я пытался найти в Интернете какую-то глобальную декларацию ввода, но не могу ее найти.

Я предпочитаю не иметь двух функций, а только одну простую.

Примеры заголовков кода: int diff(int a, int b); двойной diff(двойной a, двойной b);

Спасибо за помощь!


person PathToLife    schedule 21.10.2014    source источник
comment
добавьте третий параметр, указывающий тип аргументов и вывода. Затем пусть код проверяет этот третий параметр и действует соответствующим образом.   -  person user3629249    schedule 21.10.2014


Ответы (3)


Нет, это называется перегрузкой, и это не функция C. Для этого лучше всего создать отдельные функции.

Вы можете сделать это со всеми видами волшебства C (вы можете сделать все с достаточным количеством волшебства C), но результирующий код может быть настолько безобразным, что его невозможно будет поддерживать: -)

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

Например, допустим, вы определяете две функции следующим образом:

int     diffi (int    a, int    b) { return a - b; }
double  diffd (double a, double b) { return a - b; }

Обычно вам приходится решать, что вызывать, основываясь на типах ввода. Функция общего выбора C11 позволяет вам сделать это:

#define diff(a,b)        \
    _Generic((a),        \
        double:  diffd,  \
        int:     diffi,  \
        default: diffX   \
    )(a,b)

И что это делает, в основном, при поиске макроса diff(x,y) в исходном коде:

  • определить тип выражения (a), не вычисляя его;
  • внедрить в исходный поток токен, соответствующий этому типу (или по умолчанию, если совпадение не найдено);
  • вставьте в исходный поток текст (a,b) в конце.

Итак, если ваш исходный файл содержит строки:

x = diff (1,   2);
y = diff (1.0, 2);

это будет переведено на:

x = diffi (1  , 2);
y = diffd (1.0, 2);

давая вам вашу эффективную перегрузку.

Теперь это относительно простой случай, поскольку он опирается только на первый тип аргумента — вы увидите там дыру, если попытаетесь сделать:

z = diff (1, 2.0);

в том, что тип аргумента firstint, поэтому вы получите:

z = diffi (1, 2.0);

что не будет тем, что вы на самом деле хотите сделать. Вот тут-то и возникают сложности, поскольку вам нужно охватить четыре возможности: {int/int, int/double, double/int, double/double} и становится более сложной в зависимости от количества аргументов и возможных типов для каждого аргумента.

Однако этот полный ваш случай можно сделать с разумным использованием значений по умолчанию и вложенных общих выборов, например:

#define diff(a,b)              \
    _Generic((a),              \
        double:  diffd,        \
        default: _Generic((b), \
            double:  diffd,    \
            default: diffi     \
        )                      \
    )(a,b)

и это можно прочитать как:

  • если тип a равен double, используйте diffd;
  • в противном случае, если тип b равен double, используйте diffd;
  • в противном случае используйте diffi.
  • не забудьте также ввести аргументы.

Следующая полная программа (скомпилированная с помощью clang 3.0) показывает эту функцию в действии:

#include <stdio.h>

int diffi (int a, int b) {
    printf ("diffi %d %d", a, b);
    return a - b;
}
double diffd (double a, double b) {
    printf ("diffd %f %f", a, b);
    return a - b;
}

#define diff(a,b)              \
    _Generic((a),              \
        double:  diffd,        \
        default: _Generic((b), \
            double:  diffd,    \
            default: diffi     \
        )                      \
    )(a,b)

int main (void) {
    int i; double d;
    i = diff (1  , 2  ); printf (" --> %d\n", i);
    d = diff (1.0, 2  ); printf (" --> %f\n", d);
    d = diff (1  , 2.0); printf (" --> %f\n", d);
    d = diff (1.0, 2.0); printf (" --> %f\n", d);
    return 0;
}

Вывод этой программы:

diffi 1 2 --> -1
diffd 1.000000 2.000000 --> -1.000000
diffd 1.000000 2.000000 --> -1.000000
diffd 1.000000 2.000000 --> -1.000000

показывая, что для четырех возможностей вызывается правильная функция.


И на самом деле, как указывает rici в комментарии, вы можете полагаться на правила продвижения C, где добавление double и int (в любом порядке) дают вам double, а добавление двух переменных int дает вам int:

#define diff(a,b) _Generic((a+b), double:diffd, default:diffi)(a,b)
person paxdiablo    schedule 21.10.2014
comment
Как насчет функции C11 _Generic? (см. раздел 6.5.1.1 open-std.org /jtc1/sc22/wg14/www/docs/n1570.pdf) Это не так красиво, как C++, но и не настолько уродливо. - person rici; 21.10.2014
comment
@rici, вы должны попробовать это с функцией с четырьмя аргументами, каждый из которых может быть одним из трех типов, тогда вы оцените его уродство :-) Однако это нормально для небольших случаев использования, поэтому я добавлю его как вариант. - person paxdiablo; 22.10.2014
comment
@paxdiablo: я сам использую C++ :-) Но _Generic, безусловно, достаточно хорош для ответа на этот вопрос. - person rici; 22.10.2014
comment
@rici, ну вот, ты прав, это было не так уродливо, как я сначала подумал (по крайней мере, для конкретного случая использования). - person paxdiablo; 22.10.2014
comment
@rici, меня не часто впечатляет чья-то хитрость - хороший звонок :-) - person paxdiablo; 22.10.2014
comment
Я уже проголосовал за вас, иначе ваша правка стоит больше (+1) - person Mohit Jain; 22.10.2014
comment
_Универсальный. Действительно, какое-то волшебство. Спасибо за исчерпывающий ответ paxdiablo и rici. :7) - person PathToLife; 22.10.2014

Перегрузка функций недоступна в C. Некоторые возможные обходные пути

  1. Используйте две функции с разными именами, например: diff_i и diff_d (рекомендуется)
  2. Используйте varargs (не рекомендуется). Со временем это затруднит сопровождение кода.
  3. Используйте _Generic
person Mohit Jain    schedule 21.10.2014

Двойник IEEE754 имеет точность более 32 бит, поэтому просто напишите двойную версию, а об остальном позаботится автоматическое преобразование.

Конечно, если вы используете систему, в которой sizeof(int)>4 или char имеют более 8 бит, может быть лучше написать варианты для каждого типа и принять для них какое-то соглашение об именах на венгерском языке. Тогда вы могли бы написать, может быть:

int diffi(int, int);
double diffd(double, double);
ssize_t diffz(size_t, size_t);

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

person luser droog    schedule 21.10.2014
comment
C int имеет минимальный диапазон, а не максимальный. Таким образом, вполне могут быть реализации, в которых int требует гораздо большей точности, чем double :-) Сказав это, это неплохая идея, если вы знаете ограничения. - person paxdiablo; 21.10.2014
comment
Истинный. Спасибо! Это дает мне кое-что, чтобы добавить к ответу. - person luser droog; 21.10.2014