[轉載] C++靈魂所在之---多態的前世與今生

參考鏈接: Java是否支持goto

開頭先送大家一句話吧:? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

? ? ? ?眾所周知,在20世紀80年代早期,C++在貝爾實驗室誕生了,這是一門面向對象的語言,但它又不是全新的面向對象的語言,它是在傳統的語言(C語言)進行面向對象擴展而來,但是它有些地方與C語言又有很多區別,又添加了很多C語言原來沒有的內容與概念,所以有些地方是需要花時間去深入了解的。雖然這兩者有密切關系,但是即使你很熟悉C語言,想要熟悉C++(先不說熟練掌握或者精通C++),還是得花很大一番功夫,光光是C++之中的三大特性就夠研究好久的了。所以說學習C++的過程是一個漫長的過程,如果將來從事和C++(或者說其它任何一門優秀的語言)有關的工作,那么對語言的學習可能會貫穿一生,語言是一直發展而來的,所以說要想成為一個優秀的程序員,那么養成每天都學習一些新知識的習慣很重要,我從來都覺得一個人的習慣很重要,養成了每天學習的習慣,那么有一天突然沒有學習,你會有一種今天有什么重要任務沒有完成一樣。但凡那些優秀的人,從來都有一個好的習慣,對于這一點我堅信不疑。好了,其它的也不多說了,千里之行,始于足下。? ? ? ? ? ?

? ? ? ?對于C++之中多態我準備分三個層次講(當然這是我我理解上的三個層次,實際上這里面的內容遠比我說的要多得多,也更深得多,我是由淺入深,根據所需要的挑著看吧,當然這里的深也只是相對的,如果說C++是一片海的話,那我也只能說我只是見識過淺灘上的風景,但是真正海底的神秘我還沒有去研究,希望將來有機會可以研究到),如果只是單純的想了解一下,就沒有必要整篇都看。前幾天寫了在C++的學習過程中關于繼承方面的一些知識,今天就寫一寫關于剩下的特性:多態(封裝性需要說的內容比較少,所以就沒有寫)?

? ? ? ?如果你只是想了解一下C++之中關于多態的知識,那么第一部分有你要的答案。如果你原本就對C++之中多態有了解,但是對是想了解內層的實現,那么最后一部分會滿足你的好奇心!?

? ? ? 在這里我盜用一下王國維先生對于讀書三境界的總結來作為我每一層次的小標題。??

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ?按照我的習慣總是先從定義入手,當然這第一層次肯定了解定義是必須的。首先來看一下多態的定義:多態性的英文單詞polymorphism來源于希臘詞根poly(意為“很多”)和morph(意為“形態”),意思是具有多種形式或形態的情形,在C++語言中多態有著更廣泛的含義。在C++之中多態的定義:多態性是指具有不同功能的函數可以用同一個函數名,這樣就可以用一個函數名調用不同內容的函數。在面向對象方法中一般是這樣表述多態性的:向不同的對象發送同一個消息, 不同的對象在接收時會產生不同的行為(即方法)。借用網上的一個牛人對于C++之中多態總結的較好的三句話:一、“相同函數名”,二:“依據上下文”,三:“實現卻不同”。個人感覺這三句話總結地很到位,在這里我就盜用一下。? ? ? ? ?既然這是第一層次,所以在這里有必要先說一下關于C++之中對象的類型,先再看一張圖:?

??

? ? ? ? ? ? ? ? ?

? ? ? ? ?在了解了對象的類型之后有助于你更好地理解之后的內容。?

? ? ? ? 好了,說完定義之后,我們肯定要來說一說多態的分類了,還是以圖的形式給出,這樣比較直觀,也便于大家去理解:??

??

? ? ? ?接下來我們就來逐個分析一下:?

? ? ? ?先來看一下靜態多態:編譯器在編譯期間完成的,編譯器根據函數實參的類型(可能會進行隱式類型轉換),可推 斷出要調用哪個函數,如果有對應的函數就調用該函數,否則出現編譯錯誤。?

? ? ? ?其實我們在之前學C++過程之中,使用函數重載的時候就已經用到了多態,只不過這是靜態多態,如果當時不知道,可能你只是沒有注意或者說沒往多態這方面想而已。以下內容就是一個靜態多態。??

int My_Add(int left, int right)

{

? ? return (left + right);

}

?

float My_Add(float left, float right)

{

? ? return (left + right);

}

int main()

{

? ? cout << My_Add(10, 20) << endl;

? ? cout << My_Add(12.34f, 24.68f) << endl;

? ? return 0;

}? ? ? ?

看完靜態多態之后,我們就來看一看動態多態,在沒有特殊說明的情況之下,我們所說的多態一般都說的是動態多態。當然在說這個之前先知道什么時動態綁定:在程序執行期間(非編譯期)判斷所引用對象的實際類型,根據其實際類型調用相應的方法。? ? ? ? 我們用一個例子來說明吧!先看例子,下面再來解釋:?

?

?

class Airport? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//機場類

{

public:? ? ? ? ? ?

? ? void GoToT1()? ? ? ? ??

? ? {?

? ? ? ? cout << "You want to T1 air terminal--->Please Left" << endl;? ? ? ?//T1候機樓

? ? }? ? ? ? ? ? ??

? ? void GotoT2()? ? ? ? ??

? ? {?

? ? ? ? cout << "You want to T2 air terminal--->Please Right" << endl;? ? ? //T2候機樓

? ? }

};

?

class CPerson? ? ? ? ? ? ? ? ? ? ? ? ? ? //人類

{?

public:? ? ? ? ? ?

? ? virtual void GoToTerminal(Airport & _terminal) = 0;?

};

?

class Passage_A :public CPerson? ? ? ? ? //乘客A類(這類乘客需要去T1候機樓登機)

{?

public:? ? ? ? ? ?

? ? virtual void GoToTerminal(Airport & _terminal)

? ? {

? ? ? ? _terminal.GoToT1();

? ? }

};

?

class Passage_B :public CPerson? ? ? ? ? //乘客B類(這類乘客需要去T2候機樓登機)

{

public:? ? ? ? ? ?

? ? virtual void GoToTerminal(Airport & _terminal)

? ? {

? ? ? ? _terminal.GotoT2();

? ? }

};

?

void FunTest()?

{

? ? Airport terminal;? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? //創建機場類對象

? ? for (int iIdx = 1; iIdx <= 10; ++iIdx)? ? ? ? ? ?

? ? {

? ? ? ? CPerson* pPerson;? ? ? ? ? ? ? ? ? ??

? ? ? ? int iPerson = rand() % iIdx;? ? ? ? ? ? //設置隨機值,這樣可以使下面生成不同的子類對象? ? ? ?

? ? ? ? if (iPerson & 0x01)? ? ? ? ? ? ? ? ??

? ? ? ? {?

? ? ? ? ? ? pPerson = new Passage_A;?

? ? ? ? }

? ? ? ? else? ? ? ? ? ? ? ? ??

? ? ? ? {?

? ? ? ? ? ? pPerson = new Passage_B;

? ? ? ? }

? ? ? ? pPerson->GoToTerminal(terminal);

? ? ? ? delete pPerson;? ? ? ? ? ? ? ? ??

? ? ? ? pPerson = NULL;? ? ? ? ? ? ? ? ??

? ? ? ? Sleep(1000);? ? ? ? ? ? ? ? ? ? ? ? ? ? //每次休息1秒后繼續輸出

? ? }

}

?

int main()

{

? ? FunTest();

? ? return 0;

}?

? ? ? ? ?在這里簡單說明一下,上面代碼首先有一個機場類,里面有兩個方法,表示兩個候機樓在不同地方,你需要左拐還是右拐。接下來就是一個人類,里面只有一個純虛函數,因此這個人類就是一個抽象類。把這個抽象類作為基類,又派生出兩個子類。在子類之中把父類之中的純虛函數重新定義了(這也是必須的),(之前我在關于繼承那一篇文章之中說過菱形繼承之中就是利用虛擬繼承解決數據二義性問題),也就是說這里不存在二義性問題,但是這里似乎更加具體,直接在子類中把父類之中的虛函數(帶有virtual關鍵字修飾的函數)重寫了(基類之中是一個純虛函數,派生類必須要重寫它才可以實例化對象)。?

? ? ? ?簡單說一下重寫(或者說是覆蓋,兩者意思一樣):如果兩個成員函數處在不同的作用域之中(上面的例子是一個在父類,一個在子類),父類之中有virtual關鍵字修飾(這一點是必須的),而且它們的的參數相同,返回值類型也相同(這里有必要說明一中特殊情況就是協變,協變之中返回值類型可以不同),那么這樣的兩個成員函數就構成了重寫。(派生類中的這個函數有沒有virtual關鍵字修飾無所謂,可加可以不加,因為即使是派生類之中的這個函數,它本質上也還是一個虛函數)。?

? ? ? ?由于派生類之中對父類的純虛函數進行了重寫,因此我們可以說上述代碼實現了動態綁定。使用virtual關鍵字修飾函數時,指明該函數為虛函數(在上面例子中為純虛函數),派生類需要重新實現,編譯器將實現動態綁定。

?

?

? ? ? ?在使用對象的指針的時候要千萬注意一下,如下面的例子,沒有實現動態綁定。?

?

?int main()??

{??

? ? Person? *p;??

? ? Man? *pm;??

? ? Woman *pw;??

? ? p = &man;? ? ?//如果你去調用其中的Man類中方法(基類之中也有同名方法),那么它會調用基類之中的方法

? ? p = &woman;? ?//這是因為p的類型是一個基類的指針類型,那么在p看來,它指向的就是一個基類對象,

? ? ? ? ? ? ? ? ? ?//所以調用了基類函數。

}

? ? ? ? ? ? ??

最后看一下動態綁定的條件:?

?

? ? ? ? 1、必須是虛函數 2、通過基類類型的引用或者指針調用??

? ? ? ?到這里簡單地總結一下動態多態:動態多態性是在程序運行過程中才動態地確定操作所針對的對象。它又稱運行時的多態性(動態多態性是通過虛函數(Virtual fiinction)實現的)。其實到了這里,對于多態這一塊內容你已經有了初步的了解了,如果你只是想初步了解一下,已經足夠了。但是可能還會有一些問題,不過也影響不大,畢竟只是初步認識一下,至少你明白了多態的概念以及使用上的一些注意點。如果你想搞清楚更深一層的一些問題,你可以繼續閱讀。?

??

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

? ? ? 在這一層次上我們對虛函數,純虛函數作進一步了解,動態綁定原理是什么?然后進入一個大內容---虛表的概念。?

? ? ? 在上一個內容上我們討論過了關于動態綁定的概念,這時候可能會有疑問,動態綁定是如何實現的呢?那么接下來我們就來說一說里面原理性的內容。??

? ? ? ?再解釋之前我們還是先來理一理一些概念,這樣可以更容易理解里面的內容,先做好準備工作。?

首先是關于重載、重寫(也可以叫做覆蓋)、隱藏(或者說是重定義)這三者的有關內容,如圖所示:?

??

? ? ? ? 接下來我們再來說一說上面提到過的幾個概念,上面沒有展開講,這里仔細講一講:?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?純虛函數??

? ? ? ?在成員函數的形參后面寫上=0,則成員函數為純虛函數。包含純虛函數的類叫做抽象類(也叫接口類),抽象類不能實例化出對象(雖然抽象類不能實例化對象,但是可以定義抽像類的指針)。純虛函數在派生類中重新定義以后,派生類才能實例化出對象。如下面代碼中:??

?

class Person?

{? ? ?

? ? ?virtual void Display () = 0;? ?// 純虛函數 protected :? ? ?

? ? ?string _name ;? ? ? ? ? ? ? ? ?// 姓名

};

class Student : public Person?

{

? ? //在這類里面必須要對上面的Display ()方法重新定義以后Student才可以實力化對象

};

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 協? 變?

?

? ? ? ?在C++中,只要原來的返回類型是指向類的指針或引用,新的返回類型是指向派生類的指針或引用,覆蓋的方法就可以改變返回類型。這樣的類型稱為協變返回類型(Covariant returns type)。如以下的兩個函數就構成協變。當然協變也算一種覆蓋。??

?

class Base?

{? ? ?

? ? Base * FunTest()

? ? {

? ? ? ? //do something

? ? }

};

class Derived : public Base

{

? ? <pre name="code" class="cpp">? ? Derived * FunTest()

? ? {

? ? ? ? //do something

? ? }

};?

?

?

? ? ? ?

? ? ? ?有了上面這些鋪墊之后我們就可以開始一個比較重要的內容:關于虛表的相關知識。先來以下代碼:?

class CTest

{ public:? ? ? ?

? ? CTest()

? ? {?

? ? ? ? iTest = 10;?

? ? }? ? ? ?

? ? /*virtual */~CTest(){};?

private:? ? ? ?

? ? int iTest;?

};

int main()?

{?

? ? cout << sizeof(CTest) << endl;? ? ? ?

? ? return 0;?

}? ? ? ? ? ?

很容易的得到結果是4,但是如果將里面的注釋內容,也就是virtual關鍵字放開,那么結果又會是多少呢?知道的人會覺得這是送分題,但是不知道的人卻一臉茫然,答案是8,所以我們猜想,一定是編譯器對有virtual成員函數的類做了特殊處理。?

?

? ? ? ?先說個大概吧,簡單地說就是:對于有虛函數的類,編譯器都會維護一張虛表,對象的前四個字節就是指向虛表的指針。這也就可以解釋為什么上面那個例子的原因了。?

? ? ? ?當然我們的問題才剛剛開始,請仔細看下面一張圖,可能會解決你的一些疑惑!?

??

? ? ? ?我們來分析一下上面的內容。我們在在監視窗口之中對對象test取地址,發現雖然類中只有一個數據成員,但是發現另一個內容,其實它就是一個虛表指針,觀察它的類型,發現它里面放著類似于地址的內容,但是我們可以在內存之中去查看一下它的內容。內存之中,虛表指針和數據成員是連著一起存放的,所以這個虛表指針一定是有什么作用的。我們再打開一個內存窗口,觀察一下這個地址的所指向內容里面到底是什么。通過上面的圖我們可以發現,其實這里面放的還是一個地址,突然間又有些疑惑了,我們可以轉到反匯編去看一看,我們用virtual修飾的析構函數的入口地址就是剛剛我們看到的地址。在根據反匯編我們似乎就明白了什么。(細心的你會發現其實每個虛表的最下面總是放的是0x00000000,這也想相當于一個結束標志吧!)?

? ? ? ?這時候,我們可以再來梳理一下,其實_vfptr存放的內容就是存放函數地址的地址,即_vfptr指向函數地址所在的內存空間,我們可以用圖來表示:??

??

? ? ? ? 到現在為止,你對虛表以及虛表指針這些概念應該已經不陌生了,來總結一下就是:?

? ? ? ?test對象中維護了一個虛表指針,虛表中存放著虛函數的地址。對test對象取地址可以看到的是虛表的指針以及它的其它成員變量,這個對象又是如何調用類中虛函數的呢?其實調用的虛函數是從虛表中查找的。如果基類中有多個虛函數的話,那么虛表中也會依次按基類中虛函數定義順序存放虛函數的地址,并以0x 00 00 00 00 結尾。再如果子類中有自己定義的新的虛函數,那么會排在虛函數表的后邊。在調用虛函數時由編譯器自動計算偏移取得相應的虛函數地址。?

? ? ? ? ? ?說到這里,第二層次的內容主要也講完了,不過還有關于一些關于基類與派生類之間是如何利用這個虛表指針的,以及如果函數之中存在覆蓋或者說沒有覆蓋,那么這虛函數表是否還是一樣的呢?我把放到下一個內容,感興趣的話可以接著往下看。? ? ?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ??

? ? ? ?如果你堅持看了下來,那么這一部分可能會有些復雜,在這一部分主要是對虛表作進一步剖析,也就是上面遺留下來的內容,以及復雜一點的帶有虛函數多繼承對象模型剖析。?

? ? ? ?一個一個來說明,首先是上面遺留的內容:就是基類與派生類之間是如何利用這個虛表指針的呢?其實這個內容還是需要分為兩部分來講:一、基類與派生類不存在函數的覆蓋;二、基類與派生類之間存在函數的覆蓋。?

? ? ? ?第一種情況比較簡單,在這里我主要用文字說明一下,這樣我們可以將重點放在第二種上面。??

? ? ? ?第一種情況(不存在成員函數覆蓋):先調用基類構造函數,虛表指針先指向基類虛表,然后調用子類構造函數,子類之中也有虛表指針,而且不是同一個,其實子類在構建起來之前的時候,這個虛表指針指向的是基類的虛表,但是當子類構建出來的時候,虛表指針馬上發生變化,指向了一個新空間,這個新空間里面存放了基類的虛函數以及派生類自己的虛函數,在最后放入0x00000000,子類自己的虛表也就構建完成了。(這里說明一下:1、虛函數按照其聲明的順序存在于虛表之中。2、在派生類的虛表之中,前面是基類的虛函數,后面是派生類的虛函數)。?

? ? ? ?接著轉入第二種(存在成員函數覆蓋):以下面代碼分析(結合后面的圖)?

?

class CBase?

{

public:? ? ? ? ? ?

? ? virtual void FunTest0()

? ? {?

? ? ? ? cout << "CBase::FunTest0()" << endl;

? ? }? ? ? ? ? ?

? ? virtual void FunTest1()

? ? {

? ? ? ? cout << "CBase::FunTest1()" << endl;?

? ? }? ? ? ? ? ?

? ? virtual void FunTest2()

? ? {?

? ? ? ? cout << "CBase::FunTest2()" << endl;

? ? }? ? ? ? ??

? ? virtual void FunTest3()

? ? {?

? ? ? ? cout << "CBase::FunTest3()" << endl;?

? ? }?

};

?

class CDerived :public CBase?

{?

public:? ? ? ? ? ?

? ? virtual void FunTest0()

? ? {?

? ? ? ? cout << "CDerived::FunTest0()" << endl;

? ? }? ? ? ? ? ?

? ? virtual void FunTest1()

? ? {?

? ? ? ? cout << "CDerived::FunTest1()" << endl;

? ? }? ? ? ? ??

? ? virtual void FunTest4()

? ? {?

? ? ? ? cout << "CDerived::FunTest4()" << endl;?

? ? }? ? ? ? ? ?

? ? virtual void FunTest5()

? ? {?

? ? ? ? cout << "CDerived::FunTest5()" << endl;?

? ? }?

};

?

typedef void(*_pFunTest)();

void FunTest() {

? ? CBase base;? ? ? ? ? ?

? ? for (int iIdx = 0; iIdx < 4; ++iIdx)? ? ? ? ??

? ? {

? ? ? ? _pFunTest? pFunTest = (_pFunTest)(*((int*)*(int *)&base + iIdx));? ? ? ? ? ? ? ? ??

? ? ? ? pFunTest();

? ? }

? ? cout << endl;? ? ? ? ??

? ? CDerived derived;? ? ? ? ? ?

? ? for (int iIdx = 0; iIdx < 6; ++iIdx)? ? ? ? ?

? ? {?

? ? ? ? _pFunTest? pFunTest = (_pFunTest)(*((int*)*(int *)&derived + iIdx));? ? ? ? ? ? ? ? ? ?

? ? ? ? pFunTest();?

? ? }

}

void TestVirtual()?

{?

? ? CBase base0;? ? ? ? ?

? ? CDerived derived;? ? ? ? ??

? ? CBase& base1 = derived;?

}

?

int main()?

{

? ? FunTest();? ? ? ? ?

? ? TestVirtual();? ? ? ? ??

? ? return 0;?

}

?

?

?

?

? ? ?說到這里大部分內容都已經結束了,好了,也是時候總結一下了:?

?

派生類重寫基類的虛函數實現多態,要求函數名、參數列表、返回值完全相同。(協變除外)基類中定義了虛函數,在派生類中該函數始終保持虛函數的特性只有類的成員函數才能定義為虛函數,靜態成員函數不能定義為虛函數如果在類外定義虛函數,只能在聲明函數時加virtual關鍵字,定義時不用加構造函數不能定義為虛函數,雖然可以將operator=定義為虛函數,但最好不要這么做,使用時容 易混淆不要在構造函數和析構函數中調用虛函數,在構造函數和析構函數中,對象是不完整的,可能會 出現未定義的行為最好將基類的析構函數聲明為虛函數。(析構函數比較特殊,因為派生類的析構函數跟基類的析構 函數名稱不一樣,但是構成覆蓋,這里編譯器做了特殊處理)虛表是所有類對象實例共用的?

?

?

? ? ? ?還有一個關于多重繼承之下的虛表指針的情況分析了,這種情況就比較復雜了,在這里同樣也要分有無虛函數的覆蓋這兩種情況來分析。在這里我只簡單地說一下有虛函數重載的情況,有興趣的下來可以自己去試一試。?

?

class CBase0?

{?

public:? ? ? ? ??

? ? CBase0()

? ? {

? ? ? ? m_iTest = 0xA0;?

? ? }? ? ? ? ??

? ? virtual void Print()

? ? {?

? ? ? ? cout << "m_iTest = " << hex << m_iTest << "? CBase2::Print()" << endl;?

? ? }? ? ? ? ??

? ? int m_iTest;?

};

?

class CBase1?

{?

public:? ? ? ? ?

? ? CBase1()

? ? {?

? ? ? ? m_iTest = 0xB0;

? ? }? ? ? ? ??

? ? virtual void Print()

? ? {?

? ? ? ? cout << "m_iTest = " << hex << m_iTest << "? CBase2::Print()" << endl;?

? ? }? ? ? ? ? ?

? ? int m_iTest;?

};

?

class CBase2

{

public:? ? ? ? ?

? ? CBase2()

? ? {?

? ? ? ? m_iTest = 0xC0;

? ? }? ? ? ? ? ?

? ? virtual void Print()

? ? {?

? ? ? ? cout << "m_iTest = " << hex << m_iTest << "? CBase2::Print()" << endl;?

? ? }? ? ? ? ??

? ? int m_iTest;

};

?

class CDerived :public CBase0, public CBase1, public CBase2?

{?

public:? ? ? ? ??

? ? CDerived()

? ? {

? ? ? ? m_iTest = 0xD0;?

? ? }? ? ? ? ? ?

? ? virtual void Print()

? ? {?

? ? ? ? cout << "m_iTest = " << hex << m_iTest << "? CDerived::Print()" << endl;?

? ? }? ? ? ? ? ?

? ? int m_iTest;?

};

?

void FunTest()?

{?

? ? CDerived derived;? ? ? ? ??

? ? cout << sizeof(derived) << endl;? ? ? ? ??

? ? CBase0& base0 = derived;? ? ? ? ??

? ? base0.Print();? ? ? ? ?

? ? CBase1& base1 = derived;? ? ? ? ?

? ? base1.Print();? ? ? ? ??

? ? CBase2& base2 = derived;

? ? base2.Print();? ? ? ? ?

? ? derived.Print();

}

?

int main()

{

? ? FunTest();

? ? return 0;

}? ? ? ? ? ??

大家可以分析一下FunTest()函數會打印什么,以及Derived的內存布局是怎么樣的呢?我給大家一張圖,大家下來自行分析理解一下:?

?

??

? ? ? ? ?其實如果你有興趣,還可以自己剖析下菱形虛擬繼承!!!不過肯定會有一些復雜。?

? ? ? ? ?寫到這里,說實話真的挺累的,終于可以歇一歇了,O(∩_∩)O,在最后推薦大家一本書:深度探索C++對象模型,這本書肯定是有一定難度的,光聽“深入”兩個字就應該覺得有些挑戰,不過生活中還是應該多一些挑戰。?

? ? ? ? 最后再送大家一句話:不忘初心,方得始終!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/540363.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/540363.shtml
英文地址,請注明出處:http://en.pswp.cn/news/540363.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Code Sinppet

如果你在使用VS 2005,如果你不能使用它的Code Snippet功能&#xff0c;如果你在實現抽象類override 方法時彈出&#xff1a;Code Snippet titled [Method Stub - Body] failed to load. Verify that refactoring snippets are recognized in the Code Snippet Manager and that…

暴風TV請來中國人工智能first lady馮雁教授任首席科學家

今日下午&#xff0c;暴風AI無屏電視發布會現場&#xff0c;暴風TV宣布邀請號稱“中國人工智能first lady”、于香港科技大學任教的馮雁教授&#xff0c;擔任暴風TV人工智能首席科學顧問。 馮雁教授于現場表示&#xff0c;選擇暴風TV合作的重要原因&#xff0c;一方面在于其個人…

[轉載] java 計算協方差_Java的深度:通過協方差暴露的API泄漏

參考鏈接&#xff1a; 關于Java中null的有趣事實 java 計算協方差 Java有時可能非常棘手&#xff0c;特別是在API設計中。 讓我們看一個非常有趣的展示柜。 jOOQ強烈地將API與實現分開。 所有API都在org.jooq包中&#xff0c;并且是公共的。 大多數實現是在org.jooq.impl包…

gulp之gulp.watch報錯

gulpfile.js如下&#xff1a; 問題&#xff1a; 第一次改動文件&#xff0c;監聽正常。再次改動&#xff0c;報錯&#xff0c;如下&#xff1a; 解決&#xff1a; 總結&#xff1a; 意思&#xff0c;gulpsequence這玩意兒返回的thunk只能執行一次 轉載于:https://www.cnblogs.c…

[轉載] mybatis

參考鏈接&#xff1a; 在Java中使用_(下劃線)作為變量名 mybatis第一天 1.mybatis概述和環境搭建 mybatis概述 mybatis環境搭建 1. 創建maven工程、添加開發依賴、創建數據庫和表&#xff1b; 2. 創建domain實體類和dao mybatis是一門java語言編寫持久層框架…

設置了li(float:right),里面的li反過來顯示 - 解決辦法

設置了li(float:right),里面的li反過來顯示 - 解決辦法 可以讓ul float:right ul里的li 依然float:left 本文轉自許琴 51CTO博客&#xff0c;原文鏈接&#xff1a;http://blog.51cto.com/xuqin/1127540&#xff0c;如需轉載請自行聯系原作者

[轉載] 純函數和函數柯里化

參考鏈接&#xff1a; 用示例編寫Java柯里化Currying函數 文章目錄 純函數什么是純函數純函數例子非純函數例子 函數柯里化函數柯里化簡單例子參數復用 純函數 什么是純函數 如果函數的調用參數相同&#xff0c;則永遠返回相同的結果。它不依賴于程序執行期間函數外部任何狀…

[轉載] scala

參考鏈接&#xff1a; 在Java的數字中使用下劃線 1 scala 底層是有一種隱式轉換機制&#xff0c;比如對String類型&#xff0c;底層會轉化Scala的StringOps類型 2 scala 的通用的化簡規則&#xff1a;調方法時候&#xff0c;方法的參數列表只有一個&#xff0c;則方法的&…

MySQL數據庫學習筆記

MySQL常用語法總結 一.創建Web數據庫 1.登陸到數據庫 mysql -h hostname -u username -p mysql -h hostname -u username -D dbname -p 2.創建數據庫 CREATE database dbname 3.使用數據庫 USE dbname 4.創建數據庫表 CREATE TABLE tablename (columns) 5.列的數據 create tabl…

[轉載] java實現四種常用排序算法

參考鏈接&#xff1a; 用Java排序 四種常用排序算法 ##注&#xff1a;從小到大排 ##冒泡排序## 特點&#xff1a;效率低&#xff0c;實現簡單 思想&#xff1a;每一趟將待排序序列中最大元素移到最后&#xff0c;剩下的為新的待排序序列&#xff0c;重復上述步驟直到排完所…

[轉載] Java復制對象與集合工具類

參考鏈接&#xff1a; Java中的類和對象 項目中經常需要將某個對象的屬性值復制給另一個對象&#xff0c;或者將一個集合復制到另一個集合。利用spring提供的BeanUtils&#xff0c;自己簡單封裝了一個工具類。 public class CopyUtils { /** * 復制集合 */ public static &l…

.NET深入學習筆記(4):深拷貝與淺拷貝(Deep Copy and Shallow Copy)

今天繼續利用準備WSE安全開發文章的空閑時間&#xff0c;完善《.NET深入學習筆記》系列&#xff08;基本都是.Net重要的知識點&#xff0c;我都做了詳細的總結&#xff0c;是什么、為什么、和怎么實現&#xff09;。想必很多人也接觸過這兩個概念。做過C的人對深淺拷貝的概念一…

VIM一般模式操作

2019獨角獸企業重金招聘Python工程師標準>>> vim介紹 vim和vi幾乎是一樣的&#xff0c;唯一的區別就是當編輯一個文本時&#xff0c;使用vi不會顯示顏色&#xff0c;而使用vim會顯示顏色。 vim有三個模式&#xff1a;一般模式&#xff0c;編輯模式&#xff0c;命令模…

[轉載] 解析Java的JNI編程中的對象引用與內存泄漏問題

參考鏈接&#xff1a; Java對象如何存儲在內存中 JNI&#xff0c;Java Native Interface&#xff0c;是 native code 的編程接口。JNI 使 Java 代碼程序可以與 native code 交互——在 Java 程序中調用 native code&#xff1b;在 native code 中嵌入 Java 虛擬機調用 Java 的…

[轉載] java中創建對象的方式

參考鏈接&#xff1a; 用Java創建對象的不同方法 java中的4種創建對象的方式&#xff1a; 在這片博文中和大家簡單分享一下常見的幾種創建java對象的方式 1.使用 new 的方式&#xff0c;這也是我們最常見的一種方式 我們以 Person 類來舉例說明 例&#xff1a; Person p…

Exchange 2007 SP1 如何定時清理日志???

Exchange 2007 SP1 如何定時清理日志&#xff1f;&#xff1f;&#xff1f; 時間:2011-12-31 11:00Tag標簽&#xff1a;來源:未知 作者:達思科技 點擊: 93次此文章出自&#xff1a; 專業數據恢復 問&#xff1a;Exchange 2007 SP1 如何定時清理日志&#xff1f;&#xff1f;&am…

[轉載] java-繼承和多態

參考鏈接&#xff1a; Java中的繼承 繼承&#xff1a; 繼承就是保持已有類的特性而構造新類的過程。繼承后&#xff0c;子類能夠利用父類中定義的變量和方法&#xff0c;就像它們屬于子類本身一樣。 單繼承&#xff1a;在類層次中&#xff0c;子類只繼承一個父類的數據結構…

走進SQL Server 2005:備份與恢復功能

每當有任何新的軟件發布的時候&#xff0c;你通常都會發現一些你從來不知道或者從來不知道你需要的新的特性。SQL Server 2000中存在的許多的備份和恢復特性都同樣保留在了SQL Server 2005中&#xff0c;但是有一些新的提高同樣值得你的關注。 鏡像備份 SQL Server 2005讓你可以…

[轉載] Java對返回值的封裝

參考鏈接&#xff1a; 用Java封裝 定義自己所需要的返回值類型 public class CodeMsg implements Cloneable { private int retCode; private String message; // 通用異常 public static CodeMsg SUCCESS new CodeMsg(0, "success"); public static CodeMsg EMP…

stateful set 學習筆記

2019獨角獸企業重金招聘Python工程師標準>>> 1、創建pv # cat pv.yaml kind: PersistentVolume apiVersion: v1 metadata:name: task-pv-volumelabels:type: local spec:capacity:storage: 3GiaccessModes:- ReadWriteOncehostPath:path: "/tmp/data" 2、…