zoukankan      html  css  js  c++  java
  • 类的其他特性

    类的基本特性包括:类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回*this。

    类成员再探

    定义一个类型成员

    Screen不是显示器中的一个窗口。每个Screen包含一个用于保存Screen内容的string成员和三个string::size_type 类型的成员,它们分别表示光标的位置以及屏幕的高和宽。

    除了定义数据和函数成员之外,类还可以自定义某种类型在类中的别名。由类定义的类型名字和其他成员一样存在访问限制,可以是public或者private中的一种:

    class Screen{
    public:
        typedef string::size_type pos;
    private:
        pos cursor=0;
        pos height=0,width=0;
        string contents;
    }

    我们在Screen的public部分定义了pos,这样用户就可以使用这个名字。Screen的用户不应该指针Screen使用一个string对象来存放它的数据,因此通过把pos定义成public成员可以隐藏Screen实现的细节。

    关于pos的声明有两点需要注意。首先,我们使用了typedef,也可以等价地使用类型别名:

    class Screen{
    public:
        using pos=string::size_type;
    };

    其次,用来定义类型的成员必须先定义后使用。这一点与普通成员有所区别。因此,类型成员通常出现在类开始的地方。

    Screen类的成员函数以及内联函数

    class Screen{
    public:
        typedef string::size_type pos;
        Screen()=default;
        Screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c) {}
        char get() const
        { return contents[cursor];} //隐式内联
        inline char get(pos ht,pos wd)const ;//显式内联
        Screen &move(pos r,pos c);  //能在之后被设为内联
    private:
        pos cursor=0;
        pos height=0,width=0;
        string contents;
    };

    在类中,常有一些规模较小的函数适用于被声明成内联函数。如我们之前所见的,定义在类内部的成员函数时自动inline的。因此,Screen的构造函数和返回光标所指字符的get函数默认是inline函数。

    我们可以在类的内部把inline作为声明的一部分显式地声明成员函数,同样的,也能在类的外部用inline关键字修饰函数的定义:

    inline Screen& Screen::move(pos r,pos c) //可以在函数的定义处指定inline
    {
        pos row=r*width;
        cursor=row+r;
        return *this;
    }
    
    char Screen::get(pos r,pos c) const //在类的内部声明成inline
    {
        pos row=r*width;
        return contents[row+c];
    }

    虽然我们无需在声明和定义的地方同时说明inline,但这么做其实是合法的。不过,最好只在类外部定义的地方说明inline,这样可以使类更容易理解。

    和我们在头文件中定义inline函数的原因一样,inline成员函数也应该与相应的类定义在同一个头文件中。(内联函数要和类的定义放在同一个文件中)

    重载成员函数

    和非成员函数一样,成员函数也可以被重载,只有函数之间在参数的数量或类型上有所区别就行。成员函数的函数匹配过程同样和非成员函数非常类似。

    可变数据成员

    有时(但并不频繁)会发生这样一种情况,我们希望能修改类的某个数据成员,即使是一个const成员函数内,可以通过在变量的声明中加入mutable关键字做到这一点。

    一个可变数据成员永远不会是const,即使它是const对象的成员。因此,一个const成员函数可以改变一个可变成员的值。举个例子,我们将给Screen添加一个名为access_ctr的可变成员,通过它我们可以追踪每个Screen的成员函数被调用了多少次。

    class Screen{
    public:
        void some_member() const;
    private:
        mutable size_t access_ctr;  //即使在一个const对象内也能被修改
    };
    void Screen::some_member() const 
    {
        ++access_ctr;
    }

    尽管some_member是一个const成员函数,它仍然能够改变access_ctr的值。该成员是个可变成员,因此任何成员函数,包括const函数在内都能改变它的值。

    类数据成员的初始值

    在C++11新标准中,可以给数据成员提供一个类内初始值:

    class Window_mgr{
    private:
        vector<Screen> screens{Screen(24,80,' ')};
    };

    如我们之前所知的,类内初始值必须使用=的初始化形式或者花括号括起来的直接初始化形式(初始化screens所用的)。

    当我们提供一个类内初始值时,必须以符号=或者花括号表示

    返回*this的成员函数

    接下来我们继续添加一些函数,它们负责设置光标所在位置的字符或者其他任一给定位置的字符:

    class Screen{
    public:
        Screen &set(char);
        Screen &set(pos,pos,char);
    };
    
    inline Screen &Screen::set(char c)
    {
        contents[cursor]=c;
        return *this;
    }
    
    inline Screen &Screen::set(pos r,pos col,char ch)
    {
        contents[r*width+col]=ch;
        return *this;
    }

    和move操作一样,我们的set成员的返回值是调用set的对象的引用。返回引用的函数是左值的,意味着这些函数返回的是对象本身而非对象的副本。如果我们把一系列这样的操作连接在一条表达式中的话:

    //把光标移动到一个指定的位置,然后设置该位置的字符值

    myScreen.move(4,0).set('#');

    这些操作将在同一个对象上执行。在上面的表达式中,我们首先移动myScreen内的光标,然后设置myScreen的contents成员。也就是说,上述语句等价于

    myScreen.move(4,0);

    myScreen.set('#');

    如果我们令move和set返回Screen而非Screen&,则上述语句的行为将大不相同。

    在此例中等价于:

    //如果move返回Screen而非Screen&

    Screem temp=myScreen.move(4,0);  //对返回值进行拷贝

    temp.set('#');    //不会改变myScreen的contents

    假如当初我们定义的返回类型不是引用,则move的返回值将是*this的副本,因此调用set只能改变临时副本,而不能改变myScreen的值。

    从const成员函数返回*this

    接下来,我们继续添加一个名为display的操作,他负责打印Screen的内容。我们希望这个函数能和move以及set出现在同一序列中,因此类似于move和set,display函数也应该返回执行它的对象的引用。

    从逻辑上来说,显示一个Screen并不需要改变他的内容,因此我们令display为一个const成员,此时,this将是一个指向const的指针而*this是const对象,由此推断,display的返回类型应该是const Screen&。然而,如果真的令display返回一个const引用,则我们不能把display嵌入到一组动作的序列中去:

    Screen myScreen;

    //如果display返回常量引用,则调用set将引发错误

    myScreen.display(cout).set('*');

    即使myScreen是个分出来对象,对set的调用也无法通过编译。问题在于display的const版本返回的会是常量引用,而我们显然无权set一个常量对象。

    一个const成员函数如果以引用的形式返回*this,那么它的返回类型是常量引用

    基于const的重载

    通过区分成员函数是否是const的,我们可以对其进行重载,其原因与我们之前根据指针参数会否指向const而重载函数的原因差不多。具体说来,因为非常量版本的函数对于常量对象时不可用的,所有我们只能在一个常量对象上调用const成员函数。另一方面,虽然可以在非常量对象上调用常量版本和非常量版本,但显然此时非常量版本是一个更好的匹配。

    在下面的例子中,我们将定义一个名为do_display的私有成员,由他负责打印Screen的实际工作。所有的display操作都将调用这个函数,然后返回执行操作的对象:

    class Screen{
    public:
    //根据对象是否是const重载了display函数
        Screen &display(ostream &os)
        {
            do_display(os);
            return *this;
        }
        const Screen &display(ostream &os) const 
        {
            do_display(os);
            return *this;
        }
    private:
        void do_display(ostream &os) const  
        {
            os<<contents;
        }
    };

    和我们之前所学的一样,当一个成员调用另外一个成员时,this指针在其中隐式地传递。因此,当display调用do_display时,它的this指针隐式地传递给do_display,而当display的非常量版本调用do_display时,它的this指针将隐式地从指向非常量的指针转换成指向常量的指针。

    当do_display完成后,display函数各自返回解引用this所得的对象。在非常量版本中,this指向一个非常量对象,因此display返回一个普通的(非常量)引用;而const成员则返回一个常量引用。

    当我们在某个对象上调用display时,该对象是否是const决定了应该调用display的那个版本:

    Screen myScreen(5,3);

    const Screen blank(5,3);

    myScreen.set('#').display(cout);  //调用非常量版本

    blank.display(cout);   //调用常量版本

    类类型

    每个类定义了唯一的类型。对于两个类来说,即使他们的成员完全一样,这两个类也是两个不同的类型。

    我们可以把类名作为类型的名字使用,从而直接指向类类型。或者,我们也可以把类名跟在关键字class或struct后面:

    Sales_data item1;   //默认初始化Sales_data类型的对象

    class Sales_data item1;   //一条等价的声明

    类的声明

    就像可以把函数的声明和定义分离开来一样,我们也能仅仅声明类而暂时不定义它:

    class Screen ; //声明Screen类

    这种声明有时被称作前向声明,它向程序中引入了名字Screen并且指明Screen是一种类类型。对于类型Screen来说,在它声明之后定义之是一个不完全类型,也就是说,此时我们已知Screen是一个类类型,但是不清楚它到底包含哪些成员。

    不完全类型只能在非常有限的情景下使用:可以定义指向这种类型的指针或引用,也可以声明(但是不能定义)以不完全类型作为参数或者返回类型的函数。

    对于一个类来说,在我们创建它的对象之前该类必须被定义过,而不能仅仅被声明。否则,编译器就无法了解这样的对象需要多少存储空间。类似的,类也必须首先被定义,然后才能用引用或者指针访问其成员。毕竟,如果类尚未定义,编译器也就不清楚该类到底有哪些成员。

    直到类被定义之后数据成员才能被声明成指针类型。换句话说,我们必须首先完成类的定义,然后编译器才能指针存储该数据成员需要多少空间。因为只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己。然而,一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此允许包含指向它自身类型的引用或指针:

    class Link_screen{

      Screen window;

      Link_screen *next;

      Link_screen *prev;

    };

  • 相关阅读:
    EF4.3 到底能不能用?
    系统架构师(详解+转)
    silverlight MD5加密
    silverlight自定义安装客户端插件
    vs2010中文安装英文版silverlight5,和MVC 3
    如何自定义gridview的表格显示?
    Scott Mitchell的ASP.NET2.0数据指南中文版索引
    成功软件开发者的9种编程习惯(一)
    第8章 商品目录管理
    6/29 项目目录结构有所调整
  • 原文地址:https://www.cnblogs.com/wuchanming/p/3900035.html
Copyright © 2011-2022 走看看