上一節學習了C++的STL庫和範型:c++:-7,本節學習c++的輸入輸出和流類庫。
I/O流
(1)程序與外界環境的信息交換
當程序與外界環境進行信息交換時,存在着兩個對象:程序中的對象、文件對象。 流是一種抽象,負責在數據的生產者和數據的消費者之間建立聯繫,並管理數據的流動。
(2)流對象與文件操作
程序建立一個流對象,指定這個流對象與某個文件對象建立連接程序操作流對象,流對象通過文件系統對所連接的文件對象產生作用。
(3)提取與插入
讀操作在流數據抽象中被稱爲(從流中)提取,寫操作被稱爲(向流中)插入。
(4)流類庫結構
(5)流類列表
輸出流
(1)三個重要的輸出流:
- ostream
- ofstream
- ostringstream
(2)預先定義的輸出流對象
- cout 標準輸出
- cerr 標準錯誤輸出,沒有緩衝,發送給它的內容立即被輸出。
- clog 類似於cerr,但是有緩衝,緩衝區滿時被輸出。
(3)標準輸出換向
ofstream fout("b.out");
streambuf* pOld =cout.rdbuf(fout.rdbuf());
//…
cout.rdbuf(pOld);
(4)構造輸出流對象
- ofstream類支持磁盤文件輸出
- 如果在構造函數中指定一個文件名,當構造這個文件時該文件是自動打開的
ofstream myFile("filename");
- 可以在調用默認構造函數之後使用open成員函數打開文件
ofstream myFile; //聲明一個靜態文件輸出流對象
myFile.open("filename"); //打開文件,使流對象與文件建立聯繫
- 在構造對象或用open打開文件時可以指定模式
ofstream myFile("filename", ios_base::out | ios_base::binary);
(5)文件輸出流成員函數的三種類型
- 與操縱符等價的成員函數。
- 執行非格式化寫操作的成員函數。
- 其它修改流狀態且不同於操縱符或插入運算符的成員函數。
(6)文件輸出流成員函數
- open函數
把流與一個特定的磁盤文件關聯起來。
需要指定打開模式。 - put函數
把一個字符寫到輸出流中。 - write函數
把內存中的一塊內容寫到一個文件輸出流中 - seekp和tellp函數
操作文件流的內部指針 - close函數
關閉與一個文件輸出流關聯的磁盤文件 - 錯誤處理函數
在寫到一個流時進行錯誤處理
文本文件輸出
標準輸出設備顯示器被系統看作文本文件,所以我們以向標準設備輸出爲例,介紹文本文件輸出格式控制
(1)插入運算符
- 插入(<<)運算符
- 爲所有標準C++數據類型預先設計的,用於傳送字節到一個輸出流對象。
(2)操縱符(manipulator)
- 插入運算符與操縱符一起工作
控制輸出格式。 - 很多操縱符都定義在ios_base類中(如hex())、頭文件(如setprecision())
- 控制輸出寬度
在流中放入setw操縱符或調用width成員函數爲每個項指定輸出寬度。 - setw和width僅影響緊隨其後的輸出項,但其它流格式操縱符保持有效直到發生改變。
- dec、oct和hex操縱符設置輸入和輸出的默認進制。
(3)舉例
- 使用width控制輸出寬度
#include <iostream>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
for(int i = 0; i < 4; i++) {
cout.width(10);//控制輸出寬度,默認右對齊
cout << values[i] << endl;
}
return 0;
}
輸出:
1.23
35.36
653.7
4358.24
- 使用setw操縱符指定寬度
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
for (int i = 0; i < 4; i++)
cout << setw(6) << names[i] //和width的功能是一樣的,默認右對齊
<< setw(10) << values[i] << endl;
return 0;
}
輸出:
Zoot 1.23
Jimmy 35.36
Al 653.7
Stan 4358.24
- 設置對齊方式
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)//設置左對齊
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//取消對齊方式,默認右對齊
<< setw(10) << values[i] << endl;
return 0;
}
輸出:
Zoot 1.23
Jimmy 35.36
Al 653.7
Stan 4358.24
(4)setiosflags操縱符
- 這個程序中,通過使用帶參數的setiosflags操縱符來設置左對齊,setiosflags定義在頭文件iomanip中。
- 參數iosbase::left是iosbase的靜態常量,因此引用時必須包括ios_base::前綴。
- 這裏需要用resetiosflags操縱符關閉左對齊標誌。setiosflags不同於width和setw,它的影響是持久的,直到用resetiosflags重新恢復默認值時爲止 。
- setiosflags的參數是該流的格式標誌值,可用按位或(|)運算符進行組合
(5)setiosflags的參數(流的格式標)
- ios_base::skipws 在輸入中跳過空白 。
- ios_base::left 左對齊值,用填充字符填充右邊。
- ios_base::right 右對齊值,用填充字符填充左邊(默認對齊方式)。
- ios_base::internal 在規定的寬度內,指定前綴符號之後,數值之前,插入指定的填充字符。
- ios_base::dec 以十進制形式格式化數值(默認進制)。
- ios_base::oct 以八進制形式格式化數值 。
- ios_base::hex 以十六進制形式格式化數值。
- ios_base::showbase 插入前綴符號以表明整數的數制。
- ios_base::showpoint 對浮點數值顯示小數點和尾部的0 。
- ios_base::uppercase 對於十六進制數值顯示大寫字母A到F,對於科學格式顯示大寫字母E 。
- ios_base::showpos 對於非負數顯示正號(“+”)。
- ios_base::scientific 以科學格式顯示浮點數值。
- ios_base::fixed 以定點格式顯示浮點數值(沒有指數部分) 。
- ios_base::unitbuf 在每次插入之後轉儲並清除緩衝區內容。
(6)精度
- 浮點數輸出精度的默認值是6,例如:3466.98。
- 要改變精度:setprecision操縱符(定義在頭文件iomanip中)。
- 如果不指定fixed或scientific,精度值表示有效數字位數。
- 如果設置了iosbase::fixed或iosbase::scientific精度值表示小數點之後的位數。
(7)舉例
- 控制輸出精度——未指定fixed或scientific
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)//設置左對齊
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//清除左對齊設置
<< setw(10) << setprecision(1) << values[i] << endl; //沒有設置fixed或scientific,則精度表示有效位數
return 0;
}
輸出:
Zoot 1
Jimmy 4e+01
Al 7e+02
Stan 4e+03
- 控制輸出精度——指定fixed
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
cout << setiosflags(ios_base::fixed); //設置以fixed形式輸出,則精度爲小數點後的位數
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)//左對齊設置
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//清除左對齊設置
<< setw(10) << setprecision(1) << values[i] << endl;
return 0;
}
輸出:
Zoot 1.2
Jimmy 35.4
Al 653.7
Stan 4358.2
- 控制輸出精度——指定scientific
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main() {
double values[] = { 1.23, 35.36, 653.7, 4358.24 };
string names[] = { "Zoot", "Jimmy", "Al", "Stan" };
cout << setiosflags(ios_base::scientific);//設置以scientific形式輸出,則精度爲小數點後的位數
for (int i=0;i<4;i++)
cout << setiosflags(ios_base::left)//左對齊設置
<< setw(6) << names[i]
<< resetiosflags(ios_base::left)//清除左對齊設置
<< setw(10) << setprecision(1) << values[i] << endl;
return 0;
}
輸出:
Zoot 1.2e+00
Jimmy 3.5e+01
Al 6.5e+02
Stan 4.4e+03
二進制文件輸出
(1)二進制文件流(ofstream)
- 使用ofstream構造函數中的模式參量指定二進制輸出模式;
- 以通常方式構造一個流,然後使用setmode成員函數,在文件打開後改變模式;
- 通過二進制文件輸出流對象完成輸出。
(2)舉例:向二進制文件輸出
#include <fstream>
using namespace std;
struct Date {
int mon, day, year;
};
int main() {
Date dt = { 6, 10, 92 };
ofstream file("date.dat", ios_base::binary);//打開二進制文件
file.write(reinterpret_cast<char *>(&dt),sizeof(dt));//寫入數據
file.close();
return 0;
}
字符串輸出
將字符串作爲輸出流的目標,可以實現將其他數據類型轉換爲字符串的功能
(1)字符串輸出流( ostringstream )
- 用於構造字符串
- 功能
支持ofstream類的除open、close外的所有操作
str函數可以返回當前已構造的字符串 - 典型應用
將數值轉換爲字符串
(2)舉例:用ostringstream將數值轉換爲字符串
#include <iostream>
#include <sstream>
#include <string>
using namespace std;
//函數模板toString可以將各種支持“<<“插入符的類型的對象轉換爲字符串。
template <class T>
inline string toString(const T &v) {
ostringstream os; //創建字符串輸出流
os << v; //將變量v的值寫入字符串流
return os.str(); //返回輸出流生成的字符串
}
int main() {
string str1 = toString(5);
cout << str1 << endl;
string str2 = toString(1.2);
cout << str2 << endl;
return 0;
}
輸出:
5
1.2
輸入流
(1)重要的輸入流類
- istream類最適合用於順序文本模式輸入。cin是其實例。
- ifstream類支持磁盤文件輸入。
- istringstream:支持字符串輸入
(2)構造輸入流對象
- 如果在構造函數中指定一個文件名,在構造該對象時該文件便自動打開。
ifstream myFile("filename");
- 在調用默認構造函數之後使用open函數來打開文件。
ifstream myFile; //建立一個文件流對象
myFile.open("filename"); //打開文件"filename”
- 打開文件時可以指定模式
ifstream myFile("filename", iosbase::in | iosbase::binary);
(3)使用提取運算符從文本文件輸入
- 提取運算符(>>)對於所有標準C++數據類型都是預先設計好的。
- 是從一個輸入流對象獲取字節最容易的方法。
- ios類中的很多操縱符都可以應用於輸入流。但是隻有少數幾個對輸入流對象具有實際影響,其中最重要的是進制操縱符dec、oct和hex。
(4)輸入流相關函數
- open 把該流與一個特定磁盤文件相關聯。
- get 功能與提取運算符(>>)很相像,主要的不同點是get函數在讀入數據時包括空白字符。
- getline 功能是從輸入流中讀取多個字符,並且允許指定輸入終止字符,讀取完成後,從讀取的內容中刪除終止字符。
- read 從一個文件讀字節到一個指定的內存區域,由長度參數確定要讀的字節數。當遇到文件結束或者在文本模式文件中遇到文件結束標記字符時結束讀取。
- seekg 用來設置文件輸入流中讀取數據位置的指針。
- tellg 返回當前文件讀指針的位置。
- close 關閉與一個文件輸入流關聯的磁盤文件。
舉例
(1)get函數應用舉例
#include <iostream>
using namespace std;
int main() {
char ch;
while ((ch = cin.get()) != EOF)//鍵盤輸入一個字符
cout.put(ch);//輸出
return 0;
}
(2)爲輸入流指定一個終止字符
#include <iostream>
#include <string>
using namespace std;
int main() {
string line;
cout << "Type a line terminated by 't' " << endl;
getline(cin, line, 't');//輸入一行字符,以t字符截止
cout << line << endl;
return 0;
}
(3)從文件讀一個二進制記錄到一個結構中
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
struct SalaryInfo {
unsigned id;
double salary;
};
int main() {
SalaryInfo employee1 = { 600001, 8000 };
ofstream os("payroll", ios_base::out | ios_base::binary);
os.write(reinterpret_cast<char *>(&employee1), sizeof(employee1));//寫入
os.close();
ifstream is("payroll", ios_base::in | ios_base::binary);
if (is) {
SalaryInfo employee2;
is.read(reinterpret_cast<char *>(&employee2), sizeof(employee2));//讀取
cout << employee2.id << " " << employee2.salary << endl;
} else {
cout << "ERROR: Cannot open file 'payroll'." << endl;
}
is.close();
return 0;
}
(4)用seekg函數設置位置指針
#include <iostream>
#include <fstream>
using namespace std;
int main() {
int values[] = { 3, 7, 0, 5, 4 };
ofstream os("integers", ios_base::out | ios_base::binary);
os.write(reinterpret_cast<char *>(values), sizeof(values));//寫入
os.close();
ifstream is("integers", ios_base::in | ios_base::binary);
if (is) {
is.seekg(3 * sizeof(int));//跳到第四個數
int v;
is.read(reinterpret_cast<char *>(&v), sizeof(int));//讀一個數
cout << "The 4th integer in the file 'integers' is " << v << endl;
} else {
cout << "ERROR: Cannot open file 'integers'." << endl;
}
return 0;
}
輸出:The 4th integer in the file 'integers' is 5
(5)讀一個文件並顯示出其中0元素的位置
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ifstream file("integers", ios_base::in | ios_base::binary);
if (file) {
while (file) {//讀到文件尾file爲0
streampos here = file.tellg();//here記錄位置,streampos可以看作int類型
int v;
file.read(reinterpret_cast<char *>(&v), sizeof(int));//讀出一個數存到v中
if (file && v == 0)
cout << "Position " << here << " is 0" << endl;
}
} else {
cout << "ERROR: Cannot open file 'integers'." << endl;
}
file.close();
return 0;
}
輸出:
Position 8 is 0
字符串輸入
將字符串作爲文本輸入流的源,可以將字符串轉換爲其他數據類型
(1)字符串輸入流( istringstream)
- 用於從字符串讀取數據
- 在構造函數中設置要讀取的字符串
(2)功能
- 支持ifstream類的除open、close外的所有操作
(3)典型應用
- 將字符串轉換爲數值
(4)舉例:用istringstream將字符串轉換爲數值
#include <iostream>
#include <string>
#include <sstream>
using namespace std;
template <class T>
inline T fromString(const string &str) {
istringstream is(str); //創建字符串輸入流
T v;
is >> v; //從字符串輸入流中讀取變量v
return v; //返回變量v
}
int main() {
int v1 = fromString<int>("5");
cout << v1 << endl;
double v2 = fromString<double>("1.2");
cout << v2 << endl;
return 0;
}
輸出:
5
1.2
輸入/輸出流
(1)兩個重要的輸入/輸出流
- 一個iostream對象可以是數據的源或目的。
- 兩個重要的I/O流類都是從iostream派生的,它們是fstream和stringstream。這些類繼承了前面描述的istream和ostream類的功能。
(2)fstream類
- fstream類支持磁盤文件輸入和輸出。
- 如果需要在同一個程序中從一個特定磁盤文件讀並寫到該磁盤文件,可以構造一個fstream對象。
- 一個fstream對象是有兩個邏輯子流的單個流,兩個子流一個用於輸入,另一個用於輸出。
(3)stringstream類
- stringstream類支持面向字符串的輸入和輸出
- 可以用於對同一個字符串的內容交替讀寫,同樣是由兩個邏輯子流構成。
程序
宏定義
向程序傳遞參數
(1)輸入輸出格式
#include <fstream>
using namespace std;
#define D(a) T << #a << endl; a //將a中的值寫入文件
ofstream T("output.out"); //打開文件
int main() {
D(int i = 53;)
D(float f = 4700113.141593;)
char* s = "Is there any more?";
D(T.setf(ios::unitbuf);)
D(T.setf(ios::showbase);)
D(T.setf(ios::uppercase);)
D(T.setf(ios::showpos);)
D(T << i << endl;)
D(T.setf(ios::hex, ios::basefield);)
D(T << i << endl;)
D(T.unsetf(ios::uppercase);)
D(T.setf(ios::oct, ios::basefield);)
D(T << i << endl;)
D(T.unsetf(ios::showbase);)
D(T.setf(ios::dec, ios::basefield);)
D(T.setf(ios::left, ios::adjustfield);)
D(T.fill('0');)
D(T << "fill char: " << T.fill() << endl;)
D(T.width(8);)
T << i << endl;
D(T.setf(ios::right, ios::adjustfield);)
D(T.width(8);)
T << i << endl;
D(T.setf(ios::internal, ios::adjustfield);)
D(T.width(8);)
T << i << endl;
D(T << i << endl;) // Without width(10)
D(T.unsetf(ios::showpos);)
D(T.setf(ios::showpoint);)
D(T << "prec = " << T.precision() << endl;)
D(T.setf(ios::scientific, ios::floatfield);)
D(T << endl << f << endl;)
D(T.setf(ios::fixed, ios::floatfield);)
D(T << f << endl;)
D(T.setf(0, ios::floatfield);)
D(T << f << endl;)
D(T.precision(16);)
D(T << "prec = " << T.precision() << endl;)
D(T << endl << f << endl;)
D(T.setf(ios::scientific, ios::floatfield);)
D(T << endl << f << endl;)
D(T.setf(ios::fixed, ios::floatfield);)
D(T << f << endl;)
D(T.setf(0, ios::floatfield);)
D(T << f << endl;)
D(T.width(8);)
T << s << endl;
D(T.width(36);)
T << s << endl;
D(T.setf(ios::left, ios::adjustfield);)
D(T.width(36);)
T << s << endl;
D(T.unsetf(ios::showpoint);)
D(T.unsetf(ios::unitbuf);)
}
(2)
#include <iostream>
#include <fstream>
using namespace std;
int main(int argc, char* argv[])
{
ifstream in;
in.open(argv[1], ios::binary);//打開argv內的數組元素
if (!in) {
cout << "Cannot open file.";
return 1;
}
const int bsz = 1024;
char buf[bsz];
int line = 0;
while(in.getline(buf, bsz)) {
cout << ++line << ": " << buf << endl;
}
return 0;
}
習題
(1)定義一個結構體
struct Date{
int mon,day,year;
};
並定義變量:Date dt = { 6, 10, 92 }; 是否可以成功調用視頻中的函數,string str = toString(dt)
解析:否,Date爲我們自定義的類型,不是基本類型,沒有重載的 << 操作
(2)以二進制的格式寫入到磁盤的速度比按照文本格式輸出的效率要高
解析:對,內存中是按照二進制格式存儲的,如果按照文本格式輸出,需要將二進制格式轉變爲文本格式
(3)cout、cerr和clog是( )類的對象(填寫類名,英文),cout處理標準輸出,cerr和clog都處理標準出錯信息,只是( )輸出不帶緩衝, 而( )輸出帶緩衝(在cerr和clog中選填)。 (答案全部爲小寫,不帶空格)
解析:ostream,cerr,clog
(4)執行下列語句後,a和b的值變爲
istringstream istr("5 1.2");
int a;
float b;
istr >> a >> b;
解析:a爲5,b爲1.2
(5)適合從內存中的字符串中輸入信息的流是:istringstream
(6)適合磁盤文件輸入的輸入流是:ifstream
(7)最適合用於文本模式輸入的輸入流是:istringstream