第四章 類和對(duì)象
4.1 類和對(duì)象的基本概念
4.1.1 C和C++中struct區(qū)別
- c語(yǔ)言struct只有變量
- c++語(yǔ)言struct 既有變量箩朴,也有函數(shù)
4.1.2 類的封裝
我們編寫程序的目的是為了解決現(xiàn)實(shí)中的問(wèn)題廓块,而這些問(wèn)題的構(gòu)成都是由各種事物組成亏钩,我們?cè)谟?jì)算機(jī)中要解決這種問(wèn)題,首先要做就是要將這個(gè)問(wèn)題的參與者:事和物抽象到計(jì)算機(jī)程序中欠窒,也就是用程序語(yǔ)言表示現(xiàn)實(shí)的事物覆旭。
那么現(xiàn)在問(wèn)題是如何用程序語(yǔ)言來(lái)表示現(xiàn)實(shí)事物?現(xiàn)實(shí)世界的事物所具有的共性就是每個(gè)事物都具有自身的屬性岖妄,一些自身具有的行為型将,所以如果我們能把事物的屬性和行為表示出來(lái),那么就可以抽象出來(lái)這個(gè)事物荐虐。
比如我們要表示人這個(gè)對(duì)象七兜,在c語(yǔ)言中,我們可以這么表示:
typedef struct _Person{
char name[64];
int age;
}Person;
typedef struct _Aninal{
char name[64];
int age;
int type; //動(dòng)物種類
}Ainmal;
void PersonEat(Person* person){
printf("%s在吃人吃的飯!\n",person->name);
}
void AnimalEat(Ainmal* animal){
printf("%s在吃動(dòng)物吃的飯!\n", animal->name);
}
int main(){
Person person;
strcpy(person.name, "小明");
person.age = 30;
AnimalEat(&person);
return EXIT_SUCCESS;
}
定義一個(gè)結(jié)構(gòu)體用來(lái)表示一個(gè)對(duì)象所包含的屬性福扬,函數(shù)用來(lái)表示一個(gè)對(duì)象所具有的行為腕铸,這樣我們就表示出來(lái)一個(gè)事物惜犀,在c語(yǔ)言中,行為和屬性是分開(kāi)的狠裹,也就是說(shuō)吃飯這個(gè)屬性不屬于某類對(duì)象虽界,而屬于所有的共同的數(shù)據(jù),所以不單單是PeopleEat
可以調(diào)用Person
數(shù)據(jù)涛菠,AnimalEat
也可以調(diào)用Person
數(shù)據(jù)莉御,那么萬(wàn)一調(diào)用錯(cuò)誤,將會(huì)導(dǎo)致問(wèn)題發(fā)生俗冻。
從這個(gè)案例我們應(yīng)該可以體會(huì)到礁叔,屬性和行為應(yīng)該放在一起,一起表示一個(gè)具有屬性和行為的對(duì)象迄薄。
假如某對(duì)象的某項(xiàng)屬性不想被外界獲知琅关,比如說(shuō)漂亮女孩的年齡不想被其他人知道,那么年齡這條屬性應(yīng)該作為女孩自己知道的屬性讥蔽;或者女孩的某些行為不想讓外界知道涣易,只需要自己知道就可以。那么這種情況下勤篮,封裝應(yīng)該再提供一種機(jī)制能夠給屬性和行為的訪問(wèn)權(quán)限控制住都毒。
所以說(shuō)封裝特性包含兩個(gè)方面色罚,一個(gè)是屬性和變量合成一個(gè)整體碰缔,一個(gè)是給屬性和函數(shù)增加訪問(wèn)權(quán)限。
- 封裝
- 把變量(屬性)和函數(shù)(操作)合成一個(gè)整體戳护,封裝在一個(gè)類中
- 對(duì)變量和函數(shù)進(jìn)行訪問(wèn)控制
- 訪問(wèn)權(quán)限
- 在類的內(nèi)部(作用域范圍內(nèi))金抡,沒(méi)有訪問(wèn)權(quán)限之分,所有成員可以相互訪問(wèn)
- 在類的外部(作用域范圍外)腌且,訪問(wèn)權(quán)限才有意義:
public
梗肝,private
,protected
- 在類的外部铺董,只有
public
修飾的成員才能被訪問(wèn)巫击,在沒(méi)有涉及繼承與派生時(shí),private
和protected
是同等級(jí)的精续,外部不允許訪問(wèn)
訪問(wèn)屬性 | 屬性 | 對(duì)象內(nèi)部 | 對(duì)象外部 |
---|---|---|---|
public | 公有 | 可訪問(wèn) | 可訪問(wèn) |
private | 私有 | 可訪問(wèn) | 不可訪問(wèn) |
protected | 保護(hù) | 可訪問(wèn) | 不可訪問(wèn) |
//封裝兩層含義
//1. 屬性和行為合成一個(gè)整體
//2. 訪問(wèn)控制坝锰,現(xiàn)實(shí)事物本身有些屬性和行為是不對(duì)外開(kāi)放
class Person{
//人具有的行為(函數(shù))
public:
void Dese(){ cout << "我有錢,年輕重付,個(gè)子又高顷级,就愛(ài)嘚瑟!" << endl;}
//人的屬性(變量)
public:
int mTall; //多高,可以讓外人知道
protected:
int mMoney; // 有多少錢,只能兒子孫子知道
private:
int mAge; //年齡确垫,不想讓外人知道
};
int main(){
Person p;
p.mTall = 220;
//p.mMoney 保護(hù)成員外部無(wú)法訪問(wèn)
//p.mAge 私有成員外部無(wú)法訪問(wèn)
p.Dese();
return EXIT_SUCCESS;
}
[struct和class的區(qū)別?]
.
class
默認(rèn)訪問(wèn)權(quán)限為private
,struct
默認(rèn)訪問(wèn)權(quán)限為public
.
class A{
int mAge;
};
struct B{
int mAge;
};
void test(){
A a;
B b;
//a.mAge; //無(wú)法訪問(wèn)私有成員
b.mAge; //可正常外部訪問(wèn)
}
4.1.3 將成員變量設(shè)置為private
1. 可賦予客戶端訪問(wèn)數(shù)據(jù)的一致性弓颈。
如果成員變量不是public
帽芽,客戶端唯一能夠訪問(wèn)對(duì)象的方法就是通過(guò)成員函數(shù)。如果類中所有public
權(quán)限的成員都是函數(shù)翔冀,客戶在訪問(wèn)類成員時(shí)只會(huì)默認(rèn)訪問(wèn)函數(shù)导街,不需要考慮訪問(wèn)的成員需不需要添加(),這就省下了許多搔首弄耳的時(shí)間。
2. 可細(xì)微劃分訪問(wèn)控制纤子。
使用成員函數(shù)可使得我們對(duì)變量的控制處理更加精細(xì)菊匿。如果我們讓所有的成員變量為public
,每個(gè)人都可以讀寫它计福。如果我們?cè)O(shè)置為private
跌捆,我們可以實(shí)現(xiàn)“不準(zhǔn)訪問(wèn)”、“只讀訪問(wèn)”象颖、“讀寫訪問(wèn)”佩厚,甚至你可以寫出“只寫訪問(wèn)”。
class AccessLevels{
public:
//對(duì)只讀屬性進(jìn)行只讀訪問(wèn)
int getReadOnly(){ return readOnly; }
//對(duì)讀寫屬性進(jìn)行讀寫訪問(wèn)
void setReadWrite(int val){ readWrite = val; }
int getReadWrite(){ return readWrite; }
//對(duì)只寫屬性進(jìn)行只寫訪問(wèn)
void setWriteOnly(int val){ writeOnly = val; }
private:
int readOnly; //對(duì)外只讀訪問(wèn)
int noAccess; //外部不可訪問(wèn)
int readWrite; //讀寫訪問(wèn)
int writeOnly; //只寫訪問(wèn)
};
4.1.3課堂練習(xí)
請(qǐng)?jiān)O(shè)計(jì)一個(gè)Person
類说订,Person
類具有name
和age
屬性抄瓦,提供初始化函數(shù)(Init
),并提供對(duì)name
和age
的讀寫函數(shù)(set陶冷,get
)钙姊,但必須確保age的賦值在有效范圍內(nèi)(0-100),超出有效范圍,則拒絕賦值埂伦,并提供方法輸出姓名和年齡.(10分鐘)
4.2 面向?qū)ο蟪绦蛟O(shè)計(jì)案例
4.2.1 設(shè)計(jì)立方體類
設(shè)計(jì)立方體類(Cube
)煞额,求出立方體的面積( 2*a*b + 2*a*c + 2*b*c )
和體積( a * b * c)
,分別用全局函數(shù)和成員函數(shù)判斷兩個(gè)立方體是否相等沾谜。
//立方體類
class Cub{
public:
void setL(int l){ mL = l; }
void setW(int w){ mW = w; }
void setH(int h){ mH = h; }
int getL(){ return mL; }
int getW(){ return mW; }
int getH(){ return mH; }
//立方體面積
int caculateS(){ return (mL*mW + mL*mH + mW*mH) * 2; }
//立方體體積
int caculateV(){ return mL * mW * mH; }
//成員方法
bool CubCompare(Cub& c){
if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH()){
return true;
}
return false;
}
private:
int mL; //長(zhǎng)
int mW; //寬
int mH; //高
};
//比較兩個(gè)立方體是否相等
bool CubCompare(Cub& c1, Cub& c2){
if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()){
return true;
}
return false;
}
void test(){
Cub c1, c2;
c1.setL(10);
c1.setW(20);
c1.setH(30);
c2.setL(20);
c2.setW(20);
c2.setH(30);
cout << "c1面積:" << c1.caculateS() << " 體積:" << c1.caculateV() << endl;
cout << "c2面積:" << c2.caculateS() << " 體積:" << c2.caculateV() << endl;
//比較兩個(gè)立方體是否相等
if (CubCompare(c1, c2)){
cout << "c1和c2相等!" << endl;
}
else{
cout << "c1和c2不相等!" << endl;
}
if (c1.CubCompare(c2)){
cout << "c1和c2相等!" << endl;
}
else{
cout << "c1和c2不相等!" << endl;
}
}
4.2.2 點(diǎn)和圓的關(guān)系
設(shè)計(jì)一個(gè)圓形類(AdvCircle
)膊毁,和一個(gè)點(diǎn)類(Point
),計(jì)算點(diǎn)和圓的關(guān)系基跑。
假如圓心坐標(biāo)為x0, y0
, 半徑為r婚温,點(diǎn)的坐標(biāo)為x1, y1
:
1)點(diǎn)在圓上:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) == r*r
2)點(diǎn)在圓內(nèi):(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) < r*r
3)點(diǎn)在圓外:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) > r*r
//點(diǎn)類
class Point{
public:
void setX(int x){ mX = x; }
void setY(int y){ mY = y; }
int getX(){ return mX; }
int getY(){ return mY; }
private:
int mX;
int mY;
};
//圓類
class Circle{
public:
void setP(int x,int y){
mP.setX(x);
mP.setY(y);
}
void setR(int r){ mR = r; }
Point& getP(){ return mP; }
int getR(){ return mR; }
//判斷點(diǎn)和圓的關(guān)系
void IsPointInCircle(Point& point){
int distance = (point.getX() - mP.getX()) * (point.getX() - mP.getX()) + (point.getY() - mP.getY()) * (point.getY() - mP.getY());
int radius = mR * mR;
if (distance < radius){
cout << "Point(" << point.getX() << "," << point.getY() << ")在圓內(nèi)!" << endl;
}
else if (distance > radius){
cout << "Point(" << point.getX() << "," << point.getY() << ")在圓外!" << endl;
}
else{
cout << "Point(" << point.getX() << "," << point.getY() << ")在圓上!" << endl;
}
}
private:
Point mP; //圓心
int mR; //半徑
};
void test(){
//實(shí)例化圓對(duì)象
Circle circle;
circle.setP(20, 20);
circle.setR(5);
//實(shí)例化點(diǎn)對(duì)象
Point point;
point.setX(25);
point.setY(20);
circle.IsPointInCircle(point);
}
4.3 對(duì)象的構(gòu)造和析構(gòu)
4.3.1 初始化和清理
我們大家在購(gòu)買一臺(tái)電腦或者手機(jī),或者其他的產(chǎn)品媳否,這些產(chǎn)品都有一個(gè)初始設(shè)置栅螟,也就是這些產(chǎn)品對(duì)被創(chuàng)建的時(shí)候會(huì)有一個(gè)基礎(chǔ)屬性值。那么隨著我們使用手機(jī)和電腦的時(shí)間越來(lái)越久篱竭,那么電腦和手機(jī)會(huì)慢慢被我們手動(dòng)創(chuàng)建很多文件數(shù)據(jù)力图,某一天我們不用手機(jī)或電腦了,那么我們應(yīng)該將電腦或手機(jī)中我們?cè)黾拥臄?shù)據(jù)刪除掉室抽,保護(hù)自己的信息數(shù)據(jù)搪哪。
從這樣的過(guò)程中,我們體會(huì)一下,所有的事物在起初的時(shí)候都應(yīng)該有個(gè)初始狀態(tài)晓折,當(dāng)這個(gè)事物完成其使命時(shí)惑朦,應(yīng)該及時(shí)清除外界作用于上面的一些信息數(shù)據(jù)。
那么我們c++中OO思想也是來(lái)源于現(xiàn)實(shí)漓概,是對(duì)現(xiàn)實(shí)事物的抽象模擬漾月,具體來(lái)說(shuō),當(dāng)我們創(chuàng)建對(duì)象的時(shí)候,這個(gè)對(duì)象應(yīng)該有一個(gè)初始狀態(tài)胃珍,當(dāng)對(duì)象銷毀之前應(yīng)該銷毀自己創(chuàng)建的一些數(shù)據(jù)梁肿。
對(duì)象的初始化和清理也是兩個(gè)非常重要的安全問(wèn)題,一個(gè)對(duì)象或者變量沒(méi)有初始時(shí)觅彰,對(duì)其使用后果是未知吩蔑,同樣的使用完一個(gè)變量,沒(méi)有及時(shí)清理填抬,也會(huì)造成一定的安全問(wèn)題烛芬。c++為了給我們提供這種問(wèn)題的解決方案,構(gòu)造函數(shù)和析構(gòu)函數(shù)飒责,這兩個(gè)函數(shù)將會(huì)被編譯器自動(dòng)調(diào)用赘娄,完成對(duì)象初始化和對(duì)象清理工作。
無(wú)論你是否喜歡宏蛉,對(duì)象的初始化和清理工作是編譯器強(qiáng)制我們要做的事情遣臼,即使你不提供初始化操作和清理操作,編譯器也會(huì)給你增加默認(rèn)的操作拾并,只是這個(gè)默認(rèn)初始化操作不會(huì)做任何事揍堰,所以編寫類就應(yīng)該順便提供初始化函數(shù)。
為什么初始化操作是自動(dòng)調(diào)用而不是手動(dòng)調(diào)用辟灰?既然是必須操作个榕,那么自動(dòng)調(diào)用會(huì)更好,如果靠程序員自覺(jué)芥喇,那么就會(huì)存在遺漏初始化的情況出現(xiàn)。
4.3.1 構(gòu)造函數(shù)和析構(gòu)函數(shù)
構(gòu)造函數(shù)主要作用在于創(chuàng)建對(duì)象時(shí)為對(duì)象的成員屬性賦值凰萨,構(gòu)造函數(shù)由編譯器自動(dòng)調(diào)用继控,無(wú)須手動(dòng)調(diào)用。
析構(gòu)函數(shù)主要用于對(duì)象銷毀前系統(tǒng)自動(dòng)調(diào)用胖眷,執(zhí)行一些清理工作武通。
構(gòu)造函數(shù)語(yǔ)法:
- 構(gòu)造函數(shù)函數(shù)名和類名相同,沒(méi)有返回值珊搀,不能有void冶忱,但可以有參數(shù)。
- ClassName(){}
析構(gòu)函數(shù)語(yǔ)法:
- 析構(gòu)函數(shù)函數(shù)名是在類名前面加”~”組成,沒(méi)有返回值境析,不能有void,不能有參數(shù)囚枪,不能重載派诬。
- ~ClassName(){}
class Person{
public:
Person(){
cout << "構(gòu)造函數(shù)調(diào)用!" << endl;
pName = (char*)malloc(sizeof("John"));
strcpy(pName, "John");
mTall = 150;
mMoney = 100;
}
~Person(){
cout << "析構(gòu)函數(shù)調(diào)用!" << endl;
if (pName != NULL){
free(pName);
pName = NULL;
}
}
public:
char* pName;
int mTall;
int mMoney;
};
void test(){
Person person;
cout << person.pName << person.mTall << person.mMoney << endl;
}
4.3.1 構(gòu)造函數(shù)的分類及調(diào)用
- 按參數(shù)類型:分為無(wú)參構(gòu)造函數(shù)和有參構(gòu)造函數(shù)
- 按類型分類:普通構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)(復(fù)制構(gòu)造函數(shù))
class Person{
public:
Person(){
cout << "no param constructor!" << endl;
mAge = 0;
}
//有參構(gòu)造函數(shù)
Person(int age){
cout << "1 param constructor!" << endl;
mAge = age;
}
//拷貝構(gòu)造函數(shù)(復(fù)制構(gòu)造函數(shù)) 使用另一個(gè)對(duì)象初始化本對(duì)象
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
//打印年齡
void PrintPerson(){
cout << "Age:" << mAge << endl;
}
private:
int mAge;
};
//1. 無(wú)參構(gòu)造調(diào)用方式
void test01(){
//調(diào)用無(wú)參構(gòu)造函數(shù)
Person person1;
person1.PrintPerson();
//無(wú)參構(gòu)造函數(shù)錯(cuò)誤調(diào)用方式
//Person person2();
//person2.PrintPerson();
}
//2. 調(diào)用有參構(gòu)造函數(shù)
void test02(){
//第一種 括號(hào)法,最常用
Person person01(100);
person01.PrintPerson();
//調(diào)用拷貝構(gòu)造函數(shù)
Person person02(person01);
person02.PrintPerson();
//第二種 匿名對(duì)象(顯示調(diào)用構(gòu)造函數(shù))
Person(200); //匿名對(duì)象链沼,沒(méi)有名字的對(duì)象
Person person03 = Person(300);
person03.PrintPerson();
//注意: 使用匿名對(duì)象初始化判斷調(diào)用哪一個(gè)構(gòu)造函數(shù)默赂,要看匿名對(duì)象的參數(shù)類型
Person person06(Person(400)); //等價(jià)于 Person person06 = Person(400);
person06.PrintPerson();
//第三種 =號(hào)法 隱式轉(zhuǎn)換
Person person04 = 100; //Person person04 = Person(100)
person04.PrintPerson();
//調(diào)用拷貝構(gòu)造
Person person05 = person04; //Person person05 = Person(person04)
person05.PrintPerson();
}
b為A的實(shí)例化對(duì)象,A a = A(b) 和 A(b)的區(qū)別?
.
當(dāng)A(b) 有變量來(lái)接的時(shí)候括勺,那么編譯器認(rèn)為他是一個(gè)匿名對(duì)象缆八,當(dāng)沒(méi)有變量來(lái)接的時(shí)候,編譯器認(rèn)為A(b) 等價(jià)于 A b.
注意:不能調(diào)用拷貝構(gòu)造函數(shù)去初始化匿名對(duì)象,也就是說(shuō)以下代碼不正確:
class Teacher{
public:
Teacher(){
cout << "默認(rèn)構(gòu)造函數(shù)!" << endl;
}
Teacher(const Teacher& teacher){
cout << "拷貝構(gòu)造函數(shù)!" << endl;
}
public:
int mAge;
};
void test(){
Teacher t1;
//error C2086:“Teacher t1”: 重定義
Teacher(t1); //此時(shí)等價(jià)于 Teacher t1;
}
4.3.2 拷貝構(gòu)造函數(shù)的調(diào)用時(shí)機(jī)
- 對(duì)象以值傳遞的方式傳給函數(shù)參數(shù)
- 函數(shù)局部對(duì)象以值傳遞的方式從函數(shù)返回
(vs debug
模式下調(diào)用一次拷貝構(gòu)造疾捍,qt
不調(diào)用任何構(gòu)造) - 用一個(gè)對(duì)象初始化另一個(gè)對(duì)象
class Person{
public:
Person(){
cout << "no param contructor!" << endl;
mAge = 10;
}
Person(int age){
cout << "param constructor!" << endl;
mAge = age;
}
Person(const Person& person){
cout << "copy constructor!" << endl;
mAge = person.mAge;
}
~Person(){
cout << "destructor!" << endl;
}
public:
int mAge;
};
//1. 舊對(duì)象初始化新對(duì)象
void test01(){
Person p(10);
Person p1(p);
Person p2 = Person(p);
Person p3 = p; // 相當(dāng)于Person p2 = Person(p);
}
//2. 傳遞的參數(shù)是普通對(duì)象奈辰,函數(shù)參數(shù)也是普通對(duì)象,傳遞將會(huì)調(diào)用拷貝構(gòu)造
void doBussiness(Person p){}
void test02(){
Person p(10);
doBussiness(p);
}
//3. 函數(shù)返回局部對(duì)象
Person MyBusiness(){
Person p(10);
cout << "局部p:" << (int*)&p << endl;
return p;
}
void test03(){
//vs release乱豆、qt下沒(méi)有調(diào)用拷貝構(gòu)造函數(shù)
//vs debug下調(diào)用一次拷貝構(gòu)造函數(shù)
Person p = MyBusiness();
cout << "局部p:" << (int*)&p << endl;
}
Test03結(jié)果說(shuō)明:
編譯器存在一種對(duì)返回值的優(yōu)化技術(shù),RVO(Return Value Optimization).在vs debug
模式下并沒(méi)有進(jìn)行這種優(yōu)化冯挎,所以函數(shù)MyBusiness
中創(chuàng)建p對(duì)象,調(diào)用了一次構(gòu)造函數(shù)咙鞍,當(dāng)編譯器發(fā)現(xiàn)你要返回這個(gè)局部的對(duì)象時(shí)房官,編譯器通過(guò)調(diào)用拷貝構(gòu)造生成一個(gè)臨時(shí)Person
對(duì)象返回,然后調(diào)用p的析構(gòu)函數(shù)续滋。
我們從常理來(lái)分析的話翰守,這個(gè)匿名對(duì)象和這個(gè)局部的p
對(duì)象是相同的兩個(gè)對(duì)象,那么如果能直接返回p
對(duì)象疲酌,就會(huì)省去一個(gè)拷貝構(gòu)造和一個(gè)析構(gòu)函數(shù)的開(kāi)銷蜡峰,在程序中一個(gè)對(duì)象的拷貝也是非常耗時(shí)的,如果減少這種拷貝和析構(gòu)的次數(shù)朗恳,那么從另一個(gè)角度來(lái)說(shuō)湿颅,也是編譯器對(duì)程序執(zhí)行效率上進(jìn)行了優(yōu)化。
所以在這里粥诫,編譯器偷偷幫我們做了一層優(yōu)化:
當(dāng)我們這樣去調(diào)用: Person p = MyBusiness();
編譯器偷偷將我們的代碼更改為:
void MyBussiness(Person& _result){
_result.X:X(); //調(diào)用Person默認(rèn)拷貝構(gòu)造函數(shù)
//.....對(duì)_result進(jìn)行處理
return;
}
int main(){
Person p; //這里只分配空間油航,不初始化
MyBussiness(p);
}
4.3.3 構(gòu)造函數(shù)調(diào)用規(guī)則
默認(rèn)情況下,c++編譯器至少為我們寫的類增加3個(gè)函數(shù)
1.默認(rèn)構(gòu)造函數(shù)(無(wú)參怀浆,函數(shù)體為空)
2.默認(rèn)析構(gòu)函數(shù)(無(wú)參谊囚,函數(shù)體為空)
3.默認(rèn)拷貝構(gòu)造函數(shù),對(duì)類中非靜態(tài)成員屬性簡(jiǎn)單值拷貝如果用戶定義拷貝構(gòu)造函數(shù)执赡,c++不會(huì)再提供任何默認(rèn)構(gòu)造函數(shù)
如果用戶定義了普通構(gòu)造(非拷貝)镰踏,c++不在提供默認(rèn)無(wú)參構(gòu)造,但是會(huì)提供默認(rèn)拷貝構(gòu)造
4.3.4 深拷貝和淺拷貝
4.3.4.1 淺拷貝
同一類型的對(duì)象之間可以賦值沙合,使得兩個(gè)對(duì)象的成員變量的值相同奠伪,兩個(gè)對(duì)象仍然是獨(dú)立的兩個(gè)對(duì)象,這種情況被稱為淺拷貝.
一般情況下,淺拷貝沒(méi)有任何副作用绊率,但是當(dāng)類中有指針谨敛,并且指針指向動(dòng)態(tài)分配的內(nèi)存空間,析構(gòu)函數(shù)做了動(dòng)態(tài)內(nèi)存釋放的處理即舌,會(huì)導(dǎo)致內(nèi)存問(wèn)題佣盒。
4.3.4.2 深拷貝
當(dāng)類中有指針,并且此指針有動(dòng)態(tài)分配空間顽聂,析構(gòu)函數(shù)做了釋放處理肥惭,往往需要自定義拷貝構(gòu)造函數(shù),自行給指針動(dòng)態(tài)分配空間紊搪,深拷貝蜜葱。
class Person{
public:
Person(char* name,int age){
pName = (char*)malloc(strlen(name) + 1);
strcpy(pName,name);
mAge = age;
}
//增加拷貝構(gòu)造函數(shù)
Person(const Person& person){
pName = (char*)malloc(strlen(person.pName) + 1);
strcpy(pName, person.pName);
mAge = person.mAge;
}
~Person(){
if (pName != NULL){
free(pName);
}
}
private:
char* pName;
int mAge;
};
void test(){
Person p1("Edward",30);
//用對(duì)象p1初始化對(duì)象p2,調(diào)用c++提供的默認(rèn)拷貝構(gòu)造函數(shù)
Person p2 = p1;
}
4.3.4 多個(gè)對(duì)象構(gòu)造和析構(gòu)
4.3.4.1 初始化列表
構(gòu)造函數(shù)和其他函數(shù)不同,除了有名字耀石,參數(shù)列表牵囤,函數(shù)體之外還有初始化列表。
初始化列表簡(jiǎn)單使用:
class Person{
public:
#if 0
//傳統(tǒng)方式初始化
Person(int a,int b,int c){
mA = a;
mB = b;
mC = c;
}
#endif
//初始化列表方式初始化
Person(int a, int b, int c):mA(a),mB(b),mC(c){}
void PrintPerson(){
cout << "mA:" << mA << endl;
cout << "mB:" << mB << endl;
cout << "mC:" << mC << endl;
}
private:
int mA;
int mB;
int mC;
};
注意:初始化成員列表(參數(shù)列表)只能在構(gòu)造函數(shù)使用滞伟。
4.3.4.2 類對(duì)象作為成員
在類中定義的數(shù)據(jù)成員一般都是基本的數(shù)據(jù)類型揭鳞。但是類中的成員也可以是對(duì)象,叫做對(duì)象成員梆奈。
C++中對(duì)對(duì)象的初始化是非常重要的操作野崇,當(dāng)創(chuàng)建一個(gè)對(duì)象的時(shí)候,c++編譯器必須確保調(diào)用了所有子對(duì)象的構(gòu)造函數(shù)亩钟。如果所有的子對(duì)象有默認(rèn)構(gòu)造函數(shù)乓梨,編譯器可以自動(dòng)調(diào)用他們。但是如果子對(duì)象沒(méi)有默認(rèn)的構(gòu)造函數(shù)清酥,或者想指定調(diào)用某個(gè)構(gòu)造函數(shù)怎么辦扶镀?
那么是否可以在類的構(gòu)造函數(shù)直接調(diào)用子類的屬性完成初始化呢臭觉?但是如果子類的成員屬性是私有的鹦马,我們是沒(méi)有辦法訪問(wèn)并完成初始化的荸频。
解決辦法非常簡(jiǎn)單:對(duì)于子類調(diào)用構(gòu)造函數(shù)旭从,c++為此提供了專門的語(yǔ)法和悦,即構(gòu)造函數(shù)初始化列表退疫。
當(dāng)調(diào)用構(gòu)造函數(shù)時(shí),首先按各對(duì)象成員在類定義中的順序(和參數(shù)列表的順序無(wú)關(guān)) 依次調(diào)用它們的構(gòu)造函數(shù)鸽素,對(duì)這些對(duì)象初始化褒繁,最后再調(diào)用本身的函數(shù)體。也就是說(shuō)馍忽,先調(diào)用對(duì)象成員的構(gòu)造函數(shù)棒坏,再調(diào)用本身的構(gòu)造函數(shù)。
析構(gòu)函數(shù)和構(gòu)造函數(shù)調(diào)用順序相反遭笋,先構(gòu)造坝冕,后析構(gòu)。
//汽車類
class Car{
public:
Car(){
cout << "Car 默認(rèn)構(gòu)造函數(shù)!" << endl;
mName = "大眾汽車";
}
Car(string name){
cout << "Car 帶參數(shù)構(gòu)造函數(shù)!" << endl;
mName = name;
}
~Car(){
cout << "Car 析構(gòu)函數(shù)!" << endl;
}
public:
string mName;
};
//拖拉機(jī)
class Tractor{
public:
Tractor(){
cout << "Tractor 默認(rèn)構(gòu)造函數(shù)!" << endl;
mName = "爬土坡專用拖拉機(jī)";
}
Tractor(string name){
cout << "Tractor 帶參數(shù)構(gòu)造函數(shù)!" << endl;
mName = name;
}
~Tractor(){
cout << "Tractor 析構(gòu)函數(shù)!" << endl;
}
public:
string mName;
};
//人類
class Person{
public:
#if 1
//類mCar不存在合適的構(gòu)造函數(shù)
Person(string name){
mName = name;
}
#else
//初始化列表可以指定調(diào)用構(gòu)造函數(shù)
Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){
cout << "Person 構(gòu)造函數(shù)!" << endl;
}
#endif
void GoWorkByCar(){
cout << mName << "開(kāi)著" << mCar.mName << "去上班!" << endl;
}
void GoWorkByTractor(){
cout << mName << "開(kāi)著" << mTractor.mName << "去上班!" << endl;
}
~Person(){
cout << "Person 析構(gòu)函數(shù)!" << endl;
}
private:
string mName;
Car mCar;
Tractor mTractor;
};
void test(){
//Person person("寶馬", "東風(fēng)拖拉機(jī)", "趙四");
Person person("劉能");
person.GoWorkByCar();
person.GoWorkByTractor();
}
4.3.5 explicit關(guān)鍵字
c++提供了關(guān)鍵字explicit
瓦呼,禁止通過(guò)構(gòu)造函數(shù)進(jìn)行的隱式轉(zhuǎn)換喂窟。聲明為explicit
的構(gòu)造函數(shù)不能在隱式轉(zhuǎn)換中使用。
[explicit注意]
- explicit用于修飾構(gòu)造函數(shù),防止隱式轉(zhuǎn)化央串。
- 是針對(duì)單參數(shù)的構(gòu)造函數(shù)(或者除了第一個(gè)參數(shù)外其余參數(shù)都有默認(rèn)值的多參構(gòu)造)而言磨澡。
class MyString{
public:
explicit MyString(int n){
cout << "MyString(int n)!" << endl;
}
MyString(const char* str){
cout << "MyString(const char* str)" << endl;
}
};
int main(){
//給字符串賦值?還是初始化稳摄?
//MyString str1 = 1;
MyString str2(10);
//寓意非常明確,給字符串賦值
MyString str3 = "abcd";
MyString str4("abcd");
return EXIT_SUCCESS;
}
4.3.6 動(dòng)態(tài)對(duì)象創(chuàng)建
當(dāng)我們創(chuàng)建數(shù)組的時(shí)候弃锐,總是需要提前預(yù)定數(shù)組的長(zhǎng)度,然后編譯器分配預(yù)定長(zhǎng)度的數(shù)組空間,在使用數(shù)組的時(shí)饶碘,會(huì)有這樣的問(wèn)題瑟曲,數(shù)組也許空間太大了,浪費(fèi)空間烦衣,也許空間不足涣脚,所以對(duì)于數(shù)組來(lái)講,如果能根據(jù)需要來(lái)分配空間大小再好不過(guò)险耀。
所以動(dòng)態(tài)的意思意味著不確定性。
為了解決這個(gè)普遍的編程問(wèn)題,在運(yùn)行中可以創(chuàng)建和銷毀對(duì)象是最基本的要求搞乏。當(dāng)然c早就提供了動(dòng)態(tài)內(nèi)存分配(dynamic memory allocation),函數(shù)malloc
和free
可以在運(yùn)行時(shí)從堆中分配存儲(chǔ)單元。
然而這些函數(shù)在c++中不能很好的運(yùn)行,因?yàn)樗荒軒臀覀兺瓿蓪?duì)象的初始化工作匣椰。
4.3.6.1 對(duì)象創(chuàng)建
當(dāng)創(chuàng)建一個(gè)c++對(duì)象時(shí)會(huì)發(fā)生兩件事:
- 為對(duì)象分配內(nèi)存
- 調(diào)用構(gòu)造函數(shù)來(lái)初始化那塊內(nèi)存
第一步我們能保證實(shí)現(xiàn)齐媒,需要我們確保第二步一定能發(fā)生邀杏。c++強(qiáng)迫我們這么做是因?yàn)槭褂梦闯跏蓟膶?duì)象是程序出錯(cuò)的一個(gè)重要原因拷恨。
4.3.6.2 C動(dòng)態(tài)分配內(nèi)存方法
為了在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存小泉,c在他的標(biāo)準(zhǔn)庫(kù)中提供了一些函數(shù),malloc
以及它的變種calloc
和realloc
,釋放內(nèi)存的free
,這些函數(shù)是有效的、但是原始的,需要程序員理解和小心使用配喳。為了使用c的動(dòng)態(tài)內(nèi)存分配函數(shù)在堆上創(chuàng)建一個(gè)類的實(shí)例,我們必須這樣做:
class Person{
public:
Person(){
mAge = 20;
pName = (char*)malloc(strlen("john")+1);
strcpy(pName, "john");
}
void Init(){
mAge = 20;
pName = (char*)malloc(strlen("john")+1);
strcpy(pName, "john");
}
void Clean(){
if (pName != NULL){
free(pName);
}
}
public:
int mAge;
char* pName;
};
int main(){
//分配內(nèi)存
Person* person = (Person*)malloc(sizeof(Person));
if(person == NULL){
return 0;
}
//調(diào)用初始化函數(shù)
person->Init();
//清理對(duì)象
person->Clean();
//釋放person對(duì)象
free(person);
return EXIT_SUCCESS;
}
問(wèn)題:
- 程序員必須確定對(duì)象的長(zhǎng)度。
-
malloc
返回一個(gè)void指針少欺,c++不允許將void賦值給其他任何指針配乓,必須強(qiáng)轉(zhuǎn)崎页。 -
malloc
可能申請(qǐng)內(nèi)存失敗,所以必須判斷返回值來(lái)確保內(nèi)存分配成功翁巍。 - 用戶在使用對(duì)象之前必須記住對(duì)他初始化杈曲,構(gòu)造函數(shù)不能顯示調(diào)用初始化(構(gòu)造函數(shù)是由編譯器調(diào)用)洒嗤,用戶有可能忘記調(diào)用初始化函數(shù)。
c的動(dòng)態(tài)內(nèi)存分配函數(shù)太復(fù)雜,容易令人混淆呈野,是不可接受的轮蜕,c++中我們推薦使用運(yùn)算符new 和 delete.
4.3.6.3 new operator
C++中解決動(dòng)態(tài)內(nèi)存分配的方案是把創(chuàng)建一個(gè)對(duì)象所需要的操作都結(jié)合在一個(gè)稱為new
的運(yùn)算符里。當(dāng)用new
創(chuàng)建一個(gè)對(duì)象時(shí)葱蝗,它就在堆里為對(duì)象分配內(nèi)存并調(diào)用構(gòu)造函數(shù)完成初始化皂甘。
Person* person = new Person;
相當(dāng)于:
Person* person = (Person*)malloc(sizeof(Person));
if(person == NULL){
return 0;
}
person->Init();
New
操作符能確定在調(diào)用構(gòu)造函數(shù)初始化之前內(nèi)存分配是成功的佛析,所有不用顯式確定調(diào)用是否成功。
現(xiàn)在我們發(fā)現(xiàn)在堆里創(chuàng)建對(duì)象的過(guò)程變得簡(jiǎn)單了,只需要一個(gè)簡(jiǎn)單的表達(dá)式披坏,它帶有內(nèi)置的長(zhǎng)度計(jì)算玫氢、類型轉(zhuǎn)換和安全檢查。這樣在堆創(chuàng)建一個(gè)對(duì)象和在棧里創(chuàng)建對(duì)象一樣簡(jiǎn)單牢屋。
4.3.6.4 delete operator
new
表達(dá)式的反面是delete
表達(dá)式皱炉。delete
表達(dá)式先調(diào)用析構(gòu)函數(shù)多搀,然后釋放內(nèi)存赌髓。正如new
表達(dá)式返回一個(gè)指向?qū)ο蟮闹羔樢粯樱?code>delete需要一個(gè)對(duì)象的地址锁蠕。
delete
只適用于由new
創(chuàng)建的對(duì)象夷野。
如果使用一個(gè)由malloc
或者calloc
或者realloc
創(chuàng)建的對(duì)象使用delete
,這個(gè)行為是未定義的。因?yàn)榇蠖鄶?shù)new
和delete
的實(shí)現(xiàn)機(jī)制都使用了malloc
和free
,所以很可能沒(méi)有調(diào)用析構(gòu)函數(shù)就釋放了內(nèi)存荣倾。
如果正在刪除的對(duì)象的指針是NULL
,將不發(fā)生任何事悯搔,因此建議在刪除指針后,立即把指針賦值為NULL
,以免對(duì)它刪除兩次在刺,對(duì)一些對(duì)象刪除兩次可能會(huì)產(chǎn)生某些問(wèn)題输玷。
class Person{
public:
Person(){
cout << "無(wú)參構(gòu)造函數(shù)!" << endl;
pName = (char*)malloc(strlen("undefined") + 1);
strcpy(pName, "undefined");
mAge = 0;
}
Person(char* name, int age){
cout << "有參構(gòu)造函數(shù)!" << endl;
pName = (char*)malloc(strlen(name) + 1);
strcpy(pName, name);
mAge = age;
}
void ShowPerson(){
cout << "Name:" << pName << " Age:" << mAge << endl;
}
~Person(){
cout << "析構(gòu)函數(shù)!" << endl;
if (pName != NULL){
delete pName;
pName = NULL;
}
}
public:
char* pName;
int mAge;
};
void test(){
Person* person1 = new Person;
Person* person2 = new Person("John",33);
person1->ShowPerson();
person2->ShowPerson();
delete person1;
delete person2;
}
4.3.6.5 用于數(shù)組的new和delete
使用new
和delete
在堆上創(chuàng)建數(shù)組非常容易。
//創(chuàng)建字符數(shù)組
char* pStr = new char[100];
//創(chuàng)建整型數(shù)組
int* pArr1 = new int[100];
//創(chuàng)建整型數(shù)組并初始化
int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//釋放數(shù)組內(nèi)存
delete[] pStr;
delete[] pArr1;
delete[] pArr2;
當(dāng)創(chuàng)建一個(gè)對(duì)象數(shù)組的時(shí)候喘落,必須對(duì)數(shù)組中的每一個(gè)對(duì)象調(diào)用構(gòu)造函數(shù)茴恰,除了在棧上可以聚合初始化雕沉,必須提供一個(gè)默認(rèn)的構(gòu)造函數(shù)丈攒。
class Person{
public:
Person(){
pName = (char*)malloc(strlen("undefined") + 1);
strcpy(pName, "undefined");
mAge = 0;
}
Person(char* name, int age){
pName = (char*)malloc(sizeof(name));
strcpy(pName, name);
mAge = age;
}
~Person(){
if (pName != NULL){
delete pName;
}
}
public:
char* pName;
int mAge;
};
void test(){
//棧聚合初始化
Person person[] = { Person("john", 20), Person("Smith", 22) };
cout << person[1].pName << endl;
//創(chuàng)建堆上對(duì)象數(shù)組必須提供構(gòu)造函數(shù)
Person* workers = new Person[20];
}
4.3.6.6 delete void*可能會(huì)出錯(cuò)
如果對(duì)一個(gè)void
*指針執(zhí)行delete
操作绞蹦,這將可能成為一個(gè)程序錯(cuò)誤绩蜻,除非指針指向的內(nèi)容是非常簡(jiǎn)單的,因?yàn)樗鼘⒉粓?zhí)行析構(gòu)函數(shù).以下代碼未調(diào)用析構(gòu)函數(shù)窍蓝,導(dǎo)致可用內(nèi)存減少土涝。
class Person{
public:
Person(char* name, int age){
pName = (char*)malloc(sizeof(name));
strcpy(pName,name);
mAge = age;
}
~Person(){
if (pName != NULL){
delete pName;
}
}
public:
char* pName;
int mAge;
};
void test(){
void* person = new Person("john",20);
delete person;
}
問(wèn)題:
malloc
欧募、free
和new
遥缕、delete
可以混搭使用嗎鲸伴?也就是說(shuō)malloc
分配的內(nèi)存品腹,可以調(diào)用delete
嗎?通過(guò)new
創(chuàng)建的對(duì)象寺鸥,可以調(diào)用free
來(lái)釋放嗎柑肴?
4.3.6.7 使用new和delete采用相同形式
Person* person = new Person[10];
delete person;
以上代碼有什么問(wèn)題嗎?(vs
下直接中斷氮双、qt
下析構(gòu)函數(shù)調(diào)用一次)
使用了new
也搭配使用了delete
效诅,問(wèn)題在于Person
有10個(gè)對(duì)象胀滚,那么其他9個(gè)對(duì)象可能沒(méi)有調(diào)用析構(gòu)函數(shù),也就是說(shuō)其他9個(gè)對(duì)象可能刪除不完全乱投,因?yàn)樗鼈兊奈鰳?gòu)函數(shù)沒(méi)有被調(diào)用咽笼。
我們現(xiàn)在清楚使用new
的時(shí)候發(fā)生了兩件事: 一、分配內(nèi)存戚炫;二剑刑、調(diào)用構(gòu)造函數(shù),那么調(diào)用delete的時(shí)候也有兩件事:一双肤、析構(gòu)函數(shù)施掏;二、釋放內(nèi)存茅糜。
那么剛才我們那段代碼最大的問(wèn)題在于:person
指針指向的內(nèi)存中到底有多少個(gè)對(duì)象其监,因?yàn)檫@個(gè)決定應(yīng)該有多少個(gè)析構(gòu)函數(shù)應(yīng)該被調(diào)用。換句話說(shuō)限匣,person
指針指向的是一個(gè)單一的對(duì)象還是一個(gè)數(shù)組對(duì)象,由于單一對(duì)象和數(shù)組對(duì)象的內(nèi)存布局是不同的毁菱。更明確的說(shuō)米死,數(shù)組所用的內(nèi)存通常還包括“數(shù)組大小記錄”,使得delete的時(shí)候知道應(yīng)該調(diào)用幾次析構(gòu)函數(shù)贮庞。單一對(duì)象的話就沒(méi)有這個(gè)記錄峦筒。單一對(duì)象和數(shù)組對(duì)象的內(nèi)存布局可理解為下圖:
本圖只是為了說(shuō)明,編譯器不一定如此實(shí)現(xiàn)窗慎,但是很多編譯器是這樣做的物喷。
當(dāng)我們使用一個(gè)delete
的時(shí)候卤材,我們必須讓delete
知道指針指向的內(nèi)存空間中是否存在一個(gè)“數(shù)組大小記錄”的辦法就是我們告訴它。當(dāng)我們使用delete[]
峦失,那么delete
就知道是一個(gè)對(duì)象數(shù)組扇丛,從而清楚應(yīng)該調(diào)用幾次析構(gòu)函數(shù)。
結(jié)論:
如果在new
表達(dá)式中使用[]尉辑,必須在相應(yīng)的delete
表達(dá)式中也使用[].如果在new
表達(dá)式中不使用[], 一定不要在相應(yīng)的delete
表達(dá)式中使用[].
4.3.7 靜態(tài)成員
在類定義中帆精,它的成員(包括成員變量和成員函數(shù)),這些成員可以用關(guān)鍵字static聲明為靜態(tài)的隧魄,稱為靜態(tài)成員卓练。
不管這個(gè)類創(chuàng)建了多少個(gè)對(duì)象,靜態(tài)成員只有一個(gè)拷貝购啄,這個(gè)拷貝被所有屬于這個(gè)類的對(duì)象共享襟企。
4.3.7.1 靜態(tài)成員變量
在一個(gè)類中,若將一個(gè)成員變量聲明為static
狮含,這種成員稱為靜態(tài)成員變量顽悼。與一般的數(shù)據(jù)成員不同,無(wú)論建立了多少個(gè)對(duì)象辉川,都只有一個(gè)靜態(tài)數(shù)據(jù)的拷貝表蝙。靜態(tài)成員變量,屬于某個(gè)類乓旗,所有對(duì)象共享府蛇。
靜態(tài)變量,是在編譯階段就分配空間屿愚,對(duì)象還沒(méi)有創(chuàng)建時(shí)汇跨,就已經(jīng)分配空間。
- 靜態(tài)成員變量必須在類中聲明妆距,在類外定義穷遂。
- 靜態(tài)數(shù)據(jù)成員不屬于某個(gè)對(duì)象,在為對(duì)象分配空間中不包括靜態(tài)成員所占空間娱据。
- 靜態(tài)數(shù)據(jù)成員可以通過(guò)類名或者對(duì)象名來(lái)引用蚪黑。
class Person{
public:
//類的靜態(tài)成員屬性
static int sNum;
private:
static int sOther;
};
//類外初始化,初始化時(shí)不加static
int Person::sNum = 0;
int Person::sOther = 0;
int main(){
//1. 通過(guò)類名直接訪問(wèn)
Person::sNum = 100;
cout << "Person::sNum:" << Person::sNum << endl;
//2. 通過(guò)對(duì)象訪問(wèn)
Person p1, p2;
p1.sNum = 200;
cout << "p1.sNum:" << p1.sNum << endl;
cout << "p2.sNum:" << p2.sNum << endl;
//3. 靜態(tài)成員也有訪問(wèn)權(quán)限中剩,類外不能訪問(wèn)私有成員
//cout << "Person::sOther:" << Person::sOther << endl;
Person p3;
//cout << "p3.sOther:" << p3.sOther << endl;
system("pause");
return EXIT_SUCCESS;
}
4.3.7.2 靜態(tài)成員函數(shù)
在類定義中忌穿,前面有static說(shuō)明的成員函數(shù)稱為靜態(tài)成員函數(shù)。靜態(tài)成員函數(shù)使用方式和靜態(tài)變量一樣结啼,同樣在對(duì)象沒(méi)有創(chuàng)建前掠剑,即可通過(guò)類名調(diào)用。靜態(tài)成員函數(shù)主要為了訪問(wèn)靜態(tài)變量郊愧,但是朴译,不能訪問(wèn)普通成員變量井佑。
靜態(tài)成員函數(shù)的意義,不在于信息共享眠寿,數(shù)據(jù)溝通躬翁,而在于管理靜態(tài)數(shù)據(jù)成員,完成對(duì)靜態(tài)數(shù)據(jù)成員的封裝澜公。
- 靜態(tài)成員函數(shù)只能訪問(wèn)靜態(tài)變量姆另,不能訪問(wèn)普通成員變量
- 靜態(tài)成員函數(shù)的使用和靜態(tài)成員變量一樣
- 靜態(tài)成員函數(shù)也有訪問(wèn)權(quán)限
- 普通成員函數(shù)可訪問(wèn)靜態(tài)成員變量、也可以訪問(wèn)非經(jīng)常成員變量
class Person{
public:
//普通成員函數(shù)可以訪問(wèn)static和non-static成員屬性
void changeParam1(int param){
mParam = param;
sNum = param;
}
//靜態(tài)成員函數(shù)只能訪問(wèn)static成員屬性
static void changeParam2(int param){
//mParam = param; //無(wú)法訪問(wèn)
sNum = param;
}
private:
static void changeParam3(int param){
//mParam = param; //無(wú)法訪問(wèn)
sNum = param;
}
public:
int mParam;
static int sNum;
};
//靜態(tài)成員屬性類外初始化
int Person::sNum = 0;
int main(){
//1. 類名直接調(diào)用
Person::changeParam2(100);
//2. 通過(guò)對(duì)象調(diào)用
Person p;
p.changeParam2(200);
//3. 靜態(tài)成員函數(shù)也有訪問(wèn)權(quán)限
//Person::changeParam3(100); //類外無(wú)法訪問(wèn)私有靜態(tài)成員函數(shù)
//Person p1;
//p1.changeParam3(200);
return EXIT_SUCCESS;
}
4.3.7.3 const靜態(tài)成員屬性
如果一個(gè)類的成員坟乾,既要實(shí)現(xiàn)共享迹辐,又要實(shí)現(xiàn)不可改變,那就用 static const
修飾甚侣。
定義靜態(tài)const數(shù)據(jù)成員時(shí)明吩,最好在類內(nèi)部初始化。
class Person{
public:
//static const int mShare = 10;
const static int mShare = 10; //只讀區(qū)殷费,不可修改
};
int main(){
cout << Person::mShare << endl;
//Person::mShare = 20;
return EXIT_SUCCESS;
}
4.3.7.4 靜態(tài)成員實(shí)現(xiàn)單例模式
單例模式是一種常用的軟件設(shè)計(jì)模式印荔。在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例的特殊類。通過(guò)單例模式可以保證系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例而且該實(shí)例易于外界訪問(wèn)详羡,從而方便對(duì)實(shí)例個(gè)數(shù)的控制并節(jié)約系統(tǒng)資源仍律。如果希望在系統(tǒng)中某個(gè)類的對(duì)象只能存在一個(gè),單例模式是最好的解決方案实柠。
Singleton(單例):在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例水泉,同時(shí)它提供一個(gè)靜態(tài)的getInstance()
工廠方法,讓客戶可以訪問(wèn)它的唯一實(shí)例窒盐;為了防止在外部對(duì)其實(shí)例化草则,將其默認(rèn)構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)設(shè)計(jì)為私有;在單例類內(nèi)部定義了一個(gè)Singleton
類型的靜態(tài)對(duì)象蟹漓,作為外部共享的唯一實(shí)例炕横。
用單例模式,模擬公司員工使用打印機(jī)場(chǎng)景葡粒,打印機(jī)可以打印員工要輸出的內(nèi)容份殿,并且可以累積打印機(jī)使用次數(shù)。
class Printer{
public:
static Printer* getInstance(){ return pPrinter;}
void PrintText(string text){
cout << "打印內(nèi)容:" << text << endl;
cout << "已打印次數(shù):" << mTimes << endl;
cout << "--------------" << endl;
mTimes++;
}
private:
Printer(){ mTimes = 0; }
Printer(const Printer&){}
private:
static Printer* pPrinter;
int mTimes;
};
Printer* Printer::pPrinter = new Printer;
void test(){
Printer* printer = Printer::getInstance();
printer->PrintText("離職報(bào)告!");
printer->PrintText("入職合同!");
printer->PrintText("提交代碼!");
}
4.4 C++面向?qū)ο竽P统跆?/h2>
4.4.1 成員變量和函數(shù)的存儲(chǔ)
在c語(yǔ)言中嗽交,“分開(kāi)來(lái)聲明的伯铣,也就是說(shuō),語(yǔ)言本身并沒(méi)有支持“數(shù)據(jù)”和“函數(shù)”之間的關(guān)聯(lián)性我們把這種程序方法稱為“程序性的”轮纫,由一組“分布在各個(gè)以功能為導(dǎo)航的函數(shù)中”的算法驅(qū)動(dòng),它們處理的是共同的外部數(shù)據(jù)焚鲜。
c++實(shí)現(xiàn)了“封裝”掌唾,那么數(shù)據(jù)(成員屬性)和操作(成員函數(shù))是什么樣的呢放前?
“數(shù)據(jù)”和“處理數(shù)據(jù)的操作(函數(shù))”是分開(kāi)存儲(chǔ)的。
- c++中的非靜態(tài)數(shù)據(jù)成員直接內(nèi)含在類對(duì)象中糯彬,就像c struct一樣凭语。
- 成員函數(shù)(member function)雖然內(nèi)含在
class
聲明之內(nèi),卻不出現(xiàn)在對(duì)象中撩扒。 - 每一個(gè)非內(nèi)聯(lián)成員函數(shù)(non-inline member function)只會(huì)誕生一份函數(shù)實(shí)例.
class MyClass01{
public:
int mA;
};
class MyClass02{
public:
int mA;
static int sB;
};
class MyClass03{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
public:
int mA;
static int sB;
};
class MyClass04{
public:
void printMyClass(){
cout << "hello world!" << endl;
}
static void ShowMyClass(){
cout << "hello world似扔!" << endl;
}
public:
int mA;
static int sB;
};
int main(){
MyClass01 mclass01;
MyClass02 mclass02;
MyClass03 mclass03;
MyClass04 mclass04;
cout << "MyClass01:" << sizeof(mclass01) << endl; //4
//靜態(tài)數(shù)據(jù)成員并不保存在類對(duì)象中
cout << "MyClass02:" << sizeof(mclass02) << endl; //4
//非靜態(tài)成員函數(shù)不保存在類對(duì)象中
cout << "MyClass03:" << sizeof(mclass03) << endl; //4
//靜態(tài)成員函數(shù)也不保存在類對(duì)象中
cout << "MyClass04:" << sizeof(mclass04) << endl; //4
return EXIT_SUCCESS;
}
通過(guò)上面的案例,我們可以的得出:C++類對(duì)象中的變量和函數(shù)是分開(kāi)存儲(chǔ)搓谆。
4.4.2 this指針
4.4.2.1 this指針工作原理
通過(guò)上例我們知道炒辉,c++的數(shù)據(jù)和操作也是分開(kāi)存儲(chǔ),并且每一個(gè)非內(nèi)聯(lián)成員函數(shù)(non-inline member function)只會(huì)誕生一份函數(shù)實(shí)例泉手,也就是說(shuō)多個(gè)同類型的對(duì)象會(huì)共用一塊代碼
那么問(wèn)題是:這一塊代碼是如何區(qū)分那個(gè)對(duì)象調(diào)用自己的呢黔寇?
c++通過(guò)提供特殊的對(duì)象指針,this指針斩萌,解決上述問(wèn)題缝裤。
This
指針指向被調(diào)用的成員函數(shù)所屬的對(duì)象。
c++規(guī)定颊郎,this指針是隱含在對(duì)象成員函數(shù)內(nèi)的一種指針憋飞。當(dāng)一個(gè)對(duì)象被創(chuàng)建后,它的每一個(gè)成員函數(shù)都含有一個(gè)系統(tǒng)自動(dòng)生成的隱含指針this
姆吭,用以保存這個(gè)對(duì)象的地址榛做,也就是說(shuō)雖然我們沒(méi)有寫上this
指針,編譯器在編譯的時(shí)候也是會(huì)加上的猾编。因此this
也稱為“指向本對(duì)象的指針”瘤睹,this
指針并不是對(duì)象的一部分,不會(huì)影響sizeof
(對(duì)象)的結(jié)果答倡。
this
指針是C++實(shí)現(xiàn)封裝的一種機(jī)制轰传,它將對(duì)象和該對(duì)象調(diào)用的成員函數(shù)連接在一起,在外部看來(lái)瘪撇,每一個(gè)對(duì)象都擁有自己的函數(shù)成員获茬。一般情況下,并不寫this
倔既,而是讓系統(tǒng)進(jìn)行默認(rèn)設(shè)置恕曲。
this指針永遠(yuǎn)指向當(dāng)前對(duì)象。
成員函數(shù)通過(guò)this
指針即可知道操作的是那個(gè)對(duì)象的數(shù)據(jù)渤涌。This
指針是一種隱含指針佩谣,它隱含于每個(gè)類的非靜態(tài)成員函數(shù)中。This
指針無(wú)需定義实蓬,直接使用即可茸俭。
注意:靜態(tài)成員函數(shù)內(nèi)部沒(méi)有this指針吊履,靜態(tài)成員函數(shù)不能操作非靜態(tài)成員變量。
c++編譯器對(duì)普通成員函數(shù)的內(nèi)部處理
4.4.2.2 this指針的使用
- 當(dāng)形參和成員變量同名時(shí)调鬓,可用
this
指針來(lái)區(qū)分 - 在類的非靜態(tài)成員函數(shù)中返回對(duì)象本身艇炎,可使用
return *this.
class Person{
public:
//1. 當(dāng)形參名和成員變量名一樣時(shí),this指針可用來(lái)區(qū)分
Person(string name,int age){
//name = name;
//age = age; //輸出錯(cuò)誤
this->name = name;
this->age = age;
}
//2. 返回對(duì)象本身的引用
//重載賦值操作符
//其實(shí)也是兩個(gè)參數(shù)腾窝,其中隱藏了一個(gè)this指針
Person PersonPlusPerson(Person& person){
string newname = this->name + person.name;
int newage = this->age + person.age;
Person newperson(newname, newage);
return newperson;
}
void ShowPerson(){
cout << "Name:" << name << " Age:" << age << endl;
}
public:
string name;
int age;
};
//3. 成員函數(shù)和全局函數(shù)(Perosn對(duì)象相加)
Person PersonPlusPerson(Person& p1,Person& p2){
string newname = p1.name + p2.name;
int newage = p1.age + p2.age;
Person newperson(newname,newage);
return newperson;
}
int main(){
Person person("John",100);
person.ShowPerson();
cout << "---------" << endl;
Person person1("John",20);
Person person2("001", 10);
//1.全局函數(shù)實(shí)現(xiàn)兩個(gè)對(duì)象相加
Person person3 = PersonPlusPerson(person1, person2);
person1.ShowPerson();
person2.ShowPerson();
person3.ShowPerson();
//2. 成員函數(shù)實(shí)現(xiàn)兩個(gè)對(duì)象相加
Person person4 = person1.PersonPlusPerson(person2);
person4.ShowPerson();
system("pause");
return EXIT_SUCCESS;
}
4.4.2.3 const修飾成員函數(shù)
- 用const修飾的成員函數(shù)時(shí)缀踪,
const
修飾this
指針指向的內(nèi)存區(qū)域,成員函數(shù)體內(nèi)不可以修改本類中的任何普通成員變量虹脯, - 當(dāng)成員變量類型符前用
mutable
修飾時(shí)例外驴娃。
//const修飾成員函數(shù)
class Person{
public:
Person(){
this->mAge = 0;
this->mID = 0;
}
//在函數(shù)括號(hào)后面加上const,修飾成員變量不可修改,除了mutable變量
void sonmeOperate() const{
//this->mAge = 200; //mAge不可修改
this->mID = 10;
}
void ShowPerson(){
cout << "ID:" << mID << " mAge:" << mAge << endl;
}
private:
int mAge;
mutable int mID;
};
int main(){
Person person;
person.sonmeOperate();
person.ShowPerson();
system("pause");
return EXIT_SUCCESS;
}
4.4.2.4 const修飾對(duì)象(常對(duì)象)
- 常對(duì)象只能調(diào)用
const
的成員函數(shù) - 常對(duì)象可訪問(wèn)
const
或非const
數(shù)據(jù)成員,不能修改归形,除非成員用mutable
修飾
class Person{
public:
Person(){
this->mAge = 0;
this->mID = 0;
}
void ChangePerson() const{
mAge = 100;
mID = 100;
}
void ShowPerson(){
this->mAge = 1000;
cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
}
public:
int mAge;
mutable int mID;
};
void test(){
const Person person;
//1. 可訪問(wèn)數(shù)據(jù)成員
cout << "Age:" << person.mAge << endl;
//person.mAge = 300; //不可修改
person.mID = 1001; //但是可以修改mutable修飾的成員變量
//2. 只能訪問(wèn)const修飾的函數(shù)
//person.ShowPerson();
person.ChangePerson();
}
4.5 友元
類的主要特點(diǎn)之一是數(shù)據(jù)隱藏托慨,即類的私有成員無(wú)法在類的外部(作用域之外)訪問(wèn)。但是暇榴,有時(shí)候需要在類的外部訪問(wèn)類的私有成員厚棵,怎么辦?
解決方法是使用友元函數(shù)蔼紧,友元函數(shù)是一種特權(quán)函數(shù)婆硬,c++允許這個(gè)特權(quán)函數(shù)訪問(wèn)私有成員。這一點(diǎn)從現(xiàn)實(shí)生活中也可以很好的理解:
比如你的家奸例,有客廳彬犯,有你的臥室,那么你的客廳是Public
的查吊,所有來(lái)的客人都可以進(jìn)去谐区,但是你的臥室是私有的,也就是說(shuō)只有你能進(jìn)去逻卖,但是呢宋列,你也可以允許你的閨蜜好基友進(jìn)去。
程序員可以把一個(gè)全局函數(shù)评也、某個(gè)類中的成員函數(shù)炼杖、甚至整個(gè)類聲明為友元。
4.5.1 友元語(yǔ)法
-
friend
關(guān)鍵字只出現(xiàn)在聲明處 - 其他類盗迟、類成員函數(shù)坤邪、全局函數(shù)都可聲明為友元
- 友元函數(shù)不是類的成員,不帶
this
指針 - 友元函數(shù)可訪問(wèn)對(duì)象任意成員屬性罚缕,包括私有屬性
class Building;
//友元類
class MyFriend{
public:
//友元成員函數(shù)
void LookAtBedRoom(Building& building);
void PlayInBedRoom(Building& building);
};
class Building{
//全局函數(shù)做友元函數(shù)
friend void CleanBedRoom(Building& building);
#if 0
//成員函數(shù)做友元函數(shù)
friend void MyFriend::LookAtBedRoom(Building& building);
friend void MyFriend::PlayInBedRoom(Building& building);
#else
//友元類
friend class MyFriend;
#endif
public:
Building();
public:
string mSittingRoom;
private:
string mBedroom;
};
void MyFriend::LookAtBedRoom(Building& building){
cout << "我的朋友參觀" << building.mBedroom << endl;
}
void MyFriend::PlayInBedRoom(Building& building){
cout << "我的朋友玩耍在" << building.mBedroom << endl;
}
//友元全局函數(shù)
void CleanBedRoom(Building& building){
cout << "友元全局函數(shù)訪問(wèn)" << building.mBedroom << endl;
}
Building::Building(){
this->mSittingRoom = "客廳";
this->mBedroom = "臥室";
}
int main(){
Building building;
MyFriend myfriend;
CleanBedRoom(building);
myfriend.LookAtBedRoom(building);
myfriend.PlayInBedRoom(building);
system("pause");
return EXIT_SUCCESS;
}
友元類注意
1.友元關(guān)系不能被繼承艇纺。
2.友元關(guān)系是單向的,類A是類B的朋友,但類B不一定是類A的朋友黔衡。
3.友元關(guān)系不具有傳遞性消约。類B是類A的朋友,類C是類B的朋友员帮,但類C不一定是類A的朋友。
思考: c++是純面向?qū)ο蟮膯幔?/strong>
如果一個(gè)類被聲明為friend
,意味著它不是這個(gè)類的成員函數(shù)导饲,卻可以修改這個(gè)類的私有成員捞高,而且必須列在類的定義中,因此他是一個(gè)特權(quán)函數(shù)渣锦。c++不是完全的面向?qū)ο笳Z(yǔ)言硝岗,而只是一個(gè)混合產(chǎn)品。增加friend關(guān)鍵字只是用來(lái)解決一些實(shí)際問(wèn)題袋毙,這也說(shuō)明這種語(yǔ)言是不純的型檀。畢竟c++設(shè)計(jì)的目的是為了實(shí)用性,而不是追求理想的抽象听盖。 --- Thinking in C++
4.5.2 課堂練習(xí)
請(qǐng)編寫電視機(jī)類胀溺,電視機(jī)有開(kāi)機(jī)和關(guān)機(jī)狀態(tài),有音量皆看,有頻道仓坞,提供音量操作的方法,頻道操作的方法腰吟。由于電視機(jī)只能逐一調(diào)整頻道无埃,不能指定頻道,增加遙控類毛雇,遙控類除了擁有電視機(jī)已有的功能嫉称,再增加根據(jù)輸入調(diào)臺(tái)功能。
提示:遙控器可作為電視機(jī)類的友元類灵疮。
class Remote;
class Television{
friend class Remote;
public:
enum{ On,Off }; //電視狀態(tài)
enum{ minVol,maxVol = 100 }; //音量從0到100
enum{ minChannel = 1,maxChannel = 255 }; //頻道從1到255
Television(){
mState = Off;
mVolume = minVol;
mChannel = minChannel;
}
//打開(kāi)電視機(jī)
void OnOrOff(){
this->mState = (this->mState == On ? Off : On);
}
//調(diào)高音量
void VolumeUp(){
if (this->mVolume >= maxVol){
return;
}
this->mVolume++;
}
//調(diào)低音量
void VolumeDown(){
if (this->mVolume <= minVol){
return;
}
this->mVolume--;
}
//更換電視頻道
void ChannelUp(){
if (this->mChannel >= maxChannel){
return;
}
this->mChannel++;
}
void ChannelDown(){
if (this->mChannel <= minChannel){
return;
}
this->mChannel--;
}
//展示當(dāng)前電視狀態(tài)信息
void ShowTeleState(){
cout << "開(kāi)機(jī)狀態(tài):" << (mState == On ? "已開(kāi)機(jī)" : "已關(guān)機(jī)") << endl;
if (mState == On){
cout << "當(dāng)前音量:" << mVolume << endl;
cout << "當(dāng)前頻道:" << mChannel << endl;
}
cout << "-------------" << endl;
}
private:
int mState; //電視狀態(tài)织阅,開(kāi)機(jī),還是關(guān)機(jī)
int mVolume; //電視機(jī)音量
int mChannel; //電視頻道
};
//電視機(jī)調(diào)臺(tái)只能一個(gè)一個(gè)的調(diào)始藕,遙控可以指定頻道
//電視遙控器
class Remote{
public:
Remote(Television* television){
pTelevision = television;
}
public:
void OnOrOff(){
pTelevision->OnOrOff();
}
//調(diào)高音量
void VolumeUp(){
pTelevision->VolumeUp();
}
//調(diào)低音量
void VolumeDown(){
pTelevision->VolumeDown();
}
//更換電視頻道
void ChannelUp(){
pTelevision->ChannelUp();
}
void ChannelDown(){
pTelevision->ChannelDown();
}
//設(shè)置頻道 遙控新增功能
void SetChannel(int channel){
if (channel < Television::minChannel || channel > Television::maxChannel){
return;
}
pTelevision->mChannel = channel;
}
//顯示電視當(dāng)前信息
void ShowTeleState(){
pTelevision->ShowTeleState();
}
private:
Television* pTelevision;
};
//直接操作電視
void test01(){
Television television;
television.ShowTeleState();
television.OnOrOff(); //開(kāi)機(jī)
television.VolumeUp(); //增加音量+1
television.VolumeUp(); //增加音量+1
television.VolumeUp(); //增加音量+1
television.VolumeUp(); //增加音量+1
television.ChannelUp(); //頻道+1
television.ChannelUp(); //頻道+1
television.ShowTeleState();
}
//通過(guò)遙控操作電視
void test02(){
//創(chuàng)建電視
Television television;
//創(chuàng)建遙控
Remote remote(&television);
remote.OnOrOff();
remote.ChannelUp();//頻道+1
remote.ChannelUp();//頻道+1
remote.ChannelUp();//頻道+1
remote.VolumeUp();//音量+1
remote.VolumeUp();//音量+1
remote.VolumeUp();//音量+1
remote.VolumeUp();//音量+1
remote.ShowTeleState();
}
4.5 強(qiáng)化訓(xùn)練(數(shù)組類封裝)
MyArray.h
#ifndef MYARRAY_H
#define MYARRAY_H
class MyArray{
public:
//無(wú)參構(gòu)造函數(shù)蒲稳,用戶沒(méi)有指定容量,則初始化為100
MyArray();
//有參構(gòu)造函數(shù)伍派,用戶指定容量初始化
explicit MyArray(int capacity);
//用戶操作接口
//根據(jù)位置添加元素
void SetData(int pos, int val);
//獲得指定位置數(shù)據(jù)
int GetData(int pos);
//尾插法
void PushBack(int val);
//獲得長(zhǎng)度
int GetLength();
//析構(gòu)函數(shù)江耀,釋放數(shù)組空間
~MyArray();
private:
int mCapacity; //數(shù)組一共可容納多少個(gè)元素
int mSize; //當(dāng)前有多少個(gè)元素
int* pAdress; //指向存儲(chǔ)數(shù)據(jù)的空間
};
#endif
MyArray.cpp
#include"MyArray.h"
MyArray::MyArray(){
this->mCapacity = 100;
this->mSize = 0;
//在堆開(kāi)辟空間
this->pAdress = new int[this->mCapacity];
}
//有參構(gòu)造函數(shù),用戶指定容量初始化
MyArray::MyArray(int capacity){
this->mCapacity = capacity;
this->mSize = 0;
//在堆開(kāi)辟空間
this->pAdress = new int[capacity];
}
//根據(jù)位置添加元素
void MyArray::SetData(int pos, int val){
if (pos < 0 || pos > mCapacity - 1){
return;
}
pAdress[pos] = val;
}
//獲得指定位置數(shù)據(jù)
int MyArray::GetData(int pos){
return pAdress[pos];
}
//尾插法
void MyArray::PushBack(int val){
if (mSize >= mCapacity){
return;
}
this->pAdress[mSize] = val;
this->mSize++;
}
//獲得長(zhǎng)度
int MyArray::GetLength(){
return this->mSize;
}
//析構(gòu)函數(shù)诉植,釋放數(shù)組空間
MyArray::~MyArray(){
if (this->pAdress != nullptr){
delete[] this->pAdress;
}
}
TestMyArray.cpp
#include"MyArray.h"
void test(){
//創(chuàng)建數(shù)組
MyArray myarray(50);
//數(shù)組中插入元素
for (int i = 0; i < 50; i++){
//尾插法
myarray.PushBack(i);
//myarray.SetData(i, i);
}
//打印數(shù)組中元素
for (int i = 0; i < myarray.GetLength(); i++){
cout << myarray.GetData(i) << " ";
}
cout << endl;
}