程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 走進C標准庫(3)——"stdio.h"中的getc和ungetc

走進C標准庫(3)——"stdio.h"中的getc和ungetc

編輯:關於C語言

接前文。

再來看看getc和ungetc的實現。在看這兩個函數的實現之前,我們先來想一想這兩個函數分別需要做的工作。

int getc(FILE *stream)

說明:函數getc從stream指向的輸入流中讀取下一個字符(如果有的話),並把它由unsigned char類型轉換為int類型,並且流的相關的文件定位符(如果定義的話)向前移動一位。

返回值:函數getc返回stream指向的輸入流的下一個字符,如果流處於文件結束處,則設置該流的文件結束指示符,函數getc返回EOF。如果發生了讀錯誤,則設置流的錯誤指示符。函數getc返回EOF。

int ungetc( int c , FILE *stream );

說明:

函數ungetc把c指定的字符(轉換為unsigned char類型)退回到stream指向的輸入流中,退回字符是通過對流的後續讀取並按照退回的反順序來返回的。如果中間成功調用(對同一個流)了一個文件定位函數(fseek、fsetpos或者rewind),那麼就會丟棄流的所有退回的字符。流的相應的外部存儲保持不變。

退回的一個字符是受到保護的。如果同一個流調用ungetc函數太多次,中間又沒有讀或者文件定位操作,那麼這種操作可能會失敗。

如果c的值和宏EOF的值相等,則操作失敗且輸入流保持不變。

對ungetc的成功調用會清空流的文件結束符。讀取或者丟棄所有回退的字符後,流的文件定位符的值和這些字符回退之前的值相同。對文本流來說,在對函數ungetc的一次成功調用之後,它的文件定位符的值是不明確的,直到所有回退字符被讀取或者丟棄為止。對二進制流來說,每成功調用一次ungetc之後它的文件定位符都會減1。如果在一次調用之前它的值是零,那麼調用之後它的值是不確定的。

返回值:

函數ungetc返回轉換後的回退字符,如果操作失敗,則返回EOF。

大致來說,getc就是從stream中讀入一個字符,並將流的定位符前移;ungetc就是將流的定位符後移,然後將一個字符回退到stream流該定位符的位置上。

這時我們可能會考慮到一些問題:

1.在stdio.h閱讀筆記1中,我們看到在fopen函數調用之後,並沒有為我們的stream分配buffer,那麼這個分配會在真正進行stream讀取的時候建立嗎?

2.如果在getc之前(即流定位符在_base)就進行ungetc操作,會產生什麼樣的效果呢?

3.如原文所說:“如果同一個流調用ungetc函數太多次,中間又沒有讀或者文件定位操作,那麼這種操作可能會失敗。”,是因為文件定位符到達_base的原因嗎?

那我們就來看下getc和ungetc所做的工作:

首先來看ungetc

**!==
        == EOF) ||
              !->_flag & _IOREAD) ||->_flag & _IORW) && !(stream->_flag &
         (stream->_base ==
         (stream->_ptr == stream-> (stream->
                        ->_ptr++ (stream->_flag &
                 (*--stream->_ptr != (++stream->
                *--stream->_ptr = (->_cnt++->_flag &= ~->_flag |= _IOREAD;       
        ( &

清楚的看到,對於沒有分配buffer的I/O控制塊,進行了緩沖區的分配。另外,當文件定位符指向文件開始時且退回字符數目為0時,文件定位符無需後移

看了下_getbuf函數的實現,從中可以看到FILE類中_charbuf變量的作用。刪除了針對不同各個平台的#if代碼之後,_getbuf函數核心代碼如下:

          FILE *           REG1 FILE *         _ASSERTE(str !=         stream =          (stream->_base =                  stream->_flag |=                 stream->_bufsiz =                           stream->_flag |=                 stream->_base = ( *)&(stream->                 stream->_bufsiz =                  stream->_ptr = stream->                 stream->_cnt =           }

可以看到,當_getbuf分配不成功時,_base指針會指向_charbuf作為一個1 int長度的空間(可能是因為平台的原因,這裡將1int理解為2個char),此時的_buffersize就為2。通過_charbuf的使用,也保證了當buffer分配失敗時,也能正常從文件中讀取數據。

接下來看下getc函數的實現:

 getc(_stream)     (--(_stream)->_cnt >= 0 \
                ?  & *(_stream)->_ptr++ : _filbuf(_stream))

表達式 (--(_stream)->_cnt >= 0 ? 0xff & *(_stream)->_ptr++ : _filbuf(_stream)) 的意義為:

當回退字符的數目大於0的時候,減少一個剩余字符計數,返回0xff & *(_stream)->_ptr++;

否則,調用_filbuf(_stream)。

通過0xff & *(_stream)->_ptr++,getc函數獲得了當前文件定位符指向的內容(只取8位?難道char還能不止8位嗎?),然後文件定位符前移

剩下函數_filbuf(_stream),顯然其中含有C語言從文件中獲取文本的方法(前面都是文本的管理機制):

          FILE *           REG1 FILE *         _ASSERTE(str !=         
         stream =          (!inuse(stream) || stream->_flag &                           (stream->_flag &                 stream->_flag |=                           stream->_flag |=         
          (!          
                 stream->_ptr = stream->         stream->_cnt = _read(_fileno(stream), stream->_base, stream->          ((stream->_cnt == ) || (stream->_cnt == -                      stream->_flag |= stream->_cnt ?                 stream->_cnt =                    
          (  !(stream->_flag & (_IOWRT|_IORW)) &&
               ((_osfile_safe(_fileno(stream)) & (FTEXT|FEOFLAG)) ==
                 (FTEXT|                 stream->_flag |=             
          ( (stream->_bufsiz == _SMALL_BUFSIZ) && (stream->_flag &
               _IOMYBUF) && !(stream->_flag &                  stream->_bufsiz =          stream->_cnt--         ( & *stream->_ptr++ }

在語句stream->_cnt = _read(_fileno(stream), stream->_base, stream->_bufsiz);中通過調用_read函數來將原來在硬盤文件中的數據讀取到程序數據段的棧空間中,具體的功能看下_read的代碼:

                         
 
                    *             bytes_read;                 
          *buffer;                   
          os_read;                    
          *p, *q;                    
          peekchr;                   
         ULONG filepos;                  
         ULONG dosretval;                
 
         
          ( ((unsigned)fh >= (unsigned)_nhandle) ||
              !(_osfile(fh) &              
             errno =             _doserrno = ;              
              -          bytes_read = ;                 
         buffer =          (cnt ==  || (_osfile(fh) &             
                         ((_osfile(fh) & (FPIPE|FDEV)) && _pipech(fh) !=              
             *buffer++ =             ++             --             _pipech(fh) = LF;           
  
         
 
          ( !ReadFile( (HANDLE)_osfhnd(fh), buffer, cnt, (LPDWORD)&                      
 
              ( (dosretval = GetLastError()) ==                 
                 errno =                 _doserrno =                  -                ( dosretval ==                                                   -           bytes_read += os_read;          
          (_osfile(fh) &             
 
             
              ( (os_read != ) && (*( *)buf ==                 _osfile(fh) |=             
                 _osfile(fh) &= ~ 
             
             p = q =              (p < ( *)buf +                  (*p ==                     
                      ( !(_osfile(fh) &                         _osfile(fh) |=                     ;              
                    (*p !=                     *q++ = *p++                                      
                      (p < ( *)buf + bytes_read -                           (*(p+) ==                             p +=                              *q++ = LF;  
                          
                             *q++ = *p++;    
                                                 
                         ++ 
                         dosretval =                           ( !ReadFile( (HANDLE)_osfhnd(fh), &peekchr,                                          (LPDWORD)&                             dosretval =                          (dosretval !=  || os_read ==                              
                             *q++ =                                                                 
                              (_osfile(fh) & (FDEV|                                 
                                  (peekchr ==                                     *q++ =                                                                      *q++ =                                     _pipech(fh) =                                                                
                                  (q == buf && peekchr ==                                      
                                     *q++ =                                                                       
                                     filepos = _lseek_lk(fh, -                                      (peekchr !=                                         *q++ =       
              
             bytes_read = q - ( *           bytes_read;              
 }

通過調用ReadFile的WINAPI函數,就可以完成從磁盤中把文件的內容讀到數據段的棧內了。



通過使用已經分配好的文件句柄,將數據讀到lpBuffer指向的內存區域,讀入的最大數據量通過nNumberOfBytesToRead變量進行設定,通過lpNumberOfBytesRead返回實際讀取的字節數。

_read函數在讀到數據之後,如果設定的模式為文本流,則需要解決文本流中的換行回車的問題,這裡就不深究了。有興趣可以看文章:

http://www.360doc.com/content/11/0113/20/3508740_86319358.shtml

這樣,我們的getc就成功將FILE類的_cnt設定為了剩余字符的數目,將_base指向了通過棧上存儲數據的地址。然後,只要將當前文件位置指針_ptr中的數據返回,_ptr指向下一個位置,_cnt減一就好了。

這時候,我們回過頭來看FILE結構,就可以知道裡面成員變量的意義了。

 typedef       *           *                          * }FILE;

_ptr為數據存儲區域(可為緩沖區或_charbuf對應的存儲區域)當前位置指針。

_cnt為數據存儲區域剩余字符數目。

_base為數據存儲區域首地址。

_flag為當前I/O控制塊所控制的I/O操作類型,有讀、寫等。

_file為對應的文件描述符(詳見博文《走進C標准庫(2)》)

_charbuf為緩沖區申請失敗時的一個2字節的數據存儲區

_bufsiz為緩沖區大小

_tmpfname為所使用存儲一些中間操作數據的臨時文件。

 

到這裡,我們就可以回答開始時的3個問題了。

1.在getc和ungetc中如果沒有分配buffer,都會嘗試給I/O控制塊分配buffer,但是ungetc分配完buffer後不會嘗試從文件中將數據讀到buffer中。

2.可以在buffer的首地址處存儲一個字符。如果_ptr指在_base,那麼數據存儲區域為空(為從文件中讀取數據)時,ungetc才能在首地址處添加一個字符。

3.多次ungetc使_ptr到達_base之後,就無法再進行ungetc了。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved