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

可以把一个指针声明成为一个指向函数的指针。
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. 【mysql】Innodb三大特性之insert buffer

    一.什么是insert buffer insert buffer是一种特殊的数据结构(B+ tree)并不是缓存的一部分,而是物理页,当受影响的索引页不在buffer pool时缓存 secondar ...

  2. logback+slf4j作为日志系统

    一.logback简介 log4j和logback作者是同一人:CekiGülcü.log4j和logback都是实打实的日志系统. commons-logging,slf4j这两者是日志大管家.sl ...

  3. OGG_GoldenGate数据库配置DDL同步(案例)

    2014-03-08 Created By BaoXinjian

  4. Linux中断 - IRQ number和中断描述符

    一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述 ...

  5. C#方法参数--值参数,引用参数,输出参数

    值参数: 使用值参数,通过复制实参的值到形参的方式,把数据传递到方法,方法被调用的时候,系统做如下操作: 在栈中为形参分配空间: 复制实参到形参. 注意:一个值参数的实参不一定是变量,它可以是任何能够 ...

  6. kafka linux 启动脚本 sample

    #!/bin/sh # # chkconfig: 345 99 01 # description: Kafka # # File : Kafka # # Description: Starts and ...

  7. java与数据库交互常用到的一些方法

    下面我整理了一下java中常用的几个与数据库交互的常用方法,仅供参考: 1.执行SQL(dao层的实现类中) (1)SQL查询: //import org.hibernate.Query;//impo ...

  8. redis实践:用户注册登录功能

    本节将使用PHP和Redis实现用户注册登录功能,下面分模块来介绍具体实现方法. 1.注册 需求描述:用户注册时需要提交邮箱.登录密码和昵称.其中邮箱是用户的唯一标识,每个用户的邮箱不能重复,但允许用 ...

  9. spring cloud中通过配置文件自定义Ribbon负载均衡策略

    一.Ribbon中的负载均衡策略 1.Ribbon中支持的负载均衡策略 AvailabilityFilteringRule:过滤掉那些因为一直连接失败的被标记为circuit tripped的后端se ...

  10. SQL SERVER树形结构数据——批量删除分组数据

    定义函数获取某结点下所有子结点: CREATE FUNCTION [dbo].[fn_GetSubGroupInfoById] ( @id AS INT --某分组Id ) RETURNS @SubG ...