前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++:33---类成员指针

C++:33---类成员指针

作者头像
用户3479834
发布2021-02-03 12:29:35
8270
发布2021-02-03 12:29:35
举报
文章被收录于专栏:游戏开发司机游戏开发司机

成员指针概述:

  • 当初始化一个这样的指针时,我们令其指向类的某个成员,但是不指定该成员所属的对象
  • 直到使用成员指针时,才提供成员所属的对象
  • 成员指针是指可以指向类的非静态成员的指针
  • 一般情况下,指针指向一个对象,但是成员指针指向的是类的成员,而不是类的所创建出的对象
  • 类的静态成员不属于任何对象,因此无需特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有任何区别
  • 成员指针的类型囊括了类的类型以及成员的类型:

  • 下面我们定义一个类,作为本文讲解的基础:
代码语言:javascript
复制
class Screen {
public:
typedef std::string::size_type pos;
char get_cursor()const { return contents[cursor]; }
char get()const;
char get(pos ht, pos wd)const;
private:
std::string contents;
pos cursor;
pos height, width;
};

一、数据成员指针

数据成员指针的定义

  • 特点:
    • 需要使用*来表示当前的变量是一个指针
    • 成员指针定义时必须包含所属的类
  • 指针的定义
    • 下面定义一个指向Screen对象的指针,指针的类型为string,并且指针为常量指针(因此不能通过这个指针修改值)
    • 由于指针的类型为string,因此该指针可以指向常量(非常量)Screen对象的string成员
代码语言:javascript
复制
const string Screen::*pdata;
  • 为指针赋值:
代码语言:javascript
复制
//将pdata指向于Screen类的contents成员pdata = &Screen::contents;
  • 也可以在定义数据成员指针时直接初始化,并且使用auto或decltype来定义:
代码语言:javascript
复制
auto pdata = &Screen::contents;

使用数据成员指针

  • 指针定义之后,该指针没有指向于任何数据成员,指针只是指向于成员而非所属的对象,只有当解引用成员指针时我们才提供对象的信息
  • 例如:
代码语言:javascript
复制
Screen myScreen;Screen *pScreen = &myScreen;
//.*解引用*pdata以获得myScreen对象的contents成员auto s = myScreen.*pdata; //相当于myScreen.contents
//->*解引用*pdata以获得myScreen对象的contents成员s = pScreen->*pdata;      //相当于pScreen->contents

返回数据成员指针的函数

  • 在上面定义成员指针的时候,pdata不能出现在Screen类的外部,因为contents是private的(上面只是为了演示说明)
  • 为了体现封装性,我们通常定义一个成员函数,用该函数来返回成员的指针
  • 例如:
代码语言:javascript
复制
class Screen {public://成员函数,返回一个成员的指针static const std::string Screen::*data() {return &Screen::contents;}private:std::string contents;};
  • 我们将函数定义为static,其不属于任何对象,因此我们可以直接调用该函数来获得成员的指针
  • 当我们调用data时,就可以得到一个成员的指针。例如:
代码语言:javascript
复制
int main(){//调用data()静态函数来获得一个成员的指针const std::string Screen::*pdata = Screen::data();return 0;}
  • 定义了之后,我们可以正常的使用这个指针。例如:
代码语言:javascript
复制
int main(){Screen *pScreen = new Screen;
const std::string Screen::*pdata = Screen::data();auto s = pScreen->*pdata; //等价于pScreen->contents
return 0;}

二、成员函数指针

  • 与指向数据成员的指针类似,我们也可以声明一个指向于成员函数的指针
  • 语法注意事项:
    • 指向成员函数的指针也需要指定目标函数的返回类型和形参列表
    • 如果成员函数时const的或者是引用成员,则我们必须将const限定符或引用限定符包含进来

成员函数指针的定义

  • 最简单的方法就是使用auto来声明一个指向于成员函数的指针
代码语言:javascript
复制
//pmf是一个函数指针,指向于get_cursor函数auto pmf = &Screen::get_cursor;
  • 因为定义成员函数指针时,需要同时指定目标函数的返回值类型和形参列表,因此对于有重载的成员函数不会造成冲突。例如:
代码语言:javascript
复制
class Screen {public:typedef std::string::size_type pos;
char get_cursor()const { return contents[cursor]; }char get()const;char get(pos ht, pos wd)const;//...};
int main(){//pmf2是一个成员函数指针,其指向于返回值为char,形参为两个Screen::pos类型的成员函数char (Screen::*pmf2)(Screen::pos, Screen::pos)const;//为pmf2指针赋值pmf2 = &Screen::get;return 0;}
  • 出于优先级的考虑,上述定义中指针两侧的括号不能缺少。如果没有这对括号,编译器将认为该声明是一个(无效的)函数声明:
    • 错误的原因:编译器会认为p是一个普通函数,并且返回Screen类的一个char成员。因为其是一个普通函数,所以不能使用const限定符
代码语言:javascript
复制
//错误的语法,非成员函数p不能使用const限定符char Screen::*pmf2(Screen::pos, Screen::pos)const;
  • 和普通函数指针不同的是,在成员函数和指向该成员的指针之间不存在自动转换规则:
代码语言:javascript
复制
char (Screen::*pmf2)(Screen::pos, Screen::pos)const;
pmf2 = &Screen::get; //正确pmf2 = Screen::get;  //错误,缺少&。在成员函数和指针之间不存在自动转换规则

使用成员函数指针

  • 和使用数据成员的指针一样,我们需要使用.*或者->*运算符作用域指向成员函数的指针,来调用类的成员函数
  • 例如:
代码语言:javascript
复制
//pmf为成员函数指针,指向于get_cursor()函数auto pmf = &Screen::get_cursor;//pmf2为成员函数指针,指向于带有两个参数的get()函数char (Screen::*pmf2)(Screen::pos, Screen::pos)const = &Screen::get;
Screen myScreen;Screen *pScreen = &myScreen;
char c1 = (myScreen.*pmf)();     //等价于myScreen.get_cursor()char c2 = (pScreen->*pmf2)(0, 0);//等价于pScreen->get(0,0)
  • 在上面,在解引用成员函数的指针时需要在两侧加上括号,如果不加,那么会错误:
    • 错误的原因:我们想要调用名为pmf和pmf2的函数,然后使用这些函数的返回值作为指针指向成员运算符.*与->*的运算多想。然而pmf与pmf2并不是一个函数,因此代码错误
代码语言:javascript
复制
char c1 = myScreen.*pmf();      //错误的//其等价于myScreen.*(pmf())
char c2 = pScreen->*pmf2(0, 0); //错误的//其等价于myScreen->*(pmf2(0,0))

使用成员指针的类型别名

  • 使用类型别名或typedef可以让成员指针更容易理解
  • 例如,下面的类型别名将Action定义为两参数get函数的同义词:
代码语言:javascript
复制
//Action是一种可以指向Screen成员函数的指针,其接受两个pos实参,返回值类型为charusing Action = char (Screen::*)(Screen::pos, Screen::pos)const;
  • 现在我们定义成员函数指针时就比较方便了:
代码语言:javascript
复制
//get是一个指向成员函数的指针Action get = &Screen::get;
  • 和其他函数指针类似,我们可以将指向成员函数的指针作为某个函数的返回类型或形参类型。其中,指向成员的指针形参也可以拥有默认实参
代码语言:javascript
复制
using Action = char (Screen::*)(Screen::pos, Screen::pos)const;
//action是一个函数,其中参数2为一个指针,并且其有默认实参,指向于Screen的get成员函数Screen& action(Screen&, Action = &Screen::get);
  • 当我们调用action函数的时候,只需将Screen的一个符合要求的函数的指针或地址传入即可:
代码语言:javascript
复制
using Action = char (Screen::*)(Screen::pos, Screen::pos)const;Screen& action(Screen&, Action = &Screen::get);
int main(){Screen myScreen;Action get = &Screen::get;
action(myScreen);               //使用默认实参action(myScreen, get);          //参数2调用前面定义的指针变量getaction(myScreen, &Screen::get); //参数2显式地传入地址return 0;}

成员指针函数表

  • 对于普通函数指针和指向成员函数的指针来说,一种常见的用法是将其存入一个函数表中
  • 如果一个类含有几个相同类型的成员,则这样一张表可以帮助我们从这些成员中选择一个
  • 假定Screen类含有几个成员函数,每个函数负责将光标向指定的方向移动:
代码语言:javascript
复制
class Screen {public:typedef std::string::size_type pos;
//移动光标的一系列函数Screen& home();Screen& forward();Screen& back();Screen& up();Screen& down();private:pos cursor; //光标};
  • 这几个函数有一个共同点:都不接受任何参数,并且返回值是发生光标移动的Screen的引用
  • 现在我们开始设计函数表:
    • 在此之前,先定义一个静态成员Menu,该成员是指向光标移动函数的指针的数组
    • 定义一个move函数,使其可以调用上面的任意一个函数并执行对应的操作
    • 设计一个枚举用于函数传参
代码语言:javascript
复制
class Screen {public:typedef std::string::size_type pos;
Screen& home();Screen& forward();Screen& back();Screen& up();Screen& down();
//函数指针using Action = Screen& (Screen::*)();
//定义一个枚举enum Directions { HOME, FORWARD, BACK, UP, DOWN };//参数使用枚举来调用函数表中对应的函数Screen& move(Directions cm){//必须使用thisreturn (this->*Menu[cm])();}private:pos cursor;
static Action Menu[]; //函数表};
//初始化函数表,将内部移动光标的函数都添加进去Screen::Action Screen::Menu[] = {&Screen::home,&Screen::forward,&Screen::back,&Screen::up,&Screen::down };
  • 现在我们可以调用move函数了:
代码语言:javascript
复制
int main(){Screen myScreen;myScreen.move(Screen::HOME); //调用muScreen.homemyScreen.move(Screen::DOWN); //调用muScreen.down
return 0;}

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

成员指针不是可调用对象

  • 通过上面我们知道,想要调用成员函数指针,必须通过一个类配合.*运算符或->*运算符来调用。因此与普通的函数指针不同,成员指针不是一个可调用对象,这样的指针不支持函数调用运算符.
  • 因为成员指针不是可调用对象,所以我们不能直接将一个指向成员函数的指针传递给算法
  • 例如,下面在一个vector中寻找第一个空的string:
代码语言:javascript
复制
std::vector<std::string> svec;auto fp = &std::string::empty; //fp指向string的empty函数,fp是一个成员函数指针
//错误,必须使用.*或->*调用成员指针fpstd::find_if(svec.begin(), svec.end(), fp);
  • find_if算法需要一个可调用对象,但我们提供给它的是一个指向成员函数的指针fp。因此在find_if的源码内部执行如下形式的代码,从而导致无法通过编译:
代码语言:javascript
复制
//检查对当前元素的断言是否为真if(fp(*it))  //错误,想要通过成员指针调用函数,必须使用->*运算符
  • 显然该语句试图调用的是传入的对象,而非函数

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

  • function模板:
  • 从指向成员函数的指针获取可调用对象的一种方法是使用function模板。下面的代码就是正确的了:
代码语言:javascript
复制
std::vector<std::string> svec;
//empty函数的返回值为bool,参数为const string&function<bool(const std::string&)> fcn = &std::string::empty;
//现在是正确的了,fcn是一个可调用对象,使用.*调用emptystd::find_if(svec.begin(), svec.end(), fcn);
  • 当一个function对象包含有一个指向成员函数的指针时,function类知道它必须使用正确的指向成员的指针运算符来执行函数调用。也就是说,我们可以认为在find_if内部有类似于下面的代码:
代码语言:javascript
复制
//假设it是find_if内部的迭代器,则*it是一个string对象if(fcn(*it)) //fcn就是empty的函数指针,等价于empty(*it)
  • 其中,function将使用正确的指向成员的指针运算符。从本质上看,function类将函数调用转换成如下的形式:
代码语言:javascript
复制
if(((*it).*p)) //假设p是fcn内部的一个指向成员函数的指针,此案例中为指向于empty函数的指针
  • 下面的调用代码与上面的原理是类似的
代码语言:javascript
复制
std::vector<std::string*> svec;
//empty函数的返回值为bool,参数为const string*	function<bool(const std::string*)> fcn = &std::string::empty;
//现在是正确的了,fcn是一个可调用对象,使用->*调用emptystd::find_if(svec.begin(), svec.end(), fcn);

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

  • 通过上面知道,想要使用function,必须提供成员的调用形式。我们也可以采取另一种方法,就是使用标准库功能mem_fn来让编译器负责推断成员的类型
  • mem_fn也定义在functional头文件中,并且可以从成员指针生成一个可调用对象
  • 和function不同的是:
    • mem_fn可以根据成员指针的类型推断可调用对象的类型,而无须用户显式地指定
    • 例如:我们使用mem_fn生成一个可调用对象,该对象接受一个string实参,返回一个bool值(编译器自动推断的)
代码语言:javascript
复制
std::find_if(svec.begin(), svec.end(), mem_fn(&std::string::empty));
  • mem_fn生成的可调用对象可以通过对象调用,也可以通过指针调用:
代码语言:javascript
复制
std::vector<std::string> svec;
auto f = mem_fn(&std::string::empty); //f接受一个string或者一个string*
f(*svec.begin()); //正确,传入一个string对象,f使用.*调用emptyf(&svec[0]);      //正确,传入一个string的指针,f使用.*调用empty
  • 实际上,我们可以认为mem_fn生成的可调用对象含有一个重载的函数调用运算符:一个接受string*,另一个接受string&

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

  • 处于完整性的考虑,我们还可以使用bind从成员函数生成一个可调用对象:
代码语言:javascript
复制
std::vector<std::string> svec;
//选择范围中的每个string,并将其bind到empty的第一个隐式实参上auto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));
  • 和function类似的地方是,当我们使用bind时,必须将函数中用于表示执行对象的隐式形参转换成显式的。和mem_fn类似的地方是,bind生成的可调用对象的第一个实参既可以是string的指针,也可以是string的引用:
代码语言:javascript
复制
std::vector<std::string> svec;
auto f = bind(&string::empty._1);f(*svec.begin()); //正确,传入一个string对象,f使用.*调用emptyf(&svec[0]);      //正确,传入一个string的指针,f使用.*调用empty
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、数据成员指针
    • 数据成员指针的定义
      • 使用数据成员指针
        • 返回数据成员指针的函数
        • 二、成员函数指针
          • 成员函数指针的定义
            • 使用成员函数指针
              • 使用成员指针的类型别名
                • 成员指针函数表
                • 三、将成员函数用作可调用对象
                  • 成员指针不是可调用对象
                    • ①使用function生成一个可调用对象
                      • ②使用mem_fn生成一个可调用对象
                        • ③使用bind生成一个可调用对象
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档