聽過HelloWorld.exe的自我介紹之後,大家已經知道了一個C++程序的任務就是描述數據和處理數據。這兩大任務的對象都是數據,可現在的問題是,數據不可能無中生有地產生,C++程序也不可能憑空創造出來數據。那麼,C++程序中的數據又從何而來呢?
在現實世界中,國與國之間的交流是通過外交官來完成的。在C++世界中,也有負責應用程序跟外界進行數據交流的外交官,它們的名字就是基本輸入/輸出流對象(iostream)。一個C++程序在工作的時候,負責輸入的外交官(istream)會將現實世界中的數據(比如,來自鍵盤的用戶輸入數據)輸入程序中,然後C++程序才能對這些數據進行處理。當C++程序得到結果數據之後,負責輸出的外交官(ostream)又會將結果數據輸出(比如,輸出到屏幕或者文件)。在C++程序中,我們將這種數據在程序和外部對象(鍵盤、屏幕等)之間的流動稱為流(stream),分別由istream和ostream這兩位外交官負責。而正是這兩位外交官的通力合作,才完成了C++程序與外界的數據交流。
為了便於使用,C++標准庫中已經預先定義了4個最基本的輸入/輸出流(I/O)對象,其中最常用的是負責鍵盤輸入的cin對象和負責屏幕輸出的cout對象。另外,標准庫還定義了兩個輔助的輸出對象,分別是用於輸出程序錯誤信息的cerr和用於輸出日志信息的clog。這些對象都已經在標准庫中預先定義,只要引入相應的頭文件<iostream>,我們就可以在程序中直接使用它們來完成程序的基本輸入/輸出,就像我們在上面的程序中直接使用cout來向屏幕輸出“Hello World!”字符串一樣。
cin和cout的使用非常簡單,我們可以通過提取(get-from)符“>>”從cin中提取用戶通過鍵盤輸入的數據,實現從鍵盤到程序的數據輸入;也可以通過插入(put-to)符“<<”向cout中插入程序內的數據,實現從程序到屏幕的數據輸出。這裡,箭頭的方向形象地代表了數據流動的方向。輸入數據的時候,數據是從cin對象流出到程序,所以箭頭指向遠離cin對象的方向,而輸出數據的時候,數據是從程序流入cout對象,所以箭頭是指向靠近cout對象的方向。例如,可以使用“<<”插入符向cout對象中插入數字或者字符串,將其顯示到屏幕上:
cout<<1; // 向cout對象中插入數字1,這個數據從程序流動到屏幕 cout<<"Hello World!"; // 向cout對象插入字符串“Hello World!” cout<<"1 + 2 = "<<1+2; // 向cout對象中插入字符串“1 + 2 =”以及1+2的計算結果
第一句中的插入符將數字“1”插入到cout對象中,這樣就會在屏幕上顯示數字1。同理,第二句會在屏幕顯示一個字符串“Hello World!”。而在最後一條語句中,系統首先會計算得到“1+2”的結果數據3,然後第一個插入符會首先將“1 + 2 = ”這個字符串數據插入cout對象,接著第二個插入符會將計算結果數據3插入cout對象,這樣,我們在屏幕上看到的最終輸出就是“1 + 2 = 3”這樣一個字符串。
對於輸入流對象cin,可以使用提取符“>>”從cin輸入流中獲取用戶通過鍵盤輸入的數據並保存到程序內的變量中。例如:
// 用於保存用戶輸入數據的變量 string strName; // string類型的變量,用於保存用戶輸入的字符串 int nAge = 0; // int類型的變量,用於保存用戶輸入的整數 // 從cin對象中提取用戶輸入的字符串數據和整數數據, // 例如,輸入"Liangqiao (空格)28(回車)" // cin會讀取其中的"Liangqiao"和"28"這兩個數據, // 並分別保存到strName和nAge這兩個變量中 cin>>strName>>nAge;
在這裡,我們首先定義了兩個變量strName和nAge,分別用於保存用戶輸入的字符串數據和整數數據。然後,利用提取符“>>”從cin對象中提取用戶通過鍵盤輸入的數據,當程序執行到這裡的時候會暫停下來等待用戶輸入,一旦用戶完成輸入並回車後,“>>”就會從cin對象中提取用戶輸入的數據並分別保存到相應的變量中,這樣就完成了數據從鍵盤到應用程序的輸入。
下面再來看一個輸入和輸出配合使用的實例。
// 引入定義輸入/輸出流對象的頭文件 #include <iostream> // 使用std名字空間 using namespace std; int main() { // 在屏幕上輸出提示信息 // 在字符串的後面,我們還輸出了一個特殊的操縱符endl, // 它的做用是表示一行的結束(end of line),它會讓輸出換行 // 並刷新輸出緩沖區,讓用戶可以立即看到輸出 cout<< "請輸入兩個整數(例如,19 83):" <<endl; // 用於保存輸入數據的變量 int n1, n2; // 從cin提取用戶輸入的兩個整數,輸入時,整數之間以空格間隔 cin>> n1 >> n2; // 對數據進行處理 // 計算兩個加數的和,將結果保存到nRes變量 int nRes = n1 + n2; // 將兩個加數和計算結果輸出到屏幕 cout<< n1 << " + " << n2 // 兩個加數 << " = " << nRes <<endl; // 計算結果 return 0; }
利用cin和cout,短短的幾行代碼,就實現了一個整數加法計算程序。它可以接受用戶輸入的數據(兩個加數),然後對數據進行處理(加和運算),並最終將結果數據(nRes)輸出,使得問題(計算兩個整數的和)得到圓滿解決。
在輸出數據的時候,除了簡單地輸出數據之外,在不同的應用場景之下,我們往往對數據輸出的格式也有著不同的要求。比如,同樣是輸出一個double類型的小數,如果這個小數只是一個學生成績,那麼輸出時保留一位小數就足夠了,而如果它是一個人民幣匯率,那麼輸出時至少要保留三位小數才行。為了對輸出格式進行控制,C++提供了很多操縱符,比如我們在前面的代碼中用到過的endl就是一個控制換行的操縱符。這些操縱符可以使用“<<”直接插入到輸出流對象cout中以實現對輸出格式的控制。這些操縱符大都定義在頭文件<iomanip>中,所以如果想在代碼中使用它們來控制輸出格式,我們需要先使用預編譯指令#include引入這個頭文件才行。表2-1列出了C++中常用的格式操縱符。
表2-1 常用的輸出流格式操縱符
操 縱 符
作 用
dec
采用十進制顯示數值數據,這是輸出流對象的默認設置
hex
采用十六進制顯示數值數據,比如,十進制數值54321會被輸出顯示為十六進制的d431。如果同時在輸出流中插入一個showbase操縱符,還可以同時輸出十六進制數值的0x前綴
oct
采用八進制顯示數值數據,同樣的十進制數值54321,在這種模式下會被輸出顯示為八進制的152061
endl
插入換行符,並刷新輸出流緩沖區
setprecision(n)
設置浮點數的輸出精度為n。默認情況下,精度指的是浮點數中小數點前後所有數字的個數。如果同時在輸出流中插入了fixed操縱符,那麼精度指的就是小數點後的數字的個數
setw(n)
設置輸出的每個數據的顯示寬度
綜合運用這些操縱符,可以滿足一些特殊的輸出格式要求。例如,要求以“保留小數點後兩位有效數字”的格式輸出小數1.23456,然後換行,可以用如下的代碼實現:
cout<<fixed<<setprecision(2)<<1.23456<<endl;
在這裡,首先向cout對象插入一個fixed操縱符,表示以固定的小數位數輸出小數數值。然後,通過setprecision()設置需要保留的小數點後有效數字位數,這樣就可以達到“保留小數點後兩位有效數字”的輸出格式要求了。
除了對數值數據輸出格式的控制之外,很多時候,我們還需要對字符串的輸出格式進行控制,從而讓程序的輸出更加美觀。跟在輸出流中插入操縱符控制數值數據的輸出格式類似,我們也可以通過在字符串中加入一些用於格式控制的轉義字符來實現對字符串輸出格式的控制。這裡所謂的轉義字符,就是在某些字符前加上“\”符號構成的特殊字符,這些字符不再表達它原本的含義,轉而表達的是對格式的控制或其他特殊意義,所以被稱為轉義字符。常用的格式控制轉義字符有:“\n”表示換行;“\t”表示間隔整數個Tab的距離輸出等。例如,下面的代碼實現了換行顯示:
cout<<"分多行\n顯示一個字符串"<<endl;
程序執行後,將在屏幕上看到“\n”將一個字符串分成了兩行顯示:
分多行
顯示一個字符串
綜合使用C++語言所提供的這些輸出流操縱符和格式控制轉義字符,可以實現靈活多樣的自定義格式化輸出,從而滿足對輸出格式的個性化要求。
除了用硬件設備(鍵盤、屏幕等)與程序進行輸入/輸出之外,更多的時候,程序還需要對文件進行讀寫以實現跟文件的數據輸入/輸出:從文件讀取數據到應用程序進行處理,實現數據輸入;將處理得到的結果數據寫入文件進行保存,實現數據輸出。C++標准庫提供了ifstream(input file stream)和ofstream(output file stream),分別用來從文件中讀取數據和將數據寫入文件。它們定義在<fstream>頭文件中,如果想在程序中使用它們來讀寫文件,我們需要先引入這個頭文件。(需要提醒的是,這一小節涉及到了很多C++中比較高階的內容,比如,類、對象以及成員函數等。如果在閱讀這一小節的時候有困難,可以先跳過這一小節,等到掌握後面的必要的知識後再來閱讀,就可以輕松理解了。)
在使用的時候,我們首先需要創建它們的實例對象。如果是為了將數據輸出到文件,則創建ofstream的對象,反之則創建ifstream的對象。通過在創建對象的時候提供文件名,這些對象就可以打開或者創建相應的文件,從而將對象跟某個具體的文件關聯起來,接下來就可以操作這些對象,判斷文件是否成功打開等。
在完成這些創建對象打開文件的准備工作之後,就可以直接通過這些對象對文件進行讀寫操作了。其方式跟通過cin或cout進行數據輸入/輸出非常類似,我們也同樣是用插入符“<<”將數據插入到一個ofstream對象中,實現將程序中的數據輸出到這個對象關聯的文件;用提取符“>>”從一個ifstream對象中提取數據,實現從這個對象關聯的文件中輸入數據到程序。例如,我們首先讀取一個文件中的內容,然後將新的內容寫入這個文件:
#include <iostream>
#include <iostream> // 引入文件輸入/輸出需要的頭文件 #include <fstream> using namespace std; // 主函數 int main() { // 定義變量,保存程序中的數據 int nYear, nMonth, nDate; // 創建輸入文件流對象fin,並嘗試打開Data.txt文件 // 這個文件應該位於exe文件相同的目錄下, // 才能直接使用文件名打開,否則應該使用完整的文件路徑 ifstream fin("Date.txt"); // 判斷Date.txt文件是否成功打開 // 如果成功打開文件,則從文件中讀取內容 if( fin.is_open() ) { // 用提取符“>>”從文件輸入流對象fin中讀取文件中的數據,並保存到相應的變量 // 文件中的內容應該是以空格間隔的三個整數,例如:1983 7 3 fin>>nYear>>nMonth>>nDate; // 將讀取的數據顯示到屏幕 cout<<"文件中記錄的日期是" <<nYear<<"-"<<nMonth<<"-"<<nDate<<endl; // 讀取完成後,關閉文件 fin.close(); } else { // 如果文件打開失敗,則提示錯誤信息 cout<<"無法打開文件並進行讀取"<<endl; } // 提示用戶輸入新的數據並將其寫入文件 cout<<"請輸入新日期(例如,1981 9 22):"<<endl; // 獲取用戶通過鍵盤輸入的數據並保存到相應的變量中 cin>>nYear>>nMonth>>nDate; //創建輸出文件流對象fout,並嘗試打開Data.txt文件, // 如果這個文件不存在,則創建一個新文件並打開 ofstream fout("Date.txt"); // 如果成功打開Date.txt文件,則將用戶輸入的數據寫入文件 if( fout.is_open() ) { // 利用插入符“<<”將數據插入文件輸出流對象fout中, // 也就是將數據寫入與之關聯的Data.txt文件中 // 為了便於將來的讀取,這裡輸出的數據以空格作為間隔 fout<<nYear<<" "<<nMonth<<" "<<nDate; // 寫入完成後,關閉文件 fout.close(); } else { // 如果無法打開文件,則提示用戶信息 cout<<"無法打開文件並進行寫入"<<endl; } return 0; }
在這段程序中,首先創建了一個輸入文件流ifstream的對象fin,並利用它的構造函數將其關聯到一個文本文件Date.txt。所謂構造函數,就是這個對象創建的時所執行的函數。這裡,使用“Date.txt”文件名作為構造函數的參數,實際上就是打開這個文件並使用這個文件創建fin對象。除此之外,還可以使用fin所提供的open()成員函數來打開一個文件。當我們創建fin對象之後,在進行讀取操作之前,為了提高程序的健壯性,我們往往還需要使用它的is_open()成員函數對是否成功打開文件進行判斷,只有文件被成功打開,才能進行下一步的讀取操作。當利用fin成功打開一個文件之後,就可以利用提取符“>>”從fin中提取各種數據,實際上也就是從Data.txt文件中讀取數據。例如,如果文件中的內容如下:
1982 10 3
而對應的,程序中用於讀取的代碼是:
fin>>nYear>>nMonth>>nDate;
默認情況下,提取符“>>”會以空格為分隔符,逐個從文件中讀取數據並將其保存到相應的數據變量中。代碼執行完畢後,文件中的“1983”、“7”和“3”這三個數值就分別被讀取並保存到了程序中的nYear、nMonth和nDate 這三個變量中,實現了從文件到程序的數據輸入。在文件讀取完畢之後,需要用close()成員函數關閉文件。
同樣,為了將數據寫入文件,需要創建一個輸出文件流ofstream的對象fout,同時通過它的構造函數或open()函數來打開一個文件,將這個文件和fout對象關聯起來,然後通過插入符“<<”將需要輸出的數據插入到fout對象,也就相當於將數據寫入到了與之關聯的文件,輸出完成後用close()函數關閉文件,這樣就實現了從程序到文件的數據輸出。整個過程如圖2-9所示。
圖2-9 文件讀/寫
這裡我們所介紹的只是文件輸入/輸出的最基本操作,但已經基本上能夠滿足我們的日常需要了。除此之外,C++還提供了更豐富的文件讀寫操作,比如讀/寫二進制文件,調整移動讀寫位置等,從而也滿足我們對文件讀/寫的進一步需求。
#include "stdio.h"
int main()
{
int n,cnt=0,i;
char s[100];
freopen("D:\\a.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{
printf("%d ",n);
scanf("%s",s);
cnt=0;
for(i=0;s[i];i++)
cnt+=(s[i]=='a');
printf("%d個a\n",cnt);
}
return 0;
}
fopen() 改為: if((fp=fopen("1s.txt","w+"))==NULL)
fputc(p,fp); 改為:fprintf(fp,"%d",p);
讀語句前,加一句文件回繞到文件頭: rewind(fp);
----------------
int main(){
FILE *fp;
char read[1000];
char s;
long p;
if((fp=fopen("1s.txt","w+"))==NULL)
{
printf("\nOpen file error!press any key exit!");
getchar();
exit(0); }
p=123456;
s='\n';
fprintf(fp,"%d",p);
fputc(s,fp);
fprintf(fp,"%d",p);
rewind(fp);
fgets(read,1000,fp);
printf("%s",read);
system("pause");
return 0;}