<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++的軟件開發中非常常見,如果能恰當的使用它們能夠極大的提 高整個軟件的效率,但是很多的C++學習者對它們的各種使用情況并不是都了解,這就導致了實際的軟件開發中經常會內存泄漏,異常拋出,程序崩潰等問題。對 于C和C++的初學者,那更是被它們搞的迷迷糊糊。本篇作為[深入C++]系列的第一節,我們就帶領大家把指針和引用這個基本功練好。

    二 指針

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

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

    在上面的幾條語句中,&用來定義引用變量或對變量取其地址,*用來定義指針或得到指針所指向的變量,其中p為定義的指針變量,它指向int變量i,而pP為二級指針變量,它指向指針變量p。相應的示意圖如下:

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

    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()
    {
        
    //
     -----------------------------------------------
        
    //
     請問運行Test1函數會有什么樣的結果?
        
    //

        
    // 答:程序崩潰。同時有內存泄漏。
        
    //

        
    // 因為在GetMemory1函數的調用過程中,其實是對實參指針p做了拷貝,拷貝為局部變量,
        
    //
     在函數內的操作是對局部變量的操作,局部變量與實參是兩個不同的變量,相互不影響。
        
    //

        
    // 所以,當GetMemory1調用結束時,Test1函數中的 str一直都是 NULL。
        
    //
     strcpy(str, "hello world");將使程序崩潰。
        
    //

        
    //Test1();

        
    //
     -----------------------------------------------
        
    //
     請問運行Test2函數會有什么樣的結果?
        
    //

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

        
    //
     -----------------------------------------------
        
    //
     請問運行Test3函數會有什么樣的結果?
        
    //

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

        
    // 因為GetMemory3返回的是指向“棧內存”的指針,
        
    //
     該指針的地址不是 NULL,但其原現的內容已經被清除,新內容不可知。
        
    // 

        Test3();

        
    //
     -----------------------------------------------
        
    //
     請問運行Test4函數會有什么樣的結果?
        
    //

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

        
    //
     -----------------------------------------------
        
    //
     請問運行Test5函數會有什么樣的結果?
        
    //

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

        
    //
     -----------------------------------------------
        
    //
     請問運行Test6函數會有什么樣的結果?
        
    //

        
    // 答:篡改動態內存區的內容,后果難以預料,非常危險。
        
    //

        
    // 因為free(str);之后,str成為野指針,
        
    //
     if(str != NULL)語句不起作用。
        
    //
        Test6();

    三 C++指針與引用

    引用,其實是變量的別名,與變量是同一個東東。例如 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; 此時pa,pb為pi的別名。在C++中引入了引用概念后,我們不僅可以定義引用變量,相應的函數的傳遞方式也增加了按引用傳遞,當參數以引用方式傳遞 時,函數調用時不對實參進行拷貝,傳入函數內的變量與實參是同一個變量。下面的實例演示了指針和引用在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;
    };

    // 默認按值傳遞,當傳入時對對像進行了拷貝,函數內只是對所拷貝值的修改,所以實參沒被修改。

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

    void ChangeValueByReference(Point& pt, int x, int y)
    {
        pt.SetX(x);
        pt.SetY(y);
    }
    // 通過傳遞指針,雖然實參指針傳入時也產生了拷貝,但是在函數內通過指針任然修改了指針所指的值。

    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();
    }

    運行結果如下:

    四 函數參數傳遞方式,函數中return語句和拷貝構造函數的關系

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

    Code
    #pragma once
    #include 
    <iostream>

    class CopyAndAssign
    {
    public
    :
        CopyAndAssign(
    int
     i)
        {
            x 
    =
     i;
        }
        CopyAndAssign(
    const CopyAndAssign&
     ca)
        {
            std::cout 
    << "拷貝構造!" <<
     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); // 構造

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

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

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

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

        CopyAndAssignAsParameter(c1); // 拷貝構造
        ReturnCopyAndAssign(); // 拷貝構造

        CopyAndAssignAsParameterByReference(c1); 
        ReturnCopyAndAssignByReference(); 
    }

    親自debug,效果會更好,運行結果如下:

    五 總結

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

    六 使用注意

    1) malloc/free一起使用。

    2)new/delete一起使用。

    3)對于new中有[]時,相應的必須使用delete[]來釋放。

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

    5)對指針的使用前,應該檢查是否為空,空指針可能導致程序崩潰。

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

    七 謝謝!

                                          二 指針與數組

    一 C指針操作函數

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

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

    實例如下:

    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"// 此時的name指向了常量區,所以name指針不需要程序員釋放。
         free( willy );
         willy 
    = NULL;
    }

    malloc/free 和new/delete的區別:

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

    2) new 建立的是一個對象,new會根據對象計算大小,直接返回對象的指針,當使用完畢后調用delete來釋放,但malloc分配的是一塊內存,需要用戶制定 所要分配內存的大小,而且返回的均為void的指針,使用時需要相應的強制類型轉化,使用結束后調用free來釋放內存.

    3)new/delete的使用除了分配內存和釋放,還調用了類型的構造函數和析構函數,而malloc/free只是簡單的分配和釋放內存。

    二 數組與指針

    C++的數組經常需要和指針來結合使用,下面來進行相關的強化訓練。實例如下:

    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]) // 數組傳參為傳指針,所以函數內可以修改
    {
        
    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()
    {
        
    // 數組的定義和初始化
        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);

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

        
    // 數組+指針實現二維變長數組
        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); 
    // 數組傳參為傳指針,所以函數內可以修改
        ChangeArray(arr);
        PrintArray(arr);
    }

    代碼分析總結:

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

    三 完!

    感謝,Thanks!

                                                 三 指針與字符串

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

    一 char* 與 char []

    實例加注釋:

    Code
    void TestCharPointerAndArray()
    {
       
    char *c1 = "abc"; //abc"0常量區,c1在棧上, 常量區程序結束后自動釋放。
       
    //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,會內存泄漏
        c3 = "abc"; // abc"0 在常量區,c3指向了常量區
       
    //c3[1] = 'g'; // 常量不能修改
        int y = strlen(c3); // 3
    }

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

    二 C中字符串操作函數

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

    1) memcpy/memset/memcmp

        memcpy

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

        memset

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

       memcmp

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



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

    實例:

    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復制到字符串數組s1中,返回s1的值


    strcat

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



    strcmp

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

    strchr

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

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

    實例:

    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 );
        }
    }

    總結:

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

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

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

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

    三 std::string和std::wstring使用相當簡單哦!

    四 完!

    感謝,Thanks!

                                             四 堆棧與函數調用

    一 C++程序內存分配

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

    經典實例:(代碼來自網絡高手,沒有找到原作者)

    Code
    #include <string>

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

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

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

    實際上,被轉化成這樣:

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

      除了全局靜態對象,還有局部靜態對象通和class的靜態成員,局部靜態對象是在函數中定義的,就像棧對象一樣,只不過,其前面多了個 static關鍵字。局部靜態對象的生命期是從其所在函數第一次被調用,更確切地說,是當第一次執行到該靜態對象的聲明代碼時,產生該靜態局部對象,直到 整個程序結束時,才銷毀該對象。class的靜態成員的生命周期是該class的第一次調用到程序的結束。

    三 函數調用與堆棧

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

    另外函數堆棧的清理方式決定了當函數調用結束時由調用函數或被調用函數來清理函數幀,在VC中對函數棧的清理方式由兩種:


    參數傳遞順序 誰負責清理參數占用的堆棧
    __stdcall 從右到左 被調函數
    __cdecl 從右到左 調用者

    2) 有了上面的知識為鋪墊,我們下面細看一個函數的調用時堆棧的變化:

    代碼如下:

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

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

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

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

    第三,將Add的結果給result,堆棧如下:

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

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


    四 完!

    感謝,Thanks!

                                          五 sizeof與內存布局

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

    一 內置類型的size

    內置類型,直接上代碼,幫助大家加深記憶:

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


    運行結果如下:


    二 struct/class的大小

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

    代碼如下:

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

    運行結果如下;

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

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

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

    三 struct的嵌套

    1)實例:

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

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

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

    2)實例:

    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的未發布的編譯選項/d1 reportAllClassLayout 或 /d1 reportSingleClassLayout)

    總結:

      由于結構體的成員可以是復合類型,比如另外一個結構體,所以在尋找最寬基本類型成員時,應當包括復合類型成員的子成員,而不是把復合成員看成是一個整體。但在確定復合類型成員的偏移位置時則是將復合類型作為整體看待。

    四 空struct/class和const,static成員

    實例:

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

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

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

    總結:

    因為static成員和函數其實是類層次的,不在對象中分配空間,而成員函數其實是被編譯為全局函數了,所以也不在對象中。

    五 本節完,下次探討虛函數對內存布局的影響!

    感謝,Thanks!

                                            六 單繼承與虛函數表

    一 單繼承

    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的編譯選項查看布局:

    4)可視化表示:

    5)代碼驗證:

    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)驗證代碼運行結果:

    7)總結:

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

    二 單繼承運行時類型轉化

    1)代碼驗證:

    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)驗證代碼運行結果:

    3)總結:

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

    三 完!

    感謝,Thanks!

                                         七 多繼承與虛函數表

    一 多重繼承

    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的編譯選項查看布局:

    4)可視化表示:

    5)代碼驗證:

    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) 驗證代碼運行結果:

    7)總結:

    與單繼承相同的是所有的虛函數都包含在虛函數表中,所不同的多重繼承有多個虛函數表,當子類對父類的虛函數有重寫時,子類的函數覆蓋父類的函數在對應的虛函數位置,當子類有新的虛函數時,這些虛函數被加在第一個虛函數表的后面。

    二 多重繼承運行時類型轉化

    1)代碼驗證:

    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)驗證代碼的運行結果:

    3)總結:

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

    三 完!

    感謝,Thanks!

                                             八 虛繼承與虛函數表

    一 虛繼承

    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的編譯選項查看布局:

    4)可視化表示:

    5)代碼驗證:(此時的虛函數表不是以NULL結尾,為什么?

    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)驗證代碼結果:

    7)總結:

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

    二 虛繼承運行時類型轉化

    1)代碼驗證:

    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)驗證代碼結果:

    3)總結:

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

    三 完!

    感謝,Thanks!

                                          九 類型轉換

    一 typeid與dynamic_cast

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

    2)type_info,用來描述類型信息。type_info存儲了它所描述的類型的名字。RTTI就是使用type_info來實現的。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怎么實現那?對象,type_info,虛函數怎么關聯那?《深入C++對象模型》中說在虛函數表的開始存儲了類型信息,但是實際的VS2008中好像并沒有此信息,請高人指點哦!

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

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

    運行結果:

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

    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)隱式轉化,不需要任何操作符,轉化被自動執行,當一個值被賦值到它所兼容的類型時。
    適用:
    第一,內置基本類型的兼容轉化;
    第二, 子類指針,引用向父類的轉化;

    實例:

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

    實例:

    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();

        
    // 多重繼承或將兄弟間的轉化可能會出錯

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

        
    // 不做沒有意義的轉化
        //// 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風格的類型轉換一樣強大,含義也一樣。
    它也有功能上限制:
    第一,不能兄弟間轉化,父子間轉化沒有類型安全檢查,有可能會導致運行時錯誤,父子兄弟的動態轉化應該適用dynamic_cast;
    第二,不能去除const,適用專用的const_cast;
    第三,不能用于兩個沒有繼承關系的類,當然實際上這樣的轉化也是沒有意義的;
    第四,當然也不支持沒有意義的轉化,例如,你不能用static_cast象用C風格的類型轉換一樣把struct轉換成int類型,或者把double類型轉換成指針類型;

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

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

    實例:

    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,此轉型操作符的結果取決于編譯器,用于修改操作數類型,非類型安全的轉換符。
    適用:
    一般不推薦使用,但是一般用來對函數指針的轉化。
    實例:

    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();
    }

    三 總結

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

    感謝,Thanks!

    posted on 2010-08-26 10:52 何克勤 閱讀(806) 評論(0)  編輯  收藏 所屬分類: C/C++
    主站蜘蛛池模板: 亚洲一级视频在线观看| 亚洲va在线va天堂va不卡下载| 99国产精品视频免费观看| 最近中文字幕2019高清免费| 成人免费视频网站www| 美女黄网站人色视频免费国产| 四虎在线视频免费观看视频| 免费毛片在线看片免费丝瓜视频| 91人人区免费区人人| 搡女人免费视频大全| 最近中文字幕电影大全免费版| 亚洲免费人成在线视频观看| 亚洲黄色免费网址| 免费观看国产网址你懂的| 最近的免费中文字幕视频| 亚洲国产成人精品女人久久久| 免费人成在线观看播放国产| 国产亚洲一区二区三区在线观看| 精品国产人成亚洲区| 国产精品亚洲美女久久久| 亚洲视频在线观看一区| 亚洲一区二区三区电影| 亚洲欧美日韩中文字幕一区二区三区 | 亚洲色偷偷偷综合网| 视频一区二区三区免费观看| 久久国产乱子伦精品免费强| 一个人免费观看在线视频www| 噼里啪啦免费观看高清动漫4| 很黄很色很刺激的视频免费| 亚洲成人影院在线观看| 78成人精品电影在线播放日韩精品电影一区亚洲 | 亚洲依依成人精品| 一级毛片**免费看试看20分钟| 一个人看www免费高清字幕| A毛片毛片看免费| 9久9久女女免费精品视频在线观看 | 91精品免费在线观看| 亚洲免费在线观看| 久久精品国产亚洲av麻豆蜜芽| 亚洲一本到无码av中文字幕| 西西人体免费视频|