GCC-3.4.6源代码学习笔记(139)

5.12.5.2.2.2.1.3.9.            完成派生类的 RECORD_TYPE 布局

回到 finish_struct_1 中,下面的 keyed_classes 是一个保存那些在这个编译单元中将可能发布 vtable 的类的 tree_list 。这个链表中的候选者是不含有非内联、非纯虚方法的类。换而言之,是仅含有纯虚方法或(及)内联虚方法的类。对于这样的类,在每个该类定义出现的编译单元中(如,通过 #include ),应该发布其 vtable ;而对于含有其它虚方法的类,每个包含了其虚方法定义的编译单元要发布其 vtable

 

finish_struct_1 (continue)

 

5033     /* Find the key method.  */

5034     if (TYPE_CONTAINS_VPTR_P (t))

5035     {

5036       CLASSTYPE_KEY_METHOD (t) = key_method (t);

5037  

5038       /* If a polymorphic class has no key method, we may emit the vtable

5039         i n every translation unit where the class definition appears.  */

5040       if (CLASSTYPE_KEY_METHOD (t) == NULL_TREE)

5041         keyed_classes = tree_cons (NULL_TREE, t, keyed_classes );

5042     }

5043  

5044     /* Layout the class itself.  */

5045     layout_class_type (t, &virtuals);

5046     if (CLASSTYPE_AS_BASE (t) != t)

5047        /* We use the base type for trivial assignments, and hence it

5048         needs a mode.  */

5049       compute_record_mode (CLASSTYPE_AS_BASE (t));

 

CLASSTYPE_KEY_METHOD 保存了类中第一个非内联及非纯虚方法 ,这个域在 vtable 的产生过程中要用到。

 

4973   static tree

4974 key_method (tree type)                                                                             in class.c

4975 {

4976    tree method;

4977

4978    if (TYPE_FOR_JAVA (ty pe)

4979        || processing_template_decl

4980        || CLASSTYPE_TEMPLATE_INSTANTIATION (type)

4981        || CLASSTYPE_INTERFACE_KNOWN (type))

4982      return NULL_TREE;

4983  

4984     for (method = TYPE_METHODS (type); method != NULL_TREE;

4985          method = TREE_CHAIN (method))

4986       if (DECL_VINDEX (method) != NULL_TREE

4987          && ! DECL_DECLARED_INLINE_P (method)

4988          && ! DECL_PURE_VIRTUAL_P (method))

4989         return method;

4990  

4991     return NULL_TREE;

4992   }

 

记住在类模板中是不允许出现虚函数的。

如果我们已经确定了是否要在该编译单元中产生 vtable VTT typeinfo 及其他类似的类维护数据, CLASSTYPE_INTERFACE_KNOWN true ,这样的类即便满足上述条件,也不应该被保存在 keyed_classes 中。

接着我们可以为类布局来决定类型的真正的大小,每个数据成员的偏移量,大小,填充字节,甚至由编译器产生的维护数据。毫无疑问,这是一个相当复杂的过程。

在深入到代码之前,我们首先看一下 GCC 为下面的例子所做的布局:

1

class A { virtual A* f (); };

class B1 : virtual public A { virtual B1* f (); };

class B2 : virtual public A { virtual B2* f (); };

class C: public B1, public B2 { virtual C* f (); };

int main() { return 0; }

用命令 g++ -fdump-class-hierarchy –o test test.cc gcc 将把关于类的信息转储到一个转储文件(如: test.01.class )中,我们可以看到如下的输出(暂时忽略布局以外的信息):

Class A

   size=4 align=4

   base size=4 base align=4

A (0xb7f2d680) 0 nearly-empty

    vptr=((& A::_ZTV1A) + 8u)

Class B1

   size=4 align=4

   base size=4 base align=4

B1 (0xb7f2d700) 0 nearly-empty

    vptridx=0u vptr=((& B1::_ZTV2B1) + 16u)

  A (0xb7f2d740) 0 nearly-empty virtual

      primary-for B1 (0xb7f2d700)

      vptridx=4u vbaseoffset=-0x000000010

Class B2

   size=4 align=4

   base size=4 base align=4

B2 (0xb7f2d840) 0 nearly-empty

    vptridx=0u vptr=((& B2::_ZTV2B2) + 16u)

  A (0xb7f2d880) 0 nearly-empty virtual

      primary-for B2 (0xb7f2d840)

      vptridx=4u vbaseoffset=-0x000000010

Class C

   size=8 align=4

   base size=8 base align=4

C (0xb7f2d940) 0

    vptridx=0u vptr=((& C::_ZTV1C) + 16u)

  B1 (0xb7f2d980) 0 nearly-empty

      primary-for C (0xb7f2d940)

      subvttidx=4u

    A (0xb7f2d9c0) 0 nearly-empty virtual

        primary-for B1 (0xb7f2d980)

        vptridx=20u vbaseoffset=-0x000000010

  B2 (0xb7f2da00) 4 nearly-empty

      lost-primary

      subvttidx=12u vptridx=24u vptr=((& C::_ZTV1C) + 40u)

    A (0xb7f2d9c0) alternative-path

在输出“ A (0xb7f2d680) 0 nearly-empty ”中,“ A ”是 binfo 所来自的类,“ (0xb7f2d680) ”是 binfo 的地址,“ 0 ”是 binfo 在类布局中的偏移量,“ nearly-empty ”表明该类是一个几乎空的类。

而输出“ size=8 align=4 ”是该类的大小及对齐量,而“ base size=8 base align=4 ”是该类的 CLASSTYPE_AS_BASE (去除虚拟基类)的大小及对齐量。

2 (在类 A 中增加域“ int a ”):

class A { int a; virtual A* f (); };

class B1 : virtual public A { virtual B1* f (); };

class B2 : virtual public A { virtual B2* f (); };

class C: public B1, public B2 { virtual C* f (); };

int main() { return 0; }

其输出则是:

Class A

   size=8 align=4

   base size=8 base align=4

A (0xb7fae680) 0

    vptr=((& A::_ZTV1A) + 8u)

Class B1

   size=12 align=4

   base size=4 base align=4

B1 (0xb7fae700) 0 nearly-empty

    vptridx=0u vptr=((& B1::_ZTV2B1) + 12u)

  A (0xb7fae740) 4 virtual

      vptridx=4u vbaseoffset=-0x00000000c vptr=((& B1::_ZTV2B1) + 28u)

Class B2

   size=12 align=4

   base size=4 base align=4

B2 (0xb7fae800) 0 nearly-empty

    vptridx=0u vptr=((& B2::_ZTV2B2) + 12u)

  A (0xb7fae840) 4 virtual

      vptridx=4u vbaseoffset=-0x00000000c vptr=((& B2::_ZTV2B2) + 28u)

Class C

   size=16 align=4

   base size=8 base align=4

C (0xb7fae8c0) 0

    vptridx=0u vptr=((& C::_ZTV1C) + 12u)

  B1 (0xb7fae900) 0 nearly-empty

      primary-for C (0xb7fae8c0)

      subvttidx=4u

    A (0xb7fae940) 8 virtual

        vptridx=20u vbaseoffset=-0x00000000c vptr=((& C::_ZTV1C) + 44u)

  B2 (0xb7fae980) 4 nearly-empty

      subvttidx=12u vptridx=24u vptr=((& C::_ZTV1C) + 28u)

    A (0xb7fae940) alternative-path

通过引入域“ int a “,类 A 不再是一个几乎空的类,但 B1 B2 还是。这一事实深刻地影响着布局。在下面的代码中我们可以找到其原因。

 

4637   static void

4638   layout_class_type (tree t, tree *virtuals_p)                                                  in class.c

4639   {

4640     tree non_static_data_members;

4641     tree field;

4642     tree vptr;

4643     record_layout_info rli;

4644     /* Maps offsets (represented as INTEGER_CSTs) to a TREE_LIST of

4645       types that appear at that offset.  */

4646     splay_tree empty_base_offsets;

4647     /* True if the last field layed out was a bit-field.  */

4648     bool last_field_was_bitfield = false;

4649     /* The location at which the next field should be inserted.  */

4650     tree *next_field;

4651     /* T, as a base class.  */

4652     tree base_t;

4653  

4654     /* Keep track of the first non-static data member.  */

4655     non_static_data_members = TYPE_FIELDS (t);

4656  

4657     /* Start laying out the record.  */

4658     rli = start_record_layout (t);

4659  

4660     /* If possible, we reuse the virtual function table pointer from one

4661       of our base classes.  */

4662     determine_primary_base (t);

 

在多继承的情况下,在派生类中基类将被依次编排。第一个基类有偏移量 0 ,无疑可以从派生类的头部直接访问其成员,而其它随后的基类,要访问其成员,其成员的偏移量要加上基类本身到派生类头部的偏移量,等于包含了一个额外的间接寻址。加上深度继承的情形,需要仔细选择第一个编排的基类。注意到这里每个类定义都调用了 determine_primary_base 。如果我们为一个复杂类来调用它,其基类应该已经被这个函数处理过了。

 

1267   static void

1268   determine_primary_base (tree t)                                                                in class.c

1269   {

1270     int i, n_baseclasses = CLASSTYPE_N_BASECLASSES (t);

1271     tree vbases;

1272     tree type_binfo;

1273  

1274     /* If there are no baseclasses, there is certainly no primary base.  */

1275     if (n_baseclasses == 0)

1276        return ;

1277  

1278     type_binfo = TYPE_BINFO (t);

1279  

1280     for (i = 0; i < n_baseclasses; i++)

1281     {

1282       tree base_binfo = BINFO_BASETYPE (type_binfo, i);

1283       tree basetype = BINFO_TYPE (base_binfo);

1284  

1285       if (TYPE_CONTAINS_VPTR_P (basetype))

1286       {

1287         /* We prefer a non-virtual base, although a virtual one will

1288           do.  */

1289         if (TREE_VIA_VIRTUAL (base_binfo))

1290           continue ;

1291  

1292         if (!CLASSTYPE_HAS_PRIMARY_BASE_P (t))

1293         {

1294           set_primary_base (t, base_binfo);

1295           CLASSTYPE_VFIELDS (t) = copy_list (CLASSTYPE_VFIELDS (basetype));

1296         }

1297         else

1298         {

1299           tree vfields;

1300  

1301           /* Only add unique vfields, and flatten them out as we go.  */

1302           for (vfields = CLASSTYPE_VFIELDS (basetype);

1303               vfields;

1304               vfields = TREE_CHAIN (vfields))

1305             if (VF_BINFO_VALUE (vfields) == NULL_TREE

1306                || ! TREE_VIA_VIRTUAL (VF_BINFO_VALUE (vfields)))

1307               CLASSTYPE_VFIELDS (t)

1308                  = tree_cons (base_binfo,

1309                             VF_BASETYPE_VALUE (vfields),

1310                             CLASSTYPE_VFIELDS (t));

1311         }

1312       }

1313     }

 

含有 vtable 的非虚拟基类是最好的选择。碰到的第一个这样的基类被下面的 set_primary_base 与派生类绑定起来。注意 TYPE_VFIELD 是由编译器产生的指向 vtable 的域。

 

1253   static void

1254   set_primary_base (tree t, tree binfo)                                                           in class.c

1255   {

1256     tree basetype;

1257  

1258     CLASSTYPE_PRIMARY_BINFO (t) = binfo;

1259     basetype = BINFO_TYPE (binfo);

1260     TYPE_BINFO_VTABLE (t) = TYPE_BINFO_VTABLE (basetype);

1261     TYPE_BINFO_VIRTUALS (t) = TYPE_BINFO_VIRTUALS (basetype);

1262     TYPE_VFIELD (t) = TYPE_VFIELD (basetype);

1263   }

 

上面的域 CLASSTYPE_VFIELDS 是这个类型所持有的 vtable 链表(主要及次要 vtable )。其节点中的 TREE_VALUE 是引入这个 vtable 的类型。对于引入该 vtable ,或从其主要基类继承该 vtable 的类,节点的 TREE_PURPOSE 则是 NULL 。对于其它 vtable (来自次要基类),其节点的 TREE_PURPOSE 是该 vtable 所来自的基类的 BINFO

注意派生类的 CLASSTYPE_VFIELDS 开始是空的;而且虚拟基类,除非它几乎是空的(这时它不可能有 vtable ),是不能作为派生类的主要基类(就算它是唯一有 vtable 的基类)。因此上面对 vtable 的拷贝能确保只对非虚拟基类进行。并且注意如果是主要基类,派生类直接拷贝其 CLASSTYPE_VFIELDS (用于这个主要基类 vtable 的节点的 PURPOSE 域为 NULL )。这是因为主要基类就在派生类的头部,如果其有 vtable ,这个 vtable 的地址也就是派生类 vtable 所在地址,派生类将直接把这个 vtable 据为己有。

 

determine_primary_base (continue)

 

1315     if (!TYPE_VFIELD (t))

1316       CLASSTYPE_PRIMARY_BINFO (t) = NULL_TREE;

1317  

1318     /* Find the indirect primary bases - those virtual bases which are primary

1319       bases of something else in this hierarchy.  */

1320     for (vbases = CLASSTYPE_VBASECLASSES (t);

1321         vbases;

1322         vbases = TREE_CHAIN (vbases))

1323     {

1324       tree vbase_binfo = TREE_VALUE (vbases);

1325  

1326       /* See if this virtual base is an indirect primary base. To be so,

1327         it must be a primary base within the hierarchy of one of our

1328         direct bases.  */

1329       for (i = 0; i < n_baseclasses; ++i)

1330       {

1331         tree basetype = TYPE_BINFO_BASETYPE (t, i);

1332         tree v;

1333  

1334         for (v = CLASSTYPE_VBASECLASSES (basetype);

1335             v;

1336             v = TREE_CHAIN (v))

1337         {

1338           tree base_vbase = TREE_VALUE (v);

1339             

1340           if (BINFO_PRIMARY_P (base_vbase)

1341              && same_type_p (BINFO_TYPE (base_vbase),

1342                             BINFO_TYPE (vbase_binfo)))

1343           {

1344             BINFO_INDIRECT_PRIMARY_P (vbase_binfo) = 1;

1345             break ;

1346           }

1347         }

1348  

1349         /* If we've discovered that this virtual base is an indirect

1350            primary base, then we can move on to the next virtual

1351            base.  */

1352         if (BINFO_INDIRECT_PRIMARY_P (vbase_binfo))

1353           break ;

1354       }

1355     }

1356  

1357     /* A "nearly-empty" virtual base class can be the primary base

1358       class, if no non-virtual polymorphic base can be found.  */

1359     if (!CLASSTYPE_HAS_PRIMARY_BASE_P (t))

1360     {

1361       /* If not NULL, this is the best primary base candidate we have

1362         found so far.  */

1363       tree candidate = NULL_TREE;

1364       tree base_binfo;

1365  

1366       /* Loop over the baseclasses.  */

1367       for (base_binfo = TYPE_BINFO (t);

1368           base_binfo;

1369           base_binfo = TREE_CHAIN (base_binfo))

1370       {

1371         tree basetype = BINFO_TYPE (base_binfo);

1372  

1373         if (TREE_VIA_VIRTUAL (base_binfo)

1374            && CLASSTYPE_NEARLY_EMPTY_P (basetype))

1375         {

1376            /* If this is not an indirect primary base, then it's

1377             definitely our primary base.  */

1378           if (!BINFO_INDIRECT_PRIMARY_P (base_binfo))

1379           {

1380             candidate = base_binfo;

1381             break ;

1382           }

1383  

1384           /* If this is an indirect primary base, it still could be

1385             our primary base -- unless we later find there's another

1386              nearly-empty virtual base that isn't an indirect

1387              primary base.  */

1388           if (!candidate)

1389             candidate = base_binfo;

1390         }

1391       }

1392  

1393       /* If we've got a primary base, use it.  */

1394       if (candidate)

1395       {

1396         set_primary_base (t, candidate);

1397         CLASSTYPE_VFIELDS (t)

1398            = copy_list (CLASSTYPE_VFIELDS (BINFO_TYPE (candidate)));

1399       }  

1400     }

1401  

1402     /* Mark the primary base classes at this point.  */

1403     mark_primary_bases (t);

1404   }

 

如果没有找到含有 vtable 的非虚拟基类,派生类的 TYPE_VFIELD 保持为 NULL (在 set_primary base 中,它会被设置为主要基类的 TYPE_VFIELD ),这时不管以前把 CLASSTYPE_PRIMARY_BINFO 设置成什么,把它置为 NULL 。接着看看能不能在虚拟基类中找到可以作为主要基类的。能作为主要基类的虚拟基类,除了需要是几乎空的之外,没有被用作其基类的主要基类是优先考虑的,否则就将就使用在派生类声明中最后出现的虚拟基类(这样也减少了冲突的机会)。

严格来说,在一个派生类中只有一个主要基类。例如:

class A {…}; class B: public A {…}; class C: public B {…};

此中类 B 的主要基类是 A 。而在类 C 中,主要基类是 B ,不再是 A 。但是 C 中关于 B binfo 是从类 B 中拷贝过来的,拷贝时没有拷入关于主要基类的信息。不过虚拟基类则不然,下面的函数还会对它进行“拨乱反正”。

 

1218   static void

1219   mark_primary_bases (tree type)                                                                in class.c

1220   {

1221     tree binfo;

1222    

1223     /* Walk the bases in inheritance graph order.  */

1224     for (binfo = TYPE_BINFO (type); binfo; binfo = TREE_CHAIN (binfo))

1225     {

1226       tree base_binfo = get_primary_binfo (binfo);

1227  

1228       if (!base_binfo)

1229         /* Not a dynamic base.  */ ;

1230       else if (BINFO_PRIMARY_P (base_binfo))

1231         BINFO_LOST_PRIMARY_P (binfo) = 1;

1232       else

1233       {

1234         BINFO_PRIMARY_BASE_OF (base_binfo) = binfo;

1235         /* A virtual binfo might have been copied from within

1236            another hierarchy. As we're about to use it as a primary

1237           base, make sure the offsets match.  */

1238         if (TREE_VIA_VIRTUAL (base_binfo))

1239         {

1240           tree delta = size_diffop (convert (ssizetype,

1241                                      BINFO_OFFSET (binfo)),

1242                               convert (ssizetype,

1243                                      BINFO_OFFSET (base_binfo)));

1244         

1245            propagate_binfo_offsets (base_binfo, delta);

1246         }

1247       }

1248     }

1249   }

 

1226 行的 get_primary_binfo 返回参数 binfo 所对应类型的主要基类的 binfo 的拷贝,如果设置了主要基类的话;否则返回 NULL

 

6470   tree

6471   get_primary_binfo (tree binfo)                                                                   in class.c

6472   {

6473     tree primary_base;

6474     tree result;

6475    

6476     primary_base = CLASSTYPE_PRIMARY_BINFO (BINFO_TYPE (binfo));

6477     if (!primary_base)

6478       return NULL_TREE;

6479  

6480     result = copied_binfo (primary_base, binfo);

6481     return result;

6482   }

 

copied_binfo 根据给定的 primary_base ,从 binfo 中找出对应的部分。考虑以下例子:

class A { virtual … };

class B: public A { virtual … };

class C: public A { virtual … };

class D: public B, C { … };

首先,在 mark_primary_bases 1224 行, TYPE_BINFO(D) 得到一个链表,其中依次是 D à B à A à C à A 。显然, CLASSTYPE_PRIMARY_BINFO(D) 返回 B binfo 拷贝(它由 set_primary_base 设置);然后在下面的 2578 行,递归进入 copied_binfo 并得到 D binfo ,接着在 2581 行的 FOR 循环中,在 D 的基类 B C 中查找,找出目标 B 。因为该 B binfo 拷贝来自类 B 1230 行的 BINFO_PRIMARY_P (base_binfo) 返回 NULL ,这表示 B 不是主要基类(但现在是!)。因此在 1234 行,我们把它设为 D 的主要基类(注意与 CLASSTYPE_PRIMARY_BINFO 的区别)。

那么对于 B binfo A 是其主要基类。在 D 中查找 A binfo 拷贝与查找 B binfo 拷贝类似,除了在 D A 同时出现在 B C 中。不过查找的过程能保证找到的是 B à A 。因为 A 是类 B 的主要基类,但在 A 拷贝的 binfo 中,并没有设置 BINFO_PRIMARY_P ,它满足 1232 行条件,被设置为 B 的拷贝 binfo 的主要基类。

同样的我们可以得到 D C 部分的 A binfo 拷贝,并且它也没有设置 BINFO_PRIMARY_P

 

2556   tree

2557   copied_binfo (tree binfo, tree here)                                                           in search.c

2558   {

2559     tree result = NULL_TREE;

2560    

2561     if (TREE_VIA_VIRTUAL (binfo))

2562     {

2563       tree t;

2564  

2565       for (t = here; BINFO_INHERITANCE_CHAIN (t);

2566           t = BINFO_INHERITANCE_CHAIN (t))

2567         continue ;

2568        

2569       result = purpose_member (BINFO_TYPE (binfo),

2570                             CLASSTYPE_VBASECLASSES (BINFO_TYPE (t)));

2571       result = TREE_VALUE (result);

2572     }

2573     else if (BINFO_INHERITANCE_CHAIN (binfo))

2574     {

2575       tree base_binfos;

2576        int ix, n;

2577        

2578       base_binfos = copied_binfo (BINFO_INHERITANCE_CHAIN (binfo), here);

2579       base_binfos = BINFO_BASETYPES (base_binfos);

2580       n = TREE_VEC_LENGTH (base_binfos);

2581       for (ix = 0; ix != n; ix++)

2582       {

2583         tree base = TREE_VEC_ELT (base_binfos, ix);

2584         

2585         if (BINFO_TYPE (base) == BINFO_TYPE (binfo))

2586         {

2587           result = base;

2588           break ;

2589         }

2590       }

2591     }

2592     else

2593     {

2594       my_friendly_assert (BINFO_TYPE (here) == BINFO_TYPE (binfo), 20030202);

2595       result = here;

2596     }

2597  

2598     my_friendly_assert (result, 20030202);

2599     return result;

2600   }

 

进一步考虑如下的例子:

class A { virtual …};

class B: virtual public A { … };

class C: virtual public A { … };

class D: public B, C { … };

TYPE_BINFO(D) 链表中,依次是 D à B à A à C ,因为 A 作为虚拟基类在 D 中只能维持一个实例。这个过程是几乎相同的,除了查找虚拟基类的 binfo 拷贝时,是从继承树顶开始向下,而不是从基类开始往上爬。另外,由于虚拟基类 A 不使用拷贝 binfo ,因此在 mark_primary_bases 中,它满足 1230 行条件。

copy_base_binfos 中,连同 binfo 拷贝的还有偏移量信息。不过这个偏移量是关于被拷贝的 binfo 。而作为主要基类,虚拟基类(也包括非虚拟基类)必须被放在布局的最前面(事实上紧跟着 vtable )。这个调整由 propagate_binfo_offsets 来完成(对于非虚拟的基类 binfo ,它的偏移量一直是 0 ,并且在当前类中编排该基类时,其非虚拟基类也维持着不变的相对偏移量)。

 

4355   static void

4356   propagate_binfo_offsets (tree binfo, tree offset)                                         in class.c

4357   {

4358     int i;

4359     tree primary_binfo;

4360  

4361     /* Update BINFO's offset.  */

4362     BINFO_OFFSET (binfo)

4363       = convert (sizetype,

4364                size_binop (PLUS_EXPR,

4365                          convert (ssizetype, BINFO_OFFSET (binfo)),

4366                          offset));

4367  

4368     /* Find the primary base class.  */

4369     primary_binfo = get_primary_binfo (binfo);

4370  

4371     /* Scan all of the bases, pushing the BINFO_OFFSET adjust

4372       downwards.  */

4373     for (i = -1; i < BINFO_N_BASETYPES (binfo); ++i)

4374     {

4375       tree base_binfo;

4376  

4377       /* On the first time through the loop, do the primary base.

4378         Because the primary base need not be an immediate base, we

4379         must handle the primary base specially.  */

4380       if (i == -1)

4381       {

4382         if (!primary_binfo)

4383           continue ;

4384  

4385         base_binfo = primary_binfo;

4386       }

4387       else

4388       {

4389         base_binfo = BINFO_BASETYPE (binfo, i);

4390         /* Don't do the primary base twice.  */

4391         if (base_binfo == primary_binfo)

4392           continue ;

4393       }

4394  

4395       /* Skip virtual bases that aren't our canonical primary base.  */

4396       if (TREE_VIA_VIRTUAL (base_binfo)

4397          && BINFO_PRIMARY_BASE_OF (base_binfo) != binfo)

4398         continue ;

4399  

4400       propagate_binfo_offsets (base_binfo, offset);

4401     }

4402   }

 

函数 propagate_binfo_offsets 把偏移量的调整应用到整个基类。记得 BINFO_OFFSET 表示该基类在其派生类中的偏移量。前面看到主要基类不一定是直接基类,因此 4373 行的 FOR 循环需要多一次计数来包括可能不是直接基类的主要基类。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值