1. 程式人生 > 實用技巧 >C++ 歸納複習常規篇

C++ 歸納複習常規篇

複習之前必須說一個關鍵點 C++這門語言是強型別語言,非常的強調型別

1. 關鍵字const

1.0 ) const 非指標

int main()
{
    const int a = 1;
    int *p = (int *)&a;
    *p = 10;

    return 0;
}

結果 a = 1 *p = 10

原因:a是常量,處在常量符號表中。在編譯階段,編譯器檢查到使用a這個常量時,會從常量符號表中取出這個值,而不是從該地址記憶體中取值。

當使用 &獲取地址後,才會為這個地址申請記憶體。因此*p只是操作這個地址的值,不影響a的常量符號表中的值

  

1.1 ) const 指標

int main()
{
    int a = 1;
    int b = 2;

    const int *p1 = (int *)&a;
    int *const p2 = (int *)&b;    

    *p1 = 10;   //錯誤 
     p1 = &b;    //正確

    *p2 = 20;   //正確
     p2 = &a;   //錯誤

    return 0;
}

當const在 * 左側,即程式碼中的指標p1,表示修飾的是p1指向的地址的記憶體,因此,修改p1指向地址是可以的,但修改p1指向地址的記憶體編譯器會報錯。

當const在 * 右側,即程式碼中的指標p2,表示修飾的是p2指向的地址,因此,修改p2指向地址的記憶體是可以的,但是修改p2指向地址編譯器會報錯。

2. 關鍵字const 與define的區別

  發生的時期不同。

define發生在預處理階段,進行簡單的文字替換,但不會進行型別檢查作用域檢查。

const在預處理之後的編譯階段,會對型別進行檢查。

  因此用define的時候要格外小心,特別是巨集定義一些函式的時候,要考慮符號的優先順序問題。

3. inline 行內函數

一般呼叫一個普通較短的函式,就是跳轉到這個函式名(地址),在一些實時性要求高的程式裡面,跳轉就要有系統開銷(儲存現場、恢復現場),會降低實時性。因此可以使用inlne關鍵字來修飾函式,類似於define,就是原地展開函式。

內聯是一種請求,如果函式過於複雜,編譯器會拒絕內聯,函式變回普通函式。

4. 函式過載

這是C++一個重要的特性。

void r1chie(void)
{

}

void r1chie(int a)
{

}

void r1chie(int a,int b)
{

}

void r1chie(char a)
{

}

void r1chie(char a,int b)
{

}

int main()
{
  int a; 
  char c;
  r1chie();
  r1chie(a);
  r1chie(a,c);
  r1chie(c);
  r1chie(c,a);

  return 0;
}

我們反彙編上面這段函式

00000000004006ef <main>:
  4006ef:    55                       push   %rbp
  4006f0:    48 89 e5                 mov    %rsp,%rbp
  4006f3:    48 83 ec 10              sub    $0x10,%rsp
  4006f7:    e8 ba ff ff ff           callq  4006b6 <_Z6r1chiev> //4006b6
  4006fc:    8b 45 fc                 mov    -0x4(%rbp),%eax
  4006ff:    89 c7                    mov    %eax,%edi
  400701:    e8 b7 ff ff ff           callq  4006bd <_Z6r1chiei> //4006bd
  400706:    0f be 55 fb              movsbl -0x5(%rbp),%edx
  40070a:    8b 45 fc                 mov    -0x4(%rbp),%eax
  40070d:    89 d6                    mov    %edx,%esi
  40070f:    89 c7                    mov    %eax,%edi
  400711:    e8 b1 ff ff ff           callq  4006c7 <_Z6r1chieii> //4006c7
  400716:    0f be 45 fb              movsbl -0x5(%rbp),%eax
  40071a:    89 c7                    mov    %eax,%edi
  40071c:    e8 b3 ff ff ff           callq  4006d4 <_Z6r1chiec>  //4006d4
  400721:    0f be 45 fb              movsbl -0x5(%rbp),%eax
  400725:    8b 55 fc                 mov    -0x4(%rbp),%edx
  400728:    89 d6                    mov    %edx,%esi
  40072a:    89 c7                    mov    %eax,%edi
  40072c:    e8 af ff ff ff           callq  4006e0 <_Z6r1chieci> // 4006e0
  400731:    b8 00 00 00 00           mov    $0x0,%eax
  400736:    c9                       leaveq 
  400737:    c3                       retq   

可以看到,雖然函式名是一樣的,但是地址卻是不一樣的。

那麼過載是根據什麼呢?

4.1 )引數的數量

4.2 )引數的型別

4.3) 引數的順序

4.4) 同一作用域

5. 關鍵字new和delete

malloc函式與關鍵字new。

5.1)malloc是庫函式,需要包含標頭檔案。new需要編譯器支援。

5.2)malloc需要指定大小。new是編譯器計算。

5.3)malloc返回的是void *型別指標,還需要進行強轉。new返回的是物件型別指標,因此不需要強轉。

使用new申請記憶體,如果記憶體不需要了就記得釋放

class r1chie
{

public:
      int a;
      int b;
private:
      int c;

};


int main()
{
    int *p1 = new int();
    int *p2 = new int(10);
    int *p3 = new int[10];
    char *p4 = new char();
    r1chie *p5 = new r1chie;
        int  **p6 = new int*[10];

    delete p1;
    delete p2;
        delete[] p3;
    delete p4;
    delete p5;
    delete[] p6[10];


    return 0;
}

6. namespace 名稱空間

#include <iostream>

namespace r1chie
{
    int i = 1; 
}

namespace r1chie_2
{

    int i = 10;
}

using namespace std;

int main()
{
    
    int i = 100;
    
    cout << r1chie::i << endl;
        cout << r1chie_2::i << endl;
    cout << i << endl;

    return 0;
}

輸出結果:

1

10

100

7. C++的引用 &

  7.0)引用的本質就是指標。

7.1)一個變數可以取多個別名

  

int main()
{
    int a = 1;
    int& b = a;    
    int& c = a;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;

    a = 2;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;

    return 0;
}

輸出結果:

1
1
1
2
2
2

7.2)引用必須初始化

int main()
{
    int& b;    // 編譯器報錯
 
    return 0;
}

  7.3)引用只能初始化一次,不能改變為其它變數的引用

 7.4)不能建立陣列的引用。因為陣列是一個由若干個元素所組成的集合,所以無法建立一個數組的別名

  7.5)引用的型別要相同

int main()
{
        int a = 1;
        float& b = a; //報錯

        return 0;
}

 7.6)常引用

  7.6.1) 使用常引用,則不能使用引用對目標變數的值進行改變,從而使引用的目標成為了const。

int main()
{
        int a = 1;
        const int& b = a;

        a = 2;  //正確
        b = 3;  //報錯

        return 0;
}

7.6.2)臨時物件,臨時物件都是const型別的

string fun1();
void fun2(string &s);

fun2(fun1());  //報錯
fun2("Hello world"); //報錯

  原因是將const型別轉換成非const,C++是強型別的語言因此報錯。

  7.7)C++中引用的內部實現

int& a            --->      int* const a

void f(int& a)    ----> void f(int* const a)
{                       {
   a = 5;                   *a = 5;
}                       }    

關於引用這篇文章分析得很好https://www.cnblogs.com/Mr-xu/archive/2012/08/07/2626973.html

8. C++的強制型別轉換

  這個又是一個比較重要的特性。

 四種轉換:

8.1)static_cast

變數和物件之間的轉換 和 有繼承關係的類物件指標轉換,可通過父類物件去初始化子類物件(只會初始化父類部分)。    

class Base
{

public:
    int a;    
    
    Base(int i)
    {
         a = i;
        cout << "This is Base"<< endl;
    }
};

class Child :public Base
{

public:
    int b;
    Child(int i) : Base(i)
    {
        b = i;
        cout << "This is Child" << endl;
    }
};


int main()
{
    int a = 1;
    char b = 'a';

    a = static_cast<int>(b);

    Base *b1 = new Base(10);
    Child *c1 = static_cast<Child*>(b1);
    
    c1->b = 100;
    cout << "c1->a = " << c1->a << endl;
    cout << "c1->b = " << c1->b << endl;
    c1->a = 20;
    cout << "b1->a = " << b1->a << endl;
    return 0;
}

輸出結果

This is Base
c1->a = 10
c1->b = 100
b1->a = 20

  8.2)const_cast

    去除類物件的屬性,但必須要強制轉換成引用或指標

int main()
{
    const int a = 2;

//    int b  = const_cast<int>(a);  報錯
    int& c = const_cast<int&>(a);
    int *p = const_cast<int*>(&a);

    cout << a << endl;
    cout << c <<endl;
    cout << *p << endl;

    c  = 6;
    cout << endl;
    cout << a << endl;
    cout << c <<endl;
    cout << *p << endl;

    return 0;
}

輸出:

2
2
2

2  //從常量符號表取出,因此a的值依然是2
6
6

  8.3)dynamic_cast

  用於有繼承關係的類指標(引用)間的轉換

  用於有交叉關係的類指標(引用)間的轉換

  具有型別檢查的功能,編譯時會去檢查使用的方法是否正確,轉換是否成功只有程式執行過程才知道

  父類轉子類時,父類中必須有虛擬函式支援(換句話說必須是多型)

  

class Base
{
public:
    Base()
    {
        cout <<  "This is Base"<<endl;
    }
/*    
    virtual ~Base()
    {
        cout << "This is ~Base" << endl;
    }
*/

};

class Child : public Base
{

};


int main()
{
    Base *p = new Base;
    Child *pc = dynamic_cast<Child*>(p); //報錯,原因父類沒有虛擬函式
    cout << p << endl;

    delete p;
    


    return 0;
}

 8.4)reinterpret_cast

  用於指標之間的轉換

int main()
{
  int a = 1;
  char * b = reinterpret_cast<char*>(&a);
  char c = reinterpret_cast<int>(a); //報錯    

  return 0;  
}