能否类似于“typedef”一样定义函数原型?

6

我有多个功能非常相似 - 它们接受相同的参数,并返回相同的类型:

double mathFunction_1(const double *values, const size_t array_length);

我已经使用了 typedef 声明的指针来存储这些函数,将它们作为数组保存,以便在同一数据上轻松地使用任意数量的函数,对它们进行映射等操作。

typedef double (* MathFunction_ptr )(const double *, const size_t);

double proxy(MathFunction_ptr mathfun_ptr, const double *values, const size_t array_length);

我希望实现的是在声明和定义函数时与使用指向函数的指针一样方便易用。
因此,我考虑使用类似的typedef来使编写实际函数更加容易。我尝试这样做:
// declaration
typedef double MathFunction (const double *values, const size_t array_length);
MathFunction mathFunction_2;

以下方法部分有效。它让我在声明中“节省了一些按键”,但是定义必须完全键入。
double mathFunction_2(const double *values, const size_t array_length)
{
    // ...
}

我通过搜索发现这个问题的解决方法是: 函数原型typedef可以用于函数定义吗?

然而,它没有提供太多的替代方案,只是重申了根据标准,我在其他实验中尝试的方法是被禁止的。它提供的唯一替代方案是使用

#define FUNCTION(name) double name(const double* values, size_t array_length)

我认为这听起来有些笨重(因为我对使用预处理器持谨慎和怀疑态度)。

我尝试的做法有哪些替代方案呢?


我尝试过另外两种方法,但它们无法实现(并且根据C标准6.9.1,是被禁止和绝对错误的):

1. 这种方法行不通,因为我告诉它定义一个名为mathFunction_2的变量(我相信该变量被视为指针,但我还不够理解)像一个函数一样:

MathFunction mathFunction_2
{
    // ...
}

2. 这种方法行不通,因为它意味着我要创建一个返回函数的函数(在C语言中是不可接受的):

MathFunction mathFunction_2()
{
    // ...
}

3
它根本不能用于定义中。但是,你至少可以让编译器在将函数转换为指针之前检查函数是否与预期的原型匹配。这很漂亮,不是吗? - StoryTeller - Unslander Monica
2
宏解决方案是正确的选择。如果你害怕预处理器,要么是被C++毒害了,要么需要焦虑治疗 :-) - Jens
1
@Jens 不是互斥的。 - Christian Gibbons
1
由于OP对“节省几个按键”感兴趣,请注意double mathFunction_1(const double *values, /* here */ const size_t array_length);WET第二个const没有实际作用。与double mathFunction_1(const double *values, size_t array_length);具有相同的效果。 - chux - Reinstate Monica
1
@tehftw - 我的意思是它既表达了意图/用途,又允许编译器检查其正确性。想象一个更好的类型名称。假设你正在声明一个 UIActionFunction my_cb;。它说明了那个函数将是什么。它传达的不仅仅是一堆参数和返回类型。编译器将根据它(就像对于任何原型一样)检查定义。但是,如果您需要更改 UIActionFunction 的含义,则代码中所有使用该类型别名声明的函数的位置在重新构建时都会立即被突出显示。它已经做了很多事情了。 - StoryTeller - Unslander Monica
显示剩余7条评论
3个回答

8
你可以使用 typedef 来表示签名(也可参见 this):
typedef double MathFunction_ty (const double *, const size_t);

然后声明几个具有相同签名的函数:

MathFunction_ty func1, func2;

或者使用它来声明一些函数指针:
MathFunction_ty* funptr;

等等,在C11中可以找到所有这些内容,阅读n1570

然而,定义必须完全打出来。

当然,在函数的定义中,由于需要为每个形式参数赋予一个名称(而这些名称不是函数类型的一部分),因此必须打出完整的定义。

double func1(const double*p, const size_t s) {
  return (double)s * p[0];
}

并且

double func1(cont double*arr, const size_t ix) {
   return arr[ix];
}

即使它们的形式参数(或形式参数)具有不同的名称,它们也应该具有相同的类型(由上面的MathFunction_ty表示)。

您可能会滥用预处理器,并拥有一个丑陋的宏来缩短此类函数的定义

// ugly code:
#define DEFINE_MATH_FUNCTION(Fname,Arg1,Arg2) \
   double Fname (const double Arg1, const size_t Arg2)
DEFINE_MATH_FUNCTION(func1,p,s) { return (double)s * p[0]; }

我发现这段代码令人困惑和难以阅读。即使确实可行,我也不建议编写类似的代码。但有时出于其他原因,我确实会编写类似的代码。
(顺便说一句,想象一下如果C语言要求每个第一个形式参数都必须命名为$1,每个第二个形式参数都必须命名为$2等等;我认为这会使编程语言更加难以阅读;因此形式参数的名称对人类读者很重要,即使系统名称会使编译器的工作更简单)

阅读有关 λ演算匿名函数(C语言没有,但C++有lambda表达式),闭包(它们不是C函数,因为它们具有封闭值,所以将代码与数据混合; C++有std::function),回调函数(一种必要的“约定”来“模拟”闭包)...阅读SICP,它将改善您对C或C ++的思考。还要查看此答案


UV 用于全面覆盖所有基础 - chux - Reinstate Monica
1
非常感谢你,Brasile Starynkevitch!我学到了一些Scheme语言的知识,它是一种非常棒的编程语言,特别是GNU Guile和Racket实现。总的来说,你的回答远远超出了我的预期,真是太棒了! - tehftw

3

很不幸,在C语言中,我认为没有任何方法可以在不使用预处理器宏的情况下完成你所要求的操作。就个人而言,我同意你的评估,即它们很笨拙,应该避免使用(尽管这是一种观点,并且有争议)。

在C++中,您可能可以利用auto parameters in lambdas

这里显示的示例函数签名并不复杂,我不会担心所谓的重复。如果签名更加复杂,我会将其视为“代码异味”,即您的设计可能需要改进,我会专注于此,而不是在缩短声明的语法方法上花费精力。但这在这里并不适用。


如果我理解正确 - 我的做法是做事情的首选(正确?)方式吗?只要我没有一个很长的函数参数列表,那就应该没问题了? - tehftw
2
@tehftw 是的,我认为你所做的很好。在C语言中,使用typedef来定义指向符合某个特定签名的函数指针是常见且被接受的。即使有许多带有相同签名的函数,也没有问题,尽管你必须将它们全部打出来可能有些麻烦! - TypeIA

1

是的,你可以。实际上,这就是 typedef 声明的目的,用类型标识符声明变量类型。唯一需要注意的是,当你在头文件中使用这样的声明时:

 typedef int (*callback_ptr)(int, double, char *);

然后你声明类似于以下内容:
 callback_ptr function_to_callback;

我不确定你是否在声明一个函数指针以及参数的数量和类型,但尽管如此,一切都是正确的。

最后,我想特别提醒你一些事情。当你处理这样的问题时,通常更便宜、更快捷的方法是去编译器尝试一些示例。如果编译器没有任何投诉地完成你想要的操作,那么最有可能的情况就是你是正确的。

#include <stdio.h>
#include <math.h>

typedef double (*ptr_to_mathematical_function)(double);

extern double find_zero(ptr_to_mathematical_function f, double aprox_a, double aprox_b, double epsilon);

int main()
{
#define P(exp) printf(#exp " ==> %lg\n", exp)

    P(find_zero(cos, 1.4, 1.6, 0.000001));
    P(find_zero(sin, 3.0, 3.2, 0.000001));
    P(find_zero(log, 0.9, 1.5, 0.000001));
}

double find_zero(
    ptr_to_mathematical_function f, 
    double a, double b, double eps)
{
    double f_a = f(a), f_b = f(b);
    double x = a, f_x = f_a;

    do {
        x = (a*f_b - b*f_a) / (f_b - f_a);
        f_x = f(x);

        if (fabs(x - a) < fabs(x - b)) {
           b = x; f_b = f_x;
        } else {
            a = x; f_a = f_x;
        }
    } while(fabs(a-b) >= eps);
    return x;
}

如果你遇到这样的问题,解决它的唯一方法是使用宏。请注意,我如何使用类似但不完全相同的参数列表重复上述的printf(3)函数调用,并在下面解决了这个问题。这是你问题的第二个主要部分。
#define MY_EXPECTED_PROTOTYPE(name) double name(double x)

然后,在定义中只需使用:

MY_EXPECTED_PROTOTYPE(my_sin) {
    return sin(x);
}

MY_EXPECTED_PROTOTYPE(my_cos) {
    return cos(x);
}

MY_EXPECTED_PROTOTYPE(my_tan) {
    return tan(x);
}
...

这将扩展为:


double my_sin(double x) {
...
double my_cos(double x) {
...
double my_tan(double x) {
...

你甚至可以在头文件中使用它,例如:

MY_EXPECTED_PROTOTYPE(my_sin);
MY_EXPECTED_PROTOTYPE(my_cos);
MY_EXPECTED_PROTOTYPE(my_tan);

正如其他答案中所指出的,还有其他语言(如 C++)可以提供更多支持,但我认为这超出了本文的范围。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接