400 likes | 501 Views
Chapter 08. Pointers and Pointer-Based Strings (Part II). 教學目標. 在這部分你會學到: 指標與陣列之間的關係 使用 const 指標 實作選擇排序法( selection sort ) sizeof 運算子. 8.9 指標與陣列的關係. 陣列的名稱可以被當成指標。 指標也可以用來作陣列索引( index )的運算。. 8.9 指標與陣列的關係. 陣列的名稱可以被當成指標。 用指標來存取陣列中的元素 例: b[n] 可以用 *(bPtr+n) 來表示
E N D
Chapter 08 Pointers and Pointer-Based Strings (Part II)
教學目標 • 在這部分你會學到: • 指標與陣列之間的關係 • 使用const指標 • 實作選擇排序法(selection sort) • sizeof運算子
8.9 指標與陣列的關係 • 陣列的名稱可以被當成指標。 • 指標也可以用來作陣列索引(index)的運算。
8.9 指標與陣列的關係 • 陣列的名稱可以被當成指標。 • 用指標來存取陣列中的元素 • 例: • b[n]可以用*(bPtr+n)來表示 • 指標/位移法(pointer/offset notation) int v[ 5 ]; int *vPtr; vPtr = v; for (int i=0; i<5; i++) v[i] = 0; for (int i=0; i<5; i++) *(vPtr + i) = 0;
陣列的名稱實際上代表了個陣列的開頭位址;假設我們有一個數字陣列,則可以用一個指向數字的指標來記住其中一個元素的記憶體位址。陣列的名稱實際上代表了個陣列的開頭位址;假設我們有一個數字陣列,則可以用一個指向數字的指標來記住其中一個元素的記憶體位址。 • 如何取得開頭的位址? • vPtr = v; • vPtr = &v[0];
指標可以透過位移的方式來改變其中的記憶體位址:指標可以透過位移的方式來改變其中的記憶體位址: • (vPtr+1)的結果並非等於3001;所謂的「+1」,代表移動一個int。 • 為什麼是int?因為vPtr是一個指向int的指標。 • 因此vPtr+1所得到記憶體位址應該為3004 • 通常一個int的大小為4個位元組(bytes) • 令 • vPtr=v; • 請根據上圖,說明vPtr+1、vPtr+2、…、vPtr+4分別代表陣列中哪個元素的記憶體位址?
那麼,如何從一個位址中取得這個位址所儲存的value?那麼,如何從一個位址中取得這個位址所儲存的value? • 利用「*」:反參照運算子 • 如何得到vPtr+1、vPtr+2、…、vPtr+4這些位址中所儲存的資料? • *(vPtr+1) • *(vPtr+2) • *(vPtr+3) • *(vPtr+4)
令 • vPtr = v; • 問:vPtr現在記錄的是哪一個元素的位址? • &v[3]等同於vPtr+3 • v[3]等同於*(vPtr+3) • 陣列的名稱指標變數可以交互使用 • vPtr[3]等同於v[3] • *(vPtr+3)等同於*(v+3)
8.9 指標與陣列的關係 • 陣列的名稱指標變數可以互用。 • 例: int b[ 5 ]; int *bPtr; bPtr = b; for (int i=0; i<5; i++) b[i] = 0; for (int i=0; i<5; i++) *(bPtr + i) = 0; for (int i=0; i<5; i++) *(b + i) = 0; for (int i=0; i<5; i++) bPtr[i] = 0; 陣列/索引法(array/subscript notation) 指標/位移法(pointer/offset notation) 陣列/位移法(array/offset notation) 指標/索引法(array/subscript notation)
array / subscript notation array /offset notation
Pointer/subscript notation pointer/offset notation
常見的程式設計錯誤8.14 • 雖然指標變數和陣列名稱可以互用,但要記得指標變數中的位址是可以修改的,陣列名稱所代表的位址則不能修改。 • 例如: int b[ 5 ]= {3, 4, 2, 6, 1};int *bPtr;bPtr = b; bPtr++; //bPtr = bPtr + 1 cout << *bPtr << endl; cout << *b << endl; b++; cout << *b << endl; 加完的bPtr現在指向陣列b中的哪一個元素 請行會印出什麼? 語法錯誤,為什麼?
良好的程式設計習慣 8.14 • 雖然指標變數和陣列名稱可以互用,為簡潔起見,操作陣列時仍以陣列索引法來使用。 • 何時會用到指標來代替陣列? • 宣告陣列的時候 • 長度未定,先用指標代替宣告,日後再指定大小。 • 儲存陣列的記憶體位址。 • 注意: • 不論是陣列或指標的表示法,都不會自動為使用者提供範圍的判斷,因此在作索引存取或位移存取,都必須小心是否超出陣列的範圍。
注意:參數宣告用指標,代表接收到記憶體位址。使用卻用索引的方式來便利取用元素注意:參數宣告用指標,代表接收到記憶體位址。使用卻用索引的方式來便利取用元素 參數宣告用指標,代表接收到記憶體位址。使用反參照的方式來取用元素 將s1和s2累加,來將位址移動到下一個元素。 問題一:前面我們說過,陣列的名稱中所代表記憶體位址不能改變,何以這個地方,我們可以把s1和s2做++? 問題二:根據我們目前所學,加入我們有一個數字的陣列,討論為什麼當傳入整個陣列的時候,是傳參考呼叫,傳入某一個陣列元素的時候,卻變成傳值呼叫? 問題三:const代表唯讀,那麼在參數的地方宣告「s2為const」,為什麼s2還可以++?
8.5 使用const指標 • 最小權限原則( Principle of least privilege ) • 讓函式透過他的參數取得足夠的資料以便完成工作,但不得擁有過大的存取權限。 • 在參數中使用const: • 規範參數為唯讀。 • 可以從此參數中讀取資料,但不得修改或變更其參數。 • 將一個指標變數宣告為const? • const指標 例如:const char *s2 • 請問是s2中的內容(記憶體位址)不能改,還是s2所指向的位址中的內容不能改? s2 k
8.5 使用const指標 • 由於指標是間接取值,在使用const時,有四種方式來分別指定不同的存取權限: • 指向非常數(non-constant)資料的非常數指標 • 指標指向的位址可以變更,指向位址的內容也可以改 • 指向常數資料的非常數指標 • 指標指向的位址可以變更,指向位址的內容不能改 • 指向非常數資料的常數指標 • 指標指向的位址不能變更,但指向位址的內容可以改 • 指向常數資料的常數指標 • 指標指向的位址不能變更,指向位址的內容也不能改
1. 指向非常數資料的非常數指標 沒有加const convertToUppercase函式具有修改參數的權限
參數sPtr是一個指向非常數資料的非常數指標 如果參數為小寫字元則函式islower回傳true 如果是小寫則回傳大寫字元,如果不是,則回傳原來的字元(請注意,sPtr所指向的位址是可以被修改的) 修改sPtr中所儲存的記憶體位址
2. 指向常數資料的非常數指標 • 指標內的位址可以改,但指向的位址中的資料不能改 • 提供pass-by-reference(可以藉由位址找到原引數的內容),但藉此保護其資料。 • 宣告方式: • 將const寫在型態的前面。 • 例如: const char *s2; s2 k 這樣寫的意思,代表s2的內容可以改,但s2所指向的位址k中的內容不能改。
參數是一個指向常數資料的非常數指標 將變數y的記憶體位址傳給函式f 嘗試透過反參照運算子修改xPtr指向位址的內容 編譯錯誤
參數是一個指向常數資料的非常數指標 傳指標給函式 sPtr是一個指向常數資料的非常數指標,sPtr裡面所儲存的位址可以改(非常數),指向位址的資料不能改(常數)。 因為sPtr是非常數指標,所以可以作++的動作。
增進效能的小技巧 8.1 • 當大型的資料傳進來當參數,且被呼叫的函式不需更改它們,可使用指向常數資料的指標或參照,以達到傳址呼叫的高效率。 • 因為是指向常數資料,因此可以為原參數的內容提供保護。
3. 指向非常數資料的常數指標 • 指標內的位址不可以改,但指向位址中的資料可以改 • 永遠指向同一個記憶體位址,或透過索引法或位移法來存取 • 陣列名稱在預設情況下,為一個指向常數資料的常數指標 • 數值可以透過反參照運算子來修改 • 在宣告的時候必須要給初始值(初始的位址)。 • 怎麼宣告?將const放在指標變數名稱的前面(*的後面) int * const bPtr = &b[0]; int b[ 5 ]= {3, 4, 2, 6, 1}; b[1] = 5; *(b+2) = 3; b++; cout << *b << endl; b是一個指向非常數資料的常數指標,所以指向位址的內容可以改 但是b本身所儲存的位址不能改
ptr是一個指向非常數資料的常數指標,注意一開始的時候就要給初始值ptr是一個指向非常數資料的常數指標,注意一開始的時候就要給初始值 可以透過指標來修改x的內容 但不可以修改ptr當中以存放的位址 上述寫法會發生編譯錯誤
4. 指向常數資料的常數指標 • 指標內的位址不能改,但指向位址中的資料也不能改 • 存取權最小 • 宣告方式 • 在最前面要加const • 在「*」後面也要加const
ptr是一個指向常數資料的常數指標(注意要給初始值)ptr是一個指向常數資料的常數指標(注意要給初始值) 因為*ptr是常數,所以不能修改 因為ptr也是常數,所以也不能修改 以上的行為都會造成編譯的錯誤
8.6 使用傳址呼叫的選擇排序法 • 何謂選擇排序法(Selection sort) • 試問:如何將一個有n個數字的陣列排序? • 從元素0~n-1中找最小的,將他放在陣列的開頭(0) • 原來存在陣列0的元素怎麼辦? • 再從1~n-1中找最大的,將他放在陣列1的位置 • 再從2~n-1中找最大的,將他放在陣列2的位置 • 以下類推… • 從i~n-1中找最大的,把他放在陣列i的位置 • 最後從n-2~n-1中找最大的,將他放在陣列n-2的位置 • 請問:i的值從多少跑到多少? 0 1 n-1
演算法 • 令array是一個數字陣列,一個n代表陣列中的數字個數 for (int i = 0; i < n – 1; i++) { Find the smallest number from i to n-1; array[i] = smallest; } 如何從元素i到n-1中找到最小值?
範例 • 請以selection sort來說明以下陣列排序的過程: 0 1 2 3 4 5 6 7 8 9 10 34 56 4 10 77 51 93 30 5 52 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10
array是一個指向______資料的_______指標 size是用來傳入array的大小,應不會對他的內容作修改,因此宣告為const,代表只能讀,不能作修改 注意這個變數smallest,用來記住目前最小值是哪個元素(用他的索引來代表),一開始,初始化為i(因為從i開始找)
8.7 sizeof運算子 • 定義 • 回傳其運算元的大小(單位:位元組(bytes)) • 如果運算元提供的是一個陣列的名稱,則會計算出 (一個元素的大小) * (元素的個數 ) • 換言之,就是計算出陣列佔有多少個位元組 • 範例:假設一個int有4個bytesint myArray[ 10 ]; cout << sizeof( myArray ); 將會印出? • sizeof接受的運算元可以是 • 變數名稱 • 型態名稱 • 常數
常見的程式設計錯誤 8.7 • 雖然陣列名稱可以和指標變數互用,但使用sizeof時,有以下的問題需注意,運算元是一個陣列的時候才會計算出陣列的總位元組大小. • 運算元是指標,則回傳的是指標的位元組大小。
如果運算元是陣列,則回傳的是陣列的總bytes數如果運算元是陣列,則回傳的是陣列的總bytes數 函式getSize回傳的是用來儲存array這個位址所需要的byte大小 Ptr這個指標變數的大小
8.7 sizeof運算子 • 如何計算陣列的長度 double realArray[ 22 ]; int length = sizeof realArray / sizeof( double ); • 只有在運算元是型態的時候才需要加刮號,可是一般為了避免混淆,在此建議使用sizeof的時候,將所有的運算元加上刮號。