程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> Win32開發入門(1) 關於C++的幾個要點

Win32開發入門(1) 關於C++的幾個要點

編輯:關於C++

我不知道各位,一提起C++,第一感覺是什麼?而據俺的觀察,許多人幾乎成了“談C色變”。不管 是C還是C++,一直以來都被很多人視為相當難學的玩意兒,幸好只是一個C++,沒有C--,C**和C//,不 然,那還得了?曾記得,某年某月某日,在某論壇上看到有牛人說“C++++”,當時我猜想這是啥玩意 兒,後來經過一番順虅摸瓜,深入調查發現,原來有人作了這麼個有趣的等式:C# == C++++。

顯然,這個等式也不太正確,C#不僅繼承了C++一些特性,也繼承了Delphi中的和VB中的一些優點。

好了,這個等式意義不大,咱們不扯它了。前面我寫了許多和移動開發的文章,估計現在移動 市場泡沫也差不多膨脹起來了,你說這泡沫,泡到什麼程度呢?據說連壓根連程序都沒寫過的人,也嚷 著說:移動開發,我要(幸好不是官人,不然動機不純)。

這很容易讓人聯想到“全民炒股” 的創世紀大笑話,中國人貌似很喜歡這樣,一曰跟風,二曰盲從。這二者合並起來,正好為市場本質上 的“自發性,盲目性”等特征作了相當有力的诠釋,難怪羅斯福總統說必要時還得宏觀調控。在1932年 如果還不調控的話,估計到了1945年,在太平洋戰場上完蛋的不是零式戰斗機了,該是地獄貓戰斗機了 ,呵呵。

不管是移動互聯網,還是雲計算,各位還是理性地考慮一下吧,認為有需要才進行投 資,目前來說,移動市場絕大部分還是在娛樂上,要說真要和商業模式融合,估計現在的手機和平板電 腦還達不到這個指標,未來幾年有可能開始和商業平台對接,今年的話,不太可能,如果你計劃把你的 商業應用向移動平台擴展(我這裡用擴展,千萬不要轉移,不然會丟失原來的市場),那麼,你現在可 能要考慮你現有的應用程序框架到底有多少可以進行擴展了。

這擴展一事說起來容易,做起來 可不輕松,記得去年我在F公司工作,嘗把ERP的功能,從小的模塊開始,向Web/電子商務平台整合,技 術上是沒問題的,但業務邏輯上有可能會一敗塗地。所以,有時候,咱們做開發的,學一學市場營銷、 財務會計、企業管理、HR,甚至是文學藝術,對我們的成長還是有好處的,你只會寫程序,有時候很容 易“當局者迷”,金庸老先生在小說裡常常把這個稱為“走火入X”,不知道歐陽鋒大哥算不算。

一、指針,真的那麼恐怖嗎

很多人學C語言,就是敗在她的“石榴裙”下的,指針(Pointer),這裡我為什麼要把英文原名寫 出來了,我目的想讓你思考一下,我們常叫它指針,但是,這個翻譯到底合不合理?

在學習C 和C++時,很多人會被指針給弄得“六神無主”,雖然大家在許多書上都看到,指針就是用來保存地址 的,是吧。然很多人就是無法理解。我這裡告訴大家一個技巧,凡是遇到抽象的東西,你就不妨嘗試著 在客觀存在的事物中去尋找與其相似的東西,例如從我們日常生活中入手。我們要明白一個道理,所有 抽象的東西,追根到底,都是從客觀事物中提取出來的,也就是說,任何抽象的東西,都會在客觀世界 中找到它的原形。

那麼,在我們的日常生活中,有沒有與C/C++中指針對應的東西呢?有,多得 是:

指南針。

手表。

電流表/電壓表。

汽車上用來顯示剩余汽油的表。

……

看看,這些物體都有什麼共同特點?是不是都有一根或者多根指針?比如,下面圖 片中的山寨手表。

現在,我要你從山寨手表中讀出其指示的時間,那麼,你想一想 ,你會怎麼看出來的

這個應該會看吧,小學生都會用了。我們會先看一下時針所指的方向在哪 個時刻范圍內,如10-11之間,所以我們確定了是10點鐘;然後,我們看到分針所指的是第二個刻度, 我們讀出2,所以一組合,就是10點2。

是不是這樣讀,沒讀錯吧?

像電流表也是這樣, 我們就是通過指針所指的方向找到對應的刻度,就知道當前電流是多少安/毫安,家用的應該是以毫安 為單位。

我們知道,程序的運行是存在內存中的,因此,我們在代碼中聲明的所有變量都是存 放在內存中的某塊區域中,所以,在C語言中,指針用來告訴我們,某個變量在內存中的首地址在哪。 注意,是首地址,因為變量的長度不一定就是一個字節,有可能N多個字節,它在內存中是排列在一段 連續的區域塊中。

比如,某中學的教學樓,每個年級使用一棟樓,初一年級在A棟,初二在B棟 ,初三在C棟。某學生在校其間,由於多次非禮女同學,老師說要見家長,於是,家長K君來到了某學校 ,但學校那麼大,怎麼找到K君的兒子所在的教室呢?這時候,保安人員告訴K君,初二年級在B棟,並 用手指著東北方向(指針指向的內存地址塊的位置)。

於是,K君順著保安人員手指的方向找到 了B棟,他知道他兒子在3班,而樓上的教室都是按順序的,1班在第一個教室,2班在第二個教室,以此 類推。所以,K君很快就找到他兒子,然後把他教育了兩頓(訪問或處理指針所指向內存中的數據), 然後,K君很郁悶地下樓,離開了學校(發生析構,清理內存)。

因此,我們可以對指針這樣寶 義:

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

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

從上面的故事中,我們看到指針的兩個用途:索引內存和分配內存。

看看下面這個例 子。

#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的首地址。

再看 看下面的代碼:

#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;

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

二、取地址符號&

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

看看例子:

#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的地址。

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

#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磚家只是想出一大堆與指針相關的莫名其妙的考 題,但很讓人找不到指針在實際應用到底能干什麼,我估計那些磚家自己也不知識吧。所以,我們的考 試最大的失敗,就是讓學生不知識學了有什麼用。

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

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

#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的值還是不變呢 ?不用急,我們再把代碼改一下:

#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)指針法。把參數改為指針類型 。

#include <stdio.h>    
       
void fn(int* x)    
{    
    *x += 100;    
}    
       
void main()    
{    
    int a = 20;    
    fn(&a);//用取地址符來傳遞,因為指針是保存地址的    
    printf("  a : %d\n", a);    
    getchar();    
}

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

那麼,這次運行正確嗎 ?

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

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

#include <stdio.h>    
       
void fn(int& x)    
{    
    x += 100;    
}    
       
void main()    
{    
    int a = 20;    
    fn(a);//直接傳變量名就行了    
    printf("  a : %d\n", a);    
    getchar();    
}

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

四、指針與對象

不管是類還是結構(其實結構是一種特殊的類),它們在創建時還是要創建內存的,但是,創建類 的對象也有兩種方式,直接聲明和用指針來分配新實例。

#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.。

void Func2()    
{    
    Test* pt = new Test;    
    pt -> Do("Func2");    
    delete pt;    
}

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

現在你明白了吧?

由此,可以得出一個結論:指針只負責為對象分配和清理內存,並 不與內存中的對象實例有直接關系。

補充:

我只是為了說明,指針其實是一個數字,它 動態分配內存後,裡面存的就是它指向的內存的首地址,但是,指針變量的生命周期終結後,並沒有去 清理它所指向的內存,而需要顯示使用delete。動態new完後,不要忘了delete,不要誤解了,與 const* 沒關系。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved