1.封裝
封裝是c++面向對象的三大特性之一
? ? ? ? 將屬性和行為作為一個整體
? ? ? ? 將屬性和行為加以權限控制
語法:? ? ??
class 類名{ 訪問權限: 屬性/行為 };
訪問權限
public 公共權限 類內類外均可以訪問
protected 保護權限 類內可以訪問,類外不可以訪問
private 私有權限 類內可以訪問,類外不可以訪問
?保護權限和私有權限的區別:
????????保護權限兒子可以訪問父親的保護內容
????????私有權限兒子不可以訪問父親的私有內容
note:在c++中
struct默認的訪問區別為公有
class默認權限為私有
在開發中我們大部分將變量設置為私有,自己控制讀寫權限
而外部訪問我們定義一些公有函數作為接口即可
對象的初始化
構造函數和析構函數
在編譯過程中,如果我們不提供構造和析構函數,編譯器會提供
但是編譯器提供的構造函數和析構函數是空實現
均寫在public作用域下
構造函數
類名(){}
特點
- 沒有返回值
- 函數名稱和類名相同
- 構造函數可以有參數,可以發生重載
- 無需手動調用構造函數,只會在調用對象的時候自動調用構造函數一次? ? ? ??
?析構函數
~類名(){}
特點
- 沒有返回值
- 函數名稱與類名相同,在名稱前加上~
- 析構函數不可以存在參數,不可以重載
- 程序在對象銷毀前自動調用,無需手動調用,而且只會調用一次
析構函數在拷貝構造中先進后出?
構造函數的分類和調用
按照參數分類:無參構造,有參構造
按照類型分類:普通構造,拷貝構造
拷貝構造傳入 const 類名 &p
將對象作為參數傳入,而傳入參數不是對象的稱為普通構造
拷貝函數
如果用戶定義了有參構造函數,C++不在提供默認無參構造函數,但是會提供默認拷貝構造
如果用戶定義了拷貝構造函數,C++不提供其他構造函數
淺拷貝:簡單的賦值拷貝:問題是內存的重復釋放
如果在類中定義了指針,并申請內存,在析構函數中釋放內存,那么由于淺拷貝是賦值拷貝,就會出現清理兩次相同地址的內容的情況
深拷貝:
在拷貝構造函數中定義新的內存
{變量 = new int (*指針)
}
初始化列表
寫在類中,另外一種初始化方法
語法:構造函數(類型 變量,類型 變量): 屬性1(變量1), 屬性2(變量2) 。。。{}
類對象作為類成員
編譯器會先構造其他類的對象,然后再構造自己的類的對象
靜態成員
在成員變量或者成員函數前加上static,稱為靜態成員
1.靜態成員變量
- 所有對象共享一份數據
- 在編譯階段分配內存(全局區)
- 類內聲明,類外初始化
class Person{static int text;
};//類內聲明int Person::text = 1;
//類外初始化
?靜態成員變量訪問方式
- 通過對象進行訪問
- 通過類名進行訪問
Person::text
?2.靜態成員函數
- 所有對象共享同一個函數
- 靜態成員函數只能訪問靜態成員變量
成員變量和成員函數
空對象占用1字節內存空間,為了區分空對象占內存的位置
成員變量和成員函數分開存儲
靜態成員變量不屬于類的對象
非靜態成員變量屬于類的對象
成員函數不屬于類的對象
所以占用類的存儲的只有非靜態成員變量
this指針
當存在很多對象使用成員函數時,通過this指針解決對象區分問題,即知道是哪一個對象調用的自己
this指針指向被調用的成員函數所屬對象
無需定義,可直接使用
用途
- 當形參和成員變量同名時,使用this指針來區分
- 在類的非靜態成員函數中返回對象本身,使用return *this
使用值的方式返回會創建一個新的對象
使用引用的方式返回返回的時原來的對象
//值返回
Person add()
{this->age++;return *this
}//引用方式
Person &add()
{this->age++;return *this
}
空指針訪問成員函數
空指針也可以調用成員函數
但是不可以調用成員變量,因為在函數中默認為this ->成員變量
所以一般在函數前加上
if(this == NULL){return ;}
const修飾成員函數
常函數:成員函數加const我們稱這個函數為常函數
- 常函數內不可以修改成員屬性
- 成員屬性聲明的時候加關鍵字mutable后,在常函數依然可以修改
常對象:聲明對象前加const
note:常對象只能調用常函數
友元
有些私有屬性也想讓類外特殊的一些函數或者類訪問,就需要用到友元
目的在于讓一個函數或者一個類訪問另一個類中的私有成員
關鍵字為friend
三種實現:聲明在類內進行
- 全局函數作為友元
- 類做友元
- 成員函數做友元?
全局函數做友元
friend 返回值類型 函數名(函數參數)
類做友元
friend class 類名
類的成員函數作為友元
friend 函數返回值 友元類名::類函數名();
運算符重載
最已有的運算符重新定義,賦予其另一種功能,以適應不同的函數類型
加號運算符重載
實現兩個自定義數據類型的相加
可以通過成員函數重載,也可以通過全局函數重載
成員函數重載
全局函數重載
左移運算符重載
如果利用成員函數重載左移運算符,無法實現 cout在前面
所以只能利用全局函數重載左移運算符
#include<iostream>using namespace std;class Person {
public:int age;int money;
};ostream& operator<<(ostream& cout, Person& p) {cout << "p_age = " << p.age << endl;cout << "p_money = " << p.money << endl;return cout;
}int main() {Person p;p.age = 1;p.money = 0;cout << p << endl;system("pause");
}
遞增運算符重載
自定義整形變量
#include<iostream>using namespace std;class myint {friend ostream& operator<<(ostream& cout, myint my_int);friend myint& operator++(myint& my_int);
public:myint() {number = 0;}
private:int number;
};ostream& operator<<(ostream& cout, myint my_int) {cout << my_int.number;return cout;
}
前置遞增
//重載++運算符
myint & operator++(myint &my_int) {my_int.number++;return my_int;
}
后置遞增
//重載++運算符
myint& operator++(myint& my_int,int) {//int 代表占位參數,可以用于區分前置和后置遞增//先記錄當前結果myint temp = my_int;//然后返回my_int.number++;//最后遞增return temp;}
賦值運算符重載
c++默認給一個類添加賦值運算符對屬性進行值拷貝
但是在析構函數釋放空間時會出現問題
Person & operator=(Person &p1,Person &p2){//先判斷是都有屬性在堆區,如果有釋放干凈,然后再深拷貝if(m_age != NULL){delete m_age;m_age = NULL;}m_age= new int (*p2.m_age);return p1;}
關系運算符重載和上面的相差不大,就不做介紹了
繼承
有些類與類之間存在特殊關系,定義類的時候,下級別的成員除了擁有上一級的共性,還有自己的特性,那么這個時候我們就可以考慮到繼承的技術,目的在于減少重復代碼
基本語法
class 子類名:繼承方式 父類名{};
note:子類也成為派生類,父類也稱為基類
繼承方式
- 公共繼承
- 保護繼承
- 私有繼承
繼承中的對象
問題:從父類繼承過來的成員,哪些屬于子類對象中
父類中的所有成員都會被子類繼承下去,私有成員屬性被編譯器隱藏了,訪問不到,但是被繼承下去了
繼承中構造和析構的順序
先構造父類,后構造子類
先析構子類,后析構父類
繼承中同名成員處理方式
當子類和父類中出現同名的成員
- 訪問子類同名成員,直接訪問即可
- 訪問父類同名成員,需要加上作用域
子類::父類.父類成員
note:如果子類中出現了和父類同名的成員函數,那么子類中的成員函數會隱藏所有父類的同名成員函數
多繼承語法
語法
class 子類: 繼承方式 父類1,繼承方式 父類2,...
note:多繼承可能會引發父類中有同名成員出現,需要加上作用域區分
菱形繼承
使用虛繼承來解決該問題
我們只需要在繼承的類(羊類和駝類)前面加上virtual即可解決此問題
多態
多態分為兩類
- 靜態多態
- 動態多態
函數重載和運算符重載屬于靜態多態,復用函數名
派生類和虛函數實現運行時多態(動態多態)
靜態多態函數地址早綁定 - 編譯階段確定函數地址
動態多態的函數地址晚綁定 - 運行階段確定函數地址
動態多態條件
- 子類中重寫父類虛函數
- 需要有繼承關系
動態多態使用
- 父類的指針或者引用執行子類對象,如:
void do(animal &animal){ animal.speak(); }Cat cat;
do(cat);//可以看到定義了傳入父類的函數,卻使用子類作為參數
純虛函數和抽象類
純虛函數語法
virtual 返回值類型 函數名 (參數列表) = 0;
當類中有了純虛函數,這個類也成為抽象類
抽象類特點
- 無法實例化對象
- 子類必須重寫抽象類中的純虛函數,否則也屬于抽象類
虛析構和純虛析構
多態使用時,如果子類有屬性開辟到堆區,那么父類指針在釋放的時候無法調用到子類的析構代碼
解決方法:將父類的析構函數改為虛析構或者純虛析構
虛析構:在父類的析構函數前加上virtual。解決釋放子類對象時不干凈的問題
純虛析構:有了純虛析構之后,該類也屬于純虛類
virtual ~父類名() = 0;
//下面要寫實現