【问题标题】:Is it possible to "typedef"(of sorts) a function prototype?是否可以“typedef”(某种)函数原型?
【发布时间】:2018-07-25 10:57:10
【问题描述】:

我有多个彼此相似的函数——它们接受相同的参数,并返回相同的类型:

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)
{
    // ...
}

我通过更多搜索这个问题发现的是:Can a function prototype typedef be used in function definitions?

但是它没有提供很多替代方案,只是重申我在其他实验中尝试做的事情根据标准是被禁止的。它提供的唯一选择是使用

#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()
{
    // ...
}

【问题讨论】:

  • 它只是不能在定义中使用。但至少你让编译器在转换为指针之前检查你的函数是否匹配预期的原型。这很整洁,不是吗?
  • 宏解决方案是要走的路。如果你害怕预处理器,你要么被 C++ 毒害,要么需要焦虑治疗 :-)
  • @Jens 不互斥。
  • 因为 OP 对“节省一些击键”感兴趣。请注意,double mathFunction_1(const double *values, /* here */ const size_t array_length); 中的 WET 2nd const 没有任何功能用途。效果与double mathFunction_1(const double *values, size_t array_length);
  • @tehftw - 我的意思是它既表达了意图/用法,又允许编译器检查它的正确性。想象一个更好的类型名称。想象一下,您正在声明一个UIActionFunction my_cb;。它说明了该功能将是什么。它传达的不仅仅是一堆参数和一个返回类型。编译器将对照它检查定义(就像任何原型一样)。但是,如果您需要更改 UIActionFunction 是什么,那么代码中的所有位置将在重建时立即突出显示,对于由该类型别名声明的任何函数。它已经做了很多。

标签: c typedef function-prototypes


【解决方案1】:

您可以使用typedef 作为签名(另请参阅this):

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

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

MathFunction_ty func1, func2;

或声明一些函数 pointer 使用它:

MathFunction_ty* funptr;

等等...所有这些都在C11,阅读n1570

但是定义必须完全输入。

当然,因为你需要在函数的中为每个形参赋予一个name(并且这样的名字不是函数类型的一部分) >定义。因此

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 等等......;恕我直言使编程语言的可读性大大降低;因此形式参数的名称对人类读者很重要,即使系统名称会使编译器的工作更简单)

还可以阅读 λ-calculusanonymous functions(C 没有它们,但 C++ 有 lambda expressions)、closures(它们是不是 C 函数,因为它们具有封闭值所以将代码与数据混合;C++ 有std::function-s)、callbacks(“模仿”闭包的必要约定)...阅读SICP,它将提高您对 C 的思考或 C++。另请查看that 答案。

【讨论】:

  • 大量的道具,感谢你 Brasile Starynkevitch - 我学到了一点 Scheme 是一门了不起的语言,尤其是 GNU Guile 和 Racket 实现。总的来说,你的回答比我预期的要多,太棒了。
【解决方案2】:

不幸的是,在 CI 中,如果不使用预处理器宏,没有任何方法可以满足您的要求,至少我个人同意您的评估,即它们很笨重并且应该避免(尽管这是一个问题意见和开放辩论)。

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

您在此处显示的示例函数签名实际上并不复杂,我不会担心感知到的重复。如果签名要复杂得多,我会将其视为您的设计可以改进的“代码味道”,并且我会将精力集中在此处,而不是用于缩短声明的语法方法。此处并非如此。

【讨论】:

  • 如果我理解正确 - 我的做法是首选(正确?)做事方式?只要我没有一大堆函数参数,应该没问题?
  • @tehftw 是的,在我的书中,你所做的一切都很好。使用typedef 指向与某个签名匹配的函数的指针在C 中很常见并被接受。拥有许多具有相同签名的函数没有问题,即使您必须将它们全部输入也很烦人!
【解决方案3】:

是的,你可以。实际上,这就是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++) 对此提供支持以及更多支持,但我认为这超出了这里的范围。

【讨论】:

    猜你喜欢
    • 2011-06-02
    • 1970-01-01
    • 1970-01-01
    • 2014-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-09
    • 1970-01-01
    相关资源
    最近更新 更多