• C++之内部类(嵌套类)与外部类及友元


    先上代码:

     1 class Outer
     2 {
     3 public:
     4     Outer(){m_outerInt=0;}
     5 private:
     6     int m_outerInt;
     7 public:
     8     //内部类定义开始
     9     class Inner
    10     {
    11     public:
    12         Inner(){m_innerInt=1;}
    13     private:
    14         int m_innerInt;
    15     public:
    16         void DisplayIn(){cout<<m_innerInt<<endl;}
    17     } ;
    18     //End内部类
    19 public:
    20     void DisplayOut(){cout<<m_outerInt<<endl;}
    21 };
    22 
    23 int main()
    24 {
    25     Outer out;
    26     Outer::Inner in;
    27     out.DisplayOut();
    28     in.DisplayIn();
    29 
    30     return 0;
    31 }

    如上面代码所示,这种情况下,外部类与内部类其实联系并不大,外部类无非仅仅限定了内部类类名的作用域范围,完全可以加上Outer限定之后像使用任何其他类一样来使用内部类,Outer于Inner而言仅仅是一种命名空间。

    提问:上面代码中,内部类(Inner)成员函数(比如DisplayIn)如何访问外部类(Outer)数据成员呢?

    答:问这个问题之前,先要明白一个事实:将来你是在一个Inner实例对象上调用Inner的成员函数的,而所谓的“访问外部类数据成员”这种说法是不合理的,“外部类”及任何类,只是代码而已,是一种说明,从内存的角度来讲,程序运行起来之后,代码存储在代码区,所以应该问“如何访问外部类实例的数据成员”,如此,你得先有一个外部类实例(或者实例的指针),然后才能谈访问。

    退一步讲,如果你不管三七二十一,直接在Inner的DisplayIn方法里加上这样一行:

    1 m_outerInt=10;

    然后你编译、链接也都通过了(事实上这是不可能的),那么,在main函数中:

    1 int main()
    2 {
    3     Outer::Inner in;
    4     in.DisplayIn();
    5 
    6     return 0;
    7 }

    如果这样你都能正常运行,天理何在?DisplayIn中的m_outerInt到底是哪个实例的数据?

    所以,为了避免这样荒唐的事情发生,语法层面就已经使得上述不可能发生:连编译都不会通过。

    提问:把上面代码中的Inner设置为Outer的友元类之后,能解决问题吗?


    答:该提问者都不仅犯了第一个提问者的错误,还误解了友元的含义。

    友元举例:

     1 class Inner;
     2 
     3 class Outer
     4 {
     5 public:
     6     Outer(){m_outerInt=0;}
     7 private:
     8     int m_outerInt;
     9 public:
    10     /*//内部类定义开始
    11     class Inner
    12     {
    13     public:
    14         Inner(){m_innerInt=1;}
    15     private:
    16         int m_innerInt;
    17     public:
    18         void DisplayIn(){cout<<m_innerInt<<endl;}
    19     } ;
    20     //End内部类*/
    21 public:
    22     void DisplayOut(){cout<<m_outerInt<<endl;}
    23     friend Inner;
    24 };
    25 class Inner
    26 {
    27 public:
    28     Inner(){m_innerInt=1;}
    29 private:
    30     int m_innerInt;
    31 public:
    32     void DisplayIn(){cout<<m_innerInt<<endl;}
    33     //友元影响的函数
    34     void TestFriend(Outer out)
    35     {
    36         cout<<"Good Friend:"<<out.m_outerInt<<endl;
    37     }
    38 } ;
    39 
    40 int main()
    41 {
    42     Outer out;
    43     out.DisplayOut();
    44     Inner in;
    45     in.DisplayIn();
    46     in.TestFriend(out);
    47     return 0;
    48 }

    内部类如果想达到友元访问效果(直接通过实例或者实例指针来访问实例的非公有成员),是不需要另外再声明为friend的,原因不言自明:都已经是自己人了。

    提问:内部类实例(作为外部类的数据成员)如何访问外部类实例的成员呢?

    见如下代码:

     1 #include <iostream>
     2 #define METHOD_PROLOGUE(theClass, localClass) 
     3     theClass* pThis = ((theClass*)((char*)(this) - 
     4     offsetof(theClass, m_x##localClass))); 
     5 
     6 using namespace std;
     7 
     8 class Outer
     9 {
    10 public:
    11     Outer(){m_outerInt=0;}
    12 private:
    13     int m_outerInt;
    14 public:
    15     //内部类定义开始
    16     class Inner
    17     {
    18     public:
    19         Inner(){m_innerInt=1;}
    20     private:
    21         int m_innerInt;
    22     public:
    23         void DisplayIn(){cout<<m_innerInt<<endl;}
    24         // 在此函数中访问外部类实例数据
    25         void setOut()
    26         {
    27             METHOD_PROLOGUE(Outer,Inner);
    28             pThis->m_outerInt=10;
    29         }
    30     } m_xInner;
    31     //End内部类
    32 public:
    33     void DisplayOut(){cout<<m_outerInt<<endl;}
    34 };
    35 
    36 int main()
    37 {
    38     Outer out;
    39     out.DisplayOut();
    40     out.m_xInner.setOut();
    41     out.DisplayOut();
    42     return 0;
    43 }

    看main函数:程序执行完main函数第一句后,内存中便有了一个数据块,它存储着out的数据,而m_xInner也在数据块中,当然,&out和this指针(外部类)都指向该内存块的起始位置,而内部类代码中的this指针当然就指向m_xInner的起始内存了,offsetof(theClass, m_x##localClass)获得的便是m_xInner在该内存块中与该内存块起始地址(这正是out的地址)的距离(偏移),即内部类this-外部类this的差值(以字节为单位)这样,用内部类this减去其自身的偏移,便可得到pThis。有了out的地址,基本上可以对其为所欲为了,至于为何要有char*强转,可以go to definition of offsetof,可以看到其实现中有个关于char的转换。

     
  • 相关阅读:
    Kafka Shell基本命令(包括topic的增删改查)
    thefuck的安装和使用
    Linux运维利器之ClusterShell
    MySQL数据库的10大经典错误案例
    Mysql 常用操作
    Git 忽略特定文件或文件夹
    为什么需要拷贝构造函数
    C语言编译过程
    设计模式之建造者模式
    设计模式之工厂/抽象工厂模式
  • 原文地址:https://www.cnblogs.com/fnlingnzb-learner/p/6548113.html
Copyright © 2020-2023  润新知