printf
...
用来表示可变参数,我们看看一个经典的函数原型:
_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL printf(
_In_z_ _Printf_format_string_ char const* const _Format,
...)
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
int _Result;
va_list _ArgList;
__crt_va_start(_ArgList, _Format);
_Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
__crt_va_end(_ArgList);
return _Result;
}
#endif
先稍微展开知识点。printf
函数的定义可以说非常的标准
_Check_return_opt_
#if defined _PREFAST_ && defined _CA_SHOULD_CHECK_RETURN
#define _Check_return_opt_ _Check_return_
#else
#define _Check_return_opt_
#endif
#include <sal.h>
// Check the return value of a function e.g. _Check_return_ ErrorCode Foo();
#define _Check_return_ _SAL2_Source_(_Check_return_, (), _Check_return_impl_)
看到这里就明白,原来也是sal里用于返回值检查
_CRT_STDIO_INLINE
#if defined _NO_CRT_STDIO_INLINE
#undef _CRT_STDIO_INLINE
#define _CRT_STDIO_INLINE
#elif !defined _CRT_STDIO_INLINE
#define _CRT_STDIO_INLINE __inline
#endif
原来就是内联函数的关键字重命名了。
__CRTDECL
#define __CRTDECL __CLRCALL_PURE_OR_CDECL
#ifdef _M_CEE_PURE
#define __CLRCALL_PURE_OR_CDECL __clrcall
#else
#define __CLRCALL_PURE_OR_CDECL __cdecl
#endif
库的导出,也没啥
_In_z_
// nullterminated 'in' parameters.
// e.g. void CopyStr( _In_z_ const char* szFrom, _Out_z_cap_(cchTo) char* szTo, size_t cchTo );
#define _In_z_ _SAL2_Source_(_In_z_, (), _In_ _Pre1_impl_(__zterm_impl))
#define _In_opt_z_ _SAL2_Source_(_In_opt_z_, (), _In_opt_ _Pre1_impl_(__zterm_impl))
sal中对于入参的检测,必须是非空
你看看,你看看,我在不久前介绍说sal是真的非常经典吧(不过无人问津😅)。更多关于sal的介绍可以回看 《使用Sal减少代码缺陷 :你不必欣赏的源代码注释语言之美》
int printf(char const* const _Format,...)
{
int _Result;
va_list _ArgList;
__crt_va_start(_ArgList, _Format);
_Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
__crt_va_end(_ArgList);
return _Result;
}
有感觉熟悉而又陌生的朋友不,别想太复杂。va_list是一个类型,用于存储可变参数的信息。va_start宏用于初始化va_list。va_arg宏用于获取下一个参数。va_end宏用于清理va_list。
注意,使用va_list,va_start,va_arg和va_end宏处理可变参数时,你需要提供一个方式来确定参数的数量和类型。
那么,我这么讲是胡说么,肯定是有依据的,我们看看下面的源码部分:
#if defined _M_CEE_PURE || (defined _M_CEE && !defined _M_ARM && !defined _M_ARM64)
void __cdecl __va_start(va_list*, ...);
void* __cdecl __va_arg(va_list*, ...);
void __cdecl __va_end(va_list*);
#define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
#define __crt_va_arg(ap, t) (*(t *)__va_arg(&ap, _SLOTSIZEOF(t), _APALIGN(t,ap), (t*)0))
#define __crt_va_end(ap) ((void)(__va_end(&ap)))
#elif defined _M_IX86 && !defined _M_HYBRID_X86_ARM64
/***********************************咱们重点来看看这里*****************************************/
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
/********************************************************************************************/
#elif defined _M_ARM
#ifdef __cplusplus
void __cdecl __va_start(va_list*, ...);
#define __crt_va_start_a(ap, v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), _ADDRESSOF(v))))
#else
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _SLOTSIZEOF(v)))
#endif
#define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
#elif defined _M_HYBRID_X86_ARM64
void __cdecl __va_start(va_list*, ...);
#define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
#define __crt_va_arg(ap, t) (*(t*)((ap += _SLOTSIZEOF(t)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
#elif defined _M_ARM64
void __cdecl __va_start(va_list*, ...);
#define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
#define __crt_va_arg(ap, t) \
((sizeof(t) > (2 * sizeof(__int64))) \
? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \
: *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
#elif defined _M_ARM64EC
void __cdecl __va_start(va_list*, ...);
//take the ARM64 va_start (for now)
#define __crt_va_start_a(ap,v) ((void)(__va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), __alignof(v), _ADDRESSOF(v))))
//a hybrid va arg, to account for the shift in calling convention, with the alignment of ARM64
#define __crt_va_arg(ap, t) \
((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \
: *(t*)((ap += _SLOTSIZEOF(t) + _APALIGN(t,ap)) - _SLOTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
#elif defined _M_X64
void __cdecl __va_start(va_list* , ...);
#define __crt_va_start_a(ap, x) ((void)(__va_start(&ap, x)))
#define __crt_va_arg(ap, t) \
((sizeof(t) > sizeof(__int64) || (sizeof(t) & (sizeof(t) - 1)) != 0) \
? **(t**)((ap += sizeof(__int64)) - sizeof(__int64)) \
: *(t* )((ap += sizeof(__int64)) - sizeof(__int64)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
#endif
这一段定义了在不同的系统架构下,__crt_va_start_a
__crt_va_arg
和__crt_va_end
的具体实现方式。
我们仅以下面为例分析。
/***********************************咱们重点来看看这里*****************************************/
#ifndef _VA_LIST_DEFINED
#define _VA_LIST_DEFINED
#ifdef _M_CEE_PURE
typedef System::ArgIterator va_list;
#else
typedef char* va_list; //here
#endif
#endif
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
/********************************************************************************************/
int printf(char const* const _Format,...)
{
int _Result;
va_list _ArgList; // ==》 先等价看作 char* _ArgList;
__crt_va_start(_ArgList, _Format); // 指针指向参数列表的首地址
_Result = _vfprintf_l(stdout, _Format, NULL, _ArgList); // 循环遍历输出参数到标准输出
__crt_va_end(_ArgList); // 指针指向null
return _Result;
}
_vfprintf_l
函数原型如下,具体实现咱么就不在此一层层扒拉下去了。
_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL _vfprintf_l(
_Inout_ FILE* const _Stream,
_In_z_ char const* const _Format,
_In_opt_ _locale_t const _Locale,
va_list _ArgList
)
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
return __stdio_common_vfprintf(_CRT_INTERNAL_LOCAL_PRINTF_OPTIONS, _Stream, _Format, _Locale, _ArgList);
}
#endif
#include <cstdarg>
template<typename _Ty>
void print(_Ty count, ...) {
va_list args;
va_start(args, count);
for (int i = 0; i < count; ++i) {
int value = va_arg(args, int);
std::cout << value << " ";
}
va_end(args);
std::cout << std::endl;
}
// eg
const char* p = "jellt";
const char arr[] = {"this is a girl"};
print<int>(2,sizeof(p),sizeof(arr));
// 基本情况:只有一个参数
template<typename T>
void Mycout(const T& t) {
std::cout << t << std::endl;
}
// 递归情况:有多个参数
template<typename T, typename... Args>
void Mycout(const T& t, const Args&... args) {
std::cout << t << ", ";
Mycout(args...); // 递归调用,处理剩余的参数
}
在C++中,...
是一个可变参数包,它可以包含任意数量的参数。Args...
表示一个类型参数包,args...
表示一个非类型参数包。
在函数模板中,你可以使用 … 来定义一个接受任意数量参数的函数。然后,你可以使用 args… 来访问这些参数。例如,你可以将 args… 传递给其他函数,或者在函数体中遍历 args…。
在你的例子中,Mycout(args...)
是一个递归调用。它将 args...
展开,并将展开后的参数传递给 Mycout
函数。这个过程会一直进行,直到 args...
中只有一个参数为止。这时,编译器会选择一个参数的 Mycou
t 函数(你需要提供这个函数,否则会导致编译错误)。
这个过程是在编译时期完成的。编译器会为每个不同的参数列表生成一个不同的函数实例。这就是为什么我们说可变参数模板可以接受任意数量的参数:编译器会为我们生成所有需要的函数。
const char* p = "jellt";
const char arr[] = {"this is a girl"};
Mycout<int>(sizeof(p),sizeof(arr));