[转载] 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.mzph.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、…