二十四、輸入輸出流

1. I/O流的概念和流類庫的結構

程序的輸入指的是從輸入文件將數據傳送給程序,程序的輸出指的是從程序將數據傳送給輸出文件。

C++輸入輸出包含以下三個方面的內容:

  • 對系統指定的標準設備的輸入和輸出。即從鍵盤輸入數據,輸出到顯示器屏幕。這種輸入輸出稱為標準的輸入輸出,簡稱標準I/O。
  • 以外存磁盤文件為對象進行輸入和輸出,即從磁盤文件輸入數據,數據輸出到磁盤文件。以外存文件為對象的輸入輸出稱為文件的輸入輸出,簡稱文件I/O。
  • 對內存中指定的空間進行輸入和輸出。通常指定一個字符數組作為存儲空間(實際上可以利用該空間存儲任何信息)。這種輸入和輸出稱為字符串輸入輸出,簡稱串I/O。

2.C++的I/O對C的發展

類型安全和可擴展性

在C語言中,用prinF和scanf進行輸入輸出,往往不能保證所輸入輸出的數據是可靠的安全的。在C++的輸入輸出中,編譯系統對數據類型進行嚴格的檢查,凡是類型不正確的數據都不可能通過編譯。因此C++的I/O操作是類型安全(type safe)的。C++的I/O操作是可擴展的,不僅可以用來輸入輸出標準類型的數據,也可以用于用戶自定義類型的數據。

C++通過I/O類庫來實現豐富的I/O功能。這樣使C++的輸人輸出明顯地優于C語言中的prinF和scanf,但是也為之付出了代價,C++的I/O系統變得比較復雜,要掌握許多細節。

C++編譯系統提供了用于輸入輸出的iostream類庫。iostream這個單詞是由3個部分組成的,即i--‐o--‐stream,意為輸入輸出流。在iostream類庫中包含許多用于輸入輸出的類。常用的見表

1.png
2.png

ios是抽象基類,由它派生出istream類和ostream類,兩個類名中第1個字母i和o分別代表輸入(input)和輸出(output)。istream類支持輸入操作,ostream類支持輸出操作,iostream類支持輸入輸出操作。iostream類是從istream類和ostream類通過多重繼承而派生的類。其繼承層次見上圖表示。

C++對文件的輸入輸出需要用ifstrcam和ofstream類,兩個類名中第1個字母i和o分別代表輸入和輸出,第2個字母f代表文件(file)。ifstream支持對文件的輸入操作,ofstream支持對文件的輸出操作。類ifstream繼承了類istream,類ofstream繼承了類ostream,類fstream繼承了類iostream。見圖

3.png

I/O類庫中還有其他一些類,但是對于一般用戶來說,以上這些已能滿足需要了。

與iostream類庫有關的頭文件

iostream類庫中不同的類的聲明被放在不同的頭件中,用戶在自己的程序中用#include命令包含了有關的頭文件就相當于在本程序中聲明了所需 要用到的類。可以換 —種說法:頭文件是程序與類庫的接口,iostream類庫的接口分別由不同的頭文件來實現。常用的有

  • iostream 包含了對輸入輸出流進行操作所需的基本信息。
  • fstream 用于用戶管理的文件的I/O操作。
  • strstream 用于字符串流I/O。
  • stdiostream 用于混合使用C和C + +的I/O機制時,例如想將C程序轉變為C++程序。
  • iomanip 在使用格式化I/O時應包含此頭文件。

在iostream頭文件中定義的流對象

在 iostream 頭文件中定義的類有 ios,istream,ostream,iostream,istream _withassign,ostream_withassign,iostream_withassign 等。

在iostream頭文件中重載運算符

“<<”和“>>”本來在C++中是被定義為左位移運算符和右位移運算符的,由于在iostream頭文件中對它們進行了重載,使它們能用作標準類型數據的輸入和輸出運算符。所以,在用它們的程序中必須用#include命令把iostream包含到程序中。

#include <iostream>
1)>>a表示將數據放入a對象中。
2)<<a表示將a對象中存儲的數據拿出。

3.標準I/O流

標準I/O對象:cin,cout,cerr,clog

cout流對象

cout是console output的縮寫,意為在控制臺(終端顯示器)的輸出。強調幾點。

  1. cout不是C++預定義的關鍵字,它是ostream流類的對象,在iostream中定義。 顧名思義,流是流動的數據,cout流是流向顯示器的數據。cout流中的數據是用流插入運算符“<<”順序加入的。如果有
    cout<<"I "<<"study C++ "<<"very hard. << “wang bao ming ";
    按順序將字符串"I ", "study C++ ", "very hard."插人到cout流中,cout就將它們送到顯示器,在顯示器上輸出字符串"I study C++ very hard."。cout流是容納數據的載體,它并不是一個運算符。人們關心的是cout流中的內容,也就是向顯示器輸出什么。

  2. 用“ccmt<<”輸出基本類型的數據時,可以不必考慮數據是什么類型,系統會判斷數據的類型,并根據其類型選擇調用與之匹配的運算符重 載函數。這個過程都是自動的,用戶不必干預。如果在C語言中用prinf函數輸出不同類型的數據,必須分別指定相應的輸出格式符,十分麻煩,而且容易出 錯。C++的I/O機制對用戶來說,顯然是方便而安全的。

  3. cout流在內存中對應開辟了一個緩沖區,用來存放流中的數據,當向cout流插 人一個endl時,不論緩沖區是否已滿,都立即輸出流中所有數據,然后插入一個換行符, 并刷新流(清空緩沖區)。注意如果插人一個換行符”\n“(如cout<<a<<"\n"),則只輸出和換行,而不刷新cout 流(但并不是所有編譯系統都體現出這一區別)。

  4. 在iostream中只對"<<"和">>"運算符用于標準類型數據的輸入輸出進行了重載,但未對用戶聲明的類型數據的輸入輸出 進行重載。如果用戶聲明了新的類型,并希望用”<<"和">>"運算符對其進行輸入輸出,按照重運算符重載來做。

cerr流對象

cerr流對象是標準錯誤流,cerr流已被指定為與顯示器關聯。cerr的 作用是向標準錯誤設備(standard error device)輸出有關出錯信息。cerr與標準輸出流cout的作用和用法差不多。但有一點不同:cout流通常是傳送到顯示器輸出,但也可以被重定向 輸出到磁盤文件,而cerr流中的信息只能在顯示器輸出。當調試程序時,往往不希望程序運行時的出錯信息被送到其他文件,而要求在顯示器上及時輸出,這時應該用cerr。cerr流中的信息是用戶根據需要指定的。

clog流對象

clog流對象也是標準錯誤流,它是console log的縮寫。它的作用和cerr相同,都是在終端顯示器上顯示出錯信息。區別:cerr是不經過緩沖區,直接向顯示器上輸出有關信息,而clog中的信息存放在緩沖區中,緩沖區滿后或遇endl時向顯示器輸出。

4.png

3.1標準的輸入流

標準輸入流對象cin,重點掌握的函數

cin.get() //一次只能讀取一個字符
cin.get(一個參數) //讀一個字符
cin.get(三個參數) //可以讀字符串
cin.getline()
cin.ignore()
cin.putback()

#include <iostream>
using namespace std;

/*
cin.get() //一次只能讀取一個字符
cin.get(一個參數) //讀一個字符
cin.get(三個參數) //可以讀字符串
cin.getline()
cin.ignore()
cin.putback()
*/

//cin的operator >> 操作符 
void test1()
{
    int myInt;
    long myLong;
    char buf[128] = {0};

    cin>>myInt;
    cin>>myLong;//根據回車刷新緩沖區
    cin>>buf;   //根據空格來隔離每個變量的內容   所以怎么輸入空格???

    
    cout<<"myInt:"<<myInt<<endl;
    cout<<"myLong:"<<myLong<<endl;
    cout<<"buf:"<<buf<<endl;
}

//cin.get()方法
void test2{
   char ch;

  //cin.get()如果讀到的不是EOF標識符,那么會永遠的阻塞等待
   while((ch = cin.get()) != EOF){//從鍵盤來講Ctrl+Z代表EOF標識符
      cout<<ch<<endl;//輸入a 回車后,變成
                     /*
                      a       //為什么2個a,第一個a是輸入顯示,第二個a是輸出
                      a
                              //為什么有兩個回車 ?因為緩沖區內是a和回車一起輸出來的,第二個是endl
      
                      b
                      b

                     */
   }
}


void test3(){
    char a,b,c;

    char buf[10] = {0};

    cout<<"從輸入緩沖區去讀取數據,如果緩沖區沒有數據,就阻塞"<<endl;
    //輸入了123
    cin.get(a);//從輸入緩沖區去讀取數據,如果有就給a
    cin.get(b);
    cin.get(c);
    //等價于 cin.get(a).get(b).get(c);
    cout<<"a = "<<a<<endl;//1
    cout<<"b = "<<b<<endl;//2
    cout<<"c = "<<c<<endl;//3


    //輸入abc efg   或1234567890987654
    cin.get(buf,10,' ');//往buf寫10個字節,遇到空格停止
    cout<<buf<<endl;//abc  或1234567890
}

//cin.getline() 讀一行
void test4()
{
   char buf[128] = {0};
   cout<<"請輸入一個字符串aa bb cc dd"<<endl;
   cin.getline(buf,128);//從輸入緩沖區中讀數據到buf中,最多讀128,直到遇到\n
   //cin>>buf;//讀不了空格的

   cout<<buf<<endl;//aa bb cc dd
}

//cin.ignore()
void test5()
{
    char buf1[128];
    char buf2[128];

    cout<<"請輸入一個字符串 aa  bb  cc  dd"<<endl;
    cin>>buf1;    //aa
    cin.ignore(2);  //表示忽略頭2個字符,這里是不要bb前面的2個空格
    cin.getline(buf2,128);//  bb  cc  dd 緩沖區被讀了!

    cout<<"buf1:"<<buf1<<endl;//aa
    cout<<"buf2:"<<buf2<<endl;// bb cc dd
}

//cin.putback()
void test6()
{
   cout<<"請輸入一個數字或者字符串"<<endl;

   char ch;
   ch = cin.get();//從輸入緩沖區去讀一個字符
   if (ch >= '0' && ch <= '9')
   {
      int num;
      //此時數字第一個字符已經讀出來了,需要將ch放回到緩沖區
      cin.putback(ch);//將ch扔回緩沖區,位置就是緩沖區的頭部
      cin>>num;//把緩沖區的拿過來

      cout<<"num = "<<num<<endl;
   }//用處就是用戶輸入一堆數字或字符串,我拿出來第一個看看,判斷一下,如123,num也是123,就單純判斷第一個
   else{
    cout<<"用戶輸入的是一個字符串"<<endl;
    char buf[128] = {0};
    cin.putback(ch);//不扔回去,肯定少了個東西
    cin.getline(buf,128);

    cout<<"buf:"<<buf<<endl;
   }

}


int main(void) 
{

   //test1();
   //test2();
   //test3();
   //test4();
   //test5();
   test6();

   return 0;
}

3.2標準的輸出流

標準輸出流對象cout
cout.flush()
cout.put()
cout.write()
cout.width()
cout.fill()
cout.setf(標記)

操作符、控制符
flush
endl
oct
dec
hex
setbase
setw
setfill
setprecision

void test1()
{
    cout<<"hello"<<endl;
    cout.put('h');//只打一個字符
    cout.put('e');
    cout.put('l');

    //等價于cout.put('h').put('e').put('l')

    char *str = "hello world";
    cout.write(str,strlen(str));//hello world
    cout<<endl;
    cout.write(str,strlen(str) - 1);//hello worl
}

void test2()
{
    cout<<"<start>";
    cout.width(30);//設置接下來要輸出的長度,是30
    cout.fill('*');//把沒有填充的內容,用'*'來填充
    cout.setf(ios::showbase);//#include <iomanip>
    cout.setf(ios::internal);//設置   查表!!
    cout<<hex<<123<<"<End>\n";
    //輸出:<start>0x**********************7b<End> 
    //**28個,我肯定數錯!
    cout<<endl;
    cout<<endl;

    //使?操作符、控制符========上下等價! 表自己查
     
    cout<<"<Start>"
        <<setw(30)
        <<setfill('*')
        <<setiosflags(ios::showbase)//基數
        <<setiosflags(ios::internal)
        <<hex
        <<123
        <<"<End>\n"
        <<endl;
}

int main(void) 
{

   //test1();
   test2();

   return 0;
}

3.3輸出格式化

在輸出數據時,為簡便起見,往往不指定輸出的格式,由系統根據數據的類型采取默認的格式,但有時希望數據按指定的格式輸出,如要求以十六進制或八進制形式輸出一個整數,對輸出的小數只保留兩位小數等。有兩種方法可以達到此目的。
1)使用控制符的方法;
2)使用流對象的有關成員函數。分別敘述如下。

使用控制符的方法

#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
  int a;
  cout<<"input a:";
  cin>>a;
  cout<<"dec:"<<dec<<a<<endl;//以十進制形式輸出整數
  cout<<"hex:"<<hex<<a<<endl;//以十六進制形式輸出整數a
  cout<<"oct:"<<setbase(8)<<a<<endl;//以八進制形式輸出整數a
  const char *pt="China"; //pt指向字符串"China"
  cout<<setw(10)<<pt<<endl;//指定域寬為,輸出字符串
  cout<<setfill('*')<<setw(10)<<pt<<endl;//指定域寬,輸出字符串,空白處以'*'填>充
  double pi=22.0/7.0; //計算pi值

 //按指數形式輸出,8位小數
  cout<<setiosflags(ios::scientific)<<setprecision(8);
  cout<<"pi="<<pi<<endl;//輸出pi值
  cout<<"pi="<<setprecision(4)<<pi<<endl;//改為位小數
  cout<<"pi="<<setiosflags(ios::fixed)<<pi<<endl;//改為小數形式輸出
  return 0;
}

結果:
input
a:16
dec:16
hex:10
oct:20
China
*****China
pi=3.14285714e+00
pi=3.1429e+00
pi=0x1.9249249249249p+1

人們在輸入輸出時有一些特殊的要求,如在輸出實數時規定字段寬度,只保留兩位小數,數據向左或向右對齊等。C++提供了在輸入輸出流中使用的控制符(有的書中稱為操縱符)

1.png
舉例, 輸出雙精度數:
double a=123.456789012345; // 對a賦初值
1) cout<<a; 輸出: 123.456
2) cout<<setprecision(9)<<a; 輸出: 123.456789
3) cout<<setprecision(6); 恢復默認格式(精度為6)
4) cout<< setiosflags(ios∷fixed); 輸出: 123.456789
5) cout<<setiosflags(ios∷fixed)<<setprecision(8)<<a; 輸出:
123.45678901
6) cout<<setiosflags(ios∷scientific)<<a; 輸出: 1.234568e+02
7) cout<<setiosflags(ios∷scientific)<<setprecision(4)<<a; 輸出:1.2346e02

下面是整數輸出的例子:
int b=123456; // 對b賦初值
1) cout<<b; 輸出: 123456
2) cout<<hex<<b; 輸出: 1e240
3) cout<<setiosflags(ios∷uppercase)<<b; 輸出: 1E240
4) cout<<setw(10)<<b<<','<<b; 輸出: 123456,123456
5) cout<<setfill('*')<<setw(10)<<b; 輸出: **** 123456
6) cout<<setiosflags(ios∷showpos)<<b; 輸出: +123456

例如:各行小數點對齊。
int main( )
{
   double a=123.456,b=3.14159,c=-3214.67;
   cout<<setiosflags(ios::fixed<<setiosflags(ios::right)<<setprecision(2);
   cout<<setw(10)<<a<<endl;
[圖片上傳中...(2.png-fa90ef-1528905916866-0)]
   cout<<setw(10)<<b<<endl;
   cout<<setw(10)<<c<<endl;
   system("pause");
   return 0;
}
輸出如下:
123.46(字段寬度為10,右對齊,取兩位小數)
3.14
-‐3214.67

先統一設置定點形式輸出、取兩位小數、右對齊
這些設置對其后的輸出均有效(除非重新設置)
而setw只對其后一個輸出項有效
因此必須在輸出a,b,c之前都要寫setw(10)

用流對象的成員函數控制輸出格式
除了可以用控制符來控制輸出格式外,還可以通過調用流對象cout中用于控制輸出格式的成員函數來控制輸出格式。用于控制輸出格式的常用的成員函數如下:

2.png

流成員函數setf和控制符setiosflags括號中的參數表示格式狀態,它是通過格式標志來指定的。格式標志在類ios中被定義為枚舉值。因此在引用這些格式標志時要在前面加上類名ios和域運算符“::”。

3.png

4.對程序的幾點說明

  1. 成員函數width(n)和控制符setw(n)只對其后的第一個輸出項有效。如:cout. width(6);
cout <<20 <<3.14<<endl;
輸出結果為     203.14

在輸出第一個輸出項20時,域寬為6,因此在20前面有4個空格,在輸出3.14時,width(6)已不起作用,此時按系統默認的域寬輸出(按數據實際長度輸出)。如果要求在輸出數據時都按指定的同一域寬n輸出,不能只調用一次width(n),而必須在輸出每一項前都調用一次width(n),上面的程序中就是這樣做的。

  1. 在表3中的輸出格式狀態分為5組,每一組中同時只能選用一種(例如dec、hex和oct中只能選一,它們是互相排斥的)。在用成員函數setf和控制符setiosflags設置輸出格式狀態后,如果想改設置為同組的另一狀態,應當調用成員函unsetf(對應于成員函數self)或resetiosflags(對應于控制符setiosflags),先終止原來設置的狀態。然后再設置其他狀態,大家可以從本程序中看到這點。程序在開始雖然沒有用成員函數self和控制符setiosflags設置用dec輸出格式狀態,但系統默認指定為dec,因此要改變為hex或oct,也應當先
    用unsetf函數終止原來設置。如果刪去程序中的第7行和第10行,雖然在第8行和第11行中用成員函數setf設置了hex和oct格式,由于未終止dec格式,因此hex和oct的設置均不起作用,系統依然以十進制形式輸出。同理,程序倒數第8行的unsetf函數的調用也是不可缺少的。

  2. 用setf函數設置格式狀態時,可以包含兩個或多個格式標志,由于這些格式標志在ios類中被定義為枚舉值,每一個格式標志以一個二進位代表,因此可以用位或運算符“|”組合多個格式標志。如倒數第5、第6行可以用下面一行代替:

cout.setf(ios::internal | ios::showpos); //包含兩個狀態標志,用"|"組合

可以看到:對輸出格式的控制,既可以用控制符,也可以用cout流的有關成員函數,二者的作用是相同的。控制符是在頭文件iomanip中定義的,因此用控制符時,必須包含iomanip頭文件。cout流的成員函數是在頭文件iostream中定義的,因此只需包含頭文件iostream,不必包含iomanip。許多程序人員感到使用控制符方便簡單,可以在一個cout輸出語句中連續使用多種控制符。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容