天道酬勤,学无止境

根据用户偏好进行过滤的包装 printf 函数(wrapper printf function that filters according to user preferences)

问题

我的程序写入日志和标准输出。 然而,每条消息都有一个特定的优先级,用户在首选项中指定哪些优先级属于哪个流(日志或标准输出)。

unsigned short PRIO_HIGH = 0x0001;
unsigned short PRIO_NORMAL = 0x0002;
unsigned short PRIO_LOW = 0x0004;

首选项由一些标志处理:

unsigned short PRIO_LOG = (PRIO_HIGH | PRIO_NORMAL);
unsigned short PRIO_STD = (PRIO_HIGH);

write_log函数应该使用与 printf 函数相同的参数,并添加unsigned short priority参数。

write_log((PRIO_NORMAL|PRIO_LOW), "HELLO %s, take %d", "World", 1);

(即使PRIO_NORMAL|PRIO_LOW没什么意义......)

检查标志很容易: if(priority & PRIO_LOG) (如果在两个参数中都设置了任何标志,则返回 >1)

但是,我不知道如何将字符串文字格式参数传递给 printf 函数。 任何人都可以帮助或给我一个指针(可能是达到相同效果的替代方法)? 将不胜感激。

回答1

您想使用 C 的可变参数“varargs”功能调用 vprintf() 而不是 printf()。

#include <stdarg.h>

int write_log(int priority, const char *format, ...)
{
    va_list args;
    va_start(args, format);

    if(priority & PRIO_LOG)
            vprintf(format, args);

    va_end(args);
}

有关更多信息,请参阅与此类似的内容。

回答2

我认为 Jeff 的想法是可行的方法,但您也可以使用宏完成此操作,而无需使用 vprintf。 这可能需要 gcc:

#define write_log(priority,format,args...)        \
                  if (priority & PRIO_LOG) {      \ 
                      printf(format, ## args);    \
                  }

在此处查看有关其工作原理的信息。

回答3

如果您希望 PRIO_* 定义被用作位(使用 | 和 &),您必须为它们中的每一个指定自己的位:

unsigned short PRIO_HIGH = 0x0001;
unsigned short PRIO_NORMAL = 0x0002;
unsigned short PRIO_LOW = 0x0004;

可以使用 do { } while (0) 表示法改进宏。 它使对 write_log 的调用更像是一个语句。

#define write_log(priority,format,args...) do { \
    if (priority & PRIO_LOG) { \
        printf(format, ## args); \
    } while(0)

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 转发C中可变参数函数的调用(Forward an invocation of a variadic function in C)
    问题 在C语言中,是否可以转发可变参数函数的调用? 如 int my_printf(char *fmt, ...) { fprintf(stderr, "Calling printf with fmt %s", fmt); return SOMEHOW_INVOKE_LIBC_PRINTF; } 在这种情况下,显然不必严格按照上述方式转发调用(因为您可以通过其他方式记录调用,或者使用vfprintf),但是我正在使用的代码库要求包装程序执行一些实际的工作,并且没有(也没有增加)类似于vfprintf的辅助函数。 [更新:根据到目前为止提供的答案,似乎有些困惑。 用另一种方式表达这个问题:通常,您可以在包装任意可变参数函数吗? 回答1 如果您没有类似于vfprintf的函数,该函数采用va_list而不是可变数量的参数,则不能这样做。 请参阅http://c-faq.com/varargs/handoff.html。 例子: void myfun(const char *fmt, va_list argp) { vfprintf(stderr, fmt, argp); } 回答2 不直接,但是可变参数函数与varargs样式替代函数成对出现是很常见的(在标准库中,您几乎会普遍找到这种情况)。 例如printf / vprintf v ...函数采用va_list参数
  • __attribute __((format(printf,1,2)))用于MSVC吗?(__attribute__((format(printf, 1, 2))) for MSVC?)
    问题 使用GCC,我可以指定__attribute__((format(printf, 1, 2))) ,告诉编译器此函数采用vararg参数,这些参数是printf格式说明符。 这在包装例如vsprintf函数族的情况下非常有用。 我可以有extern void log_error(const char *format, ...) __attribute__((format(printf, 1, 2))); 每当我调用此函数时,gcc都会像检查printf一样检查参数的类型和数量是否符合给定的格式说明符,如果没有,则发出警告。 Microsoft C / C ++编译器是否具有类似的功能? 回答1 虽然在启用-Wformat时GCC会检查格式说明符,但VC ++没有进行这种检查,即使对于标准函数也是如此,因此没有等效于此__attribute__因为没有等效于-Wformat的内容。 我认为Microsoft对C ++的重视(通过保持对C ++的ISO兼容性而仅支持C89来证明)可能部分是VC ++不进行格式说明符检查的原因。 在C ++中,使用<iostream>格式说明符是不必要的。 回答2 使用SAL注释,您可以使用_Printf_format_string_ (从VS2k8或VS2k10开始)或__format_string (对于VS2k5): #undef
  • 重命名函数而不更改其引用(Rename a function without changing its references)
    问题 我有一个使用带有 -ffunction-sections 选项的 gcc 编译的目标文件。 我可以访问源文件,但不允许修改它。 file.c void foo(void) { bar(); } void bar(void) { abc(); } 我试图实现的是让所有对 bar 的引用都采用绝对地址(我将在链接器脚本中分配),而 bar 将由链接器放置在其他某个地址。 一个可能的解决方案是将 bar 重命名为 file_bar 而不更改 foo() 中对 bar 的调用。 我尝试使用 objcopy -redefine-syms 但它似乎甚至重命名了对 bar 的调用。 除非函数在同一个编译单元中,busybee 提供的解决方案解决了这个问题。 foo1.c #include <stdio.h> extern void bar1(); void foo1(){ printf("foo1\n"); } int main(){ printf("main\n"); foo1(); bar1(); } bar1.c #include <stdio.h> void bar1(){ printf("bar1\n"); } 包装器 #include <stdio.h> void __wrap_foo1(){ printf("wrap_foo1\n"); } void __wrap_bar1(
  • 共享对象中的弱链接未按预期工作(weak linking in shared object not working as expected)
    问题 我正在尝试使用 cmocka 单元测试框架,该框架建议使用弱链接来选择用户定义的实现而不是函数的实际实现。 在我的环境中,我有一个要进行单元测试的共享对象。 我在一个单独的文件中实现了单元测试,我编译并链接到共享对象。 我的问题是调用共享对象中的函数bar反过来调用该共享对象中的函数foo总是会导致foo的真正实现而不是自定义实现。 我创建了共享对象和单元测试的简化实现。 共享库ac : #include <stdio.h> void foo(void); __attribute__((weak)) void bar(void); __attribute__((weak)) void foo(void) { printf("called real foo\n"); } void bar(void) { printf("called real bar calling\n"); foo(); } 单元测试, bc : #include <stdio.h> #include <stdbool.h> bool orig_foo; bool orig_bar; void __wrap_foo(void) { printf("in foo wrapper\n"); if (orig_foo) __real_foo(); else printf("called wrapped foo\n"
  • 避免在 printf 的包装器中发出警告(Avoid warning in wrapper around printf)
    问题 我正在编写的小 C 库中有一个错误报告功能。 除了普通的error函数之外,我还想提供一个errorf函数,以便轻松地在错误消息中嵌入信息。 /* * Prints a formatted error message. Use it as you would use 'printf'. See the * 'sio_error' function. */ void sio_errorf(const char *format, ...) { // Print the error prefix if (g_input == STDIN) fputs("error: ", stderr); else fprintf(stderr, "%s: ", g_progname); // Pass on the varargs on to 'vfprintf'. va_list arglist; va_start(arglist, format); // This may produce the following warning -- ignore it: // warning: format string is not a string literal vfprintf(stderr, format, arglist); va_end(arglist); fputc('\n'
  • C语言中带有可变参数的函数的包装器(wrapper for a function with variable arguments in C)
    问题 最近我想实现一个printf包装器。 经过一番搜索,我发现vprintf非常适合这种需求: void my_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); vprintf(fmt, args); va_end(args); } 但是是否可以使用可变参数而不是va_list为printf或任何其他类似函数实现这样的包装器? (我的意思是,如果他们不提供v版本怎么办?) 由于一些评论者没有完全理解我的想法,我最好详细说明一下。 假设您在 C 库中有一个普通的printf函数。 有人给你一个fmt字符串"%d %u %f" ,以及相应的输入。 现在您想编写一个类似于printf ,但将所有%f替换为%.2f 。 当然你可以用两条语句来完成任务: replace(fmt, "%f", "%.2f"); printf(fmt, inputs); 但是如果你多次使用这个函数,你可能想要一个包装器来节省一些时间。 当然,宏可以完成此任务。 但是没有宏是否可能,例如: void myprintf(fmt, ...) { replace(fmt, "%f", "%.2f"); printf(fmt, inputs); } 这里的问题是你不知道如何用myprintf的参数...来馈送内部printf 。
  • 避免动态内存分配的 std::function 包装器(std::function wrapper that avoids dynamic memory allocation)
    问题 考虑以下代码: #include <functional> #include <cstdio> #include <cstdlib> #include <cstring> #include <time.h> struct Foo { Foo(int x) : x(x) {} void func1() const { printf("Foo::func1 x=%d\n", x); } void func2() const { printf("Foo::func2 x=%d\n", x); } int x; char junk[64]; }; struct Bar { Bar(int x) : x(x) {} void func3() const { printf("Bar::func3 x=%d\n", x); } int x; char junk[64]; }; void loop(std::function<void()>& g) { for (int i=0; i<10; ++i) { switch (rand()%3) { case 0: { Foo foo(3); g = std::bind(&Foo::func1, foo); break; } case 1: { Foo foo(4); g = std::bind(&Foo::func2, foo); break
  • 如何在Linux上重新实现(或包装)syscall函数?(How do I reimplement (or wrap) a syscall function on Linux?)
    问题 假设我想完全接管open()系统调用,也许要包装实际的syscall并执行一些日志记录。 一种方法是使用LD_PRELOAD加载一个(用户制作的)共享对象库,该库将接管open()入口点。 然后,用户制作的open()例程通过dlsym()对其进行调用并获取指向glibc函数open()的指针。 但是,以上提出的解决方案是动态解决方案。 假设我想静态地链接自己的open()包装器。 我该怎么办? 我猜想机制是一样的,但是我也猜想用户定义的open()和libc open()之间会有符号冲突。 请分享其他任何技术来达到相同的目标。 回答1 您可以使用ld提供的包装功能。 来自man ld : --wrap symbol使用包装函数。 任何对symbol未定义引用都将解析为__wrap_symbol 。 任何对__real_symbol未定义引用都将解析为symbol 。 所以,你只需要使用前缀__wrap_为您的包装功能和__real_当你想调用的真正功能。 一个简单的例子是: malloc_wrapper.c : #include <stdio.h> void *__real_malloc (size_t); /* This function wraps the real malloc */ void * __wrap_malloc (size_t size) { void
  • 为什么gets功能如此危险,以至于不应该使用它?(Why is the gets function so dangerous that it should not be used?)
    问题 当我尝试使用GCC编译使用gets()函数的C代码时,出现以下警告: (.text + 0x34):警告:“获取”功能很危险,不应使用。 我记得这与堆栈保护和安全性有关,但是我不确定为什么。 如何删除此警告,为什么会有关于使用gets()的警告? 如果gets()如此危险,那么为什么我们不能删除它呢? 回答1 为了使用gets安全,你必须知道你会究竟有多少字符来读,这样就可以使你的缓冲区足够大。 您只会知道,如果您确切知道将要读取的数据。 而不是使用gets ,而是要使用具有签名的fgets char* fgets(char *string, int length, FILE * stream); ( fgets如果读取整行,将在字符串中保留'\n' ;您必须处理该问题。) 直到1999 ISO C标准,它仍然是该语言的正式组成部分,但2011年标准正式将其删除。 大多数C实现仍支持它,但是至少gcc会为使用它的任何代码发出警告。 回答2 为什么gets()危险 第一个Internet蠕虫(莫里斯Internet蠕虫)大约在30年前(1988-11-02)逃脱了,它使用gets()和缓冲区溢出作为其在系统之间传播的方法之一。 基本问题是该函数不知道缓冲区有多大,因此它将继续读取直到找到换行符或遇到EOF为止,并且可能会溢出给定缓冲区的范围。 您应该忘记曾经听说过gets(
  • 为malloc创建包装函数,并在C中免费(Create a wrapper function for malloc and free in C)
    问题 我试图在C中free创建包装函数和malloc ,以帮助通知我内存泄漏。 有谁知道如何声明这些函数,所以当我调用malloc()和free() ,它将调用我的自定义函数,而不是标准lib函数? 回答1 您有几种选择: GLIBC特定的解决方案(主要是Linux)。 如果您的编译环境是带有gcc glibc ,则首选方法是使用malloc挂钩。 它不仅允许您指定自定义malloc和free ,而且还将通过堆栈上的返回地址来标识调用方。 POSIX特定的解决方案。 将malloc和free定义为可执行文件中原始分配例程的包装器,这将“覆盖” libc中的版本。 在包装器内,您可以调用原始的malloc实现,可以使用带有RTLD_NEXT句柄的dlsym查找该实现。 您的定义包装函数的应用程序或库需要与-ldl链接。 #define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> void* malloc(size_t sz) { void *(*libc_malloc)(size_t) = dlsym(RTLD_NEXT, "malloc"); printf("malloc\n"); return libc_malloc(sz); } void free(void *p) { void (*libc_free)(void*) =
  • 结构包装器和可变数组大小(Structure wrapper and variable array size)
    问题 我经常使用: 数组包装器按值传递数组,但问题是数组的大小是在编译时确定的(请参阅代码的第 I 部分) 依赖于变量的数组声明(参见代码的第二部分) 如何“组合”这两种类型的代码来拥有依赖于变量的数组包装器? (见代码的第三部分。我知道它不能工作,因为结构声明中有一个变量,这里只是提供一个想法) #include <stdio.h> int main() { // First part of code // Array wrapper to pass array by value but size of array is determined at compile time struct S {int m_array[5];} myarray={1,2,3,4,5}; struct S myarraycopy; struct S copyarray(struct S a) { return a ;} myarraycopy=copyarray(myarray); for (int i=0;i<5;i++) printf("%d \n",myarraycopy.m_array[i]); // Second part of code // Array declaration is function of a variable n and // so it is not
  • C代码中的错误处理(Error handling in C code)
    问题 在C库中以一致的方式处理错误时,您认为“最佳实践”是什么? 我一直在想两种方法: 始终返回错误代码。 一个典型的函数如下所示: MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize); 始终提供错误指针方法: int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError); 使用第一种方法时,可以编写如下代码,将错误处理检查直接放在函数调用上: int size; if(getObjectSize(h, &size) != MYAPI_SUCCESS) { // Error handling } 看起来比这里的错误处理代码更好。 MYAPIError error; int size; size = getObjectSize(h, &error); if(error != MYAPI_SUCCESS) { // Error handling } 但是,我认为使用返回值来返回数据会使代码更具可读性。显然,在第二个示例中,已将一些内容写入了size变量。 您是否对我为什么偏爱这些方法中的任何一个或可能将它们混合或使用其他方法有任何想法? 我不喜欢全局错误状态,因为它会使库的多线程使用更加痛苦。 编辑:关于此的C ++特定想法也很有趣,只要它们不涉及异常
  • 是否可以从C#.Net调用C函数(Is it possible to call a C function from C#.Net)
    问题 我有一个C库,想从C#应用程序中调用此库中的函数。 我尝试通过将C lib文件添加为链接器输入并将源文件添加为其他依赖项来在C lib上创建C ++ / CLI包装器。 有什么更好的方法可以实现这一点,因为不确定如何将C输出添加到c#应用程序中。 我的C代码- __declspec(dllexport) unsigned long ConnectSession(unsigned long handle, unsigned char * publicKey, unsigned char publicKeyLen); 我的CPP包装器- long MyClass::ConnectSessionWrapper(unsigned long handle, unsigned char * publicKey, unsigned char publicKeyLen) { return ConnectSession(handle, publicKey, publicKeyLen); } 回答1 该示例将针对Linux : 1)创建一个C文件libtest.c并包含以下内容: #include <stdio.h> void print(const char *message) { printf("%s\\n", message); } 那是printf的一个简单的伪包装器。
  • 检查指针是否分配了内存(Checking if a pointer is allocated memory or not)
    问题 我们可以检查传递给函数的指针是否在C中分配了内存吗? 我在C语言中写了一个自己的函数,该函数接受一个字符指针-buf [指向缓冲区的指针]和大小-buf_siz [缓冲区大小]。 实际上,在调用此函数之前,用户必须创建一个缓冲区并为其分配buf_siz的内存。 由于用户可能会忘记进行内存分配,而只是将指针传递给我的函数,因此我想检查一下。 所以有什么办法可以检查我的函数,以查看传递的指针是否确实分配了buf_siz的内存量。 EDIT1:似乎没有标准库可以检查它..但是有没有脏东西可以检查它.. ?? EDIT2:我确实知道我的函数将由优秀的C程序员使用...但是我想知道是否可以检查..如果我们愿意听.. 结论:因此,无法检查某个指针是否在函数中分配了内存 回答1 除了某些特定于实现的骇客之外,您无法检查。 指针除了指向的地方外没有其他信息。 最好的办法是说“我知道这个特定的编译器版本是如何分配内存的,所以我将取消对内存的引用,将指针移回4个字节,检查大小,确保它匹配...”,依此类推。 您不能以标准方式执行此操作,因为内存分配是由实现定义的。 更不用说他们可能根本没有动态分配它。 您只需要假设您的客户知道如何使用C进行编程即可。我能想到的唯一解决方案是自己分配内存并返回它,但这并不是一个小小的改变。 (这是一个较大的设计更改。) 回答2
  • 你最喜欢的 C++ 调试技术是什么? [关闭](What are your favorite debugging techniques in C++? [closed])
    问题 就目前而言,这个问题不适合我们的问答形式。 我们希望答案得到事实、参考或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。 如果您认为此问题可以改进并可能重新打开,请访问帮助中心以获取指导。 11 年前关闭。 有 printfs、断言、编辑和继续、日志框架吗? 你最喜欢的毒药是什么? 回答1 断言断言断言。 我的个人库中有 300000 loc(不计算注释)高度分解和重用的代码,其中大约 15%(猜测)是模板,50000 loc 是测试代码。 如果一个习语被复制,那么它就成为一个函数/方法。 就我个人而言,我认为剪切和粘贴的简易性是 DEVIL 的发明,故意放置在那里膨胀代码和传播缺陷。 大约 4% 的库是 ASSERTS 和调试代码(很少的 printfs 和几乎所有的输出都排队输出到 cout 流自定义低优先级任务,因为屏幕 IO 非常昂贵,因此时间会改变)。 也许 50% 的断言是为了保证类不变量和方法执行的后置条件。 当我重新访问一段我可能匆忙或只是在接口/对象耦合设计中犯了错误的代码时,我会毫不留情地重构,比如一个方法的主题对象确实属于一个对象对象,而该方法属于其中一个原本是对象对象(参数对象)。 如果我进行实质性的重构,对断言持开放态度似乎可以保护我免受一些愚蠢的错误的影响。 这不会发生很多,但有时。 我有一个 DEBUGGING 宏,它的作用很像
  • PHP扩展开发完整教程(下)
    第11章 PHP中的面向对象 实例化一个对象并且调用它的方法 php public function hello() { echo "hello world!\n"; } } function test_call() { $obj = new baby(); $obj->hello(); } 下面我们在扩展中实现以上test_call函数。 zend_class_entry *baby_ce; ZEND_FUNCTION(test_call) { zval *obj; MAKE_STD_ZVAL(obj); object_init_ex(obj, baby_ce); //如果确认此类没有构造函数就不用调用了。 walu_call_user_function(NULL, obj, "__construct", ""); walu_call_user_function(NULL, obj, "hello", ""); zval_ptr_dtor(&obj); return; } ZEND_METHOD(baby, __construct) { printf("a new baby!\n"); } ZEND_METHOD(baby, hello) { printf("hello world!!!!!\n"); } static zend_function_entry baby
  • 如何在 C 中包装现有函数(How to wrap existing function in C)
    问题 我正在尝试包装现有功能。 下面的代码是完美的工作。 #include<stdio.h> int __real_main(); int __wrap_main() { printf("Wrapped main\n"); return __real_main(); } int main() { printf("main\n"); return 0; } 命令: gcc main.c -Wl,-wrap,main 输出: Wrapped main main 所以我用温度改变了主要功能。 我的目标是包装 temp() 函数。 下面是代码 温度 #include<stdio.h> int temp(); int __real_temp(); int __wrap_temp() { printf("Wrapped temp\n"); return __real_temp(); } int temp() { printf("temp\n"); return 0; } int main() { temp(); return 0; } 命令: gcc temp.c -Wl,-wrap,temp 输出: temp Wrapped temp 不打印。 请指导我包装功能温度。 回答1 ld 的联机帮助页说: --wrap=symbol Use a wrapper function for
  • 为什么gets功能如此危险,以至于不应该使用它?(Why is the gets function so dangerous that it should not be used?)
    问题 当我尝试使用GCC编译使用gets()函数的C代码时,出现以下警告: (.text + 0x34):警告:“获取”功能很危险,不应使用。 我记得这与堆栈保护和安全性有关,但是我不确定为什么。 我如何删除此警告?为什么会有关于使用gets()的警告? 如果gets()如此危险,那么为什么我们不能删除它呢? 回答1 为了使用gets安全,你必须知道你会究竟有多少字符来读,这样就可以使你的缓冲区足够大。 您只会知道,如果您确切知道将要读取的数据。 而不是使用gets ,而是要使用具有签名的fgets char* fgets(char *string, int length, FILE * stream); ( fgets如果读取整行,将在字符串中保留'\n' ;您必须处理该问题。) 直到1999 ISO C标准,它仍然是该语言的正式组成部分,但2011年标准正式将其删除。 大多数C实现仍支持它,但是至少gcc会为使用它的任何代码发出警告。 回答2 为什么gets()危险 第一个Internet蠕虫(莫里斯Internet蠕虫)大约在30年前(1988-11-02)逃脱了,它使用gets()和缓冲区溢出作为其在系统之间传播的方法之一。 基本问题是该函数不知道缓冲区有多大,因此它将继续读取直到找到换行符或遇到EOF为止,并且可能会溢出给定缓冲区的范围。 您应该忘记您曾经听说过gets(
  • 在PHP中显示带序数后缀的数字(Display numbers with ordinal suffix in PHP)
    问题 我想显示数字如下 1为1, 2作为第二, ..., 150至150。 如何为代码中的每个数字找到正确的序数后缀(st,nd,rd或th)? 回答1 来自维基百科: $ends = array('th','st','nd','rd','th','th','th','th','th','th'); if (($number %100) >= 11 && ($number%100) <= 13) $abbreviation = $number. 'th'; else $abbreviation = $number. $ends[$number % 10]; 其中$number是您要写入的数字。 适用于任何自然数。 作为功​​能: function ordinal($number) { $ends = array('th','st','nd','rd','th','th','th','th','th','th'); if ((($number % 100) >= 11) && (($number%100) <= 13)) return $number. 'th'; else return $number. $ends[$number % 10]; } //Example Usage echo ordinal(100); 回答2 PHP具有内置功能。 它甚至可以处理国际化!
  • 提交 Spring 表单时日期格式错误(Wrong date format when submit Spring form)
    问题 我有一个使用 Spring MVC 和 Thymeleaf 的项目。 我需要根据他的喜好为每个用户显示不同格式的日期。 例如,用户 A 想显示像 MM/dd/yyyy 这样的日期,而用户 B 想显示像 dd/MM/yyyy 这样的日期。 为此,我使用了这个 thymeleaf 参数: th:value="${#dates.format(myDate, dateFormat)}" 值“dateFormat”基于用户偏好。 这工作正常。 我的问题是日期输入在表单中,当我提交表单时,它没有采用好的格式。 我总是得到 MM/dd/yyyy。 如果我选择格式 dd/MM/yyyy 并输入 18/01/2016,在我的弹簧控制器中,我将获得“Thu Jun 01 00:00:00 CEST 2017”,对应于 dd/MM/yyyy 中的 01/06/2017 . 我该怎么做才能获得我想要的格式的日期? 这是我的代码: <form th:action="@{/test}" th:object="${filter}" th:method="POST"> <input type="date" th:type="date" class="form-control" th:id="myDate" th:name="myDate" th:value="${#dates.format(filter