40
Pointers and Pointer-Based Strings (Part II)

Chapter 08

  • Upload
    emiko

  • View
    25

  • Download
    0

Embed Size (px)

DESCRIPTION

Chapter 08. Pointers and Pointer-Based Strings (Part II). 教學目標. 在這部分你會學到: 指標與陣列之間的關係 使用 const 指標 實作選擇排序法( selection sort ) sizeof 運算子. 8.9 指標與陣列的關係. 陣列的名稱可以被當成指標。 指標也可以用來作陣列索引( index )的運算。. 8.9 指標與陣列的關係. 陣列的名稱可以被當成指標。 用指標來存取陣列中的元素 例: b[n] 可以用 *(bPtr+n) 來表示 - PowerPoint PPT Presentation

Citation preview

Page 1: Chapter 08

Pointers and Pointer-Based Strings

(Part II)

Page 2: Chapter 08

在這部分你會學到: 指標與陣列之間的關係 使用 const 指標 實作選擇排序法( selection sort )sizeof 運算子

Page 3: Chapter 08

陣列的名稱可以被當成指標。 指標也可以用來作陣列索引( index )的運算。

Page 4: Chapter 08

陣列的名稱可以被當成指標。 用指標來存取陣列中的元素 例:

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;

Page 5: Chapter 08

陣列的名稱實際上代表了個陣列的開頭位址;假設我們有一個數字陣列,則可以用一個指向數字的指標來記住其中一個元素的記憶體位址。

如何取得開頭的位址?1. vPtr = v;2. vPtr = &v[0];

Page 6: Chapter 08
Page 7: Chapter 08

那麼,如何從一個位址中取得這個位址所儲存的 value ? 利用「 * 」:反參照運算子 如何得到 vPtr+1 、 vPtr+2 、…、 vPtr+4 這些位

址中所儲存的資料?*(vPtr+1)*(vPtr+2)*(vPtr+3)*(vPtr+4)

Page 8: Chapter 08

令vPtr = v;

• 問: vPtr 現在記錄的是哪一個元素的位址? &v[3] 等同於 vPtr+3 v[3] 等同於 *(vPtr+3) 陣列的名稱指標變數可以交互使用

vPtr[3] 等同於 v[3] *(vPtr+3) 等同於 *(v+3)

Page 9: Chapter 08

陣列的名稱指標變數可以互用。 例:

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 )

陣列 / 索引法( array/subscript notation )

指標 / 位移法( pointer/offset notation )

指標 / 位移法( pointer/offset notation )

陣列 / 位移法( array/offset notation )陣列 / 位移法( array/offset notation )

指標 / 索引法( array/subscript notation )

指標 / 索引法( array/subscript notation )

Page 10: Chapter 08

1 // Fig. 8.20: fig08_20.cpp

2 // Using subscripting and pointer notations with arrays.

3 #include <iostream>

4 using std::cout;

5 using std::endl;

6

7 int main()

8 {

9 int b[] = { 10, 20, 30, 40 }; // create 4-element array b

10 int *bPtr = b; // set bPtr to point to array b

11

12 // output array b using array subscript notation

13 cout << "Array b printed with:\n\nArray subscript notation\n";

14

15 for ( int i = 0; i < 4; i++ )

16 cout << "b[" << i << "] = " << b[ i ] << '\n';

17

18 // output array b using the array name and pointer/offset notation

19 cout << "\nPointer/offset notation where "

20 << "the pointer is the array name\n";

21

22 for ( int offset1 = 0; offset1 < 4; offset1++ )

23 cout << "*(b + " << offset1 << ") = " << *( b + offset1 ) << '\n';

array / subscript notation

array /offset notation

Page 11: Chapter 08

24

25 // output array b using bPtr and array subscript notation

26 cout << "\nPointer subscript notation\n";

27

28 for ( int j = 0; j < 4; j++ )

29 cout << "bPtr[" << j << "] = " << bPtr[ j ] << '\n';

30

31 cout << "\nPointer/offset notation\n";

32

33 // output array b using bPtr and pointer/offset notation

34 for ( int offset2 = 0; offset2 < 4; offset2++ )

35 cout << "*(bPtr + " << offset2 << ") = "

36 << *( bPtr + offset2 ) << '\n';

37

38 return 0; // indicates successful termination

39 } // end main

Pointer/subscript notation

pointer/offset notation

Page 12: Chapter 08

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

Page 13: Chapter 08

雖然指標變數和陣列名稱可以互用,但要記得指標變數中的位址是可以修改的,陣列名稱所代表的位址則不能修改。 例如:

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 中的哪一個元素

加完的 bPtr 現在指向陣列 b 中的哪一個元素

請行會印出什麼?請行會印出什麼?

語法錯誤,為什麼?語法錯誤,為什麼?

Page 14: Chapter 08

雖然指標變數和陣列名稱可以互用,為簡潔起見,操作陣列時仍以陣列索引法來使用。 何時會用到指標來代替陣列?

宣告陣列的時候長度未定,先用指標代替宣告,日後再指定大小。儲存陣列的記憶體位址。

注意:不論是陣列或指標的表示法,都不會自動為使用者

提供範圍的判斷,因此在作索引存取或位移存取,都必須小心是否超出陣列的範圍。

Page 15: Chapter 08

1 // Fig. 8.21: fig08_21.cpp

2 // Copying a string using array notation and pointer notation.

3 #include <iostream>

4 using std::cout;

5 using std::endl;

6

7 void copy1( char *, const char * ); // prototype

8 void copy2( char *, const char * ); // prototype

9

10 int main()

11 {

12 char string1[ 10 ];

13 char *string2 = "Hello";

14 char string3[ 10 ];

15 char string4[] = "Good Bye";

16

17 copy1( string1, string2 ); // copy string2 into string1

18 cout << "string1 = " << string1 << endl;

19

20 copy2( string3, string4 ); // copy string4 into string3

21 cout << "string3 = " << string3 << endl;

22 return 0; // indicates successful termination

23 } // end main

Page 16: Chapter 08

24

25 // copy s2 to s1 using array notation

26 void copy1( char * s1, const char * s2 )

27 {

28 // copying occurs in the for header

29 for ( int i = 0; ( s1[ i ] = s2[ i ] ) != '\0'; i++ )

30 ; // do nothing in body

31 } // end function copy1

32

33 // copy s2 to s1 using pointer notation

34 void copy2( char *s1, const char *s2 )

35 {

36 // copying occurs in the for header

37 for ( ; ( *s1 = *s2 ) != '\0'; s1++, s2++ )

38 ; // do nothing in body

39 } // end function copy2 string1 = Hello string3 = Good Bye

注意:參數宣告用指標,代表接收到記憶體位址。使用卻用索引的方式來便利取用元素

參數宣告用指標,代表接收到記憶體位址。使用反參照的方式來取用元素

將 s1 和 s2 累加,來將位址移動到下一個元素。

問題一:前面我們說過,陣列的名稱中所代表記憶體位址不能改變,何以這個地方,我們可以把 s1 和 s2 做 ++ ?

問題二:根據我們目前所學,加入我們有一個數字的陣列,討論為什麼當傳入整個陣列的時候,是傳參考呼叫,傳入某一個陣列元素的時候,卻變成傳值呼叫?

問題三: const 代表唯讀,那麼在參數的地方宣告「 s2 為 const 」,為什麼 s2 還可以 ++ ?

Page 17: Chapter 08

最小權限原則( Principle of least privilege ) 讓函式透過他的參數取得足夠的資料以便完成工作,

但不得擁有過大的存取權限。 在參數中使用 const :

規範參數為唯讀。 可以從此參數中讀取資料,但不得修改或變更其參數。

將一個指標變數宣告為 const ?const 指標

例如: const char *s2 請問是 s2 中的內容(記憶體位址)不能改,還是 s2 所

指向的位址中的內容不能改?s2 k

Page 18: Chapter 08

由於指標是間接取值,在使用 const 時,有四種方式來分別指定不同的存取權限: 指向非常數( non-constant )資料的非常數指標

指標指向的位址可以變更,指向位址的內容也可以改 指向常數資料的非常數指標

指標指向的位址可以變更,指向位址的內容不能改 指向非常數資料的常數指標

指標指向的位址不能變更,但指向位址的內容可以改 指向常數資料的常數指標

指標指向的位址不能變更,指向位址的內容也不能改

Page 19: Chapter 08

1 // Fig. 8.10: fig08_10.cpp

2 // Converting lowercase letters to uppercase letters

3 // using a non-constant pointer to non-constant data.

4 #include <iostream>

5 using std::cout;

6 using std::endl;

7

8 #include <cctype> // prototypes for islower and toupper

9 using std::islower;

10 using std::toupper;

11

12 void convertToUppercase( char * );

13

14 int main()

15 {

16 char phrase[] = "characters and $32.98";

17

18 cout << "The phrase before conversion is: " << phrase;

19 convertToUppercase( phrase );

20 cout << "\nThe phrase after conversion is: " << phrase << endl;

21 return 0; // indicates successful termination

22 } // end main

convertToUppercase 函式具有修改參數的權限

沒有加 const

Page 20: Chapter 08

23

24 // convert string to uppercase letters

25 void convertToUppercase( char *sPtr )

26 {

27 while ( *sPtr != '\0' ) // loop while current character is not '\0'

28 {

29 if ( islower( *sPtr ) ) // if character is lowercase,

30 *sPtr = toupper( *sPtr ); // convert to uppercase

31

32 sPtr++; // move sPtr to next character in string

33 } // end while

34 } // end function convertToUppercase The phrase before conversion is: characters and $32.98 The phrase after conversion is: CHARACTERS AND $32.98

參數 sPtr 是一個指向非常數資料的非常數指標

如果參數為小寫字元則函式islower 回傳 true

如果是小寫則回傳大寫字元,如果不是,則回傳原來的字元(請注意, sPtr所指向的位址是可以被修改的)

修改 sPtr 中所儲存的記憶體位址

Page 21: Chapter 08

指標內的位址可以改,但指向的位址中的資料不能改 提供 pass-by-reference (可以藉由位址找到原引數的

內容),但藉此保護其資料。 宣告方式:

將 const 寫在型態的前面。 例如:

const char *s2;

s2 k

這樣寫的意思,代表 s2 的內容可以改,但s2 所指向的位址 k 中的內容不能改。

Page 22: Chapter 08

1 // Fig. 8.12: fig08_12.cpp

2 // Attempting to modify data through a

3 // non-constant pointer to constant data.

4

5 void f( const int * ); // prototype

6

7 int main()

8 {

9 int y;

10

11 f( &y ); // f attempts illegal modification

12 return 0; // indicates successful termination

13 } // end main

將變數 y 的記憶體位址傳給函式f

參數是一個指向常數資料的非常數指標

14

15 // xPtr cannot modify the value of constant variable to which it points

16 void f( const int *xPtr )

17 {

18 *xPtr = 100; // error: cannot modify a const object

19 } // end function f

Borland C++ command-line compiler error message:

Error E2024 fig08_12.cpp 18: Cannot modify a const object in function f(const int *)

Microsoft Visual C++ compiler error message:

c:\cpphtp5_examples\ch08\Fig08_12\fig08_12.cpp(18) : error C2166: l-value specifies const object

GNU C++ compiler error message:

fig08_12.cpp: In function `void f(const int*)': fig08_12.cpp:18: error: assignment of read-only location

嘗試透過反參照運算子修改xPtr指向位址的內容

編譯錯誤

Page 23: Chapter 08

1 // Fig. 8.11: fig08_11.cpp

2 // Printing a string one character at a time using

3 // a non-constant pointer to constant data.

4 #include <iostream>

5 using std::cout;

6 using std::endl;

7

8 void printCharacters( const char * ); // print using pointer to const data

9

10 int main()

11 {

12 const char phrase[] = "print characters of a string";

13

14 cout << "The string is:\n";

15 printCharacters( phrase ); // print characters in phrase

16 cout << endl;

17 return 0; // indicates successful termination

18 } // end main

19

20 // sPtr can be modified, but it cannot modify the character to which

21 // it points, i.e., sPtr is a "read-only" pointer

22 void printCharacters( const char *sPtr )

23 {

24 for ( ; *sPtr != '\0'; sPtr++ ) // no initialization

25 cout << *sPtr; // display character without modification

26 } // end function printCharacters The string is: print characters of a string

參數是一個指向常數資料的非常數指標

傳指標給函式

sPtr 是一個指向常數資料的非常數指標, sPtr 裡面所儲存的位址可以改(非常數),指向位址的資料不能改(常數)。

因為 sPtr 是非常數指標,所以可以作 ++ 的動作。

Page 24: Chapter 08

當大型的資料傳進來當參數,且被呼叫的函式不需更改它們,可使用指向常數資料的指標或參照,以達到傳址呼叫的高效率。 因為是指向常數資料,因此可以為原參數的內容提供保

護。

Page 25: Chapter 08

指標內的位址不可以改,但指向位址中的資料可以改永遠指向同一個記憶體位址,或透過索引法或位移法來存

取 陣列名稱在預設情況下,為一個指向常數資料的常數指標

數值可以透過反參照運算子來修改 在宣告的時候必須要給初始值(初始的位址)。 怎麼宣告?將 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本身所儲存的位址不能改

Page 26: Chapter 08

1 // Fig. 8.13: fig08_13.cpp

2 // Attempting to modify a constant pointer to non-constant data.

3

4 int main()

5 {

6 int x, y;

7

8 // ptr is a constant pointer to an integer that can

9 // be modified through ptr, but ptr always points to the

10 // same memory location.

11 int * const ptr = &x; // const pointer must be initialized

12

13 *ptr = 7; // allowed: *ptr is not const

14 ptr = &y; // error: ptr is const; cannot assign to it a new address

15 return 0; // indicates successful termination

16 } // end main

Borland C++ command-line compiler error message:

Error E2024 fig08_13.cpp 14: Cannot modify a const object in function main()s

Microsoft Visual C++ compiler error message:

c:\cpphtp5e_examples\ch08\Fig08_13\fig08_13.cpp(14) : error C2166: l-value specifies const object GNU C++ compiler error message: fig08_13.cpp: In function `int main()': fig08_13.cpp:14: error: assignment of read-only variable `ptr'

ptr 是一個指向非常數資料的常數指標,注意一開始的時候就要給初始值

可以透過指標來修改 x的內容

但不可以修改 ptr 當中以存放的位址

上述寫法會發生編譯錯誤

Page 27: Chapter 08

指標內的位址不能改,但指向位址中的資料也不能改 存取權最小 宣告方式 在最前面要加 const 在「 * 」後面也要加 const

Page 28: Chapter 08

1 // Fig. 8.14: fig08_14.cpp

2 // Attempting to modify a constant pointer to constant data.

3 #include <iostream>

4 using std::cout;

5 using std::endl;

6

7 int main()

8 {

9 int x = 5, y;

10

11 // ptr is a constant pointer to a constant integer.

12 // ptr always points to the same location; the integer

13 // at that location cannot be modified.

14 const int *const ptr = &x;

15

16 cout << *ptr << endl;

17

18 *ptr = 7; // error: *ptr is const; cannot assign new value

19 ptr = &y; // error: ptr is const; cannot assign new address

20 return 0; // indicates successful termination

21 } // end main

ptr 是一個指向常數資料的常數指標(注意要給初始值)

因為 *ptr 是常數,所以不能修改

因為 ptr 也是常數,所以也不能修改

以上的行為都會造成編譯的錯誤

Page 29: Chapter 08

何謂選擇排序法( 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

Page 30: Chapter 08

令 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 中找到最小值?如何從元素 i到 n-1 中找到最小值?

Page 31: Chapter 08

請以 selection sort 來說明以下陣列排序的過程:

34 56

0 1

4 10

2 3

77 51

4 5

93 30

6 7

5 52

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

0 1 2 3 4 5 6 7 8 9 10

Page 32: Chapter 08

1 // Fig. 8.15: fig08_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 using std::cout;

6 using std::endl;

7

8 #include <iomanip>

9 using std::setw;

10

11 void selectionSort( int * const, const int ); // prototype

12 void swap( int * const, int * const ); // prototype

13

14 int main()

15 {

16 const int arraySize = 10;

17 int a[ arraySize ] = { 2, 6, 4, 8, 10, 12, 89, 68, 45, 37 };

18

19 cout << "Data items in original order\n";

20

21 for ( int i = 0; i < arraySize; i++ )

22 cout << setw( 4 ) << a[ i ];

23

24 selectionSort( a, arraySize ); // sort the array

25

26 cout << "\nData items in ascending order\n";

27

28 for ( int j = 0; j < arraySize; j++ )

29 cout << setw( 4 ) << a[ j ];

Page 33: Chapter 08

30

31 cout << endl;

32 return 0; // indicates successful termination

33 } // end main

34

35 // function to sort an array

36 void selectionSort( int * const array, const int size )

37 {

38 int smallest; // index of smallest element

39

40 // loop over size - 1 elements

41 for ( int i = 0; i < size - 1; i++ )

42 {

43 smallest = i; // first index of remaining array

44

45 // loop to find index of smallest element

46 for ( int index = i + 1; index < size; index++ )

47

48 if ( array[ index ] < array[ smallest ] )

49 smallest = index;

50

51 swap( &array[ i ], &array[ smallest ] );

52 } // end if

53 } // end function selectionSort

array 是一個指向 ______ 資料的_______ 指標

size 是用來傳入 array 的大小,應不會對他的內容作修改,因此宣告為 const ,代表只能讀,不能作修改

注意這個變數 smallest ,用來記住目前最小值是哪個元素(用他的索引來代表),一開始,初始化為 i(因為從 i開始找)

Page 34: Chapter 08

54

55 // swap values at memory locations to which

56 // element1Ptr and element2Ptr point

57 void swap( int * const element1Ptr, int * const element2Ptr )

58 {

59 int hold = *element1Ptr;

60 *element1Ptr = *element2Ptr;

61 *element2Ptr = hold;

62 } // end function swap

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

請問 const 在這裡的意義?

Page 35: Chapter 08

定義 回傳其運算元的大小(單位:位元組( bytes )) 如果運算元提供的是一個陣列的名稱,則會計算出

(一個元素的大小 ) * ( 元素的個數 )換言之,就是計算出陣列佔有多少個位元組

範例:假設一個 int 有 4 個 bytes int myArray[ 10 ]; cout << sizeof( myArray );

將會印出? sizeof 接受的運算元可以是

變數名稱 型態名稱 常數

Page 36: Chapter 08

雖然陣列名稱可以和指標變數互用,但使用sizeof 時,有以下的問題需注意,運算元是一個陣列的時候才會計算出陣列的總位元組大小 .

運算元是指標,則回傳的是指標的位元組大小。

Page 37: Chapter 08

1 // Fig. 8.16: fig08_16.cpp

2 // Sizeof operator when used on an array name

3 // returns the number of bytes in the array.

4 #include <iostream>

5 using std::cout;

6 using std::endl;

7

8 size_t getSize( double * ); // prototype

9

10 int main()

11 {

12 double array[ 20 ]; // 20 doubles; occupies 160 bytes on our system

13

14 cout << "The number of bytes in the array is " << sizeof( array );

15

16 cout << "\nThe number of bytes returned by getSize is "

17 << getSize( array ) << endl;

18 return 0; // indicates successful termination

19 } // end main

20

21 // return size of ptr

22 size_t getSize( double *ptr )

23 {

24 return sizeof( ptr );

25 } // end function getSize The number of bytes in the array is 160 The number of bytes returned by getSize is 4

如果運算元是陣列,則回傳的是陣列的總 bytes 數

函式 getSize回傳的是用來儲存array這個位址所需要的 byte大小

Ptr 這個指標變數的大小

Page 38: Chapter 08

如何計算陣列的長度double realArray[ 22 ];

int length = sizeof realArray / sizeof( double );只有在運算元是型態的時候才需要加刮號,可是一般為了避免混淆,在此建議使用 sizeof 的時候,將所有的運算元加上刮號。

Page 39: Chapter 08

1 // Fig. 8.17: fig08_17.cpp

2 // Demonstrating the sizeof operator.

3 #include <iostream>

4 using std::cout;

5 using std::endl;

6

7 int main()

8 {

9 char c; // variable of type char

10 short s; // variable of type short

11 int i; // variable of type int

12 long l; // variable of type long

13 float f; // variable of type float

14 double d; // variable of type double

15 long double ld; // variable of type long double

16 int array[ 20 ]; // array of int

17 int *ptr = array; // variable of type int *

Page 40: Chapter 08

18

19 cout << "sizeof c = " << sizeof c

20 << "\tsizeof(char) = " << sizeof( char )

21 << "\nsizeof s = " << sizeof s

22 << "\tsizeof(short) = " << sizeof( short )

23 << "\nsizeof i = " << sizeof i

24 << "\tsizeof(int) = " << sizeof( int )

25 << "\nsizeof l = " << sizeof l

26 << "\tsizeof(long) = " << sizeof( long )

27 << "\nsizeof f = " << sizeof f

28 << "\tsizeof(float) = " << sizeof( float )

29 << "\nsizeof d = " << sizeof d

30 << "\tsizeof(double) = " << sizeof( double )

31 << "\nsizeof ld = " << sizeof ld

32 << "\tsizeof(long double) = " << sizeof( long double )

33 << "\nsizeof array = " << sizeof array

34 << "\nsizeof ptr = " << sizeof ptr << endl;

35 return 0; // indicates successful termination

36 } // end main

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