550 likes | 753 Vues
第 八 章. 指標 (Pointer). 大綱. 8.7 指標與函數 8.8 指標與結構 8.9 指標中之指標 8.10 指標指向函數 8.11 指標陣列 8.12 指標常犯之錯誤. 8.1 位 址運算子 & 8.2 指標之宣告 8.3 指標之使用 8.4 記憶體配置 8.5 指標與陣列 8.6 指標與字串. 8.1 位 址運算子 &. 一般變數皆為靜態的資料,若欲取得該變數之位址,只能使用 位址運算子 ( Address Operator ) 「 & 」 ( Ampersand ) int a=10;
E N D
第八章 指標(Pointer)
大綱 • 8.7 指標與函數 • 8.8 指標與結構 • 8.9 指標中之指標 • 8.10 指標指向函數 • 8.11 指標陣列 • 8.12 指標常犯之錯誤 • 8.1 位址運算子& • 8.2 指標之宣告 • 8.3 指標之使用 • 8.4 記憶體配置 • 8.5 指標與陣列 • 8.6 指標與字串
8.1 位址運算子 & • 一般變數皆為靜態的資料,若欲取得該變數之位址,只能使用位址運算子(Address Operator) 「&」(Ampersand) • int a=10; • cout << &a; //輸出位址 00A5 • cout << a; //輸出內容 10
例題: 列印一般變數之位址。 #include <iostream> //cout using namespace std; int main( ){ short a=10, b; //短整數佔2bytes int c=23, d; //整數佔4 bytes float p=1.1, q; //浮點數佔4bytes cout << “&a=” << &a << endl; cout << “&b=” << &b << endl; cout << ”&c=” << &c << endl; cout << “&d=” << &d << endl; cout << ”&p=” << &p <<endl; cout << “&q=” << &q << endl; return 0; } 位址會因電腦不同而異
8.2 指標之宣告 • 指標是一種間接指向,在宣告上與一般變數不同。 • 資料型態 *變數名稱; //星號在變數之左上方 如 int *a, *b, *c; • 資料型態* 變數名稱; //星號在資料型態之右上 如 int* a, * b, * c; • 宣告 int *ptr 後變數ptr稱為指標變數,它是儲存『位址的值』, *ptr為內容,其意義如下: • *ptr: 表ptr位址內所指示之內容 • ptr: 表儲存位址
8.3 指標之使用 • 指標變數在使用時,不可指向不存在(void)之位置,否則會造成不可預料的結果,使用指標應遵循下述兩種狀況: • 1. 指向已存在之位址。 • 2. 要求分配記憶體。 • 讓指標指向已存在位址就是將一般變數之位址設定給指標變數,讓一般變數與指標共用同一塊記憶體,只要一般變數內容改變,指標變數之內容也會改變,反之亦然。 • int a=10,*p; • p=&a; a 10 *ptr
8-3-1指標與位址運算子 • *與&兩者皆為指標之參考符號 • 兩者之關係如: • int *ptr, a=10; //宣告ptr為指標變數 • ptr = &a; //ptr存變數a的位址 • 將a之位址設定給ptr,接著兩變數皆指向同一塊記憶體,變數利用「&」取得位址 。 a 10 *ptr
8-3-2 指標型態之一致性 • 指標型態之一致性: 指向已存在之指標變數應與其資料型態一致,因不同型態之指標是無法轉換的。 • 列印數值(內容): • 一般變數 cout << a; • 指標變數 cout << *ptr; • 列印位址: • 位址運算子 cout << &a; • 指標變數 cout << ptr;
8-3-3 指標之設定 • 同樣指標型態可以互相設定,亦即可使許多指標指向同一位址,如下例,p1與p2同樣指向x,即三者指向同一地點。 *p1 10 x *p2
例題: 指標間之設定。 #include <iostream> //cout using namespace std; int main( ){ int x=10; int *p1, *p2; p1 = &x; p2 = p1; //指標間之設定 cout << "p2=" << p2 << endl; cout << “*p2=”<<*p2<<endl cout <<”*p1=”<<*p1<<endl; cout <<”x=”<<x<<endl return 0; } 執行結果: p2=0064FE00 *p2=10 *p1=10 x=10
8-3-4 指標位址之運算 • 指標位址之遞增或遞減,如宣告*ptr;後之ptr++, ++ptr或ptr--, --ptr等係針對所存在之位址作該資料型態大小作加減,而非加一或減一之運算。 • 如:int *ptr, a[3]={10,20,30}; • ptr = &a; • 若&a之位址為「0064FE00」,則ptr之內容亦為「0064FE00」。 • ptr++; // ptr之內容就變為「0064FE04」
宣告 運算 增減量 char *ch; ch++; ch--; 1 int *ptr; ptr++; ptr--; 4 float *f; f++; f--; 4 double *d; d++; d--; 8 資料型態遞增減之增減量 • 單位為bytes
例題: 利用指標作運算。 #include <iostream> //cout using namespace std; int main( ){ int *ptr, a=10; ptr = &a; cout << ptr << ","<< &a << endl; cout << *ptr << a << endl; (*ptr)++; cout << "a="<< a << endl; return 0; }
例題:字串利用指標作運算。 #include <iostream> //cout using namespace std; int main( ){ char *ch, s[6]="abcde"; //字串 ch = s; cout << ch << ","<< ++ch << endl; cout << s << ","<< s+1<< ","<<s[4]<<endl; return 0; }
例題:整數陣列利用指標作運算。 #include <iostream> //cout using namespace std; int main( ){ int s[5]={10,20,30,40,50}; int *ptr = s; for (int i=0; i<5; i++) cout << "s["<<i<<"]="<< &s[i] <<endl; cout << "*ptr內容="<< *ptr << endl; cout << "ptr位址="<< ptr << endl; ptr = ptr + 1; cout << "ptr+1內容="<< *ptr << endl; cout << "ptr+1位址="<< ptr << endl; return 0; }
8.4 記憶體配置 • 對於陣列大小設定需給定一常數值,且大小設定後更改困難, 用完後記憶體無法歸還。 • 下列方式是錯誤的: cin >> size; int a[size]; • 指標要求分配記憶體時,指令敘述為new, 當記憶體使用完要歸還或釋放時指令敘述為delete, 兩者必須相互的出現。
8.4.1 new與delete之使用 • 分配或釋放記憶體之敘述格式: • 指標變數 = new 資料型態[大小](初值); //配置 • delete [ ] 指標變數; //釋放 • 配置記憶體 • 配置單一記憶體 • 配置單一記憶體並給予初值 • 配置多個記憶體: 以指標當陣列使用,有固定大小及彈性大小。
8.4.2 配置單一記憶體 • 指標變數 = new 資料型態; • delete 指標型態; • 如: • double *f; • f = new double;//配置一個浮點數空間 • delete f; //釋放一個空間,在delete 後不必加入[ ]
例題: 配置記憶體空間,輸入資料及顯示指標變數位址 #include <iostream> //cout using namespace std; int main( ){ int *a, *b, c; a= new int; b= new int; //分配空間 cout << "a之位址="<< a << endl; cout << "b之位址="<< b << endl; cout << "c之位址="<< &c << endl; cout << "輸入a, b="; cin >> *a >> *b; c = *a + *b; cout <<"c=" << c << endl; delete a; //交回分配之記憶體 delete b; return 0; } a之位址=0x00672d54 b之位址=0x00672d64 c之位址=0x0064fe00 輸入a,b=10 20 c=30
8.4.3 配置單一記憶體並給初值 • 指標型態 = new 資料型態(初值); • delete 指標型態; • 如: int *a; a = new int(10);//則*a之值為10,而非設定10個int空間 或 int *a=new int(10); //宣告,配置與初值合併
例題: 指標初值之設定(來自計算值) #include <iostream> //cout using namespace std; const doublePI=3.14159; const doubleradius = 10.0; int main( ){ float *area=new float(PI*radius*radius); cout << "Area="<<*area<<endl; cout << area << endl; delete area; cout << *area << endl; cout << area << endl; return 0; }
8.4.4 配置多個記憶體 • 指標變數 = new資料型態[大小]; • delete[ ]指標變數; • 如: • int *a; • a=new int[5]; //配置5 * 4=20 Bytes給a,固定大小。 • delete [ ] a; //釋放a之空間
例題:以指標輸入10個浮點數資料,求平均。(固定大小)例題:以指標輸入10個浮點數資料,求平均。(固定大小) #include <iostream> //cout using namespace std; int main( ){ float *f, sum=0, ave; f = newfloat[10]; cout << "Enter 10 data:\n"; for (int i=0;i<10;i++){ cin >> *(f+i); sum+= *(f+i); } ave=sum/10.0; cout << “10個數平均=" << ave << endl; delete[ ] f; return 0; }
例題: 指標資料之排序,輸入三指標變數資料後由小到大輸出。 cout << "排序後:"<<*a<<" "<<*b <<" "<<*c<<endl; delete c; //釋放記憶體 delete b; delete a; return 0; } void swap(int *a, int *b){ if (*a>*b) { int t = *a; *a = *b; *b = t; } } #include <iostream> //cout using namespace std; void swap(int *a ,int *b); int main( ){ int *a,*b,*c; a = new int; //配置記憶體 b = new int; c = new int; cout << "輸入a,b,c="; cin >> *a >> *b >> *c; swap(a,b); //交換 swap(b,c); swap(a,b);
例題: 以指標來設定彈性陣列。 #include <iostream> //cout using namespace std; int main( ){ int *a, size, i; cout <<"輸入陣列大小:"; cin >> size; a = new int[size]; //分配記憶體 for (i=0; i<size; i++){ cout <<i<<“:”<< (a+i) <<“="; //輸出位址 cin >> *(a+i); } cout <<”輸出資料:”; for (i=0; i<size; i++) cout << *(a++) <<" "; delete[ ] a; //釋放記憶體 return 0;}
8.5 指標與陣列 • 指標與陣列兩者具有相當的密切關係,如: • int str[30], *ptr; • ptr = str; 或 ptr = &str[0]; • 表ptr已被設定到str陣列第一個元素,因此要存取第五個元素可以如下寫法: • str[4]; • *(str+4); • *(ptr+4);
陣列存取資料之方法 • 設宣告int a[5], *ptr; • 以標準陣列方式存取, 如a[n]。 • 以陣列名稱加上索引值來存取,如*(a+n)。 • 以指標變數加上索引值來存取,如*(ptr+n), 需事先執行ptr=a或ptr=&a[0]。 • 以指標來存取陣列之元素比以索引來存取陣列之元素之速度較快。
8.5.1 陣列名稱之指標用法。 #include <iostream> //cout using namespace std; int main( ){ int *ptr, a[5]={5,10,15,20,25}, i; cout <<” 標準存取: ”; for (i=0; i<5; i++) cout << a[i] << “ “; cout << endl; cout <<” 陣列名稱存取: ”; for (i=0; i<5; i++) cout << *(a+i) << “ “; cout << endl; cout <<” 指標存取: ” ; ptr = a; for (i=0; i<5; i++) cout << *(ptr+i) << “ “; cout << endl; return 0; }
指標變數 陣列名稱 位址 內容 存取方式 ptr+0 a+0 0066FF00 5 a[0], *(a+0), *(ptr+0) ptr+1 a+1 0066FF04 10 a[1], *(a+1), *(ptr+1) ptr+2 a+2 0066FF08 15 a[2], *(a+2), *(ptr+2) ptr+3 a+3 0066FF0C 20 a[3], *(a+3), *(ptr+3) ptr+4 a+4 0066FFF0 25 a[4], *(a+4), *(ptr+4) ptr 0066FF00 指標與陣列
8.5.2 以指標變數取代陣列 #include <iostream> //cout using namespace std; const N = 5; int main( ){ int a[N], *ptr,i; ptr = a;//或ptr=&a[0] cout << "輸入”<<N<<”筆資料:"; for (i=0; i<N; i++) cin >> *(ptr+i);//以指標輸入 cout << "以陣列名稱輸出:"; for (i=0; i<N; i++) cout << *(a+i) << " ";cout << "\n"; return 0; }
8.5.3 陣列各元素位址之取得 #include <iostream> //cout using namespace std; const N = 5; int main( ){ int a[N], i, *ptr; cout << "以陣列元素:\n"; for (i=0; i<N; i++) cout <<"a["<<i<<"]="<< &a[i] <<endl; cout << "以指標:\n"; ptr = a; //或 ptr = &a[0]; for (i=0; i<N; i++) cout <<“ptr+”<<i<<“=”<< ptr+i <<endl; cout << "以陣列名稱:\n"; for (i=0; i<N; i++) cout <<"a+"<<i<<"="<< (a+i) <<endl; return 0;}
8.5.4 指標變數與二維陣列 #include <iostream> //cout using namespace std; const int N=4,M=3; int main( ){ int a[N][M], i, j, *ptr; ptr = &a[0][0]; for ( i=0; i<N*M; i++) cin >> *(ptr+i); cout <<” 陣列資料如下:\n”; for (i=0; i<N; i++){ for ( j=0; j<M; j++){ cout.width(3); cout << *(*(a+i)+j) << " "; } cout << "\n";} return 0; }
8.6 指標與字串 在C++內字串本身就是指標,因字串是屬於字元陣列, 如: #include <iostream> //cout using namespace std; int main( ){ char *ptr; char s[ ]="This is a string."; cout << "s= " << s << endl; ptr =s; cout <<”輸入字串:” ; cin >> ptr; cout << "輸入後: s=" << s << endl; return 0; } s=This is a string, 輸入字串:Clinton 輸入後:s=Clinton
例題: 將指標內字串搬移到另一字串內及ptr++之使用。 #include <iostream> //cout using namespace std; int main( ){ char *ptr="This is pointer string."; char s[25]; int i; cout << "ptr= " << ptr << endl; for (i=0; i<strlen(ptr)+1; i++) s[i]=*(ptr+i); cout << "搬移後 : s=" << s << endl; ptr++; //ptr=ptr+1;指向第二位置 cout << “ptr++=”<<ptr<<endl; ptr=ptr+4;//跳過4個位置到第6個位置 cout << “ptr+4=”<<ptr<<endl; return 0; } ptr=This is pointer string. 搬移後:s=This is pointer string. ptr++=his is pointer string. ptr+4=is pointer string.
8.7 指標與函數 • 函數之傳回值若以函數名稱傳回只有一個值, 這是以return傳回數值。 • 函數除了可傳回值外, 還可以利用參數傳回參考位址與指標位址, 解除只能傳回一個之限制。 • 若要傳回一個以上之資料就需利用參數之傳遞以傳回多個值, 使用方式有傳址(參考)呼叫(Call by Reference)及指標呼叫(Call by Pointer), 而函數名稱除了傳數值外亦可以傳址呼叫及指標呼叫傳回。
宣告 呼叫 函數定義 說明 int a; sum(&a,...); void sum(int *a,...) 指標參數傳回 int a; sum(a,...); void sum(int &a,...) 參考參數傳回 int *a; sum(a,...); void sum(int *a,...) 指標參數傳回 int *a; a=sum(…); int *sum(…) 函數名傳回 int *a, *b; a=sum(b,...); int *sum(int *b,...) 函數名,指標參數傳回 函數與指標之可能的組合
8.7.1 一般變數傳給指標 • 一般變數傳遞參數給函數, 若需再傳回, 在函數之參數需宣告為指標變數, 而呼叫者需以位址運算子「&」告訴函數 • 資料型態 函數名稱(資料型態*, …); //函數原型 • void call_fun( int*, int*); • 呼叫函數(&一般變數, …); //呼叫函數 • 如: { int a, b; • call_fun(&a, &b); …} • 資料型態 函數名稱(資料型態 *變數, …) //函數定義本體 • void call_fun( int *p, int *q) {….}
例題: 寫一函數傳回兩輸入資料給主程式後,在主程式內求和後輸出。 #include <iostream> //cout using namespace std; void get_data(int*, int*); int main( ){ int a,b,sum; get_data(&a,&b); sum = a + b; cout << "sum=" << sum << endl; return 0;} void get_data(int *a, int *b) { cout << "輸入a,b="; cin >> *a >> *b; } 輸入a, b=10 20 sum=30
主程式 函數get_data 內容 位址 位址 a 10 0066FF00 → 0066FF00 a b 20 0066FF04 → 0066FF04 b 共用同一塊記憶體
例題: 寫一函數將兩數互換後在主程式輸出(指標接收)。 #include <iostream> //cout using namespace std; void swap_data(int*, int*); int main( ){ int a=10, b=15; cout << "交換前:" << a <<" "<< b << endl; swap_data(&a, &b); cout << "交換後:" << a <<" "<< b << endl; return 0;} void swap_data( int *a, int *b ){ int t; t = *a; *a = *b; *b = t; } 交換前: 10 15 交換後: 15 10
8.7.2 傳址方式傳參數 #include <iostream> //cout using namespace std; void swap_data(int*, int*); int main( ){ int a=10, b=15; cout << "交換前:" << a <<" "<< b << endl; swap_data(a, b); cout << "交換後:" << a <<" "<< b << endl; return 0;} void swap_data( int &a, int &b ){ int t; t = a; a = b; b = t; }
8.7.3 指標傳給指標 #include <iostream> //cout using namespace std; void add_one(int*); int main( ){ int a=10, *b; b = &a; add_one(b); cout << "*b=" << *b << endl; cout << " a=" << a << endl; return 0; } void add_one(int *a){ (*a)++; }
8.7.4 函數名稱以指標傳回 #include <iostream> //cout using namespace std; int *get_sum(int,int); int main( ){ int a=10, b=20, *p; p = get_sum(a,b); cout << "sum=" << *p; delete p; return 0; } int *get_sum(int a, int b) { int *p=new int (a+b); return p; }
8.7.5 傳回一個以上資料 #include <iostream> //cout using namespace std; int *get_sum(int,int); int main( ){ int *a,*b,*p; a = new int(10); b = new int(20); p = get_sum(a,b); cout <<"*a="<<*a<<endl <<"*b="<<*b<<endl <<"*p="<<*p<<endl; delete p; delete b; delete a; return 0; } int *get_sum(int *a,int *b){ int *p=new int(*a+*b); (*a)++; --(*b); return p; }
8.9 指標中之指標(雙指標) • 設定一指標指向另一指標, 第二指標指向內容值之用法, 此種情形稱為多重間接指向(multiple indirection)或指標中之指標(pointers to pointers)。在此種情況下, • 第一指標儲存第二指標之位址, 而第二指標系指向儲存數值之位址。 • 雙重指標可用來設定二維陣列, 先要求列(Row)之指標, 再以列之指標來設定行(Column)之指標, 使列為第一指標, 行為第二指標, 形成陣列中陣列, 其存取方式可用一般之陣列存取方法。
指標 變數 位址 內容 單一指標 指標2 變數 指標1 位址 內容 位址 雙指標
例題: 指標中之指標。 #include <iostream> //cout using namespace std; int *get_sum(int,int); int main( ){ int x, *p, **m; x = 100; p = &x; //p指向x m = &p; //m指向p cout << "**m="<<**m<<endl; cout << "*p=" << *p << endl; cout << "x=" << x << endl; return 0; }
8.10 指標指向函數 • 指標也可指向已經存在之函數, 雖然函數不是變數但它仍然在記憶體內有一實體位址, 有實體位址就可將指標指向該位址, 就稱為函數指標(function pointer), 函數之位址是函數被呼叫時之進入點, 一旦函數被指標指到相當於可以呼叫該函數; 函數指標也可當引數(Argument)或參數(Parameter)傳給其他函數。
例題: 指標指向pow函數 。 #include <iostream> //cout #include <math.h> //pow(x,y) using namespace std; int main( ){ double x, y; double (*p)(double, double); p = pow; //求x之y次方 cout << "x, y="; cin >> x >> y; cout << x <<"之" << y <<"次方="<< (*p)(x,y); return 0; }