本篇要學習的內容和知識結構概覽程序員
將一組對象的共同特徵抽象出來, 從而造成類的概念.編程
類包括數據成員和成員函數, 不能在類的聲明中對數據成員進行初始化數組
聲明類微信
形式爲:函數
class 類名 { private: 私有數據和函數 public: 公有數據和函數 protected: 受保護的數據和函數 }; // 注意分號
不管是數據成員仍是成員函數, 都是這個類的成員, 都具備一個訪問權限, 若是沒有關鍵字進行修飾, 則默認爲private權限學習
聲明一個類, 像這樣:測試
// 聲明類 class Point { // 若是沒有修飾符, 默認爲私有的權限 double x; double y; public: // 無參構造函數 Point(); // 有參構造函數 Point(double a, double b); // 成員函數 void display(); };
定義成員函數優化
形式爲:this
// :: 爲做用域運算符, 表示這個函數屬於哪一個類spa
返回類型 類名::成員函數名(參數列表) {
函數體 // 內部實現
}
咱們在上面的聲明類的代碼中, 聲明瞭成員函數, 咱們能夠在類外面定義成員函數, 也就是給出函數體
像這樣:
// 定義成員函數 // 無參構造函數 Point::Point() {} // 有參構造函數 Point::Point(double a, double b) { x = a; y = b; } // 可使用關鍵字inline將成員函數定義爲內聯函數 inline void Point::display() { cout << x << ", " << y << endl; }
若是在聲明類的同時, 在類體內給出成員函數的定義, 則默認爲內聯函數
咱們通常都是在類體內存給出成員函數的定義
像這樣, 完成一個類的聲明和定義
// 聲明類 class Point { double x; double y; public: // 定義成員函數 Point() {} Point(double a, double b) { x = a; y = b; } void display() { // 默認爲內聯函數 cout << x << ", " << y << endl; } };
不能在類體內和類體外對數據數據成員賦值
像這樣是不行的:
class Point { // 在類體內不能給數據成員賦值 double x = 2; double y = 3; } // 在類體外不能給數據成員賦值 // x = 4; // y = 5;
只有產生了具體對象, 這些數據值纔有意義
初始化: 在產生對象時就使對象的數據成員具備指定值, 則稱爲對象的初始化
賦值: 有了對象以後, 對象調用本身的成員函數實現賦值操做
類的成員函數能夠直接使用本身類的私有成員
類外面的函數不能直接訪問類的私有成員, 而只能經過類的對象使用公有成員函數
定義類對象指針的語法: 類名 * 對象指針名 = 對象地址;
經過對象指針能夠訪問對象的成員: 對象指針名 -> 對象成員名;
像這樣:
// 定義一個類 class Point { // 聲明數據成員 double x; double y; public: // 聲明而且定義成員函數 void setXY(double a, double b) { x = a; y = b; } // 一個類的成員函數能夠訪問本身的私有成員 void display() { cout << x << ", " << y << endl; } }; int main() { // 定義對象a Point a; // 定義b爲對象a的引用 Point & b = a; // 定義p爲指向對象a的指針 Point *p = &a; // 對象和引用, 都使用"."訪問對象的成員 a.setXY(2, 3); b.setXY(4, 5); // 指針使用"->"訪問對象的成員 p -> setXY(6, 7); }
一個類若是沒有定義任何構造函數, 編譯器會自動定義一個不帶參數的構造函數, 也就是默認構造函數
好比咱們有一個類Point
則默認構造函數就是這樣:Point::Point() {};
若是一個類提供了構造函數, 系統再也不提供默認構造函數
咱們有一個Point類, 像這樣:
class Point { double x; double y; public: Point(double a, double b) { x = a; y = b; } };
則咱們就不能在main函數中這樣使用:
int main() { // Point類有本身定義的構造函數Point(double, double), 因此就沒有默認構造函數Point() // 這句話調用的是無參構造函數來定義一個對象, 因此編譯錯誤 // 咱們須要給類Point加上無參構造函數 Point a; }
咱們想要這樣使用, 則必須手動添加無參數構造函數
像這樣:
class Point { double x; double y; public: // 無參構造函數 Point(){} // 有參構造函數 Point(double a, double b) { x = a; y = b; } ; int main() { // 咱們給類加上自定義無參構造函數, 如今正確編譯 Point a; }
構造函數的名字應該與類名同名, 並在定義構造函數時不能指定返回類型, void也不能夠
// 聲明一個類 class Point { double x; double y; public: // 聲明無參構造函數 Point(); // 聲明有參構造函數 Point(double, double); }; // 在類外定義構造函數 Point::Point(){} // 第一種定義構造函數 //Point::Point(double a, double b):x(a),y(b){} // 第二種定義構造函數 Point::Point(double a, double b) { x = a; y = b; } int main() { // 產生對象 Point a(2, 3); }
咱們通常都在類的聲明內部進行函數定義
像這樣:
// 定義一個類 class Point { // 聲明數據成員 double x; double y; public: // 無參構造函數 Point(){}; // 有參構造函數 Point(double a, double b) { x = a; y = b; } }; int main() { // 無參構造函數 產生對象a Point a; // 有參構造函數 產生對象b Point b(2, 3); // 無參構造函數 產生對象數組arr1 Point arr1[2]; // 有參構造函數 產生對象數組arr2 Point arr2[2] = {Point(2, 3), Point(4, 5)}; }
注意
不能在程序中顯式地調用構造函數, 構造函數是自動調用的
即不能這樣: Point a.Point(2, 3);
只能這樣: Point a(2, 3);
做用
用來在產生對象的同時, 進行對象的初始化
new用來創建生存期可控的動態對象, 返回這個對象的指針
new和構造函數一同起做用
過程: 當用new創建動態對象時, 首先分配能夠保存這個類對象的內存空間, 而後自動調用構造函數來初始化這塊內存, 再返回這個動態對象的地址
使用new創建的動態對象只能使用delete刪除, 以釋放所佔空間
像這樣:
// new與無參構造函數 Point * p1 = new Point; // new與有參構造函數 Point * p2 = new Point(2, 3);
若是咱們定義了有參構造函數, 又想使用無參構造函數, 咱們能夠將有參構造函數的參數所有使用默認參數
像這樣:
class Point { double x; double y; public: // 聲明構造函數 // 有參構造函數 Point(double a = 0, double b = 0) { x = a; y = b; } // 成員函數 void display() { cout << x << ", " << y << endl; } }; int main() { // 產生對象 Point a; a.display(); }
做用: 經過拷貝方式使用一個類的已有對象來創建一個該類的新對象, 通常編譯器會創建一個默認的複製構造函數
像這樣:類名(const 類名 &); // 爲了避免改變原有對象, 使用const來進行修飾
複製構造函數也能夠自定義, 則編譯器再也不調用默認的複製構造函數
像這樣:
// 定義一個類 class Point { // 聲明數據成員 double x; double y; public: // 無參構造函數 Point(){ cout << "默認構造函數" << endl; }; // 有參構造函數 Point(double a, double b) { x = a; y = b; cout << "構造函數 " << x << ", " << y << endl; } // 複製構造函數 Point(const Point & t) { x = t.x; y = t.y; cout << "複製構造函數" << endl; } }; int main() { // 使用默認構造函數產生一個對象 Point a; // 使用複製構造函數產生一個對象 Point b(a); }
使用複製構造函數的三種狀況
當用一個類的對象去初始化另外一個對象時, 須要調用複製構造函數
像這樣:
// 經過構造函數實例化對象 Point a(2, 3); // 經過構造函數實例化對象 Point b(a); // 調用成員函數 b.display();
若是函數的形參是類的對象, 調用函數時, 進行形參與實參的結合時, 須要調用複製構造函數
像這樣:
// 函數, 用來顯示一個Point對象 void printPoint(Point t) { // 當函數調用時形參t是經過複製構造函數來產生的對象 t.display(); } // 函數執行完畢後, 調用形參t的析構函數, 釋放內存 int main() { // 產生對象a Point a(2, 3); // 調用函數 printPoint(a); } // 函數執行完畢後, 調用對象a的析構函數, 釋放內存
若是函數的返回值是對象, 當函數調用完成返回時, 須要調用複製構造函數, 產生臨時對象, 並在執行完返回值語句後, 析構臨時對象
// 函數, 獲得一個Point對象 Point getPoint() { Point * t = new Point(2, 3); return *t; // 產生一個對象 } // 函數執行完畢後, 調用對象t的析構函數, 釋放內存 int main() { // 調用函數返回一個類對象, 這裏在接收函數返回的對象時會自動調用複製構造函數(不調用的是編譯器進行了優化) Point a = getPoint(); } // 函數執行完畢後, 調用對象a的析構函數, 釋放內存
函數參數使用對象的引用不產生副本, 因此當對象做爲函數參數時, 推薦使用對象引用這種方式
做用:在對象消失時, 使用析構函數釋放由構造函數分配的內存
爲了與構造函數區分, 在析構函數前加」~」號,
而且在定義析構函數時, 不能指定返回類型, 即便是void類型也不能夠;
也不能指定參數, 但能夠顯式的說明參數爲void
格式: ~類名(); // 或者 ~類名(void);
代碼像這樣:
~Point(); // 或者 ~Point(void);
析構函數在對象的生存期結束時自動調用, 而後對象佔用的內存被回收
全局對象和靜態對象的析構函數在程序運行結束以前調用
類對象的數組每一個元素調用一次析構函數
像這樣: 能夠運行該代碼, 查看程序執行過程
// 定義一個類 class Point { // 聲明數據成員 double x; double y; public: // 無參構造函數 Point(){ cout << "默認構造函數" << endl; }; // 有參構造函數 Point(double a, double b) { x = a; y = b; cout << "構造函數 " << x << ", " << y << endl; } // 複製構造函數 Point(const Point & t) { x = t.x; y = t.y; cout << "複製構造函數" << endl; } // 析構函數 ~ Point() { cout << "析構函數" << endl; } }; int main() { // 使用默認構造函數產生一個對象 Point a; // 調用默認構造函數, 產生新對象 // 使用複製構造函數產生一個對象 Point b(a); // 調用複製構造函數, 產生新對象 // 對象數組 Point arr[2]; // 調用默認構造函數 2次, 產生兩個新對象 } // 程序結束後, 由於總共產生了4個對象, 因此也會調用4次析構函數
當使用運算符delete刪除一個動態對象時, 首先爲這個對象調用析構函數, 而後再釋放這個動態對象佔用的內存
像這樣:
// 使用new和默認構造函數產生一個對象 Point * p = new Point; // 使用delete來釋放內存 delete p; // 使用new和默認構造函數產生一個對象數組, 數組有兩個對象 Point * p2 = new Point[2]; // 使用delete釋放數組內存 delete []p2;
若是沒有定義析構函數, 編譯器自動爲類產生一個函數體爲空的默認析構函數
像這樣:~ Point(){};
成員函數可重載或使用默認參數, 爲了提升可讀性
// 定義一個類 class MyMax { // 私有成員 int a, b, c, d; // 函數: 求兩個數的最大值 int getMax(int v1, int v2) { return v1 > v2 ? v1 : v2; } // 公有成員 public: // 函數: 改變數據成員的值 void setValue(int v1, int v2, int v3 = 0, int v4 = 0) { a = v1; b = v2; c = v3; d = v4; } // 函數: 得到全部數據成員裏的最大值 (函數重載) int getMax() { return getMax(getMax(a, b), getMax(c, d)); } }; int main() { // 產生對象 MyMax a; // 改變數據成員的值 a.setValue(1, 2, 3, 4); // 調用成員函數 cout << a.getMax() << endl; }
當一個成員函數被調用時, 系統自動向該函數傳遞一個隱含的參數, 指向調用該函數的對象指針, 名爲this, 從而使用成員函數知道該對哪一個對象進行操做.
做用: 它將對象和該對象調用的成員函數鏈接在一塊兒, 從外部看來, 每一個對象都擁有本身的成員函數, 但處理這些數據成員的代碼能夠被全部的對象共享
咱們通常狀況下都會省略this
// 定義一個類 class Point { double x; double y; public: // 有參構造函數 Point(double a = 0, double b = 0) { x = a; y = b; } // 成員函數 void display() { cout << x << ", " << y << endl; } // 僞代碼 // void setXY(double x, double y Point * this) { // this -> x = x; // this -> y = y; // } // 咱們能夠寫成這樣 // void setXY(double x, double y) { // this -> x = x; // this -> y = y; // } // 可是通常咱們都寫成這樣 void setXY(double x, double y) { x = x; y = y; } }; int main() { // 經過構造函數實例化對象 Point a(2, 3); // 調用成員函數 a.display(); }
由於類自己就是一種新的數據類型, 因此一個類的對象能夠做爲另外一個類的成員
像這樣:
// 類: Point, 包含兩個數據成員 class Point { double x; double y; public: // 有參構造函數 Point(double a = 0, double b = 0) { x = a; y = b; } // 成員函數 void display() { cout << x << ", " << y << endl; } }; // 類: Line, 包含兩個Point對象 class Line { // 兩個爲Point類的對象做爲數據成員 Point startPoint; Point endPoint; public: // 構造函數 Line(Point start, Point end) { startPoint = start; endPoint = end; } // 成員函數 void display() { cout << "起點: "; startPoint.display(); cout << "終點: "; endPoint.display(); } // 返回一個Point對象: 起點 Point getStartPoint() { return startPoint; } // 返回一個Point對象: 終點 Point getEndPoint() { return endPoint; } }; int main() { // 經過構造函數實例化對象 Point a(2, 3); Point b(4, 5); // 經過已有對象實例化另外一個對象 Line lineA(a, b); // 調用成員函數 lineA.display(); // 調用一個對象的成員函數, 返回另外一個對象 Point startPoint = lineA.getStartPoint(); startPoint.display(); }
同一類的對象之間能夠相互賦值
Point a(2, 3); Point b = a;複製代碼
可使用對象數組
Point arr[3];複製代碼
可使用指向對象的指針, 使用取地址運算符&將一個對象的地址賦值給該指針
Point p = &a;p -> display();複製代碼
對象做爲函數參數時, 可使用對象, 對象引用和對象指針三種方式, 推薦使用對象的引用做爲函數參數, 可使用const修飾符保證原來的對象不被修改
void print(Point a) {} // 對象做爲函數參數 void print(Point & a) {} // 對象引用做爲函數參數 (推薦使用這一種) void print(Point * p) {} // 對象指針做爲函數參數
一個對象能夠做爲另外一個類的成員
class Point {} class Line { Point startPoint; Point endPoint; }
使用類的權限
類自己的成員函數可使用類的全部成員(私有和公有和受保護的成員)
類的對象只能訪問公有成員函數
其它函數不能使用類的私有成員, 也不能使用公有成員函數
雖然一個類能夠包含另外一個類的對象, 但這個類也只能經過被包含的類對象使用成員函數, 再訪問數據成員
不徹底類的聲明
class People; // 不徹底的類聲明
People * p; // 定義一個全局變量類指針
只有使用類產生對象時, 才進行內存分配
不徹底類不能進行實例化, 不然編譯出錯, 咱們使用得不是不少
空類
class Empty {};
能夠不包括任何聲明, 也能夠沒有任何行爲, 但能夠產生空類對象
像這樣:
// 定義一個空類 class Empty { public: Empty(){}; }; int main() { // 產生空類對象 Empty e; }
做用: 在開發大型項目時, 須要在一些類尚未徹底定義或實現時進行先期測試, 保證代碼能正確地被編譯, 固然咱們有時也會給它一個無參構造函數, 來消除警告
類的做用域
聲明類時使用的一對花括號{}造成類的做用域, 也包括類體外成員函數的做用域.
在類做用域中聲明的標識符只在類中可見.
像這樣:
// 定義類 class Example { int number; public: void setValue(int value) { // number在類做用域在內部因此有效, 可使用 number = value; } void changValue(int); }; void Example::changValue(<#int#> value) { // 類的做用域也包括成員函數做用域, number有效 number = value; } //int a = number; // 錯誤, 由於這是在類做用域的外部, number無效 int number; // 正確, 這代碼定義了一個全局變量number
每一個語言的類和對象其實大同小異, 可能一些名字不同, 可能一些格式不同, 可是思想是同樣的, 例如一個對象的產生, 都得申請內存, 而後再對這塊內存進行初始化, 有本身的屬性, 還有本身的行爲. 咱們在學習的時候不要糾結於語言的自己, 要學會總結和本身已經學過的其它語言的異同點, 從而總結出規律, 提煉出本質, 這纔是最主要的. 今天看到一段話送給你們, 大概是這麼說的: 不是咱們變老了就當不了程序員了, 而是由於咱們不想學習了, 因此才顯得咱們變老了, 因此也就當不了程序員了!
自學C/C++編程難度很大,不妨和一些志同道合的小夥伴一塊兒學習成長!
C語言C++編程學習交流圈子,【點擊進入】微信公衆號:C語言編程學習基地
有一些源碼和資料分享,歡迎轉行也學習編程的夥伴,和你們一塊兒交流成長會比本身琢磨更快哦!