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。

5.5.5 另一個cin.ge()版本

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循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章