C++編程面向對象入門全面詳解
類
1. struct和class的區別
如果從C語言的視角來看,所謂類就是能夠調用自身成員的結構體。而在C++中,關鍵字struct
雖然仍舊保留,但已非C語言中的結構體,而是表示默認成員共有的class
。
即在C++中,struct C{/*code*/}
和class C{public:/**/}
並無區別,例如下面兩組代碼所實現的功能是完全一致的。
//默認成員公有 struct Number{ private; float val; public: float pubVal; Number(float inVal); };
//默認成員為私有 class Number{ float val;//外部無法直接訪問 public: float pubVal; Number(float inVal); };
所謂私有成員,就是外部函數不可訪問的成員
void printPublic(Number num){ cout<<num.pubVal<<endl; } void printPrivate(Number num){ cout<<num.val<<endl; //報錯,無法訪問私有類型 }
不過從C語言的視角來看,類也的確保留瞭一些struct
的風格,其初始化方法與指針調用便是明證。
int main(){ Number num{3.14}; //相當於引用構造函數 printNumber(num); Number* pNum = # //指向num的指針 //->表示類指針所指向的成員 cout<<pNum->pubVal<<endl; system("pause"); return 0; }
輸出為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe 3.14 3.14
2. explicit構造
由於C++對泛型具備十分良好的支持,語言本身的強大可能會導致用戶在使用過程中不嚴謹,繼而增大維護成本。例如對於如下構造函數
Number::Number(float inVal){ val = inVal; }
那麼下面的幾個語句都能夠輸出正確的值
int main(){ Number num{3.14}; printNumber(num); num = 1.414; printNumber(num); printNumber(0.618); system("pause"); return 0; }
結果為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe 3.14 1.414 0.618 請按任意鍵繼續. . .
可見這三條語句都沒有報錯
Number num{3.14}; num = 1.414; printNumber(0.618);
第一條是沒有問題的,是簡單賦值語句;第二條和第三條則是暗中調用構造函數,將浮點類型的變量轉換成瞭Number
類型,這種意義不明的代碼自然會引起維護上的困難。explicit
就為解決這種問題而生的。
將構造函數用explicit
進行標記,可以有效禁止這種隱式轉換
class Number{ float val; public: explicit Number(float inVal); float pubVal; }; int main(){ Number num{3.14}; num = 1.414; //編譯不予通過 printNumber(0.618);//編譯不予通過 //... }
3. const和mutable
顧名思義,二者分別是常量與變量,前者要求成員函數不得修改類的成員變量
class Number{ float val; public: mutable float pubVal; //註意該變量用瞭mutable explicit Number(float inVal); void printVal() const; //該方法用瞭const }; void Number::printVal() const{ cout<<val<<endl; /* val = val+1; //這是不被允許的 */ pubVal = val+1; //這是被允許的 }
即,const
成員隻能修改mutable
成員。
4. 自引用
自引用是一種編程技巧,對於更改類狀態的函數,如果將類本身作為返回值,那麼就可以實現炫酷而優雅的鏈式操作。
class Number{ float val; public: explicit Number(float inVal); Number& addOne(); //其返回值是當前對象的地址 }; Number& Number::addOne(){ cout<<val++<<endl; return *this; }
其中,*this
指向調用該成員函數的對象,測試一下
int main(){ Number num{3.14}; //相當於引用構造函數 num.addOne().addOne().addOne(); system("pause"); return 0; }
結果為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe 3.14 4.14 5.14 請按任意鍵繼續. . .
5. static
顧名思義,靜態成員之所以被稱為靜態,在於其存儲位置隻有一個。對於一個類而言,無論創建瞭多少實例,類中的靜態變量就隻被存儲在那一個位置。這意味著靜態成員要比對象實例具有更長的生命周期,當一個對象被銷毀之後,靜態成員並沒有被銷毀,從而再次被調用的時候,也不必另行分配內存。
class Number{ float val; static Number defaultNum; public: explicit Number(float inVal=0); static void setDefault(float inVal); void printVal() const; }; void Number::printVal() const{ cout<<val<<endl; } //定義默認Num Number Number::defaultNum{3.14}; void Number::setDefault(float val){ defaultNum = Number{val}; }; Number::Number(float inVal){ val = inVal ? inVal : defaultNum.val; } int main(){ Number num{}; //相當於引用構造函數 num.printVal(); system("pause"); return 0; }
輸出為
PS E:\Code\cpp> .\a.exe 3.14 請按任意鍵繼續. . .
復數的實現
復數有實部和虛部,默認值為0,其加法和減法分別就是實部和虛部相減,其乘法為
#include<iostream> using namespace std; class Complex{ float real; //實部 float im; //虛部 static Complex defaultNum; public: explicit Complex(float inReal=0, float inIm=0); static void setDefault(float inReal, float inIm); void printVal() const; Complex& add(float inReal, float inIm); Complex& minus(float inReal, float inIm); Complex& multi(float inReal, float inIm); Complex& div(float inReal, float inIm); }; //默認值為{0,0} Complex Complex::defaultNum{0,0}; void Complex::setDefault(float inReal,float inIm){ defaultNum = Complex{inReal, inIm}; }; //打印當前值 void Complex::printVal() const{ cout<<"real part: "<<real<<endl; cout<<"image part:"<<im<<endl; } //加法 Complex::Complex(float inReal, float inIm){ real = inReal ? inReal : defaultNum.real; im = inIm ? inIm : defaultNum.im; } Complex& Complex::add(float inReal, float inIm){ real += inReal ? inReal : 0; im += inIm ? inIm : 0; return *this; } Complex& Complex::minus(float inReal, float inIm){ real -= inReal ? inReal : 0; im -= inIm ? inIm : 0; return *this; } Complex& Complex::multi(float inReal, float inIm){ float temp = real*inReal - im*inIm; im = real*inIm + im*inReal; real = temp; return *this; } Complex& Complex::div(float inReal, float inIm){ float temp = inReal*inReal + inIm*inIm; float tempReal = (real*inReal + im*inIm)/temp; im = (im*inReal-real*inIm)/temp; real = tempReal; return *this; } int main(){ Complex num{}; //相當於引用構造函數 num.add(1,2).multi(3,4).div(1,2); num.printVal(); system("pause"); return 0; }
下面的操作便基於這個復數類進行。
6.成員函數重載
上述的加減乘除運算,默認輸入值為實部和虛部的組合,但並不能實現兩個Complex的運算。C++支持成員函數的重載。
class Complex{ /* 上文中所定義的類的結尾 */ Complex operator+(Complex); Complex operator-(Complex); Complex operator*(Complex); Complex operator/(Complex); //實現類似數乘功能 Complex operator*(float); Complex operator/(float); }
這些函數可以通過最簡單的方式定義
Complex& Complex::add(Complex num){ real += num.real; im += num.im; return *this; }
也可以通過調用已經定義過的成員函數
Complex& Complex::multi(Complex num){ multi(num.real, num.im); return *this; }
7.運算符重載
在C++中,可以很方便地對一些運算符進行重載,其格式為
Complex operator+(Complex);
對於兩個復數a和b來說,調用重載之後的運算符a+b
等價於a.operator(b)
。
其具體實現為
class Complex{ /* 上文中所定義的類的結尾 */ Complex operator+(Complex); Complex operator-(Complex); Complex operator*(Complex); Complex operator/(Complex); } Complex Complex::operator+(Complex num){ float outReal = real+num.real; float outIm = im+num.im; return Complex{outReal, outIm}; } Complex Complex::operator-(Complex num){ return Complex{real-num.real, im-num.im}; } Complex Complex::operator*(Complex num){ return Complex{real*num.real - im*num.im, real*num.im + im*num.real}; } Complex Complex::operator/(Complex num){ float temp = num.real*num.real + num.im*num.im; return Complex{(real*num.real + im*num.im)/temp, (im*num.real-real*num.im)/temp}; } Complex Complex::operator*(float val){ return Complex{real*val,im*val}; } Complex Complex::operator/(float val){ return Complex{real/val,im/val}; } //主函數 int main(){ Complex temp{1,1}; Complex temp1 = temp-temp*temp*2; temp1.printVal(); temp.printVal(); system("pause"); return 0; }
測試一下結果為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe real part: 1 image part:-3 real part: 1 image part:1
可見操作符雖然被重載瞭,但運算次序得以保留。
8.new
C語言中通過STRUCT* struct = (STRUCT*)malloc(sizeof(STRUCT))的方式來動態地開辟內存,留待日後使用。
在C++中,new
可以勝任這一工作。
例如
int* p = new int; int* Q = new int(5);
對於Complex
類,可以通過指針形式進行實現
int main(){ Complex* temp = new Complex(1,1); temp->add(*temp); temp->printVal(); delete(temp); //銷毀temp內存 system("pause"); return 0; }
其中,->
亦承自C語言,用於類指針調用類成員,其結果為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe real part: 2 image part:2 請按任意鍵繼續. . .
9.析構函數
一般通過new
來分配內存空間,需要在調用結束之後使用delete
對內存進行釋放,delete的執行過程,便會調用析構函數。
在解釋析構函數之前,需要回顧一下構造函數,所謂構造函數,即與類名相同的函數,通過構造函數可以創建一個類的對象,並開辟足夠的內存。析構函數即銷毀函數,將構造函數開辟的內存銷毀掉。
析構函數亦與類名相同,而且無參數無return不可重載,是一個不易理解但易於使用的方法。
public: explicit Complex(float inReal=0, float inIm=0); //此即析構函數, ~Complex(){}
10.friend
在復數類中,實部和虛部被封裝為私有變量,外部函數是無法訪問的。此時,如果希望在其他類中創建一個提取復數實部或虛部的變量,則可以考慮友元機制。
所謂友元機制,即允許一個類將其非共有成員授權給指定的函數或者類,通過關鍵字friend
修飾。例如,
/* Complex類 */ friend float getReal(Complex num); }; float getReal(Complex num){ cout<<num.real<<endl; return num.real; }
這樣,getReal
就可以直接訪問Complex
類的私有成員。
11.類的繼承
一般來說,復數 a + b i a+b\text{i} a+bi並不支持類似直乘的操作,即 ( a + b i ) ∗ ( a + b i ) ≠ a b + c d i (a+b\text{i})*(a+b\text{i})\not ={ab+cd\text{i}} (a+bi)∗(a+bi)=ab+cdi,那麼如果希望構造一種新的代數關系,使之既支持復數乘法,又可以直乘,那麼就需要新建一個類,為瞭避免代碼過於重復,這個類可以作為復數類的派生類而存在。
需要註意的一點是,此前所創建的Complex
類默認成員為私有,所以其im
,real
對於子類而言是不可訪問的。出於簡單考慮,我們將class
改為struct
,這樣其子類便可無痛調用。
//Complex類的派生類 class antiComplex : Complex{ public: antiComplex(float inReal,float inIm){ real = inReal; im = inIm; }; void printVal(); antiComplex operator*(antiComplex); }; antiComplex antiComplex::operator*(antiComplex num){ return antiComplex{real*num.real,im*num.im}; } //重寫printVal函數 void antiComplex::printVal(){ cout<<"I'm antiComplex"<<endl; cout<<"real part: "<<real<<endl <<"image part:"<<im<<endl; } int main(){ antiComplex temp{1,2}; temp.printVal(); temp = temp*temp; temp.printVal(); system("pause"); return 0; }
其結果為
PS E:\Code\cpp> .\a.exe I'm antiComplex real part: 1 image part:2 I'm antiComplex real part: 1 image part:4 請按任意鍵繼續. . .
在C++中有三種繼承方式,分別是public
,private
,protected
,一般默認為public
繼承,其特點是無法訪問父類的私有成員;private
繼承則連公有成員和保護成員都無法訪問;protected
則允許其子類訪問,但不允許子類的子類訪問。
具體表現如下表所示,其讀法為public成員在private繼承時表現為private成員。
public繼承 | private繼承 | protected繼承 | |
---|---|---|---|
public成員 | public | private | protected |
private成員 | private | private | private |
protected成員 | protected | private | protected |
12.多態
所謂多態就是多個子類繼承一個基類時的差異性,例如,Complex
和antiComplex
都可以作為一種抽象數據結構的子類,畢竟二者隻有在乘除法上表現不同。
#include<iostream> using namespace std; struct Abstract{ float real; float im; Abstract(float inReal, float inIm){ real = inReal; im = inIm; } void printVal(){ cout<<"I'm Abstract"<<endl; }; Abstract& multi(Abstract val){}; }; struct Complex:Abstract{ Complex(float inReal, float inIm) :Abstract(inReal,inIm){} void printVal(); Abstract& multi(Abstract val); }; void Complex::printVal(){ cout<<"I'm Complex:" <<real<<"+"<<im<<"i"<<endl; } Abstract& Complex::multi(Abstract val){ float temp = real*val.real - im*val.im; im = real*val.real + im*val.im; real = temp; return *this; } struct antiComplex:Abstract{ antiComplex(float inReal, float inIm) :Abstract(inReal,inIm){} void printVal(); Abstract& multi(Abstract val); }; void antiComplex::printVal(){ cout<<"I'm antiComplex:" <<real<<"+"<<im<<"j"<<endl; } Abstract& antiComplex::multi(Abstract val){ real = real*val.real; im = im*val.im; return *this; } int main(){ Complex temp{1,2}; antiComplex antemp{1,2}; temp.multi(temp).multi(temp); antemp.multi(antemp).multi(temp); temp.printVal(); antemp.printVal(); system("pause"); return 0; }
其輸出結果為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe I'm Complex:-3+5i I'm antiComplex:1+4j 請按任意鍵繼續. . .
可見這個結果是錯的,原因在於multi
函數的返回指針為Abstract
類型,而基類中的multi
為一個空函數,所以隻執行一次multi
。所以,如果子類再調用函數之後繼續保持子類的方法就好瞭,這就需要使用關鍵字virtual
。
13.virtual
直接通過指針來說明virtual的功能比較合適。
struct Abstract{ float real; float im; Abstract(float inReal, float inIm){ real = inReal; im = inIm; } void printVal(){ cout<<"I'm Abstract"<<endl; }; }; struct Complex:Abstract{ Complex(float inReal, float inIm) :Abstract(inReal,inIm){} void printVal(){ cout<<"I'm Complex:" <<real<<"+"<<im<<"i"<<endl; } }; int main(){ Abstract* a; Complex temp{1,2}; a = &temp; a->printVal(); system("pause"); return 0; }
其運行結果為
PS E:\Code\cpp> g++ .\oop.cpp PS E:\Code\cpp> .\a.exe I'm Abstract
也就是說,雖然父類的指針指向瞭子類的對象,但最終指針指向的仍然是父類的函數。而如果在父類函數前加上virtual
關鍵字,即將其改為
struct Abstract{ float real; float im; Abstract(float inReal, float inIm){ real = inReal; im = inIm; } virtual void printVal(){ cout<<"I'm Abstract"<<endl; }; };
則其輸出為
PS E:\Code\cpp> .\a.exe I'm Complex:1+2i
可見虛函數的作用是使得父類指針指向實際對象的方法。將父類的multi
函數變為
virtual Abstract& multi(Abstract val){};
則最終的輸出結果為
PS E:\Code\cpp> .\a.exe I'm Complex:-16+34i I'm antiComplex:-16+136j
看的不過癮?
C++元編程初步
以上就是C++面向對象入門全面詳解的詳細內容,更多關於C++面向對象入門的資料請關註WalkonNet其它相關文章!