C++學習記錄(五)——C++類模型初探

類的概念

從概念上來講:類是現實中具備共同特色食物的抽象。程序員

這裏舉一個簡單的例子:咱們如今須要定義的一我的的類。因此咱們要找出人和人的共同點(或者說人這個物種的共性),人有身高,體重,年齡,性別...這些就是人這個類的屬性(成員變量)。人還會吃飯,走路,跳躍...這些就是人的方法(成員函數)。這樣就能夠抽象出人這個類。編程

類的定義和實現

當咱們抽象出人這個類以後咱們具體要怎麼實現這個類呢?
請看以下語句數組

class Person
{
...
};

以上就是類 的基本語法:class 類的名字 {...}; 這裏大括號最後有分號。
咱們在大括號裏面寫成員變量和成員方法。函數

類的成員函數和成員變量

class Person
{
public:
    int age;//人的年齡
    void eat()//人吃飯的成員函數
    {
        std::cout<<"人在吃飯"<<std::endl;
    }
    void run()//人跑步的成員函數
    {
        std::cout<<"人在跑步"<<std::endl;
    }
};

咱們也能夠在類外寫函數定義,類內只寫函數聲明。不過這時咱們須要用到做用域標識符(::)性能

class Person
{
public:
    int age;//人的年齡
    bool sex;//人的性別
    void eat();//人吃飯的成員函數
    void run();//人跑步的成員函數
};
void Person::eat()//人吃飯的成員函數
{
    std::cout<<"人在吃飯"<<std::endl;
}
void Person::run()
{
    std::cout<<"人在跑步"<<std::endl;
}

在這裏須要強調的是,定義位於類內的函數自動成爲內聯函數。若是但願類外定義的函數也能夠內聯的話,須要手動加inline關鍵字。優化

類對象的初始化

這裏和結構體很像
Person xiaoming;
這裏p就是Person的對象,他具備Person的屬性和方法。簡單的來講,Person至關於人這個物種,而p這個對象就是具體到誰。
就像結構體那樣:this

xiaoming.run();
xiaoming.eat();

在這裏所建立的每個對象都有一個本身的存儲空間,用於存儲本身的成員變量和成員函數。可是多有對象都共享同一個類。debug

類成員的訪問權限

這裏先簡單的瞭解一下,以後會在繼承中詳細指出。(友元能夠暫時無視)
private: 只能由該類中的函數、其友元函數訪問,不能被任何其餘訪問,該類的對象也不能訪問. 指針

protected: 能夠被該類中的函數、子類的函數、以及其友元函數訪問,但不能被該類的對象訪問 code

public: 能夠被該類中的函數、子類的函數、其友元函數訪問,也能夠由該類的對象訪問

注意:這裏要說明一個更好的方式來進行編程。把成員變量所有寫進private中,來實現數據的封裝。使用public的get set方法來得到和修改這個變量。

構造函數和析構函數

C++和Java不同,其中一個重要的區別就是:C++編譯器強制要求程序員進行內存管理,而Java的內存管理是由JVM(Java虛擬機)代替程序員完成。因此,這裏引出一個重要的概念(和C語言同樣):內存管理對於C/C++開發者來講是一個永恆的話題

構造函數

  • 構造函數必須寫在public下。
  • 構造函數沒有類型,也沒有返回值(不要多此一舉寫void)
  • 構造函數的函數名必須和類名一致。
  • 構造函數能夠重載。(請先無視掉重載,在多態的部分會詳細介紹)
  • 構造函數是由編譯器自動調用,並且只調用一次。
class Person
{
public:
    Person()
    {
        std::cout<<"我是構造函數"<<std::endl;
    }
};
構造函數的分類
  • 有參構造函數
  • 無參構造函數(默認構造函數)
class Person
{
public:
    Person()
    {
        cout<<"默認構造函數"<<endl;
    }    
    Person(int a)
    {
        age = a;
        cout<<"有參構造函數"<<endl;
    }
};

通常狀況,編譯器會提供默認的構造函數,若是程序員手動寫了有參的構造函數,那麼編譯器便不會提供默認的構造函數。

void test()
{
    //構造函數調用方式
    //括號法調用
    Person p1(1);
    p1.age = 10;
    Person p2(p1);
     
    cout<<"p2年齡:"<<p2.Age<<endl;
    Person p3;//默認構造函數不加(); Person p3() 編譯器任務這行是函數聲明
    
    //顯示法調用
    Person p4 = Person(100);//Person(100)叫匿名對象
    Person p5 = Person(p4);
    Person(100);//若是編譯器發現了匿名對象,那麼在這行代碼結束後,就釋放這個對象
}

咱們能夠經過以上方法對構造函數進行調用。
注意:上述語句中出現匿名對象。顧名思義,匿名對象就是沒有名字,這種對象在這行代碼結束後,就釋放這個對象。(匿名對象未來會有伏筆,如今請記住!)

【補充】初始化列表:這裏是有參構造函數的一種寫法:

class foo 
{ 
public: 
    foo(string s, int i):name(s), id(i){} ; // 初始化列表 
    //相應的含義:把s的值賦值給name,把i的值賦值給id
private: 
    string name ;
    int id ; 
};

使用初始化列表的緣由,主要是由於性能。對於內置類型,如int, float等,使用初始化類表和在構造函數體內初始化差異不是很大,可是對於類類型來講,最好使用初始化列表,使用初始化列表少了一次調用默認構造函數的過程,這對於數據密集型的類來講,是很是高效的。因此,咱們能使用初始化列表的時候儘量使用初始化列表。
固然,有的時候咱們也必須使用初始化列表:

  • 常量成員,由於常量只能初始化不能賦值,因此必須放在初始化列表裏面
  • 引用類型,引用必須在定義的時候初始化,而且不能從新賦值,因此也要寫在初始化列表裏面
  • 沒有默認構造函數的類類型,由於使用初始化列表能夠沒必要調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化
有關拷貝構造函數
class Person
{
public:
     Person(const Person& p)
    {
        Age = p.Age;
        cout<<"拷貝構造函數"<<endl;
    }
};

所謂拷貝構造函數就是把構造函數複製了一份

有關拷貝構造函數的調用時機:

  • 用已經建立好的的對象初始化新的對象
  • 用值傳遞的方式給函數參數傳值
  • 用值的方式返回局部對象
class Person
{
public:
    Person()
    {
        cout<<"默認構造函數調用"<<endl;
    }
    Person(int a)
    {
        cout<<"有參構造函數調用"<<endl;
    }
    Person(const Person& p)
    {
        cout<<"拷貝構造函數調用"<<endl;
    }  
    ~Person()
    {
        cout<<"析構函數調用"<<endl;
    } 
    int Age;
};
//用已經建立好的的對象初始化新的對象
void test()
{
    Person p1 ;
    p1.Age = 10;
    
    Person(p1);
}
//用值傳遞的方式給函數參數傳值
void doWork(Person p1)
{
    
}

void test02()
{
    Person p;
    p.Age;
    
    doWork(p);
}

//用值的方式返回局部對象
Person doWork2()
{
    Person p1;
    return p1;
}
void test03()
{
    Person p = doWork();
}

//請注意debug模式下和release模式下的區別
//Release模式下的優化:
/*(節省一份開銷)
Person  p;//不調用默認構造
doWork2(p);
void doWork2(Person &p)
{
    Person p1;//調用默認構造
}

~析構函數

  • 析構函數必須寫在public下
  • 析構函數不能有返回值
  • 析構函數不能有參數
  • 析構函數的函數名與類名一致前面有一個~
  • 析構函數由編譯器自動調用,且只調用一次
class Person
{
public:
    ~Person()
    {
        std::cout<<"我是析構函數"<<std::endl;
    }
};

析構函數的做用其實是作垃圾回收的。具體的內容再後面的內存管理部分會詳細說明。

總結

這裏只想說明一下編譯器會爲程序員默認的提供:無參構造函數,析構函數,拷貝構造函數,重載了賦值運算符(爲知識完整性不得不說的,後面會細講)

this指針

this指針的概念

C++的數據和操做是分開存儲的,而且每個非內聯函數成員只會誕生出一份函數實例,即多人同類型的對象公用一塊代碼
這一塊代碼是如何區分是哪一個對象調用本身呢?
引入this指針,this指針是指向被調用的成員函數所屬的對象
C++規定,this指針是隱含在對象成員函數內的一種指針。當對象被建立後,它的每個成員函數都含有一個系統自動生成的隱含指針this,用來保存這個對象的地址。雖然咱們 沒有寫上this指針,編譯器在編譯的時候會自動加上。this也稱指向本對象的指針。this指針並非對象的一部分,也不會影響sizeof(對象)的結果。
this指針是C++實現封裝的一種機制,他將對象和該對象調用的成員函數鏈接在一塊兒。從外部看來,每個對象都擁有本身的成員函數。通常狀況下,並不寫this,而是讓系統系進行默認配置。
this指針永遠指向當前對象。this指針是一種隱含的指針,它隱含於每個非靜態的成員函數中

this指針的使用

class Person
{
public:
    Person(int age)
    {
        this->age = age;//編譯器無法區分這幾個age
        //區分有兩種辦法:1.換名字  2.this指針
    }    
     //對比年齡
     void compareAge(Person &p)
     {
         if(this->age = p.age)//默認給加了this,不寫也行
         {
             cout<< "年齡相等"<<endl;
         }
         else
         {
             cout<<"年齡不等"<<endl;
         }
     } 
     //年齡相加
     Person& PlusAge(Person &p)//在這裏若是去掉&,它將從一個引用傳遞變成值傳遞
     {
         this->age += p.age;
         return *this;//指向本體
     }
    int age;  
}

上面的語句不難理解,簡單來講就是爲了區分名字。可是最後Person& PlusAge(Person &p)可能不少人感受奇怪,這實際上是this指針的第二個用途——鏈式編程。

p1.PlusAge(p2).PlusAge(p2).PlusAge(p2);//鏈式編程
    //若是變成值傳遞,每一次調用都是一個臨時的空間裏進行操做。並不會鏈式相加。這取決於你的需求

這種寫法對於有Java基礎的人來講必定不陌生。

補充const修飾成員函數

class Person
{
public:
    Person()
    {
        //構造中修改屬性
        //this永遠指向本體 至關於 Person * const this
        //雖然指針不能修改,可是能夠修改指針指向的值。
        //若是我不但願修改指針指向的值,則 const Person * const this
        this->A = 0;
        this->B = 0;
    }  
    
    //常函數
    void showInfo() const //不容許修改指針指向的值
    {    
        //this->C = 1000;
        // const Person * const this如何操做?,在函數()後面+const
        cout<<"A是"<<this->A<<endl;
        cout<<"B是"<<this->B<<endl;
        cout<<"C是"<<this->C<<endl;
    }  
    int A;
    int B;
    //若有即使是常函數也要修改的需求,可使用關鍵字mutable
    mutable int C;
}

固然,若是你真的理解了this指針的原理,則還能夠以上這種C++風格的代碼改寫成C風格

//C++風格
void showInfo() const
{
...
}
//C風格
void showInfo(cosnt Person * this)
{
...
}

若是這樣改寫的話,在調用時也要改寫

//C++風格
p.showInfo();
//C風格
p.showInfo(&p);

對象數組

有關對象數組,也是顧名思義,就是一個元素是對象的數組
Person xiaoxue[4];
這裏只有須要注意的地方:要建立的對象數組,則這個類必須有默認構造函數
若是但願使用構造函數來初始化對象數組,則:

Person p[2] = {
    Person(10),//10歲
    Person(12)//12歲
};
//也可使用不一樣的構造函數
Person p[2] = {
    Person(10),//10歲
    Person()//使用默認構造函數
};

類的做用域

概念

咱們以前就知道全局做用域和局部做用域。在這裏C++引入新的做用域——類做用域
類中定義的名稱的做用域是整個類,做用域爲整個類的名稱只有在該類的做用域中是已知的,類外是不可知的。因此,不一樣類使用相同的名字並不會衝突。
若是是類外的話,咱們可使用直接成員操做符(.),間接成員操做符(->),做用域操做符(::)來訪問類中的public方法。前提是應該使用前包含類聲明的頭文件。

做用域是整個類的常量

或許會有人這樣去書寫代碼:

class Person
{
private:
    const int Len = 30; 
    char Arr[Len];
};

這是不可行的,但確是一個常見的誤區。
首先解釋一下爲何不可行:聲明類只是描述類的形式,並無真正的建立對象。所以,在對象建立以前並無可使用的存儲空間。
雖然上述的寫法是錯誤的,可是咱們可使用別的方法達到咱們的目的。

class Person
{
private:
    enum{Len = 30};//使用枚舉 
    char Arr[Len];
};

注意,這種方式聲明枚舉並不會建立數據成員。即全部對象並不包含枚舉。並且Len僅僅是一個符號,當在做用域爲整個類的代碼中遇到它時,編譯器將用30來替換。

至於第二種方法,咱們就要引入C++中一個很是重要的關鍵字 static。

static關鍵字
  • 靜態成員變量

在一個類中,若將一個成員變量聲明爲static,這種成員變量稱爲靜態成員變量。與通常的成員變量不一樣,不管創建多少個對象,都只有一個靜態數據的拷貝。靜態成員變量,屬於某個類,對全部對象共享(其中一個修改了,其餘的也都修改了)。
靜態變量,在編譯階段就分配空間,對象尚未建立時,就已經分配空間

class Person
{
public:
    Person()
    {
        //Age = 10;//雖然不會報錯,通常不會這麼去作。
    }
    static int Age;// 在類內聲明,在類外定義。會報錯!
    
    //靜態成員變量也是有權限的
private:
    static int other;//私有權限,類內類外不能訪問    
};
int Person::Age = 0;
int Person::other = 0;//仍然算類內訪問
  • 靜態成員函數
class Person
{
public:
    Person()
    {
        //Age = 10;
    }
    static int Age;
    //靜態成員函數
    //靜態成員函數不能夠訪問普通的成員變量
    //能夠訪問靜態成員變量
    static void func()
    {
        cout<<"func調用"<<endl;
    }
    //普通成員函數能夠訪問普通成員變量,也能夠訪問靜態的成員變量
   void MyFunc()
   {
       
   }
private:
    static int other; 
    //靜態成員函數也有權限
     static void func1()
    {
        cout<<"func1調用"<<endl;
    }
};
int Person::Age = 0;
int Person::other = 0;

在咱們瞭解了static關鍵字時,咱們就已經能夠解決剛剛的問題了:

class Person
{
private:
    static const int Len = 30; 
    char Arr[Len];
};

這裏將建立一個靜態的Len常量,這個常量將於其餘的靜態成員一塊兒存儲,而不是存儲在對象中。(之後還會有機會遇到static)

相關文章
相關標籤/搜索