17.1.1流和緩衝區
C++程序把輸入和輸出看作字節流(若干字節組成的字符序列)。輸入時,程序從輸入流中抽取字節;輸出時,程序將字節插入到輸出流中。
輸入流中的字節可能來自鍵盤以及存儲設備或其他程序
輸出流中的字節可以流向屏幕、打印機、存儲設備或其他程序
流充當了程序和流源或流目標之間的橋樑,打個比方說,水滴就是字節,水滴匯成的水流就是流,我們可以從水流中取水,也可以往水流中加水,也就是從流中提取輸出的內容,插入輸入的內容。
緩衝區:用作中介的內存塊,它是將信息從設備傳輸到程序或從程序傳輸到設備的臨時存儲工具,可以提高數據的讀取速率
具體原理:內存是放進程運行時的程序和數據的,CPU訪問內存比訪問硬盤速度快,每次從磁盤文件中讀取一個字符,要消耗大量的時間,緩衝方法是從硬盤上讀取大量信息,將信息存在緩衝區裏,因爲CPU訪問內存所用時間比訪問硬盤少,所以這種方法更快。
輸出時,程序首先填滿緩衝區,然後把整塊的數據傳輸給硬盤,並清空緩衝區,這被稱爲刷新緩衝區
程序在什麼時候刷新緩衝區?
用戶按下換行符
輸入的到來時(當程序到達輸入語句時,將刷新緩衝區中當前所有輸出)
17.1.2流、緩衝區和iostream文件
爲了管理流,C++提供了一系列頭文件,它們的關係如下
streambuf類爲緩衝區提供了內存,並提供了填充緩衝區、訪問緩衝區、刷新緩衝區和管理緩衝區內存的方法
ios_base類表示流的一般特徵,如是否可讀取,是二進制流還是文本流
ostream類提供了輸出方法
istream類提供了輸入方法
iostream類提供了輸入和輸出方法
C++爲了能夠處理需要16位國際字符集或更寬的字符類型,在傳統的8位char(“窄”)類型的基礎上添加了wchar_t(“寬”)類型,這也意味着有專門的輸入輸出對象用於處理寬字符如wcin wcout(這個地方有興趣的可以區查一下兩種類型的區別)
iostream頭文件會自動生成八個流對象(四個是寬類型的,這裏只列出窄類型的)
cin
對應於標準輸入流,平時就是用cin輸入各種數據類型,也可以通過重載使其輸入用戶自定義的類
cout
對應於標準輸出流,就是用cout輸出各種數據類型,也可以通過重載使其輸出用戶自定義的類
cerr
對應着標準錯誤流,可用於顯式錯誤信息,默認情況下,這個流被關聯到標準輸出設備(顯示器),這個流沒有被緩衝,這意味着信息將直接發送給屏幕
clog
對應着標準錯誤流,可用於顯式錯誤信息,默認情況下,這個流被關聯到標準輸出設備(顯示器),與cerr不同的是,clog有緩衝區
17.2用cout進行輸出
C++將輸出看作字符流,這說明如果要在屏幕上顯示數字-2.34,需要五個字符,-、2、.、3、4。ostream類的主要任務就是將數值類型轉換爲文本形式表示的字符流。
C++通過重載<<運算符使之能夠識別c++所有的基本類型
就是所cout可以用於輸出C++的各種基本類型
1.輸出和指針
C++用指向字符串存儲位置的指針來表示字符串,所以下列cout語句都可顯示字符串
char *p = "123";
char name[20] = "1234";
cout<<"hello";
cout<<name;
cout<<p;
如何輸出指針地址呢
可以用void*將char*轉化爲其他類型來輸出其地址
如cout<<(void *)p;
這樣將會輸出p指向的地址
2.拼接輸出
可以通過cout<<a<<b<<c;這種方式輸出
原因是 cout<<a返回一個cout的對象,可以繼續輸出
17.2.2其他ostream方法
cout.put()輸出一個字符,相當於putchar()
cout.write(char*,x)輸出指定字符串的指定數量的字符,注意如果x超出了指定字符串的長度,它仍然會輸出,即輸出垃圾值
舉個例子
cout.put('a');
//輸出字符a
cout.put("1234",2);
//輸出12
17.2.3刷新輸出緩衝區
每次等緩衝區滿了再輸出太麻煩了,我們可以使用endl或者flush來刷新緩衝區
具體語法如下
cout<<"132"<<endl;
或者
cout<<"132"<<flush;(<<重載,會將其替換爲flush(cout))
17.2.4用cout進行格式化
ostream插入運算符將值轉換爲文本格式。在默認情況下,格式化值的方式如下。
* 對於char值,如果它代表的是可打印字符,則將被作爲一個字符顯示在寬度爲一個字符的字段中。
* 對於數值整型,將以十進制方式顯示在一個剛好容納該數字及負號(如果有的話)的字段中;
* 字符串被顯示在寬度等於該字符串長度的字段中。
浮點數的默認行爲有變化。下面詳細說明了老式實現和新式實現之間的區別。
*新式:浮點類型被顯示爲6位,末尾的0不顯示(注意,顯示的數字位數與數字被存儲時精度設置沒有任何關係)。數字以定點表示法顯示還是科學計數法表示,取決於它的值。具體來說,當指數大於等於6或小於等於-5時,將使用科學計數法表示。另外,字段寬度恰好容納數字和負號(如果有的話)。默認的行爲對應於帶%g說明符的標準C庫函數fprintf()。
*老式:浮點類型顯示爲帶6位小數,末尾的0不顯示(注意,顯示的數字位數與數字被存儲時的精度沒有任何關係)。數字以定點表示法顯示還是以科學計數法表示,取決於他的值。另外,字段寬度恰好容納數字和負號(如果有的話)。
1.輸出十進制 八進制 十六進制
要控制整數以十進制、十六進制還是八進制顯示,可以使用dec、hex和oct控制符。
例如,下面的函數調用將cout對象的計數系統格式狀態設置爲十六進制:
hex(cout);設置輸出爲十六進制
oct(cout);設置輸出爲八進制
dec(cout);設置輸出爲十進制
由於stream類重載了<<,所以可以直接使用cout<<hex;來設置輸出爲十六進制
#include <iostream>
using namespace std;
int main()
{
int a = 20;
cout<<hex<<a<<endl;
cout<<oct<<a<<endl;
cout<<dec<<a<<endl;
return 0;
}
/*輸出爲
14
24
20
*/
注意設置完輸出16進制之後,要想輸出10進制需要設置回來。
2.調整字段寬度
可以使用width成員函數將長度不同的數字放到寬度相同的字段中,該方法的原型爲:
int width();
int width(int i);
第一種格式返回字段寬度的當前設置;第二種格式將字段寬度設置爲i個空格,並返回以前的字段寬度值。這使得能夠保存以前的值,以便以後恢復寬度值時使用。
width()方法之影響顯示的下一個項目,然後字段寬度將恢復爲默認值。由於width()是成員函數,因此必須使用對象來調用它。
#include <iostream>
using namespace std;
int main()
{
int a = 20;
cout.width(5);
cout<<a<<a<<endl;
return 0;
}
/*輸出
20
*/
3.填充字符
在默認情況下,cout使用空格填充字段中未被使用的部分,可以使用fill()成員函數來改變填充字符。例如,下面的函數調用將填充字符改爲星號:
cout.fill('*');
#include <iostream>
using namespace std;
int main()
{
int a = 20;
cout.width(5);
cout.fill('*');
cout<<a<<a<<endl;
return 0;
}
/*輸出
***20
*/
fill填充字符會一直有效,直到修改它爲止
4.設置浮點數的顯示精度
浮點數精度的含義取決於輸出模式。在默認情況下,它指的是顯示的總位數。在定點模式和科學模式下,精度指的是小數點後面的位數。已經知道,C++的默認精度爲6位(但末尾的0將不顯示)。precision成員函數使得能夠選擇其他值。例如,下面的函數調用將cout的精度設置爲2:
cout.precision(2);
和width()的情況不同,但與fill()相似,新的精度設置將一直有效,直到被重新設置。下面的程序說明了這一點:
#include <iostream>
using namespace std;
int main()
{
cout.precision(2);
double a = 1.28;
double b = 0.222;
double c = 11.2;
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
return 0;
}
/*輸出結果
1.3
0.23
11
*/
這說明precision會進行四捨五入
5.打印末尾的0和小數點
對於有些輸出,希望保留末尾的0,iostream系列類沒有提供專門用於完成這項任務的函數,但ios_base類提供了一個setf()函數(用於set標記),能夠控制多種格式化特性。這個類還定義了多個常量,可以作爲函數的參數。例如,下面的函數調用使cout顯示末尾的小數點:
cout.setf(ios_base::showpoint);
使用默認的浮點格式時,上述語句還將導致末尾的0被顯示出來。
注意cout.setf(ios_base::showpoint);要結合precision使用
showpoint是ios_base類聲明中定義的類級靜態常量。類級意味着如果在成員函數定義的外面使用它,則必須在常量名前加上作用域運算符(::)。因此,ios_base::showpoint指的是在ios_base類中定義的一個常量。
#include <iostream>
using namespace std;
int main()
{
cout.setf(ios_base::showpoint);
double a = 1.28;
double b = 0.225;
double c = 11.2;
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
return 0;
}
/*輸出
1.28000
0.225000
11.2000
*/
頭文件iomanip
iomanip提供了很多函數來設置cout,如
setprecision()設置精度
setfill()填充字段
setw()設置字段寬度
例子:
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
double a = 1.222222;
cout<<setw(10)<<a<<endl;
cout<<setfill('*')<<a<<endl;
cout<<setw(10)<<setfill('*')<<a<<endl;
cout<<setprecision(2)<<a<<endl;
return 0;
}
/*輸出結果
1.22222
1.22222
***1.22222
1.2
*/
17.3使用cin進行輸入
cin支持C++的各種基本類型 int char double 等等
cin可以連續輸入數據 如cin>>a>>b>>c
cin可以標註輸入的數字是什麼進制 如 cin>>hex>>a 輸入的數字是一個十六進制
17.3.1cin>>如何檢查輸入
cin將跳過空白(空格、換行符和製表符),直到遇到非空白字符
int main()
{
char a[10];
cin>>a;
cout<<a;
return 0;
}
例如,如果是字符串
輸入 123
輸出爲123
cin遇到換行會停止讀入,並把換行符留在緩衝區
int main()
{
char a[10];
cin>>a;
char c = cin.get();
cout<<(int)c;
return 0;
}
/*輸入123
輸出爲
10 10是換行的ascii碼值
cin將讀取從非空白字符開始,到與目標類型不匹配的第一個字符之間的全部內容,並把剩餘沒有讀入的留在緩衝區
int main()
{
int a;
cin>>a;
char c = cin.get();
cout<<a<<endl;
cout<<c;
return 0;
}
/*輸入123z
輸出
123
z
*/
運算符將讀取1、2、3,因爲它們是整數的部分,但是不會讀取z,所以z會留在緩衝區裏,下一個cin從這裏開始讀,與此同時,運算符將字符序列123轉換爲一個整數值 賦給a
17.3.2流狀態
1設置狀態
cin或cout對象包含一個描述流狀態的數據成員,流狀態由三個ios_base元素組成:eofbit,failbit,badbit。每一個都有0和1兩個值
1是表明發生了這個問題,都將停止cin的輸入,並且只有在clear()方法來回調之後,纔可以繼續使用cin輸入數據
eofbit = 1 說明到達文件末尾
failbit = 1 說明cin操作未能讀取到預期的字符或者 I/O失敗(試圖讀取不可訪問的文件或試圖寫入受保護的磁盤)
badbit = 1 發生了其他錯誤
clear(eofbit)將會使eofbit爲1
2.I/O異常
3.流狀態的影響
只有在流狀態良好的情況(即使eofbit failbit badbit均爲0)cin才能進行輸入
int main()
{
int a;
while(cin>>a)
{
;
}
if(cin.eof())
cout<<"到達文件末尾"<<endl;
return 0;
}
使用ctril+z來告訴程序到達了文件末尾,從而使eofbit = 0
如果發生的是fail錯誤,不僅要使用clear()來使failbit =0,還要清空緩存區
#include <iostream>
using namespace std;
int main()
{
int a,c;
while(cin>>a)
{
;
}
cin.clear();
while(!isspace(c = cin.get()))讀取緩存區裏的字符,一直到讀取到空字符
cout<<(char)c<<endl;
/*
while((ch = cin.get())!='\n')讀取緩存區裏的字符,一直到讀取到換行符
cout<<(char)c<<endl;
*/
cin>>a;
cout<<a;
return 0;
}
/*輸入
123z
123
輸出
z
123
*/
17.3.3其他istream類方法