一个有意思的小工具:给它一个字符串,比如 "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;
// ... exp、log、asin、atanh 等十几math个
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++; // 遇到闭包 +1
else if (*sub_str_ptr == '(') count_kuohao--; // 遇到开括 -1

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))     /* 在 s 中找到 s1 */
{
memmove(ptr + strlen(s2), ptr + strlen(s1), strlen(ptr) - strlen(s1) + 1);
memcpy(ptr, s2, strlen(s2));
}

写在最后

这个玩具麻雀虽小:函数指针表、括号匹配、memmove 搬移内存,C 语言字符串处理的三板斧都在里面。缺点也很明显——固定 100 字节的缓冲区、魔法数字偏多,算是个”能用但可以重构”的状态。之后可以试试改成递归下降解析器,那就是正经的表达式引擎了。

完整代码

表达式函数查找并计算.c
1
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)) /* 如果在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)
{
// double:8字节 2.3E-308 到 1.7E+308
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) != '/'))
{
// printf("find exp: ");
char *exp_func_ptr = ptr;
while(*exp_func_ptr--)
{
if((*exp_func_ptr != '+') && (*exp_func_ptr != '-') && (*exp_func_ptr != '*') && (*exp_func_ptr != '/'))
{
// putchar(*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)
{
// putchar(*sub_str_ptr);
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;
}