zoukankan      html  css  js  c++  java
  • 类成员指针

    数据成员指针

    成员指针是指可以指向非静态成员的指针,成员指针指示的是类的成员,而非类的对象。类的静态成员不属于任何对象,因此无须特殊的指向静态成员指针,指向静态成员的指针与普通的指针没有什么区别。

    class Screen
    {
    public:
    	typedef std::string::size_type pos;
    	char get_cursor() const { return contents[cursor]; }
    	char get() const;
    	char get(pos gt, pos wd) const;
    private:
    	std::string contents;
    	pos cursor;
    	pos height, width;
    };
    

    声明数据成员指针:

    //@ pdata 可以指向一个常量(非常量) Screen 对象的 string 成员
    const string Screen::*pdata;
    

    常量对象的数据成员本身也是常量,因此将指针声明成指向 const string 成员的指针意味着 pdada 可以指向任何 Screen 对象的一个成员,而不管该 Screen 对象是否是常量。作为交换条件,只能使用 pdata 读取它所指的成员,而不能向它写入内容。

    初始化陈冠指针或者向它赋值时,需指定它所指的成员:

    pdata = &Screen::contents;
    

    在 C++ 11中可以使用 auto 或者 decltype:

    auto pdata = &Screen::contents;
    

    使用数据成员指针

    初始化一个成员指针或者尾为成员指针赋值时,该指针并没有指向任何数据。成员指针指定了成员而非该成员所属的对象,只有当解引用成员指针时才能提供对象的信息。

    成员指针访问运算符:.*->*

    Screen myScreen, *pScreen = &myScreen;
    //@ .* 解引用 pdata 以获得 myScreen 对象的 contents 成员
    auto s = myScreen.*pdata;
    //@ ->* 解引用 pdata 以获得 pScreen 所指对象的 contents 成员
    s = pScreen->*pdata;
    

    返回数据成员指针的函数

    常规的访问控制规则对成员指针同样有效,例如 Screen 的 contents 成员是私有的,因此之前对于 pdata 的使用必须位于 Screen 类的成员或友元内部,否则程序将引发错误。

    最好定义一个函数,令其返回值是指向该成员的指针:

    class Screen
    {
    public:
    	//@ data 是一个静态成员,返回一个成员指针
    	static const std::string Screen::*data()
    	{
    		return &Screen::contents;
    	}
    };
    

    从右向左阅读可知 data 函数返回的是一个指针,该指针指向 Screen 类的 const string 成员。

    const string  Screen::*pdata = Screen::data();
    

    成员函数指针

    //@ pmf 是一个指针,它可以指向 Screen 的某个常量成员函数
    //@ 前提是该函数不接受任何实参,并且返回一个 char
    auto pmf = &Screen::get_cursor;
    

    如果成员函数是 const 成员或者引用成员,则必须将 const 限定符或引用包含进来。

    和普通的函数指针类似,如果成员存在重载的问题,则必须显式地声明函数类型以明确指出想要使用哪个函数:

    char (Screen::*pmf2)(Screen::pos, Screen::pos) const;
    pmf2 = &Screen::get;
    

    上面 Screen::*pmf2 的括号是必不可少的,如果声明成:

    //@ 错误,非成员函数不能使用 const 限定符
    char Screen::*p(Screen::pos, Screen::pos) const;
    

    上面的声明试图定义一个名为 p 的普通函数,并且返回 Screen 类的一个 char 成员。因为声明的是普通函数,所以不能使用 const 限定符。

    和普通函数指针不同,在成员函数和指向该成员的指针之间不存在自动转换规则:

    //@ pmf 指向一个 Screen 成员,该成员不接受任何实参并且返回类型是 char
    pmf = &Screen::get;		//@ 必须显式地使用取地址运算符
    pmf = Screen::get;		//@ 错误,在成员函数和指针之间不存在自动转换规则
    

    使用成员函数指针

    使用 .*->*运算符作用于指向成员函数的指针,以调用类的成员函数:

    Screen myScreen, *pScreen = &myScreen;
    //@ 通过 pScreen 所指的对象调用 pmf 所指的函数
    char c1 = (pScreen->*pmf)();
    //@ 通过 myScreen 对象将实参 0,0 传递给含有两个形参的 get 函数
    char c2 = (myScreen.*pmf2)(0,0);
    

    上面的代码如果不加括号:

    myScreen.*pmf();
    

    则等价于:

    myScreen.*(pmf());
    

    含义是调用一个名为 pmf 的函数,然后使用该函数的返回值作为指针指向成员运算符(.*) 的运算对象,但是 pmf 并不是一个函数,因此代码将出错。

    使用成员指针的类型别名

    //@ Action 是一个指向 Screen 成员函数的指针,它接受两个 pos 实参,并返回一个 char
    using Action = char(Screen::*)(Screen::pos, Screen::po) const;
    

    通过使用 Action 可以简化指向 get 的指针定义:

    Action get = &Screen::get;	//@ get 指向 Screen 的 get 成员
    

    可以将指向成员函数的指针作为某个函数的返回类型或形参类型,其中,指向成员的指针形参也可以拥有默认实参:

    //@ action 接受一个 Screen 的引用,和一个指向 Screen 成员函数的指针
    Screen& action(Screen&, Action = &Action::get);
    

    调用 action 函数:

    Screen myScreen;
    //@ 等价的调用
    action(myScreen);				//@ 使用默认参数
    action(myScreen,get);			//@ 使用之前定义的变量 get
    action(myScreen,&Screen::get);	//@ 显式地传入地址
    

    成员指针函数表

    对于普通函数指针和指向成员函数的指针来说,一种常见的用法是将其存入一个函数表当中。如果一个类含有几个相同类型的成员,则这样一张表可以帮助从这些成员中选择一个。假定 Screen 类含有几个成员函数,每个函数负责将光标向指定的方向移动:

    class Screen
    {
    public:
    	Screen& home();
    	Screen& forward();
    	Screen& back();
    	Screen& up();
    	Screen& down();
    };
    

    我们定义一个 move 函数,使其可以调用上面任意 一个函数:

    class Screen
    {
    public:
    	using Action = Screen& (Screen::*)();
    	enum Directions {HOME,FORWARD,BACK,UP,DOWN};
    	Screen& move(Directions);
    private:
    	static Action Menu[];	//@ 函数表
    };
    

    数组 Menu 依次保存每个光标移动函数的指针,这些函数将按照 Directions 中枚举对应的偏移量存储:

    Screen& Screen::move(Directions cm)
    {
    	return (this->*Menu[cm])();	//@ Menu[cm] 指向一个成员函数
    }
    

    move 中函数调用的原理是:首先获取索引值为 cm 的 Menu 元素,该元素是指向 Screen 成员函数指针根据 this 所指的对象调用该元素所指的成员函数。

    Screen& Screen::move(Directions cm)
    {
    	return (this->*Menu[cm])();	//@ Menu[cm] 指向一个成员函数
    }
    
    Screen myScreen;
    myScreen.move(Screen::HOME);
    myScreen.move(Screen::DOWN);
    
    Screen::Action Screen::Menu[] = {
    	&Screen::home,
    	&Screen::forward,
    	&Screen::back,
    	&Screen::up,
    	&Screen::down,
    };
    

    将成员函数用作可调用对象

    要想通过一个指针成员函数的指针进行函数调用,必须首先利用 .* 运算符或 ->* 运算符将该指针绑定到特定的对象上。

    成员指针不是一个可调用对象,这样的指针不支持函数调用运算符。因为成员指针不是可调用对象,所以不能直接将一个指向成员函数的指针传递给算法:

    例如,想在一个 string 的 vector 中找到第一个空的 string:

    auto fp = &string::empty;	//@ fp 指向 string 的 empty 函数
    //@ error,必须使用 .* 或 ->* 调用成员指针
    find_if(svec.begin(), svec.end(), fp);
    

    find_if 算法需要一个可调用对象,fp 是一个成员函数指针,非可调用对象。

    使用 function 生成一个可调用对象

    从指向成员函数指针获取可调用对象的一种方法是使用标准库模板 function :

    function<bool(const string&)> fcn = &Screen::empty;
    find_if(svec.begin(), svec.end(), fcn);
    

    提供给 function 的形式必须指明对象是否是以指针或引用的形式传入:

    vector<string*> pvec;
    function<bool(const string*)> fp = &Screen::empty;
    //@ fp 接受一个指向 string 的指针,然后使用 ->* 调用 empty
    find_if(pvec.begin(), pvec.end(), fp);
    

    使用 mem_fn 生成一个可调用对象

    通过使用标准库功能 mem_fn 来让编译器负责推断成员类型。mem_fn 也定义在 function 头文件中,并且可以从成员指针生成一个可调用对象:和 function 不同的是,mem_fn 可以根据成员指针的类型推断可调用对象的类型,而无须显式地指定:

    find_if(svec.begin(), svec.end(), mem_fn(&string::empty));
    

    使用 mem_fn(&string::empty) 生成一个可调用对象,该对象接受一个 string 实参,返回一个 bool 值。

    mem_fn 生成的可调用对象可以通过对象调用,也可以通过指针调用:

    auto f = mem_fn(&string::empty);	//@ f 接受一个 string 或者 string*
    f(*svec.begin());	//@ ok,传入一个 string 对象,f 使用 .* 调用 empty
    f(&svec[0]);		//@ ok,传入一个 string 的指针,f 使用 ->* 调用 empty
    

    实际上可以认为 mem_fn 生成的可调用对象含有一对重载的函数调用运算符:

    • 一个接受 string*。
    • 一个接受 string&。

    使用 bind 生成有一个可调用对象

    可以使用 bind 从成员安徽省农户生成一个可调用对象:

    //@ 选择范围中的每个 string,并将其 bind 到 empty 的第一个隐式实参上
    auto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));
    

    和 function 类似的地方,使用 bind 函数时,必须将函数中用于表示执行对象的隐式形参转换成显示的。

    和 mem_fn 类似的地方,bind 生成的可调用对象的第一个实参既可以是 string 的指针,也可以是 string 的引用:

    auto f = bind(&string::empty, _1);
    f(*svec.begin());	//@ ok,传入一个 string 对象,f 使用 .* 调用 empty
    f(&svec[0]);		//@ ok,传入一个 string 的指针,f 使用 ->* 调用 empty
    
  • 相关阅读:
    maven settings.xml 文件
    Ajax配合后端实现Excel的导出
    线性表的链式存储--单链表
    微软最新开源工具PowerToys,让你的win10更加好用!!
    二叉树合集(三):线索二叉树(图文详解)
    二叉树合集(二):霍夫曼树(图文详解)
    二叉树合集(一):二叉树基础(含四种遍历,图文详解)
    二叉树合集(六):高度平衡的二叉搜索树简介(图文解析)
    二叉树合集(五):二叉搜索树(图片详解,含基本操作)
    【LeetCode】108.将有序数组转换为平衡二叉树(中序遍历三种方法,java实现)
  • 原文地址:https://www.cnblogs.com/xiaojianliu/p/12494953.html
Copyright © 2011-2022 走看看