c++筆記02---內聯 inline,動態內存 new/delete,引用,對象,類

1.    內聯 inline
    c 裏面的宏嵌入是源碼嵌入,內聯是二進制嵌入,二進制代替多次調用;
    內聯一般用於多次調用的小函數,用空間換時間;
    注意:遞歸函數無法作內聯,遞歸是自己調自己,所以無法自己嵌自己;
    通過 inline 關鍵字,可以建議編譯器對函數進行內聯,但是僅僅是建議,編譯器可以不執行;
        inline void foo(int x) {...}
    現在的編譯器會自己判斷是否需要內聯;

2.    動態內存分配 new/delete(操作符)
    c 裏面用下面幾個函數動態分配內存,位於堆裏面,c++ 同樣可以使用,需要包含頭文件 #include <cstdlib>
    malloc(不清零)/calloc(清零)/realloc/free
        int *p = (int *)malloc(1 * sizeof(int));        // c++裏不推薦使用
    
    new 和 malloc 區別:
    1)new 是運算符,malloc 是函數;
    2)new 最終還是調用 malloc 分配內存;
    3)malloc 僅僅是分配內存,new 還調用構造函數;

    c++裏主要用下面函數進行內存分配和釋放:(不用包含任何頭文件)
    new/delete :對單個變量進行內存分配/釋放,上面那段 malloc 代碼可以寫爲:
        int *p = new int;
        delete p;
        p = NULL;
    也可以如下初始化:(有待驗證)
        int *p = new int (56);        // 對p進行賦初值

    下面兩種變量初始化方式都正確:
        int i = 10;
        int i(10);

    一般情況下,我們 delete 完了,就要把指針清零置空;
        int *p = new int;
        delete p;
        *p = 2000;            // 這種情況下,p屬於野指針,無法確定結果,屬於未定義類型;

        int *p = new int;
        delete p;
        p = NULL;
        *p = 2000;            // 這種情況下,p爲零,結果確定,返回段錯誤;    

    對數組進行內存分配/釋放:new[]/delete[]:
        p = new int [10];
        delete[] p;            // 如果此處不帶[ ],那麼結果不確定,屬於未定義;多維數組也用這條語句釋放,不用多加[ ]
        p = NULL;
        
    注意:
        delete[] p;    相當於:    delete (p-4);
    記着,這裏 p 前面有 4 個字節用於存放數組大小;
    
3.    定位分配:在指定位置分配內存空間;(瞭解)
    並不是所有 new 分配出來的空間都在堆裏面;
    char buf[4] = {0x12, 0x34, 0x99, 0x23};     // buf裏面放了四個數,buf 位於棧裏面
    int *p = new (buf) int;                        // 在 buf 裏面分配一個 int 空間給 p,所以這個空間在棧裏面
    delete p;                                    // error,段錯誤,棧不需要釋放
    
4.    引用即別名;
        int a = 20;
        int &b = a;            // 引用,b就是a的別名
        b = 10;                // 此時 a 值爲10
        
    引用必須初始化,且非常引用必須用變量初始化;
    只有常引用可以用常量初始化;
    
    一旦初始化,就不能再引用其他變量;
        int &b;                // error
        int &b = 1000;        // error
        const int &b = 100;    // ok
        
    引用是在底層其實就是用指針來實現的。

    c++ 和 c 相比,就多加了兩個變量,一個是布爾,另一個是引用;

5.    引用應用場景:
    1)引用型參數:
        a.修改實參
        b.避免拷貝
            通過 const 可以防止在函數中意外地修改實參的值;
            同時還可以接收擁有常屬性的實參;如下:
                void foo(const int &a) {...}    // 增加 const,foo 的實參範圍變大了,既可以傳變量,也可以傳常量
                // 如果 foo 函數體內需要改變 a 的值,我們則不能加 const
                int main() { foo(100); }
    2)引用型返回值:
        int data = 100;                            // data必須是全局變量(原因見下面)
        int &foo() { return data; }                // 返回引用,相當於返回 data 本身;
        int main() { foo() = 200; }                // 如果foo不是引用,則無法把 200 返回給data,同時data要是全局變量纔行

        從一個函數中返回引用,往往是爲了將該函數的返回值作爲左值使用;
        但是,一定要保證函數所返回的引用的目標在該函數返回以後依然有定義,否則將導致不確定後果;
        也就是說,不要返回局部變量;
        除非局部變量是靜態的,或是在動態內存中分配的;
        可以返回全局、靜態、成員變量的引用,也可以返回引用型行參;
        int &foo() { int a;  return a; }    // 無效,a是局部變量
        int &foo(int a) { return a; }        // 無效,a是執行型行參
        int &foo(int &a) { return a; }        // 有效,a是引用型行參

        函數的行參可以是引用,而通過引用傳遞參數,稱之爲引用傳遞;
            int &i = foo();                        // ok
            int j = foo();                        // ok
        在引用傳遞時,經常使用 const 來保護引用參數的傳遞;
        在C++中,儘量使用引用傳遞參數,儘量使用引用代替指針;
        
        example:
        int& foo (const int& a) { return a; }
        int main () { foo (100); }
        編譯提示錯誤:傳進去的是 const a,那麼返回的應該也是 const 型;
        在函數前面加上 const 就可以了:
            const int& foo (const int& a) { return a; }
        tips:這個例題實際意義不大,因爲函數返回類型爲引用,目的是爲了做左值(可以修改);
        但這裏又加了 const 屬性,讓其不能做左值,矛盾;
        所以這個例題只是說明,如果 return const 類型,那麼函數返回值也必須是 const;

6.    引用的本質就是指針,很多場合下是可以互換的;
    在高級語言 c++ 層面上,他們還是有區別的,如下:
    1)指針是實體變量,但是引用不是實體變量,只是一個別名;
        int &a = i;     sizeof(a);                      // a 即(int)i 的大小 4
        double &b = f;     sizeof(b);                        // b 即(double)f 的大小 8
    2)指針是實體,可以不初始化(但最好初始化);引用必須初始化;
    3)指針的目標可以修改,但是引用的目標不能修改;
        int&a = i;    ==>    int* const a = &i;                // a 不能再指向別的變量
    4)可以定義指針的指針,也就是二級指針;但是不可以定義引用的指針;
        int &r = a;    int&* p = &r;                        // 不對,r不是實體,沒有地址,也就沒有指針
    5)可以定義指針的引用,但是不可以定義引用的引用;
        int a;     int *p = &a;     int*& q = p;            // OK
        int a;     int &r = a;     int&& s = r;            // ERROR,但是 int &s = r; 是正確的;
    6)可以定義指針的數組,但是不能定義引用的數組;
        int a, b, c;     int *parr[] = {&a, &b, &c};        // OK
        int a, b, c;     int &rarr[] = {a, b, c};        // ERROR
        可以定義數組的引用:
        int arr[] = {1, 3, 5};     int (&arr1)[3] = arr;    // OK
        
    舉例 1
        int a = 100, b = 200;
        int *p1 = &a, *p2 = &b;
        p1 = p2;                        // p1 指向 b 地址,但是 a 的值不變
        int &r1 = a, &r2 = b;
        r1 = r2;                        // 引用改變原對象值,此時 a = 200;

7.    顯示類型轉換運算符
    c裏面叫做強制類型轉換;
    c++裏面分爲以下幾種類型轉換:

    1)靜態類型轉換
        static_cast < 目標類型 >( 源類型變量 )
        轉換時作靜態檢查,即在編譯時進行;
        void * 到其他指針的轉換;
        如果在目標類型和源類型之間,某一個方向上可以作隱式類型轉換,那麼兩個方向上都可以作靜態類型轉換;
        如果在兩個方向上都不能作隱式類型轉換,那麼在任意一個方向上也不能作靜態類型轉換;
        int *p1 = ...;    void *p2 = p1;
        p1 = static_cast<int *>(p2);
        如果存在從源類型到目標類型的自定義轉換規則,那麼也可以使用靜態類型轉換;(瞭解,後面再講)

    2)動態類型轉換(後期多態部分講)
        dynamic_cast < 目標類型 >( 源類型變量 )
        主要用於具有多態性的父子類指針或引用之間;

    3)常類型轉換
        const_cast < 目標類型 >( 源類型變量 )
        給一個擁有const屬性的指針或引用去掉常屬性;

        int a = 100;
        const int *p1 = &a;                    // ok,常指針可以指非常;反過來不行,看下面例子
        *p1 = 200;                            // error, p1不能改
        int *p2 = p1;                            // error,p1是常屬性,p2不是,兩者屬性不匹配
        int *p2 = const_cast<int *>(p1);
        *p2 = 200;                            // ok,p2被去常了

        const int b = 100;
        int *p3 = &b;                            // error,b是const型,不能賦值給非常p3

        常量和擁有常屬性的變量是不一樣的;後者在特定情況下可以改變,而前者無法改變;
        c++ 編譯器特有的常量優化:
        const int a = 100;
        int *p1 = const_cast<int *>(&a);
        *p1 = 200;
        cout << a;                            // 100, 編譯器默認爲凡是有a的地方,都提前換爲100 :cout << 100;
        cout << *p1;                            // 200
        
        上面給 a 加了 const 屬性,如果給 a 再加上 volatile 後,const volatile int a = 100;
        編譯器就不對 a 進行常量優化,那麼最後 a 的值就是 200;(和硬件相關的時候用到)

        注意:const_cast 只是去常,不能進行類型轉換,也就是說,不能寫爲:int *p2 = const_cast<double *>(p1);
        或者如果 p1 爲 double 型,也不能寫成:int *p2 = const_cast<int *>(p1);
        
        問:關鍵字 volatile 有什麼含意 並給出三個不同的例子 ?
        答:一個定義爲 volatile 的變量是說這變量可能會被意想不到地改變,這樣,編譯器就不會去假設這個變量的值了。
        精確地說就是,優化器在用到這個變量時必須每次都小心地重新讀取這個變量的值,而不是使用保存在寄存器裏的備份。
        下面是 volatile 變量的幾個例子:
            1) 並行設備的硬件寄存器(如:狀態寄存器)
            2) 一箇中斷服務子程序中會訪問到的非自動變量 (Non-automatic variables)
            3) 多線程應用中被幾個任務共享的變量
        回答不出這個問題的人是不會被僱傭的。我認爲這是區分 C 程序員和嵌入式系統程序員的最基本的問題。
        嵌入式系統程序員經常同硬件、中斷、RTOS 等等打交道,所用這些都要求 volatile 變量。
        不懂得 volatile 內容將會帶來災難。
        假設被面試者正確地回答了這個問題(嗯,懷疑這否會是這樣),我將稍微深究一下,
        看一下這傢伙是不是真正懂得 volatile 完全的重要性。
            1) 一個參數既可以是 const 還可以是 volatile 嗎?解釋爲什麼。
            2) 一個指針可以是 volatile 嗎?解釋爲什麼。
            3) 下面的函數有什麼錯誤:
            int square (volatile int *ptr) {
                return *ptr * *ptr;
            }
            下面是答案:
            1) 是的。一個例子是隻讀的狀態寄存器。它是 volatile 因爲它可能被意想不到地改變。
            它是 const 因爲程序不應該試圖去修改它。
            2) 是的。儘管這並不很常見。一個例子是當一箇中服務子程序修改一個指向一個 buffer 的指針時。
            3) 這段代碼有個惡作劇。這段代碼的目的是用來返回指針 *ptr 指向值的平方,
            但是,由於 *ptr 指向一個 volatile 型參數,編譯器將產生類似下面的代碼:
            int square (volatile int *ptr) {
                int a, b;
                a = *ptr;
                b = *ptr;
                return a * b;
            }
            由於 *ptr 的值可能被意想不到地該變,因此 a 和 b 可能是不同的。
            結果,這段代碼可能返回不是你所期望的平方值!正確的代碼如下:
            long square (volatile int *ptr) {
                int a;
                a = *ptr;
                return a * a;
            }

    4)重解釋類型轉換
        reinterpret_cast < 目標類型 >( 源類型變量 )
        在不同類型的指針或引用之間作類型轉換,以及在指針和整型之間作類型轉換;
            可以強轉任何類型的指針;
            把整數強轉成指針;或者把指針強轉成整數;
        int i = 0x12345678;
        char *p = reinterpret_cast<char *>(&i);    // 把int看作4個char
        for (size_t i = 0; i < 4; ++i)
            cout << hex << (int)p[i] << ' ';        // 輸出:78 56 34 12(小端)
        float *p = reinterpret_cast<float *>(&i);
            cout << *p << endl;
        void *v = reinterpret_cast<void *>(i);
            cout << v << endl;

    5)目標類型變量 = 目標類型(源類型變量);    // c裏面強制類型轉換是:目標類型變量 = (目標類型)源類型變量;
        int a = int (3.14);                    // a = 3;

        char c = 'a';
        int n = (int)c;    // c還是char型,臨時生成一個臨時變量/匿名變量,強制爲int型,值和char c是一樣的
        (int)c = 100;        // erro,匿名變量都是右值,不能被賦值;

        所有的匿名變量都是右值,如果要引用它,需要帶有 const
        void foo(int &a){...}                // void foo(const int &a){...}
        char c = 'a';
        foo(c);                                // error,類型不一致
        foo((int)c);                            // 實參是int型臨時變量,是右值,行參是左值引用,如果在行參裏面加const就對了

8.    c++ 之父的建議:
    1)少用宏,多用 const、enum、inline;
        #define PI     3.14
        const double PI = 3.14;
        #define ERROR     -1
        enum{ ERROR = -1 };
        #define max(a, b)        ((a) > (b) ? (a) : (b))
        inline int double max(double a, double b){ return a > b ? a : b; }
        
        對於單純常量,最好以 const 或 enum 替換;
        對於形似函數的宏,最好用 inline 函數替換;
        
    2)變量隨用隨聲明,同時初始化;
    3)少用 malloc/free,多用 new/delete
    4)少用c風格的強制類型轉換,多用類型轉換運算符;
    5)少用c風格的字符串,"\n,\0",多用string;
    6)少用數組;
    7)樹立面向對象的編程思想;
    
    問:請說出 const 與 #define 相比,有何優點 ?
    答:const 作用:定義常量、修飾函數參數、修飾函數返回值三個作用。
    被 const 修飾的東西都受到強制保護,可以預防意外的變動,能提高程序的健壯性。
    1)const 常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。
    而對後者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會產生意料不到的錯誤。
    2)有些集成化的調試工具可以對 const 常量進行調試,但是不能對宏常量進行調試。

9.    對象---面向對象程序設計(OOP)
    1)程序就是一組對象,對象之間通過消息交換信息;
    2)類就是對對象的描述和抽象,對象就是對類的具體化和實例化;
    3)c++中對象其實就是一個變量,在計算機中是一片內存區域;
    4)類通過屬性和行爲兩方面對對象進行抽象;
        c++中,對象屬性就是成員變量;對象行爲就是成員函數;
    判斷一個程序是否是面向對象,判斷依據看其是否具有:封裝,繼承,多態;

10.    面向對象程序設計(OOP)學習過程:
    1)至少掌握一種OOP變成語言;
    2)精通一種面向對象的元語言,如UML;
    3)研究設計模式,如GOF;

11.    類的定義:
    class 類名 {
        類型 成員變量名;                            // 不能在此初始化
        返回類型 成員函數名(行參表){函數體};        // 通過函數初始化成員變量,一般是構造函數
    };                                                // 末尾的分號別忘記
    類是創建對象的模型;
    c++中類就是用戶自定義的數據類型;

    成員變量如果不初始化,結果不確定;
        int --- 隨機數
        char --- \0 或者 NULL

    c++裏,變量有成員、局部和全局之分;
    函數有成員函數和全局函數之分;沒有局部函數;

12.    訪問控制屬性:
    public: 公有成員,大家都可以訪問;
    private: 私有成員,只有自己可以訪問;(缺省屬性)
    protected: 保護成員,自己和自己的子類可以訪問;

    class 類的缺省訪控屬性是 private,struct 結構的缺省訪控屬性是 public,用於和c兼容;

    class Student{
    public:
        string m_name;
        int m_age;
        void eat(const string &food)
        { cout << food << endl; }
    };
    int main()
    {
        Student student = {"zhangfei", 23};
        Student student;                                // student就是實例化對象
        student.m_name = "zhangfei";
        student.m_age = 23;
        student.eat("beer");
        return 0;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章