2.6 輸入/輸出流

輸入/輸出流

  • C++中,提供了兩套輸入/輸出方法

    • 與C語言相兼容的輸入/輸出函數,如printfscanf函數

    • 輸入/輸出流庫ios:可實現類對象數據流的操作

流類和流對象

  • 在C++中,輸入/輸出是由”流”來處理的

  • :數據從一個位置到另一個位置的流動抽象爲流

    • 輸入流:當數據從鍵盤或磁盤文件流入到程序中時,這樣的流成爲輸入流

    • 輸出流:當數據從程序中流向屏幕或磁盤文件時,這樣的流成爲輸出流

  • 當流被建立後,可以使用一些特性的操作從流中獲取數據或向流中添加數據.從流中獲取數據成爲提取操作,向流中添加數據成爲插入操作
    這裏寫圖片描述

  • 上圖中,ios類是用來提供一些關於對流狀態進行設置的功能,它是一個虛基類,其他類都是由這個類派生的
  • streamfuf不是ios類的派生類在類ios中只有一個指針成員,指向streambuf類的一個對象.streambuf類是用來爲ios類及其派生類提供對數據的緩衝支持
  • 緩衝:指系統中在主存開闢一個專用的區域來臨時存放輸入/輸出信息,這個區域成爲緩衝區
  • 刷新:只有當緩衝區滿了或當前送入的數據爲新的一行時,系統纔對流中的數據進行處理,成爲刷新.
  • istreamostream類均是ios類的公有派生類,前者提供了向流中插入數據的有關操作,後者提供了從流中提取數據的有關操作.
  • iostream類是istreamostream類公有派生的,僅僅是將istreamostream類綜合在一起,提供方便
  • C++提供了4個預定義的標準流對象(爲了方便用戶對基本輸入/輸出流進行操作):cin,cout,cerrclog
    • cin:用於處理標準輸入,即鍵盤輸入
    • cout:用於處理標準輸出,即屏幕輸出
    • cerrclog:用來處理標準出錯信息,並將信息顯示在屏幕上.
  • 標準流通常使用插入運算符”<<”提取運算符”>>”來進行輸入/輸出操作,而且系統還會自動地完成數據類型的轉換.

流的格式控制和錯誤處理

C++標準的輸入/輸出流提供了兩種格式的控制模式:

  • 使用ios類的相關成員函數,如width,precisionfill

  • 可以直接使用的格式操作算子,如oct,hexdec

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.使用getgetline函數
  • 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類中用於輸出單個字符或字節的成員函數是putwrite.它們的原型如下:
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,ofstreamfstream類對象,如

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,ofstreamfstream類構造函數中,總有一種原型和它的成員函數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::appios::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++提供的seekgseekp函數將文件指針移動到指定的位置,它們的原型如下:
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  從文件流的尾部開始
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章