回顧第1章,第一章我們認識到了變量的定義,定義ing時賦值,操作符重載(Overloaded),和沒有深入探討的構造函數,成員函數的概念、符號直接量(與字符直接量的區別),還有輸入輸出緩沖模型之其好處(三個事件才會刷新緩沖區,輸出到設備上,分別是,緩沖區已經滿,遇到cin,顯示要求刷新(如std::endl,控制符(manipulator)))。
這章我寫得有點急切,應為之前C++學過,有些概念一跳而過,看不懂的,可以往下找紅色字體處開始(從循環不變式分析處開始的分析,再回頭來看這個)。
1 #include <iostream>
2 #include <string>
3
4 int main() {
5 //ask for the person's name
6 std::cout << "Please enter your first name: ";
7
8 //read the name
9 std::string name;
10 std::cin >> name;
11
12 //build the message that we intend to write
13 const std:;string greeting = "Hello, " + name + "!";
14
15 //we have to rewrite this part
16 }
#
#分析:我們現在需要重寫(重構//we have...后面的代碼),應該這樣思考,以前的那個程序不具備好的可擴展性,為什么呢?首先如果要求輸入的框架編程10行(空白#行變成10行),后面的代碼久要多加很多行,一行行的進行輸出。這時我們可以用循環對代碼進行重構。我們先分析,在greeting上下空白行只有一行,所以我們用pad
#表示空白行,而總的行數為2 * pad + 3(頭尾加greeting那行)。這樣我們就可以讓程序輸出任意多行。于是有如下代碼
const int pad = 1;
const int rows = pad * 2 + 3;
#另外我們這個輸出的框架是要讓左右兩邊的空白數和上下兩端的空白數相同,所以也只需要定義一個變量就夠了。每一行輸出的字符數就是greeting的長度加上pad * 2加上兩個#兩個星號。即如下代碼const std::string::size_type cols = greeting.size() + pad * 2 + 2;
1
2 #include <iostream>
3 #include <string>
4 using std::cin; using std::endl;
5 using std::cout; using std::string;
6 int main() {
7 cout << "Please enter your first name: ";
8
9 string name;
10 cin >> name;
11
12 const string greeting = "Hello, " + name + "!";
13
14 const int pad = 1;
15
16 const int rows = pad * 2 + 3;
17 const string::size_type cols = greeting.size() + pad * 2 + 2;
18
19 cout << endl;
20
21 // invariant:we have written r rows so far
22 for(int r = 0; r != rows; ++r) {
23
24 string::size_type c = 0;
25
26 // invariant:we have written c characters so far in the current row
27 while(c != cols) {
28
29 if(r == pad + 1 && c == pad + 1) {
30 cout << greeting;
31 c += greeting.size();
32 } else {
33
34 if(r == 0 || r == rows -1 || c == 0 || c == cols - 1)
35 cout << "*";
36 else
37 cout << " ";
38 ++c;
39 }
40 }
41
42 cout << endl;
43
44 }
45 return 0;
46 }
#第一個::說明string名字定義在名字空間std中,而第二個::則表示size_type來自string類。std::string定義了size_type,用來表示一個string中含有的字#符數目。如果需要一個局部變量來表示一個string長度,可以使用std::string::size_type類型定義一個變量。
#size_type是一個無符號的類型
#輸出邊界字符,如果r = 0,由循環不變式可以知道,現在一行也沒有輸出。所以當r = row - 1,已經輸出了row - 1行,接下來輸出的是最后一個部分,類似的,如果c = 0,輸出的將是第一列的部分。
#輸出邊界符號:
#那么我們如何判斷輸出greeting這行呢,由循環不變式,我們可以 r = pad + 1 時,c = pad + 1時,開始輸出greeting。
#第二章寫得有點亂,上面代碼看不懂的,請看下面分析
#首先我們要介紹一個概念,叫做循環不變式,循環不變式就是我們設置一個斷言,讓該斷言在該循環中始終都成立,結束后也成立,這樣這個斷言其實就是這段程序的意思。看如
#下代碼:
//invariant:we have written r rows so far
int r = 0;
//setting r to 0 makes the invariant true
while(r != rows) {
//we can assume that the invariant is true here
//waiting a row of output makes the invariant false
std::cout << std::endl;
//incrementing r makes the invariant true again
r++;
}
//we can conclude that the invariant is true here
#首先你應該想一想要確保不變式始終為true,只要確保在循環進入點為true,一次循環結束點為true,那么這個不變式久永遠為true,understand?如果還不理解,先吧我說
#的這句話理解了,在繼續往下看,不然你不知道我在講什么東西!
#我們的不變式就是上述斷言
invariant:we have written r rows so far #我們分析過,不變式的兩個斷點,一個設在開頭,一個在結尾,所以開頭時r = 0。此時程序一行也沒輸出,不變式為true,在結尾處r++后,仍為true,為什么呢?舉個例子,r = 0,進來之后,將輸出一行,所以此時r不應該在為0,而應該為1.
#這是每一行輸出的框架,轉換成for循環就是上面相應的代碼,而至于另外一個循環一樣個道理。
#下面再介紹一個重要的概念,這個概念我之前還真沒學好,看完后,恍然大悟,大測大悟阿!那就是循環時的計數問題。
#在C中C++中我們寫循環經常是重int i = 0,從0開始是不?就算是,你是不是經常這樣寫for(int i = 0; i <= number; i++);但是更好的寫法應該是for(int i = 0; i #!= number; i++);為什么呢?請聽我慢慢道來.
#首先我們知道在不對稱區間[0, rows)計數的話,很明顯就是rows個數,但是如果你使用的是對稱區間,[num,rows]則有rows - num + 1個數,是不是很不明顯,再則從0開 #始一目了然,別說你看不出來,我在舉個例子(0,66],和[21,86]哪一個你能快速判斷出有幾個數。
#有的人又說,這算什么阿,我從1開始貝[1,66],不就多算一個數么,習慣就好。我想說,你說的沒粗,但我懶,用不對稱區間跟塊算出,更不會出錯。在則,用不對稱區間的好 #處是容易和invariant(循環不變式)相結合,例如,如果你從1開始計數,有的人想我們把不變式改成現在輸出第r行,但是這樣是不能作為一個不變式的,所謂不變式,就是
#這個斷言永遠正確,但是當你結束循環時r = rows + 1,就變成了輸出第rows + 1行,但這個不變式就變成錯的鳥,understand。
#再則我們選者!=而不是<=來作為比較操作符。這個差別很小,但是很不一樣,前者,循環結束時(只要沒有在循環里break),就能判斷此時r = rows,但是如果是后者,我 #們這能證明至少輸出了rows行,為啥?回憶下學過的math,<=,是什么意思?
#還有一條好處,我就不羅嗦了,綜上所屬,你可以發現從0開始計數的好處!,想當一時,在寫鏈表時,就是因為這個計數問題,自己也整了個證明方法,哈哈,每想到早就有更 #簡單的方式了。
#本人才疏學淺,看不懂的,可以留言討論之。