新聞中心
點擊鏈接加入群【C語言】:http://jq.qq.com/?_wv=1027&k=2H9sgjG
上一小節(jié)中,我們留下了幾個小問題,不知道大家有沒有做預(yù)習(xí)哦。
問題一:THIS指針 問題二:運算符重載
話說這個THIS指針是個什么東東呢,我們在寫一個類的時候,為這個類添加了成員變量,添加了成員方法,我們就可以用這個類生成對象來訪問這個對象的成員變量及成員函數(shù)啦。
但是問題是它是怎么知道這些成員變量的位置的呢?舉個例子:
0012FF64 CC CC CC CC 燙燙
0012FF68 01 10 38 00 ..8.
0012FF6C 0E 00 00 00 ....
0012FF70 1F 00 00 00 ....
上面是我們的一個str對象,它占 16個字節(jié),前四個字節(jié),其實是一個allocator分配器對象(還記得basic_string的數(shù)據(jù)成員嗎,保護成員),這個類的大小是1B,但是為了字節(jié)對齊,被擴充為4B,第二行數(shù)據(jù)是指向堆區(qū)的數(shù)據(jù)滴,第三個指的是當(dāng)前堆區(qū)中的數(shù)據(jù)的長度,第四個指的是當(dāng)前第二個數(shù)據(jù)指向的空間的大小,包括沒有數(shù)據(jù)的空間。如上面的結(jié)果是,現(xiàn)在堆區(qū)中有 31個字節(jié)的空間,已經(jīng)使用了14個空間,空間首地址是 0X00381001。
好我們簡單的分析了一下string類型。那么我們就以這個類為基礎(chǔ)來研究一下我們今天的知識點吧。
#include
using namespace std; //這個還沒給大家介紹呢,大家先這么用著,它叫做命名空間
int main()
{
string str("abcdefghijklmn");
int index = str.find('b');
return 0;
}
//我先對上面的命名空間做一個簡單的介紹,比如微軟做一個解決方案,這個解決方案可能會涉及30個項目,可能需要幾百個人去做開發(fā),但是誰也不能保證每個人所用的函數(shù)名及變量名都是不同的,比如A君負(fù)責(zé)項目A并寫了一個函數(shù) FUN,B君負(fù)責(zé)項目B也寫了一個函數(shù)FUN,那么這兩個項目最終合體的時候就會沖突啦,所以,我們可以讓每個人都為自己的項目中的內(nèi)容加一個命名空間,比如A 的命名空間 叫 NMA,B的叫 NMB, 那么我們在調(diào)用A君的FUN時 就可以用 NMA::FUN,同樣NMB::FUN 也可以訪問到B的函數(shù),但是如果在自己的項目中,我們就用自己定義的一些函數(shù),那么我們就可以使用全局性的聲明 using namespace NMA; 這樣,我們所使用的函數(shù)都是來自NMA中定義的啦,先簡單介紹到這里,大家有個印象就可以了,這些操作最終會讓編譯系統(tǒng)為我們的函數(shù)加上某種意義上的范圍。
我們可以很輕松的找到變量 str的首地址,也知道它的大小就是16字節(jié),那么我們來看看
5: string str("abcdefghijklmn");
0040112D lea eax,[ebp-24h]
00401130 push eax
00401131 push offset string "abcdefghijklmn" (0042601c)
00401136 lea ecx,[ebp-1Ch]
00401139 call @ILT+110(std::basic_string
0040113E mov dword ptr [ebp-4],0
看這里,上面壓入?yún)?shù),我們不關(guān)心都是些什么數(shù)據(jù),我們主要關(guān)心 lea ecx,[ebp-1Ch],大家多測試幾次,看看這個ecx的值是什么,在這里我就直接告訴大家啦,這個ecx的值記錄的是 str的首地址,怎么樣,奇怪嗎?其實不奇怪滴,在string的函數(shù)里面要訪問它的數(shù)據(jù)成員,肯定要知道數(shù)據(jù)在什么位置上,而這個位置就是通過ecx這個寄存器傳到函數(shù)里面去滴,我們把ecx這個寄存器叫做 this指針寄存器,把這種調(diào)用方式叫做 thiscall,這里是一個重點,大家要好好理解,大家也可以再看看它調(diào)用其它函數(shù)時是不是也通過ecx傳遞了str的首地址呢。
6: int index = str.find('b');
00401145 push 0
00401147 push 62h
00401149 lea ecx,[ebp-1Ch]
0040114C call @ILT+100(std::basic_string
00401151 mov dword ptr [ebp-20h],eax
同樣的,調(diào)用find函數(shù),也發(fā)現(xiàn)了這句話 00401149 lea ecx,[ebp-1Ch] ,這就是我們傳說中的This指針啦。在函數(shù)里面,我們就可以通過this關(guān)鍵字來訪問這個對象里面的數(shù)據(jù)啦。
class CTest
{
public:
int m_age;
public:
void SetAge(int age)
{
this->m_age = age;
}
};
int main()
{
CTest test;
test.SetAge(15);
return 0;
}
14: test.SetAge(15);
00401038 push 0Fh
0040103A lea ecx,[ebp-4] //傳遞THIS指針
0040103D call @ILT+0(CTest::SetAge) (00401005)
現(xiàn)在,我們跟進(jìn)這個函數(shù),看一下使用this指針的時候,內(nèi)部的動作
00401070 push ebp //保存環(huán)境
00401071 mov ebp,esp
00401073 sub esp,44h
00401076 push ebx
00401077 push esi
00401078 push edi
00401079 push ecx //先將this指針壓棧,為什么呢?因為下面的循環(huán)操作需要使用ecx哦,ECX主要用來循環(huán)記數(shù)哦
0040107A lea edi,[ebp-44h]
0040107D mov ecx,11h //使用了ECX,所以前面要將它壓棧保存起來
00401082 mov eax,0CCCCCCCCh
00401087 rep stos dword ptr [edi]
00401089 pop ecx //還原ECX的值
0040108A mov dword ptr [ebp-4],ecx //將ECX 的值 寫在 這個函數(shù)棧楨的最下面,這也就是我們的THIS指針,以后都是通過它來訪問數(shù)據(jù)哦,看到指針的 //強大了吧
8: this->m_age = age;
0040108D mov eax,dword ptr [ebp-4]
00401090 mov ecx,dword ptr [ebp+8]
00401093 mov dword ptr [eax],ecx
這里是操作,這個地方比較簡單啦,
mov eax,this
mov ecx,參數(shù)1也就是age
mov [this],ecx //就是將age賦值給 this指向的位置
這就是傳說中的THIS指針,我們暫時先介紹這么多,哦對了,還有一點,大家想像一下*this會是什么東東呢?對象自己嘛,哈哈,是不是很容易理解,那么我們也就知道了,在函數(shù)內(nèi)部要訪問自己的成員可以加上this-> ,也可以不加,但是如上面的例子,參數(shù)名如果也叫 m_age,那么這個時候,m_age = m_age;這兩個m_age都是指的參數(shù),所以,我們要想賦值給對象的m_age,就需要使用this->m_age = m_age(參數(shù));這個現(xiàn)象叫做什么- - ,我給忘了,反正大家應(yīng)該能理解啦。
好啦,THIS指針已經(jīng)基本上解決,我們再來看看運算符重載,在學(xué)一個東東之前,我們一定要搞清它的意義,這個運算符重載的意義在哪里呢?
我們已經(jīng)學(xué)了類了,我們就可以用面向?qū)ο蟮乃枷雭砻枋鍪挛?,比如我們現(xiàn)在描述一下數(shù)學(xué)中的復(fù)數(shù)(a+bi) , 應(yīng)該有實部和虛部,我們就可以將實部和虛部抽取出來做為得復(fù)數(shù)類的成員,那么我們還知道兩個復(fù)數(shù)可以進(jìn)行加減等運算,問題也就是在這里出現(xiàn)滴,怎么運算呢?對吧,跟 兩個整數(shù)相加減是不一樣的,它們應(yīng)該有它們自己的運算法則,這個時候我們就可以使用運算符重載來改寫運算符對它們的行為,這就是它的意義啦。
我們來看一下complex這個復(fù)數(shù)類,當(dāng)然啦,STL已經(jīng)幫我們實現(xiàn)好啦。我們就來分析它吧:
在MSDN中輸入complex然后查詢,已找到的主題會顯示有兩個,那么我們點第一個,關(guān)鍵第二個是什么呢?第二個其實就是構(gòu)造函數(shù)啦,哈哈。
我們會看到有許多函數(shù),不過我們今天不研究函數(shù),主要研究運算符重載,運算符重載的方式 返回值類型 operator運算符(參數(shù)1,參數(shù)2...){} ,應(yīng)該很容易理解吧。
所以我們來看下,如果我們要實現(xiàn)兩個復(fù)數(shù)的加法,怎么寫呢? 返回值:應(yīng)該是個復(fù)數(shù),運算符+,參數(shù)應(yīng)該是2個復(fù)數(shù),那么我們來寫一下吧
complex
{
return complex
}
還有,乘法就不一樣啦,它有它的運算法則哦,不過前輩們都已經(jīng)幫我們寫好啦。
比如我們要比較兩個復(fù)數(shù)是不是相等,怎么辦?肯定要比較實部和虛部是不是都相等,這個操作需要重載 == 運算符
bool operator==(const complex
好,我們上面的內(nèi)容都是可以隨便拿兩個對象來使用的,如
#include
using namespace std;
int main()
{
complex
complex
complex
return 0;
}
如果,我們想要讓 c1=c1+c2 ,也就是 c1+=c2; 這個時候 ,我們就要重載 +=運算符啦,而且,我們還要記得THIS指針哦,所以,
complex& operator+=(const T& rhs);
上面的代碼能看明白吧,就是將 rhs 加到 調(diào)用者自身上啦,要理解兩者的區(qū)別哦
complex
c1+=c2; 這個是將c2加到c1上,然后再將結(jié)果給c1,我們看到 這里面使用的返回值和參數(shù)都是用的引用哦,其實它們內(nèi)部是怎么實現(xiàn)的呢?
很顯然,rhs搞成const引用是沒有什么問題滴,因為我們只是利用它的數(shù)據(jù),并不修改它的數(shù)據(jù),所以沒有必要做數(shù)據(jù)拷貝,只需要用一個引用(指針)能訪問到原數(shù)據(jù)即可啦。
那么返回的是什么東東呢 ,這個問題很好呢?
返回對象自己,很難理解是吧,就是*this啦,我們可以通過返回*this來返回對象自己,來實現(xiàn)級聯(lián)調(diào)用,還記得我們之前講過級聯(lián)調(diào)用嘛。
#include
using namespace std;
class CTest
{
public:
CTest& funA()
{
cout<<"funA is called"< return *this; } CTest& funB() { cout<<"funB is called"< return *this; } CTest& funC() { cout<<"funC is called"< return *this; } }; int main() { CTest test; test.funA().funB().funC().funB().funA().funB().funC().funA(); //級聯(lián)調(diào)用 return 0; } 上面的示例演示了級聯(lián)調(diào)用,那么大家接觸過級聯(lián)調(diào)用嗎,其實cout<< << << 這個就是級聯(lián)調(diào)用。 我們來看看cout是個什么東西, extern _CRTIMP ostream cout; 表示它是一個 ostream的對象,那么我們再去看看ostream typedef basic_ostream 在basic_ostream中,我們可以看到他們都返回 *THIS ,在這里不需要研究其它代碼,學(xué)習(xí)的時候一定要注意不要死扣一個點哦,那叫鉆牛角尖。 _Myt& operator<<(_Myt& (__cdecl *_F)(_Myt&)) {return ((*_F)(*this)); } //返回*this _Myt& operator<<(_Myios& (__cdecl *_F)(_Myios&)) {(*_F)(*(_Myios *)this); return (*this); } //返回*this _Myt& operator<<(ios_base& (__cdecl *_F)(ios_base&)) {(*_F)(*(ios_base *)this); return (*this); } //返回*this _Myt& operator<<(_Bool _X) {iostate _St = goodbit; const sentry _Ok(*this); if (_Ok) {const _Nput& _Fac = _USE(getloc(), _Nput); _TRY_IO_BEGIN if (_Fac.put(_Iter(rdbuf()), *this, fill(), _X).failed()) _St |= badbit; _CATCH_IO_END } setstate(_St); return (*this); } //返回*this 其實還有許多,我們會看到它們都是返回的對象自身,返回值類型都是 類名& : typedef basic_ostream<_E, _Tr> _Myt; 這樣您也就大概明白了級聯(lián)調(diào)用啦。所以,要記住一點 cout是標(biāo)準(zhǔn)輸出對象,就像我們在C中講過的 stdout對應(yīng)的結(jié)構(gòu)一樣,同樣的還是 cin,cerr。只不過是cout功能更強大啦,但是我們一般不怎么使用它,我們更多的還是去使用C語言中的函數(shù),所以,這些東西做為了解,知道這么回事就行啦。你能體會到C語言的短小精悍了吧。 運算符重載還有許多,希望大家看一下 C++ PRIMER 第四版,其實大家有了我分析的這些基礎(chǔ)后,再去看書,那就是很容易的事情啦。 總之大家要了解,運算符重載出現(xiàn)的意義,而且我們以后還會看到對指針操作的重載,主要用在迭代器中,這是相當(dāng)重要的章節(jié),我們在以后會慢慢深入滴??傊蠹乙涀?,我們并不是用C++做開發(fā),而是用其它的一庫進(jìn)行特定場合的開發(fā),但是這些內(nèi)容都是基礎(chǔ),你需要的只是理解好它們的思想,意義,當(dāng)你忘記某個運算符怎么重載的時候,百度或GOOGLE會告訴你答案,但是這些知識點你要有,如果你都不知道運算符重載,你打開百度也不知道搜索什么呀,所以,擴充自己的知識面是非常重要的,怎么擴充,看書唄,大家切記的一件事,不要看什么視頻,個人也看過一些視頻,各種講師的講的內(nèi)容著實有點不行,講不到重點,講不到原理,講不到應(yīng)用,更多的是在講一些語法,跟學(xué)生講 i++和++i的區(qū)別,甚至i++i++i++的結(jié)果,我們C只說了15小節(jié),C++應(yīng)該也不會太多,但是大多數(shù)實用技術(shù)都會給大家講到。因為我們面向WINDOWS開發(fā)的時候更多的是要對基礎(chǔ)的理解,再有就是對操作系統(tǒng)的理解,而不是語法,你不懂友元,沒有關(guān)系,我來告訴你,友元就是一種機制,你在類中的私有數(shù)據(jù)不希望被外部訪問,但是你又想在某些非成員函數(shù)中訪問,問題就出現(xiàn)了, 將那個函數(shù)聲明為友元就可以了,我們前面已經(jīng)看到了很多哦。 template complex friend complex 友元就是這個思想,用到類上面也是這么個思想,那就是友元類啦。很EASY吧。至于友元的其它細(xì)節(jié),我們見到的多了,慢慢研究研究也就會了。 其實學(xué)習(xí)重要的是什么,是思想,而不是友元怎么用,當(dāng)有需求的時候,它的作用也就體現(xiàn)出來了。 好吧,這一小節(jié)的內(nèi)容也是很重要的,我們暫時就介紹到這里,下一小節(jié),我們就開始介紹向量,vector,也稱為動態(tài)數(shù)組,它的思想跟string類似,但是數(shù)據(jù)不是char,而是支持很多種類型,類也是可以的。 下小節(jié)見! 另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機、免備案服務(wù)器”等云主機租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。
當(dāng)前題目:C++入門篇04-創(chuàng)新互聯(lián)
當(dāng)前URL:http://fisionsoft.com.cn/article/doheii.html