指向函数的指针用法

指向函数的指针用法,第1张

看完以下的,您就知道什么是指向函数指针了,其实就是回调函数!

程序员常常需要实现回调。本文将讨论函数指针的基本原则并说明如何使用函数指针实现回调。注意这里针对的是普通的函数,不包括完全依赖于不同语法和语义规则的类成员函数(类成员指针将在另文中讨论)。

声明函数指针

回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子:

void f();// 函数原型

上面的语句声明了一个函数,没有输入参数并返回void。那么函数指针的声明方法如下:

void () ();

让我们来分析一下,左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和由边圆括弧中的入口参数(本例中参数是空)。注意本例中还没有创建指针变量-只是声明了变量类型。目前可以用这个变量类型来创建类型定义名及用sizeof表达式获得函数指针的大小:

// 获得函数指针的大小

unsigned psize = sizeof (void () ());

// 为函数指针声明类型定义

typedef void (pfv) ();

pfv是一个函数指针的自定义类型,它指向的函数没有输入参数,返回类行为void。使用这个类型定义名可以隐藏复杂的函数指针语法。

指针变量应该有一个变量名:

void (p) (); //p是指向某函数的指针

p是指向某函数的指针,该函数无输入参数,返回值的类型为void。左边圆括弧里星号后的就是指针变量名。有了指针变量便可以赋值,值的内容是署名匹配的函数名和返回类型。例如:

void func()

{

/ do something /

}

p = func;

p的赋值可以不同,但一定要是函数的地址,并且署名和返回类型相同。

传递回调函数的地址给调用者

现在可以将p传递给另一个函数(调用者)- caller(),它将调用p指向的函数,而此函数名是未知的:

void caller(void(ptr)())

{

ptr(); / 调用ptr指向的函数 /

}

void func();

int main()

{

p = func;

caller(p); / 传递函数地址到调用者 /

}

如果赋了不同的值给p(不同函数地址),那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

调用规范

到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

// 被调用函数是以int为参数,以int为返回值

__stdcall int callee(int);

// 调用函数以函数指针为参数

void caller( __cdecl int(ptr)(int));

// 在p中企图存储被调用函数地址的非法 *** 作

__cdecl int(p)(int) = callee; // 出错

指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列。

第一个问题,指针数组如何初始化:char pStr[10] = { NULL};即可,数组的初始化都是放在括号中的,指针数组也是这样进行初始化。

第二个问题,将字符串赋给字符指针:pStr[0] = str或者pStr = str

第三个问题,计算类的长度:类在定义的过程中是不会非配内存的,定义只是提供一种分配内存的方案,在声明对象的时候才会分配内存,而对象的内存分成了三个部分:每个对象各个拥有的数据成员变量内存,所有同一类对象拥有的函数内存,所有同一对象拥有的静态成员变量内存。在计算对象的长度时可能需要同时考虑这三方面的内存。

1、全局静态变量、局部静态变量

2、静态函数。只能在 本源文件 中使用

3、c++中,static关键字可以定义 类 中的静态成员变量。类中static静态数据成员拥有一块独立的单独存储区,而不管创建了多少个该类的对象。这些对象共享这一块静态存储区。

4、c++中,static关键字可以定义 类 中的静态成员函数。静态成员函数也是类的一部分,而不是对象的一部分。这些对象共享这一块静态存储区

1、数组是用来存储多个相同类型的集合。数组名是首元素的地址。

2、指针相当一个变量,它存放的是其他变量在内存中地址。指针名只想内存的首地址。

区别:

1、赋值

2、存储方式:

数组:连续存放、连续内存。不是在静态区就是在栈上。

指针:灵活、内存空间不确定。

3、初始化

4、指针 *** 作

数组名的指针 *** 作:

定义:指向函数的指针。

应用:回调

c语言:全局或者静态变量,初始化发生在任何代码执行之前,属于编译期初始化。

c++:全局或者静态对象当且仅当对象首次用到时才进行构造。

解析:

作用域 :C++里作用域可分为6种: 全局 , 局部 , 类,语句 , 命名空间 和 文件作用域 。

静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。

静态局部变量 :局部作用域,只被初始化一次,直到程序结束。

静态局部变量 :局部作用域,只被初始化一次,直到程序结束

生命周期 :静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。类静态成员变量在静态存储区,当超出类作用域时回收内存。

答:能。

原因: 因为在 编译时对象 就绑定了 函数地址 ,和指针空不空没关系。

定义 : 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

产生原因 : 释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。这些我们都要注意避免。

避免办法:

1、初始化置为NULL;

2、申请内存判空;

3、指针释放后置空;

4、使用智能指针;

1、首先从作用域考虑 :C++里作用域可分为6种: 全局 , 局部 , 类 , 语句 , 命名空间 和 文件 作用域。

全局变量 :全局作用域,可以通过 extern 作用于其他非 定义的源文件

静态全局变量 :全局作用域+文件作用域,所以 无法在其他 文件中使用。

局部变量 :局部作用域,比如函数的参数,函数内的局部变量等等。

静态局部变量 :局部作用域,只被 初始化一次,直到程序结束

2、从所在空间考虑 :除了 局部变量在栈上 外,其他都在静态存储区。因为静态变量都在静态存储区,所以下次调用函数的时候还是能取到原来的值。

3、生命周期 : 局部变量在栈上,出了作用域就回收内存 ;而全局变量、静态全局变量、静态局部变量都在静 态存储区,直到程序结束才会回收内存。

区别:

1、宏不是函数 ,省去压栈退栈过程,提高效率。 内联函数本质是一个函数 , 并且内联函数本身不能直接调用自身。

2、 宏函数 是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换。 而内联函数 则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率。

3、 宏定义 是没有类型检查的,无论对还是错都是直接替换; 而内联函数 在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等。

1、赋值顺序不同 :++ i 是先加后赋值;i ++ 是先赋值后加;++i和i++都是分两步完成的。

2、效率不同 :后置++执行速度比前置的慢

3、 i++ 不能作为左值,而++i 可以

4、 两者都不是原子 *** 作

《面向对象程序设计》第02章在线测试

第一题、单项选择题(每题1分,5道题共5分)

1、假定AB为一个类,则执行 “AB r1=r2;”语句时将自动调用该类的( D)。

A、无参构造函数 B、带参构造函数

C、赋值重载函数 D、拷贝构造函数

2、一段程序的定义如下,在函数f()中将动态对象的成员n的值改为34的语句应该为 (C ) class A { int n; public: setn(int nl) {n=nl;} A(int x) {n = x; } } int f() {A ptr = new A(45); }

A、An=34; B、ptrsetn(34);

C、ptr->setn(34); D、setn(34);

3、假定AB为一个类,则执行“AB p=new AB(1,2);”语句时共调用该类构造函数的次数为(B )。

A、0 B、1

C、2 D、3

4、假定AB为一个类,px为指向该类的一个含有n个对象的动态数组的指针,则执行“delete []px;”语句时共调用该类析构函数的次数为(C )。

A、0 B、1

C、n D、n+1

5、假定AA是一个类,abc是该类的一个成员函数,则参数表中隐含的第一个参数为( B)。

A、abc B、 this

C、this D、this&

第二题、多项选择题(每题2分,5道题共10分)

1、定义析构函数时,错误的说法是 (BD) 。

A、其名与类名完全相同

B、返回类型是 void 类型

C、无形参,也不可重载

D、函数体中必须有 delete 语句

2、假定AA为一个类,a为该类私有的数据成员,GetValue()为该类公有函数成员,它返回a的值,x为该类的一个对象,则访问x对象中数据成员a的语句错误的是(ABC)。

A、xa

B、xa()

C、x->GetValue()

D、xGetValue()

3、下面关于构造函数的说法正确的是(AB)

A、构造函数的函数名与类名相同

B、构造函数可以重载

C、构造函数不可以设置默认参数

D、构造函数必须指定类型说明

4、下面关于成员函数的说法错误的是(ACD)

A、类定义中只能说明函数成员的函数头,不能定义函数体

B、类中的函数成员可以在类体中定义,也可以在类体之外定义

C、类中的函数成员在类体之外定义时必须要与类声明在同一文件中

D、在类体之外定义的函数成员不能 *** 作该类的私有数据成员

5、假定AB为一个类,则执行 “AB s=new AB(a,5);”语句的结果是(BCD)

A、动态生成对象AB赋值给变量s

B、动态生成对象s

C、如果对象AB中有公有成员a,则s->a可以访问该成员

D、动态生成对象AB,该对象的地址赋值给变量s

第三题、判断题(每题1分,5道题共5分)

1、 若Sample类中的一个成员函数说明如下: Void set(Sample &a) 其中Sample &a表示的含义是a是类Sample的对象引用,用来作函数Set()的形参。 T

正确 错误

2、 假定AA为一个类,a为该类公有的数据成员,x为该类的一个对象,则访问x对象中数据成员a的格式为x->a。 F

正确 错误

3、 静态成员函数允许访问类的数据成员。T

正确 错误

4、 建立类的对象时,先调用各个对象成员的构造函数,初始化相应的对象成员,然后才执行类的构造函数,初始化类中其他成员。T

正确 错误

5、 this是一个隐含于每一个类的成员函数的特殊指针,它是一个指向正在被某个成员函数 *** 作的对象的指针。T

正确 错误

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/12189174.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-21
下一篇2023-05-21

发表评论

登录后才能评论

评论列表(0条)

    保存