C++類相關知識總結
1、類和物件
類就是對物件的描述,主要從屬性和行為兩個方面描述。
對於屬性一般作成private , 行為作為public
函式 (1)建構函式,初始化所有的成員變數,系統自動呼叫,可以過載
(2)解構函式,在物件生命週期結束的時候自動被呼叫呼叫,不準過載
建構函式和解構函式都是系統自動呼叫的,解構函式可以通過物件呼叫
A a;
a.A(); //error 建構函式是不能手工呼叫的
a.~A(); //right 手工呼叫解構函式時,會被當作一個普通的成員函式呼叫,其中的程式碼會被執行,物件不被銷燬
(3)get,set方法 用於訪問私有的成員變數的,外界訪問變數的唯一通道
(4)類本身的行為 是我們在編碼時要下功夫的地方
2、類的組成
(1)資料部分
(2)建構函式和解構函式
(3)get & set方法
(4)業務方法
3、棧的實現(儲存int型別的棧)
要想實現,需要一個數組做容器,儲存使用者插入的資料,用指標實現; int *p;
還需要一個變數,儲存棧頂位置 int top; //top從0開始
對於棧不能無限制的存資料,所以需要一個int型別的變數來記載陣列的最大長度 int max;
(1)建構函式
當不給引數的時候,棧的長度是預設值Stack();也可以使用者指定長度Stack(int)
(2)解構函式
一定要有,因為要在堆中申請空間
(3)為保護棧資料的安全,對p不能提供get,set 方法
對於top,max的值,取決於插入的資料,所有隻能有get方法,不能設定,不需要有set方法
(4)int push(int); 插入,當棧滿的時候,返回-1,表示插入失敗
int pop(); 刪除棧頂的元素,如果正確刪除一個數據,返回資料的值,若沒有正確刪除,返回-1,表示棧空了
void disp(); 現實資料
(5)實現的時候,注意:
在函式名前加 " Stack:: "
(6)在棧中插入資料,就是在top的位置插入資料
刪除資料,就是把棧頂下移一個位置
4、繼承
(1)繼承表現一種" is a " 的關係。Teacher extend Person
其中Teacher是子類,Person是父類
子類繼承父類的所有屬性和行為
(2)class Teacher:public Person{};
表示Teacher類繼承Person類
在extends_sample2中,子類可以直接使用父類的teach函式,但是不能使用父類private的name屬性。name屬性實際上存在在子類中,但是不能被子類直接使用,因為是private屬性。private屬性只能被本物件的函式訪問,public的屬性可以被子類直接使用,但是外部函式也可以訪問public許可權的屬性。既想被子類使用又不想讓外部函式訪問,怎麼辦哪?使用protected修飾屬性就可以了。
下面是一個類的屬性具有不同的訪問控制修飾符在不同的物件中的可見性i(可見性就是可以訪問):
本物件 子類物件 其他函式
private屬性 可見 不可見 不可見
protected屬性 可見 可見 不可見
public屬性 可見 可見 可見
在繼承關鍵字extends前面也有三種不同的訪問控制修飾符號,被不同的繼承方式繼承後訪問控制權限會發生變化。可以把繼承方式理解成城門。無論外邊的人多麼胖,想通過不同寬度的城門只能減肥到相應的寬度才可以。
public extends protected extends private extends
父類的private屬性 不能訪問 不能訪問 不能訪問
父類的protected屬性 變成protected 不變 變成private,子類可以訪問,子類的子類不能訪問
父類的public屬性 不變 變成protected 變成private,子類可以訪問,子類的子類不能訪問
建構函式不能被繼承。因為建構函式只是適合於本類的產生方式。
如extends_sample4,建立子類的時候需要首先建立父類。怎麼理解哪?
考慮子類的構成,就像cs中的匪徒用的46是由ak47新增上瞄準鏡組成的。
建立子類的時候會首先呼叫父類的建構函式,因為繼承的時候沒有指定繼承時使用的父類的建構函式。
建構函式有很多種,因為沒有指定建構函式,就會預設使用無參的建構函式。如果父類沒有無參的建構函式,那麼就會出現編譯錯誤。
這是問題的產生,如何解決哪?
可以在父類中新增無參建構函式。如果我們不是父類的設計者,就應該在子類繼承的時候指定使用父類的那個建構函式。
如在寫子類建構函式時,使用這種形式Teacher(char* name, int age, double salary):Person(name,age){......},就可以指定使用父類的有參建構函式。
構造時候是先父類後子類。解構函式哪?解構函式不能被繼承。子類釋放的時候,首先呼叫自身的解構函式,再呼叫父類的解構函式。這與建構函式的呼叫順序相反。
5、在子類中可以修改父類的行為,叫方法的覆蓋
(1)在子類中的函式名必須與父類的一樣
(2)隱藏,無法產生多型
class Base{
public:
void fn( int a ){
cout<<"Base a = " << a << endl;
}
};
class Sub:public Base{
public:
void fn( ){
cout<<"Sub b = 20" << endl;
}
};
(3)呼叫父類的方法
a.Base::fn(10); //可以有針對性的呼叫父類的函式
6、函式的高內聚,低耦合
高內聚:函式的功能儘量單一,這樣的程式碼可維護性高
低耦合:避免修改函式的時候,產生連鎖反映。
7、多型
(1)什麼是多型?
一個int型別的指標,只能指向一個int型別的變數
對於物件來講Person型別的指標 ,能指向Person型別的物件
Person型別的指標能指向一個Teacher(extend Person)型別的物件
(2)多型的特徵:
父類的指標可以指向子類的物件
通過父類指標只能呼叫父類中宣告的方法
通過指標呼叫函式的時候,若函式是個虛擬函式,表現出來的行為是物件自身的行為
Person *p = new Teacher ;
編譯時型別 執行時型別
(3)子類的指標不能指向父類物件
因為子類中會有子類特有的函式,執行時不能通過指標呼叫子類特有的函式
(4)" virtual "父類函式的返回值前加此關鍵字,則為虛擬函式
(5)產生多型的必要前提:
繼承,方法覆蓋,虛擬函式
8、虛擬函式的實現原理
在每個物件中都有一個虛擬函式列表的指標,虛擬函式列表是一個棧。
在構造物件時,根據先構造父類,再構造子類的原則,父類的函式先入棧,在子類中覆蓋的函式放在上面。
等物件構造完畢時,子類的函式在最上面,根據棧後進先出的原則,先呼叫的是子類函式
9、在釋放資源的時候
delete p ;
只會呼叫Person類的解構函式,因為指標的型別是Person的,這樣會造成子類的空間得不到及時的釋放,會造成記憶體洩露
把解構函式也寫成虛擬函式,這樣就會先呼叫子類的解構函式,再析構父類的解構函式
在繼承關係中,父類的解構函式必須是虛擬函式!!!
10、多型的使用
#include <iostream>
using namespace std;
class Person{
public:
virtual double buy(){
return 2 ;
}
};
class Teacher : public Person{
public:
virtual double buy(){
return 1 ;
}
};
class Student : public Person{
public:
virtual double buy(){
return 0.5 ;
}
};
class CEO : public Person{
public:
virtual double buy(){
return 1000 ;
}
};
void shoufei( Person * p ){
cout<< p->buy() << endl;
}
int main(){
Person p ;
Teacher t ;
Student s ;
CEO c ;
shoufei( &p ) ; //通過傳入不同物件的地址,呼叫相應的函式
shoufei( &t ) ; //與if...else對比
shoufei( &s ) ; //寫程式要儘量少改動寫好的原始碼,這樣實現程式碼的通用及低耦合
shoufei( &c ) ;
return 0 ;
}
11、 本物件 子類物件 其他函式
private屬性 可見 不可見 不可見
protected屬性 可見 可見 不可見
public屬性 可見 可見 可見
public extends protected extends private extends
父類的private屬性 不能訪問 不能訪問 不能訪問
父類的protected屬性 變成protected 不變 變成private,子類可以訪問,子類的子類不能訪問
父類的public屬性 不變 變成protected 變成private,子類可以訪問,子類的子類不能訪問
12、 建構函式有很多種,因為沒有指定建構函式,就會預設使用無參的建構函式。如果父類沒有無參的建構函式,那麼就會出現編譯錯誤。
可以使用這種形式Teacher(char* name, int age, double salary):Person(name,age){…},指定使用父類的有參建構函式。
13、多型的特徵:
父類的指標可以指向子類的物件
通過父類指標只能呼叫父類中宣告的方法
通過指標呼叫函式的時候,若函式是個虛擬函式,表現出來的行為是物件自身的行為
14、產生多型:(1)指標
(2)引用
父類的引用可以引用一個子類物件
通過父類引用只能呼叫父類函式
呼叫一個父類被覆蓋了的,虛擬函式,能呼叫子類的函式
15、一個子類繼承一個父類 — 單繼承
一個子類繼承多個父類 — 多繼承
class SpiderMan : public Spider , public Person{…}
16、菱形繼承,解決重複元素的衝突
讓兩個父類同時虛繼承一個超類,把多繼承中的重複元素放在超父類中
當有多個子類同時虛繼承一個父類的時候,只有一個子類真正的構造父類
class Spider : vertual public Animal{…};
class Person : vertual public Animal{…};
class SpiderMan :public Person , public Spider{…};
多繼承儘量不要使用三層以上
17、抽象類
只有函式宣告,沒有函式實現
純虛擬函式:沒有實現的函式 virtual void writeLog(char*)=0;
若不寫" =0 “,則系統會認為是函式宣告,會試圖去別的” .cc "檔案中去找函式實現
含有純虛擬函式的類稱為抽象類,是抽象資料型別,不能建立物件
抽象型別就是為了被別人繼承的,子類覆蓋純虛擬函式,提供函式實現
通過父類規範子類的用法
如果子類沒有完全實現抽象父類的所有純虛擬函式,則認為子類還是一個抽象資料型別
用到virtual的地方:
(1)繼承
(2)多繼承
(3)純虛擬函式
抽象類的規範很重要,在工程中,對於專案的並行開發很重要
而且對於專案中的改動,能方便的應付
用指標指向相應的子類物件,方便的呼叫子類的函式
18、友員
針對類來說,自身的私有變數,不能被別的類訪問,但是,如果授權給一個類為自己的友員,就可以訪問他的私有屬性
可以作為友員的東西:另一個類,一個全域性函式。
實現友員的授權:
class Girl;
class Person{
…
friend class Girl; //友員類的宣告–>授權給Girl類,成為自己的友員,可以訪問自己的私有變量了
}
class Girl;
class Person{
........
friend void fn(); //友員函式的宣告-->授權給fn函式,成為自己的友員,可以訪問自己的私有變量了
}
友員不是類的一部分
若不是互為友員,則不能訪問友員類的私有變數
友員的使用:
Bus把售票員作為自己的友員,訪問自己的私有變數,即裝載乘客的陣列
友員在專案中的使用
19、靜態資料
在類中定義一個靜態資料 (實際上就是一種全域性變數)
(1)不依賴於物件,在物件不存在之前就已經存在了
(2)所有物件共享
與全域性變數的區別:
(1)使用的類中的靜態變數,必須通過類名使用
(2)而且受訪問控制符號的限制
(3)靜態變數在類中宣告的時候不能賦值,要在類外初始化
class A{
public :
static int a;
};
int A::a = 100; //此時才分配空間
int main(){
cout << A::a <<endl; //靜態變數的用法,不依賴物件,直接使用
}
與成員變數的區別
(1)成員變數依賴與物件,類物件不存在,成員變數也不存在
靜態變數不依賴於物件,只要有類宣告,就存在
(2)所有物件共享一份靜態變數
21、靜態函式
在函式前加static
不依賴於物件,沒有物件依然可以通過類名呼叫靜態函式
A::fn();
在類宣告的時候,靜態函式和靜態變數就存在了
靜態函式只能使用靜態變數,不能使用成員變數
22、拷貝建構函式
#include
using namespace std;
class Person{
public:
Person(){ cout<<“Person()”<<endl; }
~Person(){ cout<<"~Person() "<<endl;}
void speak(){ cout<<“hello”<<endl; }
};
void fn( Person p ){ //這裡的傳遞時值傳遞,形參是值傳遞,這裡的形參的建立是使用拷貝建構函式
p.speak();
}
int main(){
Person p ;
fn( p ) ;
return 0 ;
}
輸出結果:
Person()
hello
~Person()
~Person() //2次析構,因為其中呼叫了的系統提供的拷貝建構函式,構造出一個新物件
拷貝建構函式,以一個已存在的物件為模版建立一個新物件
宣告方法: Person(const Person & p){…} //即節省空間,又保證模版不會被修改
預設拷貝建構函式,執行的就是簡單的記憶體拷貝 — 淺拷貝
(1)淺拷貝
只拷貝地址,而不是對指標指向空間的拷貝,會造成2個指標指向同一個空間
(2)深拷貝
為指標建立新空間,拷貝指標指向空間的內容
呼叫拷貝建構函式的時機:
(1)在值傳遞的時候
(2)A a; //預設建構函式
A a1 = a; //在宣告的時候,用一個物件賦值,使用的是拷貝建構函式
(3)A a1;
A a2(a1); //顯示的呼叫拷貝建構函式,傳的引數是個物件
什麼時候需要自己寫拷貝建構函式?
使用了動態記憶體,就會面臨淺拷貝的現象,需要自己寫拷貝建構函式
23、運算子過載
a1 = a2;
系統呼叫了函式 operator=
相當於執行了這樣的操作: a1.operator=(a2);
Student& operator= (const Student &a);
函式返回左值,返回值為引用
函式返回右值,返回值為本身
/*
*student a1;
*student a2;
*a1=a2; <=> a1.operator=(a2);
*/
Student& operator= (const Student &a){
age = a.age;
id = a.id;
strcpy(name , a.name); //不用new出一塊空間,因為在宣告a1和a2的時候,兩個指標都指向一塊自己的空間,
把指標指向的變數拷貝過去,即完成賦值
return *this;
}
當在堆中申請空間,則覆蓋賦值運算子(" = ")