小技巧,大智慧:%n of sscanf
scanf 系列中有个函数 sscanf,可能有人用过,它的普通用法,我就不讲了,可以参考这里:man 3 sscanf
gnu c 实现了 C 标准的 format specify 的 %n,它的含义是返回从该次 XXscanf 调用开始到此读了多少个字节,我们可以利用这一点,来实现不需要内存分配的%s:
假定我们读取一批商品记录,每条记录包含商品ID,商品名称,商品价格,各字段的类型在代码中是自包含的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
size_t len1 = 0; ssize_t len2 = 0; char* line = NULL; while ((len2 = getline(&line, &len1, fp) > 0) { int beg = -1; // name offset begin in line int end = -1; // name offset end in line int id; const char* name; double price; int fields = sscanf(line, "%d %n%*s%n %lf", &id, &beg, &end, &price); if (2 == fields) { // 'beg' and 'end' are not counted in return value int slen = end - beg; name = line + beg; name[slen] = 0; // .... } else { fprintf(stderr, "bad recordn"); } } if (NULL != line) free(line); |
%*s 用来扫描但跳过(不存储)一个字符串,两个 %n,前一个得到name的起始偏移,后一个得到name的结束偏移……其它的,也就不用我多说了。
%n 应该是 C99 新加入的,不过这一点我不太确信,但 gnu 里面的确有这个实现,msvc 里面没有,其他的环境就不太清楚了。如果是写服务器应用,一般就不用担心它是否实现(95%以上的服务器都该有gnu环境吧,况且 glibc 和gcc是相对独立的)。
%n 特性真可谓小技巧,大智慧,我很长一段时间不知道这个东西,还一直为 XXprintf 系列可以得到已写出的字节数,而 XXscanf 无法得到已读取的字节数犯愁。
我觉得在新代码中应该使用该技巧取代 %s,当然,gnu 的 XXscanf 里面还有个 %as,它返回一个由系统 malloc 出来的字符串,需要用户自己调用 free 来释放它。如果是写纯 C 代码,并且字符串需要保存起来长期使用,应该使用 %as,否则应该使用我提到的这个技巧。