一个有意思的小工具:给它一个字符串,比如 "sin(1+1)+max(2,3)" 这种形式,它能自动找出里面的数学函数调用、解析括号里的表达式,然后调用对应的库函数把结果算出来。全程纯 C,核心用了三个技术点。
一、函数指针”查找表”
C 没有反射,怎么把字符串 "sin" 变成 sin() 函数?答案是字符串比较 + 函数指针:
1 2 3 4 5 6 7 8 9 10 11 12
| typedef double (*math_fun)(double);
math_fun get_math_fun(const char *s) { if (strcmp(s, "sin") == 0) return sin; if (strcmp(s, "cos") == 0) return cos; if (strcmp(s, "tan") == 0) return tan; if (strcmp(s, "sqrt") == 0) return sqrt; if (strcmp(s, "floor") == 0) return floor; return NULL; }
|
typedef double (*math_fun)(double) 定义了”接收 double 返回 double”的函数指针类型,数学库里这一族函数签名完全一致,正好统一管理。
二、括号匹配扫描
要从字符串里截出 sin(1+cos(2)) 的完整参数部分,不能简单找第一个 )——参数里可能嵌套括号。经典解法是计数器匹配:
1 2 3 4 5 6 7 8 9 10 11
| int count_kuohao = -1; do { if (*sub_str_ptr == ')') count_kuohao++; else if (*sub_str_ptr == '(') count_kuohao--;
if (count_kuohao != 0) exp_str[exp_str_i++] = *sub_str_ptr; else break; } while (*sub_str_ptr);
|
嵌套多少层都能正确处理。函数名的提取则是从 ( 往前回溯,直到碰上 + - * / 等运算符为止。
三、字符串替换的坑
工具函数 str_rpl 实现替换时用 memmove 挪动后面那段内存——注意必须用 memmove 而不是 memcpy,因为源和目标区域有重叠,memcpy 对重叠区间的行为是未定义的:
1 2 3 4 5
| while (ptr = strstr(s, s1)) { memmove(ptr + strlen(s2), ptr + strlen(s1), strlen(ptr) - strlen(s1) + 1); memcpy(ptr, s2, strlen(s2)); }
|
写在最后
这个玩具麻雀虽小:函数指针表、括号匹配、memmove 搬移内存,C 语言字符串处理的三板斧都在里面。缺点也很明显——固定 100 字节的缓冲区、魔法数字偏多,算是个”能用但可以重构”的状态。之后可以试试改成递归下降解析器,那就是正经的表达式引擎了。
完整代码
表达式函数查找并计算.c1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
| #include <stdio.h> #include <string.h> #include <stdlib.h> #include <math.h>
char *str_rpl(char *s, const char *s1, const char *s2) { char *ptr; while (ptr = strstr(s, s1)) { memmove(ptr + strlen(s2) , ptr + strlen(s1), strlen(ptr) - strlen(s1) + 1); memcpy(ptr, &s2[0], strlen(s2)); } return s; }
void str_reverse(char* s) { char* left = s; char* right = s + strlen(s) - 1; while (left < right) { char tmp = *left; *left = *right; *right = tmp;
left++; right--; } }
char* double2str(double num) { static char str[21]; sprintf(str, "%lf", num); return str; }
double str2double(const char* str) { return atof(str); }
typedef double (*math_fun)(double); math_fun get_math_fun(const char *s) { if (strcmp(s, "sin") == 0) { return sin; } else if (strcmp(s, "cos") == 0) { return cos; } else if (strcmp(s, "tan") == 0) { return tan; } else if (strcmp(s, "sinh") == 0) { return sinh; } else if (strcmp(s, "cosh") == 0) { return cosh; } else if (strcmp(s, "tanh") == 0) { return tanh; } else if (strcmp(s, "asin") == 0) { return asin; } else if (strcmp(s, "acos") == 0) { return acos; } else if (strcmp(s, "atan") == 0) { return atan; } else if (strcmp(s, "exp") == 0) { return exp; } else if (strcmp(s, "log") == 0) { return log; } else if (strcmp(s, "log10") == 0) { return log10; } else if (strcmp(s, "sqrt") == 0) { return sqrt; } else if (strcmp(s, "ceil") == 0) { return ceil; } else if (strcmp(s, "floor") == 0) { return floor; } else { return NULL; } }
typedef struct { int exp_count; char **exp_funcs; char **exp_strs; char **exp_results; }exp_strStuctTypedef;
exp_strStuctTypedef str_find_exp_and_calc(char *s) { char *ptr = s;
char exp_func[100] = {0}; int exp_func_i = 0; char **exp_funcs = (char **)malloc(sizeof(char *) * 100); int exp_funcs_i = 0; memset(exp_funcs, 0, sizeof(char *) * 100);
char exp_str[100] = {0}; int exp_str_i = 0; char **exp_strs = (char **)malloc(sizeof(char *) * 100); int exp_strs_i = 0; memset(exp_strs, 0, sizeof(char *) * 100);
char exp_result[100] = {0}; int exp_result_i = 0; char **exp_results = (char **)malloc(sizeof(char *) * 100); int exp_results_i = 0; memset(exp_results, 0, sizeof(char *) * 100);
printf("str: %s\n", s); while (*ptr++) { if (*ptr == '(' && (*(ptr - 1) != '(') && (*(ptr - 1) != '+') && (*(ptr - 1) != '-') && (*(ptr - 1) != '*') && (*(ptr - 1) != '/')) { char *exp_func_ptr = ptr; while(*exp_func_ptr--) { if((*exp_func_ptr != '+') && (*exp_func_ptr != '-') && (*exp_func_ptr != '*') && (*exp_func_ptr != '/')) { exp_func[exp_func_i++] = *exp_func_ptr; } else { exp_func[exp_func_i] = '\0'; exp_funcs[exp_funcs_i] = malloc(sizeof(char) * (exp_func_i + 1));
strcpy(exp_funcs[exp_funcs_i], exp_func); str_reverse(exp_funcs[exp_funcs_i]); exp_funcs_i++; memset(exp_func, 0, 100); exp_func_i = 0;
break; } }
int count_kuohao = -1; char *sub_str_ptr = ptr + 1; memset(exp_str, 0, 100);
do { if(*sub_str_ptr == ')') { count_kuohao++; } else if(*sub_str_ptr == '(') { count_kuohao--; }
if(count_kuohao != 0) { exp_str[exp_str_i++] = *sub_str_ptr; } else { exp_str[exp_str_i] = '\0'; exp_strs[exp_strs_i] = malloc(sizeof(char) * (exp_str_i + 1));
strcpy(exp_strs[exp_strs_i], exp_str); exp_strs_i++; memset(exp_str, 0, 100); exp_str_i = 0; break; }
}while(*sub_str_ptr++); } }
for (int i = 0; i < exp_strs_i; i++) { char *temp_result_str = double2str(get_math_fun(exp_funcs[i])(str2double(exp_strs[i]))); exp_results[i] = malloc(sizeof(char) * (strlen(temp_result_str) + 1)); strcpy(exp_results[i], temp_result_str); }
exp_strStuctTypedef exp_strStuct; exp_strStuct.exp_count = exp_strs_i; exp_strStuct.exp_funcs = exp_funcs; exp_strStuct.exp_strs = exp_strs; exp_strStuct.exp_results = exp_results;
return exp_strStuct; }
|