C++ Primer Plus--迴圈和關係表示式(五)
C++提供三種迴圈:for迴圈、while迴圈和do while迴圈。
5.1 for迴圈
int i;
for (i = 0; i < 5; i++)
cout << "C++ knows loops.\n";
該迴圈首先將整數變數i設定為0:
i = 0
這是迴圈的初始化部分,然後,迴圈測試部分檢查i是否小於5:
i < 5
如果確實小於5,則程式執行接下來的語句–迴圈體:
cout << "C++ knows loops.\n";
然後程式使用迴圈更新部分將i加1:
i++
接下來,迴圈開始了新的週期,將新的i值與5比較。
5.1.1 for迴圈組成部分
for迴圈的組成部分完成步驟:
1、設定初始值; 2、執行測試,判斷迴圈是否應當繼續進行; 3、執行迴圈操作; 4、更新用於測試的值。
1.表示式和語句
for語句的控制部分使用了3個表示式。
maids = (cooks = 4) + 3;
表示式cooks=4的值為4,因此maids的值為7。下面的語句由也是允許的:
x = y = z = 0;
這種方法可以快速地將若干個變數設定為相同的值。優先順序表表明,賦值運算子是從右向左結合的,因此首先將0賦給z,然後將z=0賦給y,依次類推。
從表示式到語句的轉換很容易,只要加上分號即可,因此下面是一個表示式:
age = 100
而下面一條語句:
age = 100;
更準確地說,這是一條表示式語句。只要加上分號,所有的表示式都可以成為語句,但不一定程式設計有意義。例如:
roents + 6;
編譯器允許這樣的語句,但它沒有完成任何有用的工作。程式僅僅計算和,而沒有使用得到的結果。
2.非表示式和語句
下面語句是一個表示式:
int toad;
而int toad不是表示式,因為它沒有值。因此,下面的程式碼非法:
eggs = int toad * 1000;
同樣不能把for迴圈賦給變數,for迴圈不是表示式,因此沒有值,也不能給它賦值。
3、修改規則
C++對C迴圈的基礎上添加了一項特性,要求對for迴圈句法做一些微妙的調整:
for (int i = 0; i < 5; i++)
也就是說,可以在for迴圈的初始化部分中宣告變數。
5.1.2 回到for迴圈
使用for迴圈計算並存儲前n個階乘:
formore.cpp
#include const int ArSize = 16; int main() { using namespace std; long long f[ArSize]; f[0] = f[1] = 1; for (int i = 2; i < ArSize; i++) f[i] = i * f[i-1]; for (int i = 0; i < ArSize; i++) cout << i << "! = " << f[i] << endl; return 0; }
結果:
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
11! = 39916800
12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
定義一個const值來表示陣列中的元素個數是個好辦法。如果要擴充套件處理20個階乘,則只需要修改ArSize的值為20,而不需要在程式將16修改為20。
5.1.3 修改步長
到現在為止,迴圈示例每一輪迴圈計數加1或減1。可以通過修改更新表示式來修改步長:
int by = 3;
for (int i = 0; i < 9; i = i + by)
cout << i << endl;
5.1.4 使用for迴圈訪問字串
for迴圈提供了一種依次訪問字串隨的每個字元的方式。
forstr1.cpp
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
cout << "Enter a word:\n";
string word;
cin >> word;
for (int i = word.size() - 1; i >= 0; i--)
cout << word[i];
cout << "\nBye.\n";
return 0;
}
結果:
Enter a word:
animal
lamina
Bye.
在本例子中,可以使用string物件,也可以使用char陣列,因為它們都可以使用陣列表示法來訪問字串中的字元。
5.1.5 遞增運算子(++)和遞減運算子(–)
兩個運算子都要兩種變體,字首版本位於運算元前面,如++x;字尾版本位於運算元後面,如x++,兩個版本對運算元的影響是相同的,但是影響的時間不同。
plus_one.cpp
#include <iostream>
int main()
{
using namespace std;
int a = 20;
int b = 20;
cout << "a = " << a << "; b = " << b << endl;
cout << "a++ = " << a++ << "; ++b = " << ++b << endl;
cout << "a = " << a << "; b = " << b << endl;
return 0;
}
結果:
a = 20; b = 20
a++ = 20; ++b = 21
a = 21; b = 21
a++意味著使用a當前值計算,然後將a的值加一;而b++的意思是先將b的值加1,然後使用新的值來計算表示式。例如:
int x = 5;
int y = ++x; //y的值為6
int z = 5;
int y = z++; //y的值為5
遞增和遞減運算子都是漂亮的小型運算子,不過千萬不要失去控制,在同一條語句對同一個值遞增或遞減多次。問題在於,規則“使用後修改”和“修改後使用”可能變得模糊不清。下面的語句在不同的系統中將生成不同的結果:
x = 2 * x++ * (3 - ++x);
對於這種語句,C++沒有定義正確的行為。
5.1.6 副作用和順序點
副作用指的是在計算表示式時對某些東西(如儲存在變數中的值)進行了修改;順序點是程式執行過程中的一個點。在C++中,語句中的分號就是一個順序點,這意味著程式處理下一條語句之前,賦值運算子、遞增運算子和遞減運算子執行所有的修改都必須完成。
完整的表示式:不另一個更大表達式的子表示式。完整的表示式例子有:表示式語句中的表示式部分以及用作while迴圈中檢測條件的表示式。
while (guest++ < 10)
cout << guest << endl;
在這裡,可以認為“使用值,然後遞增”,意味著先在cout語句中使用guest的值,再將其值加1。然而,表示式guest++ < 10是一個完整的表示式,因為它是一個while迴圈的測試條件,因此該表示式的末尾是一個順序點。所以,C++確保副作用(將guest加1)在程式進入cout之前完成。然而,通過使用字尾格式,可確保將guest同10進行比較後再將其值加1。
現在看如下語句:
y = (4 + x++) + (6 + x++);
表示式4 + x++不是一個完整的表示式,因此C++不保證x的值在計運算元表示式4+x++後立刻增加1。在這個例子中,整條賦值語句是一個完整表示式,而分號表示了順序點,因此C++只保證程式執行到下一條語句之前,x的值將被遞增兩次。
C++中,沒有規定是在計算每個子表示式之後將x的值遞增,還是整個表示式計算完畢才將x的值遞增,鑑於此,應避免使用這樣的表示式。
在C++11文件中,不再使用術語”順序點“,因為這個概念難以用於討論多執行緒執行。反而,使用了術語”順序“,他表示有些事情在其他事件前發生。
5.1.7 字首格式和字尾格式
x++;
++x;
從邏輯上說,上述情形下,使用字首和字尾表示式沒有區別,即當表示式的值未被使用時,因此只存在副作用。
C++允許針對類定義這些運算子,在這種情況下,使用者定義字首函式:將值加1,然後返回結果;但字尾版本首先複製一個副本,將其加去,然後將複製的副本返回。因此,對於類而言,字首版本的效率比字尾版本的效率高。
總之,對於內建型別,採用哪種格式都不會有差別;但對於使用者定義的型別,如果有使用者定義的遞增和遞減運算子,作為字首格式的效率高。
5.1.8 遞增/遞減運算子和指標
將遞增運算子使用者指標時,將把指標的值增加其指向的資料型別佔用的位元組數,這種規則適用於指標遞增和遞減:
double arr[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
double *pt = arr;
++pt;
也可以結合使用這些運算子和*運算子來修改指標所指向的值。
字首運算子的從右到左結合規則意味著++pt的含義如下:現將++應用於pt(因為++位於的右邊),然後將應用於被遞增後的pt:
double x = *++pt; //指向arr[2],值為23.4
另一方面,++*pt意味著先取pt所指向的值,然後將這個值加1:
++*pt; //指向arr[2],值為24.4
接下來,看看下面的組合:
(*pt)++;
圓括號指出,首先對指標解除引用,得到24.4,然後,運算子將這個值遞增到25.4,pt仍指向arr[2]。最後,看下面組合:
x = *pt++;
字尾用算符的優先順序高,這意味著將運算子使用者pt,而不是pt,因此對指標遞增。然而,字尾運算子意味著將對原來的地址(&arr[2])而不是遞增後的新地址解除引用,因此pt++的值為arr[2],即25.4,當該語句執行完畢後,pt的值將為arr[3]。
5.1.9 組合賦值運算子
C++有一種合併加法和賦值的運算子:
i += by;
int pa[3] = {1,2,3};
pa[1] += 1;
*(pa + 1) += 2;
pa += 1;
組合賦值運算子
| 操作符 | 作用(L為左運算元,R為右運算元)| |—| | += | L+R賦給L | | -= | L-R賦給L | | = | LR賦給L | | /= | L/R賦給L | | %= | L%R賦給L |
5.1.10 複合語句(語句塊)
int sum = 0;
int number;
for (int i =1; i <= 5; i++)
{
cout << "Value: " << i << ": ";
cin >> number;
sum += number;
}
編譯器將忽略縮排,因此需要使用花括號來說明是for中的語句塊。如果在語句塊中定義一個新的變數,則僅當程式執行該語句塊中的語句時,該變數存在。執行完該語句塊,變數將被釋放。
注意,在外部定義的變數,在語句塊內部也是被定義了。
5.1.11 逗號運算子
語句塊允許把兩條或更多條語句放到按C++句法只能放到一條語句的地方。逗號運算子對錶達式完成同樣的任務,允許將兩個表示式放到C++句法只允許放一個表示式的地方。例如:假設有一個迴圈,每輪都將一個變數加1,而另一個變數減1:
++j,--i
逗號並不總是逗號運算子,例如,下面這個宣告中的逗號將變數列表中的相鄰的名稱分開:
int i, j;
實現將一個string類物件的內容反轉。
forstr2.cpp
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
cout << "Enter a word: ";
string word;
cin >> word;
char temp;
int i,j;
for(j = 0, i = word.size() - 1; j < i; --i, ++j)
{
temp = word[i];
word[i] = word[j];
word[j] = temp;
}
cout << word << endl;
return 0;
}
結果:
Enter a word:
animal
lamina
注意宣告i,j的位置在迴圈之前,因為不能用逗號運算子將兩個宣告組合起來。這是因為宣告已經將逗號用於其他用途–風格列表中的變數。也可以使用一個宣告語句表示式來建立並初始化兩個變數,但這樣看起來有點亂:
int j = 0, i = word.size() - 1;
在這種情況下,逗號只是一個分隔符。
如果在for內部宣告temp:
char temp = word[i];
這樣,temp在每輪迴圈中都將被分配和釋放,這比在迴圈外宣告temp的速度慢一些。另一方面,如果在迴圈內部宣告,則它將在迴圈結束後釋放。
逗號運算子其他用途
i = 20, j = 2 * i //其中i=20,j=40
首先,它確保先計算第一個表示式,然後計算第二個表示式(換句話說,逗號運算子是一個順序點)。其次C++規定,逗號表示式的值是第二部分的值。例如,上面表示式的值為40,因為j=2i的值為40。
在所有的運算子中,逗號運算子的優先順序最低。例如,下面的語句:
cata = 12, 120;
被解釋為:
(cata = 12), 120;
也就是說cata為12,120不起作用。然而,由於括號的優先順序最高,下面的表示式:
cats = (12, 120);
將cats設定為120—逗號右側的表示式值。
5.1.12 關係表示式
不等於: != 等於: == 小於等於: <= 大於等於: >=
關係運算符的優先順序比算術運算子地:
x + 2 > y -2
5.1.13 賦值、比較和可能犯的錯誤
觀察下面兩者的輸出:
itn A[5] = {20, 20, 10, 20 ,1}
for (int i = 0 ; A[i] == 20; i++) //輸出前兩個20
cout << i << endl;
for (int i = 0; A[i] = 20; i++) //因為這裡使用賦值,所有程式會一直輸出20,導致程式崩潰
cout << i << endl;
第二個迴圈,一直輸出20,直到程式崩潰,電腦宕機。
5.1.14 C-風格字串比較
由於C++將C-風格字串視為地址,因此如果使用關係運算符來比較它們,將無法得到滿意的結果。相反,應使用C-風格字串庫的strcmp()函式來比較。該函式接受兩個字串地址作為引數。這意味著引數可以是指標、字串常量或字元陣列名。如果兩個字串相同,則函式返回0;如果第一個字串按字母排在第二個字串前面,則函式返回一個負數;如果第一個字串按字幕順序排在第二個字串之後,則函式返回一個正數。
實際上,”按系統排序順序“比”按字母順序“更準確,這意味著字元根據字元的系統編碼來進行比較。例如:使用ASCII碼時,所有大寫字母的編碼都要小於小寫字母,所以按排序順序,大寫字母將位於小寫字母前面。因此,字串”Zoo“在字串”aviary“之前。根據編碼進行比較還意味著大寫字母和小寫字母是不同的。
雖然不能用關係運算符來比較字串,但可以用來比較字元,因為字元實際上是整型。
for (ch = 'a'; ch <= 'z'; ch++)
cout <<ch;
compstr1.cpp
#include <iostream>
#include <cstring>
int main()
{
using namespace std;
char word[5] = "?ate";
for (char ch = 'a'; strcmp(word, "mate"); ch++)
{
cout << word << endl;
word[0] = ch;
}
cout << "After loop ends, word is :" << word << endl;
return 0;
}
結果:
?ate aate bate cate date eate fate gate hate iate jate kate late After loop ends, word is :mate
如果str1和str2相等,作則下面的表示式為true:
strcmp(str1,str2) == 0
如果str1和str2不相等,則下面兩個表示式都是true:
strcmp(str1,str2) != 0
strcmp(str1,str2)
如果str1在str2的前面,則下面表示式為true:
strcmp(str1,str2) < 0;
如果str在str2的後面,則下面表示式為true:
strcmp(str1,str2) > 0;
5.1.15 比較string類字串
如果使用sting類字串而不是C-字串,比較起來簡單些。
compstr2.cpp
#include <iostream>
int main()
{
using namespace std;
string word = "?ate";
for (char ch = 'a'; word != "mate"; ch++)
{
cout << word << endl;
word[0] = ch;
}
cout << "After loop ends, word is :" << word << endl;
return 0;
}
string類過載運算子!=的方式可以在下面條件下使用它:至少一個運算元為string物件,另一個運算元可以是string物件,也可以是C-風格字串。
5.2 while迴圈
while迴圈是沒有初始化和更新部分的for迴圈,它只有測試條件和迴圈體:
while (test-condition)
body
首先,程式計算圓括號內的測試條件表示式,如果該表示式為true,則執行迴圈體中的語句。如果希望迴圈最終能夠結束,迴圈體中的程式碼必須完成某種影響測試條件表示式的操作。
while.cpp
#include <iostream>
const int ArSize = 20;
int main()
{
using namespace std;
char name[ArSize];
cout << "Your first name: ";
cin >> name;
cout << "Here is your name, verticalized and ASCIIized:\n";
int i = 0;
while(name[i] != '\0')
{
cout << name[i] << " : " << int(name[i]) << endl;
i++;
}
return 0;
}
結果:
Your first name: zxp
Here is your name, verticalized and ASCIIized:
z : 122
x : 120
p : 112
如果沒有迴圈體中的i++來更新測試表達式的值,迴圈會一直停留在第一個陣列元素上,導致死迴圈。測試條件還可以修改為:
while(name[i])
程式的工作方式不變,對於編譯器生成程式碼的速度將更快。由於name[i]是常規字元,其值為該字元的編碼–非零值或true,當name[i]為空值時,其編碼值為0或false。
列印字元的ASCII碼,必須通過強制型別轉換將name[i]轉換為整型。
5.2.1 for與while
在C++中,for和while迴圈本質上是相同的,例如:
for (init-expression; test-expression; update-expression)
{
statements
}
可以改寫成:
init-expression;
while (test-expression)
{
statements
update-expression
}
兩者區別:
- for迴圈中省略測試條件時,將認為條件為true;
- 在for迴圈中,可使用初始化語句宣告一個區域性變數,但在while迴圈中不能這樣做;
- 如果迴圈體中包括continus語句,情況將稍有不同,後續討論。
通常,使用for迴圈來迴圈計數,因為for迴圈格式允許將所有相關的資訊—初始值、終止值和更新計算的方式放在同一個地方。在無法預先直到迴圈執行次數時,使用while迴圈。
設計迴圈時,三條原則:
- 指定迴圈終止的條件;
- 在首次測試之前初始化條件;
- 在條件被再次測試之前更新條件。
注意分號使用:
while (i < 10);
{
cout << i;
i++;
}
這將是一個空迴圈,分號將結束while迴圈。
5.2.2 編寫延時迴圈
有時候,讓程式等待一段時間很有用。while迴圈可用於這個目的。早期的技術是讓計算機進行計數,以等待一段時間:
long waite = 0;
while (waite < 10000)
waite++;
這種方法的問題是,當計算機處理的速度發生變化,必須修改計數限制。更好的辦法是讓系統時鐘來往常這種工作。
C++庫中有一個函式有助於完成這項工作,這個函式名叫clock(),返回程式開始執行後所用的系統時間。這有兩個複雜的問題:首先,clock()返回時間的單位不一定是秒,其次,該函式的返回型別在某些系統可能是long,在另一些習俗可能是unsigned long或其他型別。
但標頭檔案ctime(早期的time.h)提供瞭解決這些問題的解決方案。首先,定義一個符號常量—CLOCKS_PER_SEC,該常量等於每秒鐘包含的系統時間單位數,因此係統時間除以這個值,可以得到秒數。或者將秒數乘以CLOCKS_PER_SEC,可以得到系統時間單位為單位的時間。其次,ctime將clock作為clock()返回型別的別名,這意味著可以將變數宣告為clock_t型別,編譯器將把它轉換為long、unsigned int或適合系統的其他型別。
waiting.cpp
#include <iostream>
#include <ctime>
int main()
{
using namespace std;
cout << "Enter the delay time, in seconde: ";
float secs;
cin >> secs;
clock_t delay = secs * CLOCKS_PER_SEC;
cout << "starting\a\n";
clock_t start = clock();
while (clock() - start < delay);
cout << "done \a\n";
return 0;
}
結果:
Enter the delay time, in seconde: 5
starting
done
該程式以系統時間單位為單位計算延遲時間,避免了在每輪迴圈中將系統時間轉換為秒。
類型別名
C++為型別建立別名的方式有兩種。一種是使用前處理器:
#define BYTE char //注意沒有分號
這樣,前處理器將在編譯程式時使用char替換所有的BYTE,從而使BYTE成為char的別名。
第二種方式是使用C++(和C)的關鍵字typedef來建立別名。例如,將byte作為char的別名,可以這樣做:
typedef char byte;
要讓byte_pointer成為char指標的表明,可以將byte_pointer宣告為char指標,然後在前面加上關鍵字typedef:
typedef char * byte_pointer;
也可以使用#define,不過宣告一系列變數時,這種方法不適用,例如:
#define FLOAT_POINTER float *;
FLOAT_POINTER pa pb;
前處理器置換該宣告為這樣:
float *pa, pb;
typedef方法不會有這樣的問題。它能夠處理更復雜的類型別名,這使得與使用#define相比,typedef是一種更佳的選擇。
注意,typedef不會建立新型別,只是為已有的型別建立一個新名稱。
編寫程式對比兩者:
#include <iostream>
#define char_point char *
typedef char * byte_pointer;
int main()
{
using namespace std;
byte_pointer pa, pb;
cout << "typedef: \n";
cout << sizeof(pa) << endl;
cout << sizeof(pb) << endl;
char_point pc, pd;
cout << "#define:\n ";
cout << sizeof(pc) << endl;
cout << sizeof(pd) << endl;
return 0;
}
結果:
typedef:
8
8
#define:
8
1
5.3 do while迴圈
do while迴圈不同於介紹過的兩種迴圈,因為它是出口條件迴圈,即這種迴圈將首先執行迴圈體,然後判定測試表達式,決定是否應該繼續執行迴圈。句法如下:
do
body
while (test-expression);
通常,入口條件迴圈比出口條件迴圈好,因為入口條件迴圈在迴圈開始之前對條件進行檢查。但有時do while測試更合理,例如,請求使用者輸入時,程式必須先獲取輸入然後對它進行測試。
dowhile.cpp
#include <iostream>
using namespace std;
int main()
{
int n;
cout << "Enter number: ";
do{
cin >> n;
} while(n != 7);
cout << "Yes, 7 is my favorite.\n";
return 0;
}
結果:
Enter number: 3
4
7
Yes, 7 is my favorite.
奇特的for迴圈
int i = 0;
for (;;)
{
i++;
if (30 >= i)
break;
}
另一種變體:
int i = 0;
for(;;i++)
{
if (30 >= i)
break;
}
上述程式碼基於這樣一個事實:for迴圈中的空測試條件被視為true。這些例子不易於閱讀,也不能用作編寫迴圈的通用模型。第一個例子的功能在do while迴圈中將表達得更清晰:
int i = 0;
do{
i++
} while(i <= 30);
第二個例子使用while迴圈可以表達得更清晰:
while(i < 30)
{
i++;
}
5.4 基於範圍的for迴圈(C++11)
基於範圍的for迴圈,簡化了一種常見的迴圈任務:對陣列或容器類(vector或array)的每個元素執行相同的操作:
double prices[5] = {4.99, 10.99, 1.99, 7.99, 8.99};
for (double x: price)
cout << x << endl;
其中x最初表示陣列price的第一個元素。顯示第一個元素後,不斷執行迴圈,而x依次表示陣列的其他元素。因此,上述程式碼顯示全部5個元素。
要修改陣列的元素,需要使用不同的迴圈變數語法:
for (double &x : prices)
x = x * 0.80;
符號&表明x是一個引用變數。還可以結合使用基於for迴圈和初始化列表:
for (int x : {3, 5, 2, 6})
cout << x << " ";
cout << endl;
Linux下使用C++11編譯程式(rangefor.cpp):
g++ -std=c++11 rangefor.cpp
5.5 迴圈和文字輸入
cin物件支援3種不同模式的單字元輸入,其使用者介面各不相同。下面介紹while迴圈中使用這三種模式:
5.5.1 使用原始的cin進行輸入
程式通過選擇某個特殊的字元–哨兵字元,來作為停止表示,程式知道合適停止讀取。例如下面程式遇到#字元時停止輸入。
textcin1.cpp
#include <iostream>
using namespace std;
int main()
{
char ch;
int count = 0;
cout << "Enter characters, enter # to quit: \n";
cin >> ch;
while (ch != '#')
{
cout << ch;
++ count;
cin >> ch;
}
cout << endl << count << " characters read. \n";
return 0;
}
結果:
Enter characters, enter # to quit:
zxp is handsome # read here
zxpishandsome
13 characters read.
程式在輸出時省略了空格,原因是cin在讀取char值時,與讀取其他型別一樣,cin將忽略空格和換行。因此輸入中的空格沒有被回顯,也沒有被包括在計數內。
只有按下回車鍵,使用者輸入的內容才會被髮送給程式,這就是在執行程式時,可以在#後輸入字元的原因。
5.5.2 使用cin.get(char)補救
通常,逐個字元讀取時,程式需要檢查每個字元,包括空格、製表符和換行符。cin所屬的istream類中包括一個能夠滿足這種要求的成員函式。具體說,成員函式cin.get(char)讀取輸入中的下一個字元(即使它是空格),並將其賦值給變數ch。
textcin2.cpp
#include <iostream>
using namespace std;
int main()
{
char ch;
int count = 0;
cout << "Enter characters, enter # to quit: \n";
cin.get(ch);
while (ch != '#')
{
cout << ch;
++ count;
cin.get(ch);
}
cout << endl << count << " characters read. \n";
return 0;
}
結果:
Enter characters, enter # to quit:
zxp is handosome # read here
zxp is handosome
17 characters read.
現在程式回顯了每個字元,並將全部字元計算在內,其中包括空格。
cin.get(ch)呼叫一個值放在ch變數中,這意味著將修改該變數的值。在C語言中,要修改變數的值,必須將變數的地址傳遞給函式,即cin.get(&ch)。當在C++中,只要函式將引數宣告為引用即可。引用是C++在C上新增的一種型別。
5.5.3 使用哪一個cin.get()
前面使用過:
cin.get(name, ArSize).get();
相當於兩行:
cin.get(name., ArSize);
cin.get();
而本節,使用的為:
cin.get(ch);
從上面可以看出,cin.get()的引數可以為空,可以是一個char型別的變數,甚至還可以是兩個引數:一個整型,一個char型。這是因為C++支援函式過載。函式過載允許建立多個同名函式,條件是它們的引數列表不同。例如:如果在C++中使用cin.get(name, Arsize),則編譯器將找到使用char*和int作為引數的cin.get()版本;如果使用cin.get(ch),則編譯器將使用接受一個char引數的版本。
5.5.4 檔案末尾
前面的輸入程式通過使用#符號來表示輸入結束,這樣難令人滿意,因為這樣的符號可能就是合法的輸入的組成部分。如果輸入來自一個檔案,則可以使用一種功能更強大的計數—檢測檔案末尾(EOF)。C++輸入工具和作業系統協同工作,來檢測檔案末尾並將這種資訊告知程式。
檢測到EOF後,cin將兩位(eofbit和failbit)都設定為1。可以通過成員函式eof()來檢視eofbit是否被設定;如果檢測到EOF,則cin.eof()將返回true,否則返回false。同樣,如果failbit被設定為1,則fail()成員函式返回true,否則返回false。注意:eof和fail方法報告最近讀取的結果;即它們事後報告,而不是預先報告,因此應當cin.eof()或cin.fail()測試應放在讀取後。一般使用fail(),其可用於更多的實現中。
textcin3.cpp
#include <iostream>
using namespace std;
int main()
{
char ch;
int count = 0;
cout << "Enter characters, enter # to quit: \n";
cin.get(ch);
while (cin.fail() == false)
{
cout << ch;
++ count;
cin.get(ch);
}
cout << endl << count << " characters read. \n";
return 0;
}
結果:
Enter characters, enter # to quit:
yes yes it is very good
yes yes it is very good
no no i don't agree
no no i don't agree
//此處使用快捷鍵ctrl+d(Linux系統),windows系統使用Ctrl+z
44 characters read.
注意:Windows下面Ctrl+z就相當於EOFLinux下面Ctrl+d相當於EOF。
1. EOF結束輸入
cin方法檢測到EOF時,將設定cin物件中一個指示EOF條件的標記。設定這個標記後,cin將不讀取輸入,再次呼叫cin也不管用。對於檔案輸入是有道理,因為程式不應讀取超出檔案末尾的內容。
然而對於鍵盤輸入,有可能使用模擬EOF來結束迴圈,但稍後要讀取其他輸入,cin.clear()方法可能清除EOF標記,使輸入繼續進行。不過在某些系統中(比如:Linux),輸入快捷鍵將結束輸入和輸出,而cin.clear()將無法恢復輸入和輸出。
2. 常見的字元輸入做法
cin.get(ch);
while (cin.fail() == false)
{
cout << ch;
++ count;
cin.get(ch);
}
可以將上述程式碼使用一些簡潔方式。!運算子將true切換為false或將false切換為true。比如:
while (!cin.fail())
方法cin.get(char)的返回值是一個cin物件。然而,istream類提供了一個可以將istreamd物件(cin)轉換為bool值得函式;但cin出現在需要是bool值得地方(while迴圈得測試條件中)時,該轉換函式被呼叫。意味著可以改寫為:
while (cin)
這比cin.eof()和cin.fail()更通用,因為cin還可以檢測到其他失敗的原因,比如磁碟故障。
最後,由於cin.get(char)的返回值為cin,因此可以將迴圈簡寫為:
while (cin.get(ch))
{}
這樣,cin.get(char)只被呼叫一次,而不是兩次:迴圈前一次,迴圈後一次。為判斷迴圈測試條件,程式必須先呼叫cin.get(ch),如果成功,則將值放入ch中,然後,程式獲得函式的返回值,即cin。接下來,程式對cin進行bool轉換,如果輸入成功,則結果為true,否則為false。
C語言中字元I/O函式–getchar()和putchar(),它們仍舊適用,只要包含標頭檔案stdio.h(或cstdio)即可。也可以使用istream和iostream中類似的成員。
不接受任何引數的cin.get()成員函式將返回輸入中的下一個字元:
int ch;
ch = cin.get();
該函式的工作方式與C語言中getchar()相似,將字元編碼作為int值返回,而cin.get(ch)返回一個物件,而不是讀取的字元。同樣,可以使用cout.put()函式來顯示字元:
cout.put(ch);
該函式的工作方式與C語言中的putchar()類似,只不過其引數型別為char,而不是int。C++實現提供了三種原型:put(char),put(signed char),put(unsigned char)。給put一個int型別將導致錯誤型別,但可以通過強制型別轉換實現:cin.put(char(ch))。
當函式到達EOF時,cin.get()將返回一個符號常量EOF表示的特殊值。該常量是在標頭檔案iostream中定義的。通常,EOF被定義為-1,但沒必要知道實際值,而必需在程式中使用EOF即可。
cin.get(ch);
while (ch != EOF)
{
cout << ch;
++ count;
cin.get(ch);
}
由於EOF表示的不是有效字元編碼,因此可能不與char型別相容,例如:在有些系統中,char型別是沒有符號的,因此char變數不可能為EOF值(-1)。由於這種原因,如果使用cin.get()並測試EOF,則必須將返回值賦給int型別,而不是char型別。但是,如果將ch的型別宣告為int,而不是char,則必須在顯示ch時強制轉換為char型別。
textcin5.cpp
#include <iostream>
using namespace std;
int main()
{
int ch;
int count = 0;
while ((ch = cin.get()) != EOF)
{
cout.put(ch);
count++;
}
cout << endl << count << " characters read.\n";
return 0;
}
結果:
the smalller
the smalller
good boy
good boy
//按下快捷鍵ctrl+d
22 characters read.
迴圈條件,如果寫成如下形式:
while ( ch = cin.get() != EOF)
由於!=的優先順序高於=,因此程式首先對cin.get()的返回值和EOF進行比較。比較的的結果為false或true,而這些bool值被轉換為0或1,並賦值給ch。
ch=cin.get()和cin.get(ch)的區別
| 屬性 | cin.get(ch) | ch = cin.get() | |—| | 傳遞輸入字元的方式 | 賦給引數ch | 將函式返回值賦給ch | | 用於字元輸入時函式的返回值 | istrem物件(執行bool轉換後為true) | int型別的字元編碼 | | 達到EOF時函式的返回值 | istream物件(執行bool轉換後為false) | EOF |
cin.get(ch1).get(ch2)
這是可行的,因為函式cin.get(ch1)返回一個cin物件,然後便可以通過該物件呼叫get(ch2)。
5.6 巢狀迴圈和二維陣列
二維陣列的宣告如下:
int maxtemps[4][5];
該宣告意味著maxtemps是一個包含4個元素的陣列,其中每個元素都是一個由5個整陣列成的陣列。表示式maxtemps[0]是maxtemps陣列e第一個元素,因此maxtemps[0]本身就是一個由5個int組成的陣列。maxtemps[0]陣列的第一個元素是maxtemps[0][0],該元素是int元素。可以認為第一個下標表示行,第二個下標表示列。
假設要打陣列的所有內容:
for (int row = 0; row < 4; row++)
{
for (int col = 0; col < 5; col++)
cout << maxtemps[row][col] << "\t";
cout << endl;
}
5.6.1 初始化二維陣列
建立二維陣列時,可以初始化其所有元素:
int maxtemps[4][5] =
{
{1,2,3,4,5},
{2,3,4,5,6},
{3,4,5,6,7},
{4,5,6,7,8},
};
5.6.2 使用二維陣列
nested.cpp
#include <iostream>
using namespace std;
const int Cities = 5;
const int Years = 4;
int main()
{
const char * cities[Cities] =
{
"Griblle",
"Gribbletown",
"New Gribble",
"San Gribble",
"Gribble Vista"
};
int maxtemps[Years][Cities] =
{
{1,2,3,4,5},
{2,3,4,5,6},
{3,4,5,6,7},
{4,5,6,7,8},
};
cout << "Maximum tempeartures for 2008-2011\n";
for (int city = 0; city < Cities; city++)
{
cout << cities[city] << ": \t";
for (int year = 0; year < Years; year++)
cout << maxtemps[year][city] << "\t";
cout << endl;
}
return 0;
}
結果:
Maximum tempeartures for 2008-2011
Griblle: 1 2 3 4
Gribbletown: 2 3 4 5
New Gribble: 3 4 5 6
San Gribble: 4 5 6 7
Gribble Vista: 5 6 7 8
5.7 總結
C++提供三種迴圈:for迴圈、while迴圈和do while迴圈。