计算vtable的大小
在ClassFileParser::parseClassFile()函数中会计算vtable和itable所需要的大小,因为vtable和itable是内嵌在Klass中的,parseClassFile()函数解析完Class文件后会创建instanceKlass来保存相关的信息,在创建instanceKlass时需要知道创建对象的大小,所以必须要把vtable和itable也计算出来。下面就来介绍一下vtable和itable大小的计算过程,这一篇只介绍vtable大小的计算过程,下一篇将介绍itable大小的计算过程。
在ClassFileParser::parseClassFile()函数中首先计算vtable的大小,如下:
// Size of Java vtable (in words)
int vtable_size = 0;
int itable_size = 0;
int num_miranda_methods = 0;
GrowableArray<Method*> all_mirandas(20);
InstanceKlass* tmp = super_klass();
// 计算虚函数表的大小和mirandas方法的数量,这个虚函数表的值是什么时候进行填充的呢???
klassVtable::compute_vtable_size_and_num_mirandas(
&vtable_size,
&num_miranda_methods,
&all_mirandas,
tmp,
methods,
access_flags,
class_loader,
class_name,
local_interfaces,
CHECK_(nullHandle)
);
调用方法时传递的methods就是调用parse_methods()方法后的返回值,数组中存储了类或接口中定义或声明的所有方法。
调用的compute_vtable_size_and_num_mirandas()方法的实现如下:
void klassVtable::compute_vtable_size_and_num_mirandas(
int* vtable_length_ret,
int* num_new_mirandas,
GrowableArray<Method*>* all_mirandas,
Klass* super,
Array<Method*>* methods,
AccessFlags class_flags,
Handle classloader,
Symbol* classname,
Array<Klass*>* local_interfaces,
TRAPS) { // set up default result values
int vtable_length = 0; // start off with super's vtable length
InstanceKlass* superklass = (InstanceKlass*)super;
// 获取父类 vtable 的大小,并将当前类的 vtable 的大小设置为父类 vtable 的大小
vtable_length = super == NULL ? 0 : superklass->vtable_length(); // go thru each method in the methods table to see if it needs a new entry
int len = methods->length();
for (int i = 0; i < len; i++) {
methodHandle mh(THREAD, methods->at(i)); // 循环遍历当前 Java 类的每一个方法 ,调用 needs_new_vtable_entry()函数进行判断,
// 如果判断的结果是 true ,则将 vtable 的大小增 1
if (needs_new_vtable_entry(mh, super, classloader, classname, class_flags, THREAD)) {
vtable_length += vtableEntry::size(); // we need a new entry
}
} GrowableArray<Method*> new_mirandas(20);
// compute the number of mirandas methods that must be added to the end
get_mirandas(&new_mirandas, all_mirandas, super, methods, NULL, local_interfaces);
*num_new_mirandas = new_mirandas.length(); // Interfaces do not need interface methods in their vtables
// This includes miranda methods and during later processing, default methods
if (!class_flags.is_interface()) { // 只有类才需要处理miranda方法,接口不需要处理
// 类的vtable大小要加上miranda方法的大小
vtable_length += *num_new_mirandas * vtableEntry::size();
} // 处理数组类时,其vtable_length应该等于Object的vtable_length,通常为5,因为Object中有5个方法需要动态绑定
if (Universe::is_bootstrapping() && vtable_length == 0) {
// array classes don't have their superclass set correctly during bootstrapping
vtable_length = Universe::base_vtable_size();
} *vtable_length_ret = vtable_length;
}
vtable的大小通常都是由3部分计算得出:
父类vtable的大小+当前方法需要的vtable大小+mirandas需要的大小
下面会重点介绍needs_new_vtable_entry()方法和get_mirandas()方法,前一个方法对计算当前方法需要的vtable大小很重要,后一个方法是计算mirandas方法需要的大小。
1、needs_new_vtable_entry()方法
循环处理当前类中定义的方法,调用needs_new_vtable_entry()方法判断此方法是否需要新的vtable entry,因为有些方法可能不需要新的vtable entry,如重写父类方法时,当前类中的方法只需要更新拷贝自父类vtable中对应的vtable entry即可。调用的needs_new_entry()方法的实现如下:
源代码位置:hotspot/src/share/vm/oops/klassVtable.cpp
// Find out if a method "m" with superclass "super", loader "classloader" and
// name "classname" needs a new vtable entry. Let P be a class package defined
// by "classloader" and "classname".
// NOTE: The logic used here is very similar to the one used for computing
// the vtables indices for a method. We cannot directly use that function because,
// we allocate the InstanceKlass at load time, and that requires that the
// superclass has been loaded.
// However, the vtable entries are filled in at link time(在连接时才会被填充), and therefore
// the superclass' vtable may not yet have been filled in.
bool klassVtable::needs_new_vtable_entry(methodHandle target_method,
Klass* super,
Handle classloader,
Symbol* classname,
AccessFlags class_flags,
TRAPS) {
if (class_flags.is_interface()) { // 接口不需要vtable表
// Interfaces do not use vtables, so there is no point to assigning
// a vtable index to any of their methods. If we refrain(克制; 节制; 避免;) from doing this,
// we can use Method::_vtable_index to hold the itable index
return false;
} if (target_method->is_final_method(class_flags) || // final方法不需要一个新的entry
// a final method never needs a new entry; final methods can be statically
// resolved and they have to be present in the vtable only if they override
// a super's method, in which case they re-use its entry
( target_method()->is_static() ) || // 静态方法不需要一个新的entry
// static methods don't need to be in vtable
( target_method()->name() == vmSymbols::object_initializer_name() ) // <init>不会被动态绑定
// <init> is never called dynamically-bound
){
return false;
} // Concrete interface methods do not need new entries, they override
// abstract method entries using default inheritance rules
if (target_method()->method_holder() != NULL &&
target_method()->method_holder()->is_interface() &&
!target_method()->is_abstract() ){
return false;
} // we need a new entry if there is no superclass
if (super == NULL) {
return true;
} // private methods in classes always have a new entry in the vtable
// specification interpretation since classic has
// private methods not overriding
// JDK8 adds private methods in interfaces which require invokespecial
if (target_method()->is_private()) {
return true;
} // 省略对miranda方法的判断逻辑
return true; // found no match; we need a new entry
}
所有的私有方法都需要vtable entry。还有一类特殊的miranda方法也需要实现“晚绑定”,所以也会有vtable entry。miranda方法是为了解决早期HotSpot虚拟机的一个Bug,因为早期虚拟机在遍历Java类的方法时,只会遍历类及所有父类的方法,不会遍历Java类所实现的接口里的方法,这会导致一个问题,即如果Java类没有实现接口里的方法,那么接口中的方法将不会被遍历到。为了解决这个问题,Javac等前端编译器会向Java类中添加方法,这些方法就是miranda方法。举个例子如下:
public interface IA{
void test();
} public abstract class CA implements IA{
public CA(){
test();
}
}
CA类实现了IA接口,但是并没有实现接口中定义的test()方法,源代码并没有任何问题,如果只遍历类及父类,那么是无法查找到test()方法的,所以早期的HotSpot需要Javac等编译器会为CA类合成一个miranda方法,如下:
public interface IA{
void test();
} public abstract class CA implements IA{
public CA(){
test();
} // miranda
public abstract void test();
}
这样就解决了HotSpot不搜索接口的Bug。不过现在的虚拟机版本并不需要合成miranda方法(Class文件中不存在miranda方法),但是在填充类的vtable时,如果这个类实现的接口中有没有被实现的方法,那么仍然需要在vtable中新增vtable entry,其实也是起到了和之前一样的效果。
接着看needs_new_vtable_entry()方法中对miranda方法的判断逻辑,如下:
// search through the super class hierarchy to see if we need a new entry
ResourceMark rm;
Symbol* name = target_method()->name();
Symbol* signature = target_method()->signature();
Klass* k = super;
Method* super_method = NULL;
InstanceKlass *holder = NULL;
Method* recheck_method = NULL;
while (k != NULL) {
// lookup through the hierarchy for a method with matching name and sign.
super_method = InstanceKlass::cast(k)->lookup_method(name, signature);
if (super_method == NULL) {
break; // we still have to search for a matching miranda method
}
// get the class holding the matching method
// make sure you use that class for is_override
InstanceKlass* superk = super_method->method_holder();
// we want only instance method matches
// pretend private methods are not in the super vtable
// since we do override around them: e.g. a.m pub/b.m private/c.m pub,
// ignore private, c.m pub does override a.m pub
// For classes that were not javac'd together, we also do transitive overriding around
// methods that have less accessibility
if ( (!super_method->is_static()) &&
(!super_method->is_private()) ) { // super_method即不是静态也不是private的
if (superk->is_override(super_method, classloader, classname, THREAD)) {
return false;
// else keep looking for transitive overrides
}
} // Start with lookup result and continue to search up
k = superk->super(); // haven't found an override match yet; continue to look
} // end while // if the target method is public or protected it may have a matching
// miranda method in the super, whose entry it should re-use.
// Actually, to handle cases that javac would not generate, we need
// this check for all access permissions.
InstanceKlass *sk = InstanceKlass::cast(super);
if (sk->has_miranda_methods()) {
if (sk->lookup_method_in_all_interfaces(name, signature, false) != NULL) {
return false; // found a matching miranda; we do not need a new entry
}
}
调用lookup_method()方法搜索父类中是否有匹配name和signature的方法。如果搜索到方法,那可能是重写的情况,在重写情况下不需要新为此方法增加vtableEntry,只需要更新即可;如果搜索不到也不一定说明需要一个新的vtableEntry,因为还有miranda方法的情况,当调用lookup_method_in_all_interfaces()方法搜索到相关方法时,表示不需要新的vtableEntry,举个例子如下:
interface IA {
void test();
} abstract class CA implements IA{ } public abstract class MirandaTest extends CA {
public abstract void test();
}
在处理MirandaTest类的test()方法时,从CA和Object父类中无法搜索到test()类,但是在处理CA时,由于CA类没有实现IA接口中的test()方法,所以CA类的vtable中含有代表test()方法的vtableEntry,那么MirandaTest类中的test()方法此时就不需要一个新的vtableEntry了,只需要更新即可,所以方法最终返回false。
方法中的lookup_method()的实现如下:
Method* lookup_method(Symbol* name, Symbol* signature) const {
return uncached_lookup_method(name, signature);
} // uncached_lookup_method searches both the local class methods array and all
// superclasses methods arrays, skipping any overpass methods in superclasses.
Method* InstanceKlass::uncached_lookup_method(Symbol* name, Symbol* signature) const {
Klass* klass = const_cast<InstanceKlass*>(this);
bool dont_ignore_overpasses = true; // For the class being searched, find its overpasses.
while (klass != NULL) {
Method* method = InstanceKlass::cast(klass)->find_method(name, signature);
if ((method != NULL) && (dont_ignore_overpasses || !method->is_overpass())) {
return method;
}
klass = InstanceKlass::cast(klass)->super();
dont_ignore_overpasses = false; // Ignore overpass methods in all superclasses.
}
return NULL;
} // find_method looks up the name/signature in the local methods array
Method* InstanceKlass::find_method(Symbol* name, Symbol* signature) const {
return InstanceKlass::find_method(methods(), name, signature);
} // find_method looks up the name/signature in the local methods array
Method* InstanceKlass::find_method(Array<Method*>* methods, Symbol* name, Symbol* signature) {
int hit = find_method_index(methods, name, signature);
return hit >= 0 ? methods->at(hit): NULL;
}
其中的find_method_index()方法就是对methods进行二分算法来搜索名称为name和签名为signature的方法,这里不在介绍。
调用的is_override()方法的实现如下:
// Returns true iff super_method can be overridden by a method in targetclassname
// See JSL 3rd edition 8.4.6.1
// Assumes name-signature match
// "this" is InstanceKlass of super_method which must exist
// note that the InstanceKlass of the method in the targetclassname has not always been created yet
bool InstanceKlass::is_override(methodHandle super_method, Handle targetclassloader, Symbol* targetclassname, TRAPS) {
// Private methods can not be overridden
if (super_method->is_private()) {
return false;
}
// If super method is accessible, then override
if ((super_method->is_protected()) ||
(super_method->is_public())) {
return true;
}
// Package-private methods are not inherited outside of package
assert(super_method->is_package_private(), "must be package private");
return(is_same_class_package(targetclassloader(), targetclassname)); // 其中就是处理权限为default的方法
}
调用的lookup_method_in_all_interfaces()方法的实现如下:
// lookup a method in all the interfaces that this class implements
// Do NOT return private or static methods, new in JDK8 which are not externally visible
// They should only be found in the initial InterfaceMethodRef
Method* InstanceKlass::lookup_method_in_all_interfaces(Symbol* name,
Symbol* signature,
bool skip_default_methods) const {
Array<Klass*>* all_ifs = transitive_interfaces();
int num_ifs = all_ifs->length();
InstanceKlass *ik = NULL;
for (int i = 0; i < num_ifs; i++) {
ik = InstanceKlass::cast(all_ifs->at(i));
Method* m = ik->lookup_method(name, signature);
if (m != NULL && m->is_public() && !m->is_static() &&
(!skip_default_methods || !m->is_default_method())) {
return m;
}
}
return NULL;
}
在函数中调用此方法时,skip_default_methods的值为false。逻辑实现比较简单,调用InstanceKlass类的lookup_method()方法查找接口中是否有名称为name、签名为signature的方法,如果查找到了public、非静态方法则直接返回,也就是说父类的vtable中已经存在了名称为name、签名为signature的方法的vtableEntry,所以当前类中并不再需要一个新的vtableEntry。
2、get_mirandas()方法
调用的get_mirandas()方法的实现如下:
void klassVtable::get_mirandas(GrowableArray<Method*>* new_mirandas,
GrowableArray<Method*>* all_mirandas,
Klass* super, Array<Method*>* class_methods,
Array<Method*>* default_methods,
Array<Klass*>* local_interfaces) {
assert((new_mirandas->length() == 0) , "current mirandas must be 0"); // iterate thru the local interfaces looking for a miranda
int num_local_ifs = local_interfaces->length();
for (int i = 0; i < num_local_ifs; i++) {
InstanceKlass *ik = InstanceKlass::cast(local_interfaces->at(i));
add_new_mirandas_to_lists(new_mirandas, all_mirandas,
ik->methods(), class_methods,
default_methods, super);
// iterate thru each local's super interfaces
Array<Klass*>* super_ifs = ik->transitive_interfaces();
int num_super_ifs = super_ifs->length();
for (int j = 0; j < num_super_ifs; j++) {
InstanceKlass *sik = InstanceKlass::cast(super_ifs->at(j));
add_new_mirandas_to_lists(new_mirandas, all_mirandas,
sik->methods(), class_methods,
default_methods, super);
}
}
}
如上方法遍历当前类实现的接口以及接口所继承的所有接口,然后调用add_new_mirandas_to_lists()方法进行处理,此方法的实现如下:
// Scans current_interface_methods for miranda methods that do not
// already appear in new_mirandas, or default methods, and are also not defined-and-non-private
// in super (superclass). These mirandas are added to all_mirandas if it is
// not null; in addition, those that are not duplicates of miranda methods
// inherited by super from its interfaces are added to new_mirandas.
// Thus, new_mirandas will be the set of mirandas that this class introduces,
// all_mirandas will be the set of all mirandas applicable to this class
// including all defined in superclasses.
void klassVtable::add_new_mirandas_to_lists(
GrowableArray<Method*>* new_mirandas,
GrowableArray<Method*>* all_mirandas,
Array<Method*>* current_interface_methods,
Array<Method*>* class_methods,
Array<Method*>* default_methods,
Klass* super
){ // iterate thru the current interface's method to see if it a miranda
int num_methods = current_interface_methods->length();
for (int i = 0; i < num_methods; i++) {
Method* im = current_interface_methods->at(i);
bool is_duplicate = false;
int num_of_current_mirandas = new_mirandas->length(); // check for duplicate mirandas in different interfaces we implement
for (int j = 0; j < num_of_current_mirandas; j++) {
Method* miranda = new_mirandas->at(j);
if ((im->name() == miranda->name()) &&
(im->signature() == miranda->signature()) ){
is_duplicate = true;
break;
}
} if (!is_duplicate) { // we don't want duplicate miranda entries in the vtable
if (is_miranda(im, class_methods, default_methods, super)) { // is it a miranda at all?
InstanceKlass *sk = InstanceKlass::cast(super);
// check if it is a duplicate of a super's miranda
if (sk->lookup_method_in_all_interfaces(im->name(), im->signature(), false) == NULL) {
new_mirandas->append(im);
}
if (all_mirandas != NULL) {
all_mirandas->append(im);
}
}
} } // end for
}
遍历当前类实现的接口(直接或间接)中定义的所有方法,如果这个方法还没有被判定为miranda方法(就是在new_mirandas数组中不存在),那么调用is_miranda()方法判断此方法是否为miranda()方法,如果是,那么还需要调用当前类的父类的lookup_method_in_all_interfaces()方法来进一步判断。举个例子如下:
interface IA {
void test();
} abstract class CA implements IA{ }
在处理CA类时,由于CA实现的接口IA中的方法test()没有对应的实现,所以接口中定义的test()方法会添加到new_mirandas数组中,意思就是需要在当前CA类的vtable中添加对应的vtableEntry。再举个例子,如下:
interface IA {
void test();
} abstract class CA implements IA{ } interface IB {
void test();
}
public abstract class MirandaTest extends CA implements IB{ }
如果当前类为MirandaTest,那么实现的IB接口中的test()方法没有对应的实现,但是并不一定会添加到new_mirandas数组中,所以也就意味着不一定会新增加vtableEntry,还需要调用lookup_method_in_all_interfaces()方法来判断,由于当前类的父类CA中已经有名称和签名都相等的test()方法对应的vtableEntry了,所以只需要重用此vtableEntry即可。
调用的is_miranda()方法的实现如下:
// check if a method is a miranda method, given a class's methods table,
// its default_method table and its super
// Miranda methods are calculated twice:
// first: before vtable size calculation: including abstract and default
// This is seen by default method creation
// Second: recalculated during vtable initialization: only abstract
// This is seen by link resolution and selection.
// "miranda" means not static, not defined by this class.
// private methods in interfaces do not belong in the miranda list.
// the caller must make sure that the method belongs to an interface implemented by the class
// Miranda methods only include public interface instance methods
// Not private methods, not static methods, not default == concrete abstract
// Miranda methods also do not include overpass methods in interfaces
bool klassVtable::is_miranda(Method* m, Array<Method*>* class_methods,
Array<Method*>* default_methods, Klass* super) {
if (m->is_static() || m->is_private() || m->is_overpass()) {
return false;
}
Symbol* name = m->name();
Symbol* signature = m->signature(); if (InstanceKlass::find_instance_method(class_methods, name, signature) == NULL) {
// did not find it in the method table of the current class
if ((default_methods == NULL) ||
InstanceKlass::find_method(default_methods, name, signature) == NULL) {
// 当前类没有父类,那么接口中定义的方法肯定没有对应的实现,此接口中的方法是miranda方法
if (super == NULL) {
// super doesn't exist
return true;
} // 需要从父类中找一个非静态的、名称为name、签名为signauture的方法,如果是静态方法,则
// 需要继续查找,因为静态方法不参与动态绑定,也就不需要判断是否重写与实现等特性
Method* mo = InstanceKlass::cast(super)->lookup_method(name, signature);
while (
mo != NULL &&
mo->access_flags().is_static() &&
mo->method_holder() != NULL &&
mo->method_holder()->super() != NULL
){
mo = mo->method_holder()->super()->uncached_lookup_method(name, signature);
}
// 如果找不到或找到的是私有方法实现,那么说明接口中定义的方法没有对应的实现,此接口中的方法是miranda方法
if (mo == NULL || mo->access_flags().is_private() ) {
// super class hierarchy does not implement it or protection is different
return true;
}
}
} return false;
}
接口中的静态、私有等方法一定是非miranda方法,直接返回false。从class_methods数组中查找名称为name、签名为signature的方法,其中的class_methods就是当前分析的类中定义的所有方法,找不到说明没有实现对应的接口中定义的方法,有可能是miranda方法,需要继续进行判断。在判断miranda方法时传入的default_methods为NULL,所以需要继续从父类中判断。如果没有父类或父类中找不到对应的方法实现,那么方法会返回true,表示是miranda方法。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
13、类加载器
14、类的双亲委派机制
15、核心类的预装载
16、Java主类的装载
17、触发类的装载
18、类文件介绍
19、文件流
20、解析Class文件
21、常量池解析(1)
22、常量池解析(2)
23、字段解析(1)
24、字段解析之伪共享(2)
25、字段解析(3)
28、方法解析
29、klassVtable与klassItable类的介绍
作者持续维护的个人博客classloading.com。
关注公众号,有HotSpot源码剖析系列文章!
计算vtable的大小的更多相关文章
- 计算itable的大小
在ClassFileParser::parseClassFile()函数中计算vtable和itable所需要的大小,之前已经介绍过vtable大小的计算,这一篇将详细介绍itable大小的计算过程. ...
- ios UITextView 计算文字内容大小
先设置好 textView的内容文字,再调用以下代码,就能够得到文字内容的size,其中参数表示最大的size的尺寸,通常,高度应该不限制,宽度是控件的宽度. let newSize = statem ...
- 【转】Android绘制View的过程研究——计算View的大小
Android绘制View的过程研究——计算View的大小 转自:http://liujianqiao398.blog.163.com/blog/static/18182725720121023218 ...
- iOS计算缓存文件的大小
//获取缓存文件路径 -(NSString *)getCachesPath{ // 获取Caches目录路径 NSArray *paths = NSSearchPathForDirectoriesIn ...
- PHP计算某个目录大小的方法
用PHP来计算某个目录大小的方法. PHP CURL session COOKIE 可以调用系统命令,还可以这样: <?php function dirsize($dir) { @$dh ...
- python计算文件夹大小(linux du命令 简化版)
C盘又满了,怎么办?用了一些垃圾清理软件(或者bat脚本),但是还是不理想,那么具体哪些文件夹下面有巨大的文件呢?windows并不能通过详细信息看到每个文件夹的大小(PS:这里所谓的文件夹的大小是指 ...
- Python_计算文件夹大小
计算文件夹大小 os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 os.path.join(path1[, path2[, ...]]) 将 ...
- ios 拉伸图片和计算文字的大小
一.拉伸图片 /** * 传入图片的名称,返回一张可拉伸不变形的图片 * * @param imageName 图片名称 * * @return 可拉伸图片 */ + (UIImage *)resiz ...
- Java中计算对象的大小
一.计算对象大小的方法 Java中如何计算对象的大小呢,找到了4种方法: 1.java.lang.instrument.Instrumentation的getObjectSize方法: 2.BTrac ...
随机推荐
- Git的自定义和特殊文件配置
目录 备注: 知识点 自定义Git 忽略特殊文件 .gitignore忽略文件 忽略文件的原则是: 忽略文件示例 .gitignore文件查看和强制添加 备注: 本文参考于廖雪峰老师的博客Git教程. ...
- Windows下安装Python 3.X 版本
一. Python下载 Python官方下载地址 演示下载的版本为Python 3.8.3 ,你可以根据自己的选择安装其他版本的Python 二. Python 安装 下载完安装包双击安装时出错(Wi ...
- 目前解决移动端1px边框最好的方法
在移动端开发时,经常会遇到在视网膜屏幕中元素边框变粗的问题.本文将带你探讨边框变粗问题的产生原因及介绍目前市面上最好的解决方法. 1px 边框问题的由来 苹果 iPhone4 首次提出了 Retina ...
- 乌班图16 配置nginx
阿里云 乌班图16 安装ngnix sudo apt install nginx nginx 启动 重启 关闭 sudo service nginx start restart stop status ...
- spring tx——TransactionManger
TransactionDefinition--事务定义 定义事务属性,包括传播级别.隔离级别.名称.超时.只读等 TransactionStatus--事务状态 事务状态,包含事务对象(jdbc为Da ...
- vue手脚架中使用jq
下载jq npm install jquery; 找到build文件夹下的webpack.base.config.js 先在开始的地方引入webpack const webpack = require ...
- SSM框架整合的最新打开方式(打造最详细的SSM整合教程)
SSM整合 文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star!搜索关注微信公众号 [码出Offer] 领取各种学习资料! SSM 一.创建一个Maven项目 File ...
- 【新生学习】第一周:深度学习及pytorch基础
DEADLINE: 2020-07-25 22:00 写在最前面: 本课程的主要思路还是要求大家大量练习 pytorch 代码,在写代码的过程中掌握深度学习的各类算法,希望大家能够坚持练习,相信经度过 ...
- python基础--深浅copy(重点)
在此申明一下,博客参照了https://www.cnblogs.com/jin-xin/,自己做了部分的改动 深浅copy(重点) 先问问大家,什么是拷贝?拷贝是音译的词,其实他是从copy这个英文单 ...
- cpp求职
//Created by Arc on 2020/5/23 //////// Created by snnnow on 2020/5/20.//////面向对象的程序设计-期中测试// 根据题目实现求 ...