C语言入门基础之输入和输出

标准输入和标准输出

在C语言里要使用标准输入和标准输出必须包含stdio.h头文件,常用的标准输出和标准输入函数是printf和scanf,其中printf用来在标准输出中输出信息,而函数scanf则用来从标准输入中读取信息。那么什么是标准输入和标准输出呢?在Linux中进程通常会自动打开三个标准文件,即标准输入文件(stdin)通常对应文件描述符0;标准输出文件(stdout)对应文件描述符1和标准错误输出文件对应文件描述符2(stderr)。进程将从标准输入文件中读取输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。

标准输入函数

在stdio.h中scanf声明如下:

/* Read formatted input from stdin.   This function is a possible cancellation point and therefore not   marked 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表示输入一个单精度浮点数,其他数据类型的数据参考C语言入门基础之变量和数据类型,count保存scanf输入数据的有效数。看到这里可能有人会有疑问,为什么调用scanf从标准输入信息,需要对变量取地址,为什么要设计成这样?这里就要涉及到后面会学到的知识:指针。
在C语言里函数传参方式有2种,一种是传值另外一种是传指针。通过传值方式形参拷贝实参,得到一个实参副本对实参副本进行修改不会影响实参,而传指针方式,将会得到实参的地址,通过指针解引用可以间接修改实参的值。那么回到scanf函数那里,我们通过对变量进行取址,scanf函数内部有一个指针,将变量地址值赋给内部指针,再将标准输入的值赋值给实参,实参变量因此获得标准输入的值。

在代码片段我们还看到scanf_s这个函数(scanf_s不是C标准库函数),由于scanf函数并不是安全的,在有些编辑器上默认禁止使用scanf,如果使用则需要打开一个宏,而scanf_s是一些厂商提供的scanf函数安全版本,两者使用方法一模一样。

标准输出函数

在stdio.h中printf函数声明如下:

/* Write formatted output to stdout.   This function is a possible cancellation point and therefore not   marked 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绑定。

结语

后面讲解C语言知识时我会穿插有Linux相关知识,讲解C语言不能仅仅停留在语法层面。据我的观察,很多人学习了C语言语法后很迷茫,不知道C语言能做什么,根本原因就是你没有了解某个平台的系统编程API。Linux是一个开源操作系统,结合Linux学习C语言将会更加有趣,在Linux上进行C语言开发绝对是最佳选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容