文章目錄
類 I
——若在類體內沒有指明訪問權限,默認的訪問權限爲private。
——類是一種數據類型,系統並不會爲其分配內存空間。
——構造函數重載:同名不同參
——帶有子對象的構造函數的執行順序:https://blog.csdn.net/qq_15989473/article/details/103215333
拷貝構造函數
——拷貝構造函數的定義
Sample(Sample &S)
//名稱與類名相同,形參爲本類的引用對象。
——拷貝構造函數的三種調用情況:
// 1&2.同類對象的初始化
Sample S2(S1);
Sample S2 = S1;
// 3.對象作爲函數的參數進行傳遞
void f(A a){a.x = 1;};
A obj; f(obj);//相當於A a = obj;
// × 賦值不是初始化,未調用拷貝構造函數
S2 = S1;
析構函數
——析構函數不能重載
~A(); //析構函數聲明
A::~A(){} //析構函數定義
——析構函數和構造函數的調用順序相反
靜態成員
——靜態數據成員只能在類體內聲明,類體外初始化(假如有一個類Sample)
static int num; //類體內聲明,需要static關鍵詞
int Sample::num = 1; //類體外初始化,不需要static關鍵詞
——靜態成員的訪問有兩種方式:類名::函數名(); 或者 對象名.函數名();
Sample::function();
obj.function();
推薦用第一種,因爲指明瞭靜態成員是屬於整個類的。
——非靜態成員函數可以任意訪問靜態和非靜態成員 ,靜態成員函數只能訪問靜態成員(數據成員或成員函數)
類 II
——系統給對象分配的內存只是用來存儲數據成員的。成員函數的代碼統一放在程序的代碼區
this指針
——this指針是指向本類對象的指針。
——this指針是在用對象引用成員函數時系統自動創建的。
——this指針是被隱式定義在非靜態成員函數的形參中。
——類的靜態成員函數沒有this指針。因爲靜態成員函數爲類的所有對象所共有,不專屬於某一個對象。所以在靜態成員函數中不能直接訪問非靜態數據成員(因爲沒有this指針)
各種“常”
——常數據成員的初始化必須要用構造函數的初始化列表完成。
Sample(int temp){const_num = temp;} // ×
Sample(int temp):const_num(temp){} // √
常成員函數
——只能調用const成員函數。
——可以使用const與非const數據成員,但不能修改。
int function() const; //聲明
int Sample::function() const //類體外的定義
{
num = 0; //錯誤,不可修改數據成員。
unconst_function();//錯誤,不可調用非const成員函數
const_function();//只能調用const成員函數
return num;//正確,可以使用數據成員(const非const都可)
}
常對象
——常對象是指對象的數據成員的值在對象被調用時不能被改變。常對象必須進行初始化,且不能被更新。
//定義格式(必須進行初始化)
const Sample obj1(1,1);
Sample const obj2(0,0);
obj1.unconst_set(2,2);//錯誤,常對象不能調用非const成員函數
obj1.const_print();//只能調用const成員函數
指向對象的常指針
//指向對象的常指針
Sample *const pr = &obj1;
//pr只能指向對象obj1
pr = &obj2;//錯誤,指針值不可改變
//但是可以改變對象obj的值,如:
pr->function(2);
指向常對象的指針變量(指針值可以改變)
//指向常對象的指針變量
const Sample *pr = &obj1;
//不能通過pr去修改obj1的空間
注意與常指針相區分
總結
各種“常” | 含義 | 成員函數 | 數據成員 |
---|---|---|---|
void Sample::function() const | function爲常成員函數 | 只能調用const成員函數 | 常變量都可以使用(包括private),但不能改變其值 |
const Sample& s=obj; | s是常引用,可以認爲把對象obj的屬性變成了const | 與常對象權限相同 | 與常對象權限相同 |
const Sample *pr | pr是指向常對象的指針 | 與常對象權限相同 | 與常對象權限相同 |
Sample const obj | obj爲常對象 | 只能調用const成員函數 | 常&變量都可以使用(前提public),但不能改變其值 |
Sample *const pr=&obj; | pr爲常指針(指針值不可以改變) | const & 非const 都可調用,形式:pr->function(); | const & 非const 都可調用,形式:pr->number; |
友元
友元函數
(此處源代碼來自菜鳥教程 原鏈接)
——友元函數並不是成員函數,但有權訪問私有、保護和公有成員(所有成員)。
——友元函數在類內聲明,在類外定義
——友元函數不能直接訪問類的成員,只能訪問對象成員。
——調用友元函數時,在實際參數中需要指出要訪問的對象。如下述printWidth函數中的形參Box box
——類與類之間的友元關係不能繼承。(也就是說基類的友元函數繼承不到派生類中)
class Box
{
double width;
public:
double length;
//在類內用friend關鍵詞聲明友元函數。
//此處只能聲明,不能定義,因爲Box類還未定義。
friend void printWidth( Box box );
void setWidth( double wid );
};
//在類外像定義普通函數一樣定義友元函數
//不需要使用 類名:: 作用域符號(只有成員函數採用作用域符號)
void printWidth( Box box )
{
/* 因爲 printWidth() 是 Box 的友元,它可以直接訪問該類的任何成員 */
cout << "Width of box : " << box.width <<endl;
}
int main()
{
Box b;
// 使用成員函數設置寬度
b.setWidth(10.0);
// 使用友元函數輸出寬度
// 可以直接調用友元函數,不需要藉助對象。
printWidth(b);
return 0;
}
——特別注意下面這種寫法是錯誤的
b.printWidth(b); //錯誤
因爲友元函數不屬於類對象b的成員函數。
友元類
——如果A 是 B的友元類 -> A的成員函數可以訪問 B的私有成員
——友元類之間的關係不能傳遞,不能繼承(此處來源)如:
B 是 A 的友元,C 是 B 的友元,C 不是 A 的友元。
A 是 B 的友元,不代表 B 是 A 的友元。
類模板
——類模板,可以定義相同的操作,擁有不同數據類型的成員屬性。
——通常使用template來聲明。告訴編譯器,碰到T不要報錯,表示一種泛型.
如下,一個普通的類模板:
template <class T>
class A{
private:
T x,y;
public:
A(T xx,T yy)
{x=xx;y=yy;}
//在類內定義的函數
T fun_in()
{return (x+y);}
//在類內聲明,在類外定義的函數
T fun_out();
};
//fun_out函數在類外定義的格式
template <class T>
T A<T>::fun_out()
{return (x+y);}
int main()
{
A<int> int_obj(6,8);
int_obj.fun_in();
}
——模板的實例化指函數模板(類模板)生成模板函數(模板類)的過程。對於函數模板而言,模板實例化之後,會生成一個真正的函數。而類模板經過實例化之後,只是完成了類的定義,模板類的成員函數需要到調用時纔會被初始化。模板的實例化分爲隱式實例化和顯示實例化。(原文出處)
——類模板的參數也可以是非類型參數,普通值也可以作爲模板參數
template<class T,int NUM>
class Sample{
........
};
void main()
{Sample<int,20> obj;}
繼承與派生
三種繼承方式的特點圖解
公有繼承
私有繼承
保護繼承
不同位置的訪問權限
派生類成員 | 派生類中 | 派生類外部 | 下層派生類 |
---|---|---|---|
公用成員 | 可以 | 可以 | 可以 |
保護成員 | 可以 | 不可以 | 可以 |
私有成員 | 可以 | 不可以 | 不可以 |
不可訪問成員 | 不可以 | 不可以 | 不可以 |
直接間接 基類或派生類
構造函數與析構函數的調用順序
構造函數
析構函數
析構函數與構造函數的調用順序剛好相反
多重繼承
基本概念
聲明方法
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RGT69DH1-1579092246738)(https://cdn.jsdelivr.net/gh/cnatom/images/images/20191219144815.png)]
有關二義性
二義性的兩種情況
消除二義性的兩種方法
舉個栗子
——我們先定義兩個基類A與B,裏面包含最簡單的公有成員show:
class A{
public:
void show(){
cout << "基類A的show";
}
};
class B{
public:
void show(){
cout << "基類B的show";
}
};
——然後再用一個類C繼承A與B:
class C:public A,public B{
};
——在主函數中調用公有成員show
int main()
{
C c;
c.show();
return 0;
}
——結果意料之中的報錯了
[Error] request for member 'show' is ambiguous
——因爲編譯器不知道c.show()中的show到底是A的show還是B的show
解決方案1
√——用相應的類名來標識(消除二義性的方法一),在主函數中修改:
int main()
{
C c;
c.B::show();//添加B::表示調用B中的show
return 0;
}
解決方案2
√——由派生類提供統一的接口
——那麼我們重新定義一下C中的show成員函數,在派生類成員函數中用類名標識符調用基類同名成員函數:
class C:public A,public B{
public:
void show(){
A::show();//用類名標識來實現調用A的show函數。
cout << "與";
B::show();
}
};
——這樣的話就可以在主函數中直接用c.show()來實現用派生類的show函數間接調用A和B的show函數了:
int main()
{
C c;
c.show();
return 0;
}
解決方案3
√——利用同名隱藏
同名隱藏:當基類與派生類有同名成員時,派生類的成員會將基類成員屏蔽。
如果在定義派生類對象的模塊中通過對象名訪問同名的成員,則訪問的是派生類的成員。
這裏我們可以在派生類C中重載show成員函數,那麼調用c.show()的時候就會優先用派生類的show函數,基類的show被屏蔽掉了。
比如給C添加一個同名成員show:
class C:public A,public B{
public:
void show(){
cout << "派生類C的show";
}
};
這樣的話就可以在主函數中直接調用,而不需要 類名::
int main()
{
C c;
c.show();
return 0;
}
虛基類
提出一個問題
——假設我們有這樣組成結構的幾個類:
——具體實現:
class C{
public:
int num;
};
class A:public C{
public:
int num1;
};
class B:public C{
public:
int num2;
};
class N:public A,public B{
public:
int num3;
};
——分析:這裏C類被A類與B類繼承,在A類與B類中分別有兩個“不同”的數據成員num(來自C類)。也就是說,C類中的num變成了兩份,一份在A中,一份在B中。
——驗證一下分析:
int main()
{
N n;
n.A::num = 1;
n.B::num = 2;
cout << n.A::num << " ";
cout << n.B::num;
return 0;
}
================輸出結果===================
1 2
——問題:但是num是C類裏面的,他不應該有兩份,這該怎麼解決呢?
權宜之計
——使用虛繼承的方式=>虛基類
——只需要讓A和B虛繼承C類,這樣的話C就會只保留一份,比如我們改寫一下A與B:
//注意虛繼承的格式:virtual 繼承方式 基類名
class A:virtual public C{
public:
int num1;
};
class B:virtual public C{
public:
int num2;
};
——再在主函數中驗證一下,A中的num與B中的num是否爲同一份:
int main()
{
N n;
n.A::num = 1;//我們先改寫了A中的num
cout << n.B::num;//看看B中的num是不是與A中的是同一個。
return 0;
}
=============輸出結果===============
1
——可以看出,我們改變了A中的num,B中的num也改變了,說明C在繼承過程中只保留了一份
——同時,因爲A與B中的num是同一份,我們還可以用對象名加點的方式直接訪問,而不會產生二義性。
int main()
{
N n;
n.A::num = 1;
cout << n.num;//直接訪問
return 0;
}
虛繼承的問題
——虛繼承的構造函數是很麻煩的。
——清華大學鄭莉老師的解釋,傳送門(自行跳轉8:50):https://www.bilibili.com/video/av41347930?p=32
多態性
多態性概述
——多態是指操作系統接口具有表現多種形態的能力,即能根據操作環境的不同採取不同的處理方式。多態性是面向對象系統的主要特徵之一,在這樣的系統中,一組具有相同基本語義的方法能在同一接口下爲不同的對象服務。
——多態的類型:重載多態、強制多態、包含多態、參數多態。
——多態的種類:C++語言支持的多態性可以按其實現時機分爲編譯時多態和運行時多態兩類。
——綁定:是指把一個標識符名和一個存儲地址聯繫在一起的過程。
——編譯時的多態:綁定工作在編譯連接階段完成的情況稱爲靜態綁定。
——運行時的多態:綁定工作在程序運行階段完成的情況稱爲動態綁定。
運算符的重載
複數類爲例
說明都在註釋中給出
class Complex{
private:
int a;
int b;
public:
Complex(int aa=0,int bb=0):a(aa),b(bb){}//構造函數
Complex operator+(const Complex &obj) const;//重載+運算符
Complex& operator++();//前置++運算符 ++obj
Complex operator++(int);//後置++運算符 obj++
void show(){cout << a << "+" << b << "i";}//顯示
};
//+運算符重載的定義(重載爲成員函數)
//obj1 + obj2
//相當於obj1.operator+(obj2);
Complex Complex::operator+(const Complex &obj) const{
//使用Complex()構造函數創建一個臨時對象
return Complex(a+obj.a,b+obj.b);
}
//前置++運算符重載的定義
//這裏返回引用,是因爲++obj,使用的是自增後對象的本身
//++obj相當於obj.operator++();
Complex& Complex::operator++(){
a++;b++;return *this;
}
//後置++運算符重載的定義
//這裏返回臨時對象,這個臨時對象保存的是自增之前的對象。
//obj++相當於obj.operator(0);
//自動傳一個0進去,只是爲了跟前置區分而已。
Complex Complex::operator++(int){
Complex old(*this);
++(*this);//這裏調用已經寫好的前置運算符
return old;//返回自增前的對象。
}
最好重載爲友元
比如:
//+運算符重載的定義(重載爲成員函數)
//obj1 + obj2
//相當於obj1.operator+(obj2);
Complex Complex::operator+(const Complex &obj) const{
//使用Complex()構造函數創建一個臨時對象
return Complex(a+obj.a,b+obj.b);
}
這裏的obj1+obj2相當於obj1.operator(obj2);
也就是說第一個操作數必須爲這個類的對象
因此,如果我用10+obj,顯然是不行的。
爲了解決這個問題,我們要把運算符重載爲友元函數。
class Complex{
private:
int a;
int b;
public:
Complex(int aa=0,int bb=0):a(aa),b(bb){}//構造函數不變
friend Complex operator+(const Complex &obj1,const Complex &obj2);//+運算符重載
friend Complex& operator++(Complex &obj);//前置++運算符重載
friend Complex operator++(Complex &obj,int);//後置++運算符重載
void show(){cout << a << "+" << b << "i";}
};
//這裏將const限定符去掉了
//因爲非成員函數不能用CV限定(在C++中CV限定符指const和volatile)
//否則會報錯:cannot have cv-qualifier
//同時Complex::也去掉了,因爲友元函數是非成員函數
Complex operator+(const Complex &obj1,const Complex &obj2){
return Complex(obj1.a+obj2.a,obj1.b+obj2.b);
}
//前置++運算符的重載
//++obj
Complex& operator++(Complex &obj){
obj.a++;obj.b++;return obj;
}
//後置++運算符的重載
//obj++
//要用int來與前置區分
Complex operator++(Complex &obj,int){
Complex old(obj);
++(obj);
return old;
}
用cout的方式輸出複數
普通的數甚至字符串都可以用cout直接輸出,那麼我們自己定義的類呢?當然可以!
先說明一下,通過查閱文檔可知
cout << a << b;
相當於:
operator<<(operator<<(cout,a),b);
那麼我們就可以自己重載一個了。
方法如下
在Complex類的public內聲明一個友元函數
//這裏返回引用是爲了下一次嵌套調用。
//比如operator<<(operator<<(cout,a),b);
//調用裏層之後返回一個cout
//變成了operator<<(cout,b);
friend ostream& operator<<(ostream &out,Complex &obj);
在類外定義:
ostream& operator<<(ostream &out,Complex &obj){
out << "(" << obj.a << "+" << obj.b << "i)";
return out;
}
這樣就可以愉快的調用啦!
int main()
{
Complex a(1,2),b(2,3);
cout << a << b;
return 0;
}
============輸出結果=============
(1+2i)(2+3i)