如何在用户定义的函数中使用格式化字符串? [英] How to use formatting strings in user-defined functions?

查看:60
本文介绍了如何在用户定义的函数中使用格式化字符串?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个函数,以类似于 printf/sprintf 使用格式化字符串的方式在 LCD 上打印字符.

I want to write a function to print characters on an LCD in a similar way that printf/sprintf does using formatting strings.

推荐答案

此答案将所有其他答案的最佳部分合二为一.考虑到所有因素,我认为这是做到这一点的最佳方式,并会在展示示例后进行更详细的解释.

This answer takes the best parts of all of the other answers and puts them into one. I consider it to be the best way to do this given all factors, and will explain in more detail after presenting the example.

这是一个完整的示例,包括函数中的基本错误检查.在这里,我创建了一个类似于 printf 的函数,名为 lcd_printf(),它的工作原理与 printf() 完全一样.它使用 vsnprintf() 将格式化的字符串存储到静态分配的缓冲区中.然后,您可以将此缓冲区发送到我的注释所指示位置的 LCD 显示器.

Here's a full example, including with basic error checking in the function. Here I create a printf-like function called lcd_printf(), which works exactly like printf(). It uses vsnprintf() to store a formatted string into a statically-allocated buffer. You can then send this buffer to the LCD display at the location indicated by my comment.

lcd_print.h:

// For info on the gcc "format" attribute, read here under the section titled 
// "format (archetype, string-index, first-to-check)": 
// https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes.
int lcd_printf(const char * format, ...) __attribute__((format(printf, 1, 2)));

lcd_print.c:

#include "lcd_print.h"

#include <stdarg.h> // for variable args: va_list
#include <stdio.h>  // for vsnprintf()
#include <limits.h> // for INT_MIN

// `printf`-like function to print to the LCD display.
// Returns the number of chars printed, or a negative number in the event of an error. 
// Error Return codes: 
//     1. INT_MIN if vsnprintf encoding error, OR
//     2. negative of the number of chars it *would have printed* had the buffer been large enough (ie: buffer would 
//     have needed to be the absolute value of this size + 1 for null terminator)
int lcd_printf(const char * format, ...)
{
    int return_code;

    // Formatted string buffer: make as long as you need it to be to hold the longest string you'd ever want 
    // to print + null terminator
    char formatted_str[128]; 

    va_list arglist;
    va_start(arglist, format);

    // Produce the formatted string; see vsnprintf documentation: http://www.cplusplus.com/reference/cstdio/vsnprintf/
    int num_chars_to_print = vsnprintf(formatted_str, sizeof(formatted_str), format, arglist); 
    va_end(arglist);

    if (num_chars_to_print < 0)
    {
        // Encoding error
        return_code = INT_MIN;
        return return_code; // exit early
    }
    else if (num_chars_to_print >= sizeof(formatted_str))
    {
        // formatted_str buffer not long enough
        return_code = -num_chars_to_print;
        // Do NOT return here; rather, continue and print what we can
    }
    else
    {
        // No error
        return_code = num_chars_to_print;
    }

    // Now do whatever is required to send the formatted_str buffer to the LCD display here.

    return return_code;
}

ma​​in.c:

#include "lcd_print.h"

int main(void)
{
    int num1 = 7;
    int num2 = -1000;
    unsigned int num3 = 0x812A;

    lcd_printf("my 3 numbers are %i, %i, 0x%4X\n", num1, num2, num3);

    return 0;
}

说明&与替代方法的比较:

@Harikrishnan 指出您应该使用 sprintf().这是在正确的轨道上,是一种有效但不太通用和完整的方法.创建一个新的 可变参数函数,它使用 vsnprintf(),作为 @剑鱼和我做了,更好.

Explanation & comparison to alternate approaches:

@Harikrishnan points out you should use sprintf(). This is on the right track, and is a valid, but less versatile and complete approach. Creating a new variadic function which uses vsnprintf(), as @Swordfish and I have done, is better.

@Swordfish 按顺序对 vsnprintf() 的正确用法进行了精彩演示创建您自己的printf()-like 可变参数函数.他的示例(除了缺少错误处理)是自定义printf() 类实现的完美模板,该实现依赖于动态内存分配.他对 vsnprintf() 的第一次调用,带有一个 NULL 目标缓冲区,只不过是确定他需要为格式化的字符串分配多少字节(这是一个巧妙且这个应用程序的常用技巧),他对 vsnprintf() 的第二次调用实际上创建了格式化的字符串.对于实时应用程序,它们也具有大量 RAM(例如:PC 应用程序),这是完美的方法.但是,对于微控制器,我强烈建议不要这样做,因为:

@Swordfish does a fantastic demonstration of the proper usage of vsnprintf() in order to create your own printf()-like variadic function. His example (aside from lacking error handling) is the perfect template for a custom printf()-like implementation which relies on dynamic memory allocation. His first call to vsnprintf(), with a NULL destination buffer, does nothing more than determine how many bytes he needs to allocate for the formatted string (this is an ingenious and commonly-used trick for this application), and his second call to vsnprintf() actually creates the formatted string. For non-real-time applications which also have large amounts of RAM (ex: PC applications) this is the perfect approach. However, for microcontrollers I strongly recommend against it because:

  1. 这是不确定的.每次调用 free() 可能需要不同的(事先无法确定的)时间来完成.这是因为堆内存随着时间的推移变得碎片化.这意味着这种方法不适用于实时系统.
    • 有关 malloc()free() 的各种堆实现的更多信息,请查看 5 个堆实现,例如,此处由 FreeRTOS 描述:https://www.freertos.org/a00111.html.在此页面上搜索确定性".
  1. It is non-deterministic. Calling free() can take different (and indeterminable before-hand) amounts of time to complete each time you call it. This is because the heap memory becomes fragmented over time. This means this approach is not good for real-time systems.
    • For more information about various heap implementations for malloc() and free(), review the 5 heap implementations, for example, described by FreeRTOS here: https://www.freertos.org/a00111.html. Search this page for "deterministic".

此外,它缺少 GCC 的格式"属性,这是一个很好的接触(更多内容见下文).

Additionally, it is lacking the GCC "format" attribute, which is a nice touch (more on this below).

@P__J__ 提到 GCC格式"属性.我的例子也使用了这个.

@P__J__ mentions the GCC "format" attribute. My example uses this as well.

如果使用 GCC 编译器或任何其他具有类似功能的编译器,强烈建议将其添加到您创建的任何自定义 printf() 类函数中.

If using the GCC compiler, or any other compiler which has something similar, this is highly recommended to add to any custom printf()-like function you make.

GCC 文档,在名为 format (archetype, string-index, first-to-check) 的部分下,声明:

The GCC documentation, under the section called format (archetype, string-index, first-to-check), states:

格式属性指定函数采用应针对格式字符串进行类型检查的 printf、scanf、strftime 或 strfmon 样式参数.

The format attribute specifies that a function takes printf, scanf, strftime or strfmon style arguments that should be type-checked against a format string.

换句话说,它在编译时为您的自定义 printf() 类函数提供了额外的保护和检查.这很好.

In other words, it provides extra protections and checks to your custom printf()-like function at compile time. This is good.

对于我们的例子,只需使用 printf 作为 archetype,并使用一个数字作为 string-indexfirst-to-检查参数.

For our case, simply use printf as the archetype, and a number for the string-index and first-to-check parameters.

参数string-index指定哪个参数是格式字符串参数(从1开始),而first-to-check是第一个参数的编号检查格式字符串.

The parameter string-index specifies which argument is the format string argument (starting from 1), while first-to-check is the number of the first argument to check against the format string.

由于非静态 C++ 方法具有隐式 this 参数,因此在为 string-indexfirst-to 赋值时,此类方法的参数应从两个开始计数,而不是从一个开始计数-检查.

Since non-static C++ methods have an implicit this argument, the arguments of such methods should be counted from two, not one, when giving values for string-index and first-to-check.

换句话说,这里有一些将此属性应用于类似 printf() 的函数原型的有效示例用法:

In other words, here are some valid example usages for this attribute applied to printf()-like function prototypes:

  • 在 C:

  • In C:

int lcd_printf(const char * format, ...) __attribute__((format(printf, 1, 2))); // 1 is the format-string index (1-based), and 2 is the variadic argument (`...`) index (1-based)
int lcd_printf(my_type my_var, const char * format, ...) __attribute__((format(printf, 2, 3))); // 2 is the format-string index (1-based), and 3 is the variadic argument (`...`) index (1-based)
int lcd_printf(my_type my_var, my_type my_var2, const char * format, my_type my_var3, ...) __attribute__((format(printf, 3, 5))); // 3 is the format-string index (1-based), and 5 is the variadic argument (`...`) index (1-based)

  • 在 C++ 中:

  • In C++:

    int lcd_printf(const char * format, ...) __attribute__((format(printf, 2, 3))); // 2 is the format-string index (2-based), and 3 is the variadic argument (`...`) index (2-based)
    int lcd_printf(my_type my_var, const char * format, ...) __attribute__((format(printf, 3, 4))); // 3 is the format-string index (2-based), and 4 is the variadic argument (`...`) index (2-based)
    int lcd_printf(my_type my_var, my_type my_var2, const char * format, my_type my_var3, ...) __attribute__((format(printf, 4, 6))); // 4 is the format-string index (2-based), and 6 is the variadic argument (`...`) index (2-based)
    

  • 在我的其他答案中阅读更多内容:我应该如何在 C++ 的类方法中正确使用 __attribute__ ((format (printf, x, y))) ?.

    Read more in my other answer here: How should I properly use __attribute__ ((format (printf, x, y))) inside a class method in C++?.

    因此,将以上所有内容放在一起,您将获得我在上面介绍的微控制器的理想解决方案.

    这篇关于如何在用户定义的函数中使用格式化字符串?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆