C++ Pointer-Like Classes,C++的指针类

23 篇文章 22 订阅

所谓pointer-like class(像指针的类,下文简称为指针类),是指一个类被设计成像指针一样,可以当成指针来使用。为什么有了传统的指针还需要指针类?这是由于我们想在指针的基础上多做一点东西,或者说,我们想对指针有更多的控制或者让指针支持额外的功能。

下面介绍两类常见的指针类:智能指针和迭代器。

智能指针

对于传统的指针,我们可以使用两个操作符,即*解引用操作符,->成员访问操作符。一个类要设计成智能指针类,则也要提供这两个操作。这可以通过对类的操作符重载实现。

namespace ll {
    template<class T>
    class shared_ptr
    {
    public:
        T& operator* () const {
            return *px;
        }
        T* operator->() const {
            return px;
        }
        shared_ptr(T* p): px(p) {}
    private:
        T *px;    
    };
}

可以看到,shared_ptr里面包含了一个真正的指针px,这个指针会指向实际所指的对象。另外,shared_ptr还模拟了指针的的*和->操作,这通过操作符重载实现。下面的代码展示了shared_ptr的使用方法。

struct Foo
{
    void method() {
        cout << "method" << endl;
    }
};

ll::shared_ptr<Foo> sp(new Foo);  // 定义了一个指向Foo类型的智能指针
Foo f(*sp);  // 定义了一个Foo对象,并使用智能指针sp所指向的对象来进行初始化
f.method();  // 通过对象f调成成员函数
sp->method();  // 见下面的解释

上面的代码声明了一个智能指针sp,并通过->操作符来调用成员函数。sp->method();会首先调用操作符函数operator->() const,得到智能指针内部真实的指针px,然后再通过px调用method函数(px->method())。在这里,可能大家有个疑问,即sp->通过调用操作符函数operator->()已经消耗了一个->符号,px->method()->是怎么来的呢?C++规定,对于调用->操作符后,->操作符并不消耗掉,而是会继续作用下去。这样一来,sp->method();便等同于sp->method();

迭代器

迭代器也可以认为是上面所说的智能指针,不过迭代器除了提供*->操作外,还需要提供++--的操作。
下面的代码示例以链表的迭代器(__list_iterator)为例,展示了迭代器的特点。

namespace ll {
    template<class T>
    struct __list_node {
        void *prev;
        void *next;
        T data;
    };

    template<class T, class Ref, class Ptr>
    struct __list_iterator {
        typedef __list_iterator<T, Ref, Ptr> self;
        typedef Ptr pointer;
        typedef Ref reference;
        typedef __list_node<T>* link_type;
        link_type node;

        reference operator*() const {
            return (*node).data;
        }
        pointer operator->() const {
            return &(operator*());
        }
        self& operator++() {
            node = (link_type)((*node).next);
            return *this;
        }
        self& operator--() {
            node = (link_type)((*node).prev);
            return *this;
        }  
    };
}

链表(双向链表)的数据结构如下图所示。

链表示意图

链表的结点包含三个部分,分别前一个结点的指针,后一个结点的指针,以及数据。
迭代器__list_iterator包含一个真实的指针node,用来指向链表中的结点。可以看到,__list_iterator除了提供*->操作,还提供了++--操作。对迭代器进行++操作,会指向下一个元素,对迭代器进行--操作,会指向前一个元素。
由于node只是指向整个结点,为了取得结点的数据,还需要这样调用(*node).data

__list_iterator的使用的示例代码如下。

struct Foo
{
    void method() {
        cout << "method" << endl;
    }
};

ll::__list_iterator<Foo, Foo&, Foo*> iter;
*iter;  // 获得一个Foo对象
iter->method();  // 意思是调用Foo::method(),相当于(*iter).method(),也相当于(&(*iter))->method()

首先,我们声明一个__list_iterator迭代器iter。我们知道迭代器也是一种指针类,所以可以模拟指针的操作。
通过*操作符,我们可以得到一个Foo对象。
iter->method();则调用Foo::method(),这个语句复杂一点,解释如下。

我们把上面的有关代码摘录如下:

reference operator*() const { return (*node).data; }
pointer operator->() const { return &(operator*()); }

iter->method();会首先调用operator->() const函数,由于operator->() const里面还会调用operator*()先取得data,然后再取地址,所以operator->() const会返回data的地址。在上例中,亦即是Foo对象的地址。这样,通过->操作符,便可以调用到Foo::method()

参考资料:
1. 侯捷《C++最佳编程实践》视频,极客班,2015
2. 《C++ Primer》第15.6节,第三版,2002,潘爱民等译

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值