结论

1.虚函数表指针 和 虚函数表

  • 1.1 影响虚函数表指针个数的因素只和派生类的父类个数有关。多一个父类,派生类就多一个虚函数表指针,同时,派生类的虚函数表就额外增加一个
  • 1.2 派生类和父类同时含有虚函数,派生类的虚函数按照父类声明的顺序(从左往右),存放在继承的第一个父类中虚函数表后面,而不是单独再额外建立一张虚函数表
  • 1.3 按照先声明、先存储、先父类、再子类的顺序存放类的成员变量
  • 1.4 无论是派生类还是父类,当出现了虚函数(普通虚函数、虚析构函数、纯虚函数),排在内存布局最前面的一定是虚函数表指针

2.覆盖继承

其实,覆盖继承不够准确。

2.1 成员变量覆盖

  • 派生类和父类出现了同名的成员变量时,派生类仅仅将父类的同名成员隐藏了,而非覆盖替换
  • 派生类调用成员变量时,按照就近原则,调用自身的同名变量,解决了当调用同名变量时出现的二义性的现象

2.2 成员函数覆盖

需要考虑是否有虚函数的情况

存在虚函数的覆盖继承

父类和派生类出现了同名虚函数函数((普通虚函数、纯虚函数),派生类的虚函数表中将子类的同名虚函数的地址替换为自身的同名虚函数的地址-------多态出现

不存在虚函数的覆盖继承

父类和派生类同时出现同名成员函数,这与成员变量覆盖继承的情况是一样的,派生类屏蔽父类的同名函数

关于

  • 演示环境: VS2017 + 32位程序
  • 多继承(本文主要展开)
  • 代码写的不够规范: 因为多态中,任何带虚函数的基类类的析构函数都应该是虚析构函数。但是我这里没有写出来,目的是缩短文章篇幅。
序号 情况(多继承,基类个数:2)
1 基类均无虚函数(A,派生类有虚函数,B,派生类不含有虚函数)
2 两个基类中只有一个类有虚函数(A,派生类有虚函数,B,派生类不含有虚函数)
3 两个基类中都含有虚函数(A,派生类有虚函数,B,派生类不含有虚函数)

1.基类均无虚函数

1.1 基类均无虚函数,派生类有虚函数

1.1.1 代码

// 基类
class baseA
{
public:
int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
int _mc = 3;
int _md = 4;
}; // 派生类
class deriveA : public baseA, public baseB
{
public:
virtual void print() { std::cout << "deriveA::print()\n\n\n"; }
int _me = 3;
int _mf = 4;
};

1.1.2 内存分布

1>class deriveA	size(28):
1> +---
1> 0 | {vfptr}
1> 4 | +--- (base class baseA)
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | _mc
1>16 | | _md
1> | +---
1>20 | _me
1>24 | _mf
1> +---
1>
1>deriveA::$vftable@:
1> | &deriveA_meta
1> | 0
1> 0 | &deriveA::print
  • 因为派生类存在虚函数,故排在最前的是虚函数表指针(此时,虚函数表指针属于派生类,而非基类),接着在世基类成员变量,这里先是基类baseA,然后才是基类baseB,最后才是派生类成员变量。基类成员变量存储顺序与声明顺序有关(从靠近派生类的基类开始,先基类baseA,然后是基类baseB)。这个顺序与之前总结的规律一致: 先基类再派生类,先声明,先存储。
  • 虚函数表。由于只有派生类存在虚函数,故虚函数表中只有派生类的虚函数地址。

1.1.3 sizeof



28字节 = 基类和派生类总共六个int成员变量 + 虚函数表指针4字节

1.1.4 监视结果

1.2 基类无虚函数,派生类也没有虚函数

1.2.1 代码

// 基类
class baseA
{
public:
int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
int _mc = 3;
int _md = 4;
}; // 派生类
class deriveA : public baseA, public baseB
{
public:
int _me = 3;
int _mf = 4;
};

1.2.2 内存布局

1>class deriveA	size(24):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | _ma
1> 4 | | _mb
1> | +---
1> 8 | +--- (base class baseB)
1> 8 | | _mc
1>12 | | _md
1> | +---
1>16 | _me
1>20 | _mf
1> +---
  • 虚函数表指针。因为不存在虚函数,故没有虚函数表指针表和虚函数表。
  • 同样的,靠近派生类的是baseA,然后才是B,故按照先声明先存储的顺序,先是基类baseA的成员变量,然后是基类baseB的成员变量,最后才是派生类的成员变量。

1.2.3 sizeof



24字节= 基类总4个int成员变量 + 派生类的2个int成员变量 = 4 * 4 + 4 * 2 = 24

1.2.4 监视结果

2.两个基类中只有一个类有虚函数

2.1 两个基类中只有一个类有虚函数,派生类有虚函数

2.1.1 代码

// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
int _mc = 3;
int _md = 4;
}; // 派生类
class deriveA : public baseA, public baseB
{
public:
virtual void print() { std::cout << "virtual deriveA::print() \n\n"; }
int _me = 3;
int _mf = 4;
};

2.1.2 内存布局

1>class deriveA	size(28):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | _mc
1>16 | | _md
1> | +---
1>20 | _me
1>24 | _mf
1> +---
1>
1>deriveA::$vftable@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
1> 1 | &deriveA::print
  • 虚函数表指针。

    • 由于基类和派生类都存在虚函数,故存在虚函数表指针。
    • 内存布局顺序:先基类,再派生,先声明先存储。代码中,基类baseA存在虚函数且基类baseA先声明,所以,{vfptr}的位置放在了基类baseA中,且排在第一位,然后才是基类baseA的成员变量,接着是基类baseB的成员变量,最后才是派生类的成员变量。
  • 虚函数表。
    • 存储虚函数的虚函数表。本例中,虚函数只有基类baseA和派生类才有,故,按照先声明,先存储的顺序。依次为:&baseA::show,&deriveA::print。

2.1.3 sizeof

28字节 = 基类总共4个int成员变量 + 派生类的2个int成员变量 + 一个虚函数表指针4字节

2.1.4 监视结果

2.1.5 交换baseA和baseB的顺序呢?

  • 代码
// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
int _mc = 3;
int _md = 4;
}; // 派生类
class deriveA : public baseB, public baseA
{
public:
virtual void print() { std::cout << "virtual deriveA::print() \n\n"; }
int _me = 3;
int _mf = 4;
};
  • 内存分布
1>class deriveA	size(28):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | _mc
1>16 | | _md
1> | +---
1>20 | _me
1>24 | _mf
1> +---
1>
1>deriveA::$vftable@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
1> 1 | &deriveA::print
1>
1>deriveA::print this adjustor: 0
  • 你肯定也发现了。这与2.1.2内存布局的情况是一摸一样的。

  • 存储顺序:先基类,再派生,先声明,先存储。由于存在虚函数,所以,这个需要优先考虑。

  • 尽管baseB更靠近派生类,但是baseA的优先级更高,因为基类baseA存在虚函数而基类baseB不存在。

  • 虚表依然仅按照先声明像存储的顺序存储虚函数。基类优先级 > 派生类优先级。

  • 2.1.5.3 sizeof

    2.1.3 sizeof相同。因为没有成员变量增加和减少。

2.2 两个基类中只有一个类有虚函数,派生类没有虚函数

2.2.1 代码

// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
int _mc = 3;
int _md = 4;
}; // 派生类
class deriveA : public baseA, public baseB
{
public:
int _me = 3;
int _mf = 4;
};

2.2.1 内存分布

1>class deriveA	size(28):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | _mc
1>16 | | _md
1> | +---
1>20 | _me
1>24 | _mf
1> +---
1>
1>deriveA::$vftable@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
  • 虚函数表指针。 因为只有基类baseA存在虚函数,故需要第一个考虑虚函数表指针。 且基类baseA先声明。
  • 虚函数表,因为只有基类baseA存在虚函数,故虚函数表只保存了基类的虚函数地址。
  • 存储顺序: 先基类,再派生,先声明像存储。所以;最先是baseA,然后是baseB,最后才是派生类。

2.2.2 交换基类顺序呢?

  • 代码
class deriveA : public baseB, public baseA
{
.....
}
  • 内存分布
1>
1>class deriveA size(28):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | _mc
1>16 | | _md
1> | +---
1>20 | _me
1>24 | _mf
1> +---
1>
1>deriveA::$vftable@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
  • 你肯定又发现了,这与上面的内存分布情况是一样的。
  • 为什么?因为基类有两个,且只有其中一个存在虚函数,所以,按照先声明存储的规则,且考虑到有虚函数的基类的优先级大于没有虚函数的基类。故,含有虚函数的类成员变量排在内存分布图的虚函数表指针的后面,而且是紧挨着。

2.2.3 sizeof

2.2.4 监视结果

3.两个基类中都含有虚函数

3.1 两个基类中都含有虚函数,派生类有虚函数

3.1.1 代码

// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
virtual void play() { std::cout << "virtual baseB::play() \n\n"; }
int _mc = 3;
int _md = 4;
}; // 派生类
class deriveA : public baseA, public baseB
{
public:
virtual void print() { std::cout << "deriveA::print()\n\n"; }
int _me = 3;
int _mf = 4;
};

3.1.2 内存分布

1>class deriveA	size(32):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | {vfptr}
1>16 | | _mc
1>20 | | _md
1> | +---
1>24 | _me
1>28 | _mf
1> +---
1>
1>deriveA::$vftable@baseA@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
1> 1 | &deriveA::print
1>
1>deriveA::$vftable@baseB@:
1> | -12
1> 0 | &baseB::play
1>
1>deriveA::print this adjustor: 0
  • 等等,你是不是也发现了,内存模型中出现了2个{vfptr}。一个属于基类baseA,另一个属于基类baseB。
  • 虚函数表。 发现了吧:1.派生类的虚函数表多了一张表,2.派生类的虚函数是放在第一张虚函数表中。
  • 按照先前的顺序:先基类,再派生,先声明,先存储。但是有虚函数的类要优先考虑。这里基类baseA和baseB还有派生类都含有虚函数。那么先看基类,按照先声明先存储的顺序,baseA基类相对baseB基类先声明,故基类baseA的虚函数表指针首先被存储,接着再是基类baseA的成员变量,然后是基类baseB的虚函数表指针,基类baseB的成员变量。最后是派生类。
  • 为什么派生类的虚函数是追加在第一张虚表的后面? 请看下面的一段汇编(没学过汇编,不献丑)结论: 派生类的虚函数是追加在第一张虚表的后面。当需要使用派生类的虚函数是,用第一张表的虚函数表指针指向派生类的虚函数即可。(个人观点)下面的汇编也应该是这样:1,找到虚函数表的起始地址,2.找到派生类的虚函数偏移,3.使用虚函数表指针指向派生类的虚函数。
	deriveA *pda = &da;
00A7A02E lea eax,[ebp-28h]
00A7A031 mov dword ptr [ebp-34h],eax
pda->print();
00A7A034 mov eax,dword ptr [ebp-34h]
00A7A037 mov edx,dword ptr [eax]
00A7A039 mov esi,esp
00A7A03B mov ecx,dword ptr [ebp-34h]
00A7A03E mov eax,dword ptr [edx+4]
00A7A041 call eax
00A7A043 cmp esi,esp
00A7A045 call 00A714C4

3.1.3 sizeof

32字节 = 基类总共4个int成员变量 + 派生类的2个int变量 + 2个虚函数表指针 = 4 * 4 + 2 * 4 + 2 * 4 = 32

3.1.4 监视结果

监视结果中看不到派生类的虚函数存储情况。

3.2 两个基类中都含有虚函数,派生类没有虚函数

3.2.1 代码

// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; }
int _ma = 1;
int _mb = 2;
};
// 基类
class baseB
{
public:
virtual void play() { std::cout << "virtual baseB::play() \n\n"; }
int _mc = 1;
int _md = 2;
}; // 派生类
class deriveA : public baseA , public baseB
{
public:
int _me = 3;
int _mf = 4;
};

3.2.2 内存布局

1>class deriveA	size(32):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | {vfptr}
1>16 | | _mc
1>20 | | _md
1> | +---
1>24 | _me
1>28 | _mf
1> +---
1>
1>deriveA::$vftable@baseA@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
1>
1>deriveA::$vftable@baseB@:
1> | -12
1> 0 | &baseB::play
  • 虚函数表指针。基类baseA先于基类baseB声明,两个基类同时含有虚函数,但是先声明,先存储。故先存储基类baseA的虚函数表指针。
  • 虚函数表。 多继承,故每张表各自存储自己的虚函数表的信息。

3.2.3 sizeof

3.2.4 监视结果

4.扩展继承3个基类

4.1 3个基类都有虚函数, 派生类没有虚函数

4.1.1 代码

// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
virtual void play() { std::cout << "virtual baseB::play() \n\n"; }
int _mc = 3;
int _md = 4;
}; class baseC
{
public:
virtual void listening() { std::cout << "virtual baseC::listening() \n\n"; }
int _mh = 7;
int _mi = 8;
}; // 派生类
class deriveA : public baseA, public baseB, public baseC
{
public:
int _me = 3;
int _mf = 4;
};

4.1.2 内存布局

1>class deriveA	size(44):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | {vfptr}
1>16 | | _mc
1>20 | | _md
1> | +---
1>24 | +--- (base class baseC)
1>24 | | {vfptr}
1>28 | | _mh
1>32 | | _mi
1> | +---
1>36 | _me
1>40 | _mf
1> +---
1>
1>deriveA::$vftable@baseA@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseA::show
1>
1>deriveA::$vftable@baseB@:
1> | -12
1> 0 | &baseB::play
1>
1>deriveA::$vftable@baseC@:
1> | -24
1> 0 | &baseC::listening

4.2 三个基类中每个基类都有虚函数,派生类也有虚函数

4.2.1 代码

// 基类
class baseA
{
public:
virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
virtual void play() { std::cout << "virtual baseB::play() \n\n"; }
int _mc = 3;
int _md = 4;
}; class baseC
{
public:
virtual void listening() { std::cout << "virtual baseC::listening() \n\n"; }
int _mh = 7;
int _mi = 8;
}; // 派生类
class deriveA : public baseA, public baseB, public baseC
{
public:
virtual void show() { std::cout << "virtual deriveA::show() \n\n"; }
int _me = 3;
int _mf = 4;
};

4.2.2 内存布局

1>class deriveA	size(44):
1> +---
1> 0 | +--- (base class baseA)
1> 0 | | {vfptr}
1> 4 | | _ma
1> 8 | | _mb
1> | +---
1>12 | +--- (base class baseB)
1>12 | | {vfptr}
1>16 | | _mc
1>20 | | _md
1> | +---
1>24 | +--- (base class baseC)
1>24 | | {vfptr}
1>28 | | _mh
1>32 | | _mi
1> | +---
1>36 | _me
1>40 | _mf
1> +---
1>
1>deriveA::$vftable@baseA@:
1> | &deriveA_meta
1> | 0
1> 0 | &deriveA::show
1>
1>deriveA::$vftable@baseB@:
1> | -12
1> 0 | &baseB::play
1>
1>deriveA::$vftable@baseC@:
1> | -24
1> 0 | &baseC::listening
1>
1>deriveA::show this adjustor: 0
  • Note: 派生类的虚函数是放在第一张虚函数表中的。

4.3 三个基类中其中2个基类都有虚函数,另一个基类没有虚函数,派生类没有虚函数

4.3.1 代码

// 基类
class baseA
{
public:
int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
virtual void play() { std::cout << "virtual baseB::play() \n\n"; }
int _mc = 3;
int _md = 4;
}; class baseC
{
public:
virtual void listening() { std::cout << "virtual baseC::listening() \n\n"; }
int _mh = 7;
int _mi = 8;
}; // 派生类
class deriveA : public baseA, public baseB, public baseC
{
public:
int _me = 3;
int _mf = 4;
};

4.3.2 内存布局

1>class deriveA	size(40):
1> +---
1> 0 | +--- (base class baseB)
1> 0 | | {vfptr}
1> 4 | | _mc
1> 8 | | _md
1> | +---
1>12 | +--- (base class baseC)
1>12 | | {vfptr}
1>16 | | _mh
1>20 | | _mi
1> | +---
1>24 | +--- (base class baseA)
1>24 | | _ma
1>28 | | _mb
1> | +---
1>32 | _me
1>36 | _mf
1> +---
1>
1>deriveA::$vftable@baseB@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseB::play
1>
1>deriveA::$vftable@baseC@:
1> | -12
1> 0 | &baseC::listening

4.4 三个基类中其中2个基类都有虚函数,另一个基类没有虚函数,派生类有虚函数

4.4.1 代码

// 基类
class baseA
{
public:
// virtual void show() { std::cout << "virtual baseA::show() \n\n"; } int _ma = 1;
int _mb = 2;
}; class baseB
{
public:
virtual void play() { std::cout << "virtual baseB::play() \n\n"; }
int _mc = 3;
int _md = 4;
}; class baseC
{
public:
virtual void listening() { std::cout << "virtual baseC::listening() \n\n"; }
int _mh = 7;
int _mi = 8;
}; // 派生类
class deriveA : public baseA, public baseB, public baseC
{
public:
virtual void show() { std::cout << "virtual deriveA::show() \n\n"; }
int _me = 3;
int _mf = 4;
};

4.4.2 内存分布

1>
1>class deriveA size(40):
1> +---
1> 0 | +--- (base class baseB)
1> 0 | | {vfptr}
1> 4 | | _mc
1> 8 | | _md
1> | +---
1>12 | +--- (base class baseC)
1>12 | | {vfptr}
1>16 | | _mh
1>20 | | _mi
1> | +---
1>24 | +--- (base class baseA)
1>24 | | _ma
1>28 | | _mb
1> | +---
1>32 | _me
1>36 | _mf
1> +---
1>
1>deriveA::$vftable@baseB@:
1> | &deriveA_meta
1> | 0
1> 0 | &baseB::play
1> 1 | &deriveA::show
1>
1>deriveA::$vftable@baseC@:
1> | -12
1> 0 | &baseC::listening
1>
1>deriveA::show this adjustor: 0
  • Note: 派生类的虚函数存放在第一张虚函数表中。

c++内存分布之虚函数(多继承)的更多相关文章

  1. c++内存分布之虚函数(单一继承)

    系列 c++内存分布之虚函数(单一继承) [本文] c++内存分布之虚函数(多继承) 结论 1.虚函数表指针 和 虚函数表 1.1 影响虚函数表指针个数的因素只和派生类的父类个数有关.多一个父类,派生 ...

  2. C++解析(25):关于动态内存分配、虚函数和继承中强制类型转换的疑问

    0.目录 1.动态内存分配 1.1 new和malloc的区别 1.2 delete和free的区别 2.虚函数 2.1 构造函数与析构函数是否可以成为虚函数? 2.2 构造函数与析构函数是否可以发生 ...

  3. 【整理】C++虚函数及其继承、虚继承类大小

    参考文章: http://blog.chinaunix.net/uid-25132162-id-1564955.html http://blog.csdn.net/haoel/article/deta ...

  4. c/c++: c++继承 内存分布 虚表 虚指针 (转)

    http://www.cnblogs.com/DylanWind/archive/2009/01/12/1373919.html 前部分原创,转载请注明出处,谢谢! class Base  {  pu ...

  5. C++浅析——继承类内存分布和虚析构函数

    继承类研究 1. Code 1.1 Cbase, CTEST为基类,CTest2为其继承类,并重新申明了基类中的同名变量 class CBase { public: int Data; CBase() ...

  6. c++ 对象内存分配和虚函数

    1. c++类对象(不含虚函数)在内存中的分布 c++类中有四种成员:静态数据.非静态数据.静态函数.非静态函数. 1. 非静态数据成员放在每个对象内部,作为对象专有的数据成员 2. 静态数据成员被抽 ...

  7. c++内存分布之虚析构函数

    关于 本文代码演示环境: VS2017+32程序 虚析构函数是一种特殊的虚函数,可以知道,虚函数影响的内存分布规律应该也适用虚析构函数.看看实际结果. Note,一个类中,虚析构函数只能有一个. 本文 ...

  8. C++对象的内存分布和虚函数表

    c++中一个类中无非有四种成员:静态数据成员和非静态数据成员,静态函数和非静态函数. 1.非静态数据成员被放在每一个对象体内作为对象专有的数据成员.    2.静态数据成员被提取出来放在程序的静态数据 ...

  9. C#中的虚函数及继承关系

    转载:http://blog.csdn.net/suncherrydream/article/details/8423991 若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法. 虚 ...

随机推荐

  1. SAM 感性瞎扯

    SAM 做题笔记. 这里是 SAM 感性瞎扯. 最近学了后缀自动机(Suffix_Automaton,SAM),深感其巧妙之处,故写文以记之. 部分文字与图片来源于 OI-Wiki,hihoCoder ...

  2. python爬虫采集

    python爬虫采集 最近有个项目需要采集一些网站网页,以前都是用php来做,但现在十分流行用python做采集,研究了一些做一下记录. 采集数据的根本是要获取一个网页的内容,再根据内容筛选出需要的数 ...

  3. 用C语言的LED实验,有汇编哦!

    C语言LED实验 1.汇编激活CPU 首先要明白对于没有系统开发板(也就是裸机)来说,是没办法直接对C进行识别.所以需要一段汇编语言,来配置CPU的资源,选择CPU运行模式,初始化指针位置. 代码如下 ...

  4. 内网穿透—使用 frp 实现内外网互通

    前言 什么是内网穿透? 内网穿透,又叫 NET 穿透,是计算机用语.用通俗的说法就是你家里的个人电脑,可以直接被外网的人访问.例如你在公司,不通过远程工具,直接也可以访问到家里的电脑(本文章特指 we ...

  5. 【Penetration】红日靶场(一)

    nmap探查存活主机 nmap -sP 10.10.2.0/24 图片: https://uploader.shimo.im/f/cfuQ653BEvyA42FR.png!thumbnail?acce ...

  6. android studio 使用 aidl(三)权限验证

    这篇文章是基于android studio 使用 aidl (一) 和 android studio 使用 aidl(二) 异步回调 下面的代码都是简化的,如果看不懂请先移步上2篇文章 网上的东西太坑 ...

  7. OSGI与Spring结合开发web工程

    简介: 作为一个新的事实上的工业标准,OSGi 已经受到了广泛的关注, 其面向服务(接口)的基本思想和动态模块部署的能力, 是企业级应用长期以来一直追求的目标.Spring 是一个著名的 轻量级 J2 ...

  8. RAC常见的宏

    1. RAC           作用:用来给某个对象的某个属性绑定信号,只要产生信号内容就会把内容给属性赋值            RAC(_label, text) = _textField.ra ...

  9. 图书管理系统总结——JAVA Swing控件简介

    断断续续学习JAVA语言,写了一个多月数据库大作业,终于在五一过后写完了.由于第一次使用JAVA和数据库,遇到了许多问题,记录下来,以备以后查看. 我使用的JAVA SE,说实话,在开发后期,觉得JA ...

  10. javascript将平行的拥有上下级关系的数据转换成树形结构

    转换函数 var Littlehow = {}; /** * littlehow 2019-05-15 * 平行数据树形转换器 * @type {{format: tree.format, sort: ...