تمرير عدد متغير من الحجج حولها
-
03-07-2019 - |
سؤال
لنفترض أن لدي وظيفة C التي تأخذ عددًا متغيرًا من الوسائط: كيف يمكنني استدعاء وظيفة أخرى تتوقع عددًا متغيرًا من الوسيطات من الداخل ، وتمرير جميع الوسائط التي دخلت في الوظيفة الأولى؟
مثال:
void format_string(char *fmt, ...);
void debug_print(int dbg_lvl, char *fmt, ...) {
format_string(fmt, /* how do I pass all the arguments from '...'? */);
fprintf(stdout, fmt);
}
المحلول
لتمرير القطرات ، يجب عليك تحويلها إلى VA_List واستخدام VA_LIST في وظيفتك الثانية. خاصة؛
void format_string(char *fmt,va_list argptr, char *formatted_string);
void debug_print(int dbg_lvl, char *fmt, ...)
{
char formatted_string[MAX_FMT_SIZE];
va_list argptr;
va_start(argptr,fmt);
format_string(fmt, argptr, formatted_string);
va_end(argptr);
fprintf(stdout, "%s",formatted_string);
}
نصائح أخرى
لا توجد طريقة للاتصال (على سبيل المثال) PrintF دون معرفة عدد الحجج التي تنقلها إليها ، إلا إذا كنت ترغب في الدخول في حيل شقية وغير محمولة.
الحل المستخدم بشكل عام هو توفير شكل بديل من وظائف Vararg ، لذلك printf
لديها vprintf
الذي يأخذ va_list
بدلاً من ...
. ال ...
الإصدارات هي مجرد مغلفة حول va_list
الإصدارات.
وظائف variadic يمكن ان يكون خطير. هذه خدعة أكثر أمانًا:
void func(type* values) {
while(*values) {
x = *values++;
/* do whatever with x */
}
}
func((type[]){val1,val2,val3,val4,0});
في C ++ 0x الرائع ، يمكنك استخدام قوالب متغير:
template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}
template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
format_string(fmt, ts...);
}
يمكنك استخدام التجميع المضمن لمكالمة الوظيفة. (في هذا الرمز أفترض أن الوسائط هي أحرف).
void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
{
va_list argumentsToPass;
va_start(argumentsToPass, fmt);
char *list = new char[numOfArgs];
for(int n = 0; n < numOfArgs; n++)
list[n] = va_arg(argumentsToPass, char);
va_end(argumentsToPass);
for(int n = numOfArgs - 1; n >= 0; n--)
{
char next;
next = list[n];
__asm push next;
}
__asm push fmt;
__asm call format_string;
fprintf(stdout, fmt);
}
يمكنك تجربة ماكرو أيضا.
#define NONE 0x00
#define DBG 0x1F
#define INFO 0x0F
#define ERR 0x07
#define EMR 0x03
#define CRIT 0x01
#define DEBUG_LEVEL ERR
#define WHERESTR "[FILE : %s, FUNC : %s, LINE : %d]: "
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...) if((DEBUG_LEVEL & X) == X) \
DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)
int main()
{
int x=10;
DEBUG_PRINT(DBG, "i am x %d\n", x);
return 0;
}
على الرغم من أنه يمكنك حل تمرير التنسيق عن طريق تخزينه في المخزن المؤقت المحلي أولاً ، ولكن يحتاج إلى مكدس ويمكن أن يكون مشكلة في وقت ما للتعامل معه. حاولت المتابعة ويبدو أنه يعمل بشكل جيد.
#include <stdarg.h>
#include <stdio.h>
void print(char const* fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vprintf(fmt, arg);
va_end(arg);
}
void printFormatted(char const* fmt, va_list arg)
{
vprintf(fmt, arg);
}
void showLog(int mdl, char const* type, ...)
{
print("\nMDL: %d, TYPE: %s", mdl, type);
va_list arg;
va_start(arg, type);
char const* fmt = va_arg(arg, char const*);
printFormatted(fmt, arg);
va_end(arg);
}
int main()
{
int x = 3, y = 6;
showLog(1, "INF, ", "Value = %d, %d Looks Good! %s", x, y, "Infact Awesome!!");
showLog(1, "ERR");
}
أتمنى أن يساعدك هذا.
تم تنظيف محلول روس قليلاً. يعمل فقط إذا كانت جميع الأرجوشة هي مؤشرات. كما يجب أن يدعم تنفيذ اللغة إدخال الفاصلة السابقة إذا __VA_ARGS__
فارغ (كل من Visual Studio C ++ و GCC Do).
// pass number of arguments version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}
// NULL terminated array version
#define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}
دعنا نقول أن لديك وظيفة متغيرة نموذجية كتبتها. لأن حجة واحدة على الأقل مطلوبة قبل المتنوعة ...
, ، عليك دائمًا كتابة حجة إضافية في الاستخدام.
او هل انت؟
إذا قمت بلف وظيفة variadic في ماكرو ، فلن تحتاج إلى أي ARG سابقة. النظر في هذا المثال:
#define LOGI(...)
((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
من الواضح أن هذا أكثر ملاءمة ، حيث لا تحتاج إلى تحديد الوسيطة الأولية في كل مرة.
لست متأكدًا مما إذا كان هذا يعمل لجميع المجمعين ، لكنه نجح حتى الآن بالنسبة لي.
void inner_func(int &i)
{
va_list vars;
va_start(vars, i);
int j = va_arg(vars);
va_end(vars); // Generally useless, but should be included.
}
void func(int i, ...)
{
inner_func(i);
}
يمكنك إضافة ... إلى inner_func () إذا كنت تريد ، لكنك لا تحتاج إليها. إنه يعمل لأن VA_START يستخدم عنوان المتغير المحدد كنقطة بداية. في هذه الحالة ، نقدمها إشارة إلى متغير في FUNC (). لذلك يستخدم هذا العنوان ويقرأ المتغيرات بعد ذلك على المكدس. وظيفة inner_func () هي القراءة من عنوان المكدس لـ func (). لذلك فهو يعمل فقط إذا كانت كلتا الوظيفتين تستخدم شريحة المكدس نفسها.
ستعمل وحدات الماكرو VA_START و VA_ARG عمومًا إذا أعطيتهم أي VAR كنقطة انطلاق. لذلك إذا كنت تريد ، يمكنك تمرير مؤشرات إلى وظائف أخرى واستخدامها أيضًا. يمكنك جعل وحدات الماكرو الخاصة بك بسهولة كافية. جميع وحدات الماكرو هي عناوين ذاكرة Typecast. ومع ذلك ، فإن جعلهم يعملون لجميع المجمعين والاتفاقيات استدعاء أمر مزعج. لذلك من الأسهل بشكل عام استخدام تلك التي تأتي مع المترجم.