【C++】 C++知識點總結

做者:李春港
出處:https://www.cnblogs.com/lcgbk/p/14643010.htmlhtml

目錄

前言

這篇文章是對C++的知識點作了一些簡單的總結,基本包含了全部的C++基礎知識點。如下提到的知識點並不是深刻講解,只是大概講解了各個知識點的基本使用。如須要深刻了解,能夠針對某個知識點去深刻學習。ios

1、C++經常使用後綴

cpp, .h, cc, cxx, hppc++

2、頭文件

一、C++輸入輸出

  • 頭文件#include
  • 標準輸入(standard input)與預約義的 istream 對象 cin 對應
  • 標準輸出(standard output) 與預約義的 ostream 對象 cout 對應
  • 標準出錯(standard error)與預約義的的 ostream 對象 cerr 對應

例子:用c++寫一個簡單計算器express

#include <iostream>
    int main(void)
    {
            int a=0, b=0;
            char c=0;
            std::cout<<"please input type: a+b" <<std::endl;
            std::cin>>a>>c>>b;
            switch(c)
            {
            case '+':
                    std::cout<<a<<c<<b<<"="<<a+b<<std::endl; break;
            case '-':
                    std::cout<<a<<c<<b<<"="<<a-b<<std::endl; break;
            case '*':
                    std::cout<<a<<c<<b<<"="<<a*b<<std::endl; break;
            case '/':
                    std::cout<<a<<c<<b<<"="<<a/b<<std::endl; break;
            }
            return 0;
    }

二、在C++中使用C的庫函數

extern "C"
{
    #include <stdlib.h>
    #include <string.h>
}

3、 指針與動態內存分配

靜態內存分配(全局變量, 局部變量), 動態內存分配(在 c 中用 malloc 分配的堆空間 free 來釋放)c++中用 new 分配堆空間 delete 釋放。ubuntu

一、C

char  *name  =(char*) malloc(100);
free(name);

二、C++

  • 整形數: int *p = new int(10) ; 分配空間而且初始化爲 10 釋放 delete p
  • 整形數組:int *arr = new int[10] ; 分配十個連續整數空間 釋放 delete []arr
  • 字符型:char *p = new char('a'); 釋放 delete p;
  • 字符串:char *arr = new char[100];分配 100 個字符空間 釋放 delete []arr;

4、命名空間

爲了確保程序中的全局實體的名字不會與某些庫中聲明的全局實體名衝突,引入了命名空間。windows

一、做用

  • 避免名稱衝突;
  • 模塊化應用程序;
  • 匿名的命名空間可避免產生全局靜態變量,建立的 「匿名」 命名空間只能在建立它的文件中訪問。

二、定義

除main函數外全部函數, 變量, 類型。數組

namespace  空間名{
    函數,變量, 類型
}

例子:
namespace class01
{
    std::string name="jack";
    int age=19;
    int number = 123;
}

三、使用空間成員

一、 直接經過空間名::成員名   --標準使用--提倡使用的方法
     class01::name   -- ::所屬符號
二、using指示符指引
     using namespace calss01; // 把class01空間了裏面的內容暴露在當前位置,當文件有變量與命名空間的成員同樣時,則後期使用該成員或變量時,程序運行時會報錯;但能編譯經過
三、 using聲明
    using class01::number;
  // 當文件也有number變量時,則編譯的時候就報錯,至關於定義了兩次該變量。

所屬符號::
安全

四、命名空間嵌套

namespace  AAA{
    namespace BBB
    {
        int number=0;
    }
}

使用:數據結構

  • AAA::BBB::number;
  • using namespace AAA::BBB;   number;
  • using AAA::BBB::number;

五、匿名空間

至關於全局變量直接使用(只能在本文中使用)
static。
定義:app

namespace {
    int data;
}

匿名空間與static的異同:
static 沒法修飾自定義類型;static 產生當前 符號只在當前源文件有效,是由於它修改了符號的Bind屬性,使之變爲局部的;而匿名空間雖然能夠產生相同效果,可是符號仍是具備外部連接屬性。匿名命名空間內的變量與函數,只在當前源文件內有效;不一樣源文件的匿名命名空間,能夠存在同名符合。static須要在每一個變量加上

6、引用

引用:就是某一變量(目標)的一個別名,對引用的操做與對變量直接操做徹底同樣。
引用的聲明方法:類型標識符 &引用名=目標變量名;(別名)

int a = 10;
int &ra = a; (ra 就是 a 的引用 ,也稱 a 的別名)

一、引用特色:

  • &在此不是求地址運算符,而是起標識做用。
  • 類型標識符是指目標變量的類型。
  • 聲明引用時,必須同時對其進行初始化。
  • 引用聲明完畢後,至關於目標變量有兩個名稱即該目標原名稱和引用名,且不能再把該引用名做爲其餘變量名的別名。
  • 聲明一個引用,不是新定義了一個變量,它只表示該引用名是目標變量名的一個別名,它自己不是一種數據類型,所以引用自己不佔存儲單元,系統也不給引用分配存儲單元,但引用自己是有大小的,一個指針的大小,在64位系統:sizeof(&ra) = 8,32位爲4字節,sizeof(ra)=sizeof(a)被引用對象的大小。故:對引用求地址,就是對目標變量求地址。 &ra 與&a 相等。
  • 不能創建數組的引用。由於數組是一個由若干個元素所組成的集合,因此沒法創建一個

二、引用的應用:

1.引用做爲參數
引用的一個重要做用就是做爲函數的參數。之前的 C 語言中函數參數傳遞是值傳遞,若是有大塊數據做爲參數傳遞的時候,採用的方案每每是指針,由於這樣能夠避免將整塊數據所有壓棧,能夠提升程序的效率。可是如今(C++中)又增長了一種一樣有效率的選擇
2.常引用
常引用聲明方式:const 類型標識符 &引用名 = 目標變量名;
用這種方式聲明的引用,不能經過引用對目標變量的值進行修改,從而使引用的目標成爲 const,達到了引用的安全性。
3.引用做爲函數返回值
要以引用返回函數值,則函數定義時要按如下格式:
類型標識符 &函數名 (形參列表及類型說明){ 函數體 }
特色:

  • 以引用返回函數值,定義函數時須要在函數名前加&
  • 用引用返回一個函數值的最大好處是,在內存中不產生被返回值的副本。
  • 不能返回局部變量的引用

7、函數重載

函數重載只能在同一個類中
int open();
int open(const char* filename);
int open(const char *filename , int flag);
c++中編譯程序的是檢測函數,經過函數名和參數列表

若是在一個文件中出現同名的函數但參數列表不一樣,那麼這些函數屬於重載

函數重載的依據:

  1. 函數名相同,

  2. 參數列表不一樣,(個數, 類型) add(int, doube) add(double, int)

  3. 若是參數是指針, 或引用,那麼const修飾也能夠做爲重載依據

  4. 函數的返回不能做爲函數重載的依據

  5. 參數是否爲默認參數不能做爲函數重載的依據

8、函數缺省參數(默認參數)

int open(const char *filename, int flag=10)
int open(const char *filename="c++", int flag=10)
int open(const char *filename="c++", int flag) 錯誤

注意: 若給某一參數設置了默認值,那麼在參數表中其後(也就是右邊)全部的參數都必須也設置默認值

9、類與對象

一、類

(1)定義:

 class 類名{
    類的特徵(屬性) 成員變量
    類的行爲(功能) 成員方法, 函數
};
注意:當類裏面的成員參數函數有默認值時,若須要在外部定義該函數時,不能寫默認值,默認值只能在類裏面聲明的時候寫默認值。
例子:
 class Tdate
{
    public:
        void Set(int m, int d, int y )
        {
        month = m ;
        day = d ;
        year = y ;
        }  
    private:
        int month;
        int day;
        int year;
};

struct和class在C++都能定義類,其區別:

  • struct做爲數據結構的實現體,它默認的數據訪問控制是public的,而class做爲對象的實現體,它默認的成員變量訪問控制是private的。
  • 默認的繼承訪問權限。struct是public的,class是private的。

(2)類成員權限控制

  • public 公有 公有段的成員是提供給外部的接口,在外部能夠訪問
  • protected 保護 保護段成員在該類和它的派生類中可見,在類外不能訪問(也就是在外部建立對象時不能訪問)
  • private 私有 私有段成員僅在類中可見(友元函數或者友元類能夠訪問),在類外不能訪問(也就是在外部建立對象時不能訪問)

(3)類內/外訪問

  • 類內訪問:在類的成員函數中訪問成員(沒有任何限制)
  • 類外訪問: 在類的外部經過類的對象訪問類的成員

二、對象

定義與成員訪問:

 class Tdate
{
    public:
        int num;
        void set(int m, int d, int y )
        {
        month = m ;
        day = d ;
        year = y ;
        }  
    private:
        int month;
        int day;
        int year;
};

//定義對象
Tdate A;
Tdate *B = new Tdate( );
//對象成員訪問
A.set(1,1,1);
A.num = 2;
B->set(1,1,1);
B->num = 2;

10、構造和析構函數

一、構造函數

構造函數是成員函數,函數名與類名相同,函數沒有返回值, 函數不須要用戶調用,在建立對象的時候自動調用。
(1)若是建立一個類你沒有寫任何構造函數,則系統會自動生成默認的無參構造函數,函數爲空,什麼都不作。
(2)只要你寫了一個下面的某一種構造函數,系統就不會再自動生成這樣一個默認的構造函數,若是但願有一個這樣的無參構造函數,則須要本身顯示地寫出來
(3)參數列表初始化:只有構造函數纔有參數列表初始化。若要在類內聲明,類外定義構造函數,且使用參數列表初始化參數時,則在類內聲明的時候不容許參數列表初始化,只能類外定義的時候進行參數列表初始化
(4)函數默認參數:不管是成員函數仍是構造函數,若須要類內聲明和類外定義的時候,默認參數值在聲明或者定義的時候均可賦值,但聲明和定義的參數不能有默認值

<1>構造函數定義及重載

class Complex
{
public:
    Complex()//構造函數定義
    {
        m_real = 0.0;
        m_imag = 0.0;
    }
    
    Complex(int a,int b)//重載構造函數定義
    {
        m_real = a;
        m_imag = b;
    }
private :
    double m_real;
    double m_imag;
};

//建立對象
Complex A;//這個時候不能有()
Complex A(1,1);
Complex *A = new Complex( );//能夠有()也能夠沒有
Complex *A = new Complex(1,1);

<2>構造函數參數列表初始化

Student(string n="name", int num=1000)
        :name(n),number(num){
        //name = n;
        //number = num;
}
注意:
* name、number:是本類裏面的成員;
* n、num:是對成員賦的值或者變量;
* 不能在類裏面聲明的時候用參數列初始化,聲明的時候能夠加默認值;

對對象成員進行列表初始化:
class A
{
public:
    A(int a,int b){}
}

class B:public A
{
    A a;
public:
    B(int c,int d ):a(c,d){}
}

使用緣由及用處:

  • 構造函數是成員函數,必須建立對象後才能調用
  • 參數列表初始化是在申請空間的同時就初始化
  • 若是成員是const修飾的成員、引用成員、繼承時候調用父類構造函數,這幾種狀況就必須用參數列表初始化。

<3>拷貝構造函數

(1)若是沒有自定義拷貝構造函數,系統會默認生成一個拷貝構造函數(淺拷貝構造函數,不會拷貝堆空間)

Student Jack; //構造函數
    Student Rose = Jack; //拷貝構造函數
    Student Tom (Jack); //拷貝構造函數
   
   後面兩種拷貝構造函數不會再次調用構造函數

(2)深拷貝構造函數

class Student
{
public:
    Student(int age, const char *n){
        this->age = age;
        this->name = new char[32];
        strcpy(this->name, n);
        cout<<"Student()"<<endl;
    }//this指針就是函數調用者
    ~Student(){
        delete []this->name;
        cout<<"~Student()"<<endl;
    }
    //深拷貝構造函數
    Student(Student& s)
    {
        cout<<"Student(Student&)"<<endl;
        this->age = s.age;
        this->name = new char[32];
        strcpy(this->name, s.name);
        //this->name = s.name;
    }
private:
    int age;
    char *name;
};

二、析構函數

  • 函數名有類同樣在函數名前面添加~符號
  • 析構函數沒有返回值, 也沒有參數
  • 析構函數在對象銷燬的時候自動調用(若是new一個對象,構造函數會自動執行,只有在delete的時候才調用析構函數)

例子:

class Complex
{
public:
    Complex( )//構造函數定義
    {
       cout << "complex" << endl;
    }
   ~ Complex( )//析構函數定義
    {
       cout << "~complex" << endl;
    }
};

int main( )
{
    Complex a; 
    Complex *p = new Complex();
    delete p;
    return 0;
}

結果:
complex
complex
~complex
~complex

11、類的內存空間

類自己是一個數據類型,在沒有定義對象前是不佔用內存空間的,定義對象的時候纔會分配空間。

  • 計算一個類的對象佔用多少空間用sizeof(類名或對象)
  • 類的對象大小是其數據成員(非靜態數據段),和虛函數表指針(一個類裏最多隻能有兩個指針,一個是虛函數的指針,一個是虛繼承的指針)大小和。普通方法(普通函數)不佔用內存,但用virtual修飾的虛函數佔用一個指針大小的內存。注:一個指針的大小、內存的對齊方式和編譯器有關;64位的話,大小爲8;32位的話,大小爲4。
  • 若是一個類中沒有數據成員,也沒有虛表那麼這個類的大小規定爲 1 個字節。

12、類繼承

繼承:

  • 新的類從已知的類中獲得已有的特徵的過程
  • 新類叫派生類/子類
  • 已知的類叫基類/父類
  • 若是直接將派生類的對象賦值給基類對象,派生類自身的成員就會被丟棄,只保留基類繼承來的成員。
  • 將基類指針指向派生類對象是安全的,由於派生類對象「是」它的基類的對象。可是要注意的是,這個指針只能用來調用基類的成員函數。

做用:
繼承能夠減小重複的代碼。好比父類已經提供的方法,子類能夠直接使用,沒必要再去實現。

類的繼承格式:

class 子類名 :繼承方式 父類
{
    子類成員
};

例如:

class Base
{
public:
    Base() {}
    int b;
};

class Child: public Base
{
};

一、繼承方式

繼承方式: 公有繼承, 保護繼承, 私有繼承

  • 公有繼承(public):繼承時保持基類中各成員屬性不變,而且基類中的private成員被隱藏。派生類的成員只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的對象只能訪問基類中的public成員。
  • 保護繼承(protected):繼承時基類中各成員屬性均變爲protected,而且基類中的private成員被隱藏。派生類的成員只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的對象不能訪問基類中的任何的成員。
  • 私有繼承(private):繼承時基類中各成員屬性均變爲private,而且基類中private成員被隱藏。派生類的成員也只能訪問基類中的public/protected成員,而不能訪問private成員;派生類的對象不能訪問基類中的任何的成員。

注意1: 私有繼承在下一次被繼承時,全部從父類繼承而來的都會別隱藏,而保護繼承在下次被繼承時根據繼承的屬性其中的數據可能被重新利用,因此私有繼承的保護性更增強。

注意2: 不管那種繼承子類的大小爲子類+父類(全部成員都要加起來,包括私有成員)

二、繼承

(1)繼承構造方法和析構方法的定義和調用

由於有父類纔有子類,因此調用順序以下:
構造函數的調用順序父類構造函數—對象成員構造函數—子類構造函數。
析構函數則相反。

注意:

  • 當派生類的構造函數爲B(){cout << "Afather\n";}時,建立一個派生類默認會調用沒有參數的父類構造函數A()。
  • 若是父類構造函數帶無默認值參數,派生類構造函數怎麼寫?
    以下:
例子一:
父類構造函數
public:
    Person(string name, string sex, int age):name(name),sex(sex),age(age) {
        cout<<"Person()"<<endl;
    }
子類構造函數
public:
    Student( ):Person("jack","man",19){
        cout<<"Student()"<<endl;
    }  //==>Person();
    
例子二:
class Animal
{
public:
    Animal(int w, const char *color, int age){
    this->weight = w;
    this->age = age;
    strcpy(this->color, color);
    }
protected:
    int weight;
    int age;
    char color[32];
};
class Cat:public Animal
{
public:
    Cat(int w, const char *color, int age, const char *type):Animal(w, color, age)
    {
        strcpy(this->type, type);
    }
    void show()
    {
        cout << "weight=" << weight << "\nage=" << age << "\ncolor=" << color <<endl;
    }
protected:
    char type[32];
};

(2)繼承後成員函數調用

  • 父子類成員函數名相同時,不是重載,這時父類的此函數會別隱藏
  • 子類調用成員函數時候會檢測子類是否存在,若是存在就調用本身的, 若是不存在就調用父類的(前提是父要有這個函數)
  • 若是子類和父同時存在這個函數,必定要調用父類函數,能夠用(父類名::函數名( ))調用。

例如:

#include <iostream>
using namespace std;

class A{
public:
    A(){cout << "Afather\n";}
    ~A(){cout << "~Afather\n";}
    void fun( ){
        cout << "father fun\n";
    }
};

class B:public A{
public:
    B(){cout << "Bchildren\n";}
    ~B(){cout << "~~Bchildren\n";}
    void fun(){
        cout << "children fun\n";
    }
};

int main( )
{
    B x;
    x.A::fun( );//調用父類的fun
    return 0;
}

輸出結果:
Afather
Bchildren
father fun
~~Bchildren
~Afather

三、多繼承

(1)語法:

class <派生類名>:<繼承方式1> <基類名1>,<繼承方式2><基類名2>,…
{
    <派生類類體>
};

(2)例子1:

class A{
public:
    A(){cout<<"A()"<<endl;}
    ~A(){cout<<"~A()"<<endl;}
protected:
    int dataA;
};

class B{
public:
    B(){cout<<"B()"<<endl;}
    ~B(){cout<<"~B()"<<endl;}
protected:
    int dataB;
};

class C:public A, public B
{
public:
    C(){cout<<"C()"<<endl;}
    ~C(){cout<<"~C()"<<endl;}
protected:
    int dataC;
};

注意:建立子類對象構造順序 A->B->C
若是改成:class C:public B, public A,建立子類對象構造順序 B->A->C

(3)例子2: 若是父類構造函數帶參數

繼承關係class C:public A, public B

父類帶參數
A(int a):dataA(a){cout<<"A()"<<endl;}
B(int b):dataB(b){cout<<"B()"<<endl;}

C(int a, int b, int c):A(a), B(b),dataC(c){cout<<"C()"<<endl;}

(4)多個父類有同名的成員
多個父類有同名的成員, 在子類中訪問會出現歧義

  1. 顯示調用對應父類中的成員
    c.A::info();
    c.B::info();
  2. 在子類中添加父類的同名成員
    這個時候系統會將父類的同名成員隱藏,調用子類的成員

四、虛擬繼承

語法:

class D :virtual public B{  //虛擬繼承
 ...
};

多繼承中多級繼承時候多個父類同時繼承同一個基類出現二義性問題--用虛擬繼承解決。

例如:

class A{ 
public:
    void fun(){}
};

class B:virtual public A{ };
class C:virtual public A{ };

class D:public B,public C{ };
int main(void)
{
    D x;
    x.fun();//若是不是虛繼承,會出現二異性,由於在D類繼承了兩次A類
    return 0;
}

十3、虛函數、虛表

定義: 在類的成員函數聲明前面添加virtual

virtual void show(){cout<<data<<endl;}
  • 若是一個類中包含虛函數, 那麼這個類的對象中會包含一個虛表指針vptr
  • 虛表指針保存在對象空間的最前面
  • 虛表中存儲的是類中的虛函數地址
  • 對象調用類中虛函數,會查詢虛表指針再執行函數
  • 一個類裏最多隻有兩個虛表指針(一個是虛函數的指針,一個是虛繼承的指針)
  • 用virtual修飾的虛函數佔用一個指針大小的內存。64位的話,大小爲8;32位的話,大小爲4。
  • 同一個類的不一樣實例共用同一份虛函數表, 它們都經過一個所謂的虛函數表指針__vfptr(定義爲void**類型)指向該虛函數表.

例子1:觀察輸出的最後結果是什麼(必定要看)

#include <iostream>
using namespace std;
class Base
{
public:
    Base(){}
    virtual ~Base(){}
public:
    virtual void show(int a=123){
        cout<<"Base::show()"<<a<<endl;
    }
};
class Child:public Base
{
public:
    Child(){}
    ~Child(){}
    virtual void show(int a=321){
        cout<<"Child::show()"<<a<<endl;
    }
    virtual void info()
    {
        cout<<"Child::info()"<<endl;
    }
};
int main()
{
    Child c;
    Base *p = &c;
    p->show();
    return 0;
}

結果:
Child::show()123
注意:
(1)當show函數不是虛繼承時,輸出結果爲Base::show()123,由於父類的指針只能調用本身的成員,若是有虛繼承,則虛表裏面父類的show函數的地址會被子類的show函數地址覆蓋,被覆蓋的前提是:兩個函數的名稱和參數類型、個數和返回值類型同樣。

例子2:經過指針調用虛表中的虛函數(在ubuntu下運行,虛表地址經過qt調試查看)

#include <iostream>
using namespace std;

class Base
{
public:
    Base(){}
    virtual ~Base(){}
protected:
    virtual void show(int a= 0){
        cout<<"Base::show()"<<endl;
    }
};
class Child:public Base
{
public:
    Child(){}
    ~Child(){}
    virtual void show(){
        cout<<"Child::show()"<<endl;
    }
    virtual void info()
    {
        cout<<"Child::info()"<<endl;
    }
};
int main()
{
    Child c;
    typedef void (*Fun)();
    c.show();
    Fun f = (Fun)(((long*)(*((long*)(&c))))[2]);
    f();
    return 0;
}

結果:
Child::show()
Base::show()

十4、純虛函數(抽象函數)、抽象類

(1)純虛函數--虛函數不須要實現直接賦值爲0,純虛函數有時稱爲抽象函數。
定義:

virtual void run()=0;

(2)抽象類

  • 若是一個類中包含純虛函數,那麼這個就是抽象類,抽象類是不能建立對象。
  • 抽象類能夠派生出子類, 若是在子類中沒有把父類中的純虛函數所有實現,那麼子類照樣是抽象類。

例子:線程獲取時間

#include <iostream>
#include <pthread.h>
#include <windows.h>
#include <time.h>
using namespace  std;
class Thread{
public:
    Thread(){}
    ~Thread(){}
    void start();
    virtual void run()=0;
protected:
    pthread_t id;
};

void *handle(void *arg)
{
    Thread* th = (Thread*)arg;
    th->run();
}

void Thread::start()
{
    int ret = pthread_create(&id, NULL, handle, (void*)this);
    if(ret < 0)
    {
        cout<<"create fail"<<endl;
    }
}

//派生一個線程子類--獲取系統時間
class TimeThread: public Thread{
public:
    virtual void run()
    {
        while(1)
        {
            cout<<"TimeThread::run()"<<endl;
            Sleep(1000);
            time_t t;
            time(&t);
            cout<<pthread_self()<<"-----------"<<ctime(&t)<<endl;
        }
    }
};

int main()
{
    TimeThread tth;
    tth.start();
    TimeThread tt;
    tt.start();
    while(1){}
    return 0;
}

十5、多態、虛析構

(1)多態

<1>概念
C++中,多態性是指具備不一樣功能的函數能夠用同一個函數名,這樣就能夠用一個函數名調用不一樣內容的函數。

在面向對象方法中通常是這樣表述多態性的:向不一樣的對象發送同一消息(調用函數),不一樣的對象在接收時會產生不一樣的行爲(即方法,不一樣的實現,即執行不一樣的函數)。能夠說多態性是「一個接口,多種方法」。

多態性分爲兩類:
(1)靜態多態性:在程序編譯時系統就能決定調用的是哪一個函數,所以又稱爲編譯時的多態性,經過函數的重載實現(運算符重載實際上也是函數重載);
(2)動態多態性:在程序運行過程當中才動態地肯定操做所針對的對象,又稱爲運行時多態性,經過虛函數實現。 

區別:函數重載是同一層次上的同名函數(首部不一樣,即參數個數或類型不一樣),虛函數是不一樣層次上的同名函數(首部相同)。

<2>動態多態性和虛函數
父類引用(指針變量)指向子類對象時,調用的方法仍然是父類中的方法。若是將父類中的該方法定義爲virtual,則調用的方法就是子類中的方法了。  

說明:原本,父類指針是用來指向父類對象的,若是指向子類對象,則進行類型轉換,將子類對象的指針轉爲父類的指針,因此父類指針指向的是子類對象中的父類部分,也就沒法經過父類指針去調用子類對象中的成員函數。可是,虛函數能夠突破這一限制!若是不使用虛函數,企圖經過父類指針調用子類的非虛函數是絕對不行的!  

注意:父類中非虛函數被子類重寫後,父類指針調用的是父類的成員函數,子類指針調用的是子類中的成員函數,這並非多態!由於沒有用到虛函數!

例如:

#include <iostream>
using namespace std;
class Person{
public:
    Person(){}
    ~Person(){}
    virtual void work(){cout<<"Person::work()"<<endl;}
protected:
    int data;
};

class ChildPerson: public Person{
public:
    ChildPerson(){}
    ~ChildPerson(){}
    virtual void show(){cout<<"ChlidPerson::show()"<<endl;}
    virtual void work(){cout<<"ChildPerson::work()"<<endl;}
    virtual void info(){}
protected:
    int number;
};

class A: public Person{
public:
    A(){}
    ~A(){}
    virtual void show(){cout<<"A::show()"<<endl;}
    virtual void work(){cout<<"A::work()"<<endl;}
    virtual void info(){}
protected:
    int num;
};

int main()
{
    ChildPerson cp;
    A a;
    Person* p = &a;
    Person* pson = &cp;
    pson->work(); //ChildPerson::work();
    p->work();//A:work();
    return 0;
}

(2)虛析構

多態的時候,用父類指針指向子類對象, 在delete 父類指針的時候默認只會調用父類析構函數,子類析構函數沒有執行(可能會致使子類的內存泄漏)--經過設置父類析構函數爲虛函數類解決,執行子類析構函數後,自動執行父類析構函數。

例如:

#include<iostream>
using namespace std;
class Base
{
public:
    Base(){cout<<"create Base"<<endl;}
    virtual ~Base(){cout<<"delete Base"<<endl;}
};
class Der : public Base
{
public:
    Der(){cout<<"create Der"<<endl;}
    ~Der(){cout<<"Delete Der"<<endl;}
};
int main(int argc, char const* argv[])
{
    Base *b = new Der;
    delete b;
    return 0;
}

十6、友元

友元:是c++裏面一個特性,爲了解決在函數中能夠訪問類的私有,或保護成員。
友元函數是能夠直接訪問類的私有成員的非成員函數。它是定義在類外的普通函數,它不屬於任何類。

  • 友元優勢: 能夠在函數中直接訪問成員數據, 能夠適當提升程序效率
  • 友元缺點:在函數類的權限失效, 破壞了類的封裝性
    friend關鍵聲明友元函數,或類。

第一種定義狀況:類外定義:例如

class Data
{
public:
    Data() {}
    void setA(int a)
    {
        this->a = a;
    }
protected:
    int a;
private:
    int b;
    //在Data類中聲明函數fun爲友元函數
    friend void fun();
};
void fun()
{
    Data data;
    //data.a = 120;
    data.setA(120);
    data.b = 220;
}
友元聲明只放類內部聲明, 能夠放在類內任意位置

第二種定義狀況:類內定義例如:

class Data
{
public:
    Data() {}
    void setA(int a)
    {
        this->a = a;
    }
protected:
    int a;
private:
    int b;
    //在Data類中聲明函數fun爲友元函數
    friend void fun();
    //聲明show爲友元函數,show不是成員函數
    friend void show()
    {
        Data data;
        data.a = 130;
        cout<<data.a<<" "<<data.b<<endl;
    }
};
void show(); //在外面聲明函數

十7、友元類

在一個類中的成員函數能夠訪問另一個類中的全部成員好比在 A 類中的成員函數能夠訪問 B 類中的全部成員。有兩種方法以下:
(1)在 B 類中設置 A 類爲友元類。
(2)A::fun 要訪問B類中的全部成員, 把A::fun函數聲明爲B類的友元函數。

(1)例如:在 B 類中設置 A 類爲友元類,A類成員函數能夠訪問B類的protected、private成員,B類不能訪問A類,若是要雙向訪問則要在兩個類中聲明對方爲友元類。友元關係不能被繼承。

class B
{
public:
    B(){}
    friend class A;//在 B 類中聲明 A 類爲友元類
private:
    int bdata;
};
class A
{
public:
    A(){}
    void showB(B &b)
    {
    b.bdata = 100;//在 A 類中成員函數使用 B 類的私有數據
    }
private:
    int adata;
};

(2)A::fun 要訪問B類中的全部成員, 把A::fun函數聲明爲B類的友元函數

#include <iostream>
using namespace std;
//前向聲明----只能用於函數形參, 定義指針, 引用,不能使用類具體成員
class B;
class A{
public:
    void fun(B& b);
};
class B{
public:
    B(){}
protected:
    int mb;
private:
    int nb;
    friend void A::fun(B& b);//在B類中聲明fun爲友元函數
};
void A::fun(B& b){
    cout<<b.mb<<b.nb<<endl;
}
int main()
{
    cout << "Hello World!" << endl;
    return 0;
}

十8、運算符重載

運算符重載 關鍵子函數operator

  1. 根據實際應用需求來重載運算符, 重載的時候必須保持不能改變運算符原本的特性
  2. 只能重載c++已有的運算符,不能本身新建立運算符

一、那些運算能重載

  • 雙面運算符  (+,-,*,/, %)
  • 關係運算符 (==, !=, <,  >, <=, >=)
  • 邏輯運算符 (||,   &&,  !)
  • 單目運算符 (*, &, ++, --)
  • 位運算符   (|,  &,  ~, ^,  <<, >>)
  • 賦值運算符 (=, +=, -=,   .....)
  • 空間申請運算符 (new , delete)
  • 其餘運算符 ((),  ->,  [])

二、那些運算符不能重載

  • .(成員訪問運算符)
  • .*(成員指針訪問運算符)
  • ::(域運算符)
  • sizeof(數據類型長度運算符)
  • ?:(條件運算符, 三目運算符)

注意:

三、格式:

返回類型說明符 operator 運算符符號(<參數表>)
{
   函數體
}

四、重載方式:
1.重載方式---成員函數重載

Complex C = A+B;    --》A.operator+(B);
規定:左值是函數調用者, 右值函數的參數

2.重載方式--友元重載(普通函數重載)(能夠在類裏面定義,也能夠在類外定義類內聲明)

Complex C = A-B;     -->operator-(A, B);
規定:左值爲第一個參數, 右值爲第二個參數

一、雙目運算符重載

(1)+ -重載

#include <iostream>
using namespace std;
class Complex
{
public:
    Complex(int r, int i):real(r), image(i) {}
    //成員重載實現重載加法+
    Complex  operator+ (Complex &B)
    {
        cout<<"operator"<<endl;
        Complex C(0,0);
        C.real = this->real +  B.real;
        C.image = this->image + B.image;
        return C;
    }
    //成員重載實現 對象加一個整型數
    int operator+(const int &a)
    {
        this->real += a;
        this->image += a;
        return this->real;
    }
private:
    int real;
    int image;
    friend Complex operator- (Complex &A, Complex &B);
};
//友元重載實現重載減法
Complex operator- (Complex &A, Complex &B)
{
    Complex C(0,0);
    C.real = A.real -  B.real;
    C.image = A.image - B.image;
    return C;
}
int main()
{
    Complex A(2,2);
    Complex B(1,1);
    Complex C = A+B; //==>A.operator+(B)
    int c  =  A+100; //==>A.operator+(100)
    Complex D = A-B; //operator-(A, B)
    return 0;
}

(2)輸出、輸入, 運算符重載

#include <iostream>
using namespace std;
class Point
{
public:
    Point (int x=0, int y=0):x(x),y(y){}
    void show()
    {
        cout<<"("<<x<<","<<y<<")"<<endl;
    }
private:
    int x, y;
    //聲明友元函數
    friend  ostream& operator<<(ostream &out, Point& p);
    friend istream&  operator>>(istream &in, Point& p);
};
//重載輸出
ostream&  operator<<(ostream &out, Point& p)
{
    out<<"("<<p.x<<","<<p.y<<")"<<endl;
    return out;
}
//輸入重載
istream&  operator>>(istream &in, Point& p)
{
    in>>p.x>>p.y;
    return in;
}
int main()
{
    Point p(10,20);
    p.show();
    cout<<p<<endl;   //==> ostream& operator<<(cout, p)
    Point A(0,0);
    cin>>A;
    cout<<A;
    return 0;
}

三、單目運算符重載

(1)++A、A++重載

成員函數重載

#include <iostream>
using namespace std;
class Data
{
public:
    Data(int d=0):data(d) {}
    //重載A++
    Data operator++(int)
    {
        Data old(*this);//保存原先的數捍
        this->data += 1;//對原數進行自劍
        return old;//返回未加以前的數捍
    }
    //重載++A
    Data& operator++()
    {
        this->data += 1;//對原數進行自劍
        return *this;
    }
private:
    int data;
    friend ostream &operator<<(ostream& out, Data &d);
};
ostream &operator<<(ostream& out, Data &d)
{
    out<<d.data<<endl;
    return out;
}
int main()
{
    Data A;
    Data d = A++; //==>A.operator++(int)
    cout<<d<<A<<endl;
    Data &c = ++A;
    cout<<c<<A<<endl;
    return 0;
}

友元函數重載

#include <iostream>
using namespace std;

class A
{
    int data;
public:
    A(int d = 0):data(d) {}
    void show()
    {
        cout << this->data << endl;
    }
    //友元函數重載++A
    friend A& operator++ (A &a);
    //友元函數重載A++
    friend A operator++ (A &b,int);
    //友元函數重載<<
    friend ostream& operator<< (ostream &out,A &a);
};

//友元函數重載++A
A& operator++ (A &a)
{
    a.data += 1;
    return a;
}
//友元函數重載A++
A operator++ (A &b,int)
{
    A old(b);
    b.data += 1;
    return old;
}
//友元函數重載<<
 ostream& operator<< (ostream &out,A &a)
 {
     out << a.data;
     return  out;
 }

int main(int argc,char **argv)
{
    A a(5);
    A b = ++a;
    cout << a << " " << b << endl;

    A c(5);
    A d = c++;
    cout << c << " " << d << endl;

    return 0;
}

(2)重載中括號[ ]

#include <iostream>
using namespace std;
class Array
{
public:
    Array(int n):length(n) {
        this->ptr = new int[this->length];
    }
    ~Array(){
        delete []this->ptr;
    }
    //拷貝構造函數---深拷貝(類的成員有指針指向堆空間)
    Array(Array& array)
    {
        this->length = array.length;
        this->ptr = new int[this->length];
        memcpy(this->ptr, array.ptr , this->length);
    }
    //重載[]
    int& operator[](int i)
    {
        cout<<i<<endl;
        return this->ptr[i];//返回第i個對象
    }
private:
    int length;
    int *ptr;
};

int main()
{
    Array mArr(10);
    mArr[0] = 100;  //
    return 0;
}

四、用運算符重載實現數據類型轉換

(1)轉換構造函數

轉換構造函數的做用:是將一個其餘類型的數據轉換成一個類的對象。 當一個構造函數只有一個參數,並且該參數又不是本類的const引用時,這種構造函數稱爲轉換構造函數。 轉換構造函數是對構造函數的重載。
例如:

#include <iostream>
using namespace std;
class Complex
{
public:
    Complex():real(0),imag(0){cout << "test1\n";}
    
    Complex(double r, double i):real(r),imag(i){cout << "test2\n";}
    
    // 定義轉換構造函數
    Complex(double r):real(r),imag(0){cout << "test3\n";}  
    
    /*
    // 拷貝構造函數
    Complex(Complex &a){  cout << "test4\n";  }//當此函數存在時,Complex c = 1;Complex c2 = c1 + 3.1;編譯時都會報錯
    */
    
    void Print(){
        cout<<"real = " << real <<" image = "<<imag<<endl;
    }
    
    Complex operator+(Complex c){
        Complex ret(this->real + c.real, this->imag + c.imag);
        return  ret;
    }
private:
    double real;
    double imag;
};
int main()
{
    Complex c;
    c = 4;  // 調用轉換構造函數將1.2轉換爲Complex類型,此時會調用轉換構造函數
    c.Print();
    Complex c1(2.9, 4.2);
    Complex c2 = c1 + 3.1; // 調用轉換構造函數將3.1轉換爲Complex類型
    c2.Print();
    return 0;
}

輸出結果:
test1
test3
real = 4 image = 0
test2
test3
test2
real = 6 image = 4.2

注意:

  • 一、用轉換構造函數能夠將一個指定類型的數據轉換爲類的對象。可是不能反過來將一個類的對象轉換爲一個其餘類型的數據(例如將一個Complex類對象轉換成double類型數據)。
  • 二、若是不想讓轉換構造函數生效,也就是拒絕其它類型經過轉換構造函數轉換爲本類型,能夠在轉換構造函數前面加上explicit
(2)用運算符重載實現數據類型轉換

用轉換構造函數能夠將一個指定類型的數據轉換爲類的對象。可是不能反過來將一個類的對象轉換爲一個其餘類型的數據(例如將一個Complex類對象轉換成double類型數據)。而類型轉換函數就是專門用來解決這個問題的!
類型轉換函數的做用是將一個類的對象轉換成另外一類型的數據。

#include <iostream>
using namespace std;
class Person
{
public:
    Person(int age=0):age(age) {}
    operator int() //經過運算符重載來實現數據類型轉換
    {
        cout<<"int"<<endl;
        return age;
    }
    operator long() //經過運算符重載來實現數據類型轉換
    {
        cout<<"long"<<endl;
        return num;
    }
    int getAge(){return age;}
private:
    int age;
    long num;
};
int main()
{
    Person Jack(19);
    int age = Jack;
    int a = Jack.getAge();
    long b = Jack;
    return 0;
}

注意:

  • 一、在函數名前面不能指定函數類型,函數沒有參數。
  • 二、其返回值的類型是由函數名中指定的類型名來肯定的。
  • 三、類型轉換函數只能做爲成員函數,由於轉換的主體是本類的對象,不能做爲友元函數或普通函數。
  • 四、從函數形式能夠看到,它與運算符重載函數類似,都是用關鍵字operator開頭,只是被重載的是類型名。double類型通過重載後,除了原有的含義外,還得到新的含義(將一個Complex類對象轉換爲double類型數據,並指定了轉換方法)。這樣,編譯系統不只能識別原有的double型數據,並且還會把Complex類對象做爲double型數據處理。

十9、模板函數

一、概念: 若是一個函數實現的功能相似,可是函數參數個數相同類型不一樣,這樣就能夠把實在該功能的函數設計爲模板函數。

二、格式:

template <typename T>  //T爲類型名
數據類型 函數名(參數列表){
    函數體
}

三、注意:

  • (1)在編譯時,根據變量生成實例。
  • (2)template   T只對其下面的函數模板有效。若是要定義第二個模板函數時,則要再寫template
  • (3)typename也能夠用class。
  • (4)T名字能夠隨便取。
  • (5)當參數不同時,能夠這樣定義參數列表template <class T,class Tp>
  • (6)參數列表能夠帶默認類型,template <class T,class Tp = int>。
  • (7)模板函數只有在使用(不是運行)的時候纔會檢測錯誤。

例子1:

//設計一個模板函數實現兩個對象交換
template <typename T>
void mswap(T &a, T &b)
{
    T  c = a;
    a = b;
    b = c;
}
int main()
{
    int a = 10;
    int b = 20;
    cout<<a<<" "<<b<<endl;
    mswap(a, b);
    cout<<a<<" "<<b<<endl;
    return 0;

例子2:

//錯誤,由於不可以肯定返回值的類型
template <class T, class Tp>
Tp fun(T &a)
{
    return a;
}

//修改,但返回值定死了
template <class T, class Tp = int>
Tp fun(T &a)
{
    return a;
}

//調用函數時指定類型
template <class T, class Tp = int>
Tp fun(T &a)
{
    return a;
}
int main(void)
{
    int a = 2;
    double ret = fun<int, double>(a)
}

四、模板函數與函數普通同時存在該如何調用

template <typename T>
void mswap(T &a, T &b)
{
    cout<<"template"<<endl;
    T  c = a;
    a = b;
    b = c;
}

//普通函數
void mswap(int &a, int &b)
{
    cout<<"std"<<endl;
    int c = a;
    a = b;
    b = c;
}

調用(1)
    int a = 10;
    int b = 20;
    cout<<a<<" "<<b<<endl;
    mswap(a, b);//---普通函數
    cout<<a<<" "<<b<<endl;
調用(2)
    double a = 10;
    double b = 20;
    cout<<a<<" "<<b<<endl;
    mswap(a, b);//---模板函數
    cout<<a<<" "<<b<<endl;

若是模板函數和普通函數同時存在, 調用的時候會根據參數選擇最優函數

二10、模板類

一、模板類的定義

//設計一個模板類  -模板類的類名 A<T>
//template< class  T ,  class Ty> //A<T, Ty>

template< class T > //A<T>
class A
{
public:
    A() {}
protected:
    T dataA;
};

int main()
{
    A<int> a;//定義模板類對象
    return 0;
}

注意:

  • (1)若是是浮點型或者其餘普通類型, 是指針或者是引用 template <double &N,class T=int>
     class array{...}
    定義對象: array<N,int> a ;這裏的 N 必須是全局變量。
  • (2)參數列表能夠帶默認類型,template <class T,class Tp = int> , 若是是默認類型, 與函數的默認參數相似, 必須是若是從那個一個開始默認, 那麼後面的全部模板類型多必須有默認類型。
  • (3)若是使用數值爲整型( char, short, int, long) 時候。template <int N,class T=int> class array{...},這裏的N只能是常量不能是變量,例如 array<10,int> a或者const int a = 5; array<a,int>。

二、模板類友元重載輸出

例如: 用模板類設計一個順序表(數組)

#include <iostream>

using namespace std;

template< class T >
class MVector{
public:
    MVector(){
        this->size = 1024;
        this->count = 0;
        this->ptr = new T[this->size];
    }
    ~MVector(){
        delete []this->ptr;
    }

    //拷貝構造函數
    MVector(MVector& mv){
        this->size = mv.size;
        this->ptr = new T[this->size];
        memcpy(this->ptr, mv.ptr, this->size*sizeof(T));
    }
    //添加數據
    void append(const T &data){
        this->ptr[this->count] = data;
        this->count++;
    }
    //重載<<追加數據
    void operator<<(int data)
    {
        this->ptr[this->count] = data;
        this->count++;
    }
#if 1//在類裏面定義重載函數
    //聲明友元重載輸出<<
    friend ostream& operator<<(ostream& out, MVector &mv)
    {
        for(int i=0; i<mv.count; i++)
        {
            out<<mv.ptr[i]<<" ";
        }
        out<<endl;
        return out;
    }
#endif
    //template<class Ty>
    //friend ostream& operator<<(ostream& out, MVector<Ty> &mv);
protected:
    int count;
    int size;
    T* ptr;
};
#if 0 //在類內聲明,在類外定義重載函數
//重載輸出<<運算符
template< class Ty >
ostream& operator<<(ostream& out, MVector<Ty> &mv)
{
    for(int i=0; i<mv.count; i++)
    {
        out<<mv.ptr[i]<<" ";
    }
    out<<endl;
    return out;
}
#endif
//模板函數在使用(不是運行)該函數的時候纔會檢查語法

int main()
{
    MVector<int> mvs;
    mvs.append(100);
    mvs<<200;
    cout<<mvs;
    return 0;
}

三、模板類繼承

若是在派生子類的時候父類類沒有肯定class B: public A ,那麼子類也是模板類。
例如:

#include <iostream>

using namespace std;

//設計一個模板類A<T>
template< class T >
class A
{
public:
    A(T a) {}
protected:
    T data;
};

//設計一個子類B 繼承模板類A<T> --B類也是模板類
template< class T >
class B: public A<T>
{
public:
    B(T a):A<T>(a){}

protected:
    int datab;
};

//設計一個子類C 繼承模板類A<int> --C類就是一個具體類
class C: public A<int>
{
public:
    C():A<int>(10){}
};

int main()
{
    A<char> a(10); //模板類建立對象
    B<string> b("hello"); //模板類子類建立對象
    C c;
    return 0;
}

四、模板類中的靜態成員

編譯時根據模板生成的不一樣類的靜態成員是不一樣內存空間的;在同一個類中建立的對象的靜態成員是共用一個內存空間的。
以下:

#include <iostream>

using namespace std;

template<class T>
class Data
{
public:
    Data() {}
    void show(T msg)
    {
        data = msg;
        cout<<data<<endl;
    }
public:
    static  T  data;
};

//類外初始化靜態成員
template<class T>
T Data<T>::data ;

int main()
{
    //建立一個對象
    Data<int> mydata;   //編譯的時候會生成一個 T爲int的類
    mydata.show(100);

    Data<string>::data = "hello"; //編譯的時候會生成一個T 爲string的類
    cout<<Data<string>::data<<endl;

    Data<string> mystr;
    cout<<mystr.data<<endl;
    return 0;
}

二11、強制類型轉換const_cast、static_cast、reinterpert_cast、dynamic_cast

注意:以上,若是轉換失敗的時候會返回空

一、const_cast把常量轉爲變量

#include <iostream>

using namespace std;

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

    int *ptr = (int*)(&a);//c語言轉換(在c語言能夠這樣寫:int *ptr=&a,只是會警告,同樣能夠操做,c++不容許)
    *ptr = 1;
    cout<<a<<endl;
    cout<<*ptr<<endl;

    int &ra = const_cast<int&>(a);
    ra = 2;
    cout<<a<<endl;
    cout<<ra<<endl;

    int *x = const_cast<int*>(p);
    *x = 3;
    cout<<a<<endl;
    cout<<*x<<endl;

    return 0;
}

輸出結果:
10
1
10
2
10
3
解釋:由於a是const修飾的,此時a的值會存在符號表中,也就是改變a地址所指向的值,也不會改變a的值,當調用a的時候,編譯器回到符號表中取值,而不是從a的地址取值。

(1)爲什麼要去除const限定
緣由(1)是,咱們可能調用了一個參數不是const的函數,而咱們要傳進去的實際參數確實const的,可是咱們知道這個函數是不會對參數作修改的。因而咱們就須要使用const_cast去除const限定,以便函數可以接受這個實際參數。
例如:

#include <iostream>
using namespace std;

void Printer (int* val,string seperator = "\n")
{
        cout << val<< seperator;
}

int main(void) 
{       
        const int consatant = 20;
        //Printer(consatant);//Error: invalid conversion from 'int' to 'int*'
        Printer(const_cast<int *>(&consatant));
        
        return 0;
}

緣由(2):
還有一種我能想到的緣由,是出如今const對象想調用自身的非const方法的時候,由於在類定義中,const也能夠做爲函數重載的一個標示符。

二、static_cast靜態轉化

static_cast < type-id > ( expression )該運算符把expression轉換爲type-id類型,但沒有運行時類型檢查來保證轉換的安全性。它主要有以下幾種用法:

  • ①用於類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換,不容許不相關的類進行轉換。
    進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
    進行下行轉換(把基類指針或引用轉換成派生類表示)時,因爲沒有動態類型檢查,因此是不安全的。
  • ②用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
  • ③把空指針轉換成目標類型的空指針。
  • ④把任何類型的表達式轉換成void類型。

注意: static_cast不能轉換掉expression的const、volatile、或者__unaligned屬性

例如

#include <iostream>
using namespace std;
int main()
{
    char a = 'a';
    int b = (int)a;
    double g = static_cast<int>(a);

    //爲何不能轉換普通類型指針,卻能轉換對象指針和void指針(規定的)
    void *pp;
    double *pp1 = static_cast <double*>(pp);
    int *xx;
    void *xx1 = static_cast <void*>(xx);
    //double *xx2 = static_cast <double*>(xx);//錯誤寫法
    return 0;
}

三、reinterpret_cast強制類型轉換符

reinterpret_cast (expression)
type-id 必須是一個指針、引用、算術類型、函數指針或者成員指針。它能夠把一個指針轉換成一個整數,也能夠把一個整數轉換成一個指針(先把一個指針轉換成一個整數,再把該整數轉換成原類型的指針,還能夠獲得原先的指針值)。

  • reinterpret_cast能夠轉換任意一個32bit整數,包括全部的指針和整數。能夠把任何整數轉成指針,也能夠把任何指針轉成整數,以及把指針轉化爲任意類型的指針。但不能將非32bit的實例轉成指針。總之,只要是32bit的東東,怎麼轉都行!
  • 由於任何指針能夠被轉換到void,而void能夠被向後轉換到任何指針(對於static_cast<> 和 reinterpret_cast<>轉換均可以這樣作),若是沒有當心處理的話錯誤可能發生。

例如1:

#include <iostream>
using namespace std;
class A {
public:
    int m_a;
};
class B {
public:
    int m_b;
};
class C : public A, public B {};
int main()
{
    int n= 1231651 ;
    double *d;
    cout << d << endl;
    d=reinterpret_cast<double*> (&n);
    //爲何d和n的地址同樣但爲何地址裏面的值不同?
    //是由於double類型數據存儲的方式不同,用*d訪問時,
    //系統會以讀取double類型數據來讀取。
    cout << d << "  " << &n << endl;
    cout << *d << "  " << n << endl;
    cout << "---------------------------\n";

    //將一個32位的整數轉換成一個指針
    char *n_p = reinterpret_cast<char*>(10);
    
    //reinterpret_cast和static_cast的主要區別在於多繼承
    C c;
    printf("%p, %p, %p", &c, reinterpret_cast<B*>(&c), static_cast <B*>(&c));//前兩個的輸出值是相同的,最後一個則會在原基礎上偏移4個字節,這是由於static_cast計算了父子類指針轉換的偏移量,並將之轉換到正確的地址(c裏面有m_a,m_b,轉換爲B*指針後指到m_b處),而reinterpret_cast卻不會作這一層轉換。

    return 0;
}

結果:

例如2:

 //強制類型轉換//編寫一個程序跳轉到地址0x12345678運行
    typedef void(*Fun)(void);//定義一個函數指針數據類型
    Fun fun = reinterpret_cast<Fun>( 0x12345678 );
    fun();

四、dynamic_cast類轉換

dynamic_cast < type-id > ( expression )
說明: 該運算符把expression轉換成type-id類型的對象。Type-id必須是類的指針、類的引用或者void ;若是type-id是類指針類型,那麼expression也必須是一個指針,若是type-id是一個引用,那麼expression也必須是一個引用。
使用場景: dynamic_cast主要用於類層次間的上行轉換和下行轉換,還能夠用於類之間的交叉轉換。在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是同樣的;在進行下行轉換時,dynamic_cast具備類型檢查的功能,比static_cast更安全。
注意:
① dynamic_cast是動態轉換,只有在基類指針轉換爲子類指針時纔有意義。
② dynamic_cast<>須要類成爲多態,即包括「虛」函數,並所以而不能成爲void*。 
③ static_cast和dynamic_cast能夠執行指針到指針的轉換,或實例自己到實例自己的轉換,但不能在實例和指針之間轉換。static_cast只能提供編譯時的類型安全,而dynamic_cast能夠提供運行時類型安全。
例如:

//dynamic---用於繼承過程當中把父類指針轉換爲子類指針
#include <iostream>

using namespace std;
class A
{
public:
    A() {}
    virtual ~A(){}
};
class B:public A
{
public:
    B() {}
    ~B(){}
};
//調用
int main()
{
    A *p = new B(); //用戶子類指針初始化父類指針
    A *a = new A();//建立一個A類對象
    //如下兩句必須在基類有虛析構的狀況下才正確,不然編譯的時候報錯
    B *bptr = dynamic_cast<B*>(a);//把新的父類指針賦值子類指針(nullptr)
    B *bptr1 = dynamic_cast<B*>(p);//p是A類型指向B對象空間,把p轉回B類
    if(bptr1 == nullptr)//爲nullptr時轉換不成功
    {
        cout<<"fail"<<endl;
    }
    else {
        cout<<"success"<<endl;
    }
    return 0;
}

二12、異常捕捉和處理

在閱讀別人開發的項目中,也許你會常常看到了多處使用異常的代碼,也許你也不多碰見使用異常處理的代碼。那在何時該使用異常,又在何時不應使用異常呢?在學習完異常基本概念和語法以後,後面會有講解。

(1)異常拋出和捕捉語句

//1.拋出異常
throw  異常對象

//2.異常捕捉
try{
    可能會發生異常的代碼
}catch(異常對象){
    異常處理代碼
}
  • throw子句:throw 子句用於拋出異常,被拋出的異常能夠是C++的內置類型(例如: throw int(1);),也能夠是自定義類型。
  • try區段:這個區段中包含了可能發生異常的代碼,在發生了異常以後,須要經過throw拋出。
  • catch子句:每一個catch子句都表明着一種異常的處理。catch子句用於處理特定類型的異常。catch塊的參數推薦採用地址傳遞而不是值傳遞,不只能夠提升效率,還能夠利用對象的多態性。

(2)異常的處理規則

  • throw拋出的異常類型與catch抓取的異常類型要一致;
  • throw拋出的異常類型能夠是子類對象,catch能夠是父類對象;
  • catch塊的參數推薦採用地址傳遞而不是值傳遞,不只能夠提升效率,還能夠利用對象的多態性。另外,派生類的異常捕獲要放到父類異常撲獲的前面,不然,派生類的異常沒法被撲獲;
  • 若是使用catch參數中,使用基類捕獲派生類對象,必定要使用傳遞引用的方式,例如catch (exception &e);
  • 異常是經過拋出對象而引起的,該對象的類型決定了應該激活哪一個處理代碼;
  • 被選中的處理代碼是調用鏈中與該對象類型匹配且離拋出異常位置最近的那一個;
  • 在try的語句塊內聲明的變量在外部是不能夠訪問的,即便是在catch子句內也不能夠訪問;
  • 棧展開會沿着嵌套函數的調用鏈不斷查找,直到找到了已拋出的異常匹配的catch子句。若是拋出的異常一直沒有函數捕獲(catch),則會一直上傳到c++運行系統那裏,致使整個程序的終止。

(3)實例

  • 實例1:拋出自定義類型異常
class Data
{
public:
    Data() {}
};

void fun(int n)
{
    if(n==0)
        throw 0;//拋異常 int異常
    if(n==1)
        throw "error"; //拋字符串異常
    if(n==2)
    {
        Data data;
        throw data;
    }
    if(n>3)
    {
        throw  1.0;
    }
}

int main()
{
    try {
        fun(6);//當異常發生fun裏面,fun如下代碼就不會再執行,調到catch處執行異常處理代碼,後繼續執行catch之外的代碼。當throw拋出異常後,沒有catch捕捉,則整個程序會退出,不會執行整個程序的如下代碼
        cout<<"*************"<<endl;
    }catch (int  i) {
        cout<<i<<endl;
    }catch (const char *ptr)
    {
        cout<<ptr<<endl;
    }catch(Data &d)
    {
        cout<<"data"<<endl;
    }catch(...)//抓取 前面異常之外的全部其餘異常
    {
        cout<<"all"<<endl;
    }
    return 0;
}
  • 實例2:標準出錯類拋出和捕捉異常
#include <iostream>
using namespace std;

int main()
{
    try {
        char* p = new char[0x7fffffff];  //拋出異常
    }
    catch (exception &e){
        cout << e.what() << endl;   //捕獲異常,而後程序結束
    }
    return 0;
}

輸出結果:
當使用new進行開空間時,申請內存失敗,系統就會拋出異常,不用用戶自定義異常類型,此時捕獲到異常時,就可告訴使用者是哪裏的錯誤,便於修改。

  • 實例3:繼承標準出錯類的派生類的異常拋出和捕捉
#include <iostream>
#include <exception>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
class FileException :public exception
{
public:
    FileException(string msg) {
        this->exStr = msg;
    }
    virtual const char*what()  const noexcept//聲明這個函數不能再拋異常
    {
         return this->exStr.c_str();
    }
protected:
    string exStr;
};

void fun()
{
    int fd = ::open("./open.txt",O_RDWR);

    if(fd<0)
    {
        FileException openFail("open fail"); //建立異常對象
        throw  openFail;//拋異常
    }
}

int main( )
{
    try {
        fun();
    } catch (exception &e) {//通常須要使用引用
        cout<<e.what()<<endl;
    }
    cout<<"end"<<endl;
    return 0;
}

當文件不存在時,輸出結果:

若是在Linux上運行,上述代碼須要根據環境修改:
98標準寫法

~FileException()throw(){}//必需要
virtual const char*what()  const throw()//聲明這個函數不能再拋異常
{
     return this->exStr.c_str();
 }
 //編譯
g++ main.cpp

2011標準寫法

~FileException()noexcept{}//必需要
virtual const char*what()  const noexcept//聲明這個函數不能再拋異常
{
     return this->exStr.c_str();
}
 //編譯
g++ main.cpp -std=c++11   指定用c++11標準編譯

(4)總結

1. 使用異常處理的優勢:

  • 傳統錯誤處理技術,檢查到一個錯誤,只會返回退出碼或者終止程序等等,咱們只知道有錯誤,但不能更清楚知道是哪一種錯誤。使用異常,把錯誤和處理分開來,由庫函數拋出異常,由調用者捕獲這個異常,調用者就能夠知道程序函數庫調用出現的錯誤是什麼錯誤,並去處理,而是否終止程序就把握在調用者手裏了。

2. 使用異常的缺點:

  • 若是使用異常,光憑查看代碼是很難評估程序的控制流:函數返回點可能在你意料以外,這就致使了代碼管理和調試的困難。啓動異常使得生成的二進制文件體積變大,延長了編譯時間,還可能會增長地址空間的壓力。
  • C++沒有垃圾回收機制,資源須要本身管理。有了異常很是容易致使內存泄漏、死鎖等異常安全問題。 這個須要使用RAII來處理資源的管理問題。學習成本較高。
  • C++標準庫的異常體系定義得很差,致使你們各自定義各自的異常體系,很是的混亂。

3. 何時使用異常?

  • 建議:除非已有的項目或底層庫中使用了異常,要否則儘可能不要使用異常,雖然提供了方便,可是開銷也大。

4. 程序全部的異常均可以catch到嗎?

  • 並不是如此,只有發生異常,而且又拋出異常的狀況才能被catch到。例如,數組下標訪問越界的狀況,系統是不會自身拋出異常的,因此咱們不管怎麼catch都是無效的;在這種狀況,咱們須要自定義拋出類型,判斷數組下標是否越界,而後再根據自身須要throw自定義異常對象,這樣才能夠catch到異常,並進行進一步處理。

二十3、STL標準模板庫

容器:

  • vector---順序存儲---順序表 (訪問遍歷查詢)
  • list ------鏈式存儲 ----鏈表  (適合數據長度不肯定, 常常改變)
  • map ----鍵值對存儲 (key:value)       (適合數據成對存儲)
  • set ------容器-----------------------(存儲數據是惟一的)

一、vector(順序表)

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    //建立vector對象
    //大小變化:1024  2048  4096(開始會以2倍數增長,後面慢慢以1/三、1/5等的形式增長)
    vector<string> names;
    //賦值,3個jack
    names.assign(3,"Jack"); //Jack, Jack, Jack

    //插入數據
    //建立一個迭代器
    vector<string>::iterator it = names.begin();
    //insert以後迭代器it已經改變,返回值爲插入值的位置
    it = names.insert(++it, "Rose"); //結果:Jack Rose Jack Jack
    it = names.insert(++it,"Jim");//結果:Jack Rose Jim Jack Jack
    it = names.insert(++it, "lcg"); //結果:Jack Rose Jim lcg Jack Jack
    it = names.insert(names.end(), "Jack"); //結果:Jack Rose Jim lcg Jack Jack Jack

    //查詢數據/刪除數據---迭代器遍歷使用vector, list, map, set
    //names.end()爲順序表最後一個元素的下一個地址
    for(it = names.begin(); it != names.end(); ++it)
    {
        if(*it == "Jack")
        {
            cout<<*it<<" ";

            //擦除,返回擦出元素的下一個位置
            //例如a b c,刪除b後,返回迭代器指向c
            it=names.erase(it);
            --it;
        }
    }
    cout<<endl;

    //遍歷---順序表
    for(int i=0; i<names.size(); i++)
    {
        cout<<names[i]<<" ";
    }
    cout<<endl;

    return 0;
}
相關文章
相關標籤/搜索