标准输入函数
在stdio.h中scanf声明如下:
/* Read formatted input from stdin.This function is a possible cancellation point and therefore notmarked with __THROW. */
extern int scanf (const char *__restrict __format, ...) __wur;
使用Mac或Linux的同学,在终端上输入man scanf回车即可学习scanf函数的用法。我们可以看到注释上说明,scanf从标准输入stdin输入读取数据,在glibc中stdin的定义如下:
/*stdio.c*/
FILE *stdin = (FILE *) &_IO_2_1_stdin_;
/*libio.h*/
extern struct _IO_FILE_plus _IO_2_1_stdin_;
/*libioP.h*/
struct _IO_FILE_plus
{FILE file;const struct _IO_jump_t *vtable;
};
从以上代码我们可以知道,最终stdin是一个FILE文件流指针,我能继续追踪FILE类型为何物。
/** stdio state variables.** The following always hold:** if (_flags&(__SLBF|__SWR)) == (__SLBF|__SWR),* _lbfsize is -_bf._size, else _lbfsize is 0* if _flags&__SRD, _w is 0* if _flags&__SWR, _r is 0** This ensures that the getc and putc macros (or inline functions) never* try to write or read from a file that is in `read' or `write' mode.* (Moreover, they can, and do, automatically switch from read mode to* write mode, and back, on "r+" and "w+" files.)** _lbfsize is used only to make the inline line-buffered output stream* code as compact as possible.** _ub, _up, and _ur are used when ungetc() pushes back more characters* than fit in the current _bf, or when ungetc() pushes back a character* that does not match the previous one in _bf. When this happens,* _ub._base becomes non-nil (i.e., a stream has ungetc() data iff* _ub._base!=NULL) and _up and _ur save the current values of _p and _r.** NB: see WARNING above before changing the layout of this structure!*/
typedef struct __sFILE {unsigned char *_p; /* current position in (some) buffer */int _r; /* read space left for getc() */int _w; /* write space left for putc() */short _flags; /* flags, below; this FILE is free if 0 */short _file; /* fileno, if Unix descriptor, else -1 */struct __sbuf _bf; /* the buffer (at least 1 byte, if !NULL) */int _lbfsize; /* 0 or -_bf._size, for inline putc *//* operations */void *_cookie; /* cookie passed to io functions */int (* _Nullable _close)(void *);int (* _Nullable _read) (void *, char *, int);fpos_t (* _Nullable _seek) (void *, fpos_t, int);int (* _Nullable _write)(void *, const char *, int);/* separate buffer for long sequences of ungetc() */struct __sbuf _ub; /* ungetc buffer */struct __sFILEX *_extra; /* additions to FILE to not break ABI */int _ur; /* saved _r when _r is counting ungetc data *//* tricks to meet minimum requirements even when malloc() fails */unsigned char _ubuf[3]; /* guarantee an ungetc() buffer */unsigned char _nbuf[1]; /* guarantee a getc() buffer *//* separate buffer for fgetln() when line crosses buffer boundary */struct __sbuf _lb; /* buffer for fgetln() *//* Unix stdio files get aligned to block boundaries on fseek() */int _blksize; /* stat.st_blksize (may be != _bf._size) */fpos_t _offset; /* current lseek offset (see WARNING) */
} FILE;
看到这个结构体内部一大堆成员变量不要慌,我们重点关注里面的close、read、seek和write函数指针。我们在调用scanf函数时正是通过这几个函数指针间接调用系统函数close、read、seek和write实现标准输入关闭、读取、偏移和写功能。
int (* _Nullable _close)(void *);
int (* _Nullable _read) (void *, char *, int);
fpos_t (* _Nullable _seek) (void *, fpos_t, int);
int (* _Nullable _write)(void *, const char *, int);
从函数声明我们知道scanf返回一个int型返回值,在调用时scanf,返回正整数表示从标准输入读取到的有效数据数量,返回0表示没有输入或者输入不正确,返回负数表示发生了从标准输入读取数据发生了错误。下面我们使用scanf从标准输入读取数据的代码。
int num = 0;
float f_num = 0;int count = scanf("%d", &num);
scanf("%f", &f_num);scanf_s("%d", &num);
在scanf中输入数据并将数据保存在变量num和f_num中,调用scanf输入数据必须要用%,%d表示输入一个整数,%f表示输入一个单精度浮点数,count保存scanf输入数据的有效数。
在C语言里函数传参方式有2种,一种是传值另外一种是传指针。通过传值方式形参拷贝实参,得到一个实参副本对实参副本进行修改不会影响实参,而传指针方式,将会得到实参的地址,通过指针解引用可以间接修改实参的值。那么回到scanf函数那里,我们通过对变量进行取址,scanf函数内部有一个指针,将变量地址值赋给内部指针,再将标准输入的值赋值给实参,实参变量因此获得标准输入的值。
标准输出函数
在stdio.h中printf函数声明如下:
/* Write formatted output to stdout.This function is a possible cancellation point and therefore notmarked with __THROW. */
extern int printf (const char *__restrict __format, ...);
看到这里是不是很熟悉?printf函数的返回值也是int型,调用printf函数将会返回输出字符个数,出错则返回一个负数。同样在Linux/Mac平台的终端上输入man printf函数可以查看函数的详细使用方法(任何C标准函数都可以在Linux/Mac平台上输入man+函数名的方式查看函数使用方法)。下面是我们使用printf函数在标准输出中输出数据的代码。
int output_count = printf("num = %d\n", num);
printf("output_count = %d\n", output_count);output_count = printf("f_num = %f\n", f_num);
printf("output_count = %d\n", output_count);
在代码片段里我们看到一个\n字符,在C语言里这是一个换行符。看到这里是不是又有疑问了,为什么printf函数输出变量值时不需要对变量取地址?这就回到前面我们说过的问题了,在C语言里传值,形参是实参的副本,形参修改了不会影响到实参。而printf函数只是在标准输出中输出信息,不会修改实参的值,因此使用传值方式。
那么标准输出是什么呢?从print函数声明代码注释上看,标准输出正是stdou,我们继续在glibc中继续追踪stdout到底是什么?在stdout.c中我们看到stdout和stderr定义如下:
FILE *stdout = (FILE *) &_IO_2_1_stdout_;
FILE *stderr = (FILE *) &_IO_2_1_stderr_;
我们发现stdout、stderr和stdin的定义一模一样都是一个FILE类型指针,那么使用方式就和stdin一样了,区别则在于stdin和文件描述符0绑定,stdout和文件描述符1绑定,stderr和文件描述符2绑定。
声明:
本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。