5.C++類和對(duì)象(上)
面向過(guò)程和面向?qū)ο蟪醪秸J(rèn)識(shí)
C語(yǔ)言是面向過(guò)程的,關(guān)注的是過(guò)程,分析出求解問(wèn)題的步驟,通過(guò)函數(shù)調(diào)用逐步解決問(wèn)題。
C++是基于面向?qū)ο蟮?/strong>,關(guān)注的是對(duì)象,將一件事拆分成不同的對(duì)象,靠對(duì)象之間的交互完成。
以外賣點(diǎn)餐系統(tǒng)為例:
外賣點(diǎn)餐有具體步驟有顧客點(diǎn)餐,商家做餐,外賣員送餐,C語(yǔ)言關(guān)注的是點(diǎn)餐的各個(gè)步驟,即點(diǎn)餐、做餐和送餐的過(guò)程,而C++則是關(guān)注顧客、商家、外賣員這三個(gè)對(duì)象,依托三個(gè)對(duì)象之間的交互來(lái)完成點(diǎn)餐的過(guò)程。
類的引入
C語(yǔ)言中,結(jié)構(gòu)體內(nèi)只能定義變量,而在C++中結(jié)構(gòu)體內(nèi)不僅可以定義變量,還可以定義函數(shù)。
struct Student
{
void SetStudentInfo(const char* name, const char* gender, int age)
{
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void PrintStudentInfo()
{
cout << _name << " " << _gender << " " << _age << endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main()
{
Student s;
s.SetStudentInfo("songxin", "male", 20);
s.PrintStudentInfo();
return 0;
}
struct是C語(yǔ)言中定義結(jié)構(gòu)體的關(guān)鍵字,在C++中更傾向使用class代替
類的定義
class classname
{
//函數(shù)
//成員
};//注意這里有分號(hào)
class為定義類的關(guān)鍵字,classname為類的名稱。{}中為類的主體,注意類的定義后面要加分號(hào)。
類中的元素稱為類的成員:類中的數(shù)據(jù)稱為類的屬性或者成員變量; 類中的函數(shù)稱為類的方法或者成員函數(shù)。
類的兩種定義方式:
聲明和定義都放在類體中,需要注意的是:成員函數(shù)如果在類體中定義,則默認(rèn)會(huì)給編譯器建議使此函數(shù)成為內(nèi)聯(lián)。
聲明和定義分離
一般來(lái)說(shuō)我們更青睞第二種方式,即使這樣寫(xiě)會(huì)麻煩一些。
類的訪問(wèn)限定符及封裝
C語(yǔ)言中的結(jié)構(gòu)體并沒(méi)有訪問(wèn)限定符和封裝的概念,我們可以對(duì)結(jié)構(gòu)體中的內(nèi)容隨意訪問(wèn)并且修改,這就很考驗(yàn)程序員的素養(yǎng),人人都可以修改結(jié)構(gòu)體的數(shù)據(jù),這就會(huì)帶來(lái)一些不可預(yù)知的問(wèn)題。
無(wú)規(guī)矩不成方圓,我們無(wú)法去賭每一個(gè)程序員都是能按照規(guī)范編寫(xiě)程式,因此C++引入了訪問(wèn)限定符來(lái)限制我們對(duì)類中成員的權(quán)限。
訪問(wèn)限定符
C++實(shí)現(xiàn)封裝的方式:用類將對(duì)象的屬性與方法結(jié)合在一塊,讓對(duì)象更加完善,通過(guò)訪問(wèn)權(quán)限選擇性的將其接口提供給外部的用戶使用。
訪問(wèn)限定符如上圖,有三種權(quán)限分別設(shè)置為公有、保護(hù)、私有。
訪問(wèn)限定符說(shuō)明
public修飾的成員在類外可以直接被訪問(wèn)
protected和private修飾的成員在類外不能直接被訪問(wèn)(此處protected和private是類似的)
訪問(wèn)權(quán)限作用域從該訪問(wèn)限定符出現(xiàn)的位置開(kāi)始直到下一個(gè)訪問(wèn)限定符出現(xiàn)時(shí)為止
class的默認(rèn)訪問(wèn)權(quán)限為private,struct為public(因?yàn)閟truct要兼容C)
既然被保護(hù)的成員不能被外部通過(guò)對(duì)象所直接訪問(wèn),那么在成員函數(shù)中呢?
class Time
{
public:
Time(int hour = 0, int minute = 0, int second = 0)
:
_hour(hour),
_minute(minute),
_second(second)
{}
Time(Time& t)
{
_hour = t._hour;//通過(guò)另一個(gè)對(duì)象名直接訪問(wèn)了私有成員函數(shù)
_minute = t._minute;
_second = t._second;
}
private:
int _hour;
int _minute;
int _second;
};
有兩個(gè)解釋方法:
Time是成員函數(shù)屬于類,而訪問(wèn)限定符限制的是外部,在類域中可以隨意訪問(wèn)。
相同class的實(shí)例化出的各個(gè)對(duì)象互為友元。
注意:訪問(wèn)限定符僅在編譯時(shí)有用,當(dāng)數(shù)據(jù)映射到內(nèi)存后,沒(méi)有任何訪問(wèn)限定符上的區(qū)別。
來(lái)看看下面這個(gè)問(wèn)題
C++中struct和class的區(qū)別是什么?
C++需要兼容C語(yǔ)言,所以C++中struct可以當(dāng)成結(jié)構(gòu)體去使用。另外C++中struct還可以用來(lái)定義類。和class是定義類是一樣的,區(qū)別是struct的成員默認(rèn)訪問(wèn)方式是public,class是的成員默認(rèn)訪問(wèn)方式是private。
封裝
面向?qū)ο笕筇匦裕?strong>封裝、繼承、多態(tài)。
在類和對(duì)象階段,我們只研究類的封裝特性,那什么是封裝呢?
封裝:將數(shù)據(jù)和操作數(shù)據(jù)的方法進(jìn)行有機(jī)結(jié)合,隱藏對(duì)象的屬性和實(shí)現(xiàn)細(xì)節(jié),僅對(duì)外公開(kāi)接口來(lái)和對(duì)象進(jìn)行交互。
封裝本質(zhì)上是一種管理:我們?nèi)绾喂芾肀R俑呢?比如如果什么都不管,兵馬俑就被隨意破壞了。那么我們首先建了一座房子把兵馬俑給封裝起來(lái)。但是我們目的不是全封裝起來(lái),不讓別人看。所以我們開(kāi)放了售票通道,可以買票突破封裝在合理的監(jiān)管機(jī)制下進(jìn)去參觀。類也是一樣,我們使用類數(shù)據(jù)和方法都封裝到一下。不想給別人看到的,我們使用protected/private把成員封裝起來(lái)。開(kāi)放一些共有的成員函數(shù)對(duì)成員合理的訪問(wèn)。所以封裝本質(zhì)是管理。
類的作用域
類新定義了一個(gè)作用域,類的所以成員都在類的作用域中。在類外定義成員,需要使用::作用域解析符來(lái)指明成員屬于哪個(gè)類。
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int _age;
};
//類外定義成員函數(shù)需要加上::作用域解析符
void Person::PrintPersonInfo()
{
cout << _name << endl;
cout << _gender << endl;
cout << _age << endl;
}
總之,封裝有利于管理類的對(duì)象,讓對(duì)象的處理操作更加統(tǒng)一,對(duì)于一些我們不想給外界直接訪問(wèn)的成員可以設(shè)置為私有,對(duì)于需要被外部使用的成員設(shè)置為公有,這樣的封裝設(shè)計(jì)讓我們的程式更加規(guī)范安全。
類的實(shí)例化
用類去創(chuàng)建對(duì)象的過(guò)程,稱為類的實(shí)例化。
類形象的說(shuō)就是一個(gè)模具,我們可以使用設(shè)計(jì)好的模具去制作出實(shí)體對(duì)象。
類只聲明了類有哪些成員,而并沒(méi)有為成員分配內(nèi)存空間。
一個(gè)類可以實(shí)例化出多個(gè)對(duì)象,實(shí)例化出的對(duì)象占用實(shí)際的物理空間,存儲(chǔ)類成員變量。
做個(gè)比方。類實(shí)例化出對(duì)象就像現(xiàn)實(shí)中使用建筑設(shè)計(jì)圖建造出房子,類就像是設(shè)計(jì)圖,只設(shè)計(jì)出需要什么東西,但是并沒(méi)有實(shí)體的建筑存在,同樣類也只是一個(gè)設(shè)計(jì),實(shí)例化出的對(duì)象才能實(shí)際存儲(chǔ)數(shù)據(jù),占用物理空間。
類對(duì)象模型
計(jì)算類對(duì)象的大小
class Person
{
public:
void PrintPersonInfo();
private:
int _age;
};
int main()
{
Person p;
cout << sizeof(Person) << endl;
? ?cout << sizeof(p) << endl;
return 0;
}
輸出:
4
4
這里使用sizeof計(jì)算Person類的大小和Person類定義出來(lái)的對(duì)象大小是一樣的。
類對(duì)象的存儲(chǔ)方式
如果對(duì)象中包含類中的所有成員
缺陷:每個(gè)對(duì)象中成員變量是不同的,但是調(diào)用同一份函數(shù),如果按照此種方式存儲(chǔ),當(dāng)一個(gè)類創(chuàng)建多個(gè)對(duì)象時(shí),每個(gè)對(duì)象中都會(huì)保存一份代碼,相同代碼保存多次,浪費(fèi)空間。那么如何解決呢?
只保存成員變量,成員函數(shù)指令放在公共的代碼段
這里需要給大小 說(shuō)說(shuō)內(nèi)存中的分區(qū),C++程序的內(nèi)存格局通常分為四個(gè)區(qū):靜態(tài)區(qū)(static area),代碼區(qū)(code area),棧區(qū)(stack area),堆區(qū)(heap area)(即自由存儲(chǔ)區(qū))。靜態(tài)區(qū)存放全局變量,靜態(tài)數(shù)據(jù)和常量;所有類成員函數(shù)和非成員函數(shù)代碼存放在代碼區(qū);為運(yùn)行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回?cái)?shù)據(jù)、返回地址等存放在棧區(qū);余下的空間都被稱為堆區(qū)。
根據(jù)這個(gè)解釋,我們可以得知在類的定義時(shí),類成員函數(shù)是被放在代碼區(qū),而類的靜態(tài)成員變量是存儲(chǔ)在靜態(tài)區(qū)的,即實(shí)例化的對(duì)象并不包括靜態(tài)變量的創(chuàng)建,因而它是屬于類的。對(duì)于非靜態(tài)成員變量,我們是在類的實(shí)例化過(guò)程中(構(gòu)造對(duì)象)才在棧區(qū)或者堆區(qū)為其分配內(nèi)存,是為每個(gè)對(duì)象生成一個(gè)拷貝,所以它是屬于對(duì)象的。
那么肯定是第二種存儲(chǔ)方式更節(jié)省空間,那么實(shí)際中到底是哪種存儲(chǔ)方式呢?
依然使用剛剛的代碼,簡(jiǎn)單變形一下
//即有成員變量又有成員函數(shù)
class Person1
{
public:
void PrintPersonInfo();
private:
int _age;
};
//僅有成員變量
class Person2
{
private:
int _age;
};
//空類
class Person3
{};
int main()
{
cout << sizeof(Person1) << endl;
? cout << sizeof(Person2) << endl;
? ?cout << sizeof(Person3) << endl;
return 0;
}
輸出:
4
4
1
很顯然,我們計(jì)算對(duì)象的大小和是否含有成員函數(shù)無(wú)關(guān)(也就是說(shuō)成員函數(shù)存儲(chǔ)在代碼區(qū)),對(duì)象中也不存放成員函數(shù)的指針。
結(jié)論:一個(gè)類的大小,實(shí)際就是該類中”成員變量”大小之和,當(dāng)然也要進(jìn)行內(nèi)存對(duì)齊,注意空類的大小,空類比較特殊,編譯器給了空類一個(gè)字節(jié)來(lái)唯一標(biāo)識(shí)這個(gè)類。
注:這里的成員變量之和并不是簡(jiǎn)單的字節(jié)數(shù)相加,而是還要遵循內(nèi)存對(duì)齊規(guī)則。
結(jié)構(gòu)體內(nèi)存對(duì)齊規(guī)則
第一個(gè)成員在與結(jié)構(gòu)體偏移量為0的地址處。
其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。
注意:對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù)與該成員大小的較小值。
VS中默認(rèn)的對(duì)齊數(shù)為8;
結(jié)構(gòu)體總大小為:最大對(duì)齊數(shù)(所有變量類型最大者與默認(rèn)對(duì)齊參數(shù)取最?。┑恼麛?shù)倍。
如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己的最大對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
這里的規(guī)則和C語(yǔ)言的規(guī)則是一樣的,就不再展開(kāi)講了。
this指針
class Date
{
public:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1, d2;
d1.SetDate(2018, 5, 1);
d2.SetDate(2018, 7, 1);
d1.Display();
d2.Display();
return 0;
}
對(duì)于上述程序,d1和d2各自有不同的內(nèi)存空間,可我們的成員函數(shù)SetDate中卻并沒(méi)有指定要對(duì)哪一個(gè)對(duì)象的成員進(jìn)行操作,那么當(dāng)d1調(diào)用時(shí)函數(shù)是如何知道操作的對(duì)象是哪一個(gè)呢?
C++中通過(guò)引入this指針解決該問(wèn)題,即:C++編譯器給每個(gè)“非靜態(tài)的成員函數(shù)“增加了一個(gè)隱藏的指針參數(shù),讓該指針指向當(dāng)前對(duì)象(函數(shù)運(yùn)行時(shí)調(diào)用該函數(shù)的對(duì)象),在函數(shù)體中所有成員變量的操作,都是通過(guò)該指針去訪問(wèn)。只不過(guò)所有的操作對(duì)用戶是透明的,即用戶不需要來(lái)傳遞,編譯器自動(dòng)完成。
注:實(shí)參形參中不能顯式地去寫(xiě)this指針。
因此程序還可以這樣寫(xiě):
class Date
{
public:
void Display()
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
void SetDate(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1, d2;
d1.SetDate(2018, 5, 1);
d2.SetDate(2018, 7, 1);
d1.Display();
d2.Display();
return 0;
}
實(shí)際上我們不寫(xiě)this->成員,比那一起也會(huì)這樣給我們處理。
this指針的特性
this指針的類型:TypeOfClass* const,this指針在函數(shù)體內(nèi)不可修改。
只能在“成員函數(shù)”的內(nèi)部使用。
this指針本質(zhì)上其實(shí)是一個(gè)成員函數(shù)的形參,是對(duì)象調(diào)用成員函數(shù)時(shí),將對(duì)象地址作為實(shí)參傳遞給this形參,對(duì)象中并不存儲(chǔ)this指針。
this指針是成員函數(shù)第一個(gè)隱含的指針形參,一般情況由編譯器通過(guò)ecx寄存器自動(dòng)傳遞,不需要用戶傳遞。
如下兩種寫(xiě)法并無(wú)差別
關(guān)于this指針
this指針的存儲(chǔ)位置在哪里?
臨時(shí)變量都是存儲(chǔ)在棧上的,因此this指針作為形參,存儲(chǔ)在棧區(qū)。
this指針可以為空指針嗎?
class A
{
public:
void PrintA()
{
cout << _a << endl;
}
void Show()
{
cout << "Show()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
? ?p->Show();
? ?p->PrintA();
}
要知道0地址處我們是沒(méi)有訪問(wèn)權(quán)限的,若非法訪問(wèn)程序會(huì)崩潰。
這里的程序其實(shí)Show是可以正常調(diào)用,而PrintA是無(wú)法正常運(yùn)行的。
來(lái)看看PrintA函數(shù)體的匯編指令(部分)
首先Show可以正常調(diào)用,說(shuō)明并不是p為空指針了就無(wú)法通過(guò)p調(diào)用函數(shù)了,調(diào)用函數(shù)訪問(wèn)的是函數(shù)的地址,而雖然它們都是成員函數(shù),但是它們都存儲(chǔ)在公共代碼區(qū),并不是在0地址處去訪問(wèn)函數(shù),所以調(diào)用函數(shù)并沒(méi)有問(wèn)題,問(wèn)題出在PrintA中的?cout << _a << endl;
語(yǔ)句訪問(wèn)了_a成員變量,而p又是指向0地址處,因此就相當(dāng)于訪問(wèn)了0地址處的數(shù)據(jù),因此程序崩潰。
this指針可以為空指針,但切忌通過(guò)nullptr去訪問(wèn)指向的數(shù)據(jù)。
鏈接:https://www.dianjilingqu.com/628685.html