1. 程式人生 > >C++ Primer Plus--迴圈和關係表示式(五)

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;

也可以結合使用這些運算子和*運算子來修改指標所指向的值。

字首運算子的從右到左結合規則意味著\ast++pt的含義如下:現將++應用於pt(因為++位於\ast的右邊),然後將\ast應用於被遞增後的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,而不是\astpt,因此對指標遞增。然而,字尾運算子意味著將對原來的地址(&arr[2])而不是遞增後的新地址解除引用,因此\astpt++的值為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 | | \ast= | L\astR賦給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=2\asti的值為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迴圈。