C++ Primer 学习笔记_49_类与数据抽象 --隐含的this指针

--隐含的this指针

引言:

在前面提到过,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为this,与调用成员函数的对象绑定在一起。成员函数不能定义this形参,而是有编译器隐含地定义。成员函数可以显式的使用this指针,但不是必须这么做。

1、何时使用this指针

有一种情况下,我们必须显式使用this指针:当需要将一个对象作为整体引用而不是引用对象的一个成员时。

比如在类Screen中定义两个操作:set和move,可以使得用户将这些操作的序列连接成一个单独的表达式:

    myScreen.move(4,0).set(‘#‘);
    //其等价于
    myScreen.move(4.0);
    myScreen.set(‘#‘);

2、返回*this

在单个表达式中调用move和 set操作时,每个操作必须返回一个引用,该引用指向执行操作的那个对象:

class Screen
{
public:
    Screen &move(index r,index c);
    Screen &set(char);
    Screen &set(index,index,char);
};

这样,每个函数都会返回调用自己的那个对象。使用this指针可以用来访问该对象:

Screen &Screen::set(char c)
{
    contents[cursor] = c;
    return *this;
}

Screen &Screen::move(index r,index c)
{
    index row = r * width;
    cursor = row + c;
    return *this;
}

3、从const成员返回*this

在普通的const成员函数中,this的类型是一个指向类类型的const指针。可以改变this所指向的值,但不能改变this所保存的地址。const成员函数中,this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。

不能从const成员函数返回指向类对象的普通引用。const成 员函数只能返回*this作为一个 const引用。

我们可以给Screen类增加一个const成员函数:display操作。如果将display作为 Screen的 const成员,则 display内部的 this指针将是一个constScreen* 型的const。然而:

    myScreen.move(4,0).set(‘#‘).display(cout);	//OK
    myScreen.display().set(‘*‘);	//Error

问题在于这个表达式是在由display返回的对象上运行set。该对象是const,因为display将其对象作为const返回。我们不能在const对象上调用set。

4、基于const的重载

为了解决以上问题,我们必须定义两个display操作:一个是const,一个不是const。基于成员函数是否为const,可以重载一个成员函数;同样的,基于一个指针形参是否指向const,也可以重载一个函数。非const对象可以使用任一成员,但非const版本是一个更好的匹配。

class Screen
{
public:
    //As before

    Screen &display(std::ostream &os)
    {
        do_display(os);
        return *this;
    }
    const Screen &display(std::ostream &os) const
    {
        do_display(os);
        return *this;
    }

private:
    void do_display(std::ostream &os) const
    {
        os << contents;
    }
    //As before
};

调用:

    Screen myScreen(5,3);
    const Screen blank(5,3);
    myScreen.set(‘#‘).display(cout);    //调用非const版本
    blank.display(cout);                //调用const版本

5、可变数据成员

有时,我们希望类的数据成员(甚至是在const成员函数中)可以修改。这可以通过将它们声明为mutable来实现。

可变数据成员永远都不能为const,甚至当它们是const对象的成员时也如此。因此,const成员函数可以改变mutable成员。

class Screen
{
public:
    //...

private:
    mutable size_t access_ctr;

    //使用access_ctr来跟踪Screen成员函数的调用频度
    void do_display(std::ostream &os) const
    {
        ++ access_ctr;      //OK
        os << contents;
    }
};

【建议:用于公共代码的私有实用函数】

使用私有实用函数(如前面的do_display)的好处:

1)一般愿望是避免在多个地方编写同样的代码。

2)display操作预期会随着类的演变而变得复杂。当涉及到的动作变得更复杂时,只在一处而不是两处编写这些动作有更显著的意义。

3)很可能我们会希望在开发时给do_display增加调试信息,这些调试信息将会在代码的最终成品版本中去掉。如果只需要改变一个do_display的定义来增加或删除调试代码,这样做将更容易。 4)这个额外的函数调用不需要涉及任何开销。我们使do_display成为内联的,所以调用do_display与将代码直接放入display操作的运行时性能应该是相同的

P379习题12.13

//1. in screen.h
#ifndef SCREEN_H_INCLUDED
#define SCREEN_H_INCLUDED
#include <string>

class Screen
{
public:
    typedef std::string::size_type index;

    Screen &move(index r,index c);
    Screen &set(char);
    Screen &set(index,index,char);

    char get() const
    {
        return contents[cursor];
    }
    char get(index ht,index wd) const;

    index get_cursor() const;

    Screen &display(std::ostream &os)
    {
        do_display(os);
        return *this;
    }
    const Screen &display(std::ostream &os) const
    {
        do_display(os);
        return *this;
    }

    Screen():cursor(0),height(0),width(0){}
    Screen(index,index,const std::string &tmp);

private:
    void do_display(std::ostream &os) const
    {
        os << contents;
    }

    std::string contents;
    index cursor;
    index height,width;
};

inline char Screen::get(index ht,index wd) const
{
    index row = width * ht;
    return contents[row + wd];
}

inline Screen::index Screen::get_cursor() const
{
    return cursor;
}
#endif // SCREEN_H_INCLUDED

//2. in screen.cpp
#include "screen.h"

Screen::Screen(index ht,index wd,const std::string &cntnts):height(ht),width(wd),cursor(0),contents(cntnts){}

Screen &Screen::set(char c)
{
    contents[cursor] = c;
    return *this;
}
Screen &Screen::set(index ht,index wd,char c)
{
    index row = ht * width;
    contents[row + wd] = c;
    return *this;
}

Screen &Screen::move(index r,index c)
{
    index row = r * width;
    cursor = row + c;
    return *this;
}

//3. in main.cpp
#include <iostream>
#include "screen.h"
using namespace std;

int main()
{
    Screen myScreen(5,6,"aaaaa\naaaaa\naaaaa\naaaaa\naaaaa\naaaaa\n");
    myScreen.move(4,0).set(‘#‘).display(cout);
}

时间: 2024-11-08 08:02:22

C++ Primer 学习笔记_49_类与数据抽象 --隐含的this指针的相关文章

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

C++ Primer 学习笔记_56_类与数据抽象 --消息处理示例

复制控制 --消息处理示例 说明: 有些类为了做一些工作需要对复制进行控制.为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序.Message类和 Folder类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中.Message上有 save和 remove操作,用于在指定Folder中保存或删除该消息. 数据结构: 对每个Message,我们并不是在每个Folder中都存放一个副本,而是使每个Message保存一个指针集(set),set中

C++ Primer 学习笔记_57_类与数据抽象 --管理指针成员

复制控制 --管理指针成员 引言: 包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象! 将一个指针复制到另一个指针时,两个指针指向同一对象.当两个指针指向同一对象时,可能使用任一指针改变基础对象.类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在.指针成员默认具有与指针对象同样的行为. 大多数C++类采用以下三种方法之一管理指针成员: 1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制! 2)类

C++ Primer 学习笔记_55_类与数据抽象 --析构函数

复制控制 --析构函数 引言: 在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源.析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充. 1.何时调用析构函数 撤销类对象时会自动调用析构函数: Sales_item *p = new Sales_item; { Sales_item item(*p); //调用复制构造函数 delete p; //调用指针p的析构函数 } //调用对象item的析构函数 动态分配的对象只有在指向该对象的指针被删除时才撤销,

C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员

类 --友元.static成员 一.友元 友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类(对未被授权的函数或类,则阻止其访问):友元的声明以关键字friend开始,但是它只能出现在类定义的内部.友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响. [最佳实践] 通常,将友元声明成组的放在类定义的开始或结尾是个好主意! 1.友元关系:一个例子 假设一个窗口管理类Window_Mgr可能需要访问由其管理的Screen对象的内部

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式.auto_ptr与单例模式.const成员函数.const 对象.mutable修饰符 前言 [例]写出面向对象的五个基本原则? 解答:单一职责原则,开放封闭原则,依赖倒置原则,接口隔离原则和里氏替换原则 里氏替换原则:子类型必须能够替换他们的基类型. 设计模式分为三种类型:创建型模式.结构型模式和行为型模式 一.static 与单例模式 1.单例模式 单例模式的意图:保证一个类仅有一个实例,并提供一个访问它

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针 1.引言 在前面提到过,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针.这个隐含形参命名为this. 2.返回*this 成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针(编译器自动传递)使用this指针保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享.成员函数是只读的代码,由所有对象共享,并不占对象的存储空间,因为this指针指向当前对象,所以

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域 引言: 每个类都定义了自己的新作用域与唯一的类型.即使两个类具有完全相同的成员列表,它们也是不同的类型.每个类的成员不同与任何其他类(或任何其他作用域)的成员. 一.类作用域中的名字查找 1)首先,在使用该名字的块中查找名字的声明.只考虑在该项使用之前声明的名字. 2)如果在1)中找不到,则在包围的作用域中查找. 如果找不到任何声明,则出错. 类的定义实际上是在两个阶段中处理: 1)首先,编译器声明: 2)只有在所有成员出现之后