背景:
在我们开发C/C++项目中,经常会使用到系统提供的可变参数函数,例如printf,scanf等。根据需求我们也可能使用到可变参数函数,虽然真正使用的机会不是很多,但是我还是比较好奇如何去实现可变参数函数。这篇博文主要是我记录学习可变参数的一个demo,具体功能是模仿scanf,目前只实现简单的功能,对可变参数函数的一种简单理解。其中使用到这篇博文使用宏实现日志信息以及异常处理。除此之外,我还搜索了几遍博文感觉讲的不错,大家也可以仔细读一读。
Demo:
1 /** WARNING: this code is fresh and potentially isn't correct yet. */ 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <stdarg.h> 5 #include "dbg.h" 6 7 #define MAX_DATA 100 8 9 int read_string(char **out_string, int max_buffer) 10 { 11 *out_string = calloc(1, max_buffer + 1); 12 13 check_mem(*out_string); 14 char *result = fgets(*out_string, max_buffer, stdin); 15 check(result != NULL, "Input error."); 16 return 0; 17 error: 18 if(*out_string) free(*out_string); 19 *out_string = NULL; 20 return -1; 21 } 22 23 int read_int(int *out_int) 24 { 25 char *input = NULL; 26 int rc = read_string(&input, MAX_DATA); 27 check(rc == 0, "Failed to read number."); 28 29 *out_int = atoi(input); 30 free(input); 31 return 0; 32 error: 33 if(input) free(input); 34 return -1; 35 } 36 37 int read_scan(const char *fmt, ...) 38 { 39 int i = 0; 40 int rc = 0; 41 int *out_int = NULL; 42 char *out_char = NULL; 43 char *out_string = NULL; 44 int max_buffer = 0; 45 va_list argp; 46 va_start(argp, fmt); 47 48 for (i = 0; fmt[i] != '\0'; i++) { 49 if (fmt[i] == '%') { 50 i++; 51 switch(fmt[i]) 52 { 53 case '\0': 54 sentinel("Invalid format, you ended with %%."); 55 break; 56 case 'd': 57 out_int = va_arg(argp, int *); 58 rc = read_int(out_int); 59 check(rc == 0, "Failed to read int."); 60 break; 61 case 'c': 62 out_char = va_arg(argp, char *); 63 *out_char = fgetc(stdin); 64 break; 65 case 's': 66 max_buffer = va_arg(argp, int); 67 out_string = va_arg(argp, char **); 68 rc = read_string(out_string, max_buffer); 69 check(rc == 0, "Failed to read string."); 70 break; 71 default: 72 sentinel("Invalid format."); 73 } 74 } else { 75 fgetc(stdin); 76 } 77 check(!feof(stdin) && ! ferror(stdin), "Input error."); 78 } 79 va_end(argp); 80 return 0; 81 error: 82 va_end(argp); 83 return -1; 84 } 85 86 int main(int argc, char* argv[]) 87 { 88 char *first_name = NULL; 89 char initial = ' '; 90 char *last_name = NULL; 91 int age = 0; 92 int age1 = 0; 93 int age2 = 0; 94 printf("What's your first name?"); 95 int rc = read_scan("%s", MAX_DATA, &first_name); 96 check(rc == 0, "Failed first name."); 97 98 printf("What's your initial?"); 99 rc = read_scan("%c\n", &initial); 100 check(rc == 0, "Failed initial."); 101 102 printf("What's your last name?"); 103 rc = read_scan("%s", MAX_DATA, &last_name); 104 check(rc == 0, "Failed last name."); 105 106 printf("How old are you?"); 107 rc = read_scan("%d", &age); 108 check(rc == 0, "Failed age."); 109 110 rc = read_scan("%d%s", &age1, MAX_DATA, &first_name); 111 check(rc == 0, "Failed more age."); 112 113 printf("-------- RESULT -------\n"); 114 printf("First name: %s", first_name); 115 printf("Initial: %c\n", initial); 116 printf("Last name : %s", last_name); 117 printf("Age: %d\n", age1); 118 119 free(first_name); 120 free(last_name); 121 return 0; 122 error: 123 return -1; 124 }
这个demo主要是简单模拟scanf函数功能。
看看结果:
1 zhaoscmatoMacBook-Pro:c zhaosc$ ./ex25 2 What's your first name?LI 3 What's your initial?T 4 What's your last name?Shuchao 5 How old are you?26 6 23 7 Zhaosc 8 -------- RESULT ------- 9 First name: Zhaosc 10 Initial: T 11 Last name : Shuchao 12 Age: 23
简单说说:
首先必须引入头文件stdarg.h,这样才能使用结构体va_list,和宏va_start,va_arg,va_start,va_end,具体每个宏在“读一读”中有详细的说明。这个demo只支持%s,%c和%d,他们可以单独使用也可以组合使用。其中主要处理在read_scan函数中。