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。

  • 相关阅读:
    poj 2187 Beauty Contest(旋转卡壳)
    poj 2540 Hotter Colder(极角计算半平面交)
    poj 1279 Art Gallery(利用极角计算半平面交)
    poj 3384 Feng Shui(半平面交的联机算法)
    poj 1151 Atlantis(矩形面积并)
    zoj 1659 Mobile Phone Coverage(矩形面积并)
    uva 10213 How Many Pieces of Land (欧拉公式计算多面体)
    uva 190 Circle Through Three Points(三点求外心)
    zoj 1280 Intersecting Lines(两直线交点)
    poj 1041 John's trip(欧拉回路)
  • 原文地址:https://www.cnblogs.com/wuchanming/p/3760109.html
Copyright © 2011-2022 走看看