什麼是類
類 是 物件導向的基礎。c裡面是沒有對象的,只有資料,即靜態死物。
從面向過程升級到物件導向後,有了對象的概念,對象是資料與方法的合體,是動態活物。
類代表著一類事物的特徵。而對象,是類的具體化,執行個體化。
類的聲明與定義
一般來說,類的聲明在相應的標頭檔中,類的定義在相應的源檔案中。這樣實現了介面與實現的分離。
//Dog.h#ifndef _DOG_H_ #define _DOG_H_#include <string>#include <iostream>class Dog { //類的聲明 public: //公用成員Dog(){} //預設建構函式Dog(float weight) : weight(weight){ //可以隱式轉換 sex = true; }explicit Dog(bool isMale) : sex(isMale){}//explicit 修飾不可以隱式轉換Dog(float weight , bool sex);Dog(const Dog &dog) : weight(dog.weight) , sex(dog.sex) , name(dog.name) , power(dog.power){}; //複製建構函式 Dog& operator=(const Dog &dog){ //賦值函數 weight = dog.weight;sex = dog.sex; } ~Dog(){ //解構函式}void bellow();inline std::string toString(); // 類定義外部的inline方法,方法定義必須與類定義在同一個檔案float getWeight() const{ //類定義內部定義的方法,預設為inlinereturn weight;}void info(std::ostream& out);static int getNum() { //靜態成員函數return num;}//int t1 = 1; //error , 類定義中成員變數不能初始化。const static int t2 = 3; //只有 const static 類型才能在類定義中初始化。static int t3; //static 成員變數初始化寫在源檔案中。protected: //保護成員private: //私人成員float weight;bool sex;mutable int power; //mutable類型變數,在const 對象中依然可以修改。std::string name; static int num; //靜態成員變數};std::string Dog::toString(){ // 類定義外部的inline方法,方法定義必須與類定義在同一個檔案std::string str("Dog[name=");str+=name;str+=",weight=";str+=weight;str+=",sex=";str+=sex;str+="]";return str;}#endif
//Dog.cpp#include "Dog.h"#include <iostream>void Dog::bellow(){ //類方法的定義std::cout<<"wang wang wang"<<std::endl;}void Dog::info(std::ostream& out){out << "Dog[name=" << name <<",weight=" << weight <<",sex=" << sex <<"]" << std::endl;}//const int Dog::t2;//const static 成員定義。int Dog::t3 = 10;//static 成員初始化。類的執行個體化
Dog d(1.0f);
Dog *d2 = new Dog(1.0f);
類的初始化:建構函式
當建立一個類對象時,建構函式將會被調用。
如:
語句 Dog d(1.0f);
Dog *d2 = new Dog(1.0f);
會使編譯器調用 Dog(float w) : weight(w) {} 來初始化對象
建構函式分為3部分:函數簽名(函數名加形參列表),即
Dog(float w)
初始化列表 冒號與大括弧之間的部分為初始化列表。即
weight(w)
建構函式 即大括弧中的內容。
建構函式執行時,先執行初始化列表,然後再執行建構函式。
先執行初始化列表:
初始化列表的任務是 初始化類中的成員變數。順序是按照類中聲明的順序挨個初始化。
類類型成員初一定初始化,在初始化列表的,就用初始化列表中的初始化方法。不在初始化列表的,就調用類的預設建構函式。 從這裡可以看出,沒有預設建構函式的類類型,參考型別,const類型 必須在初始化列表。
內建類型和複合類型的不一定初始化,如果在初始化列表,就用初始化列表中的初始化方法。如果不在,就得看對象的位置,全域的初始化,局部的不初始化。
然後再執行建構函式
//playdog.cpp#include <iostream>#include <cstdlib>#include "Dog.h"Dog gloabDog;int main(){Dog localDog;gloabDog.info(std::cout);localDog.info(std::cout);return EXIT_SUCCESS;}
輸出:
由此,可以看出對象是局部變數的情況下,沒有出現在相應初始化列表的內建類型類成員沒有初始化。
複製建構函式與賦值函數
複製建構函式是一個特殊的建構函式。它只有一個參數,相同類對象的引用。
賦值函數是將相同類的對象賦值給對象。
Dog(const Dog &dog) : weight(dog.weight) , sex(dog.sex) , name(dog.name) , power(dog.power){}; //複製建構函式Dog& operator=(const Dog &dog){ //賦值函數 weight = dog.weight;sex = dog.sex;}
Dog d1;Dog d2(d1);//調用複製建構函式Dog d3 = d1;//調用複製建構函式Dog d4;d4 = d1;//調用賦值函數
賦值建構函式,賦值函數,如果不寫的話,編譯器會預設一個。系統預設的 就是將成員變數按照聲明的順序挨個複製一遍。
複製建構函式出現的地方:
1,上面的顯示調用。
2,對象作為方法參數傳入, 對象作為方法傳回值返回。
3,初始化容器
4,初始化數組元素
Dog d2(d1);//顯示調用std::string caculate(std::string str1 , std::string str2);//形參,傳回值std::vector<std::string> svec(5);//先調用string的預設建構函式產生一個string臨時對象,然後調用5次複製建構函式來初始化容器std::string[] strs = {//顯示調用建構函式產生臨時對象,然後調用複製建構函式來初始化數組元素string("hello"),string("word")}解構函式
解構函式是刪除對象時調用的方法,用來釋放對象佔用的資源。解構函式是無論是否編寫,編譯器都會預設一個解構函式。這個預設的解構函式就是按照成員變數聲明的順序的倒序挨個釋放資源,如果是類類型,就調用該對象的解構函式。
調用解構函式的時候,先調用使用者寫的,然後再調用編譯器預設的。
三法則
複製建構函式,賦值函數,解構函式, 這三個函數什麼時候需要寫呢?
一般來說,類中有指標成員變數,或者有需要特殊控制的資源,就需要寫。這是3個函數一定是同時需要寫,或同時不需要寫。
靜態成員
類中static 修飾靜態成員。靜態成員屬於類,不屬於任何一個對象。
所以 static方法中不能使用 this,不能訪問非靜態方法,非靜態變數。
靜態成員變數在類定義時初始化。定義在類定義中,初始化在類方法實現源檔案中。
c++ primer 中說 const static 成員在類定義中聲明初始化,但是還必須在類實現源檔案中在空初始化一次。
如: 類定義中 const static int t2 = 3; 但是還必須在Dog.cpp中 const int t2;
但在我的機器上,Dog.cpp中不加這一句仍然沒問題。
static 成員的調用
int main(){std::cout << Dog::t3 << std::endl;std::cout << Dog::t2 << std::endl;return EXIT_SUCCESS;}
類的封裝
這裡由類的成員訪問限定符(member access specifier)實現。
public 任意都可訪問。
private 只有本類可訪問
protected 不能被類外訪問(這點與私人成員類似),但可以被衍生類別的成員函數訪問。
預設內嵌函式
c++中,inline表示內嵌函式。在程式調用這些成員函數時,並不是真正地執行函數的調用過程(如保留返回地址等處理),而是把函數代碼嵌入程式的調用點。這樣可以大大減少調用成員函數的時間開銷。
C++要求對一般的內嵌函式要用關鍵字inline聲明,但對類內定義的成員函數,可以省略inline,因為這些類內定義成員函數已被隱含地指定為內嵌函式。
如 float getWeight() const 就預設為內嵌函式了。
不在類定義內定義的內嵌函式,必須在類定義同一個檔案中定義。
如 inline std::string toString(); 必須也在Dog.h 中定義,如果在Dog.cpp中定義,就會出現編譯錯誤。
建構函式隱式轉換
只有一個形參的建構函式可以參與隱式轉換。
如 因為有建構函式 Dog(float weight)
Dog d = 1.0f;
編譯器會把 float 類型 的 1.0f 通過 Dog(float weight) 隱式轉換成一個臨時的 Dog 類型對象,然後賦值給d.
explicit 修飾建構函式 可以禁止 隱式轉換。