函数与指针(接录总结)

来源:互联网 发布:大学数学解题软件 编辑:程序博客网 时间:2024/05/14 16:59

<!-- /* Font Definitions */ @font-face{font-family:宋体;panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-alt:SimSun;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 135135232 16 0 262145 0;}@font-face{font-family:华文楷体;panose-1:2 1 6 0 4 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:647 135200768 16 0 262303 0;}@font-face{font-family:Cambria;panose-1:2 4 5 3 5 4 6 3 2 4;mso-font-charset:0;mso-generic-font-family:roman;mso-font-pitch:variable;mso-font-signature:-1610611985 1073741899 0 0 159 0;}@font-face{font-family:"/@华文楷体";panose-1:2 1 6 0 4 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:647 135200768 16 0 262303 0;}@font-face{font-family:"/@宋体";panose-1:2 1 6 0 3 1 1 1 1 1;mso-font-charset:134;mso-generic-font-family:auto;mso-font-pitch:variable;mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal{mso-style-parent:"";margin:0cm;margin-bottom:.0001pt;mso-pagination:widow-orphan;font-size:10.0pt;font-family:Cambria;mso-fareast-font-family:华文楷体;mso-bidi-font-family:宋体;}a:link, span.MsoHyperlink{color:blue;text-decoration:underline;text-underline:single;}a:visited, span.MsoHyperlinkFollowed{color:purple;text-decoration:underline;text-underline:single;}@page Section1{size:612.0pt 792.0pt;margin:72.0pt 90.0pt 72.0pt 90.0pt;mso-header-margin:35.4pt;mso-footer-margin:35.4pt;mso-paper-source:0;}div.Section1{page:Section1;}-->

http://www.pconline.com.cn/pcedu/empolder/gj/c/0503/566020.html

函数存放在内存的代码区域内,它们同样有地址,我们如何能获得函数的地址呢?

如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。

int (*fp)(int a);//这里就定义了一个指向函数的指针;

cout<<test<<endl;//显示函数地址

int (*fp)(int a);  fp=test;//将函数test的地址赋给函数学指针fp

typedef定义可以简化函数指针的定义,在定义一个的时候感觉不出来,但定义多了就知道方便了;

typedef int (*fp)(int a);//注意,这里不是生命函数指针,而是定义一个函数指针的类型,这个类型是自己定义的,类型名为fp 
    fp fpi;//这里利用自己定义的类型名fp定义了一个fpi的函数指针
    fpi=test; 

变量如整型的定义如果在下面,可以先extern声明再引用;但struct却不行,报错为localclasses cannot be used to declare 'extern' variables,为什么??可能和extern的用法有关……

定义struct结构体:struct 结构体名{

                                    包含的成员;

                                     ... } 变量名;

//这里的结构体名如果后面不用的话,可以省略;如果暂时不急定义变量来使用的话,那么这里的变量名也可以省略;其他的一个都不能少。※注意:结构体定义是不可以递归的;

注意在调用成员的时候格式为两种:
1:tm->min;//最常用的
2:(*tm).min;//很少用了

typedef struct tagNode

{

 char *pItem;

 pNode pNext;

} *pNode; //编译错误,因为:新结构建立的过程中遇到了pNext域的声明,类型是pNode,要知道pNode表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识pNode

解决方法:

 

  1)typedef struct tagNode {

                   char *pItem;

                   struct tagNode *pNext; }*pNode;

 

  2)typedef struct tagNode *pNode;

              structtagNode{

                  char *pItem;

                   pNode pNext; }; //此处用typedef给一个还未完全声明的类型起新名字。C语言编译器支持这种做法

 

        3)、规范做法:struct tagNode{

                                   char *pItem;

                                 struct tagNode *pNext;};

                              typedef struct tagNode*pNode;

 

typedef 原类型名   新类型名; //功能:将原类型名表示的数据类型用新类型名代表;

例如:typedef char string[12]; //string text; 表明text为含12个字符的数组;

int f(int &a,int&b){return a+b;}

int(*p)(int &,int&);

 

这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)

类型

  对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

  Char

  偏移量必须为sizeof(char)1的倍数

  int

  偏移量必须为sizeof(int)4的倍数

  float

  偏移量必须为sizeof(float)4的倍数

  double

  偏移量必须为sizeof(double)8的倍数

  Short

  偏移量必须为sizeof(short)2的倍数

但是,还有更奇怪的,

struct MyStruct{

                charc3;

                doubled;

                charc2;

                int i;

                char c;

}s;这是32字节;

struct MyStruct{

                charc3;

                char c;

                int i;

                doubled;

                charc2;

}s;这是24字节;

struct MyStruct

  {

  char dda;//偏移量为0,满足对齐方式,dda占用1个字节;

  double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8

  //的倍数,需要补足7个字节才能使偏移量变为8(满足对齐

  //方式),因此VC自动填充7个字节,dda1存放在偏移量为8

  //的地址上,它占用8个字节。

  int type//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍

  //数,满足int的对齐方式,所以不需要VC自动填充,type

  //放在偏移量为16的地址上,它占用4个字节。

  }//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构

  //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof

  //(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为

  //sizeof(double)=8的倍数。

  所以该结构总的大小为:sizeof(MyStruc)1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。

n字节的对齐方式

  VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

  VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

  否则必须为n的倍数。下面举例说明其用法。

  #pragma pack(push) //保存对齐状态

  #pragma pack(4)//设定为4字节对齐

  struct test

  {

  char m1;

  double m4;

  int m3;

  };

  #pragma pack(pop)//恢复对齐状态

  以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n,m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)

 

虚拟内存空间就是虚拟地址空间。在32位操作系统中,应用被分配到4GB的属于自己的虚拟地址空间(2GB给应用,2GB给操作系统)。因此每个进程都有2G的虚拟内存空间,空间大小只是寻址能力范围。

 

http://blog.163.com/liuyunfeng484/blog/static/66831715200971101314369/

问题:声明与函数

 

  有一段程序存储在起始地址为 0的一段内存上,如果我们想要调用这段程序,请问该如何去做?

 

  答案

 

  答案是 (*(void (*)( ) )0)( )。看起来确实令人头大,那好,让我们知难而上,从两个不同的途径来详细分析这个问题。

 

  答案分析:从尾到头

 

  首先,最基本的函数声明: void function(paramList);

 

  最基本的函数调用: function(paramList);

 

  鉴于问题中的函数没有参数,函数调用可简化为function();

 

   其次,根据问题描述,可以知道 0是这个函数的入口地址,也就是说,0是一个函数的指针。使用函数指针的函数声明形式是:void (*pFunction)(),相应的调用形式是: (*pFunction)(),则问题中的函数调用可以写作:(*0)()

 

  第三,大家知道,函数指针变量不能是一个常数,因此上式中的 0必须要被转化为函数指针。

 

  我们先来研究一下,对于使用函数指针的函数:比如 void(*pFunction)( ),函数指针变量的原型是什么?这个问题很简单,pFunction函数指针原型是( void (*)( ) ),即去掉变量名,清晰起见,整个加上()号。

 

  所以将 0强制转换为一个返回值为void,参数为空的函数指针如下:(void (*)( ) )

 

   OK,结合2)3)的分析,结果出来了,那就是:(*(void (*)( ) )0)( )

 

  答案分析:从头到尾理解答案

 

   (void (*)( )) ,是一个返回值为void,参数为空的函数指针原型。

   (void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0.

   *(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字

   (*(void (*)( ))0)( ),这当然就是一个函数了。

 

  我们可以使用 typedef清晰声明如下:

 

   typedef void (*pFun)( );

 

这样定义之后,pFun就是一个返回类型为void无参数的函数指针变量了。

 

  这样函数变为 (*(pFun)0 )( );

 

问题:三个声明的分析

 

  对声明进行分析,最根本的方法还是类比替换法,从那些最基本的声明上进行类比,简化,从而进行理解,下面通过分析三个例子,来具体阐述如何使用这种方法。

 

1int* (*a[5])(int, char*);

 

   首先看到标识符名 a"[]"优先级大于"*"a"[5]"先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向"(int,char*)",很明显,指向的是一个函数,这个函数参数是"int,char*",返回值是"int*"OK,结束了一个。:)

 

2void (*b[10]) (void (*)());

 

   b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是"void(*)()"【注10】,返回值是"void"。完毕!

 

  注意:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是"void"

 

3. doube(*)() (*pa)[9];

 

   pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是"doube(*)()"(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是"double")。

 

函数在内存中有一个物理位置,而这个位置是可以赋给一个指针的。一零点函数的地址就是该函数的入口点。因此,函数指针可被用来调用一个函数。函数的地址是用不带任何括号或参数的函数名来得到的。(这很类似于数组地址的得到方法,即,在只有数组名而无下标是就得到数组地址。)

 

怎样说明一个函数指针变量呢 ?

为了说明一个变量 fn_pointer 的类型是"返回值为 int 的函数指针", 你可以使用下面的说明语句:

int (*fn_pointer) ();

为了让编译器能正确地解释这句语句, *fn_pointer 必须用括号围起来。若漏了这对括号, :

int *fn_pointer ();

的意思完全不同了。fn_pointer 将是一个函数名, 其返回值为 int 类型的指针。

 

在C语言中规定,一个函数总是占用一段连续的内存区,  而函数名就是该函数所占内存区的首地址。  我们可以把函数的这个首地址( 或称入口地址 ) 赋予一个指针变量,  使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。  我们把这种指向函数的指针变量称为" 函数指针变量 "

函数指针变量定义的一般形式为:

类型说明符 (* 指针变量名 )();

其中 " 类型说明符 " 表示被指函数的返回值的类型。"(* 指针变量名 )" 表示 "*" 后面的变量是定义的指针变量。  最后的空括号表示指针变量所指的是一个函数。

例如: int (*pf)();

表示 pf 是一个指向函数入口的指针变量,该函数的返回值 ( 函数值 ) 是整型。

 

从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:

 

1. 先定义函数指针变量,如后一程序中第 9 int (*pmax)(); 定义 pmax 为函数指针变量。

 

2. 把被调函数的入口地址 ( 函数名 ) 赋予该函数指针变量,如程序中第 11 pmax=max;

 

3. 用函数指针变量形式调用函数,如程序第 14 z=(*pmax)(x,y);  调用函数的一般形式为:(* 指针变量名 ) ( 实参表 ) 使用函数指针变量还应注意以下两点:

 

a. 函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。

 

b. 函数调用中 "(* 指针变量名 )" 的两边的括号不可少,其中的* 不应该理解为求值运算,在此处它只是一种表示符号。

 

C语言中函数是一种function-to-pointer的方式,即对于一个函数,会将其自动转换成指针的类型.

&fun, fun, *fun这三个值的结果是一样的.其实对于最后的那个*fun, 即使前面加上很多个*, 其结果也不变, **fun, ***fun的结果都是一样的. 对于这个问题, 因为之前讲过函数是一种
function-to-pointer方式, 其会自动转换成指针的类型, &fun是该函数的地址, 为指针类型, fun是一个函数, 会转换成其指针类型, 而对于*fun, 由于fun已经变成了指针类型, 指向这个函数, 所以*fun就是取这个地址的函数, 而又根据function-to-pointer,该函数也转变成了一个指针, 所以以此类推, 这三个值的结果是相同的.

int (*p[3])(int, int);

p[0] = max;

int (*p)();
这是一个函数指针, p所指向的函数是一个不带任何参数, 并且返回值为int的一个函数.
int (*fun())();
这个式子与上面式子的区别在于用fun()代替了p,fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个不带任何参数,并且返回值为int的一个函数.

void (*signal(int signo, void (*fun)(int)))(int);

就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针的函数), 而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.

#include<signal.h>
#include<stdlib.h>
#include<stdio.h>

void sig_fun2(int signo)
{
        printf("in sig_fun2:%d/n",signo);
}

void sig_fun1(int signo)
{
        printf("in sig_fun1:%d/n",signo);
}

int main()
{
        unsigned long i;
        if (signal(SIGUSR1, sig_fun1) == SIG_ERR)
        {
                printf("signalfun1 error/n");
                exit(1);
        }

        (signal(SIGUSR1, sig_fun2))(30);

        printf("done/n");
        return 0;
}