zoukankan      html  css  js  c++  java
  • 嵌套类 局部类

    嵌套类 局部类

    《c++ primer 3th》

    C++嵌套类

    1、   嵌套类的名字只在外围类可见。

    2、   类的私有成员只有类的成员和友元可以访问,因此外围类不可以访问嵌套类的私有成员。嵌套类可以访问外围类的成员(通过对象、指针或者引用)。

    3、   一个好的嵌套类设计:嵌套类应该设成私有。嵌套类的成员和方法可以设为 public 。

    4、   嵌套类可以直接访问外围类的静态成员、类型名( typedef )、枚举值。

    嵌套类

    一个类可以在另一个类中定义,这样的类被称为嵌套类嵌套类是其外围类的一个成员。嵌套类的定义可以出现在其外围类的公有、私有或保护区中。

    嵌套类的名字在其外围类域中是可见的,但是在其他类域或名字空间中是不可见的,这意味着,嵌套类的名字不会与外围域中声明的相同名字冲突。例如:

    class Node { /* ... */ };

    class Tree {

    public:

    // Node 被封装在 Tree 的域中

    // 在类域中 Tree::Node 隐藏了 ::Node

    class Node {...};

     

    // ok: 被解析为嵌套类: Tree::Node

    Node *tree;

    };

     

    // Tree::Node 在全局域中不可见

    // Node 被解析为全局的 Node 声明

    Node *pnode;

    class List {

    public:

    // Node 被封装在 List 的域中

    // 在类域 List::Node 中隐藏了 ::Node

    class Node {...};

     

    // ok: 解析为: List::Node

    Node *list;

    };

    与非嵌套类一样,嵌套类可以有与自身同样类型的成员:

    // Not ideal configuration: evolving class definition

    class List {

    public:

    class ListItem {

    friend class List; // 友元声明

    ListItem( int val = 0 ); // 构造函数

    ListItem *next; // 指向自己类的指针

    int value;

    };

    // ...

    private:

    ListItem *list;

    ListItem *at_end;

    };

    私有成员是指这样的成员,它只能在该类的成员或友元定义中被访问。除非外围类被声明为嵌套类的友元,否则它没有权利访问嵌套类的私有成员。这就是为什么ListItem 把List声明为友元的原因:为了允许类List的成员定义访问ListItem 的私有成员。嵌套类也没有任何特权访问其外围类的私有成员。如果我们想授权ListItem 允许它访问类List 的私有成员,那么该外围类List 必须把嵌套类ListItem 声明为友元。在前面的例子中ListItem 不是List 的友元,所以它不能引用List 的私有成员。

    把类ListItem 声明为List类的公有成员意味着,该嵌套类可以在整个程序中(在类List的友元和成员定义之外)用作类型。例如:

    // ok: 全局域中的声明

    List::ListItem *headptr;

    这超出了我们的本意。嵌套类ListItem 支持List类的抽象,我们不希望让ListItem 类型在整个程序中都可以被访问。那么,比较好的设计是把ListItem 嵌套类定义为类List 的私有成员:

    // 不理想的配置: 要改进的类定义

    class List {

    public:

    // ...

    private:

    class ListItem {

    // ...

    };

    ListItem *list;

    ListItem *at_end;

    };

    现在,只有List 的成员和友元的定义可以访问类型ListItem 。把类ListItem 的所有成员都声明为公有的也不再有任何坏处。因为ListItem 类是List 的私有成员,所以只有List 类的友元和成员可以访问ListItem 的成员。有了这个新的设计,我们就不再需要友元声明了。下面是类List 的新定义:

    // 较好的设计!

    class List {

    public:

    // ...

    private:

    // 现在 ListItem 是一个私有的嵌套类型

    class ListItem {

    // 它的成员都是公有的

    public:

    ListItem( int val = 0 );

    ListItem *next;

    int value;

    };

    ListItem *list;

    ListItem *at_end;

    };

    在类ListItem 的定义中没有把构造函数定义为inline(内联的),构造的数必须在类定义之外被定义。在哪儿可以定义它呢?ListItem 的构造函数不是类List 的成员,所以我们不能在类List 的体内定义。ListItem 的构造函数必须被定义在全局域中——该域含有其外围类的定义。当我们没有在嵌套类体内以inline 形式定义嵌套类的成员函数时,我们就必须在最外围的类之外定义这些成员函数。下面是ListItem 构造函数的一种可能的定义。但是,对于全局域定义的语法来说这是不正确的:

    class List {

    public:

    // ...

    private:

    class ListItem {

    public:

    ListItem( int val = 0 );

    // ...

    };

    };

    // 错误: ListItem 不在全局域中

    ListItem::ListItem( int val ) { ... }

    问题在于,名字ListItem 在全局域中是不可见的。在全局域中使用ListItem必须指明ListItem是类List 中嵌套的类。可以通过用其外围类名List 限定修饰类名ListItem 来做到这一点。下面是正确的语法:

    // 用外围类名限定修饰嵌套类名

    List::ListItem::ListItem( int val ) {

    value = val;

    next = 0;

    }

    注意,只有嵌套类名是限定修饰的。第一个限定修饰符List::指外围类,它限定修饰其后的名字——嵌套类ListItem。第二个ListItem 是指构造函数而不是嵌套类。下列定义中的成员名字是不正确的:

    // 错误: 构造函数名是 ListItem 而不是 List::ListItem

    List::ListItem::List::ListItem( int val ) {

    value = val;

    next = 0;

    }

    如果ListItem 已经声明了一个静态成员,那么它的定义也要放在全局域中。在这样的定义中,静态成员名看起来如下所示:

    int List::ListItem::static_mem = 1024;

    注意,对于成员函数和静态数据成员而言,不一定只有嵌套类的公有成员,才能在类定义之外被定义。类ListItem 的私有成员也可以被定义在全局域中。

    嵌套类也可以被定义在其外围类之外。例如,Lisiltem 的定义也可以在全局域中被给出,如下:

    class List {

    public:

    // ...

    private:

    // 这个声明是必需的

    class ListItem;

    ListItem *list;

    ListItem *at_end;

    };

    // 用外围类名限定修饰嵌套类名

    class List::ListItem {

    public:

    ListItem( int val = 0 );

    ListItem *next;

    int value;

    };

    在全局定义中,嵌套类ListItem 的名字必须由其外围类List 的名字限定修饰。注意,在类List体内的ListItem 的声明不能省略。如果嵌套类没有先被声明为其外围类的一个成员,则全局域中的定义不能被指定为嵌套类。在全局域中定义的嵌套类不一定是其外围类的公有成员。

    在嵌套类的定义被看到之前,我们只能声明嵌套类的指针和引用。即使类ListItem 是在全局域中被定义的,List 的数据成员list 和at_end 仍然是有效的,因为这两个成员都是指针。如果这两个成员中有一个是对象而不是指针,那么类List 的成员声明将会引发一个编译错误。例如:

    class List {

    public:

    // ...

    private:

    // 这个声明是必需的

    class ListItem;

    ListItem *list;

    ListItem at_end; // 错误: 未定义嵌套类 ListItem

    };

    为什么会希望在类定义之外定义嵌套类呢?或许嵌套类支持外围类的实现细节,我们不想让List类的用户看到ListItem 的细节。因此,我们不愿把嵌套类的定义放在含有List 类接口的头文件中。于是,我们只能在含有List 类及其成员实现的文本文件中给出嵌套类ListItem的定义。

    嵌套类可以先被声明,然后再在外围类体中被定义。这允许多个嵌套类具有互相引用的成员,例如:

    class List {

    public:

    // ...

    private:

    // List::ListItem 的声明

    class ListItem;

    class Ref {

    ListItem *pli; // pli 类型为: List::ListItem*

    };

    // List::ListItem 的定义

    class ListItem {

    Ref *pref; // pref 的类型为: List::Ref*

    };

    };

    如果类ListItem 没有在类Ref 之前先被声明,那么成员pli 的声明就是错的,因为名字ListItem 没有被声明。

    嵌套类不能直接访问其外围类的非静态成员,即使这些成员是公有的,任何对外围类的非静态成员的访问都要求通过外围类的指针、引用或对象来完成。例如:

    class List {

    public:

    int init( int );

    private:

    class ListItem {

    public:

    ListItem( int val = 0 );

    void mf( const List & );

    int value;

    int memb;

    };

    };

    List::ListItem::ListItem( int val )

    {

    // List::init() 是类 List 的非静态成员

    // 必须通过 List 类型的对象或指针来使用

    value = init( val ); // 错误: 非法使用 init

    }

    使用类的非静态成员时,编译器必须能够识别出非静态成员属于哪个对象。在类ListItem的成员函数中,this 指针只能被隐式地应用在类ListItem 的成员上,而不是外围类的成员上。由于隐式的this 指针,我们知道数据成员value 指向被凋用构造函数的对象。在ListItem 的构造函数中的this 指针的类型是ListItem*。 而要访问成员init()所需的是List 类型的对象或List*类型的指针。

    下面是成员函数mf()通用引用参数引用init()。从这里我们能够知道,成员init()是针对函数实参指定的对象而被调用的:

    void List::ListItem::mf( const List &il ) {

    memb = il.init(); // ok: 通过引用调用 init()

    }

    尽管访问外围类的非静态数据成员需要通过对象、指针或引用才能完成,但是嵌套类可以直接访问外围类的静态成员、类型名、枚举值(假定这些成员是公有的)。类型名是一个typedef 名字、枚举类型名、或是一个类名。例如:

    class List {

    public:

    typedef int (*pFunc)();

    enum ListStatus { Good, Empty, Corrupted };

    // ...

    private:

    class ListItem {

    public:

    void check_status();

    ListStatus status; // ok

    pFunc action; // ok

    // ...

    };

    // ...

    };

    pFunc、ListStatus 和ListItem 都是外围类List的域内部的嵌套类型名。这三个名字以及ListStatus 的枚举值都可以被用在ListItem 的域中,这些成员可以不加限定修饰地被引用:

    void List::ListItem::check_status()

    {

    ListStatus s = status;

    switch ( s ) {

    case Empty: ...

    case Corrupted: ...

    case Good: ...

    }

    }

    在ListItem 的域之外,以及在外围类List 域之外引用外围类的静态成员、类型名和枚举名都要求域解析操作符,例如:

    List::pFunc myAction; // ok

    List::ListStatus stat = List::Empty; // ok

    当引用一个枚举值时,我们不能写:

    List::ListStatus::Empty

    这是因为枚举值可以在定义枚举的域内被直接访问。为什么?因为枚举定义并不像类定义一样维护了自己相关的域。

     

    在嵌套类域中的名字解析

    让我们来看看在嵌套类的定义,及其成员定义中的名字解析是怎样进行的。被用在嵌套类的定义中的名字(除了inline 成员函数定义中的名字和缺省实参的名字之外)其解析过程如下:

    1、考虑出现在名字使用点之前的嵌套类的成员声明。

    2、如果第1 步没有成功,则考虑出现在名字使用点之前的外围类的成员声明。

    3、如果第2 步没有成功,则考虑出现在嵌套类定义之前的名字空间域中的声明。

    例如:

    enum ListStatus { Good, Empty, Corrupted };

    class List {

    public:

    // ...

    private:

    class ListItem {

    public:

    // 查找:

    // 1) 在 List::ListItem 中

    // 2) 在 List 中

    // 3) 在全局域中

    ListStatus status; // 引用全局枚举

    // ...

    };

    // ...

    };

    编译器首先在类ListItem 的域中查找ListStatus 的声明。因为没有找到成员声明,所以编译器接着在类List 的域中查找ListStatus 的声明。因为在List 类中也没有找到声明。于是编译器在全局域中查找ListStatus 的声明。在这三个域中,只有位于ListStatus使用点之前的声明才会被编译器考虑。编译器找到了全局枚举ListStatus 的声明,它是被用在Status 声明中的类型。

    如果在全局域中,在外围域List 之外定义嵌套类ListItem,则List类的所有成员都已经被声明完毕,因而编译器将考虑其所有声明:

    class List {

    private:

    class ListItem;

    // ...

    public:

    enum ListStatus { Good, Empty, Corrupted };

    // ...

    };

    class List::ListItem {

    public:

    // 查找:

    // 1) 在 List::ListItem 中

    // 2) 在 List 中

    // 3) 在全局域中

    ListStatus status; // List::ListStatus

    // ...

    };

    ListItem的名字解析过程首先在类ListItem 的域中开始查找。因为没有找到成员声明,所以编译器在类List 的域内查找ListStatus 的声明。因为类List 的完整定义都已经能够看得到,所以这一步查找考虑List 的所有成员。于是找到List 中嵌套的enumListStatus, 尽管它是在ListItem 之后被声明的。status 是List 的ListStatus 类型的一个枚举对象。如果List没有名为ListStatus 的成员,则名字查找过程会在全局域中。在嵌套类ListItem 定义之前查找声明。

    被用在嵌套类的成员函数定义中的名字其解析过程如下:

    1、首先考虑在成员函数局部域中的声明。

    2、如果第1 步没有成功,则考虑所有嵌套类成员的声明。

    3、如果第2 步没有成功,则考虑所有外围类成员的声明。

    4、如果第3 步没有成功,则考虑在成员函数定义之前的名字空间域中出现的声明。

    在下面的代码段中,成员函数check_status()定义中的list 引用了哪个声明?

    class List {

    public:

    enum ListStatus { Good, Empty, Corrupted };

    // ...

    private:

    class ListItem {

    public:

    void check_status();

    ListStatus status; // ok

    // ...

    };

    ListItem *list;

    // ...

    };

    int list = 0;

    void List::ListItem::check_status()

    {

    int value = list; // 哪个 list?

    }

    很有可能程序员想让check_status()中的List 引用全局对象:

    1、value 和全局对象List 的类型都是int。List::list 成员是指针类型,在没有显式转换的情况它不能被赋给value。

    2、不允许ListItem 访问其外围类的私有数据成员,如List。

    3、list 是一个非静态数据成员,在ListItem 的成员函数中必须通过对象、指针或引用来访问它。

    但是,尽管有这些原因,在成员check_status()中用到的名字List 仍被解析为类List 的数据成员list。记住,如果在嵌套类ListItem 的域中没有找到该名字;则在查找全局域之前,下一个要查找的是其外围类的域。外围类List 的成员list 隐藏了全局域中的对象。于是产生一个错误消息,因为在check_status()中使用指针list 是无效的。

    只有在名字解析成功之后,编译器才会检查访问许可和类型兼容性。如果名字的用法本身就是错误的,则名字解析过程将不会再去查找更适合于该名字用法的声明,而是产生一个错误消息。

    为了访问全局对象list, 必须使用全局域解析操作符:

    void List::ListItem:: check_status() {

    value = ::list; // ok

    }

    如果成员函数check_status()被定义成位于ListItem 类体中的内联函数,则上面所讲到的最后一步修改会使编译器产生一个错误消息,报告说全局域中的list 没有被声明。

    class List {

    public:

    // ...

    private:

    class ListItem {

    public:

    // 错误: 没有可见的 ::list 声明

    void check_status() { int value = ::list; }

    // ...

    };

    ListItem *list;

    // ...

    };

    int list = 0;

    全局对象list 是在类List 定义之后被声明的,对于在类体中内联定义的成员函数,只考虑在外围类定义之前可见的全局声明。如果check_status()的定义出现在List 的定义之后,则编译器考虑在check_status()定义之前可见的全局声明,于是找到对象list 的全局声明。

    局部类

    类也可以定义在函数体内,这样的类被称为局部类局部类只在定义它的局部域内可见。与嵌套类不同的是,在定义该类的局部域外没有语法能够引用局部类的成员。因此,局部类的成员函数必须被定义在类定义中。在实际中,这就把局部类的成员函数的复杂性限制在几行代码中。否则,对读者来说,代码将变得很难理解。

    因为没有语法能够在名字空间域内定义局部类的成员,所以也不允许局部类声明静态数据成员。

    在局部类中嵌套的类可以在其类定义之外被定义。但是,该定义必须出现在包含外围局部类定义的局部域内。在局部域定义中的嵌套类的名字必须由其外围类名限定修饰。在外围类中,该嵌套类的声明不能被省略,例如:

    void foo( int val )

    {

    class Bar {

    public:

    int barVal;

    class nested; // 嵌套类的声明是必需的

    };

    // 嵌套类定义

    class Bar::nested {

    // ...

    };

    }

    外围函数没有特权访问局部类的私有成员。当然,这可以通过使外围函数成为局部类的友元来实现。但是,看起来,局部类几乎从不需要私有成员。能够访问局部类的程序部分只有很少的一部分。局部类被封装在它的局部域中,通过信息隐藏进一步封装好像有点太过了。在实际中,很难找到一个理由不把局部类的所有成员都声明为公有的。

    同嵌套类一样,局部类可以访问的外围域中的名字也是有限的。局部类只能访问在外围局部域中定义的类型名、静态变量以及枚举值,例如:

    int a, val;

    void foo( int val )

    {

    static int si;

    enum Loc { a = 1024, b };

    class Bar {

    public:

    Loc locVal; // ok;

    int barVal;

    void fooBar( Loc l = a ) { // ok: Loc::a

    barVal = val; // 错误: 局部对象

    barVal = ::val; // OK: 全局对象

    barVal = si; // ok: 静态局部对象

    locVal = b; // ok: 枚举值

    }

    };

    // ...

    }

    在局部类体内(不包括成员函数定义中的)的名字解析过程是:在外围域中查找出现在局部类定义之前的声明。在局部类的成员函数体内的名字的解析过程是:在查找外围域之前,首先直找该类的完整域。

    还是一样,如果先找到的声明使该名字的用法无效,则不考虑其他声明。即使在fooBar()中使用val 是错的,编译器也不会找到全局变量val,除非用全局域解析操作符限定修饰val。

  • 相关阅读:
    Binary Search Tree Iterator
    Oracle迁移:Linux->Windows
    OCP考点实战演练02-日常维护篇
    Oracle数据库全球化
    Oracle管理磁盘空间和资源
    Oracle数据库资源管理
    使用普通用户替代root来管理IEE
    记录一则ORA-12154,ORA-12560解决过程
    Oracle段收缩功能
    Oracle使用SQL传输表空间
  • 原文地址:https://www.cnblogs.com/wuchanming/p/3760109.html
Copyright © 2011-2022 走看看