<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 134,comments - 22,trackbacks - 0
       一、指針與引用

    一 概括

    指針和引用,在C++的軟件開發(fā)中非常常見,如果能恰當(dāng)?shù)氖褂盟鼈兡軌驑O大的提 高整個(gè)軟件的效率,但是很多的C++學(xué)習(xí)者對它們的各種使用情況并不是都了解,這就導(dǎo)致了實(shí)際的軟件開發(fā)中經(jīng)常會內(nèi)存泄漏,異常拋出,程序崩潰等問題。對 于C和C++的初學(xué)者,那更是被它們搞的迷迷糊糊。本篇作為[深入C++]系列的第一節(jié),我們就帶領(lǐng)大家把指針和引用這個(gè)基本功練好。

    二 指針

    指針,指針的定義是什么呢?好像要想給個(gè)直接的定義還是很難的哦,所以我們這里用它的語法結(jié)合圖來認(rèn)識它。

    int i = 10;int *p = NULL;p = &i;int j = *p; int **pP = NULL; pP = &p;

    在上面的幾條語句中,&用來定義引用變量或?qū)ψ兞咳∑涞刂罚?用來定義指針或得到指針?biāo)赶虻淖兞浚渲衟為定義的指針變量,它指向int變量i,而pP為二級指針變量,它指向指針變量p。相應(yīng)的示意圖如下:

    C++是對C的擴(kuò)展,我們首先來看指針在C中的使用,下面的經(jīng)典實(shí)例來自林銳的 《高質(zhì)量編程》,記住函數(shù)的默認(rèn)參數(shù)傳遞方式為按值傳遞,即實(shí)參被傳入函數(shù)內(nèi)時(shí)進(jìn)行了拷貝,函數(shù)內(nèi)其實(shí)是對拷貝對象的操作,還有當(dāng)函數(shù)使用return返 回變量時(shí),其實(shí)返回的是原對象的一個(gè)拷貝,此時(shí)的實(shí)參和原對象有可能是一般變量也有可能是指針變量。

    Code
    #pragma once
    #include 
    <cstring>
    #include 
    <cstdio>
    #include 
    <cstdlib>

    // -----------------------------------------------
    void GetMemory1(char *p, int num)
    {
        p 
    = (char*
    )malloc(num);
    }
    void Test1(void
    )
    {
        
    char *str =
     NULL;
        GetMemory1(str, 
    100
    );
        strcpy(str, 
    "hello world"
    );
        printf(str);
    }

    // -----------------------------------------------

    void GetMemory2(char **p, int num)
    {
        
    *= (char*
    )malloc(num);
    }
    void Test2(void
    )
    {
        
    char * str =
     NULL;
        GetMemory2(
    &str, 100
    );
        strcpy(str, 
    "hello world"
    );
        printf(str);
        free(str);
    }

    // -----------------------------------------------

    char* GetMemory3(void)
    {
        
    char p[] ="hello world"
    ;
        
    return
     p;
    }
    void Test3(void
    )
    {
        
    char* str =
     NULL;
        str 
    =
     GetMemory3();
        printf(str);
    }

    // -----------------------------------------------

    char* GetMemory4(void)
    {
        
    char *= "hello world"

        
    return
     p;
    }
    void
     Test4()
    {
        
    char* str =
     NULL;
        str 
    =
     GetMemory4();
        printf(str);
    }

    // -----------------------------------------------

    char* GetMemory5(void)
    {
        
    char *= (char*)malloc(100
    );
        strcpy(p,
    "hello world"
    );
        
    return
     p;
    }
    void
     Test5()
    {
        
    char* str =
     NULL;
        str 
    =
     GetMemory5();
        printf(str);
        free(str);
    }

    // -----------------------------------------------

    void  Test6( void )
    {
        
    char * str = (char*)malloc(100
    );
        strcpy(str, 
    "hello"
    );
        free(str);
        
    if (str !=
     NULL)
        {
            strcpy(str,  
    "world"
     );
            printf(str);
        }
    }

    // -----------------------------------------------

    void TestPointerAndReference()
    {
        
    //
     -----------------------------------------------
        
    //
     請問運(yùn)行Test1函數(shù)會有什么樣的結(jié)果?
        
    //

        
    // 答:程序崩潰。同時(shí)有內(nèi)存泄漏。
        
    //

        
    // 因?yàn)樵贕etMemory1函數(shù)的調(diào)用過程中,其實(shí)是對實(shí)參指針p做了拷貝,拷貝為局部變量,
        
    //
     在函數(shù)內(nèi)的操作是對局部變量的操作,局部變量與實(shí)參是兩個(gè)不同的變量,相互不影響。
        
    //

        
    // 所以,當(dāng)GetMemory1調(diào)用結(jié)束時(shí),Test1函數(shù)中的 str一直都是 NULL。
        
    //
     strcpy(str, "hello world");將使程序崩潰。
        
    //

        
    //Test1();

        
    //
     -----------------------------------------------
        
    //
     請問運(yùn)行Test2函數(shù)會有什么樣的結(jié)果?
        
    //

        
    // 答:(1)能夠輸出hello world; (2)但是調(diào)用結(jié)束要對內(nèi)存釋放,否則內(nèi)存泄漏;
        
    //
        Test2();

        
    //
     -----------------------------------------------
        
    //
     請問運(yùn)行Test3函數(shù)會有什么樣的結(jié)果?
        
    //

        
    // 答:可能是亂碼。
        
    //

        
    // 因?yàn)镚etMemory3返回的是指向“棧內(nèi)存”的指針,
        
    //
     該指針的地址不是 NULL,但其原現(xiàn)的內(nèi)容已經(jīng)被清除,新內(nèi)容不可知。
        
    // 

        Test3();

        
    //
     -----------------------------------------------
        
    //
     請問運(yùn)行Test4函數(shù)會有什么樣的結(jié)果?
        
    //

        
    // 答:(1)能夠輸出hello world; (2) 此時(shí)的str指向了常量區(qū),不需要程序員釋放,程序結(jié)束自動釋放。
        
    //
        Test4();

        
    //
     -----------------------------------------------
        
    //
     請問運(yùn)行Test5函數(shù)會有什么樣的結(jié)果?
        
    //

        
    // 答:(1)能夠輸出hello world; (2)但是調(diào)用結(jié)束要對內(nèi)存釋放,否則內(nèi)存泄漏;
        
    //
        Test5();

        
    //
     -----------------------------------------------
        
    //
     請問運(yùn)行Test6函數(shù)會有什么樣的結(jié)果?
        
    //

        
    // 答:篡改動態(tài)內(nèi)存區(qū)的內(nèi)容,后果難以預(yù)料,非常危險(xiǎn)。
        
    //

        
    // 因?yàn)閒ree(str);之后,str成為野指針,
        
    //
     if(str != NULL)語句不起作用。
        
    //
        Test6();

    三 C++指針與引用

    引用,其實(shí)是變量的別名,與變量是同一個(gè)東東。例如 int i = 10; int &a = i; int &b = i; 這樣 a,b為i的引用,即a,b為i的別名,還有 int * pi = new int(10); int *& pa = pi; int *& pb = pi; 此時(shí)pa,pb為pi的別名。在C++中引入了引用概念后,我們不僅可以定義引用變量,相應(yīng)的函數(shù)的傳遞方式也增加了按引用傳遞,當(dāng)參數(shù)以引用方式傳遞 時(shí),函數(shù)調(diào)用時(shí)不對實(shí)參進(jìn)行拷貝,傳入函數(shù)內(nèi)的變量與實(shí)參是同一個(gè)變量。下面的實(shí)例演示了指針和引用在C++的使用。

    Code
    #pragma once
    #include 
    <iostream>

    class Point
    {
    public
    :
        Point(
    int x, int
     y)
        {
            _x 
    =
     x;
            _y 
    =
     y;
        }
        
    void SetX(int
     x)
        {
            _x 
    =
     x;
        }
        
    void SetY(int
     y)
        {
            _y 
    =
     y;
        }
        
    void
     PrintPoint()
        {
            std::cout 
    << "The Point is : "<< '(' << _x << ',' << _y << ')' <<
     std::endl;
        }
        
    void
     PrintPointAdress()
        {
            std::cout 
    << "The Point's adress is : " << this <<
     std::endl;
        }
    private
    :
        
    int
     _x;
        
    int
     _y;
    };

    // 默認(rèn)按值傳遞,當(dāng)傳入時(shí)對對像進(jìn)行了拷貝,函數(shù)內(nèi)只是對所拷貝值的修改,所以實(shí)參沒被修改。

    void ChangeValue(Point pt, int x, int y)
    {
        pt.SetX(x);
        pt.SetY(y);
    }
    // 按引用傳遞,函數(shù)內(nèi)外同一值,所以修改了實(shí)參。

    void ChangeValueByReference(Point& pt, int x, int y)
    {
        pt.SetX(x);
        pt.SetY(y);
    }
    // 通過傳遞指針,雖然實(shí)參指針傳入時(shí)也產(chǎn)生了拷貝,但是在函數(shù)內(nèi)通過指針任然修改了指針?biāo)傅闹怠?/span>
    void ChangeValueByPointer(Point *pt, int x, int y)
    {
        pt
    ->
    SetX(x);
        pt
    ->
    SetY(y);
    }

    void
     TestChangeValue()
    {
        Point pt(
    10,10
    );
        pt.PrintPoint();
        ChangeValue(pt,
    100,100
    );
        pt.PrintPoint();
        ChangeValueByReference(pt,
    200,200
    );
        pt.PrintPoint();
        ChangeValueByPointer(
    &pt,300,300
    );
        pt.PrintPoint();
    }

    // 按引用傳遞,所以指針可以被返回。

    void ChangePointerByReference(Point *& pPt, int x, int y)
    {
        pPt 
    = new
     Point(x, y);
    }
    // 對二級指針拷貝,但是二級指針指向的一級指針被返回。

    void ChangePointerByTwoLevelPointer(Point **pPt, int x, int y)
    {
        
    *pPt = new
     Point(x, y);
    }

    void
     TestChangePointer()
    {
        Point 
    *pPt =
     NULL;
        ChangePointerByReference(pPt, 
    1000,1000
    );
        pPt
    ->
    PrintPoint();
        pPt
    ->
    PrintPointAdress();
        delete pPt;
        pPt 
    =
     NULL;

        
    int *= new int(10
    );
        
    //
    int *p2 = new int(10);
        
    //int *p3 = new int(10);


        ChangePointerByTwoLevelPointer(
    &pPt, 2000,2000);
        pPt
    ->
    PrintPoint();
        pPt
    ->
    PrintPointAdress();
        delete pPt;
        pPt 
    =
     NULL;
    }

    void
     TestPointerAndReference2()
    {
        TestChangeValue();
        TestChangePointer();
    }

    運(yùn)行結(jié)果如下:

    四 函數(shù)參數(shù)傳遞方式,函數(shù)中return語句和拷貝構(gòu)造函數(shù)的關(guān)系

    通過上面的2個(gè)實(shí)例,如果還有人對函數(shù)的參數(shù)傳遞方式和return有疑問的啊,可以對下面的代碼親自debug。

    Code
    #pragma once
    #include 
    <iostream>

    class CopyAndAssign
    {
    public
    :
        CopyAndAssign(
    int
     i)
        {
            x 
    =
     i;
        }
        CopyAndAssign(
    const CopyAndAssign&
     ca)
        {
            std::cout 
    << "拷貝構(gòu)造!" <<
     std::endl;
            x 
    =
     ca.x;
        }
        CopyAndAssign
    & operator=(const CopyAndAssign&
     ca)
        {
            std::cout 
    << "賦值操作符" <<
     std::endl;
            x 
    =
     ca.x;
            
    return *this
    ;
        }
    private
    :
        
    int
     x;
    };

    CopyAndAssign ReturnCopyAndAssign()
    {
        CopyAndAssign temp(
    20); // 構(gòu)造

        return temp;
    }
    void
     CopyAndAssignAsParameter(CopyAndAssign ca)
    {
    }

    CopyAndAssign
    &
     ReturnCopyAndAssignByReference()
    {
        CopyAndAssign temp(
    20); // 構(gòu)造

        return temp;
    }
    void CopyAndAssignAsParameterByReference(CopyAndAssign&
     ca)
    {
    }

    void
     TestCopyAndAssign()
    {
        CopyAndAssign c1(
    10); // 構(gòu)造

        CopyAndAssignAsParameter(c1); // 拷貝構(gòu)造
        ReturnCopyAndAssign(); // 拷貝構(gòu)造

        CopyAndAssignAsParameterByReference(c1); 
        ReturnCopyAndAssignByReference(); 
    }

    親自debug,效果會更好,運(yùn)行結(jié)果如下:

    五 總結(jié)

    1) 指針也是變量,它存儲其他變量的地址。例如int *p = new int(10); p是指針變量,p實(shí)際是存儲了一個(gè)int變量的地址。
    2)引用其實(shí)是一個(gè)別名,跟原對象是同一個(gè)東東。例如 std::string str = "hello"; std::string & strR = str;此時(shí)strR跟str其實(shí)是同一個(gè)東東,strR可以看成是str的一個(gè)小名。
    3) 函數(shù)默認(rèn)的傳參方式為按值傳遞,即當(dāng)實(shí)參傳入是其實(shí)是做了拷貝,函數(shù)內(nèi)其實(shí)是對所拷貝對象的操作。例如 void Increase(int x) { x++; } 調(diào)用時(shí) int i = 10; Increase(i); Increase函數(shù)內(nèi)部其實(shí)是對i的一個(gè)拷貝(我們假設(shè)為ii)進(jìn)行++。所以在函數(shù)調(diào)用結(jié)束后原來的i的值仍然保持不變。
    4)函數(shù)的傳參方式 可以顯示的指定按引用來傳遞,按引用傳遞時(shí),函數(shù)內(nèi)即對實(shí)參的操作,沒有拷貝操作,所以函數(shù)內(nèi)對實(shí)參的修改,當(dāng)然后調(diào)用結(jié)束后反映到實(shí)參上。例如void Increase(int & x) { x++;} 調(diào)用 int i = 10; Increase(i);此時(shí)Increase內(nèi)部的++即是對i的操作,所以函數(shù)調(diào)用結(jié)束后i的值被修改。
    5)函數(shù)中如果有return返回變量時(shí),其實(shí)所返回的也是一個(gè)拷貝。所以當(dāng)使用return返回對象時(shí)一定要考慮所返回對象的拷貝構(gòu)造函數(shù)是否能夠滿足要求。

    六 使用注意

    1) malloc/free一起使用。

    2)new/delete一起使用。

    3)對于new中有[]時(shí),相應(yīng)的必須使用delete[]來釋放。

    4)用free或delete釋放了內(nèi)存之后,立即將指針設(shè)置為NULL,防止產(chǎn)生“野指針”。

    5)對指針的使用前,應(yīng)該檢查是否為空,空指針可能導(dǎo)致程序崩潰。

    6)非內(nèi)置類型的參數(shù)傳遞,使用const引用代替一般變量。

    七 謝謝!

                                          二 指針與數(shù)組

    一 C指針操作函數(shù)

    new和delete對C++的程序員也許很熟悉,但是malloc和free被用來在C代碼中用來內(nèi)存分配和釋放,很多C++開發(fā)者并不能游刃有余的使用,下面實(shí)例解析malloc和free的使用。

           malloc void *malloc(long NumBytes):該函數(shù)分配了NumBytes個(gè)字節(jié),并返回了指向這塊內(nèi)存的指針。如果分配失敗,則返回一個(gè)空指針(NULL)。
           free void free(void *FirstByte): 該函數(shù)是將之前用malloc分配的空間還給程序或者是操作系統(tǒng),也就是釋放了這塊內(nèi)存,讓它重新得到自由。

    實(shí)例如下:

    Code
    #pragma once
    #include 
    <string>

    void TestMallocAndFree()
    {
        
    char *ptr = NULL;
        ptr 
    = (char*)malloc(100 * sizeof(char));
        
    if (NULL == ptr)
        {
            
    return;
        }
        memcpy(ptr,
    "Hello!",strlen("Hello!"));
        free(ptr);
        ptr 
    = NULL;

         typedef 
    struct data_type 
         {
           
    int age;
           
    char *name;
         } data;
     
         data 
    *willy = NULL;
         willy 
    = (data*) malloc( sizeof(data) );
         willy
    ->age = 20;
         willy
    ->name = "jason"// 此時(shí)的name指向了常量區(qū),所以name指針不需要程序員釋放。
         free( willy );
         willy 
    = NULL;
    }

    malloc/free 和new/delete的區(qū)別:

    1)new/delete是保留字,不需要頭文件支持. malloc/free需要頭文件庫函數(shù)支持. 使用malloc/free需要包含 #include<cstdlib> 或<stdlib>.

    2) new 建立的是一個(gè)對象,new會根據(jù)對象計(jì)算大小,直接返回對象的指針,當(dāng)使用完畢后調(diào)用delete來釋放,但malloc分配的是一塊內(nèi)存,需要用戶制定 所要分配內(nèi)存的大小,而且返回的均為void的指針,使用時(shí)需要相應(yīng)的強(qiáng)制類型轉(zhuǎn)化,使用結(jié)束后調(diào)用free來釋放內(nèi)存.

    3)new/delete的使用除了分配內(nèi)存和釋放,還調(diào)用了類型的構(gòu)造函數(shù)和析構(gòu)函數(shù),而malloc/free只是簡單的分配和釋放內(nèi)存。

    二 數(shù)組與指針

    C++的數(shù)組經(jīng)常需要和指針來結(jié)合使用,下面來進(jìn)行相關(guān)的強(qiáng)化訓(xùn)練。實(shí)例如下:

    Code
    #pragma once
    #include 
    <iostream>
    using namespace std;

    void PrintArray(double *p, int num)
    {
        
    for(int i = 0; i < num; ++i)
        {
            cout 
    << " " << p[i] << " ";
        }
        cout 
    << endl << "The array is end!" << endl;
    }
    void PrintArray(double arr[3]) 
    {
        
    for(int i = 0; i < 3++i)
        {
            cout 
    << " " << *(arr+i)/*arr[i]*/ << " ";
        }
        cout 
    << endl << "The array is end!" << endl;
    }
    void ChangeArray(double arr[3]) // 數(shù)組傳參為傳指針,所以函數(shù)內(nèi)可以修改
    {
        
    for(int i = 0; i < 3++i)
        {
            arr[i] 
    = 10;
        }
    }
    void PrintArray(double arr[3][3])
    {
        
    for(int i = 0; i < 3++i)
            
    for(int j = 0; j < 3++j)
                cout 
    << " " << arr[i][j] << " ";
        cout 
    << endl << "The array is end!" << endl;
    }

    int GetLength(){return 3;}

    void TestArray()
    {
        
    // 數(shù)組的定義和初始化
        short months[12= {31,28,31,30,31,30,31,31,30,31,30,31};
        
    double arr[3]; 
        arr[
    0= 1.0;
        arr[
    1= 2.0;
        arr[
    2= 3.0;
        
    double arr2[] = {1.0,2.0,3.0};
        
    //double arr3[3] = arr; // error
        PrintArray(arr,3);
        PrintArray(
    &arr[0],3);
        PrintArray(arr2);

        
    double matrix2 [2][2= {1.0,0.0,0.0,1.0};
        
    double matrix3 [3][3= {{1.0,0.0,0.0},
                                {
    0.0,1.0,0.0},
                                {
    0.0,0.0,1.0}};
        PrintArray(matrix3[
    0],3*3);
        PrintArray(
    &matrix3[0][0],3*3);
        
    //PrintArray(matrix3,3*3);
        PrintArray(matrix3);

        
    // 指針來模擬數(shù)組
        double *p3 = new double[GetLength()];
        p3[
    0= 10.0;
        p3[
    1= 20.0;
        p3[
    2= 30.0;
        PrintArray(p3,
    3);
        PrintArray(p3);
        delete []p3;

        
    // 數(shù)組+指針實(shí)現(xiàn)二維變長數(shù)組
        double *p4[2];
        p4[
    0= new double[2];
        p4[
    1= new double[4];
        p4[
    0][0= 10;
        p4[
    0][1= 20;
        p4[
    1][0= 30;
        p4[
    1][1= 40;
        p4[
    1][2= 50;
        p4[
    1][3= 60;
        PrintArray(p4[
    0],2);
        PrintArray(p4[
    1],4);
        delete [] p4[
    0];
        delete [] p4[
    1];

        PrintArray(arr); 
    // 數(shù)組傳參為傳指針,所以函數(shù)內(nèi)可以修改
        ChangeArray(arr);
        PrintArray(arr);
    }

    代碼分析總結(jié):

    1)數(shù)組的定義必須使用常量指定長度,例如:double arr[3],但是使用指針時(shí)可以是運(yùn)行時(shí)指定,例如double *p3 = new double[getLength()]。
    2)數(shù)組定義時(shí)即分配空間且在棧上,不需要程序員來對內(nèi)存管理,但是如果對指針使用了new[],則必須由程序員在使用完畢后delete[]。
    3) 一維數(shù)組數(shù)組名即第一個(gè)元素的地址,例如:double arr[3]中,arr == &arr[0]為true。
    4)二維數(shù)組中第一行的地址即為第一個(gè)元素的地址,例如:double matrix3 [3][3],matrix[0] == &matrix[0][0]為true。
    5)可以使用指針數(shù)組來模擬變長二維數(shù)組,例如:double *p4[2]; p4[0] = new double[2]; p4[1] = new double[4];
    6)二維數(shù)組內(nèi)存中同一維數(shù)組仍為連續(xù)的區(qū)域,所以可以將二維數(shù)組和一維相互轉(zhuǎn)化。
    7)一維數(shù)組名即為第一個(gè)元素的地址,所以可以同指針隱式轉(zhuǎn)化,但二維數(shù)組名不是第一個(gè)元素地址,所以不能轉(zhuǎn)化。
    8) 當(dāng)函數(shù)傳入數(shù)組,實(shí)際傳首元素的指針,所以可以在函數(shù)內(nèi)修改數(shù)組元素。

    三 完!

    感謝,Thanks!

                                                 三 指針與字符串

    開始之前必須明確strlen的含義,原型為size_t strlen( char *str ); strlen返回字符串的長度,即null("0)之前的字符的數(shù)量。

    一 char* 與 char []

    實(shí)例加注釋:

    Code
    void TestCharPointerAndArray()
    {
       
    char *c1 = "abc"; //abc"0常量區(qū),c1在棧上, 常量區(qū)程序結(jié)束后自動釋放。
       
    //c1[1] = 'g'; // 常量不能修改
        int i = strlen(c1); // 3

       
    char c2[] = "abc"; // c2,abc"0都在棧上
        c2[1] = 'g'; // 可以修改
        int j = strlen(c2); // 3
        int jj = sizeof(c2); // 4

       
    char *c3 = ( char* )malloc(4* sizeof(char)); // c3 棧上
        memcpy(c3,"abc",4); // abc"0 在堆上, 4 = 3(strlen("abc")) + 1('"0');
        c3[1] = 'g'; // 可以修改
        int x = strlen(c3); // 3
        free(c3); //如果這里不free,會內(nèi)存泄漏
        c3 = "abc"; // abc"0 在常量區(qū),c3指向了常量區(qū)
       
    //c3[1] = 'g'; // 常量不能修改
        int y = strlen(c3); // 3
    }

    字符串都以"0結(jié)尾,所以例如:char *c1 = "abc";char c2[] = "abc";,使用strlen得到長度都為3,但是實(shí)際的存儲空間為strlen+1即3+1。

    二 C中字符串操作函數(shù)

    C++的程序員對C中的字符串指針操作的函數(shù)卻并不是相當(dāng)?shù)氖煜ぁ6鳦中的這些字符串的指針操作函數(shù)有的時(shí)候也是必須要面對的,比如我們的庫要提供 C函數(shù)接口,保持向后兼容和跨平臺,還有我們經(jīng)常使用一些第三方的庫中都或多或少的使用到了這些C中的指針操作函數(shù),所以下面列出C的指針操作函數(shù),幫助 大家熟悉之。

    1) memcpy/memset/memcmp

        memcpy

    原型:extern void *memcpy( void *to, const void *from, size_t count );
    包含:#include <string.h> 或<string>或<cstring>
    功能:由src所指內(nèi)存區(qū)域復(fù)制count個(gè)字節(jié)到dest所指內(nèi)存區(qū)域。
    說明:src和dest所指內(nèi)存區(qū)域不能重疊,函數(shù)返回指向dest的指針。

        memset

    原型:extern void* memset( void* buffer, int ch, size_t count );
    包含:#include <string.h> 或<string>或<cstring>
    功能:把buffer所指內(nèi)存區(qū)域的前count個(gè)字節(jié)設(shè)置成字符c。
    說明:返回指向buffer的指針。

       memcmp

    原型:extern int memcmp(const void *buffer1, const void *buffer2, size_t count );
    包含:#include <string.h> 或<string>或<cstring>
    功能:比較內(nèi)存區(qū)域buf1和buf2的前count個(gè)字節(jié)。
    說明:
            當(dāng)buf1<buf2時(shí),返回值<0
            當(dāng)buf1=buf2時(shí),返回值=0
            當(dāng)buf1>buf2時(shí),返回值>0



        memchr
    原型: extern void *memchr( const void *buffer, int ch, size_t count );
    包含:#include <string.h> 或<string>或<cstring>
    功能:查找ch在buffer中第一次出現(xiàn)的位置。
    說明:如果發(fā)現(xiàn)返回指針,如果沒有返回NULL。

    實(shí)例:

    Code
    void TestMemFunction()
    {
       
    char *s1="Hello!"; // Hello!"0
        int l = strlen(s1); // 6
        char *d1 = new char[l+1]; // d1 需要strlen(s1) + 1 空間
        memcpy(d1,s1,l+1);

        memcpy(d1,d1,l);

        memmove(d1
    + 1,d1,l-1);

       
    const int ARRAY_LENGTH = 5;
       
    char the_array[ARRAY_LENGTH];
       
    // zero out the contents of the_array
        memset( the_array, 'c', ARRAY_LENGTH );

       
    char *a1 = "source1";
       
    char arr1[8] = "source2";
       
    int r = memcmp(a1,arr1,strlen(a1) - 1); // 僅比較source,所以相等

       
    char str[17];
       
    char *ptr;
        strcpy(str,
    "This is a string");
        ptr
    = (char*)memchr(str, 'r', strlen(str));
    }

    2) strlen/strcpy/strcat/strcmp/strchr/strcoll/strstr/strtok/strtod/strtol


    strcpy

    char *strcpy(char *s1, const char *s2) 將字符串s2復(fù)制到字符串?dāng)?shù)組s1中,返回s1的值


    strcat

    char *strcat(char *s1, const char *s2)
    將字符串s2添加到字符串s1的后面。s2的第一個(gè)字符重定義s1的null終止符。返回s1的值



    strcmp

    int strcmp(const char *s1, const char *s2)
    比較字符串s1和字符串s2。函數(shù)在s1等于、小于或大于s2時(shí)分別返回0、小于0或者大于0的值

    strchr

    char *strchr(char * str,int c ); 在str中查找c第一次出現(xiàn)的位置。

    strstr char *strstr(char *str,const char *strSearch );在string1中查找string2第一次出現(xiàn)的位置。
    strtok char *strtok(char *strToken,const char *strDelimit ); 分割字符串。

    實(shí)例:

    Code
    void TestStrFunction()
    {
        
    char string[11];
        
    char *str1 = "123456789"// 123456789"0
        strcpy(string, str1);

        strcat(
    string,"A"); //123456789A"0

        
    int r = strcmp(string,"123456789B"); // 123456789A"0 < 123456789B"0
    }

    void TestStrFunction2()
    {
        
    int  ch = 'r';
        
    char string[] = "The quick # brown dog # jumps over # the lazy fox";
        
    char *pdest = NULL;
        pdest 
    = strchr( string, ch );

        pdest 
    = NULL;
        
    char * str = "dog";
        pdest 
    = strstr(string,str);

        pdest 
    = NULL;
        
    char delims[] = "#";
        pdest 
    = strtok( string, delims );
        
    while( pdest != NULL ) 
        {
            pdest 
    = strtok( NULL, delims );
        }
    }

    總結(jié):

    1)以mem開始的函數(shù)用來bytes的操作,所以需要指定長度,但是以str用來操作以"0結(jié)尾的字符串,不需要指定長度。

    2)對于unicode,相應(yīng)的字符串操作函數(shù)前綴為wcs,例如wcscpy,wcscat,wcscmp,wcschr,wcsstr,wcstok等。

    3)在vc中還提供了有安全檢測的字符串函數(shù)后綴_s,例如strcpy_s,strcat_s,strcmp_s,wcscpy_s,wcscat_s,wcscmp_s等。

    4)char*如果指向常量區(qū),不能被修改,且此char*不需要delete。例如 char* pStr = "ABC";。

    三 std::string和std::wstring使用相當(dāng)簡單哦!

    四 完!

    感謝,Thanks!

                                             四 堆棧與函數(shù)調(diào)用

    一 C++程序內(nèi)存分配

    1) 在棧上創(chuàng)建。在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲單元都在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲單元自動被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,一般使用寄存器來存取,效率很高,但是分配的內(nèi)存容量有限。
    2) 從堆上分配,亦稱動態(tài)內(nèi)存分配。程序在運(yùn)行的時(shí)候用malloc或new申請任意多少的內(nèi)存,程序員自己負(fù)責(zé)在何時(shí)用free或delete來釋放內(nèi)存。動態(tài)內(nèi)存的生存期由程序員自己決定,使用非常靈活。
    3) 從靜態(tài)存儲區(qū)域分配。內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好,這塊內(nèi)存在程序的整個(gè)運(yùn)行期間都存在。例如全局變量,static變量。
    4) 文字常量分配在文字常量區(qū),程序結(jié)束后由系統(tǒng)釋放。
    5)程序代碼區(qū)。

    經(jīng)典實(shí)例:(代碼來自網(wǎng)絡(luò)高手,沒有找到原作者)

    Code
    #include <string>

    int a=0;    //全局初始化區(qū)
    char *p1;   //全局未初始化區(qū)
     void main()
    {
       
    int b;//
        char s[]="abc";   //
        char *p2;         //
        char *p3="123456";   //123456"0在常量區(qū),p3在棧上。
        static int c=0;   //全局(靜態(tài))初始化區(qū)
        p1 = (char*)malloc(10);
        p2
    = (char*)malloc(20);   //分配得來得10和20字節(jié)的區(qū)域就在堆區(qū)。
        strcpy(p1,"123456");   //123456"0放在常量區(qū),編譯器可能會將它與p3所向"123456"0"優(yōu)化成一個(gè)地方。
    }

    二 三種內(nèi)存對象的比較
    棧對象的優(yōu)勢是在適當(dāng)?shù)臅r(shí)候自動生成,又在適當(dāng)?shù)臅r(shí)候自動銷毀,不需要程序員操心;而且棧對象的創(chuàng)建速度一般 較堆對象快,因?yàn)榉峙涠褜ο髸r(shí),會調(diào)用operator new操作,operator new會采用某種內(nèi)存空間搜索算法,而該搜索過程可能是很費(fèi)時(shí)間的,產(chǎn)生棧對象則沒有這么麻煩,它僅僅需要移動棧頂指針就可以了。但是要注意的是,通常棧 空間容量比較小,一般是1MB~2MB,所以體積比較大的對象不適合在棧中分配。特別要注意遞歸函數(shù)中最好不要使用棧對象,因?yàn)殡S著遞歸調(diào)用深度的增加, 所需的棧空間也會線性增加,當(dāng)所需棧空間不夠時(shí),便會導(dǎo)致棧溢出,這樣就會產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
    堆對象創(chuàng)建和銷毀都要由程序員負(fù)責(zé),所以,如果 處理不好,就會發(fā)生內(nèi)存問題。如果分配了堆對象,卻忘記了釋放,就會產(chǎn)生內(nèi)存泄漏;而如 果已釋放了對象,卻沒有將相應(yīng)的指針置為NULL,該指針就是所謂的“懸掛指針”,再度使用此指針時(shí),就會出現(xiàn)非法訪問,嚴(yán)重時(shí)就導(dǎo)致程序崩潰。但是高效 的使用堆對象也可以大大的提高代碼質(zhì)量。比如,我們需要?jiǎng)?chuàng)建一個(gè)大對象,且需要被多個(gè)函數(shù)所訪問,那么這個(gè)時(shí)候創(chuàng)建一個(gè)堆對象無疑是良好的選擇,因?yàn)槲覀? 通過在各個(gè)函數(shù)之間傳遞這個(gè)堆對象的指針,便可以實(shí)現(xiàn)對該對象的共享,相比整個(gè)對象的傳遞,大大的降低了對象的拷貝時(shí)間。另外,相比于棧空間,堆的容量要 大得多。實(shí)際上,當(dāng)物理內(nèi)存不夠時(shí),如果這時(shí)還需要生成新的堆對象,通常不會產(chǎn)生運(yùn)行時(shí)錯(cuò)誤,而是系統(tǒng)會使用虛擬內(nèi)存來擴(kuò)展實(shí)際的物理內(nèi)存。
    靜態(tài)存儲區(qū)。所有的靜態(tài)對象、全局對象都于靜態(tài)存儲區(qū)分配。關(guān)于全局對象,是在main()函數(shù)執(zhí)行前就分配好了的。其實(shí),在main()函數(shù)中的顯示代 碼執(zhí)行之前,會調(diào)用一個(gè)由編譯器生成的_main()函數(shù),而_main()函數(shù)會進(jìn)行所有全局對象的的構(gòu)造及初始化工作。而在main()函數(shù)結(jié)束之 前,會調(diào)用由編譯器生成的exit函數(shù),來釋放所有的全局對象。比如下面的代碼:

    void main(void)
    {
    … …// 顯式代碼
    }

    實(shí)際上,被轉(zhuǎn)化成這樣:

    void main(void)
    {
    _main(); //隱式代碼,由編譯器產(chǎn)生,用以構(gòu)造所有全局對象
    … … // 顯式代碼
    … …
    exit() ; // 隱式代碼,由編譯器產(chǎn)生,用以釋放所有全局對象
    }

      除了全局靜態(tài)對象,還有局部靜態(tài)對象通和class的靜態(tài)成員,局部靜態(tài)對象是在函數(shù)中定義的,就像棧對象一樣,只不過,其前面多了個(gè) static關(guān)鍵字。局部靜態(tài)對象的生命期是從其所在函數(shù)第一次被調(diào)用,更確切地說,是當(dāng)?shù)谝淮螆?zhí)行到該靜態(tài)對象的聲明代碼時(shí),產(chǎn)生該靜態(tài)局部對象,直到 整個(gè)程序結(jié)束時(shí),才銷毀該對象。class的靜態(tài)成員的生命周期是該class的第一次調(diào)用到程序的結(jié)束。

    三 函數(shù)調(diào)用與堆棧

    1)編譯器一般使用棧來存放函數(shù)的參數(shù),局部變量等來實(shí)現(xiàn)函數(shù)調(diào)用。有時(shí)候函數(shù)有嵌套調(diào)用,這個(gè)時(shí)候棧中會有多個(gè)函數(shù)的信息,每個(gè)函數(shù)占用一個(gè)連續(xù)的區(qū)域。一個(gè)函數(shù)占用的區(qū)域被稱作幀(frame)。同時(shí)棧是線程獨(dú)立的,每個(gè)線程都有自己的棧。例如下面簡單的函數(shù)調(diào)用:

    另外函數(shù)堆棧的清理方式?jīng)Q定了當(dāng)函數(shù)調(diào)用結(jié)束時(shí)由調(diào)用函數(shù)或被調(diào)用函數(shù)來清理函數(shù)幀,在VC中對函數(shù)棧的清理方式由兩種:


    參數(shù)傳遞順序 誰負(fù)責(zé)清理參數(shù)占用的堆棧
    __stdcall 從右到左 被調(diào)函數(shù)
    __cdecl 從右到左 調(diào)用者

    2) 有了上面的知識為鋪墊,我們下面細(xì)看一個(gè)函數(shù)的調(diào)用時(shí)堆棧的變化:

    代碼如下:

    Code
    int Add(int x, int y)
    {
        
    return x + y;
    }

    void main()
    {
        
    int *pi = new int(10);
        
    int *pj = new int(20);
        
    int result = 0;
        result 
    = Add(*pi,*pj);
        delete pi;
        delete pj;
    }

    對上面的代碼,我們分為四步,當(dāng)然我們只畫出了我們的代碼對堆棧的影響,其他的我們假設(shè)它們不存在,哈哈!

    第一,int *pi = new int(10);   int *pj = new int(20);   int result = 0; 堆棧變化如下:

    第二,Add(*pi,*pj);堆棧如下:

    第三,將Add的結(jié)果給result,堆棧如下:

    第四,delete pi;    delete pj; 堆棧如下:

    第五,當(dāng)main()退出后,堆棧如下,等同于main執(zhí)行前,哈哈!


    四 完!

    感謝,Thanks!

                                          五 sizeof與內(nèi)存布局

    有了前面幾節(jié)的鋪墊,本節(jié)開始摸索C++的對象的內(nèi)存布局,平臺為windows32位+VS2008。

    一 內(nèi)置類型的size

    內(nèi)置類型,直接上代碼,幫助大家加深記憶:

    Code
    void TestBasicSizeOf()
    {
        cout
    << __FUNCTION__ << endl;

        cout
    << "  sizeof(char)= " << sizeof ( char ) << endl;
        cout
    << "  sizeof(int)= " << sizeof ( int ) << endl;
        cout
    << "  sizeof(float)= " << sizeof ( float ) << endl;
        cout
    << "  sizeof(double)= " << sizeof ( double ) << endl;

        cout
    << "  sizeof('$')=" << sizeof ( '$' ) << endl;
        cout
    << "  sizeof(1)= " << sizeof ( 1 ) << endl;
        cout
    << "  sizeof(1.5f)= " << sizeof ( 1.5f ) << endl;
        cout
    << "  sizeof(1.5)= " << sizeof ( 1.5 ) << endl;

        cout
    << "  sizeof(Good!)= " << sizeof ( "Good!" ) << endl ;

       
    char  str[] = "CharArray!";
       
    int  a[10]; 
       
    double  xy[10];
        cout
    << "  char str[] = ""CharArray!""," << " sizeof(str)= " << sizeof (str) << endl;
        cout
    << "  int a[10]," << " sizeof(a)= " << sizeof (a) << endl;
        cout
    << "  double xy[10]," << " sizeof(xy)= " <<   sizeof (xy) << endl;

        cout
    << "  sizeof(void*)= " << sizeof(void*) << endl;
    }


    運(yùn)行結(jié)果如下:


    二 struct/class的大小

    在C++中我們知道struct和class的唯一區(qū)別就是默認(rèn)的訪問級別不同,struct默認(rèn)為public,而class的默認(rèn)為 private。所以考慮對象的大小,我們均以struct為例。對于struct的大小對于初學(xué)者來說還確實(shí)是個(gè)難回答的問題,我們就通過下面的一個(gè) struct定義加逐步的變化來引出相關(guān)的知識。

    代碼如下:

    Code
    struct st1
    {
        
    short number;
        
    float math_grade;
        
    float Chinese_grade;
        
    float sum_grade;
        
    char  level;
    }; //20

    struct st2
    {
        
    char  level;
        
    short number;
        
    float math_grade;
        
    float Chinese_grade;
        
    float sum_grade;
    };//16

    #pragma pack(1)
    struct st3
    {
        
    char  level;
        
    short number;
        
    float math_grade;
        
    float Chinese_grade;
        
    float sum_grade;
    }; //15
    #pragma pack() 

    void TestStructSizeOf()
    {
        cout 
    << __FUNCTION__ << endl;

        cout 
    << "  sizeof(st1)= " << sizeof (st1) << endl;
        cout 
    << "  offsetof(st1,number) " << offsetof(st1,number) << endl;
        cout 
    << "  offsetof(st1,math_grade) " << offsetof(st1,math_grade) << endl;
        cout 
    << "  offsetof(st1,Chinese_grade) " << offsetof(st1,Chinese_grade) << endl;
        cout 
    << "  offsetof(st1,sum_grade) " << offsetof(st1,sum_grade) << endl;
        cout 
    << "  offsetof(st1,level) " << offsetof(st1,level) << endl;

        cout 
    << "  sizeof(st2)= " << sizeof (st2) << endl;
        cout 
    << "  offsetof(st2,level) " << offsetof(st2,level) << endl;
        cout 
    << "  offsetof(st2,number) " << offsetof(st2,number) << endl;
        cout 
    << "  offsetof(st2,math_grade) " << offsetof(st2,math_grade) << endl;
        cout 
    << "  offsetof(st2,Chinese_grade) " << offsetof(st2,Chinese_grade) << endl;
        cout 
    << "  offsetof(st2,sum_grade) " << offsetof(st2,sum_grade) << endl;


        cout 
    << "  sizeof(st3)= " << sizeof (st3) << endl;
        cout 
    << "  offsetof(st3,level) " << offsetof(st3,level) << endl;
        cout 
    << "  offsetof(st3,number) " << offsetof(st3,number) << endl;
        cout 
    << "  offsetof(st3,math_grade) " << offsetof(st3,math_grade) << endl;
        cout 
    << "  offsetof(st3,Chinese_grade) " << offsetof(st3,Chinese_grade) << endl;
        cout 
    << "  offsetof(st3,sum_grade) " << offsetof(st3,sum_grade) << endl;
    }

    運(yùn)行結(jié)果如下;

    基于上面的對struct的測試,我們是不是有些驚呆哦,對于C++的初學(xué)者更是情不自禁的說:“我靠!原來順序不同所占空間都不同啊,還有那個(gè) pack是啥東東啊?”,其實(shí)這里蘊(yùn)含了一個(gè)內(nèi)存對齊的問題,在計(jì)算機(jī)的底層進(jìn)行內(nèi)存的讀寫的時(shí)候,如果內(nèi)存對齊的話可以提高讀寫效率,下面是VC的默認(rèn) 規(guī)則:

    1) 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
    2) 結(jié)構(gòu)體每個(gè)成員相對于結(jié)構(gòu)體首地址的偏移量(offset)都是成員大小的整數(shù)倍, 如有需要編譯器會在成員之間加上填充字節(jié)(internal adding);
    3) 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個(gè)成員之后加上填充字節(jié)(trailing padding)。

    當(dāng)然VC提供了工程選項(xiàng)/Zp[1|2|4|8|16]可以修改對齊方式,當(dāng)然我們也可以在代碼中對部分類型實(shí)行特殊的內(nèi)存對齊方式,修改方式為#pragma pack( n ),n為字節(jié)對齊
    數(shù),其取值為1、2、4、8、16,默認(rèn)是8,取消修改用#pragma pack(),如果結(jié)構(gòu)體某成員的sizeof大于你設(shè)置的,則按你的設(shè)置來對齊

    三 struct的嵌套

    1)實(shí)例:

    Code
    struct A
    {
        
    int i;
        
    char c;
        
    double d;
        
    short s;
    }; 
    // 24

    struct B
    {
        
    char cc;
        A a;
        
    int ii;
    }; 
    // 40

    布局:(使用VS的未發(fā)布的編譯選項(xiàng)/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

    2)實(shí)例:

    Code
    #pragma pack(4)
    struct A2
    {
        
    int i;
        
    char c;
        
    double d;
        
    short s;
    }; 
    // 20
    #pragma pack()

    struct B2
    {
        
    char cc;
        A2 a;
        
    int ii;
    }; 
    // 28

    布局:(使用VS的未發(fā)布的編譯選項(xiàng)/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

    總結(jié):

      由于結(jié)構(gòu)體的成員可以是復(fù)合類型,比如另外一個(gè)結(jié)構(gòu)體,所以在尋找最寬基本類型成員時(shí),應(yīng)當(dāng)包括復(fù)合類型成員的子成員,而不是把復(fù)合成員看成是一個(gè)整體。但在確定復(fù)合類型成員的偏移位置時(shí)則是將復(fù)合類型作為整體看待。

    四 空struct/class和const,static成員

    實(shí)例:

    Code
    struct empty{}; // 1
    struct constAndStatic
    {
        
    const int i;
        
    static char c;
        
    const double d;
        
    static void TestStatic(){}
        
    void TestNoStatic(){}
    }; 
    // 16

    布局:(使用VS的未發(fā)布的編譯選項(xiàng)/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

    上面的實(shí)例中empty的大小為1,而constAndStatic的大小為16。

    總結(jié):

    因?yàn)閟tatic成員和函數(shù)其實(shí)是類層次的,不在對象中分配空間,而成員函數(shù)其實(shí)是被編譯為全局函數(shù)了,所以也不在對象中。

    五 本節(jié)完,下次探討虛函數(shù)對內(nèi)存布局的影響!

    感謝,Thanks!

                                            六 單繼承與虛函數(shù)表

    一 單繼承

    1) 代碼:

    Code
    #include <iostream>
    using namespace std;

    class A
    {
    public:
        
    void f1(){cout << "A::f1" << endl;}
        
    void f2(){cout << "A::f2" << endl;}
        
    virtual void v1(){cout << "A::v1" << endl;}
        
    virtual void v2(){cout << "A::v2" << endl;}
        
    int x;
    };

    class B : public A
    {
    public:
        
    void f2(){cout << "B::f2" << endl;} // 覆蓋
        void v2(){cout << "B::v2" << endl;} // 重寫

        
    void f3(){cout << "B::f3" << endl;} 
        
    virtual void v3(){cout << "B::v3" << endl;}
        
    int y;
    };

    class C : public B
    {
    public:
        
    void f3(){cout << "C::f3" << endl;} // 覆蓋
        void v1(){cout << "C::v1" << endl;} // 重寫
        void v3(){cout << "C::v3" << endl;} // 重寫
        int z;
    };

    2)類圖:

    3)VS2008的編譯選項(xiàng)查看布局:

    4)可視化表示:

    5)代碼驗(yàn)證:

    Code
    typedef void (*Fun)();

    void PrintVTable(A *pA)
    {
        
    int *pVT = (int*)*(int*)(pA);
        Fun
    * pF = (Fun*)(pVT + 0);
        
    int iLength = 0;
        
    while(*pF != NULL)
        {
            (
    *pF)();
            
    ++iLength;
            pF 
    = (Fun*)(pVT + iLength);
        }
    }
    void PrintMembers(A *pA)
    {
        
    int *= (int*)(pA);
        
    int i = 1;
        
    while(i <= 3)
        {
            cout 
    << *(p+i) << endl;
            i
    ++;
        }
    }
    void TestVT()
    {
        A 
    *pA = new C();
        C 
    *pC = dynamic_cast<C*>(pA);
        pC
    ->= 10;
        pC
    ->= 20;
        pC
    ->= 30;
        PrintVTable(pA);
        PrintMembers(pA);
        delete pA;
    }

    6)驗(yàn)證代碼運(yùn)行結(jié)果:

    7)總結(jié):

    單繼承的對象的布局,第一個(gè)為虛函數(shù)表指針vtbl,其后為成員且先基類后子類,虛函數(shù)表里包含了所有的虛函數(shù)的地址,以NULL結(jié)束。虛函數(shù)如果子類有重寫,就由子類的重新的代替。

    二 單繼承運(yùn)行時(shí)類型轉(zhuǎn)化

    1)代碼驗(yàn)證:

    Code
    void TestDynamicCast()
    {
        A 
    *pA = new C();
        cout 
    << "A:" << pA << endl;
        B 
    *pB = dynamic_cast<B*>(pA);
        cout 
    << "B:" << pB << endl;
        C 
    *pC = dynamic_cast<C*>(pA);
        cout 
    << "C:" << pC << endl;
    }

    2)驗(yàn)證代碼運(yùn)行結(jié)果:

    3)總結(jié):

    我們上面看了單繼承的內(nèi)存布局,而這樣的內(nèi)存布局也就決定了當(dāng)dynamic_cast的時(shí)候,都還是同一地址,不需要做指針的移動。只是類型的改變即所能訪問的范圍的改變。

    三 完!

    感謝,Thanks!

                                         七 多繼承與虛函數(shù)表

    一 多重繼承

    1) 代碼:

    Code
    #include <iostream>
    using namespace std;

    class B1
    {
    public:
        
    int x;
        
    virtual void v1(){ cout << "B1::v1" << endl; }
        
    void f1(){cout << "B1::f1" << endl; }
    };

    class B2
    {
    public:
        
    int y;
        
    virtual void v2(){ cout << "B2::v2" << endl; }
        
    void f2(){ cout << "B2::f2" << endl; }
    };

    class B3
    {
    public:
        
    int z;
        
    virtual void v3(){ cout << "B3::v3" << endl; }
        
    void f3(){ cout << "B3::f3" << endl; }
    };

    class D : public B1, public B2, public B3
    {
    public:
        
    int a;
        
    void v3(){ cout << "D::v3" << endl; }
        
    virtual void vD(){ cout << "D::vD" << endl; }
    };

    2)類圖:

    3)VS2008的編譯選項(xiàng)查看布局:

    4)可視化表示:

    5)代碼驗(yàn)證:

    Code
    typedef void (*Fun)();

    void PrintMember(int *pI)
    {
        cout 
    << *pI << endl;
    }
    void PrintVT(int *pVT)
    {
        
    while(*pVT != NULL)
        {
            (
    *(Fun*)(pVT))();
            pVT
    ++;
        }
    }

    void PrintVTAndMember(B1 *pD)
    {
        
    int *pRoot = (int*)pD;
        
    int *pVTB1 = (int*)*(pRoot + 0);PrintVT(pVTB1);
        
    int *pMB1 = pRoot +1; PrintMember(pMB1);
        
    int *pVTB2 = (int*)*(pRoot + 2);PrintVT(pVTB2);
        
    int *pMB2 = pRoot +3; PrintMember(pMB2);
        
    int *pVTB3 = (int*)*(pRoot + 4);PrintVT(pVTB3);
        
    int *pMB3 = pRoot +5; PrintMember(pMB3);
    }

    void TestVT()
    {
        B1 
    *pB1 = new D();
        D 
    *pD = dynamic_cast<D*>(pB1);
        pD
    ->= 10;
        pD
    ->= 20;
        pD
    ->= 30;
        pD
    ->= 40;
        PrintVTAndMember(pD);
        delete pD;
    }

    6) 驗(yàn)證代碼運(yùn)行結(jié)果:

    7)總結(jié):

    與單繼承相同的是所有的虛函數(shù)都包含在虛函數(shù)表中,所不同的多重繼承有多個(gè)虛函數(shù)表,當(dāng)子類對父類的虛函數(shù)有重寫時(shí),子類的函數(shù)覆蓋父類的函數(shù)在對應(yīng)的虛函數(shù)位置,當(dāng)子類有新的虛函數(shù)時(shí),這些虛函數(shù)被加在第一個(gè)虛函數(shù)表的后面。

    二 多重繼承運(yùn)行時(shí)類型轉(zhuǎn)化

    1)代碼驗(yàn)證:

    Code
    void TestDynamicCast()
    {
        B1 
    *pB1 = new D();
        cout 
    << "B1:" << pB1 << endl;
        D 
    *pD = dynamic_cast<D*>(pB1);
        cout 
    << "D:"<< pD << endl;
        B2 
    *pB2 = dynamic_cast<B2*>(pB1);
        cout 
    << "B2:" << pB2 << endl;
        B3 
    *pB3 = dynamic_cast<B3*>(pB1);
        cout 
    << "B3:" << pB3 << endl;
        delete pD;
    }

    2)驗(yàn)證代碼的運(yùn)行結(jié)果:

    3)總結(jié):

    從多重繼承的內(nèi)存布局,我們可以看到子類新加入的虛函數(shù)被加到了第一個(gè)基類的虛函數(shù)表,所以當(dāng)dynamic_cast的時(shí)候,子類和第一個(gè)基類的地址相同,不需要移動指針,但是當(dāng)dynamic_cast到其他的父類的時(shí)候,需要做相應(yīng)的指針的移動。

    三 完!

    感謝,Thanks!

                                             八 虛繼承與虛函數(shù)表

    一 虛繼承

    1) 代碼:

    Code
    #include <iostream>
    using namespace std;

    class B
    {
    public:
        
    int i;
        
    virtual void vB(){ cout << "B::vB" << endl; }
        
    void fB(){ cout << "B::fB" << endl;}
    };

    class D1 : virtual public B
    {
    public:
        
    int x;
        
    virtual void vD1(){ cout << "D1::vD1" << endl; }
        
    void fD1(){ cout << "D1::fD1" << endl;}
    };

    class D2 : virtual public B
    {
    public:
        
    int y;
        
    void vB(){ cout << "D2::vB" << endl;}
        
    virtual void vD2(){ cout << "D2::vD2" << endl;}
        
    void fD2(){ cout << "D2::fD2" << endl;}
    };

    class GD :  public D1, public D2
    {
    public:
        
    int a;
        
    void vB(){ cout << "GD::vB" << endl;}
        
    void vD1(){cout << "GD::vD1" << endl;}
        
    virtual void vGD(){cout << "GD::vGD" << endl;}
        
    void fGD(){cout << "GD::fGD" << endl;}
    };

    2)類圖:

    3)VS2008的編譯選項(xiàng)查看布局:

    4)可視化表示:

    5)代碼驗(yàn)證:(此時(shí)的虛函數(shù)表不是以NULL結(jié)尾,為什么?

    Code
    typedef void (*Fun)();

    void PrintMember(int *pI)
    {
        cout 
    << *pI << endl << endl;
    }
    void PrintVT(int *pVT)
    {
        
    while(*pVT != NULL)
        {
            (
    *(Fun*)(pVT))();
            pVT
    ++;
        }
    }

    void PrintMemberAndVT(GD *pGD)
    {
        
    int *pRoot = (int*)pGD;

        
    int *pD1VT = (int*)*(pRoot + 0); 
        (
    *(Fun*)(pD1VT))(); (*(Fun*)(pD1VT +1))();
        
    int *pVB = (int*)*(pRoot +1);  cout << "vbtable's adress:" << *pVB << endl;
        
    int *pX = (pRoot + 2); PrintMember(pX);

        
    int *pD2VT = (int*)*(pRoot + 3); 
        (
    *(Fun*)(pD2VT))();
        
    int *pVB2 = (int*)*(pRoot +4); cout << "vbtable's adress:" << *pVB2 << endl;
        
    int *pY = (pRoot + 5); PrintMember(pY);

        
    int *pA = (pRoot + 6); PrintMember(pA);

        
    int *pBVT = (int*)*(pRoot + 7); 
        (
    *(Fun*)(pBVT))();
        
    int *pI = (pRoot + 8); PrintMember(pI);
    }

    void TestVT()
    {
        B 
    *pB = new GD();
        GD 
    *pGD = dynamic_cast<GD*>(pB);
        pGD
    ->= 10;
        pGD
    ->= 20;
        pGD
    ->= 30;
        pGD
    ->= 40;
        PrintMemberAndVT(pGD);
        delete pGD;
    }

    6)驗(yàn)證代碼結(jié)果:

    7)總結(jié):

    虛繼承,使公共的基類在子類中只有一份,我們看到虛繼承在多重繼承的基礎(chǔ)上多了vbtable來存儲到公共基類的偏移。

    二 虛繼承運(yùn)行時(shí)類型轉(zhuǎn)化

    1)代碼驗(yàn)證:

    Code
    void TestDynamicCast()
    {
        B 
    *pB = new GD();
        GD 
    *pGD = dynamic_cast<GD*>(pB);
        cout 
    << "GD:" << pGD << endl;
        D1 
    *pD1 = dynamic_cast<D1*>(pB);
        cout 
    << "D1:" << pD1 << endl;
        D2 
    *pD2 = dynamic_cast<D2*>(pB);
        cout 
    << "D2:" << pD2 << endl;
        cout 
    << "B:" << pB << endl;
    }

    2)驗(yàn)證代碼結(jié)果:

    3)總結(jié):

    還是從內(nèi)存布局來看dynamic_cast時(shí)地址的變化,第一個(gè)基類的地址與子類相同,其他的基類和虛基類需要做偏移。

    三 完!

    感謝,Thanks!

                                          九 類型轉(zhuǎn)換

    一 typeid與dynamic_cast

    1)RTTI, Runtime Type Identification (RTTI) or Run-time type information (RTTI),表示在運(yùn)行時(shí)動態(tài)決定變量的類型,來調(diào)用正確的虛函數(shù)。 RTTI在VS2008中默認(rèn)為關(guān)閉,可以通過修改編譯選項(xiàng)Enable Run-Time Type Info 為 Yes,來啟用RTTI,只有當(dāng)啟動RTTI時(shí),用來RTTI功能的typeid和dynamic_cast才能正常工作。

    2)type_info,用來描述類型信息。type_info存儲了它所描述的類型的名字。RTTI就是使用type_info來實(shí)現(xiàn)的。type_info的定義如下:

    Code
    class type_info {
    public:
      
    virtual ~type_info();
      
    bool operator== (const type_info& rhs) const;
      
    bool operator!= (const type_info& rhs) const;
      
    bool before (const type_info& rhs) const;
      
    const char* name() const;
    private:
      type_info (
    const type_info& rhs);
      type_info
    & operator= (const type_info& rhs);
    };

    問題:RTTI怎么實(shí)現(xiàn)那?對象,type_info,虛函數(shù)怎么關(guān)聯(lián)那?《深入C++對象模型》中說在虛函數(shù)表的開始存儲了類型信息,但是實(shí)際的VS2008中好像并沒有此信息,請高人指點(diǎn)哦!

    3)typeid,在運(yùn)行時(shí)獲得對象的類型,typeid()返回的是const type_info&,而 type_info包含了對象真實(shí)類型的名字。typeid能被用來獲取一個(gè)引用對象或指針指向的對象的運(yùn)行時(shí)的真實(shí)類型。當(dāng)然如果對象為null或編譯 時(shí)沒有使用/GR的話,typeid的會拋出異常bad_typeid exception或__non_rtti_object。實(shí)例代碼:

    Code
    class Base 
    {
    public:
        
    virtual void f(){ }
    };
     
    class Derived : public Base 

    public:
        
    void f2() {}
    }; 

    void main ()
    {
        Base 
    *pB = new Derived();
        
    const type_info& t = typeid(*pB);cout <<t.name() << endl;
        delete pB;

        Derived d;
        Base
    & b = d;
        cout 
    << typeid(b).name() << endl;
    }

    運(yùn)行結(jié)果:

    4)dynamic_cast,用來運(yùn)行時(shí)的類型轉(zhuǎn)化,需要/GR來正確運(yùn)行。
    適用:
    第一,用于所有的父子和兄弟間指針和引用的轉(zhuǎn)化,有類型安全檢查;
     
    第二,對指針類型,如果不成功,返回NULL,對引用類型,如果不成功,則拋出異常;
     
    第三,類型必須要有虛函數(shù),且打開/GR編譯選項(xiàng),否則不能使用dynamic_cast。
    實(shí)例代碼:

    Code
    class AA 
    {
    public:
        
    virtual void do_sth(){ std::cout<<"AA"n"; }
    };
    class BB 
    {
    public:
        
    virtual void do_sth(){ std::cout<<"BB"n"; }
    };
    class CC : public AA, public BB
    {
    public:
        
    virtual void do_sth(){ std::cout<<"CC"n"; } 
    };

    void DynamicCastTest()
    {
        AA 
    *pA = new CC;
        BB 
    *pB = dynamic_cast<BB*>(pA);
        
    if(pB != NULL)
            cout 
    << "cast successful!" << endl;
        CC 
    *pC = dynamic_cast<CC*>(pA);
        
    if(pC != NULL)
         cout 
    << "cast successful!" << endl;
    }

    二 其他cast

    1)隱式轉(zhuǎn)化,不需要任何操作符,轉(zhuǎn)化被自動執(zhí)行,當(dāng)一個(gè)值被賦值到它所兼容的類型時(shí)。
    適用:
    第一,內(nèi)置基本類型的兼容轉(zhuǎn)化;
    第二, 子類指針,引用向父類的轉(zhuǎn)化;

    實(shí)例:

    Code
    class A
    {
    public:
        
    virtual ~A(){}
    };
    class B : public A
    {
    };

    void ImplicitCast()
    {
        
    short a = 2000;
        
    int b;
        b 
    = a;

        
    double d = 10.05;
        
    int i;
        i 
    = d;

        
    int j = 75;
        
    char c;
        c 
    = j;

        A
    * pA = new B();
    }

    2)強(qiáng)制類型轉(zhuǎn)化,即我們常說的C風(fēng)格的類型轉(zhuǎn)化,基本上可以用于所有的轉(zhuǎn)化,但是沒有意義的轉(zhuǎn)化除外,但是父子類,兄弟間的轉(zhuǎn)化沒有類型檢查可能導(dǎo)致運(yùn)行是錯(cuò)誤。
    適用:
    第一,基本類型轉(zhuǎn)化;
    第二,void*到其他指針的轉(zhuǎn)化;
    第三,去除const;
    第五,函數(shù)指針的轉(zhuǎn)化;
    第六,父子類轉(zhuǎn)化,但是多重繼承和兄弟轉(zhuǎn)化,可能有運(yùn)行時(shí)錯(cuò)誤,沒有類型檢查;
    第七,任何兩個(gè)類,但是沒有實(shí)際意義,運(yùn)行可能出錯(cuò);
    第八,不能用于沒有意義的轉(zhuǎn)化,嚴(yán)厲禁止,例如,你不能用static_cast象用C風(fēng)格的類型轉(zhuǎn)換一樣把struct轉(zhuǎn)換成int類型,或者把double類型轉(zhuǎn)換成指針類型;
    第九,在C++一般更推薦新加的static_cast,const_cast,dynamic_cast和reinterpret_cast轉(zhuǎn)化方式;

    實(shí)例:

    Code
    class CDummy 
    {
    public:
        CDummy(
    float x, float y)
        {
            i 
    = x;
            j 
    = y;
        }
    private:
        
    float i,j;
    };

    class CAddition 
    {
    public:
        CAddition (
    int a, int b) { x=a; y=b; }
        
    int result() { return x+y;}
    private:
        
    int x,y;
    };

    int Testing()
    {
        std::cout 
    << "Testing" << std::endl;
        
    return 10;
    }

    void ExplicitCast()
    {
        
    double r = (double)1 / 3;

        
    int *pi = new int(10);
        
    void *pV;
        pV 
    = pi;
        
    int *pj = (int*)pV; // 或 int *pj = int*(pV);

        
    const int* pa = new int(20);
        
    int *pb;
        pb 
    = (int*)pa;
        
    *pb = 30;
        std::cout 
    << *pa << std::endl;

        typedef 
    void (*Fun)();

        Fun f 
    = (Fun)Testing;
        f();

        
    // 多重繼承或?qū)⑿值荛g的轉(zhuǎn)化可能會出錯(cuò)

        
    // 雖然可以正確的編譯,但是運(yùn)行有問題,所以我們不做沒有意義的轉(zhuǎn)化
        
    //CDummy d(10,30);
        
    //CAddition * padd;
        
    //padd = (CAddition*) &d;
        
    //std::cout << padd->result();

        
    // 不做沒有意義的轉(zhuǎn)化
        //// error
        //struct st{int i; double d;};
        
    //st s;
        
    //int x = (int)s; //c2440

        
    //double y = 10.0;
        
    //int *p = (int*)y; // c2440
    }

    3)static_cast在功能上基本上與C風(fēng)格的類型轉(zhuǎn)換一樣強(qiáng)大,含義也一樣。
    它也有功能上限制:
    第一,不能兄弟間轉(zhuǎn)化,父子間轉(zhuǎn)化沒有類型安全檢查,有可能會導(dǎo)致運(yùn)行時(shí)錯(cuò)誤,父子兄弟的動態(tài)轉(zhuǎn)化應(yīng)該適用dynamic_cast;
    第二,不能去除const,適用專用的const_cast;
    第三,不能用于兩個(gè)沒有繼承關(guān)系的類,當(dāng)然實(shí)際上這樣的轉(zhuǎn)化也是沒有意義的;
    第四,當(dāng)然也不支持沒有意義的轉(zhuǎn)化,例如,你不能用static_cast象用C風(fēng)格的類型轉(zhuǎn)換一樣把struct轉(zhuǎn)換成int類型,或者把double類型轉(zhuǎn)換成指針類型;

    4)const_cast,用來修改類型的const或volatile屬性。

    適用:
    第一,常量指針被轉(zhuǎn)化成非常量指針,并且仍然指向原來的對象;
    第二,常量引用被轉(zhuǎn)換成非常量引用,并且仍然指向原來的對象;
    第三,常量對象被轉(zhuǎn)換成非常量對象;

    實(shí)例:

    Code
    void ConstCastTest()
    {
        
    const int* pa = new int(20);
        
    int *pb;
        pb 
    = const_cast<int*>(pa);
        
    *pb = 30;
        std::cout 
    << *pa << std::endl;
    }

    5)reinterpret_cast,此轉(zhuǎn)型操作符的結(jié)果取決于編譯器,用于修改操作數(shù)類型,非類型安全的轉(zhuǎn)換符。
    適用:
    一般不推薦使用,但是一般用來對函數(shù)指針的轉(zhuǎn)化。
    實(shí)例:

    Code
    // 不可以移植,不推薦使用
    int ReinterpretTest()
    {
        
    struct dat { short a; short b;};
        
    long value = 0x00100020;
        dat 
    * pd = reinterpret_cast<dat *> (&value);
        std::cout 
    << pd-><< std::endl; // 0x0020
        std::cout << pd-><< std::endl; // 0x0010
        return 0;
    }

    typedef 
    void (*Fun)();

    int Testing()
    {
        std::cout 
    << "Testing" << std::endl;
        
    return 10;
    }

    void ReinterpretTest2()
    {
        
    //Fun f = (Fun)Testing;
        
    //f();
        Fun f = reinterpret_cast<Fun>(Testing);
        f();
    }

    三 總結(jié)

    在C++一般更推薦新加的static_cast,const_cast,dynamic_cast和reinterpret_cast轉(zhuǎn)化方式;

    感謝,Thanks!

    posted on 2010-08-26 10:52 何克勤 閱讀(811) 評論(0)  編輯  收藏 所屬分類: C/C++
    主站蜘蛛池模板: 国产亚洲精品看片在线观看 | 97se亚洲综合在线| 亚洲欧美国产国产综合一区 | 国产亚洲av片在线观看18女人| 亚洲人成777在线播放| 亚洲欧洲av综合色无码| 国产乱码免费卡1卡二卡3卡| 亚洲日本一区二区一本一道| 久久精品国产亚洲AV忘忧草18| 一级日本高清视频免费观看| 99久久免费国产精品特黄| 亚洲av无码久久忘忧草| 亚洲成在人线aⅴ免费毛片| 亚洲熟女精品中文字幕| 久久免费福利视频| 亚洲第一网站男人都懂| 亚洲色丰满少妇高潮18p| 在线免费观看一区二区三区| 久久亚洲精品成人无码网站| 18禁美女裸体免费网站| 亚洲国产精品一区二区成人片国内| 色偷偷尼玛图亚洲综合| 免费电视剧在线观看| 91亚洲va在线天线va天堂va国产| 3344永久在线观看视频免费首页| 亚洲性色高清完整版在线观看| 光棍天堂免费手机观看在线观看| 亚洲va中文字幕无码| 国产在线国偷精品免费看| 成年人在线免费看视频| 亚洲成人福利在线观看| 成人人观看的免费毛片| 一级毛片**免费看试看20分钟| 亚洲综合国产精品| 无码免费一区二区三区免费播放| 精品国产_亚洲人成在线高清| 国产免费伦精品一区二区三区| 亚洲精品无码成人片在线观看 | 91福利免费网站在线观看| 亚洲黄色片在线观看| 日本不卡视频免费|