Chapter 5 – 指標和字串 目標: 能夠使用指標 能用指標藉著傳參照呼叫方式,將引數傳給函式 了解指標、陣列和字串間的密切關係 了解指向函式的指標之使用方法 能夠宣告和使用字串指標
Chapter 5 – 指標和字串 大綱 5.1 簡介 5.2 指標變數的宣告和初始化 5.3 指標運算子 5.4 利用傳參照呼叫函式 5.1 簡介 5.2 指標變數的宣告和初始化 5.3 指標運算子 5.4 利用傳參照呼叫函式 5.5 將指標宣告為 const 的用法 5.6 使用傳參照作泡沫排序法 5.7 指標運算式與指標的算術運算 5.8 指標與陣列的關係 5.9 指標陣列 5.10 洗牌與發牌 5.11 函式指標 5.12 介紹字元和字串處理 5.12.1 字元與字串的基本認識 5.12.2 字串處理程式庫中的字串操作函式 5.13 (選讀性的範例研究)物件的探索:物件之間的合作關係
5.1 簡介 指標 功能強大、但很難完全掌握 可模擬傳參照呼叫方式 與陣列、字串的關係很密切 5.1 簡介 指標 功能強大、但很難完全掌握 可模擬傳參照呼叫方式 與陣列、字串的關係很密切 可以執行動態資料結構, 例如:linked lists(鏈結串列), queues(佇列), stacks(堆疊) and trees(樹). 第5章會談到 pointers with structures 的用法。 第9、10章談到用 pointers 與 references 的物件導向程式設計 15章介紹動態記憶體管理的方式
5.2 指標變數宣告與初始化 指標變數 Indirection 間接 指標變數的宣告 - 使用之間必須先宣告 其內容是「記憶體位址」 5.2 指標變數宣告與初始化 指標變數 其內容是「記憶體位址」 一般變數內容是某個值(直接存取) 指標內含某個有特殊值之變數的位址(間接存取) Indirection 間接 存取一指標的值 指標變數的宣告 - 使用之間必須先宣告 * 表示該變數是一個指標變數 int *myPtr, myData;//myData 是整數變數 宣告指向 int 的指標,也就是型態為 int * 的指標 一個宣告敘述要宣告多個指標時,要寫多個 * int *myPtr1, *myPtr2; count 7 count 7 countPtr
讓所取的指標變數包含有 Ptr 的字樣,可以更清楚地表達這些變數是指標,而不是一般變數,需要適當地處理。 5.2 指標變數宣告與初始化 可以宣告指向任何資料型態的指標 指標的初始化 可初始化為 0, NULL, 或一個位址 0 或 NULL 表示不指向任何東西 良好的程式設計習慣5.1 讓所取的指標變數包含有 Ptr 的字樣,可以更清楚地表達這些變數是指標,而不是一般變數,需要適當地處理。 測試和除錯的小技巧 5.1 對指標作初值化,可避免指標指到未知或未初始化的記憶體。
& (address operator、定址運算子) 5.3 指標運算子 & (address operator、定址運算子) 傳回運算元位址的單元運算子 例如 int y = 5; int *yPtr; yPtr = &y; // yPtr 拿到 y 的位址 yPtr “指向” y yPtr y 5 500000 600000 yptr 的值等於y 的位址
* (indirection/dereferencing 運算子) 5.3 指標運算子 * (indirection/dereferencing 運算子) 傳回運算元所指到的記憶體內容 前面的例子中 *yPtr 就傳回 y (因為 yPtr 指向 y). * 可用來將某個值存到指標所指向的記憶體中 *yPtr = 7; // 將 7 存到 y 中 被 * 運算的指標一定要是 lvalue(不可是數值) * 與 & 是相反的運算 會互相抵銷 *&myVar == myVar 與 &*yPtr == yPtr
1. Declare variables 2 Initialize variables 3. Print Program Output 1 // Fig. 5.4: fig05_04.cpp 2 // Using the & and * operators 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 int main() 9 { 10 int a; // a is an integer 11 int *aPtr; // aPtr is a pointer to an integer 12 13 a = 7; 14 aPtr = &a; // aPtr set to address of a 15 16 cout << "The address of a is " << &a 17 << "\nThe value of aPtr is " << aPtr; 18 19 cout << "\n\nThe value of a is " << a 20 << "\nThe value of *aPtr is " << *aPtr; 21 22 cout << "\n\nShowing that * and & are inverses of " 23 << "each other.\n&*aPtr = " << &*aPtr 24 << "\n*&aPtr = " << *&aPtr << endl; 25 return 0; 26 } 1. Declare variables 2 Initialize variables 3. Print Program Output a 的位址就是 aPtr 的值 The * operator returns an alias to what its operand points to. aPtr points to a, so *aPtr returns a. 注意 * 與 & 互相抵銷 aPtr 6AFDF4 a 12FED4 7 The address of a is 006AFDF4 The value of aPtr is 006AFDF4 The value of a is 7 The value of *aPtr is 7 Showing that * and & are inverses of each other. &*aPtr = 006AFDF4 *&aPtr = 006AFDF4
優先順序 Operators Associativity ()[] left to right ++ -- static_cast<type>() right to left(prefix) ++ -- + - ! & * left to right(postfix) * / % left to right + - left to right << >> left to right < <= > >= left to right == != left to right && left to right || left to right ?: left to right = += -= *= /= %= right to left , left to right
5.3 指標運算子 常見的程式設計錯誤 5.2~5.4 透過未適當初始化的指標來、或未指向適當位址的指標來存取,都可能產生執行時期的致命錯誤,或者可能會無意修改到重要資料,或者執行結束但得到錯誤的結果。 嘗試對非指標變數作 dereference 為語法錯誤。 對指向 0 的指標作 dereference 通常都是致命的執行錯誤 可攜性的小技巧 5.1 指標的輸出格式與電腦系統有關,有些是以十六進位整數輸出、有些以十進位整數輸出。
用指標引數作 call by reference 5.4 利用參照呼叫函式 C++ 有三種傳參數的方法 用指標引數作 call by reference 用 & 傳遞引數的位址 允許函式改變引數的內容(因為知道該引數的位址) 傳陣列時不需寫 &,因為陣列名稱就是記憶體位址 在函式中用 * 來存取參數內容 void doubleNum( int *number ) { *number = 2 * ( *number ); } *number 代表傳入函式中的引數內容 呼叫函式時,必須傳入記憶體位址 doubleNum( &myNum );
number 125 5 n undefined undefined 5 1 // Fig. 5.6: fig05_06.cpp 2 // Cube a variable using pass-by-value. 3 #include <iostream> 5 using std::cout; 6 using std::endl; 8 int cubeByValue( int ); // prototype 10 int main() 11 { 12 int number = 5; 14 cout << "The original value of number is " << number; 17 number = cubeByValue( number ); 19 cout << "\nThe new value of number is " << number << endl; 21 return 0; 23 } 25 26 int cubeByValue( int n ) 27 { 28 return n * n * n; // cube local variable n and return result 30 } number fig05_06.cpp (1 of 2) 125 5 n undefined undefined 5 The original value of number is 5 The new value of number is 125
1. Function prototype - takes a pointer to an int. 1 // Fig. 5.7: fig05_07.cpp 2 // Cube a variable using call-by-reference 3 // with a pointer argument 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 void cubeByReference( int * ); // prototype 10 11 int main() 12 { 13 int number = 5; 14 15 cout << "The original value of number is " << number; 16 cubeByReference( &number ); 17 cout << "\nThe new value of number is " << number << endl; 18 return 0; 19 } 20 21 void cubeByReference( int *nPtr ) 22 { 23 *nPtr = *nPtr * *nPtr * *nPtr; // cube number in main 24 } 1. Function prototype - takes a pointer to an int. 1.1 Initialize variables 2. Call function 3. Define function Program Output 注意如何將 number 的位址傳進去 - cubeByReference 期待一個指標 (變數的記憶體位址). number 5 125 在 cubeByReference中, 使用 *nPtr (*nPtr 等於外面的 number). nPtr undefined The original value of number is 5 The new value of number is 125
Fig. 7.8 Analysis of a typical call-by-value. (Part 1 of 2.) int main() { number = 5 ; number=cubeByValue(number); } cubeByValue( n ) return n * n * n; number n Before main calls cubeByValue : undefined int main() { number = 5 ; number = cubeByValue( number ); } cubeByValue( n ) return n * n * n; number n After cubeByValue receives the call: 125 int cubeByValue( n ) { return n * n * n; } main() number = 5 ; number = cubeByValue( number ); number n After cubeByValue cubes parameter and before returns to main : Fig. 7.8 Analysis of a typical call-by-value. (Part 1 of 2.)
and before assigning the result to 125 int main() { number = 5 ; number = cubeByValue( number ); } cubeByValue( n ) return n * n * n; number n After cubeByValue returns to main and before assigning the result to : undefined 125 int main() { number = 5 ; number = cubeByValue( number ); } cubeByValue( n ) return n * n * n; number n After main completes the assignment to : undefined Fig. 7.8 Analysis of a typical call-by-value. (Part 2 of 2.)
receives the call and before is cubed: int main() { number = 5 ; cubeByReference( &number ); } void cubeByReference( *nPtr ) *nPtr = *nPtr * *nPtr * *nPtr; number nPtr Before main calls cubeByReference : undefined void cubeByReference( int *nPtr ) { *nPtr = *nPtr * *nPtr * *nPtr; } main() number = 5 ; cubeByReference( &number ); number nPtr After cubeByReference receives the call and before *nPtr is cubed: call establishes this pointer 125 void cubeByReference( int *nPtr ) { *nPtr = *nPtr * *nPtr * *nPtr; } main() number = 5 ; cubeByReference( &number ); number nPtr After *nPtr is cubed and before program control returns to main : called function modifies caller’s variable Fig. 7.9 Analysis of a typical call-by-reference with a pointer argument.
5.4 利用參照呼叫函式 常見的程式設計錯誤 5.5 需要對指標作 dereferencing 來拿到指標所指到的記憶體內容時卻忘記作的話,會產生錯誤。 良好的程式設計習慣 5.2 除非被呼叫的函式明確地需要修改呼叫者所傳入之引數值,否則都應使用傳值呼叫來傳遞引數,這才符合最小開放權限的原則。
5.5 將指標宣告為 const 的用法 const 修飾詞 const 指標 常數變數內容不可被改變 只能指向相同的記憶體位址 宣告時需要作初值化 int *const myPtr = &x; int *const -指向非常數資料的常數指標 const int *myPtr = &x; const int -指向常數資料的非常數指標 const int *const Ptr = &x; const int *const -指向常數資料的常數指標
指向非常數資料的非常數指標 1 // Fig. 5.10: fig05_10.cpp 2 // Nonconstant pointer to nonconstant data 9 #include <cctype> // prototypes for islower and toupper 11 void convertToUppercase( char * ); 13 int main() 14 { 15 char phrase[] = "characters and $32.98"; 17 cout << "The phrase before conversion is: " << phrase; 18 convertToUppercase( phrase ); 19 cout << "\nThe phrase after conversion is: " 20 << phrase << endl; 22 return 0; 24 } 27 void convertToUppercase( char *sPtr ) 28 { 29 while ( *sPtr != '\0' ) { // current character is not '\0' 31 if ( islower( *sPtr ) ) // if character is lowercase, 32 *sPtr = toupper( *sPtr ); // convert to uppercase 34 ++sPtr; // move sPtr to next character in string 36 } 38 } phrase c h a r … \0 C H A R 指向非常數資料的非常數指標 sPtr The phrase before conversion is: characters and $32.98 The phrase after conversion is: CHARACTERS AND $32.98
可攜性的小技巧 5.2 雖然 const 在 ANSI C 與 C++ 已完整定義,但某些編譯器並未適當地強制它的用途。 良好的程式設計習慣 5.3 使用函式前,檢查其函式原型以便判斷它的參數是否可修改
指向常數資料的非常數指標 1 // Fig. 5.11: fig05_11.cpp 2 // Nonconstant pointer to constant data 4 #include <iostream> 6 using std::cout; 7 using std::endl; 9 void printCharacters( const char * ); 11 int main() 12 { 13 char phrase[] = "print characters of a string"; 14 15 cout << "The string is:\n"; 16 printCharacters( phrase ); 17 cout << endl; 19 return 0; 21 } 25 void printCharacters( const char *sPtr ) 26 { 27 for ( ; *sPtr != '\0'; sPtr++ ) // no initialization 28 cout << *sPtr; 30 } phrase p r i n … \0 fig05_11.cpp (1 of 2) 指向常數資料的非常數指標 const char xPtr The string is: print characters of a string
fig05_12.cpp (1 of 1) fig05_12.cpp output (1 of 1) 1 // Fig. 5.12: fig05_12.cpp 2 // Nonconstant pointer to constant data 5 void f( const int * ); // prototype 7 int main() 8 { 9 int y; 11 f( &y ); // f attempts illegal modification 13 return 0; // indicates successful termination 15 } // end main 16 19 void f( const int *xPtr ) 20 { 21 *xPtr = 100; // error: cannot modify a const object 23 } // end function f y 100 fig05_12.cpp (1 of 1) fig05_12.cpp output (1 of 1) const int * xPtr d:\cpphtp4_examples\ch05\Fig05_12.cpp(21) : error C2166: l-value specifies const object
2 // Attempting to modify a constant pointer to 3 // non-constant data 1 // Fig. 5.13: fig05_13.cpp 2 // Attempting to modify a constant pointer to 3 // non-constant data 4 #include <iostream> 5 6 int main() 7 { 8 int x, y; 9 10 int * const ptr = &x; // ptr is a constant pointer to an 11 // integer. An integer can be modified 12 // through ptr, but ptr always points 13 // to the same memory location. 14 *ptr = 7; 15 ptr = &y; // error: ptr is const; cannot assign new address 16 17 return 0; 18 } x 7 y int * const ptr 改變 *ptr 是允許的 - x 並不是常數 改變 ptr 是錯誤 – ptr 是常數指標 Error E2024 Fig05_13.cpp 15: Cannot modify a const object in function main()
用 const 可加強最小開放權限的原則,使用此原則適當地設計軟體,可大幅減少除錯時間和不恰當的副作用,並使程式較容易修改與維護。 //Fig. 5.14: fig05_14.cpp constant pointer to constant data #include <iostream.h> int main() { int x = 5, y; const int *const ptr = &x; cout << *ptr << endl; *ptr = 7; ptr = &y; return 0; } 指向常數資料的常數指標 這兩行都有錯誤,不能改變所指到的 記憶體內容,也不可改變所指的位置 const int x 7 5 y int * const ptr Errors 軟體工程的觀點 5.1~5.3 用 const 可加強最小開放權限的原則,使用此原則適當地設計軟體,可大幅減少除錯時間和不恰當的副作用,並使程式較容易修改與維護。 在函式中某參數內容不應被修改的話,此參數就應宣告為 const 以確保它不會被無意地修改。 用傳值呼叫時只能改變一個值,這個值是用函式傳回值來改變。要修改多個值時,這些值都需要用 call-be-reference 來傳遞。
5.5 將指標宣告為 const 的用法 執行上的小技巧 5.1 使用指向常數資料的指標或參照來傳遞大量資料,可以達到 call-by-reference 的效率與 call-by-value 的安全性。 常見的程式設計錯誤 5.6 沒有給宣告為 const 的指標初值時,會造成語法錯誤。
5.6 使用傳參考呼叫的泡沫排序法 使用指標來實作泡沫排序法 虛擬碼 swap 函式必須接收兩個陣列元素的記憶體位址(用 &) 5.6 使用傳參考呼叫的泡沫排序法 使用指標來實作泡沫排序法 swap 函式必須接收兩個陣列元素的記憶體位址(用 &) 陣列元素預設的傳參數方式是傳值呼叫 使用指標與 * 運算子,swap 能夠將兩個陣列元素的內容互相對調。 虛擬碼 Initialize array print data in original order Call function bubblesort print sorted array Define bubblesort
Bubblesort 用指標的方式拿到陣列的實際元素位址,陣列的名稱是一個指標。 1 // Fig. 5.15: fig05_15.cpp 2 // This program puts values into an array, sorts the values into 3 // ascending order, and prints the resulting array. 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 #include <iomanip> 10 11 using std::setw; 12 13 void bubbleSort( int *, const int ); 14 15 int main() 16 { 17 const int arraySize = 10; 18 int a[ arraySize ] = { 2, 6, 4, 8, 10, 12, 89, 68, 45, 37 }; 19 int i; 20 21 cout << "Data items in original order\n"; 22 23 for ( i = 0; i < arraySize; i++ ) 24 cout << setw( 4 ) << a[ i ]; 25 26 bubbleSort( a, arraySize ); // sort the array 27 cout << "\nData items in ascending order\n"; 28 29 for ( i = 0; i < arraySize; i++ ) 30 cout << setw( 4 ) << a[ i ]; 31 32 cout << endl; 33 return 0; 34 } 1. Initialize array 1.1 Declare variables 2. Print array 2.1 Call bubbleSort 2.2 Print array Bubblesort 用指標的方式拿到陣列的實際元素位址,陣列的名稱是一個指標。
3. Define bubbleSort 3.1 Define swap Program Output 36 void bubbleSort( int *array, const int size ) 37 { 38 void swap( int * const, int * const ); 39 40 for ( int pass = 0; pass < size - 1; pass++ ) 41 42 for ( int j = 0; j < size - 1; j++ ) 43 44 if ( array[ j ] > array[ j + 1 ] ) 45 swap( &array[ j ], &array[ j + 1 ] ); 46 } 47 48 void swap( int * const element1Ptr, int * const element2Ptr ) 49 { 50 int hold = *element1Ptr; 51 *element1Ptr = *element2Ptr; 52 *element2Ptr = hold; 53 } 3. Define bubbleSort 3.1 Define swap Program Output swap 的 prototype 放在 bubble sort 中,只有 bubble sort 可以呼叫 swap swap 用指標方式拿陣列的元素位址,並作 dereferences 來修改原始的陣列元素。 仍將 array 當作陣列來處理 要傳元素的位址 Data items in original order 2 6 4 8 10 12 89 68 45 37 Data items in ascending order 2 4 6 8 10 12 37 45 68 89
5.6 使用傳參考呼叫的泡沫排序法 sizeof(單元運算子) sizeof 的運算元可以是 傳回將運算元的“位元組數” 5.6 使用傳參考呼叫的泡沫排序法 sizeof(單元運算子) 傳回將運算元的“位元組數” 以陣列為運算元的話,sizeof 會傳回 ( 每個元素的位元組數 ) * ( 元素個數 ) 若 sizeof( int ) = 4 則 int myArray[10]; cout << sizeof(myArray); 將輸出 40 sizeof 的運算元可以是 變數名稱 Variable names 型態名稱 Type names 常數值 Constant values
//Fig. 5.17: fig05_17.cpp 測試 sizeof #include <iostream.h> #include <iomanip> size_t getSize(int *); int main() { char c; short s; int i; long l; float f; double d; long double ld; int array[ 20 ], *ptr = array; cout << "sizeof c = " << sizeof c << "\tsizeof(char) = " << sizeof( char ) << "\nsizeof s = " << sizeof s << "\tsizeof(short) = " << sizeof( short ) << "\nsizeof i = " << sizeof i << "\tsizeof(int) = " << sizeof( int ) << "\nsizeof l = " << sizeof l << "\tsizeof(long) = " << sizeof( long ) << "\nsizeof f = " << sizeof f << "\tsizeof(float) = " << sizeof( float ) << "\nsizeof d = " << sizeof d << "\tsizeof(double) = " << sizeof( double ) << "\nsizeof ld = " << sizeof ld << "\tsizeof(long double) = " << sizeof( long double ) << "\nsizeof array = " << sizeof array << "\nsizeof ptr = " << sizeof ptr << “\nnumber of bytes returned by getSize is “ << getSize(array) << endl; return 0; } size_t getSize(int *ptr) { return sizeof(ptr);
將函式原型放在其他函式中,限制此函式只能被有函式原型的函式呼叫,這樣能加強最小開放權限原則。 sizeof c = 1 sizeof(char) = 1 sizeof s = 2 sizeof(short) = 2 sizeof i = 4 sizeof(int) = 4 sizeof l = 4 sizeof(long) = 4 sizeof f = 4 sizeof(float) = 4 sizeof d = 8 sizeof(double) = 8 sizeof ld = 8 sizeof(long double) = 8 sizeof array = 80 sizeof ptr = 4 number of bytes returned by getSize is 4 軟體工程的觀點 5.4~5.5 將函式原型放在其他函式中,限制此函式只能被有函式原型的函式呼叫,這樣能加強最小開放權限原則。 將陣列傳到函式中時、也將陣列的大小傳進去(而不是將陣列大小預設在函式裡面),這樣可以函式更一般化,愈一般化的函式愈能夠被再使用。
5.6 使用傳參考呼叫的泡沫排序法 常見的程式設計錯誤 5.7~5.8 5.6 使用傳參考呼叫的泡沫排序法 常見的程式設計錯誤 5.7~5.8 將陣列傳到函式時,在函式中用 sizeof 運算子來找此陣列參數的位元組數,所得到的結果是指標的位元組數、而非陣列的位元組數。 以型態名稱當作 sizeof 的運算元,且省略括號時,就告成語法錯誤。 可攜性的小技巧 5.3 某些特定資料型態的位元組數在不同電腦系統中會有不同,當您寫的程式與這情形相關、且又會在不同電腦系統執行時,使用 sizeof 來決定這些資料型態的位元組數。 執行上的小技巧 5.2 sizeof 是編譯時期的單元運算子,而非執行時的函式,因此使用sizeof 不會降低執行效率。
5.7 指標運算式和指標的算術運算 指標算術運算 32位元的機器上 5 個元素的 int 陣列 5.7 指標運算式和指標的算術運算 指標算術運算 指標的遞增/遞減(increment/decrement) (++ or --) 將指標加/減一個整數 ( + or += , - or -=) 兩個指標可以相減 指標指到一個陣列上、作的算術運算才有意義 32位元的機器上 5 個元素的 int 陣列 指標 vPtr 指向陣列 v 的第一個元素 v[0],第3000的位置 vPtr = 3000 vPtr += 2; 則 vPtr 成為 3008 vPtr 變成指向 v[2] pointer variable vPtr v[0] v[1] v[2] v[4] v[3] 3000 3004 3008 3012 3016 location
5.7 指標運算式和指標的算術運算 指標的減法 指標的比較 5.7 指標運算式和指標的算術運算 指標的減法 將兩個指標之間的距離(數字值)算出來 vPtr2 = &v[ 2 ]; vPtr = &v[ 0 ]; vPtr2 - vPtr == 2 指標的比較 指到同一個陣列的兩個 pointers 比較、相減才有意義。 可測試哪一個指標所指的位置比較後面 測試指標是否指向 0 (NULL) if ( vPtr == 0 ) statement
指標的設定運算(pointer assignment) 5.7 指標運算式和指標的算術運算 指標的設定運算(pointer assignment) 若不是相同型態的指標,必須用轉型運算子(用 C 語言的作法) (float *) var_name 例外:指向 void 的指標 (type void *) 泛型指標,可表示任何型態 所有指標都可以指定給 void 的指標,不需要用強制轉換型態的運算子,但要將 void 指標 指定給其他指標時,仍要作強制轉換型態的動作。 void 的指標不能作 dereferenced
5.7 指標運算式和指標的算術運算 常見的程式設計錯誤 5.9~5.13 對沒有指向陣列的指標作算術運算,這是邏輯錯誤 5.7 指標運算式和指標的算術運算 常見的程式設計錯誤 5.9~5.13 對沒有指向陣列的指標作算術運算,這是邏輯錯誤 將兩個沒有指到相同陣列的指標作相減或比較,這也是邏輯錯誤。 使用指標算術運算,超過陣列範圍,這也是邏輯錯誤。 將一個指標指定給另一個不同型態的指標(不是 void *) ,若沒有使用強制型態轉換,這是語法錯誤。 對 void * 的指標作 dereference 是語法錯誤。
5.8 指標與陣列之間的關係 陣列與指標的關係非常密切 陣列的名稱就是一個常數指標 5.8 指標與陣列之間的關係 陣列與指標的關係非常密切 陣列的名稱就是一個常數指標 指標可以作陣列下標(array subscripting)的運算 若已宣告五個元素的陣列 b[ 5 ] 與一個指標 bPtr 將 b 的位址存到 bPtr bPtr = b bptr 就等於是陣列 b 最前面元素的位址 bPtr == &b[ 0 ] 良好的程式設計習慣 5.4 當對陣列作操作時,使用陣列符號而不用指標的符號,這樣比較清楚,雖然需要花比較多的編譯時間。
5.8 指標與陣列之間的關係 用指標來存取陣列的元素 陣列元素 b[ n ] 可用 *( bPtr + n ) 來存取 5.8 指標與陣列之間的關係 用指標來存取陣列的元素 陣列元素 b[ n ] 可用 *( bPtr + n ) 來存取 稱為指標/偏移量表示法 陣列本身也可以用指標算術運算 b[ 3 ] 與 *(b + 3) 相同 指標也可以用下標,指標/下標表示法 (pointer/subscript notation) bPtr[ 3 ] 與 b[ 3 ] 相同 常見的程式設計錯誤 5.14 雖然陣列名稱是指向陣列開頭的指標,而指標所指的位置可以用算術運算式加以修改,但陣列名稱不能修改,因為陣列名稱是常數指標。
// Fig. 5.20: fig05_20.cpp 示範陣列與指標的關係 #include <iostream> using std::cout; using std::endl; int main(){ int b[] = { 10, 20, 30, 40 }, i, offset; int *bPtr = b; // set bPtr to point to array b cout << "Array b printed with:\n" << "Array subscript notation\n"; for ( i = 0; i < 4; i++ ) cout << "b[" << i << "] = " << b[ i ] << '\n'; cout << "\nPointer/offset notation where\n" << "the pointer is the array name\n"; for ( offset = 0; offset < 4; offset++ ) cout << "*(b + " << offset << ") = " << *( b + offset ) << '\n'; cout << "\nPointer subscript notation\n"; cout << "bPtr[" << i << "] = " << bPtr[ i ] << '\n'; cout << "\nPointer/offset notation\n"; cout << "*(bPtr + " << offset << ") = " << *( bPtr + offset ) << '\n'; return 0; }
Array b printed with: Array subscript notation b[0] = 10 b[1] = 20 b[2] = 30 b[3] = 40 Pointer/offset notation where the pointer is the array name *(b + 0) = 10 *(b + 1) = 20 *(b + 2) = 30 *(b + 3) = 40 Pointer subscript notation bPtr[0] = 10 bPtr[1] = 20 bPtr[2] = 30 bPtr[3] = 40 Pointer/offset notation *(bPtr + 0) = 10 *(bPtr + 1) = 20 *(bPtr + 2) = 30 *(bPtr + 3) = 40
// Fig. 5.21: fig05_21.cpp 用指標與陣列方式作複製字串的涵數 #include <iostream> using std::cout; using std::endl; void copy1( char *, const char * ); void copy2( char *, const char * ); int main(){ char string1[ 10 ], *string2 = "Hello", string3[ 10 ], string4[] = "Good Bye"; copy1( string1, string2 ); cout << "string1 = " << string1 << endl; copy2( string3, string4 ); cout << "string3 = " << string3 << endl; return 0; } void copy1( char *s1, const char *s2 ){ for ( int i = 0; ( s1[ i ] = s2[ i ] ) != '\0'; i++ ) ; // do nothing in body void copy2( char *s1, const char *s2 ){ for ( ; ( *s1 = *s2 ) != '\0'; s1++, s2++ ) string1 = Hello string3 = Good Bye
5.9 指標陣列 陣列可以包含指標 通常用來儲存字串的陣列 suit 的每個元素是指向字元(字串)的指標 5.9 指標陣列 陣列可以包含指標 通常用來儲存字串的陣列 char *suit[ 4 ] = {"Hearts", "Diamonds", "Clubs", "Spades" }; suit 的每個元素是指向字元(字串)的指標 字串本身不在陣列中、只有指向字串的指標在陣列中 suit 這個陣列的大小固定、但字串可以任意大小。 suit[3] suit[2] suit[1] suit[0] ’H’ ’e’ ’a’ ’r’ ’t’ ’s’ ’\0’ ’D’ ’i’ ’m’ ’o’ ’n’ ’d’ ’C’ ’l’ ’u’ ’b’ ’S’ ’p’
5.10 範例研究:模擬洗牌和發牌的程式 洗牌的程式 使用一個指向字串的指標陣列來存紙牌的名稱 用二維陣列來當作紙牌(用花色與點數當作下標) 5.10 範例研究:模擬洗牌和發牌的程式 洗牌的程式 使用一個指向字串的指標陣列來存紙牌的名稱 用二維陣列來當作紙牌(用花色與點數當作下標) 1-52 的數字代表其發牌順序 將 1 到 52 的數字隨機地放到 suit 這個陣列還沒有放數字的位置上。 Hearts Diamonds Clubs Spades 1 2 3 Ace Two Three Four Five Six Seven Eight Nine Ten Jack Queen King 4 5 6 7 8 9 10 11 12 deck[2][12] represents the King of Clubs
5.10 範例研究:模擬洗牌和發牌的程式 模擬洗牌與發牌的虛擬碼 第三次細部修改 第一次細部修改 第二次細部修改 5.10 範例研究:模擬洗牌和發牌的程式 模擬洗牌與發牌的虛擬碼 第三次細部修改 Choose slot of deck randomly While chosen slot of deck has been previously chosen Choose slot of deck randomly Place card number in chosen slot of deck 第一次細部修改 第二次細部修改 Initialize the suit array Initialize the face array Initialize the deck array Shuffle the deck Deal 52 cards For each of the 52 cards Place card number in randomly selected unoccupied slot of deck For each of the 52 cards Find card number in deck array and print face and suit of card For each slot of the deck array If slot contains card number Print the face and suit of the card
1. Initialize suit and face arrays 1 // Fig. 5.24: fig05_24.cpp 2 // Card shuffling dealing program 3 #include <iostream> 4 5 using std::cout; 6 using std::ios; 7 8 #include <iomanip> 9 10 using std::setw; 11 using std::setiosflags; 12 13 #include <cstdlib> 14 #include <ctime> 15 16 void shuffle( int [][ 13 ] ); 17 void deal( const int [][ 13 ], const char *[], const char *[] ); 18 19 int main() 20 { 21 const char *suit[ 4 ] = 22 { "Hearts", "Diamonds", "Clubs", "Spades" }; 23 const char *face[ 13 ] = 24 { "Ace", "Deuce", "Three", "Four", 25 "Five", "Six", "Seven", "Eight", 26 "Nine", "Ten", "Jack", "Queen", "King" }; 27 int deck[ 4 ][ 13 ] = { 0 }; 28 29 srand( time( 0 ) ); 30 31 shuffle( deck ); 32 deal( deck, face, suit ); 33 1. Initialize suit and face arrays 1.1 Initialize deck array 2. Call function shuffle 2.1 Call function deal
會產生 indefinite postponement 的現象,不確定會延遲多久。 34 return 0; 35 } 36 37 void shuffle( int wDeck[][ 13 ] ) 38 { 39 int row, column; 40 41 for ( int card = 1; card <= 52; card++ ) { 42 do { 43 row = rand() % 4; 44 column = rand() % 13; 45 } while( wDeck[ row ][ column ] != 0 ); 46 47 wDeck[ row ][ column ] = card; 48 } 49 } 50 51 void deal( const int wDeck[][ 13 ], const char *wFace[], 52 const char *wSuit[] ) 53 { 54 for ( int card = 1; card <= 52; card++ ) 55 56 for ( int row = 0; row <= 3; row++ ) 57 58 for ( int column = 0; column <= 12; column++ ) 59 60 if ( wDeck[ row ][ column ] == card ) 61 cout << setw( 5 ) << setiosflags( ios::right ) 62 << wFace[ column ] << " of " 63 << setw( 8 ) << setiosflags( ios::left ) 64 << wSuit[ row ] 65 << ( card % 2 == 0 ? '\n' : '\t' ); 66 } The numbers 1-52 are randomly placed into the deck array. 3. Define functions 會產生 indefinite postponement 的現象,不確定會延遲多久。 Searches deck for the card number, then prints the face and suit. 三層迴圈速度很慢
這個洗牌發牌的程式可以寫得更好、速度更快。 Six of Clubs Seven of Diamonds Ace of Spades Ace of Diamonds Ace of Hearts Queen of Diamonds Queen of Clubs Seven of Hearts Ten of Hearts Deuce of Clubs Ten of Spades Three of Spades Ten of Diamonds Four of Spades Four of Diamonds Ten of Clubs Six of Diamonds Six of Spades Eight of Hearts Three of Diamonds Nine of Hearts Three of Hearts Deuce of Spades Six of Hearts Five of Clubs Eight of Clubs Deuce of Diamonds Eight of Spades Five of Spades King of Clubs King of Diamonds Jack of Spades Deuce of Hearts Queen of Hearts Ace of Clubs King of Spades Three of Clubs King of Hearts Nine of Clubs Nine of Spades Four of Hearts Queen of Spades Eight of Diamonds Nine of Diamonds Jack of Diamonds Seven of Clubs Five of Hearts Five of Diamonds Four of Clubs Jack of Hearts Jack of Clubs Seven of Spades Program Output 這個洗牌發牌的程式可以寫得更好、速度更快。
5.10 範例研究:模擬洗牌和發牌的程式 Exercise 5.16 對應關係:1-52 數字代表 52 張不同的牌 5.10 範例研究:模擬洗牌和發牌的程式 Exercise 5.16 對應關係:1-52 數字代表 52 張不同的牌 A 2 3 4 5 6 7 8 9 10 J Q K Hearts 1 11 12 13 Diamonds 14 15 16 17 18 19 20 21 22 23 24 25 26 Clubs 27 28 29 30 31 32 33 34 35 36 37 38 39 Spades 40 41 42 43 44 45 46 47 48 49 50 51 52 洗牌:從第一個位置起,隨機找出另一個位置,與另一個位置的數字對調。發牌順序:diamonds 6, spades A, … 1 2 3 4 5 6 7 8 9 10 11 12 19 40 27 25 36 46 34 35 41 18 44 13 28 14 16 21 30 31 17 24 33 15 42 43 23 45 29 32 47 26 50 38 52 39 48 51 37 49 22 20