|
2008/1/25 下午 01:26:03
小弟最近處碰指標的部份因此有一堆觀念被搞混 請各位大大能幫小弟解決以下這些問題 感激不盡m(_ _)m
<1> 指標的好處就是節省記憶體空間,那為什麼有的時候宣告變數不使用指標 而是使用一般的宣告方式,如下兩種方式宣告,但是目的都只是放數值 int *p; *p=111;//假設放入111的值 和 int p; p=111; 向這種情況下大部分還是使用int p 是不是因為方便的關係,所以不使用指標??
<2> 有的時候使用指標要加上new才可以使用 int *p=new int; 可是宣告的時候不就已經有配置記憶體了嗎? 為啥還要使用new從新配置??
<3> 宣告指標時使用new和NULL有啥差別??? int *p=new int; 和 int *p=NULL; 兩者差異????????
<4> 我知道使用new的話,當不使用指標時必須最後delete 但是如果的指標是在區域環境下宣告 那還需要delete嗎????
<5> 我發現delete有兩種就是delete和delete[ ] delete給一般指標專用??? delete[ ]難道是給多維指標陣列使用嗎?? 用錯地方會不會沒有效過???
<6> 如果使用了結構指標 是否還需要delete??? delete的方法是否和指標一樣???
這是目前遇到的一些渾淆觀念O_O 希望知道答案的大大能夠幫助回答 另外請把我當白癡詳細教...感恩:D
|
|
|
2008/1/25 下午 04:17:17
什麼是指標? 就是本身是用來記錄"位址"的變數, 或者你可以想像它的值是用來指向某個空間. 例如:
int a; int *b = &a;
b記錄的便是變數a所在空間的位址, 也就是指向變數a所在的地方. 因此*b=10的結果, 便是將10填入b所指的地方, 也就是變數a的實體空間裡. 其結果跟a=10是一樣的.
void* p;
這樣的寫法表示, p指向的是一個空間, 但這個空間裡記錄的是什麼結構, 不知道. 因此在不知指向的空間是什麼結構前, 你無法直接填值進去, 必須另外轉型才行.
char** p;
這樣的話, p指向的空間裡, 是記錄char*這樣的結構 (也就是再記錄一個位址值), 因此*p存取到的, 都是位址值. 取到位址值後, 必須再指向一次, 才能得到實際的空間, 也就是**p才是指向記錄char這個結構的空間.
陣列變數其實也可以視為指標來看待:
int p1[10]; int *p2 = p1;
p1與p2指向的空間位址都是一樣的, 只不過p1是立刻可以知道是一個連續10個int空間的陣列, p2的話, 只知道是指向一個記錄int的空間, 是不是陣列則是程式撰寫者才知道. 在這個例子中, 因為已經知道p2指向的是一個int陣列, 所以可以用p2[5]這樣的方式來存取陣列內容, 或者也可以寫成*(p2+5), 它的意義則是p2指向的位址再往下5個結構, 然後存取裡面的值. 因為已經知道p2指向的空間裡, 記錄的是int結構, 所以再往下5個int便是第6個int空間, 跟p2[5]是相同的.
int p1[10][20]; int **p2;
二維以上的陣列與指標就有點不同了, p1是指向一個一大塊的空間, 這個空間被規劃成10x20個int, p2則是指向一個int*的空間, 然後再從該空間裡記錄的位址再去存取實際的內容, 這種方法為間接存取. 假設p2是被做成跟p1一樣10x20維度的陣列, 那麼p2在空間的運用上也與p1不同. 因為p1指向的空間是一整塊, 維度也是已知 (必須是已知), 所以p1[i][j]馬上可以知道是在記錄空間裡的那一個地方, 但p2就不同了. p2必須是先指向一個int*[10]的空間, 每個空間分別指向int[20]的空間, 因此實際的空間並非一大塊, 而是拆成11個 (其中一個是用來存放其他空間的位址用的), 因此p2[i][j]實際上是先p2[i]從int*[10]空間裡取到指標值, 然後再經由這個指標值再做[j]得到實際的空間, 然後進行存取. 我記得Raymond曾畫過不少關於這類指標的圖, 試著搜尋看看吧, 比較容易了解一些.
有了以上的基礎知識後, 你的問題才比較好回答:
><1> >指標的好處就是節省記憶體空間
指標的好處不是節省記憶體, 大部份的情況反而比較浪費記憶體. 但指標的目的, 多半是要做彈性的處理, 特別是不知道資料量大小的情況下, 往往必須使用動態記憶體, 此時便需要配合指標來處理. 這點需要你再慢慢去體會吧.
>如下兩種方式宣告,但是目的都只是放數值 >int *p; >*p=111;//假設放入111的值 >和 >int p; >p=111; >向這種情況下大部分還是使用int p >是不是因為方便的關係,所以不使用指標??
int* p; *p=111;
p是用來指向一個int空間, 但你沒給初值, 也就是不知指向那裡, 因此將111存入不知那個地方, 一定會出問題. 指標變數本身的空間 (也是有佔用空間唷), 是用來記錄位址, 你填入p的值必須是某個空間的位址, 在p沒指向一個切確的空間前, 是不能去存取它指向的空間內容. 即使有指向某個空間, 也要看該空間還是否合法, 像指向的動態記憶體被釋放掉了, 或是指向的區域變數已離開它所在的scope等, 這時原指向的空間已消失, 再去存取, 往往會造成許多問題.
int p; p=111;
這樣的變數p, 本身空間是一個整數, 因此存取的, 都是整數值.
每個變數宣告後, 都會佔用一定的空間, 至於佔用多少空間, 便視它的形態而定. 而存取的內容是什麼, 也是依變數的形態而定. 前例中, int*p, p佔用sizeof(int*)空間, 存取的內容是int*結構的位址值, int p, p佔用sizeof(int)空間, 存取的內容是int結構的整數值.
|
|
|
2008/1/25 下午 04:17:30
><2> >有的時候使用指標要加上new才可以使用 >int *p=new int; >可是宣告的時候不就已經有配置記憶體了嗎? >為啥還要使用new從新配置??
前面已提過, 宣告所配置的記憶體, 是變數本身的記憶體 (以此例便是用來記錄int*的記憶體), 問題是p目前還沒有值, 要如何給值呢? 你可以給另一個int空間(或int陣列)的位址給它, 但更常見的, 便是用new配置動態記憶體空間給它. 沒有給值前, p的內容是沒有意義, *p也就不知指到那裡去, 存取一個隨機某處的記憶體, 不當掉才怪.
><3> >宣告指標時使用new和NULL有啥差別??? >int *p=new int; >和 >int *p=NULL; >兩者差異????????
指標變數在給初值時, 除了立即配置給予一個空間位址外, 也可以直接給予NULL, 表示還沒指向任何空間. 因為還沒指向任何空間, *p一樣會出問題. 給NULL的目的, 通常是為了用來判斷, 例如:
if (p == NULL) ...
可以用來得知, p是否已指向一個合法空間. 當然是否給NULL值是程式撰寫者必須自己控制的, 當p指向的空間已消滅時, 便要立刻將p再設成NULL, 表示已不再指向任何空間, 如此前述這樣的比較才有意義. 所以使用指標是必須多費一點心, 不然除錯起來可是很困難的.
><4> >我知道使用new的話,當不使用指標時必須最後delete >但是如果的指標是在區域環境下宣告 >那還需要delete嗎????
並不是不使用指標時才delete, 而是不再使用該動態記憶體時, 這點跟指標沒關係. 例如:
int *p1 = new int[10]; int *p2 = p1;
現在主要看int[10];這個動態記憶體空間什麼時候不再使用, 不再使用時便delete. 此例裡, p1不再使用時, 還有p2指向這個空間. 但要記得, 動態記憶體空間一定要有一個指標指向它的頭, 不然就無法delete掉了.
至於離開區域環境, 消失的是指標變數的空間, 也就是原本用來記錄動態記憶體空間位址的空間會消失, 但已配置的動態記憶體還是存在的 (這裡雖有一些小例外, 但非常態), 因此在離開前若不再使用動態記憶體, 都必須delete掉, 不然就會有memory leak.
><5> >我發現delete有兩種就是delete和delete[ ] >delete給一般指標專用??? >delete[ ]難道是給多維指標陣列使用嗎?? >用錯地方會不會沒有效過???
delete[]主要是用來釋放物件陣列的動態記憶體空間. 例如:
class a {...};
a* b = new a[10];
此時配置的動態空間會視為10個a物件, 然後分別呼叫a的建構函數 (因有10個, 故會呼叫10次).
delete[] b;
在釋放前, 知道b是一個陣列 (加[]的目的), 因此會分別呼叫10個a物件裡解構函數 (共10次).
delete b;
這樣的寫法, 則是將b視為指向一個a物件 (非陣列), 因此只會呼叫第1個a物件裡的解構函數 (共1次)
一般的原則是, new後面有[]的, 在delete時, 也要加上[], new後面沒[]的, delete時便不要加, 這樣就不會出錯了.
><6> >如果使用了結構指標 >是否還需要delete??? >delete的方法是否和指標一樣???
結構指標? 不懂你所指的意義.
|
|
|
2008/1/25 下午 04:54:09
><1> >指標的好處就是節省記憶體空間,那為什麼有的時候宣告變數不使用指標 誰說的...如果是書上寫的....請把書撕了吧
>而是使用一般的宣告方式,如下兩種方式宣告,但是目的都只是放數值 >int *p; >*p=111;//假設放入111的值 >和 >int p; >p=111; >向這種情況下大部分還是使用int p >是不是因為方便的關係,所以不使用指標?? 不是,是因為第一種寫法完全是錯誤的
指標是一種特殊的變數 它代表的不是資料本身 而是資料的位址 我們可以改變指標的值讓它指向不同的位址 然後使用*操作它指向的位址裡的資料 例如 int i= 0; //配置了一個int變數i, 並設定初始值為0 int *p; //配置了一個int指標變數p p= &i; //p指向變數i的空間 *p= 111; //設定p指向空間的值為111 printf( "%d", i); //此時i的值已經透過p被改變成111了, 所以這裡將會印出111
回到你的問題 第一種寫法配置了一個指標變數但沒有設定它指向的地方 運氣好的話它的初始值指向一個未配置的空間 程式一執行就給你一個Segmentation fault錯誤訊息 運氣不好的話它會指到一個配置好的空間 讓你的設值動作影響到別的運算 影響到你的程式結果又讓你難以發現錯在那
><2> >有的時候使用指標要加上new才可以使用 >int *p=new int; >可是宣告的時候不就已經有配置記憶體了嗎? >為啥還要使用new從新配置?? int *p;配置的空間是一個指標 而不是int變數
><3> >宣告指標時使用new和NULL有啥差別??? >int *p=new int; >和 >int *p=NULL; >兩者差異???????? NULL是個特殊位址 用來表示這個指標並沒有指向任何位址
><4> >我知道使用new的話,當不使用指標時必須最後delete >但是如果的指標是在區域環境下宣告 >那還需要delete嗎???? 要 new並不是一種宣告
><5> >我發現delete有兩種就是delete和delete[ ] >delete給一般指標專用??? >delete[ ]難道是給多維指標陣列使用嗎?? >用錯地方會不會沒有效過??? delete是對映new操作產生的物件 delete []是對映new []操作產生的物件陣列 範例 int *p= new int[ 10]; delete [] p; 這個用錯會造成memory leak
><6> >如果使用了結構指標 >是否還需要delete??? >delete的方法是否和指標一樣??? 你的結構指標是指什麼?
基本上用new產生的就必須delete 用alloc系列函式產生的就必須free 跟用什麼指標無關
|
|
|
2008/1/25 下午 05:11:13
><6> >如果使用了結構指標 >是否還需要delete??? >delete的方法是否和指標一樣??? 你的結構指標是指什麼?
感謝大大的解說O_Oa 至於第六個的問題指標結構就是如下
typedef struct abc { int data; }
abc *p;
p->data=1000;//假設給值
當最後不使用的時候 是不是和一般指標一樣 delete p;??????
|
|
|
2008/1/25 下午 06:02:09
>至於第六個的問題指標結構就是如下 > >typedef struct abc >{ >int data; >} > >abc *p; > >p->data=1000;//假設給值 > >當最後不使用的時候 >是不是和一般指標一樣 >delete p;??????
無論指標指向的結構是class, struct, 或是一般的純資料 (如char, int, float等), new和delete的對應方式都一樣. 因此:
abc *p = new abc; 對應的便是delete p;
abc *p = new abc[10]; 對應的便是delete[] p;
附帶再說一下. 該用delete[]卻用delete, 其實未必會出問題, 像純資料char, int等, 本身沒有解構函數, 或是像你例子中的struct也沒有解構函數, 因此用delete與用delete[]並沒有差, 也不會造成memory leak. 例如:
int *p = new int[10]; delete p; 與delete[] p; 作用是相同的.
不過當有解構函數時, delete與delete[]就不同了, 當用delete[]卻誤用delete便可能出問題. 因此為養成良好習慣, 前例還是使用delete[] p;較佳.
|
|
|
2008/1/25 下午 09:05:05
><4> >我知道使用new的話,當不使用指標時必須最後delete >但是如果的指標是在區域環境下宣告 >那還需要delete嗎????
只要用到指標, 就意味著有兩個物件: 第一個是指標本身的內容; 第二個是指標所指向的空間:
{ int *p = new int; ... }
int * +=====+ +=======+ p| *---+----> | <int> | +=====+ +=======+
'p' 是個區域自動物件, 在離開包起它的 {} 時會自動消失.
但 'p' 所指向的空間是 new 出來的, 它會一直存活到你去 delete 為止.
兩個不同的物件, 要分開思考處理.
|
|
|
2008/1/29 下午 07:54:48
感謝各位大大的回答:D 對於指標我目前只剩一個小問題了
也就是不管我在全域或是區域下宣告指標 最後都一定要delete...
但是我測試了一下 我在副程式的地方使用了指標 並且回傳的值是指標 假設副程式如下
float* abc(int a) { float * b=new float[2];
b[0]=a*1; b[1]=a*2;
return(b); }
問題來了...這種情形下我要回傳指標 所以我沒辦法delete 因為我在主程式的地方呼叫完副程式後加入 delete b;
可是程式卻會說這行錯誤O_Oa
所以是不是表示副程式的指標不用delete 或者是因為我把副程式寫再另一個*.h檔裡面 所以導致不用delete???
|
|
|
2008/1/29 下午 08:13:43
oh
指標是指標
記憶體是記憶體
so
int *ptr = abc(10); delete ptr;
而不是
int *ptr = abc(10); delete b;
不要被形式上的變數名稱給綁住了
|
|
|
2008/1/30 上午 12:14:57
>感謝各位大大的回答:D >對於指標我目前只剩一個小問題了 > >也就是不管我在全域或是區域下宣告指標 >最後都一定要delete...
感覺上, 你對指標還是有點似懂非懂的. 我直接用淺顯的例子來說明好了. 指標也是一種存"值"的變數, 跟整數變數, 浮點數變數一樣, 只不過它存的是位址值而已.
int a;
&a傳回來的, 是a這個變數的所在位址, 假設位址值就是1234, 那麼:
int* b = &a;
b所存的值就是1234, *b就是要去存取位址1234這個記憶體裡面的資料. 就好像是門牌號碼一樣, 門牌號碼表示是那一棟房子, 但房子裡裝的是什麼就不一定, b表示的便是門牌號碼, 而*b則是到它代表的房子裡去存取資料.
new/delete的作用, 就好像有一堆空房子, new是找一個空房子, 然後傳回該門牌號碼 (位址值), delete便是告知門牌號碼, 表示這個房子不再使用了. 因此
int* b = new int[10];
new int[10];會傳回一個位址值, 假設是5678, 然後放到b這個變數裡. 當不用時, 便要將5678這個位址值傳回給delete做釋放的動作, 因為b的值是5678, 所以delete b剛好就是將原先new出來的記憶體給釋放掉. 如果多做了一個:
b++;
那麼b的值變掉了 (假設是5679), 這樣跟原先new傳回來的位址不同, 此時delete b已不是原先new出來的記憶體位址, 就會造成問題.
你的例子也是相同:
float* abc(int a) { float * b=new float[2];
b[0]=a*1; b[1]=a*2;
return(b); }
float* c = abc(10);
在abc函數裡new出來的位址值, 假設是2345, 那麼最後return b;就是將位址值2345傳出去, 於是c的值變成位址2345, 這時delete c就可以釋放new出來的那一塊記憶體 (位址2345). 如果你在abc裡有delete b;又傳回b的值, 會變成怎樣? 就會變成, 我申請了一個空房子, 告訴外面呼叫該函數的人xxx門牌號碼的房子可以用, 但卻又釋放回系統, 讓其他的人可以再申請到這個房子使用. 最後就可能造成多個人共用同一個房子, 而造成衝突.
如果裡面外面都沒有delete呢? 就變成申請了一個空房, 不用時就擺著不管. 下次想用時, 又要了另一個空房. 由於空房 (記憶體) 有限, 不用的空房佔著不放, 最後可能就全部用盡了, 再也沒多的空房可用.
這樣的講解應該比較容易理解吧? 指標變數只是在記錄"位址值", 所有對指標變數的運算與傳遞, 都是在處理這個位址值. 因此:
int a = 10; int* b = &a; char* c = (char*)b; void* d = c;
b, c, d的值都一樣, 都是a所在的"位址值". *之前的, 就是表示這個位址所在的記憶體是什麼資料形態, 相當於宣告門牌號碼代表的房子裡, 是放什麼東西一樣.
使用new/delete或是malloc/free得到的便是動態配置出來的記憶體位址, 它需要指標變數來記錄這個位址值. 一但配置後, 不管這個位址值傳到那裡去, 最後一定要在某一個地方做釋放的動作, 不然系統就認為這塊記憶體一直被使用中, 不會再拿來使用.
所以請認清楚, 指標只是記錄"位址值", 要不要釋放, 什麼時候釋放, 便看你的用途而定. 像:
int a; int* b = &a;
這時就不能用delete b; 因為a不是動態記憶體, 它的位址便不能隨便釋放. 這點就好像已有人的住家, 先借用它的門牌號碼來用, 然後卻把它給釋放掉, 讓系統視為空屋處理, 想當然就會出問題.
總之, 處理指標, 就必須有"位址值"的概念與想法, 否則常會搞混與出錯.
|
|
|
2008/1/30 上午 09:36:39
先不談其它的 你的b宣告在函式裡 是個區域變數 但你卻想在區域外操作b 你的想法基本上就有錯了
|
|
|
2008/1/30 下午 07:28:20
回樓上的 可能是我的問題沒說的很清楚@@a
因為副程式的裡面b是指標 而且已經有配置一塊記憶體給b了 但是因為要return回去 又不能在return之前delete 也沒辦法寫在returm下面delete
所以我才被這個問題困擾 所以這個問題就是這種情況b到底要不要delete 如果需要的話 是要怎麼delete??
|
|
|
2008/1/30 下午 08:37:47
...
麻煩看一下我的說明, 再仔細想想, 答案已在裡面了. 這裡再明白的指出:
float* abc(int a) { float * b=new float[2];
b[0]=a*1; b[1]=a*2;
return(b); }
int main(void) { float* c = abc(10); // 呼叫後, 函數abc裡new出來的記憶體已被傳出, 現在記錄在指標c裡 // // 這時可以開始用指標c指向的記憶體 // delete c; // 不用時便釋放 // }
|
|
|
2008/1/30 下午 09:16:40
>因為副程式的裡面b是指標 >而且已經有配置一塊記憶體給b了 >但是因為要return回去 >又不能在return之前delete >也沒辦法寫在returm下面delete > >所以我才被這個問題困擾 >所以這個問題就是這種情況b到底要不要delete >如果需要的話 >是要怎麼delete??
先不管指標, 用 int 來看看 return 是個怎樣的情況:
int func(void) { int func_i = ...; ... return func_i; }
int main(void) { int main_i; ... main_i = func(); ... }
在副程式 func() 結束前, 它傳回 func_i, 然後 func() 就結束. 在 func() 裡面的自動變數 func_i 也當然就消失了.
但這個會有問題嗎? 沒有!
因為在 func() 結束, func_i 消失前, return func_i 會把 func_i 的內容複製到主程式裡的 main_i 變數.
main() | func() ------------+----------- | mainˍi | funcˍi +======+ | +======+ | | | | | +======+ | +======+ ︿ | | | | | +------------+ |
這裡有一個很重要的重點, 就是『複製內容』, func_i 的內容.
現在把 func_i 及 main_i 的類型改成指標, 變數名字也稍微改了一下, 以免在敘述中跟上面的混淆. 同樣的, func() 的回傳類型也要改成指標類型:
int* func(void) { int* func_p = ...; ... return func_p; }
int main(void) { int* main_p; ... main_p = func(); ... }
return func_p 所複製的也是『指標的內容』. 我在之前的回覆裡就已經強調, 「指標」意味著有兩個不同的東西: 指標的內容, 及所指向的空間.
回傳指標物件所傳回的是指標的內容. 既然所複製的是指標的內容, 所以 main_p 所指向的也就是之前 func_p 所指向的空間.
main() | func() ------------+----------- 複製前: | | mainˍp | funcˍp +======+ | +======+ +===+ | | | | *----+---->| | +======+ | +======+ +===+ | | 複製: | mainˍp | funcˍp +======+ | +======+ | | | | | +======+ | +======+ ︿ | | | | | +------------+ | 複製後: | mainˍp | funcˍp +======+ | +======+ +===+ | * | | | *----+---->| | +==|===+ | +======+ +===+ | ︿ | | +------------------------+
〔續...〕
|
|
|
2008/1/30 下午 09:16:57
〔...續〕
現在看回你的程式 (只顯示有關的部份): float* abc() { float *b = new ...; ... return b; }
int main() { float *c = abc(); }
在回傳的過程中, 變數 'b' 的內容複製到變數 'c' 裡. 所以 'c' 也指向 'b' 所 new 出來的空間.
在 abc() 結束後, 'b' 會消失, 但它所指向的內容, 由於是 new 出來的, 還存在. 而這個配置出來的空間, 在 main() 裡面變成是用 c 來指著.
delete 所在乎的是指標的內容, 而不是哪個變數.
你不可能在 main() 裡面 delete b, 但 delete c 是可以的:
int main() { float *c = abc(); ... delete [] c; ... }
|
|
|
2008/1/30 下午 09:30:54
float* abc(int a) { float * b=new float[2];
b[0]=a*1; b[1]=a*2;
return(b); }
基本上不鼓勵"外部刪除配置的記憶體"這件事, 你提供了一個 abc 函數, 函數內 配置 了記憶體, 就要有另一個成對的函數來刪除記憶體.
void deletePtr( float* ptr ) { delete ptr; }
函數是你寫的, 你自己都知道是用 new 或 malloc 去配置, 能正確的釋放, 但是, 如果你的 abc() 是做成 lib 給別人使用, 別人根本無法得知該用 delete 還是 free 去釋放記憶體.
另外, 假設 float* pf= new float; float* ptr1= pf; float* ptr2= prt1;
以下的各個作法, 效果都是相同的, 但只能做其中一種. delete pf; 或 delete ptr1; 或 delete ptr2; 都可以.
所以你的 abc 函數, 不必急著在函數裡直接刪除, 因為你配置記憶體, 並想把 指標 return 回去繼續使用, 如果在函數內就把記憶體刪除了, 那 return 那段 就沒意義了不是嗎? 等你的指標確定不使用了, 再進行記憶體的釋放即可.
不要執著於 abc() 函數裡的變數名稱 b. 再說, 如果你使用 malloc( ) 這個函數, 你也不必去知道 malloc 裡使用了什麼變數, 該怎麼針對那個變數去釋放, 不是嗎? 而且使用 abc() 的方式, 應該必須是: float* ptr= abc( 3 ); // 方法 ( 1 ) 而不能是: abc( 3 ); // 方法 ( 2 ) 即然如此, 你拿到了 ptr, 就可以針對 abc()裡配置的記憶體作釋放了. 如果使用者用了方法2, 這樣就會造成 memory leak, 這也不是原本設計 abc() 時 所預想的. 方法2的意思, 就跟你使用: new int; 意思一樣, 沒有任何參考或指標去記錄這個配置記憶體行為, 你就無法去釋放.
|
|
|
|
|
|
C++ |
 |
|
|
專家等級 |
評價 |
|
|
一代宗師 |
10000 |
|
|
曠世奇才 |
5000 |
|
|
頂尖高手 |
3000 |
|
|
卓越專家 |
1500 |
|
|
優秀好手 |
750 |
|
|
|
|
|
|
|
|
|
Microsoft Internet Explorer
6.0. Screen 1024x768 pixel. High Color (16 bit).
2000-2019 程式設計俱樂部 http://www.programmer-club.com.tw/ |
|
|