01
—
標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出
在C語言里要使用標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出必須包含stdio.h頭文件,常用的標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)輸入函數(shù)是printf和scanf,其中printf用來在標(biāo)準(zhǔn)輸出中輸出信息,而函數(shù)scanf則用來從標(biāo)準(zhǔn)輸入中讀取信息。
那么什么是標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出呢?
在Linux中進程通常會自動打開三個標(biāo)準(zhǔn)文件,即標(biāo)準(zhǔn)輸入文件(stdin)通常對應(yīng)文件描述符0;
標(biāo)準(zhǔn)輸出文件(stdout)對應(yīng)文件描述符1和標(biāo)準(zhǔn)錯誤輸出文件對應(yīng)文件描述符2(stderr)。進程將從標(biāo)準(zhǔn)輸入文件中讀取輸入數(shù)據(jù),將正常輸出數(shù)據(jù)輸出到標(biāo)準(zhǔn)輸出文件,而將錯誤信息送到標(biāo)準(zhǔn)錯誤文件中。02
—
標(biāo)準(zhǔn)輸入函數(shù)
在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的同學(xué),在終端上輸入man scanf回車即可學(xué)習(xí)scanf函數(shù)的用法。我們可以看到注釋上說明,scanf從標(biāo)準(zhǔn)輸入stdin輸入讀取數(shù)據(jù),在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文件流指針,我能繼續(xù)追蹤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;
看到這個結(jié)構(gòu)體內(nèi)部一大堆成員變量不要慌,我們重點關(guān)注里面的close、read、seek和write函數(shù)指針。我們在調(diào)用scanf函數(shù)時正是通過這幾個函數(shù)指針間接調(diào)用系統(tǒng)函數(shù)close、read、seek和write實現(xiàn)標(biāo)準(zhǔn)輸入關(guān)閉、讀取、偏移和寫功能。
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);
從函數(shù)聲明我們知道scanf返回一個int型返回值,在調(diào)用時scanf,返回正整數(shù)表示從標(biāo)準(zhǔn)輸入讀取到的有效數(shù)據(jù)數(shù)量,返回0表示沒有輸入或者輸入不正確,返回負(fù)數(shù)表示發(fā)生了從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù)發(fā)生了錯誤。下面我們使用scanf從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù)的代碼。
int num = 0; float f_num = 0; int count = scanf(“%d”, &num); scanf(“%f”, &f_num); scanf_s(“%d”, &num);
在scanf中輸入數(shù)據(jù)并將數(shù)據(jù)保存在變量num和f_num中,調(diào)用scanf輸入數(shù)據(jù)必須要用%,%d表示輸入一個整數(shù),%f表示輸入一個單精度浮點數(shù),其他數(shù)據(jù)類型的數(shù)據(jù)參考C語言入門基礎(chǔ)之變量和數(shù)據(jù)類型,count保存scanf輸入數(shù)據(jù)的有效數(shù)。
看到這里可能有人會有疑問,為什么調(diào)用scanf從標(biāo)準(zhǔn)輸入信息,需要對變量取地址,為什么要設(shè)計成這樣?這里就要涉及到后面會學(xué)到的知識:指針。在C語言里函數(shù)傳參方式有2種,一種是傳值另外一種是傳指針。通過傳值方式形參拷貝實參,得到一個實參副本對實參副本進行修改不會影響實參,而傳指針方式,將會得到實參的地址,通過指針解引用可以間接修改實參的值。
那么回到scanf函數(shù)那里,我們通過對變量進行取址,scanf函數(shù)內(nèi)部有一個指針,將變量地址值賦給內(nèi)部指針,再將標(biāo)準(zhǔn)輸入的值賦值給實參,實參變量因此獲得標(biāo)準(zhǔn)輸入的值。
在代碼片段我們還看到scanf_s這個函數(shù)(scanf_s不是C標(biāo)準(zhǔn)庫函數(shù)),由于scanf函數(shù)并不是安全的,在有些編輯器上默認(rèn)禁止使用scanf,如果使用則需要打開一個宏,而scanf_s是一些廠商提供的scanf函數(shù)安全版本,兩者使用方法一模一樣。
03
—
標(biāo)準(zhǔn)輸出函數(shù)
在stdio.h中printf函數(shù)聲明如下:
/* 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函數(shù)的返回值也是int型,調(diào)用printf函數(shù)將會返回輸出字符個數(shù),出錯則返回一個負(fù)數(shù)。
同樣在Linux/Mac平臺的終端上輸入man printf函數(shù)可以查看函數(shù)的詳細使用方法(任何C標(biāo)準(zhǔn)函數(shù)都可以在Linux/Mac平臺上輸入man+函數(shù)名的方式查看函數(shù)使用方法)。下面是我們使用printf函數(shù)在標(biāo)準(zhǔn)輸出中輸出數(shù)據(jù)的代碼。
int output_count = printf(“num = %d ”, num); printf(“output_count = %d ”, output_count); output_count = printf(“f_num = %f ”, f_num); printf(“output_count = %d ”, output_count);
在代碼片段里我們看到一個 字符,在C語言里這是一個換行符??吹竭@里是不是又有疑問了,為什么printf函數(shù)輸出變量值時不需要對變量取地址?這就回到前面我們說過的問題了,在C語言里傳值,形參是實參的副本,形參修改了不會影響到實參。而printf函數(shù)只是在標(biāo)準(zhǔn)輸出中輸出信息,不會修改實參的值,因此使用傳值方式。
那么標(biāo)準(zhǔn)輸出是什么呢?從print函數(shù)聲明代碼注釋上看,標(biāo)準(zhǔn)輸出正是stdou,我們繼續(xù)在glibc中繼續(xù)追蹤stdout到底是什么?在stdout.c中我們看到stdout和stderr定義如下:
FILE *stdout = (FILE *) &_IO_2_1_stdout_; FILE *stderr = (FILE *) &_IO_2_1_stderr_;
我們發(fā)現(xiàn)stdout、stderr和stdin的定義一模一樣都是一個FILE類型指針,那么使用方式就和stdin一樣了,區(qū)別則在于stdin和文件描述符0綁定,stdout和文件描述符1綁定,stderr和文件描述符2綁定。
04
—
結(jié)語
后面講解C語言知識時我會穿插有Linux相關(guān)知識,講解C語言不能僅僅停留在語法層面。據(jù)我的觀察,很多人學(xué)習(xí)了C語言語法后很迷茫,不知道C語言能做什么,根本原因就是你沒有了解某個平臺的系統(tǒng)編程API。Linux是一個開源操作系統(tǒng),結(jié)合Linux學(xué)習(xí)C語言將會更加有趣,在Linux上進行C語言開發(fā)絕對是最佳選擇。
編輯:jq
-
Linux
+關(guān)注
關(guān)注
87文章
11329瀏覽量
209969 -
C語言
+關(guān)注
關(guān)注
180文章
7614瀏覽量
137256 -
File
+關(guān)注
關(guān)注
0文章
19瀏覽量
14348 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4344瀏覽量
62810 -
代碼
+關(guān)注
關(guān)注
30文章
4809瀏覽量
68821
原文標(biāo)題:C語言入門基礎(chǔ)之輸入和輸出
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論