接上一篇C语言中可变参数函数实现原理,从理论上详细介绍了C语言中可变参数函数的实现,这一篇从minix内核源码中的scanf函数入手,学习C语言经典可变参数函数的实现过程
在scanf.c文件中,可以看到scanf函数,代码如下:
#include <stdio.h> #include <stdarg.h> #include "loc_incl.h" int scanf(const char *format, ...) { va_list ap; int retval; va_start(ap, format); retval = _doscan(stdin, format, ap); va_end(ap); return retval; }
对于va_list、va_start、va_end等在stdarg.h头文件中定义的宏,都已经在(stdarg.h头文件源代码分析)一文中介绍过。
在上述代码中我们可以看到有一个_doscan函数,而这一函数在头文件loc_incl.h中定义,函数声明如下:
int _doscan(FILE * stream, const char *format, va_list ap);
_doscan函数的实现源代码如下:
int _doscan(register FILE *stream, const char *format, va_list ap) { int done = 0; /* number of items done */ int nrchars = 0; /* number of characters read */ int conv = 0; /* # of conversions */ int base; /* conversion base */ unsigned long val; /* an integer value */ register char *str; /* temporary pointer */ char *tmp_string; /* ditto */ unsigned width = 0; /* width of field */ int flags; /* some flags */ int reverse; /* reverse the checking in [...] */ int kind; register int ic = EOF; /* the input character */ #ifndef NOFLOAT long double ld_val; #endif if (!*format) return 0; while (1) { if (isspace(*format)) { while (isspace(*format)) format++; /* skip whitespace */ ic = getc(stream); nrchars++; while (isspace (ic)) { ic = getc(stream); nrchars++; } if (ic != EOF) ungetc(ic,stream); nrchars--; } if (!*format) break; /* end of format */ if (*format != '%') { ic = getc(stream); nrchars++; if (ic != *format++) break; /* error */ continue; } format++; if (*format == '%') { ic = getc(stream); nrchars++; if (ic == '%') { format++; continue; } else break; } flags = 0; if (*format == '*') { format++; flags |= FL_NOASSIGN; } if (isdigit (*format)) { flags |= FL_WIDTHSPEC; for (width = 0; isdigit (*format);) width = width * 10 + *format++ - '0'; } switch (*format) { case 'h': flags |= FL_SHORT; format++; break; case 'l': flags |= FL_LONG; format++; break; case 'L': flags |= FL_LONGDOUBLE; format++; break; } kind = *format; if ((kind != 'c') && (kind != '[') && (kind != 'n')) { do { ic = getc(stream); nrchars++; } while (isspace(ic)); if (ic == EOF) break; /* outer while */ } else if (kind != 'n') { /* %c or %[ */ ic = getc(stream); if (ic == EOF) break; /* outer while */ nrchars++; } switch (kind) { default: /* not recognized, like %q */ return conv || (ic != EOF) ? done : EOF; break; case 'n': if (!(flags & FL_NOASSIGN)) { /* silly, though */ if (flags & FL_SHORT) *va_arg(ap, short *) = (short) nrchars; else if (flags & FL_LONG) *va_arg(ap, long *) = (long) nrchars; else *va_arg(ap, int *) = (int) nrchars; } break; case 'p': /* pointer */ set_pointer(flags); /* fallthrough */ case 'b': /* binary */ case 'd': /* decimal */ case 'i': /* general integer */ case 'o': /* octal */ case 'u': /* unsigned */ case 'x': /* hexadecimal */ case 'X': /* ditto */ if (!(flags & FL_WIDTHSPEC) || width > NUMLEN) width = NUMLEN; if (!width) return done; str = o_collect(ic, stream, kind, width, &base); if (str < inp_buf || (str == inp_buf && (*str == '-' || *str == '+'))) return done; /* * Although the length of the number is str-inp_buf+1 * we don't add the 1 since we counted it already */ nrchars += str - inp_buf; if (!(flags & FL_NOASSIGN)) { if (kind == 'd' || kind == 'i') val = strtol(inp_buf, &tmp_string, base); else val = strtoul(inp_buf, &tmp_string, base); if (flags & FL_LONG) *va_arg(ap, unsigned long *) = (unsigned long) val; else if (flags & FL_SHORT) *va_arg(ap, unsigned short *) = (unsigned short) val; else *va_arg(ap, unsigned *) = (unsigned) val; } break; case 'c': if (!(flags & FL_WIDTHSPEC)) width = 1; if (!(flags & FL_NOASSIGN)) str = va_arg(ap, char *); if (!width) return done; while (width && ic != EOF) { if (!(flags & FL_NOASSIGN)) *str++ = (char) ic; if (--width) { ic = getc(stream); nrchars++; } } if (width) { if (ic != EOF) ungetc(ic,stream); nrchars--; } break; case 's': if (!(flags & FL_WIDTHSPEC)) width = 0xffff; if (!(flags & FL_NOASSIGN)) str = va_arg(ap, char *); if (!width) return done; while (width && ic != EOF && !isspace(ic)) { if (!(flags & FL_NOASSIGN)) *str++ = (char) ic; if (--width) { ic = getc(stream); nrchars++; } } /* terminate the string */ if (!(flags & FL_NOASSIGN)) *str = '\0'; if (width) { if (ic != EOF) ungetc(ic,stream); nrchars--; } break; case '[': if (!(flags & FL_WIDTHSPEC)) width = 0xffff; if (!width) return done; if ( *++format == '^' ) { reverse = 1; format++; } else reverse = 0; for (str = Xtable; str < &Xtable[NR_CHARS] ; str++) *str = 0; if (*format == ']') Xtable[*format++] = 1; while (*format && *format != ']') { Xtable[*format++] = 1; if (*format == '-') { format++; if (*format && *format != ']' && *(format) >= *(format -2)) { int c; for( c = *(format -2) + 1 ; c <= *format ; c++) Xtable[c] = 1; format++; } else Xtable['-'] = 1; } } if (!*format) return done; if (!(Xtable[ic] ^ reverse)) { /* MAT 8/9/96 no match must return character */ ungetc(ic, stream); return done; } if (!(flags & FL_NOASSIGN)) str = va_arg(ap, char *); do { if (!(flags & FL_NOASSIGN)) *str++ = (char) ic; if (--width) { ic = getc(stream); nrchars++; } } while (width && ic != EOF && (Xtable[ic] ^ reverse)); if (width) { if (ic != EOF) ungetc(ic, stream); nrchars--; } if (!(flags & FL_NOASSIGN)) { /* terminate string */ *str = '\0'; } break; #ifndef NOFLOAT case 'e': case 'E': case 'f': case 'g': case 'G': if (!(flags & FL_WIDTHSPEC) || width > NUMLEN) width = NUMLEN; if (!width) return done; str = f_collect(ic, stream, width); if (str < inp_buf || (str == inp_buf && (*str == '-' || *str == '+'))) return done; /* * Although the length of the number is str-inp_buf+1 * we don't add the 1 since we counted it already */ nrchars += str - inp_buf; if (!(flags & FL_NOASSIGN)) { ld_val = strtod(inp_buf, &tmp_string); if (flags & FL_LONGDOUBLE) *va_arg(ap, long double *) = (long double) ld_val; else if (flags & FL_LONG) *va_arg(ap, double *) = (double) ld_val; else *va_arg(ap, float *) = (float) ld_val; } break; #endif } /* end switch */ conv++; if (!(flags & FL_NOASSIGN) && kind != 'n') done++; format++; } return conv || (ic != EOF) ? done : EOF; } _doscan函数代码
在上面的源代码中,值得注意的是第26行的getc宏,定义代码如下:
#define getc(p) (--(p)->_count >= 0 ? (int) (*(p)->_ptr++) : \ __fillbuf(p))
getc的调用形式:ch=getc(fp); 功能是从文件指针指向的文件读入一个字符,并把它作为函数值返回给int型变量ch。
第4行~第17行,定义一些后面需要用到的变量
第23行~第34行,跳过format格式串中的空格,并且跳过输入流中的空格
第37行~第42行,输入流stream与format格式串中的空白符(空白符可以是空格(space)、制表符(tab)和新行符(newline))保持一致
第44行~第52行,在format中的字符为'%'的前提下,stream中的字符也为'%',则继续
第54行~第57行,format当前字符为'*',表示读指定类型的数据但不保存
第58行~第62行,指定说明最大域宽。 在百分号(%)与格式码之间的整数用于限制从对应域读入的最大字符数于宽度
第64行~第282行,switch语句,用于格式修饰符,这些修饰符包括: h、l、L、c、p、b、d、i、o、u……,还有基于扫描集的'['修饰符
-----------------------------------------------------------------------------------
对scanf函数的源码分析,需要在scanf函数的语法格式详细的理解基础上进行,由于scanf函数实现十分复杂,需要仔细的品味,这里只是比较初步的分析,具体还有待后期不断的完善
相关推荐
摘 要:本文从scanf 函数使用的多个方面(格式字符及其附加格式说明字符、输入数据的格式、格式说明的分隔符、输入项的使用、格式说明与输入项的对应)介绍scanf函数使用的方法及其注意事项,以使读者正确、有效地...
C语言中scanf函数是进行数据输入的基础,它来源于C标准库函数,具备强大的输入功能,但在实际使用时若不当心,可能会导致错误或非预期结果。VC++6.0环境下的例子将用于说明在使用scanf函数时常见的问题及其解决方法...
当执行到scanf函数时,程序就暂停等待用户输入,该函数只接受变量的地址,格式为&变量名。是一个阻塞式的函数,2用户输入完毕后,则将值赋值给变量,至此函数调用完毕。敲回车键告知计算机键入完毕。 (二)使用注意...
scanf函数中的“格式控制”后面应当是变量地址。例如:scanf("%d%d%d", &a, &b, &c);如果写成scanf("%d%d%d", a, b, c);就会出现错误。 b. 如果在“格式控制”字符串中除了格式说明以外还有其他字符,则在输人数据...
在C语言编程中,`scanf`函数是标准输入库中的一个关键函数,用于从标准输入设备(通常是键盘)读取数据。它具有广泛的应用,包括读取整数、浮点数、字符、字符串等。本篇文章将深入探讨`scanf`函数的详细用法,以及...
Linux运维-嵌入式物联网开发教程-C语言的scanf函数.mp4
C语言scanf函数用法研究 本文主要探讨了C语言中scanf函数的用法,着重指出scanf函数在使用过程中的易错点,并通过例子展示了scanf函数在不同情况下的使用注意事项。 一、 scanf函数的基本用法 scanf函数的基本...
"浅析C语言中scanf()函数的用法" 本文主要介绍了C语言中的scanf()函数的用法,分析了scanf()函数的机制、原型、格式控制字符串、空白符的作用、非空白符的作用、scanf()函数的运行机制等方面。同时,通过三个程序的...
得一个 3×3 的矩阵转置,用一函数实现之。在主函数中用 scanf 函数输入矩阵元素
"C语言scanf函数应用问题" C语言中的scanf函数是最常用的输入函数,但如果使用不当,就会出现错误或得不到预想的结果。因此,了解scanf函数的使用规则和注意事项非常重要。 一、格式说明符和输入项的三对应(类型...
C语言中scanf()函数对程序健壮性不良影响的解决办法
此外,C语言还支持函数重载(Overload),但与C++不同,C语言中的函数重载不是语言特性,而是通过命名约定来实现的。例如,可以创建多个同名函数,但参数类型或数量不同,以达到类似的效果。 函数的优化技巧包括:...
【C语言中的scanf()函数详解】 在C语言中,`scanf()`函数是一个非常重要的格式化输入函数,用于从标准输入设备(通常是键盘)接收用户输入的数据,并将其转换为指定的数据类型存储到预先定义的变量中。掌握`scanf()...
Python 不像C语言那样内置了`scanf`函数,但在Python中可以使用第三方库来实现类似功能。`scanf`函数在C语言中是一个用于从标准输入读取格式化数据的函数,能够将用户输入的数据按照预设的格式解析并存储到对应的...
- `scanf`函数中的格式控制字符串后面的参数应该是变量的地址。 - 错误示例:`scanf("%d,%d", a, b);` - 正确示例:`scanf("%d,%d", &a, &b);` 2. **格式控制字符串中的其他字符**: - 如果`scanf`的格式控制...
`scanf`函数在C语言中扮演着重要的角色,它是标准输入函数,用于从标准输入(通常是键盘)读取数据,并将其转换为指定的数据类型存储到变量中。然而,`scanf`并非易用之选,其行为有时可能让人困惑。本文将深入探讨`...
如果输入的数据个数多于scanf函数中指定的变量的个数,那么没有被读取的数据将暂留缓冲区,形成所谓的数据“垃圾”。 4. 解决scanf函数使用中出现的问题 为了解决scanf函数使用中出现的问题,可以采取以下方法: ...
通过将相关操作封装在函数中,可以提高代码的复用性和可维护性。例如,创建一个`display_message()`函数: ```c void display_message(char* msg) { printf("%s", msg); } ``` 然后在`main()`函数中调用: ```c int...
1、C语言之scanf()函数简要介绍1(格式化字符篇)案例程序 2、建议配合本专栏相关文章学习 3、适用于C语言初学者 4、本文件下载永久免费
与printf函数相同,C语言也允许在使用scanf函数之前不必包含stdio.h文件。 scanf函数的一般形式为: scanf(“格式控制字符串”, 地址表列); 其中,格式控制字符串的作用与printf函数相同,但不能显示非格式字符串...