第七章。指针和函数的关系

可以把一个指针声明成为一个指向函数的指针。
intfun1(char*,int);
int(*pfun1)(char*,int);
pfun1=fun1;
....
....
inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。
可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
例十三:
intfun(char*);
inta;
charstr[]="abcdefghijklmn";
a=fun(str);
...
...
intfun(char*s)
{
intnum=0;
for(inti=0;i{
num+=*s;s++;
}
returnnum;
}
这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味着同时对str进行了自加1运算。
第八章。指针类型转换

当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
例十四:
1。floatf=12.3;
2。float*fptr=&f;
3。int*p;
在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?
p=&f;
不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
p=(int*)&f;

如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*TYPE,
那么语法格式是:
(TYPE*)p;
这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。
一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。
例十五:
voidfun(char*);
inta=125,b;
fun((char*)&a);
...
...
voidfun(char*s)
{
charc;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}
}
注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针char*temp, 然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。
我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:
unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=20345686;
ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制
)
ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制)
编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:
unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=某个数,这个数必须代表一个合法的地址;
ptr=(TYPE*)a;//呵呵,这就可以了。
严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYP
E*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。
想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完 全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
例十六:
inta=123,b;
int*ptr=&a;
char*str;
b=(int)ptr;//把指针ptr的值当作一个整数取出来。
str=(char*)b;//把这个整数的值当作一个地址赋给指针str。
好了,现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。

C++中的指针(二) 函数指针

先说一下C式的函数指针。这种函数指针的应用十分广泛。
对于任何函数 void print(string s),它的指针这样定义:
void (*pfun)(string) = NULL;
pfun= &print;
或者 pfun = print;两种写法没有区别。

pfun是指针变量名。可以指向任何只带一个string参数,返回void的函数。这里让它指向print()函数。
以后调用它的时候直接写
if (pfun)
   pfun("Hello world");
C++编译器会通过pfun找到print函数,然后call  print("Hello world");
一个简单应用是可以作菜单操作。例如在文本模式下的界面,让用户选择如下操作:
"0.print, 1.copy, 2.delete, 3. quit, 4.help"
那么可以写5个函数:
void print();
void copy();
void delete();
void quit();
void help();
然后用一个函数指针数组把他们存在一起:
void (*p[])() = {print, copy, delete, quit, help};
然后根据用户入0,1,2,3,4来直接叫函数
cin >> index;
p[index]();

在windows环境下编译这种函数指针被认为是用C/C++呼叫规则(C/C++ calling convention)。就是呼叫函数caller清理函数呼叫时生成的stack。另一种规则叫标准呼叫规则(standard calling convention)。由被叫函数callee清理自己的stack。二者一般情况下区别不大,但standard calling convention更合理,因为这样使函数size变小了一点。
实际上写C/C++函数指针的时候省略了 __cdecl 前缀。 应该写成void (__decel *p[])();
而标准规范用 __stdcall前缀。 也可以用宏CALLBACK,这也就是著名的回调函数了。

使用CALLBACK的另一个好处就是呼叫函数(caller)不需要具体关心被叫函数(callee)是什么而直接呼叫。例如我们要写一个排序函数。可以用各种不同算法。如冒泡法。
void CALLBACK BubbleSort(int *pStart, int *pEnd);
也可以用quick sort
void CALLBACK QuickSort(int *pStart, int *pEnd);

那么呼叫方只需要定义一个指向这种格式的函数指针:
void (CALLBACK *p)(int*, int*),然后让p指向想用的函数就可以了。
这里只对int类型排序,实际上这种排序函数可以再叫一个CALLBACK函数来决定排序规则。以使算法可以应用到各种不同类型的变量以及不同的排序规则中。在各算法书上都有介绍。如果大家有兴趣,我可以写一下这个排序函数。

另一个典型的例子是MFC中Timer使用的CALLBACK函数,每当Timer Exprie的时候会去叫这个函数,根据返回值决定下一个动作。

C++中的函数指针与C的不同
class C
{
public:
   bool test();
}
这里指向print的指针不是bool *p(),而是bool (C::*p)();
呼叫这个函数的时候这样写:
C c, *pc=&c;
bool (C::*p)() = &C::test;

c.*p();
或者 pc->*p();

赋值那行bool (C::*p)() = &C::test;在VS2003里右边可以省去 C::,到了VS2005语法更严格了,被禁止了。这里的成员函数指针对非静态函数有效。静态函数不依赖于任何object,它的表示方法和C的一样。
对于非静态成员函数的指针的继承关系是这样的:upcast合法,downcast不合法。这样的到的指针永远是安全的。

非静态成员函数指针在实际程序中的应用很多。一个典型的例子是用来写state machine(状态机器?)。例如程序在控制一个机器人的初始化阶段。整个初始化需要三个函数:1。初始化机器人的身子,2。初始化机器的左手,3。初始化机器人的右手。这样我们在state machine中用两个成员函数指针分别指向当前的状态和下一个状态 bool (CStateMachine::*m_pCurrentState), bool (CStateMachine::*m_pNextState)。。
一开始永远叫Start()
CStateMachine::CStateMachine
{
   m_pCurrentState = CStateMachine::Start;
}
然后在每一个State里面管理状态变化:
bool CStateMachine::Start()
{
   .....
   m_pNextState = CStateMachine::InitializeLeftHand();
}

bool CStateMachine::InitializeLeftHand()
{
   ....
   m_pNextState = CStateMachine::InitializeRightHand();
}

bool CStateMachine::InitializeRightHand()
{
   ....
   m_pNextState = NULL;
}

这样很清晰的标志了整个初始化的过程。当然这个过程也可以用很土的程序实现,设一个flag,然后把flag于函数一一对应。但那样作出来的程序不易懂,同时增加新状态的时候不好维护。

对于CStateMachine的核心部份可以这样控制:对于任何一步操作,如果函数返回true表示成功,执行下一步
(this->*(m_pCurrentState = m_pNextState))()。如果失败则报错,同时让用户选择重试(Retry)还是放弃(Abort)还是忽略(Ignor)。
如果Abort则结束StateMachine,
如果Retry则再次叫当前函数this->*m_pCurrentState();
如果Ignor则忽略当前错误继续下一步。this->*(m_pCurrentState = m_pNextState)();
当没有下一个状态的时候StateMachine结束。 (m_pNextState == NULL)
这是标准工业中的用法,大家不妨看一看,写成一个标准的class。这将是个很有用的练习。

pionter指针小结的更多相关文章

  1. C语言 指针小结

    指针 -->指针变量 类型名 *变量名 int *point1; char *point2; 注意:*p可以直接使用,它代表指针p指向的变量,*p可以当做被指向的变量使用!~~~~ 一个变量的地 ...

  2. C51指针小结

    一. 指针变量的定义 指针变量定义与一般变量的定义类似,其形式如下: 数据类型 [存储器类型1] * [存储器类型2] 标识符: [存储器类型1] 表示被定义为基于存储器的指针.无此选项时,被定义为一 ...

  3. C和指针小结(C/C++程序设计)

    C和指针 相关基础知识:内存的分配(谭浩强版) 1.整型变量的地址与浮点型/字符型变量的地址区别?(整型变量/浮点型变量的区别是什么) 2.int *p,指向整型数据的指针变量. 3.通过指针变量访问 ...

  4. C语言指针-小结

    1) 指针变量可以进行加减运算,但是指针变量的加减运算并不是加上或减去一个数,而是跟指针指向的数据类型有关,数据类型在系统中占了多少个字节,指针+1后就向后移动了多少个字节. 2) int *poin ...

  5. c++ 常量指针

    一.指向常量的指针 定义形式: const 类型 * 指针名; 不能通过指针修改地址里的值. int i=0x123; const int *p=&i; *p=; //错误 //前置const ...

  6. C学习笔记-指针

    指针的概念 指针也是一个变量,指针变量的值是另一个变量的地址 换句话说就是,指针存放的是一个内存地址,该地址指向另一块内存空间 指针变量的定义 指向一个变量的变量 int *p = NULL; p = ...

  7. C++—复合类型

    内容概要: -创建和使用数组 -创建和使用C-风格字符串 -创建和使用string类字符串 -使用方法getline()和get()读取字符串 -混合输入字符串和数字 -创建和使用结构 -创建和使用共 ...

  8. c语言基础学习07

    ============================================================================= 涉及到的知识点有: 1.指针.指针的概念.指 ...

  9. C++ Primer Plus (Stephen Prata 著)

    第1章 预备知识 (已看) 第2章 开始学习C++ (已看) 第3章 处理数据 (已看) 第4章 复合类型 (已看) 第5章 循环和关系表达式 (已看) 第6章 分支语句和逻辑运算符 (已看) 第7章 ...

随机推荐

  1. 升级openssh基于openssl

    OpenSSH is the premier connectivity tool for remote login with the SSH protocol. It encrypts all tra ...

  2. yum插件yum-fastestmirror

    yum多个mirror自动选择速度最快的mirror,yum-fastestmirror插件,它会自动选择最快的mirror 配置文件: /etc/yum/pluginconf.d/fastestmi ...

  3. 【TP框架】包括TP3.1和3.2,自带缓存使用机制

    原文章出处: http://blog.163.com/liwei1987821@126/blog/static/172664928201422133218356/ 写在开始:缓存变量和session变 ...

  4. HDUOJ -----1686

    Oulipo Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Subm ...

  5. Foundations of Machine Learning: Boosting

    Foundations of Machine Learning: Boosting Boosting是属于自适应基函数(Adaptive basis-function Model(ABM))中的一种模 ...

  6. 谷歌地图地理解析和反解析geocode.geocoder详解(转)

    谷歌地图地理解析和反解析geocode.geocoder详解 谷歌Geocoder服务 实例代码 地址解析就是将地址(如:贵州省贵阳市)转换为地理坐标(如经度:106.71,纬度:26.57)的过程. ...

  7. Java Comparator和Comparabler的区别

    一. Comparable public class Person implements Comparable<Person>{ public int Age; public String ...

  8. iOS CPU占有率达到了100%甚至更多,然后导致App闪退

    今天在真机调试的过程中,发现了一个严重的问题,发现CPU的使用率竟然达到了100%,以至于会导致运行内存占用过高,被系统的看门狗机制给杀掉. 下面就讲一讲怎么去定位这个问题: 1.打开Xcode,把项 ...

  9. python学习笔记—— 多进程中的 孤儿进程和僵尸进程

    1 基本概述 1.1 孤儿进程和僵尸进程 父进程创建子进程后,较为理想状态是子进程结束,父进程回收子进程并释放子进程占有的资源:而实际上,父子进程是异步过程,两者谁先结束是无顺的,一般可以通过父进程调 ...

  10. yield与send实现协程操作

    yield与send实现协程操作 之前我们说过,在函数内部含有yield语句即称为生成器. 下面,我们来看看在函数内部含有yield语句达到的效果.首先,我们来看看以下代码: def foo(): w ...