优雅地尝试以特定的方式执行各种功能

Elegantly attempt to execute various functions a specific way

本文关键字:方式 执行 功能      更新时间:2023-10-16

我尝试按顺序执行各种函数n次,只有在前一个函数没有返回false(错误)的情况下才能继续执行,否则我会重置并重新开始。

一个序列的例子是:

  1. 打开模块:module.power(true),3次尝试
  2. 等待信号:module.signal(),10次尝试
  3. 发送消息:module.sendSMS('test'),3次尝试
  4. 关闭模块:module.power(false),1次尝试

这些操作中的每一个都以相同的方式完成,只是更改DEBUG文本和要启动的函数:

DEBUG_PRINT("Powering ON");  // This line changes
uint8_t attempts = 0;
uint8_t max_attempts = 3;  // max_attempts changes
while(!module.power(true) && attempts < max_attempts){  // This line changes
attempts++;
DEBUG_PRINT(".");
if(attempts == max_attempts) {
DEBUG_PRINTLN(" - Failed.");
soft_reset();  // Start all over again
}
delay(100);
}
DEBUG_PRINTLN(" - Success");
wdt_reset(); // Reset watchdog timer, ready for next action

有没有一种优雅的方法可以把这个过程放在一个函数中,我可以调用它以这种特定的方式执行所需的函数,例如:

void try_this_action(description, function, n_attempts)

这将使上面的操作1-4类似于:

try_this_action("Powering ON", module.power(true), 3);
try_this_action("Waiting for signal", module.signal(), 10);
try_this_action("Sending SMS", module.sendSMS('test'), 3);
try_this_action("Powering OFF", module.power(false), 1);

我遇到的一个困难是,被调用的函数有不同的语法(有些接受参数,有些不接受…)。除了在我需要的地方复制/粘贴大量代码之外,还有更优雅的模块化方法吗?

我遇到的一个困难是调用的函数有不同的语法(有些采用参数,有些不采用…)。

这确实是一个问题。同时,对于同一个函数,实际函数参数也可能发生变化。

有没有更优雅的除了复制/粘贴chunck代码之外,还有一种模块化的方法我需要它的地方?

我认为你可以制作一个可变函数,它使用函数的特定知识来调度,以处理不同的函数签名和实际参数。不过,我怀疑我是否会认为这个结果更优雅。

我倾向于通过宏来处理这份工作,而不是:

// desc:     a descriptive string, evaluated once
// action:   an expression to (re)try until it evaluates to true in boolean context
// attempts: the maximum number of times the action will be evaluated, itself evaluated once
#define try_this_action(desc, action, attempts) do { 
int _attempts = (attempts);                      
DEBUG_PRINT(desc);                               
while(_attempts && !(action)) {                  
_attempts -= 1;                              
DEBUG_PRINT(".");                            
delay(100);                                  
}                                                
if (_attempts) {                                 
DEBUG_PRINTLN(" - Success");                 
} else {                                         
DEBUG_PRINTLN(" - Failed.");                 
soft_reset();                                
}                                                
wdt_reset();                                     
} while (0)

用法正如您所描述的:

try_this_action("Powering ON", module.power(true), 3);

。。尽管效果就像你在每个位置插入了每个动作的代码,但使用这样的宏会产生更容易阅读的代码,而且在词汇上不会重复。因此,例如,如果您需要更改尝试操作的步骤,可以通过修改宏一次性完成。

您需要使函数指针都具有相同的签名。我会用这样的东西;

typedef int(*try_func)(void *arg);

并且具有类似于以下内容的try_this_action(...)签名;

void try_this_action(char * msg, int max_trys, try_func func, void *arg)

然后你会实施类似的行动;

int power(void *pv)
{
int *p = pv;    
int on_off = *p;
static int try = 0;
if (on_off && try++)
return 1;
return 0;
}
int signal(void *pv)
{
static int try = 0;
if (try++ > 6)
return 1;
return 0;
}

并这样称呼他们;

int main(int c, char *v[])
{
int on_off = 1;
try_this_action("Powering ON", 3, power, &on_off);
try_this_action("Signaling", 10, signal, 0);
}

不同arity的函数可以用通用签名来抽象(想想main)。与其每个人都给出自己独特的论点,不如简单地为他们提供:

  1. 参数计数
  2. 指向参数的指针向量

这就是操作系统如何处理它运行的所有程序。我举了一个非常基本的例子,你可以在下面查看。

#include <stdio.h>
#include <stdlib.h>
/* Define total function count */
#define MAX_FUNC        2
/* Generic function signature */
typedef void (*func)(int, void **, const char *);
/* Function pointer array (NULL - initialized) */
func functions[MAX_FUNC];
/* Example function #1 */
void printName (int argc, void **argv, const char *desc) {
fprintf(stdout, "Running: %sn", desc);
if (argc != 1 || argv == NULL) {
fprintf(stderr, "Err in %s!n", desc);
return;
}
const char *name = (const char *)(argv[0]);
fprintf(stdout, "Name: %sn", name);
}
/* Example function #2 */
void printMax (int argc, void **argv, const char *desc) {
fprintf(stdout, "Running: %sn", desc);
if (argc != 2 || argv == NULL) {
fprintf(stderr, "Err in %s!n", desc);
return;
}
int *a = (int *)(argv[0]), *b = (int *)(argv[1]);
fprintf(stdout, "Max: %dn", (*a > *b) ? *a : *b);
}

int main (void) {
functions[0] = printName;               // Set function #0
functions[1] = printMax;                // Set function #1

int f_arg_count[2] = {1, 2};            // Function 0 takes 1 argument, function 1 takes 2.
const char *descs[2] = {"printName", "printMax"};
const char *name = "Natasi";            // Args of function 0
int a = 2, b = 3;                       // Args of function 1
int *args[2] = {&a, &b};                // Args of function 1 in an array.
void **f_args[2] = {(void **)(&name), 
(void **)(&args)};    // All function args.
// Invoke all functions.    
for (int i = 0; i < MAX_FUNC; i++) {
func f = functions[i];
const char *desc = descs[i];
int n = f_arg_count[i];
void **args = f_args[i];
f(n, args, desc);
}
return EXIT_SUCCESS;
}

您可以使用变元函数,在参数列表中首先声明那些始终存在的参数,然后声明变量部分。在下面的代码中,我们为操作函数定义了一种类型,void返回具有参数列表:

typedef void (*action)(va_list);

然后定义为动作执行做准备的通用动作例程:

void try_this_action(char *szActionName, int trials, action fn_action, ...)
{
va_list args;
va_start(args, fn_action);    //Init the argument list
DEBUG_PRINT(szActionName);  // This line changes
uint8_t attempts = 0;
uint8_t max_attempts = trials;  // max_attempts changes
//Here we call our function through the pointer passed as argument
while (!fn_action(args) && attempts < max_attempts)
{   // This line changes
attempts++;
DEBUG_PRINT(".");
if (attempts == max_attempts)
{
DEBUG_PRINTLN(" - Failed.");
soft_reset();   // Start all over again
}
delay(100);
}
DEBUG_PRINTLN(" - Success");
wdt_reset();    // Reset watchdog timer, ready for next action
va_end(args);
}

必须对每个函数进行编码以使用参数列表:

int power(va_list args)
{
//First recover all our arguments using the va_arg macro
bool cond = va_arg(args, bool);
if (cond == true)
{
... //do something
return true;
}
return false;
}

用途为:

try_this_action("Powering ON", 3, module.power, true);
try_this_action("Waiting for signal", 10, module.signal);
try_this_action("Sending SMS", 3, module.sendSMS, "test");
try_this_action("Powering OFF", 1, module.power, false);

如果您需要更多关于可变函数和stdarg.h宏用法的信息,请在网上搜索。从这里开始https://en.cppreference.com/w/c/variadic.

它也可以被编码为宏实现,正如John Bollinger回答中的优秀建议,但在这种情况下,你必须考虑每个宏使用都会实例化整个代码,这最终可能会更好地提高速度(避免函数调用),但可能不适合内存有限的系统(嵌入式),或者需要引用函数try_this_action(不存在)的位置。