c++中”指標”終結理解

來源:互聯網
上載者:User

我們可以對指標這樣定義:

通過指標中存放的首地址,應用程式順利地找到某個變數。就好像我最近認識了一位朋友,他叫我有空去他家坐坐,然後,他留下了地址。某個周末我正閑著,忽然想起這位朋友,於是,我就根據他留的地址去找他,結果,當我來到傻B街230號出租房時,裡面走出一個我不認識的人,於是,我問他我這位朋友去哪了,陌生人說,我剛租了這房子,你找的可能是前一位租戶吧。

所以,指標所指向的地址,有可能是變數B,也有可能是變數F,或者變數S,指標是房東,可以把房子租給B,C,或F,它可以動態為變數分配記憶體,也可以把變數銷毀(delete),交不起房租就滾蛋(解構函式)。

從上面的故事中,我們看到指標的兩個用途:索引記憶體和分配記憶體。

看看下面這個例子。

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int* pint =
    new int(100);
  5. printf(" *pint的值:%d\n", *pint);
  6. printf(" pint的值:0x %x\n", pint);
  7. getchar();
  8. }
#include <stdio.h>void main(){int* pint = new int(100);printf("  *pint的值:%d\n", *pint);printf("  pint的值:0x %x\n", pint);getchar();}

你猜猜,它運行後會出現什嗎?

我們看到了,pint裡面存的就是整型100的首地址,因為它是int*,是指向int的指標,所以指標知道,找到首地址後,我只關注從首地址開始,連續的4個位元組,後面的我不管了,因為我只知道int有四個位元組。上面的例子,我們看到pint的值就是0x10f1968,這就是整型100在記憶體中的首地址,所以,100所擁有的記憶體塊可能是:

0x10f1968 , 0x10f1969, 0x10f196A, 0x10f196b

總之是連續的記憶體塊來儲存這4個位元組。

new int(100),表示指標pint在首地址為0x10f1968的記憶體地區建立了一個4個位元組的地區,裡面儲存的值就是整型100,所以,pint取得的就是100的首地址,而加上*號就不同了,看看上面的例子,*pint的值就是100了。這樣一來,我們又得到一個技巧:

利用指標標識符 * 放在指標變數前即可獲得指標所指地址中儲存的實際值。

我都大家一個很簡單的技巧。看看下面兩行代碼。

int *p = new int(200);

int p = 200;

因為 * 放在類型後或放在變數名前面都是可以的,即int* pint和int *pint是一個道理。這樣一來,我們不妨把int *pint 看作int (*pint),將整個*pint看作一個整體,這樣看上去是不是和下面的聲明很像?

int a = 30;

所以,int* p = new int(30)中,*p返回的值就是int的本值30,而p則只是返回30的首地址。

再看看下面的代碼:

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int* arrint = newint[3];
  5. arrint[0] = 20;
  6. arrint[1] = 21;
  7. arrint[2] = 22;
  8. for(int i =0; i < 3; i++)
  9. {
  10. printf(" 數組[%d] = %d\n", i, arrint[i]);
  11. }
  12. delete [] arrint; // 清理記憶體
  13. getchar();
  14. }
#include <stdio.h>void main(){int* arrint = new int[3];arrint[0] = 20;arrint[1] = 21;arrint[2] = 22;for(int i =0; i < 3; i++){printf("  數組[%d] = %d\n", i, arrint[i]);}delete [] arrint; // 清理記憶體getchar();}

現在你可以猜猜它的運行結果是什麼。

從上面的代碼我們又看到了指標的第三個功能:建立數組

上例中,我建立了有三個元素的數組。在使用完成後,要使用delete來刪除已指派的記憶體,所以,我們的第一個例子中,其實不完善,我們沒有做記憶體清理。

int* pint = new int(100);

/****/

delete pint;

為什麼指標可以建立數組?前面我提到過,指標是指向首地址的,那麼你想想,我們的數組如果在堆上分配了記憶體,它們是不是也按一定次序存放在一塊連續的記憶體位址中,整個數組同樣構成了一段記憶體塊。

二、取地址符號&

很多書和教程都把這個符號叫引用,但我不喜歡翻譯為引用,因為引用不好理解,如果叫取地址符,那我估計你就會明白了,它就是返回一個變數的首地址。

看看例子:

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int a = 50;
  5. int* p = &a;
  6. printf(" a的值:%d\n", a);
  7. printf(" p的值:0x_%x\n", p);
  8. getchar();
  9. }
#include <stdio.h>void main(){int a = 50;int* p = &a;printf("  a的值:%d\n", a);printf("  p的值:0x_%x\n", p);getchar();}

我們不能直接對指標變數賦值,要把變數的地址傳給指標,就要用取地址符&。上面的代碼中我們聲明了int類型的變數a,值為50,通過&符號把變數a的地址存到p指標中,這樣,p指向的就是變數a的首地址了,故:a的值的50,而p的值就應該是a的地址。

那麼,這樣做有啥好處呢?我們把上面的例子再擴充一下,變成這樣:

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void main()
  3. {
  4. int a = 50;
  5. int* p = &a;
  6. printf(" a的值:%d\n", a);
  7. printf(" p的值:0x_%x\n", p);
  8. /* 改變指標所指向的地址塊中的值,就等於改變了變數的值 */
  9. *p = 250;
  10. printf(" a的新值:%d\n", a);
  11. getchar();
  12. }
#include <stdio.h>void main(){int a = 50;int* p = &a;printf("  a的值:%d\n", a);printf("  p的值:0x_%x\n", p);/* 改變指標所指向的地址塊中的值,就等於改變了變數的值 */*p = 250;printf("  a的新值:%d\n", a);getchar();}

先預覽一下結果。

不知道大家在這個例子中發現了什嗎?

我們定義了變數a,值為50,然後指標p指向了a的首地址,但注意,後面我只是改變了p所指向的那塊記憶體中的值,我並沒有修改a的值,但是,你看看最後a的值也變為了250,想一想,這是為什嗎?

三、參數的傳遞方式

很多書,包括一些電腦二級的考試內容,那些傻S磚家只是想出一大堆與指標相關的莫名其妙的考題,但很讓人找不到指標在實際應用到底能幹什麼,我估計那些磚家自己也不知識吧。所以,我們的考試最大的失敗,就是讓學生不知識學了有什麼用。

上面介紹了指標可以存首地址,可以分配記憶體,可以建立數組,還說了取地址符&,那麼,這些東西有什麼用呢?你肯定會問,我直接聲明一個變數也是要佔用記憶體的,那我為什麼要吃飽了沒事幹還要用指標來存放首地址呢?

好,我先不回答,我們再說說函數的參數傳遞。看看下面這樣的例子。

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void fn(int x)
  3. {
  4. x += 100;
  5. }
  6. void main()
  7. {
  8. int a = 20;
  9. fn(a);
  10. printf(" a : %d\n", a);
  11. getchar();
  12. }
#include <stdio.h>void fn(int x){x += 100;}void main(){int a = 20;fn(a);printf("  a : %d\n", a);getchar();}

我們希望,在調用函數fn後,變數a的值會加上100,現在我們運行一下,看看結果:

我們可能會很失望,為什麼會這樣?我明明是把20傳進了fn函數的,為什麼a的值還是不變呢?不用急,我們再把代碼改一下:

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void fn(int x)
  3. {
  4. printf(" 參數的地址:0x_%d\n", &x);
  5. x += 100;
  6. }
  7. void main()
  8. {
  9. int a = 20;
  10. fn(a);
  11. printf(" a : %d\n", a);
  12. printf(" a的地址:0x_%x\n", &a);
  13. getchar();
  14. }
#include <stdio.h>void fn(int x){printf("  參數的地址:0x_%d\n", &x);x += 100;}void main(){int a = 20;fn(a);printf("  a : %d\n", a);printf("  a的地址:0x_%x\n", &a);getchar();}

運行結果如下:

看到了嗎?變數a和fn函數的參數x的地址是不一樣的,這意味著什麼呢?這說明,變數a的值雖然傳給了參數x,但實際上是聲明了一個新變數x,而x的值為20罷了,最後加上100,x的中的值是120,但a的值沒有變,因為在函數內被+100的根本不是變數a,而是變數x(參數)。

這樣,就解釋了為什麼麼函數調用後a的值仍然不變的原因。

那麼,如何讓函數調用後對變數a作修改,讓它變成120呢?這裡有兩個方法:

(1)指標法。把參數改為指標類型。

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void fn(int* x)
  3. {
  4. *x += 100;
  5. }
  6. void main()
  7. {
  8. int a = 20;
  9. fn(&a);//用取地址符來傳遞,因為指標是儲存地址的
  10. printf(" a : %d\n", a);
  11. getchar();
  12. }
#include <stdio.h>void fn(int* x){*x += 100;}void main(){int a = 20;fn(&a);//用取地址符來傳遞,因為指標是儲存地址的printf("  a : %d\n", a);getchar();}

這裡要注意,把變數傳給指標類型的參數,要使用取地址符&。

那麼,這次運行正確嗎?

好了,終於看到想要的結果了。

(2)引用法,就是把參數改為&傳遞的。

[cpp]
view plaincopyprint?
  1. #include <stdio.h>
  2. void fn(int& x)
  3. {
  4. x += 100;
  5. }
  6. void main()
  7. {
  8. int a = 20;
  9. fn(a);//直接傳變數名就行了
  10. printf(" a : %d\n", a);
  11. getchar();
  12. }
#include <stdio.h>void fn(int& x){x += 100;}void main(){int a = 20;fn(a);//直接傳變數名就行了printf("  a : %d\n", a);getchar();}

可以看到,這樣的運行結果也是正確的。

四、指標與對象

不管是類還是結構(其實結構是一種特殊的類),它們在建立時還是要建立記憶體的,但是,建立類的對象也有兩種方式,直接聲明和用指標來分配新執行個體。

[cpp]
view plaincopyprint?
  1. #include <iostream>
  2. using namespace std;
  3. class Test
  4. {
  5. public:
  6. Test();
  7. ~Test();
  8. void Do(char* c);
  9. };
  10. Test::Test()
  11. {
  12. cout << "Test對象被建立。" << endl;
  13. }
  14. Test::~Test()
  15. {
  16. cout << "Test對象被銷毀。" << endl;
  17. }
  18. void Test::Do(char* c)
  19. {
  20. cout << "在" << c << "中調用了Do方法。" << endl;
  21. }
  22. void Func1()
  23. {
  24. Test t;
  25. t.Do("Func1");
  26. /*
  27. 當函數執行完了,t的生命週期結束,發生析構。
  28. */
  29. }
  30. void Func2()
  31. {
  32. Test* pt = new Test;
  33. pt -> Do("Func2");
  34. /*
  35. 用指標建立的對象,就算指標變數的生命週期結束,但記憶體中的對象沒有被銷毀。
  36. 因此,解構函式沒有被調用。
  37. */
  38. }
  39. int main()
  40. {
  41. Func1();
  42. cout << "---------------------" << endl;
  43. Func2();
  44. getchar();
  45. return 0;
  46. }
#include <iostream>using namespace std;class Test{public:Test();~Test();void Do(char* c);};Test::Test(){cout << "Test對象被建立。" << endl;}Test::~Test(){cout << "Test對象被銷毀。" << endl;}void Test::Do(char* c){cout << "在" << c << "中調用了Do方法。" << endl;}void Func1(){Test t;t.Do("Func1");/*當函數執行完了,t的生命週期結束,發生析構。*/}void Func2(){Test* pt = new Test;pt -> Do("Func2");/*用指標建立的對象,就算指標變數的生命週期結束,但記憶體中的對象沒有被銷毀。因此,解構函式沒有被調用。*/}int main(){Func1();cout << "---------------------" << endl;Func2();getchar();return 0;}

我們來看看這個例子,首先定義了一個類Test,在類的建構函式中輸出對象被建立的個息,在發生析構時輸出對象被銷毀。

接著, 我們分別在兩個函數中建立Test類的對象,因為對象是在函數內部定義的,根據其生命週期原理,在函數返回時,對象會釋放,在記憶體中的資料會被銷毀。理論上是這樣的,那麼,程式實際運行後會如何呢?

這時候我們發現一個有趣的現象,在第一個函數直接以變數形式建立的對象在函數執行完後被銷毀,因為解構函式被調用;可是,我們看到第二個函數中並沒有發生這樣的事,用指標建立的對象,在函數完成時居然沒有調用解構函式。

直接建立對象,變數直接與類執行個體關聯,這樣一來,當變數的生命週期結束時,自然會被處理掉,而用指標建立的執行個體,指標變數本身並不儲存該執行個體的資料,它僅僅是存了對象執行個體的首地址罷了,指標並沒有與執行個體直接有聯絡,所以,在第二個函數執行完後,被銷毀的是Test*,而不是Test的對象,僅僅是儲存首地址的指標被釋放了而已,而Test對象依然存在於記憶體中,因此,在第二個函數完成後,Test的解構函式不會調用,因為它還沒死呢。

那麼,如何讓第二個函數在返回時也銷毀對象執行個體呢?還記得嗎,我前文中提過。對,用delete.。

[cpp]
view plaincopyprint?
  1. void Func2()
  2. {
  3. Test* pt = new Test;
  4. pt -> Do("Func2");
  5. delete pt;
  6. }
void Func2(){Test* pt = new Test;pt -> Do("Func2");delete pt;}

現在看看,是不是在兩個函數返回時,都能夠銷毀對象。

現在你明白了吧?

由此,可以得出一個結論:指標只負責為對象分配和清理記憶體,並不與記憶體中的對象執行個體有直接關係。

 

原文出處:http://blog.csdn.net/tcjiaan/article/details/8493072

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.