c++涉及继承和虚继承时的内存布局

2023-11-08

今天是清明节假期的第二天,天气阴沉,无心于游玩,遂决定宅于实验室。
现在来说每天拜读一下大牛的博客已成生活中不可或缺之乐趣!但是俗话说的好:”光说不练假把式!“,今天拜读了浩哥的博客,感触颇多,于是就产生了本篇博文!
目的主要还是总结一下自己看到和想到的一些东西,以及遇到的一些问题,关于文中提出的问题受本人水平所限,可能略显拙劣,还望各位大牛莫要见笑,不吝赐教!
个人认为c++语言的出现是面向对象程序设计的一个里程碑,在今天看来面向对象对于大家来说都不陌生,几乎现在新近流行的每一种编程语言都包含有面向对象的特性。在面向对象中很重要的一个行为就是 继承 多态 ,有关于他们的定义这里不再赘述!继承的确为我们的程序设计提供了很大的便利,最重要的一点就是它极大的提高了我们代码的复用性,我们可以将多个对象中共有的部分提取出来放在一个基类里面。但是c++中继承形式的多样性也极大增加代码的复杂性和不确定性,其中尤以多重继承为甚,它使得对象的关系不再是单纯的线性的继承关系而是随着代码功能的增加渐渐的演变成一种不可控的网状的关系,这样极大的降低了代码的可读性!也成为了c++经常为人所诟病的一个槽点,当然为了解决这个问题,也催生了另外一种面向对象的编程语言的产生,那就是现如今来说大名鼎鼎的JAVA语言!
当然任何事物都有其两面性,虽然多重继承存在极大的隐患但是无可否认它也为我们的设计带了极大的便利!成魔成佛还是最关键还是在于它的使用者,与此同时c++自身对于多重继承存在问题也引入了很多解决的办法,其中很重要的一个方面就是虚函数和虚继承!关于虚函数大家相比也都不陌生,它是面向对象的多态中不可或缺的一件神兵利器!为了使用这件神兵,我们的子类对象中都会存放着一张虚函数表(有点类似于“辟邪剑谱”),当我们用一个基类的指针来指向子类的对象,并用该指针进行函数调用时就会通过查阅这张虚函数表来实现动态绑定!
现在问题就来了:
1、这样的一张虚表它放在内存的什么地方呢?
2、在多重继承当中可能存在多张虚表他们的分布是怎么样的?
3、有关于虚继承中虚表的分布又是一个怎样的情况呢!
假如您很清楚上面的问题,那么你完全可以X掉这个网页了。假如您还有一点的困惑,那么准备好零食扫几眼,说不定会对你有点帮助!下面我们就开始吧!
还是先上例子:

点击(此处)折叠或打开

  1. /*************************************************************************
  2.     > File Name: memRank.cpp
  3.     > Author: dongdaoxiang
  4.     > Mail: dongdaoxiang@ncic.ac.cn 
  5.     > Virtual Table
  6.     > Created Time: 2013年04月04日 星期四 16时57分13秒
  7.  ************************************************************************/

  8. #include<iostream>
  9. using namespace std;
  10. class Parent
  11. {
  12.     public:
  13.         int age;
  14.         Parent():age(10){}
  15.         virtual void f(){cout << "Parent::f()" << endl;}
  16.         virtual void g(){cout << "Parent::g()" << endl;}
  17. };
  18. class Child : public Parent
  19. {
  20.     public:
  21.         int num;
  22.         Child():num(6){}
  23.         virtual void f(){cout << "Child::f()" << endl;}
  24.         virtual void h(){cout << "Child::h()" << endl;}
  25.         virtual void gc(){cout << "Child::gc()" << endl;}
  26. };
  27. class GrandChild : public Child
  28. {
  29.     public:
  30.         int gpa;
  31.         GrandChild() : gpa(4){}
  32.         virtual void f(){cout << "GrandChild::f()" << endl;}
  33.         virtual void ggc(){cout << "GrandChild::ggc()" << endl;}
  34.         virtual void gc(){cout << "GrandChild::gc()" << endl;}
  35. };

  36. int main()
  37. {
  38.     typedef void (*Func)(void);
  39.     GrandChild temp;
  40.     Parent *p = new GrandChild();
  41.     int ** ptVtab = (int **)(&temp); //这里我们用一个二级指针指向虚表内存储的虚函数的入口地址,为什么用int呢?跟机器相关32位的机器一个指针
  42.     int i = 0; //占用4个字节的存储空间!
  43.     Func f;
  44.     for( i= 0; (Func)ptVtab[0][i] != NULL; i++)//对象内存空间的起始位置
  45.     {
  46.         f = (Func)ptVtab[0][i];
  47.         f();
  48.     }
  49.     cout << (int) ptVtab[1] << endl;//跳过四个字节之后的位置!
  50.     cout << (int) ptVtab[2] << endl;
  51.     cout << (int) ptVtab[3] << endl;
  52.     p->f();
  53.     return 0;
  54. }
大家可以先想象一下这个输出:

点击(此处)折叠或打开

  1. mini@mini-ThinkPad-T420:~/unixtest$ ./a.out 
  2. GrandChild::f()
  3. Parent::g()
  4. Child::h()
  5. GrandChild::gc()
  6. GrandChild::ggc()
  7. 10
  8. 6
  9. 4
  10. GrandChild::f()
纳尼,这都是什么玩意?不要着急我们先来看上述的代码,在代码中我们定义了一个基类Parent,其后一个线性的继承关系。当我们在main函数中定义了一个GrandChild的对象之后,取这个对象所在内存地址的起始位置会获得我们之前提到那张虚函数表!由于我们在GrandChild中重新定义了由父类定义来的f()函数,所以这样虚表的中其实位置指向了GrandChild中定义的f()的入口地址,所以当我们定义一个Parent的指针指向一个GrandChild的对象来调用f()函数时,他将从虚函数表中拉取GrandChild的入口地址,从而实现动态绑定!
虚表中函数的分布关系:
首先是由子类对象重新定义的虚函数的入口地址;
基类对象中其他虚函数的入口地址;
子类对象中其他虚函数的入口地址;
然后跳过这样虚表的地址之后:
首先是基类对象中定义的属性值
然后子类对象中定义的属性值
我们可以看到在这种线性的继承关系中子类对象中仅仅维护一张虚表!
我们再来看下面的一个例子!

点击(此处)折叠或打开

  1. /*************************************************************************
  2.     > File Name: memRank2.cpp
  3.     > Author: dongdaoxiang
  4.     > Mail: dongdaoxiang@ncic.ac.cn 
  5.     > Virtual Table
  6.     > Created Time: 2013年04月05日 星期五 12时59分53秒
  7.  ************************************************************************/

  8. #include<iostream>
  9. using namespace std;
  10. class Base1 {

  11.     public:

  12.         int ibase1;

  13.         Base1():ibase1(10) {}

  14.         virtual void f() { cout << "Base1::f()" << endl; }

  15.         virtual void g() { cout << "Base1::g()" << endl; }

  16.         virtual void h() { cout << "Base1::h()" << endl; }
  17. };

  18. class Base2 {

  19.     public:

  20.         int ibase2;

  21.         Base2():ibase2(20) {}

  22.         virtual void f() { cout << "Base2::f()" << endl; }

  23.         virtual void g() { cout << "Base2::g()" << endl; }

  24.         virtual void h() { cout << "Base2::h()" << endl; }

  25. };

  26. class Base3 {

  27.     public:

  28.         int ibase3;

  29.         Base3():ibase3(30) {}

  30.         virtual void f() { cout << "Base3::f()" << endl; }

  31.         virtual void g() { cout << "Base3::g()" << endl; }

  32.         virtual void h() { cout << "Base3::h()" << endl; }
  33. };

  34. class Derive : public Base1, public Base2, public Base3 {

  35.     public:

  36.         int iderive;

  37.         Derive():iderive(100) {}

  38.         virtual void f() { cout << "Derive::f()" << endl; }

  39.         virtual void g1() { cout << "Derive::g1()" << endl; }

  40. };

  41. int main()
  42. {
  43.     typedef void(*Func)(void);
  44.     Derive d;
  45.     int **pd = (int **)(&d);
  46.     int i = 0;
  47.     while(< 4)
  48.     {
  49.         Func f = (Func)pd[0][i];
  50.         f();
  51.         i++;
  52.     }
  53.     int s = (int)(pd[1]);
  54.     cout << s << endl;
  55.     i = 0;
  56.     cout << "===============================================" << endl;
  57.     while(< 3)
  58.     {
  59.         Func f = (Func)pd[2][i];
  60.         f();
  61.         i++;
  62.     }
  63.     s = (int)(pd[3]);
  64.     cout << s << endl;
  65.     cout << "===============================================" << endl;
  66.     i = 0;
  67.     while(< 3)
  68.     {
  69.         Func f = (Func)pd[4][i];
  70.         f();
  71.         i++;
  72.     }
  73.     s = (int)(pd[5]);
  74.     cout << s << endl;
  75.     s = (int)(pd[6]);
  76.     cout << s << endl;
  77.     return 0;
  78. }
我们会看到如下的执行结果:

点击(此处)折叠或打开

  1. mini@mini-ThinkPad-T420:~/unixtest$ ./a.out 
  2. Derive::f()
  3. Base1::g()
  4. Base1::h()
  5. Derive::g1()
  6. 10
  7. ===============================================
  8. Derive::f()
  9. Base2::g()
  10. Base2::h()
  11. 20
  12. ===============================================
  13. Derive::f()
  14. Base3::g()
  15. Base3::h()
  16. 30
  17. 100
为了便于说明我就拿来浩哥画的这样图:

图画已经很清晰了,当我们申请一个Derive的对象时,由于该对象存在三个父类于是在它的地址空间中就存放了三张虚表,但是我们会发现对于Drive本身的虚函数就放在第一张虚表内! ( 上图中有点小错误就是第三张虚表内的函数前缀改为Base3 )
分布情况如下:
base1的虚表;
继承自base1的属性值;
base2的虚表;
继承自base2的属性值;
base3的虚表;
继承自base3的属性值;
子类自身的属性值;

好了这是这种情况,别着急我们接着向下看下面的例子:

点击(此处)折叠或打开

  1. /*************************************************************************
  2.     > File Name: memRank3.cpp
  3.     > Author: dongdaoxiang
  4.     > Mail: dongdaoxiang@ncic.ac.cn 
  5.     > Func: virtal table
  6.     > Created Time: 2013年04月05日 星期五 15时21分32秒
  7.  ************************************************************************/

  8. #include<iostream>
  9. using namespace std;
  10. class B

  11. {

  12.     public:

  13.         int ib;

  14.         char cb;

  15.     public:

  16.         B():ib(0),cb('B') {}

  17.         virtual void f() { cout << "B::f()" << endl;}

  18.         virtual void Bf() { cout << "B::Bf()" << endl;}

  19. };

  20. class B1 : public B

  21. {

  22.     public:

  23.         int ib1;

  24.         char cb1;

  25.     public:

  26.         B1():ib1(11),cb1('1') {}

  27.         virtual void f() { cout << "B1::f()" << endl;}

  28.         virtual void f1() { cout << "B1::f1()" << endl;}

  29.         virtual void Bf1() { cout << "B1::Bf1()" << endl;}
  30. };

  31. class B2: public B

  32. {

  33.     public:

  34.         int ib2;

  35.         char cb2;

  36.     public:

  37.         B2():ib2(12),cb2('2') {}

  38.         virtual void f() { cout << "B2::f()" << endl;}

  39.         virtual void f2() { cout << "B2::f2()" << endl;}

  40.         virtual void Bf2() { cout << "B2::Bf2()" << endl;}
  41. };

  42. class D : public B1, public B2

  43. {

  44.     public:

  45.         int id;

  46.         char cd;

  47.     public:

  48.         D():id(100),cd('D') {}

  49.         virtual void f() { cout << "D::f()" << endl;}

  50.         virtual void f1() { cout << "D::f1()" << endl;}

  51.         virtual void f2() { cout << "D::f2()" << endl;}

  52.         virtual void Df() { cout << "D::Df()" << endl;}
  53. };

  54. int main()
  55. {
  56.     typedef void(*Func)(void);
  57.     D d;
  58.     int **pd = (int**)&d;
  59.     int i = 0;
  60.     cout << "+++++++++++++++++++B1_vTable+++++++++++++++++" << endl;
  61.     while(< 6)
  62.     {
  63.         Func f = (Func)(pd[0][i]);
  64.         f();
  65.         i++;
  66.     }
  67.     int s = (int)(pd[1]);
  68.     cout << s << endl;
  69.     int *= (int *)&d;
  70.     char c = (char)*(+ 2);
  71.     cout << c << endl;
  72.     s = (int)(pd[3]);
  73.     cout << s << endl;
  74.     c = (char)*(+ 4);
  75.     cout << c << endl;
  76.     cout << "===================B2_vTable=================" << endl;
  77.     i = 0;
  78.     while(< 4)
  79.     {
  80.         Func f = (Func)(pd[5][i]);
  81.         f();
  82.         i++;
  83.     }
  84.     s = (int)(pd[6]);
  85.     cout << s << endl;
  86.     c = (char)*(+ 7);
  87.     cout << c << endl;
  88.     s = (int)(pd[8]);
  89.     cout << s << endl;
  90.     c = (char)*(+ 9);
  91.     cout << c << endl;
  92.     s = (int)(pd[10]);
  93.     cout << s << endl;
  94.     c = (char)*(+ 11);
  95.     cout << c << endl;
  96.     return 0;
  97. }
根据上面的例子大家可以尽情的猜测一下本例的结果?看看自己有没有猜对:

点击(此处)折叠或打开

  1. mini@mini-ThinkPad-T420:~/unixtest$ ./a.out 
  2. +++++++++++++++++++B1_vTable+++++++++++++++++
  3. D::f()
  4. B::Bf()
  5. D::f1()
  6. B1::Bf1()
  7. D::f2()
  8. D::Df()
  9. 0
  10. B
  11. 11
  12. 1
  13. ===================B2_vTable=================
  14. D::f()
  15. B::Bf()
  16. D::f2()
  17. B2::Bf2()
  18. 0
  19. B
  20. 12
  21. 2
  22. 100
  23. D
这样的执行结果说明了什么问题:
1、首先子类继承了拥有同一个基类的父类时,他将保有两个父类继承来的两份基类的数据副本!
2、类空间内部采取了对齐,char的数据成员其实占用了int型的数据空间!
内存空间分布:
1、B1的虚函数表空间(其中存放了重定义的基类的虚函数、 基类的虚函数自己内部的虚函数的入口地址和来自父类的B1虚函数地址)
2、基类的数据成员
3、父类的数据成员
4、B2的虚函数表空间(自己重定义的虚函数、基类的虚函数、来自父类B2的虚函数地址)
5、基类的数据成员
6、父类的数据成员
7、自己的数据成员(放在了整个数据空间的末尾
画成一张图如下(浩哥出品)

如果我们采用虚继承结果会是怎么样的呢?

点击(此处)折叠或打开

  1. /*************************************************************************
  2.     > File Name: memRank3.cpp
  3.     > Author: dongdaoxiang
  4.     > Mail: dongdaoxiang@ncic.ac.cn 
  5.     > Func: virtal table
  6.     > Created Time: 2013年04月05日 星期五 15时21分32秒
  7.  ************************************************************************/

  8. #include<iostream>
  9. using namespace std;
  10. class B

  11. {

  12.     public:

  13.         int ib;

  14.         char cb;

  15.     public:

  16.         B():ib(0),cb('B') {}

  17.         virtual void f() { cout << "B::f()" << endl;}

  18.         virtual void Bf() { cout << "B::Bf()" << endl;}

  19. };

  20. class B1 : virtual public B

  21. {

  22.     public:

  23.         int ib1;

  24.         char cb1;

  25.     public:

  26.         B1():ib1(11),cb1('1') {}

  27.         virtual void f() { cout << "B1::f()" << endl;}

  28.         virtual void f1() { cout << "B1::f1()" << endl;}

  29.         virtual void Bf1() { cout << "B1::Bf1()" << endl;}
  30. };

  31. class B2: virtual public B

  32. {

  33.     public:

  34.         int ib2;

  35.         char cb2;

  36.     public:

  37.         B2():ib2(12),cb2('2') {}

  38.         virtual void f() { cout << "B2::f()" << endl;}

  39.         virtual void f2() { cout << "B2::f2()" << endl;}

  40.         virtual void Bf2() { cout << "B2::Bf2()" << endl;}
  41. };

  42. class D : public B1, public B2

  43. {

  44.     public:

  45.         int id;

  46.         char cd;

  47.     public:

  48.         D():id(100),cd('D') {}

  49.         virtual void f() { cout << "D::f()" << endl;}

  50.         virtual void f1() { cout << "D::f1()" << endl;}

  51.         virtual void f2() { cout << "D::f2()" << endl;}

  52.         virtual void Df() { cout << "D::Df()" << endl;}
  53. };

  54. int main()
  55. {
  56.     typedef void(*Func)(void);
  57.     D d;
  58.     int **pd = (int**)&d;
  59.     int i = 0;
  60.     cout << "+++++++++++++++++++B1_vTable+++++++++++++++++" << endl;
  61.     while(< 5)
  62.     {
  63.         Func f = (Func)(pd[0][i]);
  64.         f();
  65.         i++;
  66.     }
  67.     int s = (int)(pd[1]);
  68.     cout << s << endl;
  69.     int *= (int *)&d;
  70.     char c = (char)*(+ 2);
  71.     cout << c << endl;
  72.     cout << "===================B2_vTable=================" << endl;
  73.     i = 0;
  74.     while(< 3)
  75.     {
  76.         Func f = (Func)(pd[3][i]);
  77.         f();
  78.         i++;
  79.     }
  80.     s = (int)(pd[4]);
  81.     cout << s << endl;
  82.     c = (char)*(+ 5);
  83.     cout << c << endl;
  84.     s = (int)(pd[6]);
  85.     cout << s << endl;
  86.     c = (char)*(+ 7);
  87.     cout << c << endl;
  88.     cout << "------------------B_vTable-------------------" << endl;
  89.     i = 0;
  90.     while(< 2)
  91.     {
  92.         Func f = (Func)(pd[8][i]);
  93.         f();
  94.         i++;
  95.     }
  96.     s = (int)(pd[9]);
  97.     cout << s << endl;
  98.     c = (char)*(+ 10);
  99.     cout << c << endl;
  100.     return 0;
  101. }
结果又变成了这样:

点击(此处)折叠或打开

  1. mini@mini-ThinkPad-T420:~/unixtest$ ./a.out 
  2. +++++++++++++++++++B1_vTable+++++++++++++++++
  3. D::f()
  4. D::f1()
  5. B1::Bf1()
  6. D::f2()
  7. D::Df()
  8. 11
  9. 1
  10. ===================B2_vTable=================
  11. D::f()
  12. D::f2()
  13. B2::Bf2()
  14. 12
  15. 2
  16. 100
  17. D
  18. ------------------B_vTable-------------------
  19. D::f()
  20. B::Bf()
  21. 0
  22. B
我想从结果上来看大家已经很清楚了
假如我们在继承的时候采用虚继承的方式,就会解决在一般继承之中,存在的基类多副本的问题,但是虚继承中的子类多了一张基类的虚函数表,由次其实可以衍生出很多比较有意思的问题,例如:在一般的多重继承和虚继承的情况下子类的内存空间有多大?我们在回答诸如此类的问题的时候,就要考虑到基类多副本和虚表的地址占用的空间大小了?
在虚继承的情况下内存空间分布:
1、B1的虚函数表(子类重定义的基类的虚函数、重定义的B1的虚函数、子类自己的虚函数)
2、继承自B1的数据成员
3、B2的虚函数表(子类重定义的基类的数据成员、重定义的B2的虚函数)
4、继承自B2的数据成员
5、子类自己的数据成员
6、基类的虚函数表(子类重定义的虚函数、基类的虚函数)
7、基类的数据成员

wow,终于把这些字码完了,不知道你有没有耐心的看完哦!
最后附上浩的博客原文的地址:
http://blog.csdn.net/haoel/article/details/3081328





本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++涉及继承和虚继承时的内存布局 的相关文章

  • 追忆我那为之奋斗了5年的地方

    虽然已经下定决心离开 但是当邮件发出那一刻 我的手还是忍不住的发抖 心跳在不停的加速 这毕竟是我工作了5年的地方 这里有我熟悉的面孔 直到过了几分钟 Boss回了个信息 过来下 我才深吸一口气 缓缓的走向他的办公室 追忆 初入职顺利过关斩将
  • Linux 编译安装 openssl库

    Linux 编译安装 openssl库 如果是不需要特定版本的openssl库的安装非常简单 直接sudo apt install opensll即可 而且像Ubuntu这种应该是自带了openssl库的 运行openssl version
  • vmware启动虚拟机黑屏解决办法

    以管理员身份在命令提示符窗口中输入 netsh winsock reset 然后重启计算机即可解决
  • stm32每周学习报告2.0

    STM32 通用定时器简介 STM32 的通用定时器是一个通过可编程预分频器 PSC 驱动的 16 位自动装载计数器 CNT 构成 STM32 的通用定时器可以被用于 测量输入信号的脉冲长度 输入捕获 或者产生输出波 形 输出比较和 PWM
  • SQL之视图、变量、存储过程、函数

    视图 虚拟表 和正常表一样使用 视图的好处 修改视图 方式一 视图不存在就创建 存在就替换 create or replace view name as select 方式二 alter view name as select 删除视图 d
  • 简单运行C程序的步骤和方法

    1 安装C Free程序 如下图可见 2 新建一个源程序 点击左上角 文件 然后选择新建 进行命名 3 开始编译程序 以This is a C program 为例 4 点击编译 并开始运行 如出现错误和警告 系统会给予提醒 改正后就可以运
  • C语言函数大全-- v 开头的函数

    v 开头的函数 1 va start 1 1 函数说明 1 2 演示示例 1 3 运行结果 2 va arg 2 1 函数说明 2 2 演示示例 3 va copy 3 1 函数说明 3 2 演示示例 4 va end 4 1 函数说明 4
  • 【Clion+CubeMX开发STM32】(三)为你的工程创建GIT远程仓库

    目录 下载安装git 链接github远程仓库 上传 下载安装git 网上已经有很多git的安装教程 本文就不再赘述了 推荐一个链接 下载安装Git链接 链接github远程仓库 一 注册你的Git账号并登录 二 创建远程仓库 填写仓库名
  • 小程序云开发多表查询

    原文链接 https juejin im post 5baadb086fb9a05ce2740968 关联表学习 文中代码并不是实际代码 伪代码不可直接运行 功能 用户 喜欢 文章 表 用户表 users id username 唯一标识
  • Java常用配置项和命令行

    JVM配置项说明 1 java虚拟机可配置参数整理 java参数配置参数分成三类 标准参数 开头 如 version 非标准参数 X开头 非稳定参数 XX开头 第一部分 JMM配置参数 Xmn 新生代大小 Xms 初始内存大小 Xmx 堆最

随机推荐

  • 【Gale Shapley 婚姻稳定匹配算法实现】

    原理 所有男性按照好感的高低向对应女性求婚 每个女性在所有的向她发出求婚的男性和其丈夫 如果暂无丈夫则不做比较 选择一个最喜欢的 如果这个最喜欢的是当前的丈夫 则婚姻关系不变 否则与当前丈夫离婚 并与向她发出求婚请求的男性结婚 在每个女性处
  • facebook NLP 自然语言处理框架 Pytext 简介

    自然语言处理 NLP 在现代深度学习生态中越来越常见 从流行的深度学习框架到云端API的支持 例如Google云 Azure AWS或Bluemix NLP是深度学习平台不可或缺的部分 尽管已经取得了令人难以置信的进步 但构建大规模的NLP
  • 开源OLAP引擎测评报告(SparkSql、Presto、Impala、HAWQ、ClickHouse、GreenPlum) ...

    本文为博主公司原创文章 仿冒必究 转载请回复留言 开源OLAP引擎测评报告 SparkSql Presto Impala HAWQ ClickHouse GreenPlum 易观CTO 郭炜 序现在大数据组件非常多 众说不一 在每个企业不同
  • 大语言模型(LLM)及使用方法

    大语言模型 LLM Large Language Model 是一种基于深度学习的自然语言处理技术 它使用深度神经网络来学习自然语言的统计规律 以便能够自动地生成 理解和处理自然语言 LLM通常具有数亿个参数和数十亿个标记 能够处理大规模的
  • spring websocket 利用注解接收和发送消息

    websocket只定义了文字和字节俩种形式的消息格式 没有像http协议那样子有那么丰富的协议规范 我们看看http的协议格式 websocket之所以没有自己定义那么多的协议格式 是希望有框架自己来实现定义这些格式 我们称之为webso
  • RAC+Dataguard环境中JDBC Failover配置

    在rac dataguard的环境中 为了减少应用宕机时间及改动 提高业务的可用性 要求jdbc客户端配置failover 同时为了很好地做应用分割 又不能load balance 即在第一个实例不行时 去偿试第二个实例 两个实例都不行时
  • 安卓调试

    欢迎关注 全栈工程师修炼指南 公众号 点击 下方卡片 即可关注我哟 设为 星标 每天带你 基础入门 到 进阶实践 再到 放弃学习 花开堪折直须折 莫待无花空折枝 作者主页 https www weiyigeek top 博客 https b
  • python中一个函数调用另一个函数中的变量

    我们在一个函数func2 中想使用另一个函数func1 中的变量 通常会使用返回值的方法 但是在调用的时候 也会将func2 整体运行一遍 如果func2 函数体的运行对于func1 取返回值没有影响则完全可以 但是如果func2 函数体的
  • linux操作分析lab3

    内核准备 内核和相关环境 wget https raw github com mengning mykernel master mykernel 2 0 for linux 5 4 34 patch sudo apt install axe
  • 浅谈TCP/IP协议

    一 TCP IP协议 TCP 传输控制协议 IP 因特网互联协议 TCP IP 合称网络通讯协议 是Internet最基本的协议 Internet国际互联网络的基础 由网络层的IP协议和传输层的TCP协议组成 TCP IP 定义了电子设备如
  • c++链表对应节点相加

    题目 给定两个链表 分别表示两个非负整数 它们的数字逆序存储在链表中 并且每个节点只存储一个数字 计 算两个数的和 并且返回和的链表头指针 如 输入 3 gt 6 gt 9 2 gt 5 gt 7 输出 5 gt 1 gt 7 gt 1 思
  • 自己动手写线程池——向JDK线程池进发

    优质资源分享 学习路线指引 点击解锁 知识定位 人群定位 Python实战微信订餐小程序 进阶级 本课程是python flask 微信小程序的完美结合 从项目搭建到腾讯云部署上线 打造一个全栈订餐系统 Python量化交易实战 入门级 手
  • Java七大设计原则

    文章目录 一 单一职责原则 1 不遵守单一职责原则 2 遵守单一职责原则 二 接口隔离原则 编辑 三 依赖倒转原则 1 改进前 2 改进后 四 里氏替换原则 五 开闭原则OCP 六 迪米特法则 七 合成复用原则 八 UML类图 一 单一职责
  • form-group 两种常用使用

    用法一 运行结果如下 form group 增加盒子的下边界 form control 充满整个父元素 并且有换行作用 用法二 运行结果如下 control label 元素内实现包含内容右对齐 FR 海涛高软 QQ技术交流群 386476
  • java的反射

    一 反射的定义 基于 JDK8 Oracle官网对反射的解释是本文基于 JDK8 Oracle官网对反射的解释是 反射使 Java 代码可以发现有关已加载类的字段 方法和构造函数的信息 并在安全性限制内使用反射对这些字段 方法和构造函数进行
  • 熵,信息熵,香农熵,微分熵,交叉熵,相对熵

    2019 07 13 https blog csdn net landstream article details 82383503 https blog csdn net pipisorry article details 5169528
  • Java 实现二分法查找

    二分法 public class BinarySearch public static void main String args int array 1 5 8 11 19 22 31 35 40 45 48 49 50 int targ
  • Java面向对象,你真的会用吗?

    就在今天 自己写的一个C 项目 同事说用面向对象的思想写比较好 其实面向对象思想 感觉这个东西谁不知道啊 但是 确实这个程序没有使用 使用的好处是什么呢 又要如何使用呢 这是很值得思考的问题 面向对象简称OO Object Oriented
  • OpenWrt的学习和总结

    OpenWrt的学习和总结 内容目录 1OpenWrt背景知识 2 2OpenWrt 基础知识 2 2 1目录结构 2 2 2扩展软件包feeds 3 2 3OpenWrt SDK 4 2 4固件升级 8 3OpenWrt内部机制 8 3
  • c++涉及继承和虚继承时的内存布局

    原文地址 c 涉及继承和虚继承时的内存布局 作者 风箫夜吟 今天是清明节假期的第二天 天气阴沉 无心于游玩 遂决定宅于实验室 现在来说每天拜读一下大牛的博客已成生活中不可或缺之乐趣 但是俗话说的好 光说不练假把式 今天拜读了浩哥的博客 感触