C++中的類
概念
類用於指定對象的形式,它包含了數據表示法和用於處理數據的方法。類中的數據和方法稱為類的成員。函數在一個類中被稱為類的成員。
類的實質是一種引用數據類型。每個類包含數據說明和一組操作數據或傳遞消息的函數。類的實例稱為對象。
特性
封裝性
將數據和操作封裝為一個有機的整體,由於類中私有成員都是隱藏的,只向外部提供有限的接口,所以能夠保證內部的高內聚性和與外部的低耦合性。用者不必了解具體的實現細節,而只是要通過外部接口,以特定 的訪問權限來使用類的成員,能夠增強安全性和簡化編程。
繼承性
子類可以繼承父類的屬性和方法,不用再寫一遍。
多態性
同一操作作用於不同對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向父類(基類)的指針,來調用實現子類(派生類)中的方法。
C++中類的定義
class classname{
Access specifiers: //private pubilic protected
Data members/variables;
Member functions(){}
};
訪問類中的\(Member\)時,直接classname.membername
,但\(private\)和\(protected\)的\(Member\)不能用.
來訪問。
類成員函數
如同其他\(Member\)一樣,類中函數也是一種\(Member\)。
成員函數可以定義在類定義內部,或者單獨使用范圍解析運算符 :: 來定義。
注:在\(::\)運算符之前必須使用類名。
類訪問修飾符
就是上面提到的 \(public~private~protected\)。
一個類可以有多個 $public、~protected $ 或 \(private\) 標記區域。每個標記區域在下一個標記區域開始之前或者在遇 到類主體結束右括號之前都是有效的。成員和類的默認訪問修飾符是 \(private\)。
public
\(pubilcmember\) 在程序中類的外部是可訪問的,即是說我們可以直接在外部修改 \(publicmember\) ,比如:
class classname{
...
};
int main(){
classname.membername = value;
return 0;
}
private
顧名思義,對比前面的 \(public\) ,其實意思就是 \(privatemember\)在類的外部是不可訪問的,甚至是不可查看的。只有類和友元函數可以訪問私有成員。
注:默認情況下類的所有成員都是 \(private\) 。
實際操作中,我們一般會在私有區域定義數據,在公有區域定義相關的函數,以便在類的外部也可以調用這些函數。
protected
和 \(private\) 差不多,區別在於 \(protectedmember\) 在子類中是可訪問的。
類的構造函數與析構函數
用於初始化 \(classmember\) ,其在每次創建類的新對象時執行。構造函數的名稱與類的名稱是完全相同的,並且不會返回任何類型,也不會返回 \(void\) 。
這是一個構造函數的例子:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC();
private:
double price;
};
//這個是構造函數
PC::PC(void) {
cout << "PC has been generated" << endl;
}
void PC::setprice(double x) {
price = x;
}
double PC::getprice() {
return price;
}
int main()
{
double price;
PC ALIENWARE;
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
cout << "Price is: " << ALIENWARE.getprice() << endl;
return 0;
}
其中PC()
即為構造函數,定義方式如代碼所示。
帶參數的構造函數
用於初始化,下面是例子:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC(double prc);
private:
double price;
};
//這個是構造函數
PC::PC(double prc) {
cout << "PC has been generated" << endl;
price = prc;
cout << "Defult price is: " << prc << endl;
}
void PC::setprice(double x) {
price = x;
}
double PC::getprice() {
return price;
}
int main()
{
double price;
PC ALIENWARE(0.0);
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
cout << "Price is: " << ALIENWARE.getprice() << endl;
return 0;
}
初始化列表
class A{
public:
double X,Y,Z;
};
A::A(double a, double b, double c) : X(a), Y(b), Z(c)
類的析構函數
與構造函數相反,析構函數在每次刪除對象時執行,有助於在跳出程序前釋放資源。
注:析構函數不返回任何值,也不能帶參數。
拿上面的代碼改下做例子:
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC(double prc);
~PC();
private:
double price;
};
PC::PC(double prc) {
cout << "PC has been generated" << endl;
price = prc;
cout << "Defult price is: " << prc << endl;
}
//這個就是析構函數
PC::~PC() {
cout << "PC sold" << endl;
}
void PC::setprice(double x) {
price = x;
}
double PC::getprice() {
return price;
}
int main()
{
double price;
PC ALIENWARE(0.0);
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
cout << "Price is: " << ALIENWARE.getprice() << endl;
return 0;
}
注:1、一個類內可以有多個構造函數,但只能有一個析構函數。
2、C++初始化 \(classmember\) 時,是按照聲明順序初始化的,而不是按照出現在初始化列表中的順序。
拷貝構造函數
拷貝構造函數是一種特殊的構造函數,具有單個形參,該形參(常用const修飾)是對該類類型的引用。當定義一個新對象並用一個同類型的對象對它進行初始化時,將顯示使用拷貝構造函數。當該類型的對象傳遞給函數或從函數返回該類型的對象時,將隱式調用拷貝構造函數。
必須定義拷貝構造函數的情況:
只包含類類型成員或內置類型(但不是指針類型)成員的類,無須顯式地定義拷貝構造函數也可以拷貝;有的類有一個數據成員是指針,或者是有成員表示在構造函數中分配的其他資源,這兩種情況下都必須定義拷貝構造函數。
C++調用拷貝構造函數的情況:
1、一個對象以值傳遞的方式傳入函數體
2、一個對象一值傳遞的額方式從函數返回
3、一個對象需要通過另外一個對象進行初始化
注:當出現類的等號賦值時,會調用拷貝函數,在未定義顯示拷貝構造函數的情況下,系統會調用默認的拷貝函數——即淺拷貝,它能夠完成成員的一一復制。當數據成員中沒有指針時,淺拷貝是可行的。但當數據成員中有指針時,如果采用簡單的淺拷貝,則兩類中的兩個指針將指向同一個地址,當對象快結束時,會調用兩次析構函數,而導致指針懸掛現象(指針指向非法的內存地址,那么這個指針就是懸掛指針,也叫野指針)。所以,這時,必須采用深拷貝。
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
class PC {
public:
void setprice(double x);
double getprice();
PC(double prc);
PC(const PC& obj);
~PC();
private:
double* price;
};
//這個是拷貝構造函數
PC::PC(const PC& obj) {
cout << "new class copied." << endl;
price = new double;
*price = *obj.price;
}
PC::PC(double prc) {
cout << "PC has been gernerated" << endl;
price = new double;
*price = prc;
cout << "Defult price is: " << prc << endl;
}
PC::~PC() {
cout << "PC sold" << endl;
delete price;
}
void PC::setprice(double x) {
*price = x;
}
double PC::getprice() {
return *price;
}
int main()
{
double price;
PC ALIENWARE(0.0);
cout << "Enter Price: " << endl;
cin >> price;
ALIENWARE.setprice(price);
PC ASUS = ALIENWARE;
cout << "ASUS price : " << ASUS.getprice() << " ALIENWARE price : " << ALIENWARE.getprice() <<endl;
return 0;
}
友元函數
類的友元函數是定義在類外部,但有權訪問類的所有 \(private\) 成員和 \(protected\) 成員的函數。
友元可以是一個函數,該函數被稱為友元函數;友元也可以是一個類,該類被稱為友元類,在這種情況下,整個類及其所有成員都是友元。
友元函數的聲明
若要聲明函數為一個類的友元,則在類定義中在該函數前加上friend
。若要聲明一個類 \(A\) 的所有成員為另一個類 \(B\) 的友元,則需在 \(B\) 中添加friend class A
舉個例子:
C#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
friend void printprice(PC PC_0)//友元函數
void setprice(double prc);
private:
double price;
};
void PC::setprice(double prc) {
price = prc;
cout << "price set as :" << prc << endl;
}
void printprice(PC PC_0) {
cout << "price is: " << PC_0.price << endl;//友元函數直接訪問PC的classmember
}
int main()
{
PC ROG;
double price;
cout << "Enter price: " << endl;
cin >> price;
ROG.setprice(price);
printprice(ROG);
return 0;
}
友元類的聲明
把上面的代碼改一下:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
friend class Print;
private:
double price;
};
class Print {
public:
void printprice(PC PC_0);
};
void Print::printprice(PC PC_0) {
cout << "Price is: " << PC_0.price;
}
void PC::setprice(double prc) {
price = prc;
cout << "price set as :" << prc << endl;
}
int main()
{
PC ROG;
Print printer;
double price;
cout << "Enter price: " << endl;
cin >> price;
ROG.setprice(price);
printer.printprice(ROG);
return 0;
}
內聯函數
其實就是在函數聲明前面加個inline
。
引入內聯函數的目的是為了解決程序中函數調用的效率問題,程序在編譯器編譯的時候,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體進行替換,而對於其他的函數,都是在運行時候才被替代。
注:1、內聯函數中不允許使用循環和\(switch\)。
2、內聯函數的定義必須出現在內聯函數第一次調用之前。
this指針
C++中每個對象都能通過 \(this\) 指針來訪問自己的地址。
#include<cstdio>
#include<iostream>
#include<typeinfo>
using namespace std;
class PC {
public:
PC(double prc) {
price = prc;
}
bool max(PC PC_0) {
return (this->price >= PC_0.price) ? true : false;
}
private:
double price;
};
int main()
{
PC ALIENWARE(15999), ASUS(12999);
cout << "The expensive one is: " << endl;
if (ALIENWARE.max(ASUS)) cout << "ALIENWARE" << endl;
else cout << "ASUS" << endl;
return 0;
}
注:友元函數沒有 \(this\) 指針,只有成員函數才有。
靜態成員
其實就是在 \(classmember\) 聲明前面加個static
。
通俗的說靜態成員是一個類的所有對象的共享成員。
舉個例子,下面的代碼實現了統計類的對象的個數的功能:
#include<cstdio>
#include<iostream>
#include<typeinfo>
using namespace std;
class PC {
public:
PC(double prc) {
price = prc;
num++;
}
static int num;//靜態成員數據
static int getnum();//靜態成員函數
private:
double price;
};
int PC::num = 0;
int PC::getnum()
{
return PC::num;
}
int main()
{
PC ALIENWARE(15999), ASUS(12999);
cout << "total PC :" << PC::getnum() << endl;
return 0;
}
注:1、靜態成員函數即使在類對象不存在的情況下也能被調用。
2、靜態成員函數只能訪問靜態成員數據、其他靜態成員函數和類外部的其他函數。
3、靜態成員函數沒有 \(this\) 指針。
4、靜態成員變量在類中僅僅是聲明,沒有定義,所以要在類的外面定義,實際上是給靜態成員變量分配內 存。如果不加定義就會報錯,初始化是賦一個初始值,而定義是分配內存。
C++中的繼承
在C++中,繼承允許我們依據另一個類來定義一個類。創建一個新類時,只需指定新類繼承了一個已有的類的成員即可,大大簡化了代碼結構。繼承可以比作集合之間的包含關系,一個父類即是其子類的子集。
注:子類不繼承父類的構造函數、析構函數和拷貝構造函數,重載運算符,友元函數。
子類和父類(基類和派生類)
定義子類所需格式:
class derived_class : access_specifier base_class \\derived_class子類 base_class父類
注:一個類可以派生自多個類。其中 \(access\_specifier\) 可以是 \(public~protected~private\) 中任意一個,若未制訂則默認為 \(private\)。
一個例子:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
private:
double price;
};//父類
void PC::setprice(double prc) {
price = prc;
}
double PC::getprice() {
return price;
}
class ALIENWARE : public PC {
public:
ALIENWARE();
};//子類
ALIENWARE::ALIENWARE() {
cout << "ALIENWARE has been generated." << endl;
}
int main()
{
ALIENWARE m15r4;
m15r4.setprice(15999);
cout << "Price is: " << m15r4.getprice() << endl;
return 0;
}
注:1、子類不可訪問父類的 \(private\) 成員,但可以通過調用父類的 \(public\) 或 \(protected\) 成員來訪問。
2、繼承類型一般使用 \(public\) ,這樣子類繼承的成員的類型都與父類一致(即父類 \(public\) 成員在子類也是 \(public\) , \(protected~private\) 同理)。
子類中的構造函數
上面提到,子類不能直接訪問父類的 \(private\) 成員,而必須通過調用父類的 \(public/protected\) 方法來訪問,所以子類的構造函數是不能直接設置其從父類繼承的成員的。因此,子類構造函數必須使用父類的構造函數。
下面給倆例子,分別是不同的子類構造函數實現方式:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
private:
double price;
};
void PC::setprice(double prc) {
price = prc;
}
double PC::getprice() {
return price;
}
class ALIENWARE : public PC {
public:
double weight;
ALIENWARE(double wgt, const PC& PC_0);
};
ALIENWARE::ALIENWARE(double wgt, const PC& PC_0) : weight(wgt), PC(PC_0)
{
cout << "ALIENWARE has been generated." << endl;
}
//這里有個小細節要注意下,這個構造函數的參數里面父類的形參必需帶個引用&。原因是使用引用就不用初始化父類的對象A了,如果不帶&的話必須先初始化A。為了更好地展示這一點,這部分代碼就沒寫父類的構造函數。
//**在函數調用過程中,如果參數不是引用調用,那就必須要對其進行初始化,如果是引用調用,就沒有影響。**
int main()
{
PC A;
//cout << "A's price is: " << A.price << endl;
//注意到如果沒有自己寫構造函數,而是使用編譯器默認構造函數的話,這個地方A是沒有初始化的,所以會導致 //上面注釋里提到的情況。
//總之編譯器默認構造函數非常坑,建議都自己寫。
ALIENWARE m15r4(4,A);
m15r4.setprice(15999);
cout << "Price is: " << m15r4.getprice() << endl;
cout << "Wight is: " << m15r4.weight << endl;
return 0;
}
PS:如有意獲取更多有關默認構造函數的內容,參見https://blog.csdn.net/bear_n/article/details/72798301。
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
PC(double prc);
private:
double price;
};
//第二種實現方式就必須把父類的構造函數寫出來傳參數
PC::PC(double prc) {
price = prc;
}
void PC::setprice(double prc) {
price = prc;
}
double PC::getprice() {
return price;
}
class ALIENWARE : public PC {
public:
double weight;
ALIENWARE(double wgt, double prc);
};
ALIENWARE::ALIENWARE(double wgt, double prc) : weight(wgt), PC(prc)
{
cout << "ALIENWARE has been generated." << endl;
}
int main()
{
ALIENWARE m15r4(4, 15999);
cout << "Price is: " << m15r4.getprice() << endl;
cout << "Wight is: " << m15r4.weight << endl;
return 0;
}
其實還有一種,含有類對象作為數據成員的類的構造函數。但是這個東西跟子類的構造函數比較相似,就不展開了。
簡單提一下“含有類對象作為數據成員的類”是個什么玩意:
class A {
...
};
class B {
public:
A a;
...
};
//這個B就是“含有類對象作為數據成員的類”。
多繼承
C++允許一個子類繼承自多個父類:
class derived_class : access_specifier base_class_1, access_specifier base_class_2 ...{
...
};
多態
由於C++編譯器遇到子類與父類具有同名但不同功能的函數時,默認調用的是父類中的版本(靜態鏈接),造成了諸多不便,C++便提供了多態來擴展程序的可用性。
C++ 多態意味着調用成員函數時,會根據調用函數的對象的類型來執行不同的函數。
形成多態必須具備三個條件:
1、必須存在繼承關系;
2、繼承關系必須有同名虛函數(其中虛函數是在父類中使用關鍵字virtual
聲明的函數,在子類中重新定 義父類中定義的虛函數時,會告訴編譯器不要靜態鏈接到該函數);
3、存在父類類型的指針或者引用,通過該指針或引用調用虛函數;
虛函數與純虛函數
虛函數是在父類中用關鍵字virtual
聲明的函數。在子類中重新定義父類中的虛函數后,使用特定對象類型調用該函數時,編譯器會動態鏈接到所需的函數。
純虛函數就是一個真的什么都不干的虛函數,格式如下:
virtual typename function() = 0;
純虛函數的作用大概就是節約時間,反正虛函數一般不會被調用。。。
一個例子:
#include<cstdio>
#include<iostream>
using namespace std;
class PC {
public:
void setprice(double prc);
double getprice();
virtual void PRICE() = 0;//用純虛函數就可以偷懶力
PC();
private:
double price;
};//父類
PC::PC() : price(0) {}
double PC::getprice() {
return price;
}
void PC::setprice(double prc) {
price = prc;
}
class ALIENWARE : public PC {
public:
ALIENWARE();
void PRICE() {
cout << "Price of ALIENWARE is: " << this->getprice() << endl;
}
};
class ASUS : public PC {
public:
ASUS();
void PRICE() {
cout << "Price of ASUS is: " << this->getprice() << endl;
}
};
ALIENWARE::ALIENWARE() {
cout << "ALIENWARE has been generated." << endl;
}
ASUS::ASUS() {
cout << "ASUS has been generated." << endl;
}
int main()
{
ALIENWARE m15r4;
ASUS ROG;
m15r4.setprice(15999);
ROG.setprice(14999);
m15r4.PRICE();
ROG.PRICE();
return 0;
}
注:1、含有純虛函數的類叫抽象類,抽象類不能創建對象·定義實例,但可以聲明指向實現該抽象類的具體類的指 針或引用。
2、在有動態分配堆上內存的時候,析構函數必須是虛函數,但沒有必要是純虛的。
3、友元不是成員函數,只有成員函數才可以是虛擬的,因此友元不能是虛擬函數。但可以通過讓友元函數調 用虛擬成員函數來解決友元的虛擬問題。
C++重載運算符和重載函數
C++ 允許在同一作用域中的某個函數和運算符指定多個定義,分別稱為函數重載和運算符重載。也就是說,允許出現多個同名不同功能的函數或運算符。
重載函數
這個沒什么好說的:
#include<cstdio>
#include<iostream>
using namespace std;
void print(int x) {
cout << "integer: " << x << endl;
}
void print(double x) {
cout << "double float: " << x << endl;
}
void print(char x[]) {
cout << "string: " << x << endl;
}
int main()
{
int a = 1; double b = 3.0; char c[] = "Hello";
print(a);
print(b);
print(c);
return 0;
}
注:重載函數時,編譯器不區分類型的引用和類型本身。
重載運算符
這個就比較重要了,但這里不詳細寫。
C++中大部分內置運算符支持重載,這個重載相當於自定義運算。實際上,重載運算符是一種特殊的函數,因此帶有返回類型和參數列表。它的形式如下:
返回值類型 operater要重載的運算符(參數列表);
重載運算符在類里面非常好用,它使得類的對象間的運算成為可能:
#include<cstdio>
#include<iostream>
using namespace std;
class Junior_high_3 {
public:
void setnum(int mal, int fel);
int getnum();
Junior_high_3();
Junior_high_3 operator+(const Junior_high_3& cls) {
Junior_high_3 tot;
tot.male = this->male + cls.male;
tot.female = this->female + cls.female;
return tot;
}
private:
int male, female;
};
Junior_high_3::Junior_high_3() : male(0), female(0) {}
int Junior_high_3::getnum() {
return male + female;
}
void Junior_high_3::setnum(int mal, int fel) {
male = mal, female = fel;
}
int main()
{
Junior_high_3 class1, class2, grade;
int m1, f1, m2, f2;
cout << "Enter the num of boys in class1:" << endl;
cin >> m1;
cout << "Enter the num of girls in class1:" << endl;
cin >> f1;
cout << "Enter the num of boys in class2:" << endl;
cin >> m2;
cout << "Enter the num of girls in class2:" << endl;
cin >> f2;
class1.setnum(m1, f1), class2.setnum(m2, f2);
grade = class1 + class2;
cout << "Our grade has " << grade.getnum() << " students" << endl;
return 0;
}
其他運算符的重載跟這個基本一樣,但有很多細節之處需要注意。
注:不可重載的運算符有. .* ->* :: sizeof ?: #