輸入/輸出流
C++中,提供了兩套輸入/輸出方法
與C語言相兼容的輸入/輸出函數,如
printf
和scanf
函數輸入/輸出流庫ios:可實現類對象數據流的操作
流類和流對象
在C++中,輸入/輸出是由”流”來處理的
流:數據從一個位置到另一個位置的流動抽象爲流
輸入流:當數據從鍵盤或磁盤文件流入到程序中時,這樣的流成爲輸入流
輸出流:當數據從程序中流向屏幕或磁盤文件時,這樣的流成爲輸出流
當流被建立後,可以使用一些特性的操作從流中獲取數據或向流中添加數據.從流中獲取數據成爲提取操作,向流中添加數據成爲插入操作
- 上圖中,
ios
類是用來提供一些關於對流狀態進行設置的功能,它是一個虛基類,其他類都是由這個類派生的 streamfuf
不是ios
類的派生類在類ios
中只有一個指針成員,指向streambuf
類的一個對象.streambuf
類是用來爲ios類及其派生類提供對數據的緩衝支持- 緩衝:指系統中在主存開闢一個專用的區域來臨時存放輸入/輸出信息,這個區域成爲緩衝區
- 刷新:只有當緩衝區滿了或當前送入的數據爲新的一行時,系統纔對流中的數據進行處理,成爲刷新.
istream
和ostream
類均是ios
類的公有派生類,前者提供了向流中插入數據的有關操作,後者提供了從流中提取數據的有關操作.iostream
類是istream
和ostream
類公有派生的,僅僅是將istream
和ostream
類綜合在一起,提供方便- C++提供了4個預定義的標準流對象(爲了方便用戶對基本輸入/輸出流進行操作):
cin
,cout
,cerr
和clog
cin
:用於處理標準輸入,即鍵盤輸入cout
:用於處理標準輸出,即屏幕輸出cerr
和clog
:用來處理標準出錯信息,並將信息顯示在屏幕上.
- 標準流通常使用插入運算符”<<”和提取運算符”>>”來進行輸入/輸出操作,而且系統還會自動地完成數據類型的轉換.
流的格式控制和錯誤處理
C++標準的輸入/輸出流提供了兩種格式的控制模式:
使用
ios
類的相關成員函數,如width
,precision
和fill
等可以直接使用的格式操作算子,如
oct
,hex
和dec
等
1.使用格式控制成員函數
在ios
類中控制輸入/輸出的成員函數有:
函數 | 作用 |
---|---|
int ios::width(); | 返回當前的寬度設置 |
int ios::width(int); | 設置寬度並返回上一次的設置 |
int ios::precision(); | 返回當前的精度設置 |
int ios::precision(int); | 設置精度並返回上一次的設置 |
char ios::fill(); | 返回當前空位填充的字符 |
char ios::fill(char); | 設置空位填充的字符並返回上一次的設置 |
long ios::setf(long); | 設置狀態標誌並返回上一次的狀態標誌 |
long ios::unsetf(long); | 消除狀態標誌並返回上一次的狀態標誌 |
long ios::flags(); | 返回當前的狀態標誌 |
long ios::flags(long); | 設置狀態標誌並返回上一次的狀態標誌 |
* 狀態標誌:各個狀態值之間通過|
組合而成,在ios
類中是一個公共的枚舉類型,各個標誌代表的含義如下:
標誌 | 含義 |
---|---|
ios::skipws | 跳過輸入中的空白符 |
ios::left | 輸出數據按輸入域左對齊 |
ios::right | 輸出數據按輸入域右對齊(默認) |
ios::internal | 數據的符號左對齊,數據本身右對齊,符號和數據之間爲填充字符 |
ios::dec | 轉換爲十進制形式 |
ios::oct | 轉換爲八進制形式 |
ios::hex | 轉換爲十六進制形式 |
ios::showbase | 輸出的數值前面帶有基數符號(0或0x) |
ios::showpoint | 顯示浮點數的小數點和後面的0 |
ios::showpos | 顯示證書前面的”+”號 |
ios::uppercase | 用大寫字母(A~F)輸出十六進制數 |
ios::scientific | 按科學計數法顯示浮點數 |
ios::fixed | 用定點格式顯示浮點數 |
ios::unitbuf | 輸出操作完成後立即刷新緩衝區 |
ios::stdio | 每次輸入操作完成後刷新stdout和stderr |
#include <iostream>
using namespace std;
int main()
{
int nNum = 12345;
double dNum = 12345.6789;
char *str[] = {"This","is","a Test"};
// 設置標誌:八進制,顯示基,正號
cout.setf(ios::oct|ios::showbase|ios::showpos);
cout << nNum << "\t" << dNum << endl;
// 設置十六進制,科學計數法,大寫標誌
cout.setf(ios::hex|ios::scientific|ios::uppercase);
cout << nNum << "\t" << dNum << endl;
// 設置填充符號爲*
cout.fill('*');
for(int i=0;i<3;i++)
{
cout.width(12);
cout << str[i] << " ";
}
cout << endl;
// 設置標誌:左對齊
cout.setf(ios::left);
for(int i=0;i<3;i++)
{
cout.width(12);
cout << str[i] << " ";
}
cout << endl;
return 0;
}
/**
+12345 +12345.7
+12345 +1.234568E+04
********This **********is ******a Test
This******** is********** a Test******
*/
2.使用格式算子
- 格式算子是一個對象,可以直接用插入符或提取符來操作.C++提供的預定義格式的算子如下:
格式算子 | 功能 | 輸入(I)/輸出(O) |
---|---|---|
dec | 設置爲十進制 | I/O |
hex | 設置爲十六進制 | I/O |
oct | 設置爲八進制 | I/O |
ws | 提取空白字符 | I |
endl | 插入一個換行符 | O |
ends | 插入一個空字符 | O |
flush | 刷新輸出流 | O |
setbase(int) | 設置轉換基數,參數值可以是0,8,16和10,0代表默認基數 | I/O |
resetiosflags(long) | 取消指定的標誌 | I/O |
setiosflags(long) | 設置指定的標誌 | I/O |
setfill(int) | 設置填充字符 | O |
setprecision(int) | 設置浮點數的精度 | O |
setw(int) | 設置輸出寬度 | O |
* 注意,從resetiosflags
一直到後面的格式算子,需要包含頭文件–iomanip
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int nNum = 12345;
double dNum = 12345.6789;
char *str[] = {"This","is","a Test"};
// 設置標誌:八進制,顯示基,正號
cout << setiosflags(ios::oct|ios::showbase|ios::showpos);
cout << nNum << "\t" << dNum << endl;
// 設置十六進制,科學計數法,大寫標誌
cout<< setiosflags(ios::hex|ios::scientific|ios::uppercase);
cout << nNum << "\t" << dNum << endl;
// 設置填充符號爲*
cout << setfill('*');
for(int i=0;i<3;i++)
cout << setw(12) << str[i] << " ";
cout << endl;
// 設置標誌:左對齊
cout << setiosflags(ios::left);
for(int i=0;i<3;i++)
cout << setw(12) << str[i] << " ";
cout << endl;
return 0;
}
/**
+12345 +12345.7
+12345 +1.234568E+04
********This **********is ******a Test
This******** is********** a Test******
*/
3.流的錯誤處理
在輸入/輸出過程中,一旦發現操作錯誤,C++流就會將發生的錯誤記錄下來.用戶可以使用C++提供的錯誤檢查功能,檢測和查明錯誤發生的原因和性質,然後調用
clear
函數清除錯誤狀態,使流能夠恢復處理在
ios
類中,定義了一個公有枚舉成員io_state
來記錄各種錯誤的性質:
enum io_state
{
goodbit = 0x00; // 正常
eofbit = 0x01; // 已達到文件尾
failbit = 0x02; // 操作失敗
badit = 0x03; // 非法操作
};
ios
類中又定義了檢測上述流狀態的成員函數,如下:
函數 | 含義 |
---|---|
int ios::rdstate(); | 返回當前的流狀態,它等於io_state的枚舉值 |
int ios::bad(); | 如果badbit位被置1,返回非0 |
void ios::clear(int); | 清楚錯誤狀態 |
int ios::eof(); | 返回非0表示提取操作已達文件尾 |
int ios::fail(); | 如果failbit位被置1,返回非0 |
int ios::good(); | 操作正常,返回非0 |
// 檢測流的錯誤
#include <iostream>
using namespace std;
int main()
{
int i,s;
char buf[80];
cout << "輸入一個整數: ";
cin >> i;
s = cin.rdstate();
cout << "流的狀態爲: " << hex << s << endl;
while(s)
{
// cin有緩衝流,必須先清空
cin.clear();
cin.getline(buf,80);
cout << "非法輸入,重新輸入一個整數: ";
cin >> i;
s = cin.rdstate();
}
return 0;
}
使用輸入/輸出成員函數
1.輸入操作的成員函數
- 數據的輸入/輸出可以分爲三大類:字符類,字符串和數據
1.使用get
和getline
函數
get
函數:用於輸入字符或字符串.函數原型:
int get();//從輸入流中提取一個字符,並轉換成整型數值
istream& get(char& rch);//從輸入流中提取字符到`rch`
istream& get(char* pch,int nCount,char delim = '\n');// 從輸入流中提取一個字符串並由pch返回,nCount指定提取字符的最多個數,delim指定結束字符
getline
函數的函數原型:
istream& getline(char* pch,int nCount,char delim = '\n');
// 用來從輸入流中提取一個輸入行,並把提取的字符串由pch返回
// get和getline的使用
#include <iostream>
using namespace std;
int main()
{
char s1[80], s2[80], s3[80];
cout << "請輸入一個字符: ";
cout << cin.get() << endl;
cin.get(); // 提取換行符
cout << "請輸入一行字符串: ";
for (int i = 0; i < 80; i++)
{
cin.get(s1[i]);
if (s1[i] == '\n')
{
s1[i] = '\0';
break;
}
}
cout << s1 << endl;
cout << "請輸入一行字符串: ";
cin.get(s2, 80);
cout << s2 << endl;
cin.get(); // 提取換行符
cout << "請輸入一行字符串: ";
cin.getline(s3, 80);
cout << s3 << endl;
system("pause");
return 0;
}
/*
請輸入一個字符: A
65
請輸入一行字符串: This is a test!
This is a test!
請輸入一行字符串: Computer
Computer
請輸入一行字符串: 你今天過得好嗎?
你今天過得好嗎?
*/
- 注意:
get
函數遇到換行符就會結束提取,換行符仍然在緩衝區中,而getline
函數則會連換行符都提取.
2.使用read
函數
read
函數不僅可以讀取字符或字符串(稱爲文本流),而且可以讀取字節流.函數原型如下:
// 以下幾種形式都是從輸入流中讀取由nCount指定數目的字節並將它們放在由pch或puch或psch指定的數組中
istream& read(char* pch,int nCount);
istream& read(unsigned char* puch,int nCount);
istream& read(signed char* psch,int nCount);
// read函數的使用
#include <iostream>
using namespace std;
int main()
{
char data[80];
cout << "請輸入: " << endl;
cin.read(data, 80);
data[cin.gcount()] = '\0';
cout << endl << data << endl;
system("pause");
return 0;
}
/*
請輸入:
12345
ABCDE
This is a test!
^Z
12345
ABCDE
This is a test!
*/
^Z
表示按下[Ctrl+Z]組合鍵,表示數據輸入提前結束,gcount
函數是istream
類的一個成員函數,用來返回上一次提取的字符個數.當使用read
函數讀取數據時,不會因爲換行符而結束讀取,因此它的讀取多行的字符串
2.輸出操作的成員函數
ostream
類中用於輸出單個字符或字節的成員函數是put
和write
.它們的原型如下:
ostream& put(char ch);
ostream& write(const char* pch,int nCount);
ostream& write(const unsigned char* puch,int nCount);
ostream& write(const signed char* psch,int nCount);
提取和插入運算符重載
- C++中允許用戶重載”
>>
“和”<<
“運算符,以便用戶利用標準的輸入/輸出流來輸入/輸出自己定的數據類型(包括類),實現對象的輸入/輸出.重載時,最好將重載聲明爲類的友元函數,以便訪問類中的私有成員.
#include <iostream>
using namespace std;
class CStudent
{
public:
friend ostream& operator << (ostream& os, CStudent& stu);
friend istream& operator >> (istream& is, CStudent& stu);
private:
char strName[10]; // 姓名
char strID[10]; // 學號
float fScore[3]; // 三門成績
};
ostream& operator << (ostream& os, CStudent& stu)
{
os << endl << "學生信息如下: " << endl;
os << "姓名: " << stu.strName << endl;
os << "學號: " << stu.strID << endl;
os << "成績: " << stu.fScore[0] << ",\t" << stu.fScore[1] << ",\t" << stu.fScore[2] << endl;
return os;
}
istream& operator >> (istream& is, CStudent& stu)
{
cout << "請輸入學生信息" << endl;
cout << "姓名: ";
is >> stu.strName;
cout << "學號: ";
is >> stu.strID;
cout << "三門成績:";
is >> stu.fScore[0] >> stu.fScore[1] >> stu.fScore[2];
return is;
}
int main()
{
CStudent one;
cin >> one;
cout << one;
system("pause");
return 0;
}
/*請輸入學生信息
姓名: LiMing
學號: 123456
三門成績:80 90 75
學生信息如下:
姓名: LiMing
學號: 123456
成績: 80, 90, 75
*/
經過重載後的提取和插入運算符,實現了對象的直接輸入和輸出.
文件流及其處理
1.文件流概述
- C++將文件看成由連續的字符(字節)的數據順序組成的.
- 根據文件中數據的組織方式,可分爲 文本文件(ASCII文件) 和 二進制文件
- 文本文件:文件中的每一個字節用以存放一個字節的ASCII碼值
- 二進制文件:將數據以二進制存放在文件中,它保持裏數據在內存中存放的原有格式
- 無論是文本文件還是二進制文件,都需要用文本指針來操縱.一個文件指針總是和一個文件所關聯的.當文件每一次打開時,文件指針指向文件的開始,隨着文件的處理,文件指針不斷地在文件中移動,並一直指向最新處理的字符(字節)位置.
- 文件處理有兩種方式
- 文件的順序處理:從文件的第一個字符(字節)開始順序處理到文件的最後一個字符(字節),文件指針也相應地從文件的開始位置到文件的結尾.這種方式,文件稱爲順序文件
- 文件的隨機處理:在文件中通過C++相關的函數移動文件指針,並指向所要處理的字符(字節)位置.這種方式,文件稱爲隨機文件
- C++提供的文件操作的文件流庫,體系結構如下圖:
ifstream
類是從istream
類公有派生而來的,用來支持從輸入文件中提取數據的各種操作ofstream
類是從ostream
類公有派生而來的,用來實現把數據寫入文件中的各種操作fstream
類是從iostream
類公有派生而來的,用來提供從文件中提取數據或把數據寫入文件的各種操作filebuf
類是從streambuf
類派生而來的,用來**管理磁盤文件的緩衝區,應用程序一般不涉及該類- 上述類的成員函數在進行文件操作時,需要包括頭文件—
fstream
.文件操作一般是按打開文件,讀寫文件,關閉文件這三個步驟進行的.
2.順序文件操作
- 在C++打開或創建一個指定的文件需要下列兩個步驟:
1.聲明一個ifstream
,ofstream
或fstream
類對象,如
ifstream infile; // 聲明一個輸入(讀)文件流對象
ofstream outfile; // 聲明一個輸出(寫)文件流對象
fstream iofile; // 聲明一個可讀可寫的文件流對象
2.使用文件流類的成員函數打開或創建一個指定的文件,使得該文件與聲明的文件流對象聯繫起來,這樣,對流對象的操作也是對文件的操作.如:
infile.open("file1.txt");
outfile.open("file2.txt");
iofile.open("file3.txt",ios::in|ios::out);
3.以上兩部可合爲一步進行,即在聲明對象時指定文件名,如:
ifstream infile("file1.txt");
ofstream outfile("file2.txt");
fstream iofile("file3.txt",ios::in|ios::out);
- 在
ifstream
,ofstream
或fstream
類構造函數中,總有一種原型和它的成員函數open
功能相同,它們的函數原型如下:
ifstream(const char* szName,int nMode = ios::in,int nProt = filebuf::openprot);
void ifstream::open(const char* szName,int nMode = ios::in,int nProt = filebuf::openprot);
ofstreawm(const char* szName,int nMode = ios::out,int nProt = filebuf::openprot);
void ofstream::open(const char* szName,int nMode= ios::out,int nProt = filebuf::openprot);
fstream(const char* szName,int nMode,int nProt = filebuf::openprot);
void fstream::open(const char* szName,int nMode,int nProt = filebuf::openprot);
其中,szName
用來指定要打開的文件名,包括路徑和擴展名.Mode
指定文件的訪問方式,Prot
指定文件的共享方式,默認是filebuf::openprot
,表示DOS兼容的方式
* 文件訪問方式
方式 | 含義 |
---|---|
ios::app | 打開一個文件使新的內容始終添加在文件的末尾 |
ios::ate | 打開一個文件使新的內容添加在文件的末尾,但下一次添加時,卻在當前位置處進行 |
ios::in | 爲輸入(讀)打開一個文件,若文件存在,不清除文件原有內容 |
ios::out | 爲輸出(寫)打開一個文件 |
ios::trunc | 若文件存在,清除文件原有內容 |
ios::nocreate | 打開一個已有的文件,若文件不存在,則打開失敗 |
ios::noreplace | 若打開的文件已經存在,則打開失敗 |
ios::binary | 二進制文件方式(默認是文本文件方式) |
* 注意:
* nMode
指定文件的訪問方式通過"|"
運算組合而成
* ios::trunc
方式通常與ios::out
,ios::ate
,ios::app
和ios::in
進行組合
* ios::binary
是二進制文件方式,通常組合如下
ios::in | ios::binary 表示打開一個只讀的二進制文件
ios::out | ios::binary 表示打開一個可寫的二進制文件
ios::in | ios::out | ios::binary 表示打開一個可讀可寫的二進制文件
- 在文件使用結束後,要及時調用
close
函數關閉 - 文件打開後,就可以對文件進行讀寫操作
- 讀:
get
,getline
,read
函數以及提取符”>>
” - 寫:
put
,write
函數以及插入符”<<
”
- 讀:
// 將文件內容保存在另一個文件中,並把內容顯示在屏幕上
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream file1; // 定義一個fstream類的對象用於讀
file1.open("Ex_DataFile.txt",ios::in);
if(!file1)
{
cout << "Ex_DataFile.txt不能打開!\n";
return -1;
}
fstream file2; // 定義一個fstream類的對象用於寫
file2.open("Ex_DataFileBak.txt",ios::out | ios::trunc);
if(!file2)
{
cout << "Ex_DataFileBak不能被創建!\n";
file1.close();
return -2;
}
char ch;
while(!file1.eof())
{
file1.read(&ch,1);
cout << ch;
file2.write(&ch,1);
}
file2.close();
file1.close();
return 0;
}
3.隨機文件操作
- 隨機文件提供在文件中來回移動文件指針和非順序地讀寫文件的能力,這樣讀寫磁盤文件某一數據以前無需讀寫其前面的數據,從而能快速地檢索,修改和刪除文件中的信息
- C++順序文件和隨機文件間的差異不是物理的,這兩種文件都是以順序字符流的方式將信息寫在磁盤等介質上,其區別僅僅在於文件的訪問和更新的方法.以隨機的方式訪問文件時,文件中的信息在邏輯上組織成定長的記錄格式. 這樣就可以通過邏輯的方法,將文件指針直接移動到所讀寫數據的起始位置,來讀取數據或者將數據直接寫到文件的這個位置上.
- 定長的記錄格式:指文件中的數據被解釋成C++的同一類型的信息的集合.例如,都是整型數或者都是用戶所定義的某一種結構的數據等.
- 在以隨機的方式讀寫文件時,同樣必須首先打開文件,且隨機方式和順序方式打開文件所用的函數也完全相同,但隨機方式的文件流的打開模式必須同時有
ios::in | ios::out
- 在文件打開的時候,文件指針指向文件的第一個字符(字節),可以用C++提供的
seekg
和seekp
函數將文件指針移動到指定的位置,它們的原型如下:
istream& seekg(long pos);
istream& seekg(long off,ios::seek_dir dir);
ostream& seekp(long pos);
ostream& seekp(long off,ios::seek_dir dir);
其中,pos
用來指定文件指針的絕對位置,而off
用來指定文件指針的相對偏移時,文件指針的最後位置還依靠dir
的值,dir
的值可以爲
ios::beg 從文件流的頭部開始
ios::cur 從當前的文件指針位置開始
ios::end 從文件流的尾部開始